diff --git a/lerna.json b/lerna.json index ff69a18459..0dc09b27be 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.2.38", + "version": "3.2.39", "npmClient": "yarn", "concurrency": 20, "command": { diff --git a/packages/builder/src/stores/builder/queries.js b/packages/builder/src/stores/builder/queries.js deleted file mode 100644 index 7aeb9ff8fd..0000000000 --- a/packages/builder/src/stores/builder/queries.js +++ /dev/null @@ -1,130 +0,0 @@ -import { writable, get, derived } from "svelte/store" -import { datasources } from "./datasources" -import { integrations } from "./integrations" -import { API } from "@/api" -import { duplicateName } from "@/helpers/duplicate" - -const sortQueries = queryList => { - queryList.sort((q1, q2) => { - return q1.name.localeCompare(q2.name) - }) -} - -export function createQueriesStore() { - const store = writable({ - list: [], - selectedQueryId: null, - }) - const derivedStore = derived(store, $store => ({ - ...$store, - selected: $store.list?.find(q => q._id === $store.selectedQueryId), - })) - - const fetch = async () => { - const queries = await API.getQueries() - sortQueries(queries) - store.update(state => ({ - ...state, - list: queries, - })) - } - - const save = async (datasourceId, query) => { - const _integrations = get(integrations) - const dataSource = get(datasources).list.filter( - ds => ds._id === datasourceId - ) - // Check if readable attribute is found - if (dataSource.length !== 0) { - const integration = _integrations[dataSource[0].source] - const readable = integration.query[query.queryVerb].readable - if (readable) { - query.readable = readable - } - } - query.datasourceId = datasourceId - const savedQuery = await API.saveQuery(query) - store.update(state => { - const idx = state.list.findIndex(query => query._id === savedQuery._id) - const queries = state.list - if (idx >= 0) { - queries.splice(idx, 1, savedQuery) - } else { - queries.push(savedQuery) - } - sortQueries(queries) - return { - list: queries, - selectedQueryId: savedQuery._id, - } - }) - return savedQuery - } - - const importQueries = async ({ data, datasourceId }) => { - return await API.importQueries(datasourceId, data) - } - - const select = id => { - store.update(state => ({ - ...state, - selectedQueryId: id, - })) - } - - const preview = async query => { - const result = await API.previewQuery(query) - // Assume all the fields are strings and create a basic schema from the - // unique fields returned by the server - const schema = {} - for (let [field, metadata] of Object.entries(result.schema)) { - schema[field] = metadata || { type: "string" } - } - return { ...result, schema, rows: result.rows || [] } - } - - const deleteQuery = async query => { - await API.deleteQuery(query._id, query._rev) - store.update(state => { - state.list = state.list.filter(existing => existing._id !== query._id) - return state - }) - } - - const duplicate = async query => { - let list = get(store).list - const newQuery = { ...query } - const datasourceId = query.datasourceId - - delete newQuery._id - delete newQuery._rev - newQuery.name = duplicateName( - query.name, - list.map(q => q.name) - ) - - return await save(datasourceId, newQuery) - } - - const removeDatasourceQueries = datasourceId => { - store.update(state => ({ - ...state, - list: state.list.filter(table => table.datasourceId !== datasourceId), - })) - } - - return { - subscribe: derivedStore.subscribe, - fetch, - init: fetch, - select, - save, - import: importQueries, - delete: deleteQuery, - preview, - duplicate, - removeDatasourceQueries, - } -} - -export const queries = createQueriesStore() diff --git a/packages/builder/src/stores/builder/queries.ts b/packages/builder/src/stores/builder/queries.ts new file mode 100644 index 0000000000..c6511dc346 --- /dev/null +++ b/packages/builder/src/stores/builder/queries.ts @@ -0,0 +1,156 @@ +import { derived, get, Writable } from "svelte/store" +import { datasources } from "./datasources" +import { integrations } from "./integrations" +import { API } from "@/api" +import { duplicateName } from "@/helpers/duplicate" +import { DerivedBudiStore } from "@/stores/BudiStore" +import { + Query, + QueryPreview, + PreviewQueryResponse, + SaveQueryRequest, + ImportRestQueryRequest, + QuerySchema, +} from "@budibase/types" + +const sortQueries = (queryList: Query[]) => { + queryList.sort((q1, q2) => { + return q1.name.localeCompare(q2.name) + }) +} + +interface BuilderQueryStore { + list: Query[] + selectedQueryId: string | null +} + +interface DerivedQueryStore extends BuilderQueryStore { + selected?: Query +} + +export class QueryStore extends DerivedBudiStore< + BuilderQueryStore, + DerivedQueryStore +> { + constructor() { + const makeDerivedStore = (store: Writable) => { + return derived(store, ($store): DerivedQueryStore => { + return { + list: $store.list, + selectedQueryId: $store.selectedQueryId, + selected: $store.list?.find(q => q._id === $store.selectedQueryId), + } + }) + } + + super( + { + list: [], + selectedQueryId: null, + }, + makeDerivedStore + ) + + this.select = this.select.bind(this) + } + + async fetch() { + const queries = await API.getQueries() + sortQueries(queries) + this.store.update(state => ({ + ...state, + list: queries, + })) + } + + async save(datasourceId: string, query: SaveQueryRequest) { + const _integrations = get(integrations) + const dataSource = get(datasources).list.filter( + ds => ds._id === datasourceId + ) + // Check if readable attribute is found + if (dataSource.length !== 0) { + const integration = _integrations[dataSource[0].source] + const readable = integration.query[query.queryVerb].readable + if (readable) { + query.readable = readable + } + } + query.datasourceId = datasourceId + const savedQuery = await API.saveQuery(query) + this.store.update(state => { + const idx = state.list.findIndex(query => query._id === savedQuery._id) + const queries = state.list + if (idx >= 0) { + queries.splice(idx, 1, savedQuery) + } else { + queries.push(savedQuery) + } + sortQueries(queries) + return { + list: queries, + selectedQueryId: savedQuery._id || null, + } + }) + return savedQuery + } + + async importQueries(data: ImportRestQueryRequest) { + return await API.importQueries(data) + } + + select(id: string | null) { + this.store.update(state => ({ + ...state, + selectedQueryId: id, + })) + } + + async preview(query: QueryPreview): Promise { + const result = await API.previewQuery(query) + // Assume all the fields are strings and create a basic schema from the + // unique fields returned by the server + const schema: Record = {} + for (let [field, metadata] of Object.entries(result.schema)) { + schema[field] = (metadata as QuerySchema) || { type: "string" } + } + return { ...result, schema, rows: result.rows || [] } + } + + async delete(query: Query) { + if (!query._id || !query._rev) { + throw new Error("Query ID or Revision is missing") + } + await API.deleteQuery(query._id, query._rev) + this.store.update(state => ({ + ...state, + list: state.list.filter(existing => existing._id !== query._id), + })) + } + + async duplicate(query: Query) { + let list = get(this.store).list + const newQuery = { ...query } + const datasourceId = query.datasourceId + + delete newQuery._id + delete newQuery._rev + newQuery.name = duplicateName( + query.name, + list.map(q => q.name) + ) + + return await this.save(datasourceId, newQuery) + } + + removeDatasourceQueries(datasourceId: string) { + this.store.update(state => ({ + ...state, + list: state.list.filter(table => table.datasourceId !== datasourceId), + })) + } + + init = this.fetch +} + +export const queries = new QueryStore() diff --git a/packages/client/src/api/api.js b/packages/client/src/api/api.ts similarity index 94% rename from packages/client/src/api/api.js rename to packages/client/src/api/api.ts index d4c8faa4d2..b944f7bd7c 100644 --- a/packages/client/src/api/api.js +++ b/packages/client/src/api/api.ts @@ -1,6 +1,10 @@ import { createAPIClient } from "@budibase/frontend-core" -import { authStore } from "../stores/auth.js" -import { notificationStore, devToolsEnabled, devToolsStore } from "../stores/" +import { authStore } from "../stores/auth" +import { + notificationStore, + devToolsEnabled, + devToolsStore, +} from "../stores/index" import { get } from "svelte/store" export const API = createAPIClient({ diff --git a/packages/client/src/api/index.js b/packages/client/src/api/index.js index 5eb6b2b6f4..3c53045cfd 100644 --- a/packages/client/src/api/index.js +++ b/packages/client/src/api/index.js @@ -1,5 +1,5 @@ -import { API } from "./api.js" -import { patchAPI } from "./patches.js" +import { API } from "./api" +import { patchAPI } from "./patches" // 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 diff --git a/packages/client/src/index.d.ts b/packages/client/src/index.d.ts new file mode 100644 index 0000000000..7e13670b33 --- /dev/null +++ b/packages/client/src/index.d.ts @@ -0,0 +1,5 @@ +interface Window { + "##BUDIBASE_APP_ID##": string + "##BUDIBASE_IN_BUILDER##": string + MIGRATING_APP: boolean +} diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js index 40d066f2ee..68d75d2806 100644 --- a/packages/client/src/sdk.js +++ b/packages/client/src/sdk.js @@ -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" diff --git a/packages/client/src/stores/auth.js b/packages/client/src/stores/auth.ts similarity index 94% rename from packages/client/src/stores/auth.js rename to packages/client/src/stores/auth.ts index 214cc7bce2..15f44e7c32 100644 --- a/packages/client/src/stores/auth.js +++ b/packages/client/src/stores/auth.ts @@ -2,7 +2,9 @@ import { API } from "api" import { writable } from "svelte/store" const createAuthStore = () => { - const store = writable(null) + const store = writable<{ + csrfToken?: string + } | null>(null) // Fetches the user object if someone is logged in and has reloaded the page const fetchUser = async () => { diff --git a/packages/client/src/stores/derived/currentRole.js b/packages/client/src/stores/derived/currentRole.js index 8bb4c5a25d..056a05f8ab 100644 --- a/packages/client/src/stores/derived/currentRole.js +++ b/packages/client/src/stores/derived/currentRole.js @@ -1,7 +1,7 @@ import { derived } from "svelte/store" import { Constants } from "@budibase/frontend-core" import { devToolsStore } from "../devTools.js" -import { authStore } from "../auth.js" +import { authStore } from "../auth" import { devToolsEnabled } from "./devToolsEnabled.js" // Derive the current role of the logged-in user diff --git a/packages/client/src/stores/notification.js b/packages/client/src/stores/notification.ts similarity index 80% rename from packages/client/src/stores/notification.js rename to packages/client/src/stores/notification.ts index 054117aaba..fa28b9f40a 100644 --- a/packages/client/src/stores/notification.js +++ b/packages/client/src/stores/notification.ts @@ -6,7 +6,7 @@ const DEFAULT_NOTIFICATION_TIMEOUT = 3000 const createNotificationStore = () => { let block = false - const store = writable([]) + const store = writable<{ id: string; message: string; count: number }[]>([]) const blockNotifications = (timeout = 1000) => { block = true @@ -14,11 +14,11 @@ const createNotificationStore = () => { } const send = ( - message, + message: string, type = "info", - icon, + icon: string, autoDismiss = true, - duration, + duration?: number, count = 1 ) => { if (block) { @@ -66,7 +66,7 @@ const createNotificationStore = () => { } } - const dismiss = id => { + const dismiss = (id: string) => { store.update(state => { return state.filter(n => n.id !== id) }) @@ -76,13 +76,13 @@ const createNotificationStore = () => { subscribe: store.subscribe, actions: { send, - info: (msg, autoDismiss, duration) => + info: (msg: string, autoDismiss?: boolean, duration?: number) => send(msg, "info", "Info", autoDismiss ?? true, duration), - success: (msg, autoDismiss, duration) => + success: (msg: string, autoDismiss?: boolean, duration?: number) => send(msg, "success", "CheckmarkCircle", autoDismiss ?? true, duration), - warning: (msg, autoDismiss, duration) => + warning: (msg: string, autoDismiss?: boolean, duration?: number) => send(msg, "warning", "Alert", autoDismiss ?? true, duration), - error: (msg, autoDismiss, duration) => + error: (msg: string, autoDismiss?: boolean, duration?: number) => send(msg, "error", "Alert", autoDismiss ?? false, duration), blockNotifications, dismiss, diff --git a/packages/client/src/stores/routes.js b/packages/client/src/stores/routes.ts similarity index 82% rename from packages/client/src/stores/routes.js rename to packages/client/src/stores/routes.ts index 8e318af2e3..3f200a9c88 100644 --- a/packages/client/src/stores/routes.js +++ b/packages/client/src/stores/routes.ts @@ -4,8 +4,24 @@ import { API } from "api" import { peekStore } from "./peek" import { builderStore } from "./builder" +interface Route { + path: string + screenId: string +} + +interface StoreType { + routes: Route[] + routeParams: {} + activeRoute?: Route | null + routeSessionId: number + routerLoaded: boolean + queryParams?: { + peek?: boolean + } +} + const createRouteStore = () => { - const initialState = { + const initialState: StoreType = { routes: [], routeParams: {}, activeRoute: null, @@ -22,7 +38,7 @@ const createRouteStore = () => { } catch (error) { routeConfig = null } - let routes = [] + const routes: Route[] = [] Object.values(routeConfig?.routes || {}).forEach(route => { Object.entries(route.subpaths || {}).forEach(([path, config]) => { routes.push({ @@ -43,13 +59,13 @@ const createRouteStore = () => { return state }) } - const setRouteParams = routeParams => { + const setRouteParams = (routeParams: StoreType["routeParams"]) => { store.update(state => { state.routeParams = routeParams return state }) } - const setQueryParams = queryParams => { + const setQueryParams = (queryParams: { peek?: boolean }) => { store.update(state => { state.queryParams = { ...queryParams, @@ -60,13 +76,13 @@ const createRouteStore = () => { return state }) } - const setActiveRoute = route => { + const setActiveRoute = (route: string) => { store.update(state => { state.activeRoute = state.routes.find(x => x.path === route) return state }) } - const navigate = (url, peek, externalNewTab) => { + const navigate = (url: string, peek: boolean, externalNewTab: boolean) => { if (get(builderStore).inBuilder) { return } @@ -93,7 +109,7 @@ const createRouteStore = () => { const setRouterLoaded = () => { store.update(state => ({ ...state, routerLoaded: true })) } - const createFullURL = relativeURL => { + const createFullURL = (relativeURL: string) => { if (!relativeURL?.startsWith("/")) { return relativeURL } diff --git a/packages/client/src/utils/schema.js b/packages/client/src/utils/schema.ts similarity index 57% rename from packages/client/src/utils/schema.js rename to packages/client/src/utils/schema.ts index 60800afc53..5400d62087 100644 --- a/packages/client/src/utils/schema.js +++ b/packages/client/src/utils/schema.ts @@ -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 +) => { if (!schema) { return null } - let relationshipAdditions = {} + let relationshipAdditions: Record = {} 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, diff --git a/packages/frontend-core/src/api/index.ts b/packages/frontend-core/src/api/index.ts index f7b05c338a..6efc90023a 100644 --- a/packages/frontend-core/src/api/index.ts +++ b/packages/frontend-core/src/api/index.ts @@ -68,13 +68,13 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => { ): Promise => { // Try to read a message from the error let message = response.statusText - let json: any = null + let json = null try { json = await response.json() if (json?.message) { message = json.message } else if (json?.error) { - message = json.error + message = JSON.stringify(json.error) } } catch (error) { // Do nothing @@ -93,7 +93,7 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => { // Generates an error object from a string const makeError = ( message: string, - url?: string, + url: string, method?: HTTPMethod ): APIError => { return { @@ -226,7 +226,7 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => { return await handler(callConfig) } catch (error) { if (config?.onError) { - config.onError(error) + config.onError(error as APIError) } throw error } @@ -239,13 +239,9 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => { patch: requestApiCall(HTTPMethod.PATCH), delete: requestApiCall(HTTPMethod.DELETE), put: requestApiCall(HTTPMethod.PUT), - error: (message: string) => { - throw makeError(message) - }, invalidateCache: () => { cache = {} }, - // Generic utility to extract the current app ID. Assumes that any client // that exists in an app context will be attaching our app ID header. getAppID: (): string => { diff --git a/packages/frontend-core/src/api/types.ts b/packages/frontend-core/src/api/types.ts index 0db1049591..4819b4cd3b 100644 --- a/packages/frontend-core/src/api/types.ts +++ b/packages/frontend-core/src/api/types.ts @@ -46,7 +46,7 @@ export type Headers = Record export type APIClientConfig = { enableCaching?: boolean attachHeaders?: (headers: Headers) => void - onError?: (error: any) => void + onError?: (error: APIError) => void onMigrationDetected?: (migration: string) => void } @@ -86,14 +86,13 @@ export type BaseAPIClient = { patch: ( params: APICallParams ) => Promise - error: (message: string) => void invalidateCache: () => void getAppID: () => string } export type APIError = { message?: string - url?: string + url: string method?: HTTPMethod json: any status: number diff --git a/packages/frontend-core/src/fetch/CustomFetch.ts b/packages/frontend-core/src/fetch/CustomFetch.ts index afd3d18ba9..dfd29c4a02 100644 --- a/packages/frontend-core/src/fetch/CustomFetch.ts +++ b/packages/frontend-core/src/fetch/CustomFetch.ts @@ -1,6 +1,7 @@ import DataFetch from "./DataFetch" interface CustomDatasource { + type: "custom" data: any } diff --git a/packages/frontend-core/src/fetch/DataFetch.ts b/packages/frontend-core/src/fetch/DataFetch.ts index 0079fec057..b10a8b0a69 100644 --- a/packages/frontend-core/src/fetch/DataFetch.ts +++ b/packages/frontend-core/src/fetch/DataFetch.ts @@ -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 | 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 = {} for (const fieldKey of Object.keys(schema)) { diff --git a/packages/frontend-core/src/fetch/FieldFetch.ts b/packages/frontend-core/src/fetch/FieldFetch.ts index ac1e683c51..694443a5dc 100644 --- a/packages/frontend-core/src/fetch/FieldFetch.ts +++ b/packages/frontend-core/src/fetch/FieldFetch.ts @@ -1,7 +1,10 @@ import { Row } from "@budibase/types" import DataFetch from "./DataFetch" -export interface FieldDatasource { +type Types = "field" | "queryarray" | "jsonarray" + +export interface FieldDatasource { + 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 extends DataFetch< + FieldDatasource, FieldDefinition > { async getDefinition(): Promise { diff --git a/packages/frontend-core/src/fetch/GroupUserFetch.ts b/packages/frontend-core/src/fetch/GroupUserFetch.ts index a14623bfb0..e07e5331d4 100644 --- a/packages/frontend-core/src/fetch/GroupUserFetch.ts +++ b/packages/frontend-core/src/fetch/GroupUserFetch.ts @@ -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, }, }) diff --git a/packages/frontend-core/src/fetch/JSONArrayFetch.ts b/packages/frontend-core/src/fetch/JSONArrayFetch.ts index cae9a1e521..d746c923f8 100644 --- a/packages/frontend-core/src/fetch/JSONArrayFetch.ts +++ b/packages/frontend-core/src/fetch/JSONArrayFetch.ts @@ -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 diff --git a/packages/frontend-core/src/fetch/NestedProviderFetch.ts b/packages/frontend-core/src/fetch/NestedProviderFetch.ts index 666340610f..af121fcef8 100644 --- a/packages/frontend-core/src/fetch/NestedProviderFetch.ts +++ b/packages/frontend-core/src/fetch/NestedProviderFetch.ts @@ -2,6 +2,7 @@ import { Row, TableSchema } from "@budibase/types" import DataFetch from "./DataFetch" interface NestedProviderDatasource { + type: "provider" value?: { schema: TableSchema primaryDisplay: string diff --git a/packages/frontend-core/src/fetch/QueryArrayFetch.ts b/packages/frontend-core/src/fetch/QueryArrayFetch.ts index 9142000fe6..7f4d34aaa6 100644 --- a/packages/frontend-core/src/fetch/QueryArrayFetch.ts +++ b/packages/frontend-core/src/fetch/QueryArrayFetch.ts @@ -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 diff --git a/packages/frontend-core/src/fetch/QueryFetch.ts b/packages/frontend-core/src/fetch/QueryFetch.ts index 0754edd267..09dde86cbd 100644 --- a/packages/frontend-core/src/fetch/QueryFetch.ts +++ b/packages/frontend-core/src/fetch/QueryFetch.ts @@ -4,6 +4,7 @@ import { ExecuteQueryRequest, Query } from "@budibase/types" import { get } from "svelte/store" interface QueryDatasource { + type: "query" _id: string fields: Record & { pagination?: { diff --git a/packages/frontend-core/src/fetch/RelationshipFetch.ts b/packages/frontend-core/src/fetch/RelationshipFetch.ts index f853a753cd..89a85ab0e4 100644 --- a/packages/frontend-core/src/fetch/RelationshipFetch.ts +++ b/packages/frontend-core/src/fetch/RelationshipFetch.ts @@ -2,6 +2,7 @@ import { Table } from "@budibase/types" import DataFetch from "./DataFetch" interface RelationshipDatasource { + type: "link" tableId: string rowId: string rowTableId: string diff --git a/packages/frontend-core/src/fetch/TableFetch.ts b/packages/frontend-core/src/fetch/TableFetch.ts index f5927262cb..67cac6b6a7 100644 --- a/packages/frontend-core/src/fetch/TableFetch.ts +++ b/packages/frontend-core/src/fetch/TableFetch.ts @@ -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 { +interface TableDatasource { + type: "table" + tableId: string +} + +export default class TableFetch extends DataFetch { async determineFeatureFlags() { return { supportsSearch: true, diff --git a/packages/frontend-core/src/fetch/UserFetch.ts b/packages/frontend-core/src/fetch/UserFetch.ts index 656cd840fe..36aebac506 100644 --- a/packages/frontend-core/src/fetch/UserFetch.ts +++ b/packages/frontend-core/src/fetch/UserFetch.ts @@ -2,11 +2,7 @@ import { get } from "svelte/store" import DataFetch, { DataFetchParams } from "./DataFetch" import { TableNames } from "../constants" import { utils } from "@budibase/shared-core" -import { - BasicOperator, - SearchFilters, - SearchUsersRequest, -} from "@budibase/types" +import { SearchFilters, SearchUsersRequest } from "@budibase/types" interface UserFetchQuery { appId: string @@ -14,18 +10,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) { super({ ...opts, datasource: { + type: "user", tableId: TableNames.USERS, }, }) @@ -52,7 +52,7 @@ export default class UserFetch extends DataFetch< const finalQuery: SearchFilters = utils.isSupportedUserSearch(rest) ? rest - : { [BasicOperator.EMPTY]: { email: null } } + : {} try { const opts: SearchUsersRequest = { diff --git a/packages/frontend-core/src/fetch/ViewFetch.ts b/packages/frontend-core/src/fetch/ViewFetch.ts index b6830e7118..df00b9bbfc 100644 --- a/packages/frontend-core/src/fetch/ViewFetch.ts +++ b/packages/frontend-core/src/fetch/ViewFetch.ts @@ -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 { +export default class ViewFetch extends DataFetch { async getDefinition() { const { datasource } = this.options diff --git a/packages/frontend-core/src/fetch/ViewV2Fetch.ts b/packages/frontend-core/src/fetch/ViewV2Fetch.ts index cdd3bab6ed..aa5fbd60a2 100644 --- a/packages/frontend-core/src/fetch/ViewV2Fetch.ts +++ b/packages/frontend-core/src/fetch/ViewV2Fetch.ts @@ -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 { +interface ViewDatasource { + type: "viewV2" + id: string +} + +export default class ViewV2Fetch extends DataFetch< + ViewDatasource, + ViewV2Enriched +> { async determineFeatureFlags() { return { supportsSearch: true, diff --git a/packages/frontend-core/src/fetch/index.ts b/packages/frontend-core/src/fetch/index.ts index c1f35abef2..d80aa10df6 100644 --- a/packages/frontend-core/src/fetch/index.ts +++ b/packages/frontend-core/src/fetch/index.ts @@ -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 = ({ 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, diff --git a/packages/frontend-core/src/index.ts b/packages/frontend-core/src/index.ts index 37951dc776..c0baa63ab6 100644 --- a/packages/frontend-core/src/index.ts +++ b/packages/frontend-core/src/index.ts @@ -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" diff --git a/packages/types/src/api/web/global/self.ts b/packages/types/src/api/web/global/self.ts index 517559d1ca..9d99a1f1a5 100644 --- a/packages/types/src/api/web/global/self.ts +++ b/packages/types/src/api/web/global/self.ts @@ -15,5 +15,5 @@ export interface GetGlobalSelfResponse extends User { license: License budibaseAccess: boolean accountPortalAccess: boolean - csrfToken: boolean + csrfToken: string } diff --git a/packages/types/src/documents/app/screen.ts b/packages/types/src/documents/app/screen.ts index b2cedf31a9..a8c32118d3 100644 --- a/packages/types/src/documents/app/screen.ts +++ b/packages/types/src/documents/app/screen.ts @@ -33,11 +33,6 @@ export interface ScreenRoutesViewOutput extends Document { export type ScreenRoutingJson = Record< string, { - subpaths: Record< - string, - { - screens: Record - } - > + subpaths: Record } > diff --git a/packages/types/src/sdk/featureFlag.ts b/packages/types/src/sdk/featureFlag.ts index 7b61b70772..996d3bba8d 100644 --- a/packages/types/src/sdk/featureFlag.ts +++ b/packages/types/src/sdk/featureFlag.ts @@ -1,9 +1,15 @@ export enum FeatureFlag { USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR", + + // Account-portal + DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL", } export const FeatureFlagDefaults = { [FeatureFlag.USE_ZOD_VALIDATOR]: false, + + // Account-portal + [FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false, } export type FeatureFlags = typeof FeatureFlagDefaults