From 0c12217b14d806c6439694dbd8de11922b3e2ef2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 20:11:28 +0100 Subject: [PATCH 01/43] Convert screens store to ts --- .../src/stores/builder/{screens.js => screens.ts} | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) rename packages/builder/src/stores/builder/{screens.js => screens.ts} (98%) diff --git a/packages/builder/src/stores/builder/screens.js b/packages/builder/src/stores/builder/screens.ts similarity index 98% rename from packages/builder/src/stores/builder/screens.js rename to packages/builder/src/stores/builder/screens.ts index 8298a1469d..5c1fa6da1a 100644 --- a/packages/builder/src/stores/builder/screens.js +++ b/packages/builder/src/stores/builder/screens.ts @@ -13,13 +13,19 @@ import { import { createHistoryStore } from "@/stores/builder/history" import { API } from "@/api" import { BudiStore } from "../BudiStore" +import { Screen } from "@budibase/types" -export const INITIAL_SCREENS_STATE = { +interface ScreenState { + screens: Screen[] + selectedScreenId: string | null +} + +export const INITIAL_SCREENS_STATE: ScreenState = { screens: [], selectedScreenId: null, } -export class ScreenStore extends BudiStore { +export class ScreenStore extends BudiStore { constructor() { super(INITIAL_SCREENS_STATE) From 8160914bb33954af7d49b5eb493551c4f571fa86 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 20:26:37 +0100 Subject: [PATCH 02/43] Initial typings --- .../builder/src/stores/builder/components.ts | 13 +-- .../builder/src/stores/builder/screens.ts | 87 ++++++++++--------- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts index 9ad9a75f84..e60d4e80a4 100644 --- a/packages/builder/src/stores/builder/components.ts +++ b/packages/builder/src/stores/builder/components.ts @@ -57,7 +57,7 @@ interface ComponentSetting { interface ComponentState { components: Record customComponents: string[] - selectedComponentId: string | null + selectedComponentId: string | null | undefined componentToPaste?: Component | null settingsCache: Record selectedScreenId?: string | null @@ -478,10 +478,11 @@ export class ComponentStore extends BudiStore { extras._children = [] } + const $selectedScreen = get(selectedScreen) // Add step name to form steps - if (componentName.endsWith("/formstep")) { + if (componentName.endsWith("/formstep") && $selectedScreen) { const parentForm = findClosestMatchingComponent( - get(selectedScreen).props, + $selectedScreen.props, get(selectedComponent)._id, (component: Component) => component._component.endsWith("/form") ) @@ -608,7 +609,7 @@ export class ComponentStore extends BudiStore { async patch( patchFn: (component: Component, screen: Screen) => any, componentId?: string, - screenId?: string + screenId?: string | null ) { // Use selected component by default if (!componentId || !screenId) { @@ -840,7 +841,7 @@ export class ComponentStore extends BudiStore { getPrevious() { const state = get(this.store) const componentId = state.selectedComponentId - const screen = get(selectedScreen) + const screen = get(selectedScreen)! const parent = findComponentParent(screen.props, componentId) const index = parent?._children.findIndex( (x: Component) => x._id === componentId @@ -889,7 +890,7 @@ export class ComponentStore extends BudiStore { const state = get(this.store) const component = get(selectedComponent) const componentId = component?._id - const screen = get(selectedScreen) + const screen = get(selectedScreen)! const parent = findComponentParent(screen.props, componentId) const index = parent?._children.findIndex( (x: Component) => x._id === componentId diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts index 5c1fa6da1a..15c4c9b88f 100644 --- a/packages/builder/src/stores/builder/screens.ts +++ b/packages/builder/src/stores/builder/screens.ts @@ -13,11 +13,11 @@ import { import { createHistoryStore } from "@/stores/builder/history" import { API } from "@/api" import { BudiStore } from "../BudiStore" -import { Screen } from "@budibase/types" +import { Component, Screen } from "@budibase/types" interface ScreenState { screens: Screen[] - selectedScreenId: string | null + selectedScreenId: string | null | undefined } export const INITIAL_SCREENS_STATE: ScreenState = { @@ -43,7 +43,7 @@ export class ScreenStore extends BudiStore { this.sequentialScreenPatch = this.sequentialScreenPatch.bind(this) this.removeCustomLayout = this.removeCustomLayout.bind(this) - this.history = createHistoryStore({ + const history = createHistoryStore({ getDoc: id => get(this.store).screens?.find(screen => screen._id === id), selectDoc: this.select, afterAction: () => { @@ -57,8 +57,8 @@ export class ScreenStore extends BudiStore { }, }) - this.delete = this.history.wrapDeleteDoc(this.deleteScreen) - this.save = this.history.wrapSaveDoc(this.saveScreen) + this.delete = history.wrapDeleteDoc(this.deleteScreen) + this.save = history.wrapSaveDoc(this.saveScreen) } /** @@ -72,7 +72,7 @@ export class ScreenStore extends BudiStore { * Replace ALL store screens with application package screens * @param {object} pkg */ - syncAppScreens(pkg) { + syncAppScreens(pkg: { screens: Screen[] }) { this.update(state => ({ ...state, screens: [...pkg.screens], @@ -85,7 +85,7 @@ export class ScreenStore extends BudiStore { * @param {string} screenId * @returns */ - select(screenId) { + select(screenId: string) { // Check this screen exists const state = get(this.store) const screen = state.screens.find(screen => screen._id === screenId) @@ -113,12 +113,12 @@ export class ScreenStore extends BudiStore { * @throws Will throw an error containing the name of the component causing * the invalid screen state */ - validate(screen) { + validate(screen: Screen) { // Recursive function to find any illegal children in component trees const findIllegalChild = ( - component, - illegalChildren = [], - legalDirectChildren = [] + component: Component, + illegalChildren: string[] = [], + legalDirectChildren: string[] = [] ) => { const type = component._component @@ -178,7 +178,7 @@ export class ScreenStore extends BudiStore { const illegalChild = findIllegalChild(screen.props) if (illegalChild) { const def = componentStore.getDefinition(illegalChild) - throw `You can't place a ${def.name} here` + throw `You can't place a ${def?.name} here` } } @@ -189,7 +189,7 @@ export class ScreenStore extends BudiStore { * @param {object} screen * @returns {object} */ - async saveScreen(screen) { + async saveScreen(screen: Screen): Promise { const appState = get(appStore) // Validate screen structure if the app supports it @@ -236,7 +236,7 @@ export class ScreenStore extends BudiStore { * After saving a screen, sync plugins and routes to the appStore * @param {object} savedScreen */ - async syncScreenData(savedScreen) { + async syncScreenData(savedScreen: Screen) { const appState = get(appStore) // If plugins changed we need to fetch the latest app metadata let usedPlugins = appState.usedPlugins @@ -262,21 +262,23 @@ export class ScreenStore extends BudiStore { * This is slightly better than just a traditional "patch" endpoint and this * supports deeply mutating the current doc rather than just appending data. */ - sequentialScreenPatch = Utils.sequential(async (patchFn, screenId) => { - const state = get(this.store) - const screen = state.screens.find(screen => screen._id === screenId) - if (!screen) { - return - } - let clone = cloneDeep(screen) - const result = patchFn(clone) + sequentialScreenPatch = Utils.sequential( + async (patchFn: (screen: Screen) => any, screenId: string) => { + const state = get(this.store) + const screen = state.screens.find(screen => screen._id === screenId) + if (!screen) { + return + } + let clone = cloneDeep(screen) + const result = patchFn(clone) - // An explicit false result means skip this change - if (result === false) { - return + // An explicit false result means skip this change + if (result === false) { + return + } + return this.save(clone) } - return this.save(clone) - }) + ) /** * @param {function} patchFn @@ -304,7 +306,7 @@ export class ScreenStore extends BudiStore { * @param {object} screen * @returns */ - async replace(screenId, screen) { + async replace(screenId: string, screen: Screen) { if (!screenId) { return } @@ -343,14 +345,14 @@ export class ScreenStore extends BudiStore { * @param {object | array} screens * @returns */ - async deleteScreen(screens) { + async deleteScreen(screens: Screen[]) { const screensToDelete = Array.isArray(screens) ? screens : [screens] // Build array of promises to speed up bulk deletions - let promises = [] - let deleteUrls = [] + let promises: Promise[] = [] + let deleteUrls: string[] = [] screensToDelete.forEach(screen => { // Delete the screen - promises.push(API.deleteScreen(screen._id, screen._rev)) + promises.push(API.deleteScreen(screen._id!, screen._rev!)) // Remove links to this screen deleteUrls.push(screen.routing.route) }) @@ -365,7 +367,10 @@ export class ScreenStore extends BudiStore { }) // Deselect the current screen if it was deleted - if (deletedIds.includes(state.selectedScreenId)) { + if ( + state.selectedScreenId && + deletedIds.includes(state.selectedScreenId) + ) { state.selectedScreenId = null componentStore.update(state => ({ ...state, @@ -395,13 +400,13 @@ export class ScreenStore extends BudiStore { * @param {any} value * @returns */ - async updateSetting(screen, name, value) { + async updateSetting(screen: Screen, name: string, value: string) { if (!screen || !name) { return } // Apply setting update - const patchFn = screen => { + const patchFn = (screen: Screen) => { if (!screen) { return false } @@ -428,7 +433,7 @@ export class ScreenStore extends BudiStore { ) }) if (otherHomeScreens.length && updatedScreen.routing.homeScreen) { - const patchFn = screen => { + const patchFn = (screen: Screen) => { screen.routing.homeScreen = false } for (let otherHomeScreen of otherHomeScreens) { @@ -438,11 +443,11 @@ export class ScreenStore extends BudiStore { } // Move to layouts store - async removeCustomLayout(screen) { + async removeCustomLayout(screen: Screen) { // Pull relevant settings from old layout, if required const layout = get(layoutStore).layouts.find(x => x._id === screen.layoutId) - const patchFn = screen => { - screen.layoutId = null + const patchFn = (screen: Screen) => { + delete screen.layoutId screen.showNavigation = layout?.props.navigation !== "None" screen.width = layout?.props.width || "Large" } @@ -454,9 +459,9 @@ export class ScreenStore extends BudiStore { * and up-to-date. Ensures stability after a product update. * @param {object} screen */ - async enrichEmptySettings(screen) { + async enrichEmptySettings(screen: Screen) { // Flatten the recursive component tree - const components = findAllMatchingComponents(screen.props, x => x) + const components = findAllMatchingComponents(screen.props, (x: string) => x) // Iterate over all components and run checks components.forEach(component => { From ce322211b993e770017f859c6030c037485d77a7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 20:29:08 +0100 Subject: [PATCH 03/43] Initial convert history store --- packages/builder/src/stores/builder/{history.js => history.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/builder/src/stores/builder/{history.js => history.ts} (99%) diff --git a/packages/builder/src/stores/builder/history.js b/packages/builder/src/stores/builder/history.ts similarity index 99% rename from packages/builder/src/stores/builder/history.js rename to packages/builder/src/stores/builder/history.ts index 62a8ed2f97..1889b0b5f6 100644 --- a/packages/builder/src/stores/builder/history.js +++ b/packages/builder/src/stores/builder/history.ts @@ -1,4 +1,4 @@ -import * as jsonpatch from "fast-json-patch/index.mjs" +import * as jsonpatch from "fast-json-patch" import { writable, derived, get } from "svelte/store" export const Operations = { From 08f9b2046e0ff93a90146e0722359bf55b3eb20a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 20:58:44 +0100 Subject: [PATCH 04/43] Fix types --- .../builder/src/stores/builder/history.ts | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/builder/src/stores/builder/history.ts b/packages/builder/src/stores/builder/history.ts index 1889b0b5f6..e7888f7be4 100644 --- a/packages/builder/src/stores/builder/history.ts +++ b/packages/builder/src/stores/builder/history.ts @@ -1,13 +1,27 @@ import * as jsonpatch from "fast-json-patch" import { writable, derived, get } from "svelte/store" -export const Operations = { - Add: "Add", - Delete: "Delete", - Change: "Change", +export const enum Operations { + Add = "Add", + Delete = "Delete", + Change = "Change", } -export const initialState = { +interface Operator { + id: string + type: Operations + doc: any + forwardPatch?: jsonpatch.Operation[] + backwardsPatch?: jsonpatch.Operation[] +} + +interface HistoryState { + history: any[] + position: number + loading?: boolean +} + +export const initialState: HistoryState = { history: [], position: 0, loading: false, @@ -18,6 +32,11 @@ export const createHistoryStore = ({ selectDoc, beforeAction, afterAction, +}: { + getDoc: any + selectDoc: any + beforeAction: any + afterAction: any }) => { // Use a derived store to check if we are able to undo or redo any operations const store = writable(initialState) @@ -31,8 +50,8 @@ export const createHistoryStore = ({ // Wrapped versions of essential functions which we call ourselves when using // undo and redo - let saveFn - let deleteFn + let saveFn: any + let deleteFn: any /** * Internal util to set the loading flag @@ -66,14 +85,14 @@ export const createHistoryStore = ({ * For internal use only. * @param operation the operation to save */ - const saveOperation = operation => { + const saveOperation = (operation: Operator) => { store.update(state => { // Update history let history = state.history let position = state.position if (!operation.id) { // Every time a new operation occurs we discard any redo potential - operation.id = Math.random() + operation.id = Math.random().toString() history = [...history.slice(0, state.position), operation] position += 1 } else { @@ -93,8 +112,8 @@ export const createHistoryStore = ({ * @param fn the save function * @returns {function} a wrapped version of the save function */ - const wrapSaveDoc = fn => { - saveFn = async (doc, operationId) => { + const wrapSaveDoc = (fn: (doc: any) => Promise) => { + saveFn = async (doc: any, operationId: string) => { // Only works on a single doc at a time if (!doc || Array.isArray(doc)) { return @@ -141,8 +160,8 @@ export const createHistoryStore = ({ * @param fn the delete function * @returns {function} a wrapped version of the delete function */ - const wrapDeleteDoc = fn => { - deleteFn = async (doc, operationId) => { + const wrapDeleteDoc = (fn: (doc: any) => Promise) => { + deleteFn = async (doc: any, operationId: string) => { // Only works on a single doc at a time if (!doc || Array.isArray(doc)) { return From f0ec4c5e00890fae1a2896e2d772137e3ea11097 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 21:04:50 +0100 Subject: [PATCH 05/43] Use generics on history --- .../builder/src/stores/builder/automations.ts | 2 +- packages/builder/src/stores/builder/history.ts | 16 ++++++++-------- packages/builder/src/stores/builder/screens.ts | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/builder/src/stores/builder/automations.ts b/packages/builder/src/stores/builder/automations.ts index 9b20b4cd03..7a1e856369 100644 --- a/packages/builder/src/stores/builder/automations.ts +++ b/packages/builder/src/stores/builder/automations.ts @@ -1434,7 +1434,7 @@ class AutomationStore extends BudiStore { constructor() { super(initialAutomationState) this.actions = automationActions(this) - this.history = createHistoryStore({ + this.history = createHistoryStore({ getDoc: this.actions.getDefinition.bind(this), selectDoc: this.actions.select.bind(this), beforeAction: () => {}, diff --git a/packages/builder/src/stores/builder/history.ts b/packages/builder/src/stores/builder/history.ts index e7888f7be4..b2aa01c03b 100644 --- a/packages/builder/src/stores/builder/history.ts +++ b/packages/builder/src/stores/builder/history.ts @@ -7,10 +7,10 @@ export const enum Operations { Change = "Change", } -interface Operator { +interface Operator { id: string type: Operations - doc: any + doc: T forwardPatch?: jsonpatch.Operation[] backwardsPatch?: jsonpatch.Operation[] } @@ -27,16 +27,16 @@ export const initialState: HistoryState = { loading: false, } -export const createHistoryStore = ({ +export const createHistoryStore = ({ getDoc, selectDoc, beforeAction, afterAction, }: { - getDoc: any - selectDoc: any - beforeAction: any - afterAction: any + getDoc: (id: string) => T | undefined + selectDoc: (id: string) => void + beforeAction?: (operation?: Operator) => void + afterAction?: (operation?: Operator) => void }) => { // Use a derived store to check if we are able to undo or redo any operations const store = writable(initialState) @@ -85,7 +85,7 @@ export const createHistoryStore = ({ * For internal use only. * @param operation the operation to save */ - const saveOperation = (operation: Operator) => { + const saveOperation = (operation: Operator) => { store.update(state => { // Update history let history = state.history diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts index 15c4c9b88f..fff54b364d 100644 --- a/packages/builder/src/stores/builder/screens.ts +++ b/packages/builder/src/stores/builder/screens.ts @@ -18,6 +18,7 @@ import { Component, Screen } from "@budibase/types" interface ScreenState { screens: Screen[] selectedScreenId: string | null | undefined + selected?: Screen } export const INITIAL_SCREENS_STATE: ScreenState = { @@ -285,7 +286,7 @@ export class ScreenStore extends BudiStore { * @param {string | null} screenId * @returns */ - async patch(patchFn, screenId) { + async patch(patchFn, screenId: string) { // Default to the currently selected screen if (!screenId) { const state = get(this.store) From 25d450602f553fb2b31e42555085d20afbd11d3f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 21:08:31 +0100 Subject: [PATCH 06/43] Type history --- .../builder/src/stores/builder/history.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/builder/src/stores/builder/history.ts b/packages/builder/src/stores/builder/history.ts index b2aa01c03b..132dcd9ab8 100644 --- a/packages/builder/src/stores/builder/history.ts +++ b/packages/builder/src/stores/builder/history.ts @@ -1,3 +1,4 @@ +import { Document } from "@budibase/types" import * as jsonpatch from "fast-json-patch" import { writable, derived, get } from "svelte/store" @@ -7,7 +8,7 @@ export const enum Operations { Change = "Change", } -interface Operator { +interface Operator { id: string type: Operations doc: T @@ -15,19 +16,19 @@ interface Operator { backwardsPatch?: jsonpatch.Operation[] } -interface HistoryState { - history: any[] +interface HistoryState { + history: Operator[] position: number loading?: boolean } -export const initialState: HistoryState = { +export const initialState: HistoryState = { history: [], position: 0, loading: false, } -export const createHistoryStore = ({ +export const createHistoryStore = ({ getDoc, selectDoc, beforeAction, @@ -39,7 +40,7 @@ export const createHistoryStore = ({ afterAction?: (operation?: Operator) => void }) => { // Use a derived store to check if we are able to undo or redo any operations - const store = writable(initialState) + const store = writable>(initialState) const derivedStore = derived(store, $store => { return { ...$store, @@ -220,7 +221,7 @@ export const createHistoryStore = ({ // Undo ADD if (operation.type === Operations.Add) { // Try to get the latest doc version to delete - const latestDoc = getDoc(operation.doc._id) + const latestDoc = getDoc(operation.doc._id!) const doc = latestDoc || operation.doc await deleteFn(doc, operation.id) } @@ -238,7 +239,7 @@ export const createHistoryStore = ({ // Undo CHANGE else { // Get the current doc and apply the backwards patch on top of it - let doc = jsonpatch.deepClone(getDoc(operation.doc._id)) + let doc = jsonpatch.deepClone(getDoc(operation.doc._id!)) if (doc) { jsonpatch.applyPatch( doc, @@ -302,7 +303,7 @@ export const createHistoryStore = ({ // Redo DELETE else if (operation.type === Operations.Delete) { // Try to get the latest doc version to delete - const latestDoc = getDoc(operation.doc._id) + const latestDoc = getDoc(operation.doc._id!) const doc = latestDoc || operation.doc await deleteFn(doc, operation.id) } @@ -310,7 +311,7 @@ export const createHistoryStore = ({ // Redo CHANGE else { // Get the current doc and apply the forwards patch on top of it - let doc = jsonpatch.deepClone(getDoc(operation.doc._id)) + let doc = jsonpatch.deepClone(getDoc(operation.doc._id!)) if (doc) { jsonpatch.applyPatch(doc, jsonpatch.deepClone(operation.forwardPatch)) await saveFn(doc, operation.id) From 6f063f6a213226c9cfbd25865c66161b5ad9e61c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 21:12:22 +0100 Subject: [PATCH 07/43] Type anys --- packages/builder/src/stores/builder/history.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/stores/builder/history.ts b/packages/builder/src/stores/builder/history.ts index 132dcd9ab8..3f9b156498 100644 --- a/packages/builder/src/stores/builder/history.ts +++ b/packages/builder/src/stores/builder/history.ts @@ -51,8 +51,8 @@ export const createHistoryStore = ({ // Wrapped versions of essential functions which we call ourselves when using // undo and redo - let saveFn: any - let deleteFn: any + let saveFn: (doc: T, operationId: string) => Promise + let deleteFn: (doc: T, operationId: string) => Promise /** * Internal util to set the loading flag @@ -113,15 +113,15 @@ export const createHistoryStore = ({ * @param fn the save function * @returns {function} a wrapped version of the save function */ - const wrapSaveDoc = (fn: (doc: any) => Promise) => { - saveFn = async (doc: any, operationId: string) => { + const wrapSaveDoc = (fn: (doc: T) => Promise) => { + saveFn = async (doc: T, operationId: string) => { // Only works on a single doc at a time if (!doc || Array.isArray(doc)) { return } startLoading() try { - const oldDoc = getDoc(doc._id) + const oldDoc = getDoc(doc._id!) const newDoc = jsonpatch.deepClone(await fn(doc)) // Store the change @@ -161,8 +161,8 @@ export const createHistoryStore = ({ * @param fn the delete function * @returns {function} a wrapped version of the delete function */ - const wrapDeleteDoc = (fn: (doc: any) => Promise) => { - deleteFn = async (doc: any, operationId: string) => { + const wrapDeleteDoc = (fn: (doc: T) => Promise) => { + deleteFn = async (doc: T, operationId: string) => { // Only works on a single doc at a time if (!doc || Array.isArray(doc)) { return From 26955d5cd9d0168f39481cd6b3e09efb5c332daa Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 21:30:00 +0100 Subject: [PATCH 08/43] Type history --- .../builder/src/stores/builder/automations.ts | 6 ++-- .../builder/src/stores/builder/history.ts | 35 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/builder/src/stores/builder/automations.ts b/packages/builder/src/stores/builder/automations.ts index 7a1e856369..5e17b46155 100644 --- a/packages/builder/src/stores/builder/automations.ts +++ b/packages/builder/src/stores/builder/automations.ts @@ -2,7 +2,7 @@ import { derived, get } from "svelte/store" import { API } from "@/api" import { cloneDeep } from "lodash/fp" import { generate } from "shortid" -import { createHistoryStore } from "@/stores/builder/history" +import { createHistoryStore, HistoryStore } from "@/stores/builder/history" import { licensing } from "@/stores/portal" import { tables, appStore } from "@/stores/builder" import { notifications } from "@budibase/bbui" @@ -1428,13 +1428,13 @@ const automationActions = (store: AutomationStore) => ({ }) class AutomationStore extends BudiStore { - history: any + history: HistoryStore actions: ReturnType constructor() { super(initialAutomationState) this.actions = automationActions(this) - this.history = createHistoryStore({ + this.history = createHistoryStore({ getDoc: this.actions.getDefinition.bind(this), selectDoc: this.actions.select.bind(this), beforeAction: () => {}, diff --git a/packages/builder/src/stores/builder/history.ts b/packages/builder/src/stores/builder/history.ts index 3f9b156498..916e417872 100644 --- a/packages/builder/src/stores/builder/history.ts +++ b/packages/builder/src/stores/builder/history.ts @@ -1,6 +1,6 @@ import { Document } from "@budibase/types" import * as jsonpatch from "fast-json-patch" -import { writable, derived, get } from "svelte/store" +import { writable, derived, get, Readable } from "svelte/store" export const enum Operations { Add = "Add", @@ -9,7 +9,7 @@ export const enum Operations { } interface Operator { - id: string + id?: string type: Operations doc: T forwardPatch?: jsonpatch.Operation[] @@ -28,6 +28,25 @@ export const initialState: HistoryState = { loading: false, } +export interface HistoryStore + extends Readable< + HistoryState & { + canUndo: boolean + canRedo: boolean + } + > { + wrapSaveDoc: ( + fn: (doc: T) => Promise + ) => (doc: T, operationId?: string) => Promise + wrapDeleteDoc: ( + fn: (doc: T) => Promise + ) => (doc: T, operationId?: string) => Promise + + reset: () => void + undo: () => Promise + redo: () => Promise +} + export const createHistoryStore = ({ getDoc, selectDoc, @@ -38,7 +57,7 @@ export const createHistoryStore = ({ selectDoc: (id: string) => void beforeAction?: (operation?: Operator) => void afterAction?: (operation?: Operator) => void -}) => { +}): HistoryStore => { // Use a derived store to check if we are able to undo or redo any operations const store = writable>(initialState) const derivedStore = derived(store, $store => { @@ -51,8 +70,8 @@ export const createHistoryStore = ({ // Wrapped versions of essential functions which we call ourselves when using // undo and redo - let saveFn: (doc: T, operationId: string) => Promise - let deleteFn: (doc: T, operationId: string) => Promise + let saveFn: (doc: T, operationId?: string) => Promise + let deleteFn: (doc: T, operationId?: string) => Promise /** * Internal util to set the loading flag @@ -113,8 +132,8 @@ export const createHistoryStore = ({ * @param fn the save function * @returns {function} a wrapped version of the save function */ - const wrapSaveDoc = (fn: (doc: T) => Promise) => { - saveFn = async (doc: T, operationId: string) => { + const wrapSaveDoc = (fn: (doc: T) => Promise) => { + saveFn = async (doc: T, operationId?: string) => { // Only works on a single doc at a time if (!doc || Array.isArray(doc)) { return @@ -162,7 +181,7 @@ export const createHistoryStore = ({ * @returns {function} a wrapped version of the delete function */ const wrapDeleteDoc = (fn: (doc: T) => Promise) => { - deleteFn = async (doc: T, operationId: string) => { + deleteFn = async (doc: T, operationId?: string) => { // Only works on a single doc at a time if (!doc || Array.isArray(doc)) { return From 13027c6a6d890000532e4f298c821570dfd0c6ca Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 22:58:37 +0100 Subject: [PATCH 09/43] Type utils --- .../src/utils/{utils.js => utils.ts} | 71 +++++++++++++------ 1 file changed, 49 insertions(+), 22 deletions(-) rename packages/frontend-core/src/utils/{utils.js => utils.ts} (82%) diff --git a/packages/frontend-core/src/utils/utils.js b/packages/frontend-core/src/utils/utils.ts similarity index 82% rename from packages/frontend-core/src/utils/utils.js rename to packages/frontend-core/src/utils/utils.ts index f0635fbeac..0eaca6548e 100644 --- a/packages/frontend-core/src/utils/utils.js +++ b/packages/frontend-core/src/utils/utils.ts @@ -1,8 +1,10 @@ import { makePropSafe as safe } from "@budibase/string-templates" import { Helpers } from "@budibase/bbui" import { cloneDeep } from "lodash" +import { SearchFilterGroup, UISearchFilter } from "@budibase/types" -export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) +export const sleep = (ms: number) => + new Promise(resolve => setTimeout(resolve, ms)) /** * Utility to wrap an async function and ensure all invocations happen @@ -10,10 +12,15 @@ export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) * @param fn the async function to run * @return {Promise} a sequential version of the function */ -export const sequential = fn => { - let queue = [] - return (...params) => { - return new Promise((resolve, reject) => { +export const sequential = < + TReturn, + TFunction extends (...args: any[]) => Promise +>( + fn: TFunction +): ((...args: Parameters) => Promise) => { + let queue: any[] = [] + return (...params: Parameters) => { + return new Promise((resolve, reject) => { queue.push(async () => { let data, error try { @@ -45,9 +52,9 @@ export const sequential = fn => { * @param minDelay the minimum delay between invocations * @returns a debounced version of the callback */ -export const debounce = (callback, minDelay = 1000) => { - let timeout - return async (...params) => { +export const debounce = (callback: Function, minDelay = 1000) => { + let timeout: NodeJS.Timeout + return async (...params: any[]) => { return new Promise(resolve => { if (timeout) { clearTimeout(timeout) @@ -70,11 +77,11 @@ export const debounce = (callback, minDelay = 1000) => { * @param minDelay * @returns {Function} a throttled version function */ -export const throttle = (callback, minDelay = 1000) => { - let lastParams +export const throttle = (callback: Function, minDelay = 1000) => { + let lastParams: any[] let stalled = false let pending = false - const invoke = (...params) => { + const invoke = (...params: any[]) => { lastParams = params if (stalled) { pending = true @@ -98,10 +105,10 @@ export const throttle = (callback, minDelay = 1000) => { * @param callback the function to run * @returns {Function} */ -export const domDebounce = callback => { +export const domDebounce = (callback: Function) => { let active = false - let lastParams - return (...params) => { + let lastParams: any[] + return (...params: any[]) => { lastParams = params if (!active) { active = true @@ -119,7 +126,17 @@ export const domDebounce = callback => { * * @param {any} props * */ -export const buildFormBlockButtonConfig = props => { +export const buildFormBlockButtonConfig = (props?: { + _id: string + actionType: string + dataSource: { resourceId: string } + notificationOverride: boolean + actionUrl: string + showDeleteButton: boolean + deleteButtonLabel: string + showSaveButton: boolean + saveButtonLabel: string +}) => { const { _id, actionType, @@ -227,7 +244,11 @@ export const buildFormBlockButtonConfig = props => { const defaultButtons = [] - if (["Update", "Create"].includes(actionType) && showSaveButton !== false) { + if ( + actionType && + ["Update", "Create"].includes(actionType) && + showSaveButton !== false + ) { defaultButtons.push({ text: saveText || "Save", _id: Helpers.uuid(), @@ -251,7 +272,13 @@ export const buildFormBlockButtonConfig = props => { return defaultButtons } -export const buildMultiStepFormBlockDefaultProps = props => { +export const buildMultiStepFormBlockDefaultProps = (props?: { + _id: string + stepCount: number + currentStep: number + actionType: string + dataSource: { resourceId: string } +}) => { const { _id, stepCount, currentStep, actionType, dataSource } = props || {} // Sanity check @@ -361,7 +388,7 @@ export const buildMultiStepFormBlockDefaultProps = props => { * @param {Object} filter UI filter * @returns {Object} parsed filter */ -export function parseFilter(filter) { +export function parseFilter(filter: UISearchFilter) { if (!filter?.groups) { return filter } @@ -369,13 +396,13 @@ export function parseFilter(filter) { const update = cloneDeep(filter) update.groups = update.groups - .map(group => { - group.filters = group.filters.filter(filter => { + ?.map(group => { + group.filters = group.filters?.filter((filter: any) => { return filter.field && filter.operator }) - return group.filters.length ? group : null + return group.filters?.length ? group : null }) - .filter(group => group) + .filter((group): group is SearchFilterGroup => !!group) return update } From 058b3def3a1e235361da31da883bc41a39273864 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 23:03:01 +0100 Subject: [PATCH 10/43] Type remaining screens --- .../builder/src/stores/builder/components.ts | 1 + .../builder/src/stores/builder/screens.ts | 28 ++++++++++--------- packages/frontend-core/src/utils/utils.ts | 18 ++++++------ 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts index e60d4e80a4..8fa7f5113d 100644 --- a/packages/builder/src/stores/builder/components.ts +++ b/packages/builder/src/stores/builder/components.ts @@ -41,6 +41,7 @@ interface ComponentDefinition { settings?: ComponentSetting[] features?: Record typeSupportPresets?: Record + illegalChildren?: string[] } interface ComponentSetting { diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts index fff54b364d..d87d12f23c 100644 --- a/packages/builder/src/stores/builder/screens.ts +++ b/packages/builder/src/stores/builder/screens.ts @@ -27,6 +27,9 @@ export const INITIAL_SCREENS_STATE: ScreenState = { } export class ScreenStore extends BudiStore { + save: (doc: Screen) => Promise + delete: (doc: Screen) => Promise + constructor() { super(INITIAL_SCREENS_STATE) @@ -120,7 +123,7 @@ export class ScreenStore extends BudiStore { component: Component, illegalChildren: string[] = [], legalDirectChildren: string[] = [] - ) => { + ): string | undefined => { const type = component._component if (illegalChildren.includes(type)) { @@ -145,13 +148,6 @@ export class ScreenStore extends BudiStore { } const definition = componentStore.getDefinition(component._component) - // Reset whitelist for direct children - legalDirectChildren = [] - if (definition?.legalDirectChildren?.length) { - legalDirectChildren = definition.legalDirectChildren.map(x => { - return `@budibase/standard-components/${x}` - }) - } // Append blacklisted components and remove duplicates if (definition?.illegalChildren?.length) { @@ -264,7 +260,10 @@ export class ScreenStore extends BudiStore { * supports deeply mutating the current doc rather than just appending data. */ sequentialScreenPatch = Utils.sequential( - async (patchFn: (screen: Screen) => any, screenId: string) => { + async ( + patchFn: (screen: Screen) => any, + screenId: string + ): Promise => { const state = get(this.store) const screen = state.screens.find(screen => screen._id === screenId) if (!screen) { @@ -286,7 +285,10 @@ export class ScreenStore extends BudiStore { * @param {string | null} screenId * @returns */ - async patch(patchFn, screenId: string) { + async patch( + patchFn: (screen: Screen) => void, + screenId: string | undefined | null + ) { // Default to the currently selected screen if (!screenId) { const state = get(this.store) @@ -346,8 +348,8 @@ export class ScreenStore extends BudiStore { * @param {object | array} screens * @returns */ - async deleteScreen(screens: Screen[]) { - const screensToDelete = Array.isArray(screens) ? screens : [screens] + async deleteScreen(screen: Screen) { + const screensToDelete = [screen] // Build array of promises to speed up bulk deletions let promises: Promise[] = [] let deleteUrls: string[] = [] @@ -387,7 +389,7 @@ export class ScreenStore extends BudiStore { return state }) - return null + return } /** diff --git a/packages/frontend-core/src/utils/utils.ts b/packages/frontend-core/src/utils/utils.ts index 0eaca6548e..124f0f03b9 100644 --- a/packages/frontend-core/src/utils/utils.ts +++ b/packages/frontend-core/src/utils/utils.ts @@ -127,15 +127,15 @@ export const domDebounce = (callback: Function) => { * @param {any} props * */ export const buildFormBlockButtonConfig = (props?: { - _id: string - actionType: string - dataSource: { resourceId: string } - notificationOverride: boolean - actionUrl: string - showDeleteButton: boolean - deleteButtonLabel: string - showSaveButton: boolean - saveButtonLabel: string + _id?: string + actionType?: string + dataSource?: { resourceId: string } + notificationOverride?: boolean + actionUrl?: string + showDeleteButton?: boolean + deleteButtonLabel?: string + showSaveButton?: boolean + saveButtonLabel?: string }) => { const { _id, From 4671633bfeb6086113b2f06658d12e8d4acae419 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 23:14:45 +0100 Subject: [PATCH 11/43] Fix types --- .../builder/src/stores/builder/componentTreeNodes.ts | 2 +- packages/builder/src/stores/builder/websocket.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/stores/builder/componentTreeNodes.ts b/packages/builder/src/stores/builder/componentTreeNodes.ts index 420c540e37..367bc1f0d9 100644 --- a/packages/builder/src/stores/builder/componentTreeNodes.ts +++ b/packages/builder/src/stores/builder/componentTreeNodes.ts @@ -49,7 +49,7 @@ export class ComponentTreeNodesStore extends BudiStore { // Will ensure all parents of a node are expanded so that it is visible in the tree makeNodeVisible(componentId: string) { - const selectedScreen: Screen = get(selectedScreenStore) + const selectedScreen: Screen = get(selectedScreenStore)! const path = findComponentPath(selectedScreen.props, componentId) diff --git a/packages/builder/src/stores/builder/websocket.ts b/packages/builder/src/stores/builder/websocket.ts index bd9e2c8d4d..b9b6c0eb63 100644 --- a/packages/builder/src/stores/builder/websocket.ts +++ b/packages/builder/src/stores/builder/websocket.ts @@ -16,7 +16,14 @@ import { auth, appsStore } from "@/stores/portal" import { screenStore } from "./screens" import { SocketEvent, BuilderSocketEvent, helpers } from "@budibase/shared-core" import { notifications } from "@budibase/bbui" -import { Automation, Datasource, Role, Table, UIUser } from "@budibase/types" +import { + Automation, + Datasource, + Role, + Screen, + Table, + UIUser, +} from "@budibase/types" export const createBuilderWebsocket = (appId: string) => { const socket = createWebsocket("/socket/builder") From 79180bfac5512304dbf1d91234dec774f5bff42f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 09:48:58 +0100 Subject: [PATCH 12/43] Cleanups --- packages/builder/src/stores/builder/componentTreeNodes.ts | 6 +++--- packages/builder/src/stores/builder/components.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/stores/builder/componentTreeNodes.ts b/packages/builder/src/stores/builder/componentTreeNodes.ts index 367bc1f0d9..775985abdc 100644 --- a/packages/builder/src/stores/builder/componentTreeNodes.ts +++ b/packages/builder/src/stores/builder/componentTreeNodes.ts @@ -1,7 +1,7 @@ import { get } from "svelte/store" import { selectedScreen as selectedScreenStore } from "./screens" import { findComponentPath } from "@/helpers/components" -import { Screen, Component } from "@budibase/types" +import { Component } from "@budibase/types" import { BudiStore, PersistenceType } from "@/stores/BudiStore" interface OpenNodesState { @@ -49,9 +49,9 @@ export class ComponentTreeNodesStore extends BudiStore { // Will ensure all parents of a node are expanded so that it is visible in the tree makeNodeVisible(componentId: string) { - const selectedScreen: Screen = get(selectedScreenStore)! + const selectedScreen = get(selectedScreenStore) - const path = findComponentPath(selectedScreen.props, componentId) + const path = findComponentPath(selectedScreen?.props, componentId) const componentIds = path.map((component: Component) => component._id) diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts index 8fa7f5113d..d831d35ab6 100644 --- a/packages/builder/src/stores/builder/components.ts +++ b/packages/builder/src/stores/builder/components.ts @@ -610,14 +610,14 @@ export class ComponentStore extends BudiStore { async patch( patchFn: (component: Component, screen: Screen) => any, componentId?: string, - screenId?: string | null + screenId?: string ) { // Use selected component by default if (!componentId || !screenId) { const state = get(this.store) componentId = componentId ?? state.selectedComponentId ?? undefined const screenState = get(screenStore) - screenId = screenId || screenState.selectedScreenId + screenId = (screenId || screenState.selectedScreenId) ?? undefined } if (!componentId || !screenId || !patchFn) { return From f5e8ed6e37cf7afeb3378a4038d8c01bf7d35fc4 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 09:50:02 +0100 Subject: [PATCH 13/43] Fix tests --- packages/builder/src/stores/builder/history.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/builder/src/stores/builder/history.ts b/packages/builder/src/stores/builder/history.ts index 916e417872..6568c5abca 100644 --- a/packages/builder/src/stores/builder/history.ts +++ b/packages/builder/src/stores/builder/history.ts @@ -9,7 +9,7 @@ export const enum Operations { } interface Operator { - id?: string + id?: number type: Operations doc: T forwardPatch?: jsonpatch.Operation[] @@ -22,7 +22,7 @@ interface HistoryState { loading?: boolean } -export const initialState: HistoryState = { +export const initialState = { history: [], position: 0, loading: false, @@ -37,10 +37,10 @@ export interface HistoryStore > { wrapSaveDoc: ( fn: (doc: T) => Promise - ) => (doc: T, operationId?: string) => Promise + ) => (doc: T, operationId?: number) => Promise wrapDeleteDoc: ( fn: (doc: T) => Promise - ) => (doc: T, operationId?: string) => Promise + ) => (doc: T, operationId?: number) => Promise reset: () => void undo: () => Promise @@ -70,8 +70,8 @@ export const createHistoryStore = ({ // Wrapped versions of essential functions which we call ourselves when using // undo and redo - let saveFn: (doc: T, operationId?: string) => Promise - let deleteFn: (doc: T, operationId?: string) => Promise + let saveFn: (doc: T, operationId?: number) => Promise + let deleteFn: (doc: T, operationId?: number) => Promise /** * Internal util to set the loading flag @@ -112,7 +112,7 @@ export const createHistoryStore = ({ let position = state.position if (!operation.id) { // Every time a new operation occurs we discard any redo potential - operation.id = Math.random().toString() + operation.id = Math.random() history = [...history.slice(0, state.position), operation] position += 1 } else { @@ -133,7 +133,7 @@ export const createHistoryStore = ({ * @returns {function} a wrapped version of the save function */ const wrapSaveDoc = (fn: (doc: T) => Promise) => { - saveFn = async (doc: T, operationId?: string) => { + saveFn = async (doc: T, operationId?: number) => { // Only works on a single doc at a time if (!doc || Array.isArray(doc)) { return @@ -181,7 +181,7 @@ export const createHistoryStore = ({ * @returns {function} a wrapped version of the delete function */ const wrapDeleteDoc = (fn: (doc: T) => Promise) => { - deleteFn = async (doc: T, operationId?: string) => { + deleteFn = async (doc: T, operationId?: number) => { // Only works on a single doc at a time if (!doc || Array.isArray(doc)) { return From 15ffe58a8164b5b5b8f1d70cedcd4cd6e7321922 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 10:38:50 +0100 Subject: [PATCH 14/43] Fix undefined --- packages/builder/src/stores/builder/screens.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts index d87d12f23c..a31c8fb728 100644 --- a/packages/builder/src/stores/builder/screens.ts +++ b/packages/builder/src/stores/builder/screens.ts @@ -10,7 +10,7 @@ import { navigationStore, selectedComponent, } from "@/stores/builder" -import { createHistoryStore } from "@/stores/builder/history" +import { createHistoryStore, HistoryStore } from "@/stores/builder/history" import { API } from "@/api" import { BudiStore } from "../BudiStore" import { Component, Screen } from "@budibase/types" @@ -27,6 +27,7 @@ export const INITIAL_SCREENS_STATE: ScreenState = { } export class ScreenStore extends BudiStore { + history: HistoryStore save: (doc: Screen) => Promise delete: (doc: Screen) => Promise @@ -47,7 +48,7 @@ export class ScreenStore extends BudiStore { this.sequentialScreenPatch = this.sequentialScreenPatch.bind(this) this.removeCustomLayout = this.removeCustomLayout.bind(this) - const history = createHistoryStore({ + this.history = createHistoryStore({ getDoc: id => get(this.store).screens?.find(screen => screen._id === id), selectDoc: this.select, afterAction: () => { @@ -61,8 +62,8 @@ export class ScreenStore extends BudiStore { }, }) - this.delete = history.wrapDeleteDoc(this.deleteScreen) - this.save = history.wrapSaveDoc(this.saveScreen) + this.delete = this.history.wrapDeleteDoc(this.deleteScreen) + this.save = this.history.wrapSaveDoc(this.saveScreen) } /** From 8842bfe3b400371c2395bd3de360fbfacffec3e2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 15:50:36 +0100 Subject: [PATCH 15/43] Fix types --- .../src/stores/builder/componentTreeNodes.ts | 2 +- packages/builder/src/stores/builder/screens.ts | 7 +++++-- packages/frontend-core/src/utils/utils.ts | 14 ++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/builder/src/stores/builder/componentTreeNodes.ts b/packages/builder/src/stores/builder/componentTreeNodes.ts index 2f34e79d01..17793c9672 100644 --- a/packages/builder/src/stores/builder/componentTreeNodes.ts +++ b/packages/builder/src/stores/builder/componentTreeNodes.ts @@ -1,7 +1,7 @@ import { get } from "svelte/store" import { selectedScreen as selectedScreenStore } from "./screens" import { findComponentPath } from "@/helpers/components" -import { Component } from "@budibase/types" +import { Component, Screen } from "@budibase/types" import { BudiStore, PersistenceType } from "@/stores/BudiStore" interface OpenNodesState { diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts index 5163c6a3ea..04405feb70 100644 --- a/packages/builder/src/stores/builder/screens.ts +++ b/packages/builder/src/stores/builder/screens.ts @@ -35,7 +35,7 @@ export const initialScreenState: ScreenState = { export class ScreenStore extends BudiStore { history: any delete: any - save: any + save: (screen: Screen) => Promise constructor() { super(initialScreenState) @@ -281,7 +281,10 @@ export class ScreenStore extends BudiStore { * supports deeply mutating the current doc rather than just appending data. */ sequentialScreenPatch = Utils.sequential( - async (patchFn: (screen: Screen) => any, screenId: string) => { + async ( + patchFn: (screen: Screen) => boolean, + screenId: string + ): Promise => { const state = get(this.store) const screen = state.screens.find(screen => screen._id === screenId) if (!screen) { diff --git a/packages/frontend-core/src/utils/utils.ts b/packages/frontend-core/src/utils/utils.ts index 5f7f7063a7..b75ae02d07 100644 --- a/packages/frontend-core/src/utils/utils.ts +++ b/packages/frontend-core/src/utils/utils.ts @@ -17,12 +17,13 @@ export const sequential = < TFunction extends (...args: any[]) => Promise >( fn: TFunction -): ((...args: Parameters) => Promise) => { - let queue: any[] = [] - return (...params: Parameters) => { - return new Promise((resolve, reject) => { +): TFunction => { + let queue: (() => Promise)[] = [] + const result = (...params: Parameters) => { + return new Promise((resolve, reject) => { queue.push(async () => { - let data, error + let data: TReturn | undefined + let error: unknown try { data = await fn(...params) } catch (err) { @@ -35,7 +36,7 @@ export const sequential = < if (error) { reject(error) } else { - resolve(data) + resolve(data!) } }) if (queue.length === 1) { @@ -43,6 +44,7 @@ export const sequential = < } }) } + return result as TFunction } /** From 3f10a3404cf4a3305f04a4fa455844c5cec964aa Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 16:00:44 +0100 Subject: [PATCH 16/43] Types --- packages/builder/src/stores/builder/screens.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts index 04405feb70..646f598403 100644 --- a/packages/builder/src/stores/builder/screens.ts +++ b/packages/builder/src/stores/builder/screens.ts @@ -10,7 +10,7 @@ import { navigationStore, selectedComponent, } from "@/stores/builder" -import { createHistoryStore } from "@/stores/builder/history" +import { createHistoryStore, HistoryStore } from "@/stores/builder/history" import { API } from "@/api" import { BudiStore } from "../BudiStore" import { @@ -33,8 +33,8 @@ export const initialScreenState: ScreenState = { // Review the nulls export class ScreenStore extends BudiStore { - history: any - delete: any + history: HistoryStore + delete: (screens: Screen) => Promise save: (screen: Screen) => Promise constructor() { @@ -365,10 +365,10 @@ export class ScreenStore extends BudiStore { * Any deleted screens will then have their routes/links purged * * Wrapped by {@link delete} - * @param {Screen | Screen[]} screens + * @param {Screen } screens */ - async deleteScreen(screens: Screen | Screen[]) { - const screensToDelete = Array.isArray(screens) ? screens : [screens] + async deleteScreen(screen: Screen) { + const screensToDelete = [screen] // Build array of promises to speed up bulk deletions let promises: Promise[] = [] let deleteUrls: string[] = [] From d322f85acfb70499c44facda56fe5f83749faddb Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 16:02:59 +0100 Subject: [PATCH 17/43] Clean code --- packages/builder/src/stores/builder/componentTreeNodes.ts | 2 +- packages/builder/src/stores/builder/components.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/stores/builder/componentTreeNodes.ts b/packages/builder/src/stores/builder/componentTreeNodes.ts index 17793c9672..b88563de6a 100644 --- a/packages/builder/src/stores/builder/componentTreeNodes.ts +++ b/packages/builder/src/stores/builder/componentTreeNodes.ts @@ -56,7 +56,7 @@ export class ComponentTreeNodesStore extends BudiStore { return {} } - const path = findComponentPath(selectedScreen?.props, componentId) + const path = findComponentPath(selectedScreen.props, componentId) const componentIds = path.map((component: Component) => component._id) diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts index 1944096f5b..46d3e07eae 100644 --- a/packages/builder/src/stores/builder/components.ts +++ b/packages/builder/src/stores/builder/components.ts @@ -483,9 +483,8 @@ export class ComponentStore extends BudiStore { extras._children = [] } - const $selectedScreen = get(selectedScreen) // Add step name to form steps - if (componentName.endsWith("/formstep") && $selectedScreen) { + if (componentName.endsWith("/formstep")) { const parentForm = findClosestMatchingComponent( screen.props, get(selectedComponent)._id, @@ -621,7 +620,7 @@ export class ComponentStore extends BudiStore { const state = get(this.store) componentId = componentId ?? state.selectedComponentId ?? undefined const screenState = get(screenStore) - screenId = (screenId || screenState.selectedScreenId) ?? undefined + screenId = screenId || screenState.selectedScreenId } if (!componentId || !screenId || !patchFn) { return From 7f55db4bf5d820b8e42eb9e55f1fb6a677265b6e Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 29 Jan 2025 16:48:12 +0000 Subject: [PATCH 18/43] force apt update before certbot install --- hosting/single/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index e4858d4af0..ae6c55d9de 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -86,7 +86,7 @@ COPY hosting/single/ssh/sshd_config /etc/ COPY hosting/single/ssh/ssh_setup.sh /tmp # setup letsencrypt certificate -RUN apt-get install -y certbot python3-certbot-nginx +RUN apt-get update && apt-get install -y certbot python3-certbot-nginx COPY hosting/letsencrypt /app/letsencrypt RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh From df4cb7b35eb08621393e01877aa0a9c5557ec015 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 29 Jan 2025 16:54:37 +0000 Subject: [PATCH 19/43] under apt-get update --- hosting/single/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index ae6c55d9de..e4858d4af0 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -86,7 +86,7 @@ COPY hosting/single/ssh/sshd_config /etc/ COPY hosting/single/ssh/ssh_setup.sh /tmp # setup letsencrypt certificate -RUN apt-get update && apt-get install -y certbot python3-certbot-nginx +RUN apt-get install -y certbot python3-certbot-nginx COPY hosting/letsencrypt /app/letsencrypt RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh From 794acbb569754353e63e327e29198289382d9a30 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Jan 2025 18:04:27 +0100 Subject: [PATCH 20/43] Fix iife reference --- packages/server/src/jsRunner/bundles/snippets.ts | 3 +-- packages/string-templates/package.json | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/server/src/jsRunner/bundles/snippets.ts b/packages/server/src/jsRunner/bundles/snippets.ts index 8244b2eef8..343cccd36f 100644 --- a/packages/server/src/jsRunner/bundles/snippets.ts +++ b/packages/server/src/jsRunner/bundles/snippets.ts @@ -1,6 +1,5 @@ // @ts-ignore -// eslint-disable-next-line local-rules/no-budibase-imports -import { iifeWrapper } from "@budibase/string-templates/iife" +import { iifeWrapper } from "@budibase/string-templates" export default new Proxy( {}, diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index b1b4b9ef55..acd0ea4fa8 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -11,8 +11,7 @@ "require": "./dist/bundle.cjs", "import": "./dist/bundle.mjs" }, - "./package.json": "./package.json", - "./iife": "./dist/iife.mjs" + "./package.json": "./package.json" }, "scripts": { "build": "tsc --emitDeclarationOnly && rollup -c", From 52b504a607eae49e30dbe6e8900ccbb3da1dcf35 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Jan 2025 18:11:39 +0100 Subject: [PATCH 21/43] Add objectId test --- .../api/routes/tests/queries/mongodb.spec.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts index a37957fe7e..c42227b623 100644 --- a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts +++ b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts @@ -634,6 +634,28 @@ if (descriptions.length) { } }) }) + + it("should be able to select a ObjectId in a transformer", async () => { + const query = await createQuery({ + fields: { + json: {}, + extra: { + actionType: "find", + }, + }, + transformer: "return data.map(x => ({ id: x._id }))", + }) + + const result = await config.api.query.execute(query._id!) + + expect(result.data).toEqual([ + { id: expectValidId }, + { id: expectValidId }, + { id: expectValidId }, + { id: expectValidId }, + { id: expectValidId }, + ]) + }) }) it("should throw an error if the incorrect actionType is specified", async () => { From 11b6547c21de504d1d914f340123b41f10929027 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Jan 2025 20:50:09 +0100 Subject: [PATCH 22/43] Inject polyfills --- .../bundles/bson-polyfills.ivm.bundle.js | 6 ++++ packages/server/src/jsRunner/bundles/index.ts | 2 ++ .../server/src/jsRunner/vm/isolated-vm.ts | 31 +++++++------------ 3 files changed, 19 insertions(+), 20 deletions(-) create mode 100644 packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js diff --git a/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js b/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js new file mode 100644 index 0000000000..e8147c63da --- /dev/null +++ b/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js @@ -0,0 +1,6 @@ +function atob(...args){ + return atobCB(...args) +} +function btoa(...args){ + return btoaCB(...args) +} diff --git a/packages/server/src/jsRunner/bundles/index.ts b/packages/server/src/jsRunner/bundles/index.ts index b62adac1cc..3a00ee96cc 100644 --- a/packages/server/src/jsRunner/bundles/index.ts +++ b/packages/server/src/jsRunner/bundles/index.ts @@ -5,6 +5,7 @@ export const enum BundleType { BSON = "bson", SNIPPETS = "snippets", BUFFER = "buffer", + BSON_POLYFILLS = "bson_polyfills", } const bundleSourceFile: Record = { @@ -12,6 +13,7 @@ const bundleSourceFile: Record = { [BundleType.BSON]: "./bson.ivm.bundle.js", [BundleType.SNIPPETS]: "./snippets.ivm.bundle.js", [BundleType.BUFFER]: "./buffer.ivm.bundle.js", + [BundleType.BSON_POLYFILLS]: "./bson-polyfills.ivm.bundle.js", } const bundleSourceCode: Partial> = {} diff --git a/packages/server/src/jsRunner/vm/isolated-vm.ts b/packages/server/src/jsRunner/vm/isolated-vm.ts index 3863be742d..65a2615ed7 100644 --- a/packages/server/src/jsRunner/vm/isolated-vm.ts +++ b/packages/server/src/jsRunner/vm/isolated-vm.ts @@ -173,30 +173,21 @@ export class IsolatedVM implements VM { return result } ), + atobCB: new ivm.Callback((...args: Parameters) => { + const result = atob(...args) + return result + }), + btoaCB: new ivm.Callback((...args: Parameters) => { + const result = btoa(...args) + return result + }), }) - // "Polyfilling" text decoder. `bson.deserialize` requires decoding. We are creating a bridge function so we don't need to inject the full library - const textDecoderPolyfill = class TextDecoderMock { - constructorArgs - - constructor(...constructorArgs: any) { - this.constructorArgs = constructorArgs - } - - decode(...input: any) { - // @ts-expect-error - this is going to run in the isolate, where this function will be available - // eslint-disable-next-line no-undef - return textDecoderCb({ - constructorArgs: this.constructorArgs, - functionArgs: input, - }) - } - } - .toString() - .replace(/TextDecoderMock/, "TextDecoder") + // "Polyfilling" text decoder and other utils. `bson.deserialize` requires decoding. We are creating a bridge function so we don't need to inject the full library + const bsonPolyfills = loadBundle(BundleType.BSON_POLYFILLS) const script = this.isolate.compileScriptSync( - `${textDecoderPolyfill};${bsonSource}` + `${bsonPolyfills};${bsonSource}` ) script.runSync(this.vm, { timeout: this.invocationTimeout, release: false }) new Promise(() => { From 9ff29c794e906202550dbce0e4621c02810bfa2c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Jan 2025 21:01:19 +0100 Subject: [PATCH 23/43] Add TextDecoder polyfill --- .prettierignore | 3 ++- .../bundles/bson-polyfills.ivm.bundle.js | 23 +++++++++++++++---- .../server/src/jsRunner/vm/isolated-vm.ts | 5 ++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.prettierignore b/.prettierignore index b1ee287391..b0f9f8cdbf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,4 +9,5 @@ packages/backend-core/coverage packages/builder/.routify packages/sdk/sdk packages/pro/coverage -**/*.ivm.bundle.js \ No newline at end of file +**/*.ivm.bundle.js +!**/bson-polyfills.ivm.bundle.js \ No newline at end of file diff --git a/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js b/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js index e8147c63da..33a3e9deaa 100644 --- a/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js +++ b/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js @@ -1,6 +1,21 @@ -function atob(...args){ - return atobCB(...args) +function atob(...args) { + return atobCB(...args) } -function btoa(...args){ - return btoaCB(...args) +function btoa(...args) { + return btoaCB(...args) +} + +class TextDecoder { + constructorArgs + + constructor(...constructorArgs) { + this.constructorArgs = constructorArgs + } + + decode(...input) { + return textDecoderCb({ + constructorArgs: this.constructorArgs, + functionArgs: input, + }) + } } diff --git a/packages/server/src/jsRunner/vm/isolated-vm.ts b/packages/server/src/jsRunner/vm/isolated-vm.ts index 65a2615ed7..4b17d52dba 100644 --- a/packages/server/src/jsRunner/vm/isolated-vm.ts +++ b/packages/server/src/jsRunner/vm/isolated-vm.ts @@ -161,6 +161,8 @@ export class IsolatedVM implements VM { const bsonSource = loadBundle(BundleType.BSON) + // "Polyfilling" text decoder and other utils. `bson.deserialize` requires decoding. We are creating a bridge function so we don't need to inject the full library + const bsonPolyfills = loadBundle(BundleType.BSON_POLYFILLS) this.addToContext({ textDecoderCb: new ivm.Callback( (args: { @@ -183,9 +185,6 @@ export class IsolatedVM implements VM { }), }) - // "Polyfilling" text decoder and other utils. `bson.deserialize` requires decoding. We are creating a bridge function so we don't need to inject the full library - const bsonPolyfills = loadBundle(BundleType.BSON_POLYFILLS) - const script = this.isolate.compileScriptSync( `${bsonPolyfills};${bsonSource}` ) From 148924f82123b9a20fe28dde1327124b3eb1e5b3 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 08:47:18 +0100 Subject: [PATCH 24/43] Use polyfills directly --- .../bundles/bson-polyfills.ivm.bundle.js | 83 +++++++++++++++++-- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js b/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js index 33a3e9deaa..e7949042c8 100644 --- a/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js +++ b/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js @@ -1,8 +1,81 @@ -function atob(...args) { - return atobCB(...args) -} -function btoa(...args) { - return btoaCB(...args) +if (typeof btoa !== "function") { + var chars = { + ascii: function () { + return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" + }, + indices: function () { + if (!this.cache) { + this.cache = {} + var ascii = chars.ascii() + + for (var c = 0; c < ascii.length; c++) { + var chr = ascii[c] + this.cache[chr] = c + } + } + return this.cache + }, + } + + function atob(b64) { + var indices = chars.indices(), + pos = b64.indexOf("="), + padded = pos > -1, + len = padded ? pos : b64.length, + i = -1, + data = "" + + while (i < len) { + var code = + (indices[b64[++i]] << 18) | + (indices[b64[++i]] << 12) | + (indices[b64[++i]] << 6) | + indices[b64[++i]] + if (code !== 0) { + data += String.fromCharCode( + (code >>> 16) & 255, + (code >>> 8) & 255, + code & 255 + ) + } + } + + if (padded) { + data = data.slice(0, pos - b64.length) + } + + return data + } + + function btoa(data) { + var ascii = chars.ascii(), + len = data.length - 1, + i = -1, + b64 = "" + + while (i < len) { + var code = + (data.charCodeAt(++i) << 16) | + (data.charCodeAt(++i) << 8) | + data.charCodeAt(++i) + b64 += + ascii[(code >>> 18) & 63] + + ascii[(code >>> 12) & 63] + + ascii[(code >>> 6) & 63] + + ascii[code & 63] + } + + var pads = data.length % 3 + if (pads > 0) { + b64 = b64.slice(0, pads - 3) + + while (b64.length % 4 !== 0) { + b64 += "=" + } + } + + return b64 + } } class TextDecoder { From 7fdb22459ae9b2b015e35ecbbf2d341ea795e571 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 08:47:24 +0100 Subject: [PATCH 25/43] Use polyfills directly --- packages/server/src/jsRunner/vm/isolated-vm.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/server/src/jsRunner/vm/isolated-vm.ts b/packages/server/src/jsRunner/vm/isolated-vm.ts index 4b17d52dba..3123bcaa44 100644 --- a/packages/server/src/jsRunner/vm/isolated-vm.ts +++ b/packages/server/src/jsRunner/vm/isolated-vm.ts @@ -175,14 +175,6 @@ export class IsolatedVM implements VM { return result } ), - atobCB: new ivm.Callback((...args: Parameters) => { - const result = atob(...args) - return result - }), - btoaCB: new ivm.Callback((...args: Parameters) => { - const result = btoa(...args) - return result - }), }) const script = this.isolate.compileScriptSync( From cfe296bcc904c589a28a8bd918279ef60ab5357c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 10:55:57 +0100 Subject: [PATCH 26/43] Add polyfills --- .../bundles/bson-polyfills.ivm.bundle.js | 52 ++++++++++++++----- .../server/src/jsRunner/vm/isolated-vm.ts | 14 ----- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js b/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js index e7949042c8..94d08b84ed 100644 --- a/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js +++ b/packages/server/src/jsRunner/bundles/bson-polyfills.ivm.bundle.js @@ -78,17 +78,45 @@ if (typeof btoa !== "function") { } } -class TextDecoder { - constructorArgs - - constructor(...constructorArgs) { - this.constructorArgs = constructorArgs - } - - decode(...input) { - return textDecoderCb({ - constructorArgs: this.constructorArgs, - functionArgs: input, - }) +if (typeof TextDecoder === "undefined") { + globalThis.TextDecoder = class { + constructor(encoding = "utf8") { + if (encoding !== "utf8") { + throw new Error( + `Only UTF-8 is supported in this polyfill. Recieved: ${encoding}` + ) + } + } + decode(buffer) { + return String.fromCharCode(...buffer) + } + } +} + +if (typeof TextEncoder === "undefined") { + globalThis.TextEncoder = class { + encode(str) { + const utf8 = [] + for (const i = 0; i < str.length; i++) { + const codePoint = str.charCodeAt(i) + + if (codePoint < 0x80) { + utf8.push(codePoint) + } else if (codePoint < 0x800) { + utf8.push(0xc0 | (codePoint >> 6)) + utf8.push(0x80 | (codePoint & 0x3f)) + } else if (codePoint < 0x10000) { + utf8.push(0xe0 | (codePoint >> 12)) + utf8.push(0x80 | ((codePoint >> 6) & 0x3f)) + utf8.push(0x80 | (codePoint & 0x3f)) + } else { + utf8.push(0xf0 | (codePoint >> 18)) + utf8.push(0x80 | ((codePoint >> 12) & 0x3f)) + utf8.push(0x80 | ((codePoint >> 6) & 0x3f)) + utf8.push(0x80 | (codePoint & 0x3f)) + } + } + return new Uint8Array(utf8) + } } } diff --git a/packages/server/src/jsRunner/vm/isolated-vm.ts b/packages/server/src/jsRunner/vm/isolated-vm.ts index 3123bcaa44..37ee048dc2 100644 --- a/packages/server/src/jsRunner/vm/isolated-vm.ts +++ b/packages/server/src/jsRunner/vm/isolated-vm.ts @@ -161,21 +161,7 @@ export class IsolatedVM implements VM { const bsonSource = loadBundle(BundleType.BSON) - // "Polyfilling" text decoder and other utils. `bson.deserialize` requires decoding. We are creating a bridge function so we don't need to inject the full library const bsonPolyfills = loadBundle(BundleType.BSON_POLYFILLS) - this.addToContext({ - textDecoderCb: new ivm.Callback( - (args: { - constructorArgs: any - functionArgs: Parameters["decode"]> - }) => { - const result = new TextDecoder(...args.constructorArgs).decode( - ...args.functionArgs - ) - return result - } - ), - }) const script = this.isolate.compileScriptSync( `${bsonPolyfills};${bsonSource}` From 42eefddd5cc6b3a877368f59556cf24070c3478f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 11:26:05 +0100 Subject: [PATCH 27/43] Add tests --- .../api/routes/tests/queries/mongodb.spec.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts index c42227b623..065efc95ae 100644 --- a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts +++ b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts @@ -656,6 +656,122 @@ if (descriptions.length) { { id: expectValidId }, ]) }) + + it("can handle all bson field types", async () => { + collection = generator.guid() + await withCollection(async collection => { + await collection.insertOne({ + _id: new BSON.ObjectId("65b0123456789abcdef01234"), + stringField: "This is a string", + numberField: 42, + doubleField: new BSON.Double(42.42), + integerField: new BSON.Int32(123), + longField: new BSON.Long("9223372036854775807"), + booleanField: true, + nullField: null, + arrayField: [1, 2, 3, "four", { nested: true }], + objectField: { + nestedString: "nested", + nestedNumber: 99, + }, + dateField: { $date: "2025-01-30T12:00:00Z" }, + // timestampField: new BSON.Timestamp({ t: 1706616000, i: 1 }), + binaryField: new BSON.Binary( + new TextEncoder().encode("bufferValue") + ), + objectIdField: new BSON.ObjectId("65b0123456789abcdef01235"), + regexField: new BSON.BSONRegExp("^Hello.*", "i"), + minKeyField: new BSON.MinKey(), + maxKeyField: new BSON.MaxKey(), + decimalField: new BSON.Decimal128("12345.6789"), + codeField: new BSON.Code( + "function() { return 'Hello, World!'; }" + ), + codeWithScopeField: new BSON.Code( + "function(x) { return x * 2; }", + { x: 10 } + ), + }) + }) + + const query = await createQuery({ + fields: { + json: {}, + extra: { + actionType: "find", + collection, + }, + }, + transformer: "return data.map(x => ({ ...x }))", + }) + + const result = await config.api.query.execute(query._id!) + + expect(result.data).toEqual([ + { + _id: "65b0123456789abcdef01234", + arrayField: [ + 1, + 2, + 3, + "four", + { + nested: true, + }, + ], + binaryField: "YnVmZmVyVmFsdWU=", + booleanField: true, + codeField: { + code: "function() { return 'Hello, World!'; }", + }, + codeWithScopeField: { + code: "function(x) { return x * 2; }", + scope: { + x: 10, + }, + }, + dateField: "2025-01-30T12:00:00.000Z", + decimalField: { + bytes: { + "0": 21, + "1": 205, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 56, + "15": 48, + "2": 91, + "3": 7, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + }, + }, + doubleField: 42.42, + integerField: 123, + longField: { + high: 2147483647, + low: -1, + unsigned: false, + }, + maxKeyField: {}, + minKeyField: {}, + nullField: null, + numberField: 42, + objectField: { + nestedNumber: 99, + nestedString: "nested", + }, + objectIdField: "65b0123456789abcdef01235", + regexField: {}, + stringField: "This is a string", + }, + ]) + }) }) it("should throw an error if the incorrect actionType is specified", async () => { From cfba775636be13d97f8c2e7676e0dcc29e85a325 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 11:33:58 +0100 Subject: [PATCH 28/43] Check types --- .../api/routes/tests/queries/mongodb.spec.ts | 40 +++++-------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts index 065efc95ae..c8c12acbf9 100644 --- a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts +++ b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts @@ -674,7 +674,7 @@ if (descriptions.length) { nestedString: "nested", nestedNumber: 99, }, - dateField: { $date: "2025-01-30T12:00:00Z" }, + dateField: new Date(Date.UTC(2025, 0, 30, 12, 30, 20)), // timestampField: new BSON.Timestamp({ t: 1706616000, i: 1 }), binaryField: new BSON.Binary( new TextEncoder().encode("bufferValue") @@ -702,7 +702,12 @@ if (descriptions.length) { collection, }, }, - transformer: "return data.map(x => ({ ...x }))", + transformer: `return data.map(x => ({ + ...x, + binaryField: x.binaryField?.toString('utf8'), + decimalField: x.decimalField.toString(), + longField: x.longField.toString() + }))`, }) const result = await config.api.query.execute(query._id!) @@ -719,7 +724,7 @@ if (descriptions.length) { nested: true, }, ], - binaryField: "YnVmZmVyVmFsdWU=", + binaryField: "bufferValue", booleanField: true, codeField: { code: "function() { return 'Hello, World!'; }", @@ -730,34 +735,11 @@ if (descriptions.length) { x: 10, }, }, - dateField: "2025-01-30T12:00:00.000Z", - decimalField: { - bytes: { - "0": 21, - "1": 205, - "10": 0, - "11": 0, - "12": 0, - "13": 0, - "14": 56, - "15": 48, - "2": 91, - "3": 7, - "4": 0, - "5": 0, - "6": 0, - "7": 0, - "8": 0, - "9": 0, - }, - }, + dateField: "2025-01-30T12:30:20.000Z", + decimalField: "12345.6789", doubleField: 42.42, integerField: 123, - longField: { - high: 2147483647, - low: -1, - unsigned: false, - }, + longField: "9223372036854775807", maxKeyField: {}, minKeyField: {}, nullField: null, From 49d50e053fdc8994cddbced63c192c39bb646c40 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 11:38:04 +0100 Subject: [PATCH 29/43] Check types --- packages/server/src/api/routes/tests/queries/mongodb.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts index c8c12acbf9..63280f85c1 100644 --- a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts +++ b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts @@ -706,7 +706,8 @@ if (descriptions.length) { ...x, binaryField: x.binaryField?.toString('utf8'), decimalField: x.decimalField.toString(), - longField: x.longField.toString() + longField: x.longField.toString(), + regexField: x.regexField.toString() }))`, }) @@ -749,7 +750,7 @@ if (descriptions.length) { nestedString: "nested", }, objectIdField: "65b0123456789abcdef01235", - regexField: {}, + regexField: "/^Hello.*/i", stringField: "This is a string", }, ]) From 5d6088104d71570e58fdd299bbaa50b8de602d8b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 12:06:01 +0100 Subject: [PATCH 30/43] Improve tests --- .../server/src/api/routes/tests/queries/mongodb.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts index 63280f85c1..d74db5a7d3 100644 --- a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts +++ b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts @@ -675,7 +675,7 @@ if (descriptions.length) { nestedNumber: 99, }, dateField: new Date(Date.UTC(2025, 0, 30, 12, 30, 20)), - // timestampField: new BSON.Timestamp({ t: 1706616000, i: 1 }), + timestampField: new BSON.Timestamp({ t: 1706616000, i: 1 }), binaryField: new BSON.Binary( new TextEncoder().encode("bufferValue") ), @@ -707,7 +707,9 @@ if (descriptions.length) { binaryField: x.binaryField?.toString('utf8'), decimalField: x.decimalField.toString(), longField: x.longField.toString(), - regexField: x.regexField.toString() + regexField: x.regexField.toString(), + // TODO: currenlty not supported, it looks like there is bug in the library. Getting: Timestamp constructed from { t, i } must provide t as a number + timestampField: null }))`, }) From 79405dd3429f2368fedc745de142fd551e96d542 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 12:06:17 +0100 Subject: [PATCH 31/43] Fix test --- packages/server/src/api/routes/tests/queries/mongodb.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts index d74db5a7d3..9c4502d192 100644 --- a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts +++ b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts @@ -754,6 +754,7 @@ if (descriptions.length) { objectIdField: "65b0123456789abcdef01235", regexField: "/^Hello.*/i", stringField: "This is a string", + timestampField: null, }, ]) }) From 768522b41055a87fb1abf2b0d36c48f142a73dcf Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 12:12:28 +0100 Subject: [PATCH 32/43] Rename --- packages/server/src/api/routes/tests/queries/mongodb.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts index 9c4502d192..37af4e74e1 100644 --- a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts +++ b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts @@ -657,7 +657,7 @@ if (descriptions.length) { ]) }) - it("can handle all bson field types", async () => { + it("can handle all bson field types with transformers", async () => { collection = generator.guid() await withCollection(async collection => { await collection.insertOne({ From 735b8206ce737706b062327bb601f0b5084895ba Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 12:39:25 +0100 Subject: [PATCH 33/43] Types --- packages/frontend-core/src/utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-core/src/utils/utils.ts b/packages/frontend-core/src/utils/utils.ts index b75ae02d07..85cc5de54b 100644 --- a/packages/frontend-core/src/utils/utils.ts +++ b/packages/frontend-core/src/utils/utils.ts @@ -55,7 +55,7 @@ export const sequential = < * @returns a debounced version of the callback */ export const debounce = (callback: Function, minDelay = 1000) => { - let timeout: NodeJS.Timeout + let timeout: ReturnType return async (...params: any[]) => { return new Promise(resolve => { if (timeout) { From e94bbbde34ba4db234598fab49fbd56e79e0fd7b Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 30 Jan 2025 12:36:38 +0000 Subject: [PATCH 34/43] Bump version to 3.3.4 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 8d7460f053..b08a471b05 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.3.3", + "version": "3.3.4", "npmClient": "yarn", "concurrency": 20, "command": { From 2dc3a693a8c588da2036cbe5d8de43601d8e4e42 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 30 Jan 2025 15:22:04 +0000 Subject: [PATCH 35/43] Bump version to 3.3.5 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index b08a471b05..c16b958d24 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.3.4", + "version": "3.3.5", "npmClient": "yarn", "concurrency": 20, "command": { From 3b0c4d1008a13a33bb1c5ec68b3f6cf5531169cb Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 31 Jan 2025 09:08:19 +0100 Subject: [PATCH 36/43] Use existing definitions instead of using the manifest --- packages/builder/src/helpers/screen.ts | 46 ------------- .../src/stores/builder/screenComponent.ts | 69 ++++++++++++++++--- 2 files changed, 60 insertions(+), 55 deletions(-) delete mode 100644 packages/builder/src/helpers/screen.ts diff --git a/packages/builder/src/helpers/screen.ts b/packages/builder/src/helpers/screen.ts deleted file mode 100644 index 296a597adb..0000000000 --- a/packages/builder/src/helpers/screen.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Component, Screen, ScreenProps } from "@budibase/types" -import clientManifest from "@budibase/client/manifest.json" - -export function findComponentsBySettingsType( - screen: Screen, - type: string | string[] -) { - const typesArray = Array.isArray(type) ? type : [type] - - const result: { - component: Component - setting: { - type: string - key: string - } - }[] = [] - function recurseFieldComponentsInChildren(component: ScreenProps) { - if (!component) { - return - } - - const definition = getManifestDefinition(component) - const setting = - "settings" in definition && - definition.settings.find((s: any) => typesArray.includes(s.type)) - if (setting && "type" in setting) { - result.push({ - component, - setting: { type: setting.type!, key: setting.key! }, - }) - } - component._children?.forEach(child => { - recurseFieldComponentsInChildren(child) - }) - } - - recurseFieldComponentsInChildren(screen?.props) - return result -} - -function getManifestDefinition(component: Component) { - const componentType = component._component.split("/").slice(-1)[0] - const definition = - clientManifest[componentType as keyof typeof clientManifest] - return definition -} diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index 226d233090..640d4e6db3 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -1,13 +1,18 @@ -import { derived } from "svelte/store" +import { derived, get } from "svelte/store" import { tables } from "./tables" import { selectedScreen } from "./screens" import { viewsV2 } from "./viewsV2" -import { findComponentsBySettingsType } from "@/helpers/screen" -import { UIDatasourceType, Screen } from "@budibase/types" +import { + UIDatasourceType, + Screen, + Component, + ScreenProps, +} from "@budibase/types" import { queries } from "./queries" import { views } from "./views" import { bindings, featureFlag } from "@/helpers" import { getBindableProperties } from "@/dataBinding" +import { ComponentDefinition, componentStore } from "./components" function reduceBy( key: TKey, @@ -38,11 +43,15 @@ const validationKeyByType: Record = { } export const screenComponentErrors = derived( - [selectedScreen, tables, views, viewsV2, queries], - ([$selectedScreen, $tables, $views, $viewsV2, $queries]): Record< - string, - string[] - > => { + [selectedScreen, tables, views, viewsV2, queries, componentStore], + ([ + $selectedScreen, + $tables, + $views, + $viewsV2, + $queries, + $componentStore, + ]): Record => { if (!featureFlag.isEnabled("CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS")) { return {} } @@ -51,9 +60,11 @@ export const screenComponentErrors = derived( datasources: Record ) { const result: Record = {} + for (const { component, setting } of findComponentsBySettingsType( screen, - ["table", "dataSource"] + ["table", "dataSource"], + $componentStore.components )) { const componentSettings = component[setting.key] if (!componentSettings) { @@ -111,3 +122,43 @@ export const screenComponentErrors = derived( return getInvalidDatasources($selectedScreen, datasources) } ) + +function findComponentsBySettingsType( + screen: Screen, + type: string | string[], + definitions: Record +) { + const typesArray = Array.isArray(type) ? type : [type] + + const result: { + component: Component + setting: { + type: string + key: string + } + }[] = [] + + function recurseFieldComponentsInChildren(component: ScreenProps) { + if (!component) { + return + } + + const definition = definitions[component._component] + + const setting = definition?.settings?.find((s: any) => + typesArray.includes(s.type) + ) + if (setting && "type" in setting) { + result.push({ + component, + setting: { type: setting.type!, key: setting.key! }, + }) + } + component._children?.forEach(child => { + recurseFieldComponentsInChildren(child) + }) + } + + recurseFieldComponentsInChildren(screen?.props) + return result +} From fdb16f463e7028eb71d180f41b75b7fdd1232821 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 31 Jan 2025 09:08:54 +0100 Subject: [PATCH 37/43] Don't allow importing the manifest --- eslint-local-rules/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eslint-local-rules/index.js b/eslint-local-rules/index.js index 9348706399..d9d894c33e 100644 --- a/eslint-local-rules/index.js +++ b/eslint-local-rules/index.js @@ -41,12 +41,11 @@ module.exports = { if ( /^@budibase\/[^/]+\/.*$/.test(importPath) && importPath !== "@budibase/backend-core/tests" && - importPath !== "@budibase/string-templates/test/utils" && - importPath !== "@budibase/client/manifest.json" + importPath !== "@budibase/string-templates/test/utils" ) { context.report({ node, - message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests, @budibase/string-templates/test/utils and @budibase/client/manifest.json.`, + message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests and @budibase/string-templates/test/utils.`, }) } }, From 4ea8b60b3b5961429b6a6d0874624261a4ce3fc1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 12:48:59 +0100 Subject: [PATCH 38/43] Renames --- packages/builder/src/stores/builder/components.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts index 90e1abfecf..043c2da275 100644 --- a/packages/builder/src/stores/builder/components.ts +++ b/packages/builder/src/stores/builder/components.ts @@ -452,7 +452,7 @@ export class ComponentStore extends BudiStore { * @returns */ createInstance( - componentName: string, + componentType: string, presetProps: any, parent: any ): Component | null { @@ -461,11 +461,13 @@ export class ComponentStore extends BudiStore { throw "A valid screen must be selected" } - const definition = this.getDefinition(componentName) + const definition = this.getDefinition(componentType) if (!definition) { return null } + let componentName = `New ${definition.friendlyName || definition.name}` + // Generate basic component structure let instance: Component = { _id: Helpers.uuid(), @@ -475,7 +477,7 @@ export class ComponentStore extends BudiStore { hover: {}, active: {}, }, - _instanceName: `New ${definition.friendlyName || definition.name}`, + _instanceName: componentName, ...presetProps, } @@ -500,7 +502,7 @@ export class ComponentStore extends BudiStore { } // Add step name to form steps - if (componentName.endsWith("/formstep")) { + if (componentType.endsWith("/formstep")) { const parentForm = findClosestMatchingComponent( screen.props, get(selectedComponent)?._id, @@ -529,14 +531,14 @@ export class ComponentStore extends BudiStore { * @returns */ async create( - componentName: string, + componentType: string, presetProps: any, parent: Component, index: number ) { const state = get(this.store) const componentInstance = this.createInstance( - componentName, + componentType, presetProps, parent ) From b7ce306e6a0949fb4f34c7e15bccb2b7a1f3c00f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 16:30:55 +0100 Subject: [PATCH 39/43] Add counter --- .../builder/src/stores/builder/components.ts | 9 +++++++++ packages/builder/src/stores/builder/index.js | 3 ++- .../src/stores/builder/screenComponent.ts | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts index 043c2da275..7aedf05438 100644 --- a/packages/builder/src/stores/builder/components.ts +++ b/packages/builder/src/stores/builder/components.ts @@ -20,6 +20,7 @@ import { previewStore, tables, componentTreeNodesStore, + screenComponents, } from "@/stores/builder" import { buildFormSchema, getSchemaForDatasource } from "@/dataBinding" import { @@ -467,6 +468,14 @@ export class ComponentStore extends BudiStore { } let componentName = `New ${definition.friendlyName || definition.name}` + const $screenComponents = get(screenComponents) + + const sameNameCount = $screenComponents.filter(c => + new RegExp(`^${componentName}( \\d*)?$`).test(c._instanceName) + ).length + if (sameNameCount) { + componentName = `${componentName} ${sameNameCount + 1}` + } // Generate basic component structure let instance: Component = { diff --git a/packages/builder/src/stores/builder/index.js b/packages/builder/src/stores/builder/index.js index 54c1aa39a2..7dd7f67828 100644 --- a/packages/builder/src/stores/builder/index.js +++ b/packages/builder/src/stores/builder/index.js @@ -16,7 +16,7 @@ import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js" import { deploymentStore } from "./deployments.js" import { contextMenuStore } from "./contextMenu.js" import { snippets } from "./snippets" -import { screenComponentErrors } from "./screenComponent" +import { screenComponents, screenComponentErrors } from "./screenComponent" // Backend import { tables } from "./tables" @@ -68,6 +68,7 @@ export { snippets, rowActions, appPublished, + screenComponents, screenComponentErrors, } diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index 226d233090..56e9d311a4 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -3,11 +3,12 @@ import { tables } from "./tables" import { selectedScreen } from "./screens" import { viewsV2 } from "./viewsV2" import { findComponentsBySettingsType } from "@/helpers/screen" -import { UIDatasourceType, Screen } from "@budibase/types" +import { UIDatasourceType, Screen, Component } from "@budibase/types" import { queries } from "./queries" import { views } from "./views" import { bindings, featureFlag } from "@/helpers" import { getBindableProperties } from "@/dataBinding" +import { findAllComponents } from "@/helpers/components" function reduceBy( key: TKey, @@ -111,3 +112,16 @@ export const screenComponentErrors = derived( return getInvalidDatasources($selectedScreen, datasources) } ) + +export const screenComponents = derived( + [selectedScreen], + ([$selectedScreen]) => { + if (!$selectedScreen) { + return [] + } + const allComponents = findAllComponents( + $selectedScreen.props + ) as Component[] + return allComponents + } +) From 6f2d279f0e48c0c3557dca806ff715036b22bb33 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 16:47:04 +0100 Subject: [PATCH 40/43] Use existing utils --- packages/builder/src/helpers/duplicate.ts | 6 ++++-- .../builder/src/stores/builder/components.ts | 18 +++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/builder/src/helpers/duplicate.ts b/packages/builder/src/helpers/duplicate.ts index b4740b3e52..eb705c6525 100644 --- a/packages/builder/src/helpers/duplicate.ts +++ b/packages/builder/src/helpers/duplicate.ts @@ -76,13 +76,15 @@ export const getSequentialName = ( { getName, numberFirstItem, + separator = "", }: { getName?: (item: T) => string numberFirstItem?: boolean + separator?: string } = {} ) => { if (!prefix?.length) { - return null + return "" } const trimmedPrefix = prefix.trim() const firstName = numberFirstItem ? `${prefix}1` : trimmedPrefix @@ -107,5 +109,5 @@ export const getSequentialName = ( max = num } }) - return max === 0 ? firstName : `${prefix}${max + 1}` + return max === 0 ? firstName : `${prefix}${separator}${max + 1}` } diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts index 7aedf05438..6693cbf4dc 100644 --- a/packages/builder/src/stores/builder/components.ts +++ b/packages/builder/src/stores/builder/components.ts @@ -38,6 +38,7 @@ import { Table, } from "@budibase/types" import { utils } from "@budibase/shared-core" +import { getSequentialName } from "@/helpers/duplicate" interface Component extends ComponentType { _id: string @@ -467,15 +468,14 @@ export class ComponentStore extends BudiStore { return null } - let componentName = `New ${definition.friendlyName || definition.name}` - const $screenComponents = get(screenComponents) - - const sameNameCount = $screenComponents.filter(c => - new RegExp(`^${componentName}( \\d*)?$`).test(c._instanceName) - ).length - if (sameNameCount) { - componentName = `${componentName} ${sameNameCount + 1}` - } + const componentName = getSequentialName( + get(screenComponents), + `New ${definition.friendlyName || definition.name}`, + { + getName: c => c._instanceName, + separator: " ", + } + ) // Generate basic component structure let instance: Component = { From 2f4e798cf592d12681371d005b49c3c0279c3b20 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Jan 2025 16:57:48 +0100 Subject: [PATCH 41/43] Fix test --- packages/builder/src/helpers/tests/duplicate.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/helpers/tests/duplicate.test.ts b/packages/builder/src/helpers/tests/duplicate.test.ts index 131e76a6c2..5e956d7b1c 100644 --- a/packages/builder/src/helpers/tests/duplicate.test.ts +++ b/packages/builder/src/helpers/tests/duplicate.test.ts @@ -49,7 +49,7 @@ describe("getSequentialName", () => { it("handles nullish prefix", async () => { const name = getSequentialName([], null) - expect(name).toBe(null) + expect(name).toBe("") }) it("handles just the prefix", async () => { From a340ec0d29f0e466c769d52d76001f805fa3579c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 31 Jan 2025 09:10:07 +0100 Subject: [PATCH 42/43] Update feature flag --- packages/builder/src/stores/builder/screenComponent.ts | 2 +- packages/types/src/sdk/featureFlag.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index 684c08605a..f1a9440c02 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -53,7 +53,7 @@ export const screenComponentErrors = derived( $queries, $componentStore, ]): Record => { - if (!featureFlag.isEnabled("CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS")) { + if (!featureFlag.isEnabled("CHECK_COMPONENT_SETTINGS_ERRORS")) { return {} } function getInvalidDatasources( diff --git a/packages/types/src/sdk/featureFlag.ts b/packages/types/src/sdk/featureFlag.ts index d9f092c80a..97893a1b5e 100644 --- a/packages/types/src/sdk/featureFlag.ts +++ b/packages/types/src/sdk/featureFlag.ts @@ -1,6 +1,6 @@ export enum FeatureFlag { USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR", - CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS = "CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS", + CHECK_COMPONENT_SETTINGS_ERRORS = "CHECK_COMPONENT_SETTINGS_ERRORS", // Account-portal DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL", @@ -8,7 +8,7 @@ export enum FeatureFlag { export const FeatureFlagDefaults = { [FeatureFlag.USE_ZOD_VALIDATOR]: false, - [FeatureFlag.CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS]: false, + [FeatureFlag.CHECK_COMPONENT_SETTINGS_ERRORS]: false, // Account-portal [FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false, From 4a68d1d09be3087f63058ef47d1010608db1916b Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Fri, 31 Jan 2025 11:24:51 +0000 Subject: [PATCH 43/43] Bump version to 3.3.6 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index c16b958d24..15f405c847 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.3.5", + "version": "3.3.6", "npmClient": "yarn", "concurrency": 20, "command": {