Merge pull request #15338 from Budibase/ts/util-schema

Typification of util schema
This commit is contained in:
Adria Navarro 2025-01-10 15:43:07 +01:00 committed by GitHub
commit 32b22aa093
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 114 additions and 76 deletions

View File

@ -29,7 +29,7 @@ import { ActionTypes } from "./constants"
import {
fetchDatasourceSchema,
fetchDatasourceDefinition,
} from "./utils/schema.js"
} from "./utils/schema"
import { getAPIKey } from "./utils/api.js"
import { enrichButtonActions } from "./utils/buttonActions.js"
import { processStringSync, makePropSafe } from "@budibase/string-templates"

View File

@ -1,13 +1,5 @@
import { API } from "api"
import TableFetch from "@budibase/frontend-core/src/fetch/TableFetch"
import ViewFetch from "@budibase/frontend-core/src/fetch/ViewFetch"
import QueryFetch from "@budibase/frontend-core/src/fetch/QueryFetch"
import RelationshipFetch from "@budibase/frontend-core/src/fetch/RelationshipFetch"
import NestedProviderFetch from "@budibase/frontend-core/src/fetch/NestedProviderFetch"
import FieldFetch from "@budibase/frontend-core/src/fetch/FieldFetch"
import JSONArrayFetch from "@budibase/frontend-core/src/fetch/JSONArrayFetch"
import ViewV2Fetch from "@budibase/frontend-core/src/fetch/ViewV2Fetch"
import QueryArrayFetch from "@budibase/frontend-core/src/fetch/QueryArrayFetch"
import { DataFetchMap, DataFetchType } from "@budibase/frontend-core"
/**
* Constructs a fetch instance for a given datasource.
@ -16,22 +8,20 @@ import QueryArrayFetch from "@budibase/frontend-core/src/fetch/QueryArrayFetch"
* @param datasource the datasource
* @returns
*/
const getDatasourceFetchInstance = datasource => {
const handler = {
table: TableFetch,
view: ViewFetch,
viewV2: ViewV2Fetch,
query: QueryFetch,
link: RelationshipFetch,
provider: NestedProviderFetch,
field: FieldFetch,
jsonarray: JSONArrayFetch,
queryarray: QueryArrayFetch,
}[datasource?.type]
const getDatasourceFetchInstance = <
TDatasource extends { type: DataFetchType }
>(
datasource: TDatasource
) => {
const handler = DataFetchMap[datasource?.type]
if (!handler) {
return null
}
return new handler({ API, datasource })
return new handler({
API,
datasource: datasource as never,
query: null as any,
})
}
/**
@ -39,21 +29,23 @@ const getDatasourceFetchInstance = datasource => {
* @param datasource the datasource to fetch the schema for
* @param options options for enriching the schema
*/
export const fetchDatasourceSchema = async (
datasource,
export const fetchDatasourceSchema = async <
TDatasource extends { type: DataFetchType }
>(
datasource: TDatasource,
options = { enrichRelationships: false, formSchema: false }
) => {
const instance = getDatasourceFetchInstance(datasource)
const definition = await instance?.getDefinition(datasource)
if (!definition) {
const definition = await instance?.getDefinition()
if (!instance || !definition) {
return null
}
// Get the normal schema as long as we aren't wanting a form schema
let schema
let schema: any
if (datasource?.type !== "query" || !options?.formSchema) {
schema = instance.getSchema(definition)
} else if (definition.parameters?.length) {
schema = instance.getSchema(definition as any)
} else if ("parameters" in definition && definition.parameters?.length) {
schema = {}
definition.parameters.forEach(param => {
schema[param.name] = { ...param, type: "string" }
@ -73,7 +65,12 @@ export const fetchDatasourceSchema = async (
}
// Enrich schema with relationships if required
if (definition?.sql && options?.enrichRelationships) {
if (
definition &&
"sql" in definition &&
definition.sql &&
options?.enrichRelationships
) {
const relationshipAdditions = await getRelationshipSchemaAdditions(schema)
schema = {
...schema,
@ -89,20 +86,26 @@ export const fetchDatasourceSchema = async (
* Fetches the definition of any kind of datasource.
* @param datasource the datasource to fetch the schema for
*/
export const fetchDatasourceDefinition = async datasource => {
export const fetchDatasourceDefinition = async <
TDatasource extends { type: DataFetchType }
>(
datasource: TDatasource
) => {
const instance = getDatasourceFetchInstance(datasource)
return await instance?.getDefinition(datasource)
return await instance?.getDefinition()
}
/**
* Fetches the schema of relationship fields for a SQL table schema
* @param schema the schema to enrich
*/
export const getRelationshipSchemaAdditions = async schema => {
export const getRelationshipSchemaAdditions = async (
schema: Record<string, any>
) => {
if (!schema) {
return null
}
let relationshipAdditions = {}
let relationshipAdditions: Record<string, any> = {}
for (let fieldKey of Object.keys(schema)) {
const fieldSchema = schema[fieldKey]
if (fieldSchema?.type === "link") {
@ -110,7 +113,10 @@ export const getRelationshipSchemaAdditions = async schema => {
type: "table",
tableId: fieldSchema?.tableId,
})
Object.keys(linkSchema || {}).forEach(linkKey => {
if (!linkSchema) {
continue
}
Object.keys(linkSchema).forEach(linkKey => {
relationshipAdditions[`${fieldKey}.${linkKey}`] = {
type: linkSchema[linkKey].type,
externalType: linkSchema[linkKey].externalType,

View File

@ -1,6 +1,7 @@
import DataFetch from "./DataFetch"
interface CustomDatasource {
type: "custom"
data: any
}

View File

@ -13,6 +13,7 @@ import {
UISearchFilter,
} from "@budibase/types"
import { APIClient } from "../api/types"
import { DataFetchType } from "."
const { buildQuery, limit: queryLimit, runQuery, sort } = QueryUtils
@ -59,7 +60,7 @@ export interface DataFetchParams<
* For other types of datasource, this class is overridden and extended.
*/
export default abstract class DataFetch<
TDatasource extends {},
TDatasource extends { type: DataFetchType },
TDefinition extends {
schema?: Record<string, any> | null
primaryDisplay?: string
@ -368,7 +369,7 @@ export default abstract class DataFetch<
* @param schema the datasource schema
* @return {object} the enriched datasource schema
*/
private enrichSchema(schema: TableSchema): TableSchema {
enrichSchema(schema: TableSchema): TableSchema {
// Check for any JSON fields so we can add any top level properties
let jsonAdditions: Record<string, { type: string; nestedJSON: true }> = {}
for (const fieldKey of Object.keys(schema)) {

View File

@ -1,7 +1,10 @@
import { Row } from "@budibase/types"
import DataFetch from "./DataFetch"
export interface FieldDatasource {
type Types = "field" | "queryarray" | "jsonarray"
export interface FieldDatasource<TType extends Types> {
type: TType
tableId: string
fieldType: "attachment" | "array"
value: string[] | Row[]
@ -15,8 +18,8 @@ function isArrayOfStrings(value: string[] | Row[]): value is string[] {
return Array.isArray(value) && !!value[0] && typeof value[0] !== "object"
}
export default class FieldFetch extends DataFetch<
FieldDatasource,
export default class FieldFetch<TType extends Types> extends DataFetch<
FieldDatasource<TType>,
FieldDefinition
> {
async getDefinition(): Promise<FieldDefinition | null> {

View File

@ -8,6 +8,7 @@ interface GroupUserQuery {
}
interface GroupUserDatasource {
type: "groupUser"
tableId: TableNames.USERS
}
@ -20,6 +21,7 @@ export default class GroupUserFetch extends DataFetch<
super({
...opts,
datasource: {
type: "groupUser",
tableId: TableNames.USERS,
},
})

View File

@ -1,7 +1,7 @@
import FieldFetch from "./FieldFetch"
import { getJSONArrayDatasourceSchema } from "../utils/json"
export default class JSONArrayFetch extends FieldFetch {
export default class JSONArrayFetch extends FieldFetch<"jsonarray"> {
async getDefinition() {
const { datasource } = this.options

View File

@ -2,6 +2,7 @@ import { Row, TableSchema } from "@budibase/types"
import DataFetch from "./DataFetch"
interface NestedProviderDatasource {
type: "provider"
value?: {
schema: TableSchema
primaryDisplay: string

View File

@ -4,7 +4,7 @@ import {
generateQueryArraySchemas,
} from "../utils/json"
export default class QueryArrayFetch extends FieldFetch {
export default class QueryArrayFetch extends FieldFetch<"queryarray"> {
async getDefinition() {
const { datasource } = this.options

View File

@ -4,6 +4,7 @@ import { ExecuteQueryRequest, Query } from "@budibase/types"
import { get } from "svelte/store"
interface QueryDatasource {
type: "query"
_id: string
fields: Record<string, any> & {
pagination?: {

View File

@ -2,6 +2,7 @@ import { Table } from "@budibase/types"
import DataFetch from "./DataFetch"
interface RelationshipDatasource {
type: "link"
tableId: string
rowId: string
rowTableId: string

View File

@ -1,8 +1,13 @@
import { get } from "svelte/store"
import DataFetch from "./DataFetch"
import { SortOrder, Table, UITable } from "@budibase/types"
import { SortOrder, Table } from "@budibase/types"
export default class TableFetch extends DataFetch<UITable, Table> {
interface TableDatasource {
type: "table"
tableId: string
}
export default class TableFetch extends DataFetch<TableDatasource, Table> {
async determineFeatureFlags() {
return {
supportsSearch: true,

View File

@ -14,18 +14,22 @@ interface UserFetchQuery {
}
interface UserDatasource {
tableId: string
type: "user"
tableId: TableNames.USERS
}
interface UserDefinition {}
export default class UserFetch extends DataFetch<
UserDatasource,
{},
UserDefinition,
UserFetchQuery
> {
constructor(opts: DataFetchParams<UserDatasource, UserFetchQuery>) {
super({
...opts,
datasource: {
type: "user",
tableId: TableNames.USERS,
},
})

View File

@ -1,9 +1,16 @@
import { Table, View } from "@budibase/types"
import { Table } from "@budibase/types"
import DataFetch from "./DataFetch"
type ViewV1 = View & { name: string }
type ViewV1Datasource = {
type: "view"
name: string
tableId: string
calculation: string
field: string
groupBy: string
}
export default class ViewFetch extends DataFetch<ViewV1, Table> {
export default class ViewFetch extends DataFetch<ViewV1Datasource, Table> {
async getDefinition() {
const { datasource } = this.options

View File

@ -1,9 +1,17 @@
import { SortOrder, UIView, ViewV2, ViewV2Type } from "@budibase/types"
import { SortOrder, ViewV2Enriched, ViewV2Type } from "@budibase/types"
import DataFetch from "./DataFetch"
import { get } from "svelte/store"
import { helpers } from "@budibase/shared-core"
export default class ViewV2Fetch extends DataFetch<UIView, ViewV2> {
interface ViewDatasource {
type: "viewV2"
id: string
}
export default class ViewV2Fetch extends DataFetch<
ViewDatasource,
ViewV2Enriched
> {
async determineFeatureFlags() {
return {
supportsSearch: true,

View File

@ -1,18 +1,20 @@
import TableFetch from "./TableFetch.js"
import ViewFetch from "./ViewFetch.js"
import ViewV2Fetch from "./ViewV2Fetch.js"
import TableFetch from "./TableFetch"
import ViewFetch from "./ViewFetch"
import ViewV2Fetch from "./ViewV2Fetch"
import QueryFetch from "./QueryFetch"
import RelationshipFetch from "./RelationshipFetch"
import NestedProviderFetch from "./NestedProviderFetch"
import FieldFetch from "./FieldFetch"
import JSONArrayFetch from "./JSONArrayFetch"
import UserFetch from "./UserFetch.js"
import UserFetch from "./UserFetch"
import GroupUserFetch from "./GroupUserFetch"
import CustomFetch from "./CustomFetch"
import QueryArrayFetch from "./QueryArrayFetch.js"
import { APIClient } from "../api/types.js"
import QueryArrayFetch from "./QueryArrayFetch"
import { APIClient } from "../api/types"
const DataFetchMap = {
export type DataFetchType = keyof typeof DataFetchMap
export const DataFetchMap = {
table: TableFetch,
view: ViewFetch,
viewV2: ViewV2Fetch,
@ -24,15 +26,14 @@ const DataFetchMap = {
// Client specific datasource types
provider: NestedProviderFetch,
field: FieldFetch,
field: FieldFetch<"field">,
jsonarray: JSONArrayFetch,
queryarray: QueryArrayFetch,
}
// Constructs a new fetch model for a certain datasource
export const fetchData = ({ API, datasource, options }: any) => {
const Fetch =
DataFetchMap[datasource?.type as keyof typeof DataFetchMap] || TableFetch
const Fetch = DataFetchMap[datasource?.type as DataFetchType] || TableFetch
const fetch = new Fetch({ API, datasource, ...options })
// Initially fetch data but don't bother waiting for the result
@ -43,29 +44,27 @@ export const fetchData = ({ API, datasource, options }: any) => {
// Creates an empty fetch instance with no datasource configured, so no data
// will initially be loaded
const createEmptyFetchInstance = <
TDatasource extends {
type: keyof typeof DataFetchMap
}
>({
const createEmptyFetchInstance = <TDatasource extends { type: DataFetchType }>({
API,
datasource,
}: {
API: APIClient
datasource: TDatasource
}) => {
const handler = DataFetchMap[datasource?.type as keyof typeof DataFetchMap]
const handler = DataFetchMap[datasource?.type as DataFetchType]
if (!handler) {
return null
}
return new handler({ API, datasource: null as any, query: null as any })
return new handler({
API,
datasource: null as never,
query: null as any,
})
}
// Fetches the definition of any type of datasource
export const getDatasourceDefinition = async <
TDatasource extends {
type: keyof typeof DataFetchMap
}
TDatasource extends { type: DataFetchType }
>({
API,
datasource,
@ -79,9 +78,7 @@ export const getDatasourceDefinition = async <
// Fetches the schema of any type of datasource
export const getDatasourceSchema = <
TDatasource extends {
type: keyof typeof DataFetchMap
}
TDatasource extends { type: DataFetchType }
>({
API,
datasource,

View File

@ -1,5 +1,6 @@
export { createAPIClient } from "./api"
export { fetchData } from "./fetch"
export { fetchData, DataFetchMap } from "./fetch"
export type { DataFetchType } from "./fetch"
export * as Constants from "./constants"
export * from "./stores"
export * from "./utils"