Remove built-in patch functionality from core API client and instead manually patch client library API endpoints
This commit is contained in:
parent
cfa02f88f6
commit
5b2b3e9add
|
@ -1,137 +0,0 @@
|
|||
import { createAPIClient, Constants } from "@budibase/frontend-core"
|
||||
import { notificationStore } from "./stores"
|
||||
import { FieldTypes } from "./constants"
|
||||
|
||||
export const API = createAPIClient({
|
||||
// Attach client specific headers
|
||||
attachHeaders: headers => {
|
||||
// Attach app ID header
|
||||
headers["x-budibase-app-id"] = window["##BUDIBASE_APP_ID##"]
|
||||
|
||||
// Attach client header if not inside the builder preview
|
||||
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
||||
headers["x-budibase-type"] = "client"
|
||||
}
|
||||
},
|
||||
|
||||
// Show an error notification for all API failures.
|
||||
// We could also log these to sentry.
|
||||
// Or we could check error.status and redirect to login on a 403 etc.
|
||||
onError: error => {
|
||||
const { status, method, url, message, handled } = error || {}
|
||||
|
||||
// Log any errors that we haven't manually handled
|
||||
if (!handled) {
|
||||
console.error("Unhandled error from API client", error)
|
||||
return
|
||||
}
|
||||
|
||||
// Notify all errors
|
||||
if (message) {
|
||||
// Don't notify if the URL contains the word analytics as it may be
|
||||
// blocked by browser extensions
|
||||
if (!url?.includes("analytics")) {
|
||||
notificationStore.actions.error(message)
|
||||
}
|
||||
}
|
||||
|
||||
// Log all errors to console
|
||||
console.warn(`[Client] HTTP ${status} on ${method}:${url}\n\t${message}`)
|
||||
},
|
||||
|
||||
// Patch certain endpoints with functionality specific to client apps
|
||||
patches: {
|
||||
// Enrich rows so they properly handle client bindings
|
||||
fetchSelf: async ({ output }) => {
|
||||
const user = output
|
||||
if (user && user._id) {
|
||||
if (user.roleId === "PUBLIC") {
|
||||
// Don't try to enrich a public user as it will 403
|
||||
return user
|
||||
} else {
|
||||
return (await enrichRows([user], Constants.TableNames.USERS))[0]
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
fetchRelationshipData: async ({ params, output }) => {
|
||||
const tableId = params[0]?.tableId
|
||||
return await enrichRows(output, tableId)
|
||||
},
|
||||
fetchTableData: async ({ params, output }) => {
|
||||
const tableId = params[0]
|
||||
return await enrichRows(output, tableId)
|
||||
},
|
||||
searchTable: async ({ params, output }) => {
|
||||
const tableId = params[0]?.tableId
|
||||
return {
|
||||
...output,
|
||||
rows: await enrichRows(output?.rows, tableId),
|
||||
}
|
||||
},
|
||||
fetchViewData: async ({ params, output }) => {
|
||||
const tableId = params[0]?.tableId
|
||||
return await enrichRows(output, tableId)
|
||||
},
|
||||
|
||||
// Wipe any HBS formulae from table definitions, as these interfere with
|
||||
// handlebars enrichment
|
||||
fetchTableDefinition: async ({ output }) => {
|
||||
Object.keys(output?.schema || {}).forEach(field => {
|
||||
if (output.schema[field]?.type === "formula") {
|
||||
delete output.schema[field].formula
|
||||
}
|
||||
})
|
||||
return output
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Enriches rows which contain certain field types so that they can
|
||||
* be properly displayed.
|
||||
* The ability to create these bindings has been removed, but they will still
|
||||
* exist in client apps to support backwards compatibility.
|
||||
*/
|
||||
const enrichRows = async (rows, tableId) => {
|
||||
if (!Array.isArray(rows)) {
|
||||
return []
|
||||
}
|
||||
if (rows.length) {
|
||||
const tables = {}
|
||||
for (let row of rows) {
|
||||
// Fall back to passed in tableId if row doesn't have it specified
|
||||
let rowTableId = row.tableId || tableId
|
||||
let table = tables[rowTableId]
|
||||
if (!table) {
|
||||
// Fetch table schema so we can check column types
|
||||
table = await API.fetchTableDefinition(rowTableId)
|
||||
tables[rowTableId] = table
|
||||
}
|
||||
const schema = table?.schema
|
||||
if (schema) {
|
||||
const keys = Object.keys(schema)
|
||||
for (let key of keys) {
|
||||
const type = schema[key].type
|
||||
if (type === FieldTypes.LINK && Array.isArray(row[key])) {
|
||||
// Enrich row a string join of relationship fields
|
||||
row[`${key}_text`] =
|
||||
row[key]
|
||||
?.map(option => option?.primaryDisplay)
|
||||
.filter(option => !!option)
|
||||
.join(", ") || ""
|
||||
} else if (type === "attachment") {
|
||||
// Enrich row with the first image URL for any attachment fields
|
||||
let url = null
|
||||
if (Array.isArray(row[key]) && row[key][0] != null) {
|
||||
url = row[key][0].url
|
||||
}
|
||||
row[`${key}_first`] = url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import { createAPIClient } from "@budibase/frontend-core"
|
||||
import { notificationStore } from "../stores"
|
||||
|
||||
export const API = createAPIClient({
|
||||
// Attach client specific headers
|
||||
attachHeaders: headers => {
|
||||
// Attach app ID header
|
||||
headers["x-budibase-app-id"] = window["##BUDIBASE_APP_ID##"]
|
||||
|
||||
// Attach client header if not inside the builder preview
|
||||
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
||||
headers["x-budibase-type"] = "client"
|
||||
}
|
||||
},
|
||||
|
||||
// Show an error notification for all API failures.
|
||||
// We could also log these to sentry.
|
||||
// Or we could check error.status and redirect to login on a 403 etc.
|
||||
onError: error => {
|
||||
const { status, method, url, message, handled } = error || {}
|
||||
|
||||
// Log any errors that we haven't manually handled
|
||||
if (!handled) {
|
||||
console.error("Unhandled error from API client", error)
|
||||
return
|
||||
}
|
||||
|
||||
// Notify all errors
|
||||
if (message) {
|
||||
// Don't notify if the URL contains the word analytics as it may be
|
||||
// blocked by browser extensions
|
||||
if (!url?.includes("analytics")) {
|
||||
notificationStore.actions.error(message)
|
||||
}
|
||||
}
|
||||
|
||||
// Log all errors to console
|
||||
console.warn(`[Client] HTTP ${status} on ${method}:${url}\n\t${message}`)
|
||||
},
|
||||
})
|
|
@ -0,0 +1,9 @@
|
|||
import { API } from "./api.js"
|
||||
import { patchAPI } from "./patches.js"
|
||||
|
||||
// Certain endpoints which return rows need patched so that they transform
|
||||
// and enrich the row docs, so that they can be correctly handled by the
|
||||
// client library
|
||||
patchAPI(API)
|
||||
|
||||
export { API }
|
|
@ -0,0 +1,107 @@
|
|||
import { Constants } from "@budibase/frontend-core"
|
||||
import { FieldTypes } from "../constants"
|
||||
|
||||
export const patchAPI = API => {
|
||||
/**
|
||||
* Enriches rows which contain certain field types so that they can
|
||||
* be properly displayed.
|
||||
* The ability to create these bindings has been removed, but they will still
|
||||
* exist in client apps to support backwards compatibility.
|
||||
*/
|
||||
const enrichRows = async (rows, tableId) => {
|
||||
if (!Array.isArray(rows)) {
|
||||
return []
|
||||
}
|
||||
if (rows.length) {
|
||||
const tables = {}
|
||||
for (let row of rows) {
|
||||
// Fall back to passed in tableId if row doesn't have it specified
|
||||
let rowTableId = row.tableId || tableId
|
||||
let table = tables[rowTableId]
|
||||
if (!table) {
|
||||
// Fetch table schema so we can check column types
|
||||
table = await API.fetchTableDefinition(rowTableId)
|
||||
tables[rowTableId] = table
|
||||
}
|
||||
const schema = table?.schema
|
||||
if (schema) {
|
||||
const keys = Object.keys(schema)
|
||||
for (let key of keys) {
|
||||
const type = schema[key].type
|
||||
if (type === FieldTypes.LINK && Array.isArray(row[key])) {
|
||||
// Enrich row a string join of relationship fields
|
||||
row[`${key}_text`] =
|
||||
row[key]
|
||||
?.map(option => option?.primaryDisplay)
|
||||
.filter(option => !!option)
|
||||
.join(", ") || ""
|
||||
} else if (type === "attachment") {
|
||||
// Enrich row with the first image URL for any attachment fields
|
||||
let url = null
|
||||
if (Array.isArray(row[key]) && row[key][0] != null) {
|
||||
url = row[key][0].url
|
||||
}
|
||||
row[`${key}_first`] = url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// Enrich rows so they properly handle client bindings
|
||||
const fetchSelf = API.fetchSelf
|
||||
API.fetchSelf = async () => {
|
||||
const user = await fetchSelf()
|
||||
if (user && user._id) {
|
||||
if (user.roleId === "PUBLIC") {
|
||||
// Don't try to enrich a public user as it will 403
|
||||
return user
|
||||
} else {
|
||||
return (await enrichRows([user], Constants.TableNames.USERS))[0]
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
const fetchRelationshipData = API.fetchRelationshipData
|
||||
API.fetchRelationshipData = async params => {
|
||||
const tableId = params?.tableId
|
||||
const rows = await fetchRelationshipData(params)
|
||||
return await enrichRows(rows, tableId)
|
||||
}
|
||||
const fetchTableData = API.fetchTableData
|
||||
API.fetchTableData = async tableId => {
|
||||
const rows = await fetchTableData(tableId)
|
||||
return await enrichRows(rows, tableId)
|
||||
}
|
||||
const searchTable = API.searchTable
|
||||
API.searchTable = async params => {
|
||||
const tableId = params?.tableId
|
||||
const output = await searchTable(params)
|
||||
return {
|
||||
...output,
|
||||
rows: await enrichRows(output?.rows, tableId),
|
||||
}
|
||||
}
|
||||
const fetchViewData = API.fetchViewData
|
||||
API.fetchViewData = async params => {
|
||||
const tableId = params?.tableId
|
||||
const rows = await fetchViewData(params)
|
||||
return await enrichRows(rows, tableId)
|
||||
}
|
||||
|
||||
// Wipe any HBS formulae from table definitions, as these interfere with
|
||||
// handlebars enrichment
|
||||
const fetchTableDefinition = API.fetchTableDefinition
|
||||
API.fetchTableDefinition = async tableId => {
|
||||
const definition = await fetchTableDefinition(tableId)
|
||||
Object.keys(definition?.schema || {}).forEach(field => {
|
||||
if (definition.schema[field]?.type === "formula") {
|
||||
delete definition.schema[field].formula
|
||||
}
|
||||
})
|
||||
return definition
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "./api.js"
|
||||
import { API } from "api"
|
||||
import {
|
||||
authStore,
|
||||
notificationStore,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "../api"
|
||||
import { API } from "api"
|
||||
import { get, writable } from "svelte/store"
|
||||
|
||||
const createAppStore = () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "../api"
|
||||
import { API } from "api"
|
||||
import { writable } from "svelte/store"
|
||||
|
||||
const createAuthStore = () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { writable, derived, get } from "svelte/store"
|
||||
import Manifest from "manifest.json"
|
||||
import { findComponentById, findComponentPathById } from "../utils/components"
|
||||
import { API } from "../api"
|
||||
import { API } from "api"
|
||||
|
||||
const dispatchEvent = (type, data = {}) => {
|
||||
window.parent.postMessage({ type, data })
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { API } from "../api"
|
||||
import { API } from "api"
|
||||
import { FieldTypes } from "../constants"
|
||||
import { routeStore } from "./routes"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { get, writable } from "svelte/store"
|
||||
import { push } from "svelte-spa-router"
|
||||
import { API } from "../api"
|
||||
import { API } from "api"
|
||||
import { peekStore } from "./peek"
|
||||
import { builderStore } from "./builder"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API } from "../api.js"
|
||||
import { API } from "api"
|
||||
import { JSONUtils } from "@budibase/frontend-core"
|
||||
import TableFetch from "@budibase/frontend-core/src/fetch/TableFetch.js"
|
||||
import ViewFetch from "@budibase/frontend-core/src/fetch/ViewFetch.js"
|
||||
|
|
|
@ -25,7 +25,6 @@ import { buildViewEndpoints } from "./views"
|
|||
const defaultAPIClientConfig = {
|
||||
attachHeaders: null,
|
||||
onError: null,
|
||||
patches: null,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,7 +184,7 @@ export const createAPIClient = config => {
|
|||
}
|
||||
|
||||
// Attach all endpoints
|
||||
API = {
|
||||
return {
|
||||
...API,
|
||||
...buildAnalyticsEndpoints(API),
|
||||
...buildAppEndpoints(API),
|
||||
|
@ -210,18 +209,4 @@ export const createAPIClient = config => {
|
|||
...buildUserEndpoints(API),
|
||||
...buildViewEndpoints(API),
|
||||
}
|
||||
|
||||
// Assign any patches
|
||||
const patches = Object.entries(config.patches || {})
|
||||
if (patches.length) {
|
||||
patches.forEach(([method, fn]) => {
|
||||
const baseFn = API[method]
|
||||
API[method] = async (...params) => {
|
||||
const output = await baseFn(...params)
|
||||
return await fn({ params, output })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return API
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue