diff --git a/packages/client/src/api/rows.js b/packages/client/src/api/rows.js
index 6930105d78..e6dc8879fa 100644
--- a/packages/client/src/api/rows.js
+++ b/packages/client/src/api/rows.js
@@ -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
}
diff --git a/packages/client/src/components/Provider.svelte b/packages/client/src/components/Provider.svelte
index 8c50eeeb01..bc95412bab 100644
--- a/packages/client/src/components/Provider.svelte
+++ b/packages/client/src/components/Provider.svelte
@@ -1,6 +1,8 @@
diff --git a/packages/client/src/store/context.js b/packages/client/src/store/context.js
index 5092161037..80777cbeff 100644
--- a/packages/client/src/store/context.js
+++ b/packages/client/src/store/context.js
@@ -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
})
}
diff --git a/packages/client/src/store/datasource.js b/packages/client/src/store/datasource.js
new file mode 100644
index 0000000000..58fa632c49
--- /dev/null
+++ b/packages/client/src/store/datasource.js
@@ -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()
diff --git a/packages/client/src/store/index.js b/packages/client/src/store/index.js
index 575c5d98f2..9d08423374 100644
--- a/packages/client/src/store/index.js
+++ b/packages/client/src/store/index.js
@@ -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"
diff --git a/packages/standard-components/src/List.svelte b/packages/standard-components/src/List.svelte
index 4ba10cc28c..b117bbdb70 100644
--- a/packages/standard-components/src/List.svelte
+++ b/packages/standard-components/src/List.svelte
@@ -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),
- },
- ]