Automatically refresh data when related data changes
This commit is contained in:
parent
7c3ccf69f9
commit
fe00c66700
|
@ -1,4 +1,4 @@
|
|||
import { notificationStore } from "../store/notification"
|
||||
import { notificationStore, datasourceStore } from "../store"
|
||||
import API from "./api"
|
||||
import { fetchTableDefinition } from "./tables"
|
||||
|
||||
|
@ -6,6 +6,9 @@ import { fetchTableDefinition } from "./tables"
|
|||
* Fetches data about a certain row in a table.
|
||||
*/
|
||||
export const fetchRow = async ({ tableId, rowId }) => {
|
||||
if (!tableId || !rowId) {
|
||||
return
|
||||
}
|
||||
const row = await API.get({
|
||||
url: `/api/${tableId}/rows/${rowId}`,
|
||||
})
|
||||
|
@ -16,6 +19,9 @@ export const fetchRow = async ({ tableId, rowId }) => {
|
|||
* Creates a row in a table.
|
||||
*/
|
||||
export const saveRow = async row => {
|
||||
if (!row?.tableId) {
|
||||
return
|
||||
}
|
||||
const res = await API.post({
|
||||
url: `/api/${row.tableId}/rows`,
|
||||
body: row,
|
||||
|
@ -23,6 +29,10 @@ export const saveRow = async row => {
|
|||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row saved")
|
||||
|
||||
// Refresh related datasources
|
||||
datasourceStore.actions.invalidateDatasource(row.tableId)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -30,6 +40,9 @@ export const saveRow = async row => {
|
|||
* Updates a row in a table.
|
||||
*/
|
||||
export const updateRow = async row => {
|
||||
if (!row?.tableId || !row?._id) {
|
||||
return
|
||||
}
|
||||
const res = await API.patch({
|
||||
url: `/api/${row.tableId}/rows/${row._id}`,
|
||||
body: row,
|
||||
|
@ -37,6 +50,10 @@ export const updateRow = async row => {
|
|||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row updated")
|
||||
|
||||
// Refresh related datasources
|
||||
datasourceStore.actions.invalidateDatasource(row.tableId)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -44,12 +61,19 @@ export const updateRow = async row => {
|
|||
* Deletes a row from a table.
|
||||
*/
|
||||
export const deleteRow = async ({ tableId, rowId, revId }) => {
|
||||
if (!tableId || !rowId || !revId) {
|
||||
return
|
||||
}
|
||||
const res = await API.del({
|
||||
url: `/api/${tableId}/rows/${rowId}/${revId}`,
|
||||
})
|
||||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row deleted")
|
||||
|
||||
// Refresh related datasources
|
||||
datasourceStore.actions.invalidateDatasource(tableId)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -57,6 +81,9 @@ export const deleteRow = async ({ tableId, rowId, revId }) => {
|
|||
* Deletes many rows from a table.
|
||||
*/
|
||||
export const deleteRows = async ({ tableId, rows }) => {
|
||||
if (!tableId || !rows) {
|
||||
return
|
||||
}
|
||||
const res = await API.post({
|
||||
url: `/api/${tableId}/rows`,
|
||||
body: {
|
||||
|
@ -67,6 +94,10 @@ export const deleteRows = async ({ tableId, rows }) => {
|
|||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success(`${rows.length} row(s) deleted`)
|
||||
|
||||
// Refresh related datasources
|
||||
datasourceStore.actions.invalidateDatasource(tableId)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<script>
|
||||
import { getContext, setContext } from "svelte"
|
||||
import { createContextStore } from "../store"
|
||||
import { getContext, setContext, onMount } from "svelte"
|
||||
import { datasourceStore, createContextStore } from "../store"
|
||||
import { ActionTypes } from "../constants"
|
||||
import { generate } from "shortid"
|
||||
|
||||
export let data
|
||||
export let actions
|
||||
|
@ -13,15 +15,40 @@
|
|||
setContext("context", newContext)
|
||||
$: providerKey = key || $component.id
|
||||
|
||||
// Instance ID is unique to each instance of a provider
|
||||
let instanceId
|
||||
|
||||
// Add data context
|
||||
$: data !== undefined && newContext.actions.provideData(providerKey, data)
|
||||
|
||||
// Add actions context
|
||||
$: {
|
||||
actions?.forEach(({ type, callback }) => {
|
||||
if (instanceId) {
|
||||
actions?.forEach(({ type, callback, metadata }) => {
|
||||
newContext.actions.provideAction(providerKey, type, callback)
|
||||
|
||||
// Register any "refresh datasource" actions with a singleton store
|
||||
// so we can easily refresh data at all levels for any datasource
|
||||
if (type === ActionTypes.RefreshDatasource) {
|
||||
const { datasource } = metadata || {}
|
||||
datasourceStore.actions.registerDatasource(
|
||||
datasource,
|
||||
instanceId,
|
||||
callback
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Generate a permanent unique ID for this component and use it to register
|
||||
// any datasource actions
|
||||
instanceId = generate()
|
||||
|
||||
// Unregister all datasource instances when unmounting this provider
|
||||
return () => datasourceStore.actions.unregisterInstance(instanceId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
|
|
@ -4,28 +4,28 @@ export const createContextStore = existingContext => {
|
|||
const store = writable({ ...existingContext })
|
||||
|
||||
// Adds a data context layer to the tree
|
||||
const provideData = (key, data) => {
|
||||
if (!key) {
|
||||
const provideData = (providerId, data) => {
|
||||
if (!providerId) {
|
||||
return
|
||||
}
|
||||
store.update(state => {
|
||||
state[key] = data
|
||||
state[providerId] = data
|
||||
|
||||
// Keep track of the closest component ID so we can later hydrate a "data" prop.
|
||||
// This is only required for legacy bindings that used "data" rather than a
|
||||
// component ID.
|
||||
state.closestComponentId = key
|
||||
state.closestComponentId = providerId
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
// Adds an action context layer to the tree
|
||||
const provideAction = (key, actionType, callback) => {
|
||||
if (!key || !actionType) {
|
||||
const provideAction = (providerId, actionType, callback) => {
|
||||
if (!providerId || !actionType) {
|
||||
return
|
||||
}
|
||||
store.update(state => {
|
||||
state[`${key}_${actionType}`] = callback
|
||||
state[`${providerId}_${actionType}`] = callback
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
|
||||
export const createDatasourceStore = () => {
|
||||
const store = writable([])
|
||||
|
||||
// Registers a new datasource instance
|
||||
const registerDatasource = (datasource, instanceId, refresh) => {
|
||||
if (!datasource || !instanceId || !refresh) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a list of all relevant datasource IDs which would require that
|
||||
// this datasource is refreshed
|
||||
let datasourceIds = []
|
||||
|
||||
// Extract table ID
|
||||
if (datasource.type === "table") {
|
||||
if (datasource.tableId) {
|
||||
datasourceIds.push(datasource.tableId)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract both table IDs from both sides of the relationship
|
||||
else if (datasource.type === "link") {
|
||||
if (datasource.rowTableId) {
|
||||
datasourceIds.push(datasource.rowTableId)
|
||||
}
|
||||
if (datasource.tableId) {
|
||||
datasourceIds.push(datasource.tableId)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the datasource ID (not the query ID) for queries
|
||||
else if (datasource.type === "query") {
|
||||
if (datasource.datasourceId) {
|
||||
datasourceIds.push(datasource.datasourceId)
|
||||
}
|
||||
}
|
||||
|
||||
// Store configs for each relevant datasource ID
|
||||
if (datasourceIds.length) {
|
||||
store.update(state => {
|
||||
datasourceIds.forEach(id => {
|
||||
state.push({
|
||||
datasourceId: id,
|
||||
instanceId,
|
||||
refresh,
|
||||
})
|
||||
})
|
||||
return state
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Removes all registered datasource instances belonging to a particular
|
||||
// instance ID
|
||||
const unregisterInstance = instanceId => {
|
||||
store.update(state => {
|
||||
return state.filter(instance => instance.instanceId !== instanceId)
|
||||
})
|
||||
}
|
||||
|
||||
// Invalidates a specific datasource ID by refreshing all instances
|
||||
// which depend on data from that datasource
|
||||
const invalidateDatasource = datasourceId => {
|
||||
const relatedInstances = get(store).filter(instance => {
|
||||
return instance.datasourceId === datasourceId
|
||||
})
|
||||
relatedInstances?.forEach(instance => {
|
||||
instance.refresh()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: { registerDatasource, unregisterInstance, invalidateDatasource },
|
||||
}
|
||||
}
|
||||
|
||||
export const datasourceStore = createDatasourceStore()
|
|
@ -3,6 +3,7 @@ export { notificationStore } from "./notification"
|
|||
export { routeStore } from "./routes"
|
||||
export { screenStore } from "./screens"
|
||||
export { builderStore } from "./builder"
|
||||
export { datasourceStore } from "./datasource"
|
||||
|
||||
// Context stores are layered and duplicated, so it is not a singleton
|
||||
export { createContextStore } from "./context"
|
||||
|
|
|
@ -2,17 +2,23 @@
|
|||
import { getContext } from "svelte"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
|
||||
export let datasource = []
|
||||
|
||||
const { API, styleable, Provider, builderStore, ActionTypes } = getContext(
|
||||
"sdk"
|
||||
)
|
||||
const component = getContext("component")
|
||||
|
||||
export let datasource = []
|
||||
|
||||
let rows = []
|
||||
let loaded = false
|
||||
|
||||
$: fetchData(datasource)
|
||||
$: actions = [
|
||||
{
|
||||
type: ActionTypes.RefreshDatasource,
|
||||
callback: () => fetchData(datasource),
|
||||
metadata: { datasource },
|
||||
},
|
||||
]
|
||||
|
||||
async function fetchData(datasource) {
|
||||
if (!isEmpty(datasource)) {
|
||||
|
@ -20,13 +26,6 @@
|
|||
}
|
||||
loaded = true
|
||||
}
|
||||
|
||||
$: actions = [
|
||||
{
|
||||
type: ActionTypes.RefreshDatasource,
|
||||
callback: () => fetchData(datasource),
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<Provider {actions}>
|
||||
|
|
Loading…
Reference in New Issue