budibase/packages/builder/src/stores/backend/tables.js

226 lines
5.8 KiB
JavaScript

import { get, writable, derived } from "svelte/store"
import { cloneDeep } from "lodash/fp"
import { API } from "api"
import { SWITCHABLE_TYPES, FIELDS } from "constants/backend"
export function createTablesStore() {
const store = writable({
list: [],
selectedTableId: null,
})
const derivedStore = derived(store, $store => ({
...$store,
selected: $store.list?.find(table => table._id === $store.selectedTableId),
}))
const fetch = async () => {
const tables = await API.getTables()
store.update(state => ({
...state,
list: tables,
}))
}
const singleFetch = async tableId => {
const table = await API.getTable(tableId)
store.update(state => {
const list = []
// update the list, keep order accurate
for (let tbl of state.list) {
if (table._id === tbl._id) {
list.push(table)
} else {
list.push(tbl)
}
}
state.list = list
return state
})
}
const select = tableId => {
store.update(state => ({
...state,
selectedTableId: tableId,
}))
}
const save = async table => {
const updatedTable = cloneDeep(table)
const oldTable = get(store).list.filter(t => t._id === table._id)[0]
const fieldNames = []
// Update any renamed schema keys to reflect their names
for (let key of Object.keys(updatedTable.schema)) {
// If field name has been seen before remove it
if (fieldNames.indexOf(key.toLowerCase()) !== -1) {
delete updatedTable.schema[key]
continue
}
const field = updatedTable.schema[key]
const oldField = oldTable?.schema[key]
// If the type has changed then revert back to the old field
if (
oldField != null &&
oldField?.type !== field.type &&
SWITCHABLE_TYPES.indexOf(oldField?.type) === -1
) {
updatedTable.schema[key] = oldField
}
// Field has been renamed
if (field.name && field.name !== key) {
updatedTable.schema[field.name] = field
updatedTable._rename = { old: key, updated: field.name }
delete updatedTable.schema[key]
}
// Finally record this field has been used
fieldNames.push(key.toLowerCase())
}
const savedTable = await API.saveTable(updatedTable)
replaceTable(savedTable._id, savedTable)
select(savedTable._id)
// make sure tables up to date (related)
let newTableIds = []
for (let column of Object.values(updatedTable?.schema || {})) {
if (column.type === FIELDS.LINK.type) {
newTableIds.push(column.tableId)
}
}
let oldTableIds = []
for (let column of Object.values(oldTable?.schema || {})) {
if (column.type === FIELDS.LINK.type) {
oldTableIds.push(column.tableId)
}
}
const tableIdsToFetch = [...new Set([...newTableIds, ...oldTableIds])]
// too many tables to fetch, just get all
if (tableIdsToFetch.length > 3) {
await fetch()
} else {
await Promise.all(tableIdsToFetch.map(id => singleFetch(id)))
}
return savedTable
}
const deleteTable = async table => {
if (!table?._id) {
return
}
await API.deleteTable({
tableId: table._id,
tableRev: table._rev || "rev",
})
replaceTable(table._id, null)
}
const saveField = async ({
originalName,
field,
primaryDisplay = false,
indexes,
}) => {
let draft = cloneDeep(get(derivedStore).selected)
// delete the original if renaming
// need to handle if the column had no name, empty string
if (originalName != null && originalName !== field.name) {
delete draft.schema[originalName]
draft._rename = {
old: originalName,
updated: field.name,
}
}
// Optionally set display column
if (primaryDisplay) {
draft.primaryDisplay = field.name
} else if (draft.primaryDisplay === originalName) {
const fields = Object.keys(draft.schema)
// pick another display column randomly if unselecting
draft.primaryDisplay = fields.filter(
name => name !== originalName || name !== field
)[0]
}
if (indexes) {
draft.indexes = indexes
}
draft.schema = {
...draft.schema,
[field.name]: cloneDeep(field),
}
await save(draft)
}
const deleteField = async field => {
let draft = cloneDeep(get(derivedStore).selected)
delete draft.schema[field.name]
await save(draft)
}
// Handles external updates of tables
const replaceTable = (tableId, table) => {
if (!tableId) {
return
}
// Handle deletion
if (!table) {
store.update(state => ({
...state,
list: state.list.filter(x => x._id !== tableId),
}))
return
}
// Add new table
const index = get(store).list.findIndex(x => x._id === table._id)
if (index === -1) {
store.update(state => ({
...state,
list: [...state.list, table],
}))
}
// Update existing table
else if (table) {
// This function has to merge state as there discrepancies with the table
// API endpoints. The table list endpoint and get table endpoint use the
// "type" property to mean different things.
store.update(state => {
state.list[index] = {
...table,
type: state.list[index].type,
}
return state
})
}
}
const removeDatasourceTables = datasourceId => {
store.update(state => ({
...state,
list: state.list.filter(table => table.sourceId !== datasourceId),
}))
}
return {
...store,
subscribe: derivedStore.subscribe,
fetch,
init: fetch,
select,
save,
delete: deleteTable,
saveField,
deleteField,
replaceTable,
removeDatasourceTables,
}
}
export const tables = createTablesStore()