From 428df339c4b0a6c4933fcd4221879031529a50f3 Mon Sep 17 00:00:00 2001
From: Peter Clement <peter@budibase.com>
Date: Fri, 3 Jan 2025 16:33:51 +0000
Subject: [PATCH 1/8] Theme / Row actions / Snippers stores

---
 .../builder/{rowActions.js => rowActions.ts}  | 57 +++++++++-------
 .../builder/src/stores/builder/snippets.js    | 35 ----------
 .../builder/src/stores/builder/snippets.ts    | 32 +++++++++
 packages/builder/src/stores/builder/theme.js  | 58 ----------------
 packages/builder/src/stores/builder/theme.ts  | 67 +++++++++++++++++++
 5 files changed, 132 insertions(+), 117 deletions(-)
 rename packages/builder/src/stores/builder/{rowActions.js => rowActions.ts} (71%)
 delete mode 100644 packages/builder/src/stores/builder/snippets.js
 create mode 100644 packages/builder/src/stores/builder/snippets.ts
 delete mode 100644 packages/builder/src/stores/builder/theme.js
 create mode 100644 packages/builder/src/stores/builder/theme.ts

diff --git a/packages/builder/src/stores/builder/rowActions.js b/packages/builder/src/stores/builder/rowActions.ts
similarity index 71%
rename from packages/builder/src/stores/builder/rowActions.js
rename to packages/builder/src/stores/builder/rowActions.ts
index 9cc4063b91..378579e41b 100644
--- a/packages/builder/src/stores/builder/rowActions.js
+++ b/packages/builder/src/stores/builder/rowActions.ts
@@ -6,18 +6,29 @@ import { automationStore } from "./automations"
 import { API } from "@/api"
 import { getSequentialName } from "@/helpers/duplicate"
 
-const initialState = {}
+interface RowAction {
+  id: string
+  name: string
+  tableId: string
+  allowedSources?: string[]
+}
 
-export class RowActionStore extends BudiStore {
+interface RowActionState {
+  [tableId: string]: RowAction[]
+}
+
+const initialState: RowActionState = {}
+
+export class RowActionStore extends BudiStore<RowActionState> {
   constructor() {
     super(initialState)
   }
 
   reset = () => {
-    this.store.set(initialState)
+    this.set(initialState)
   }
 
-  refreshRowActions = async sourceId => {
+  refreshRowActions = async (sourceId: string) => {
     if (!sourceId) {
       return
     }
@@ -34,26 +45,30 @@ export class RowActionStore extends BudiStore {
 
     // Fetch row actions for this table
     const res = await API.rowActions.fetch(tableId)
-    const actions = Object.values(res || {})
+    const actions = Object.values(res || {}) as RowAction[]
     this.update(state => ({
       ...state,
       [tableId]: actions,
     }))
   }
 
-  createRowAction = async (tableId, viewId, name) => {
+  createRowAction = async (tableId: string, viewId?: string, name?: string) => {
     if (!tableId) {
       return
     }
 
     // Get a unique name for this action
     if (!name) {
-      const existingRowActions = get(this.store)[tableId] || []
+      const existingRowActions = get(this)[tableId] || []
       name = getSequentialName(existingRowActions, "New row action ", {
         getName: x => x.name,
       })
     }
 
+    if (!name) {
+      return
+    }
+
     // Create the action
     const res = await API.rowActions.create(tableId, name)
 
@@ -73,41 +88,35 @@ export class RowActionStore extends BudiStore {
     return res
   }
 
-  enableView = async (tableId, rowActionId, viewId) => {
+  enableView = async (tableId: string, rowActionId: string, viewId: string) => {
     await API.rowActions.enableView(tableId, rowActionId, viewId)
     await this.refreshRowActions(tableId)
   }
 
-  disableView = async (tableId, rowActionId, viewId) => {
+  disableView = async (
+    tableId: string,
+    rowActionId: string,
+    viewId: string
+  ) => {
     await API.rowActions.disableView(tableId, rowActionId, viewId)
     await this.refreshRowActions(tableId)
   }
 
-  rename = async (tableId, rowActionId, name) => {
-    await API.rowActions.update({
-      tableId,
-      rowActionId,
-      name,
-    })
-    await this.refreshRowActions(tableId)
-    automationStore.actions.fetch()
-  }
-
-  delete = async (tableId, rowActionId) => {
+  delete = async (tableId: string, rowActionId: string) => {
     await API.rowActions.delete(tableId, rowActionId)
     await this.refreshRowActions(tableId)
     // We don't need to refresh automations as we can only delete row actions
     // from the automations store, so we already handle the state update there
   }
 
-  trigger = async (sourceId, rowActionId, rowId) => {
+  trigger = async (sourceId: string, rowActionId: string, rowId: string) => {
     await API.rowActions.trigger(sourceId, rowActionId, rowId)
   }
 }
 
 const store = new RowActionStore()
-const derivedStore = derived(store, $store => {
-  let map = {}
+const derivedStore = derived<RowActionStore, RowActionState>(store, $store => {
+  const map: RowActionState = {}
 
   // Generate an entry for every view as well
   Object.keys($store || {}).forEach(tableId => {
@@ -115,7 +124,7 @@ const derivedStore = derived(store, $store => {
     map[tableId] = $store[tableId]
     for (let action of $store[tableId]) {
       const otherSources = (action.allowedSources || []).filter(
-        sourceId => sourceId !== tableId
+        (sourceId: string) => sourceId !== tableId
       )
       for (let source of otherSources) {
         map[source] ??= []
diff --git a/packages/builder/src/stores/builder/snippets.js b/packages/builder/src/stores/builder/snippets.js
deleted file mode 100644
index 4e98ef1bdc..0000000000
--- a/packages/builder/src/stores/builder/snippets.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { writable, get } from "svelte/store"
-import { API } from "@/api"
-import { appStore } from "./app"
-
-const createsnippets = () => {
-  const store = writable([])
-
-  const syncMetadata = metadata => {
-    store.set(metadata?.snippets || [])
-  }
-
-  const saveSnippet = async updatedSnippet => {
-    const snippets = [
-      ...get(store).filter(snippet => snippet.name !== updatedSnippet.name),
-      updatedSnippet,
-    ]
-    const app = await API.saveAppMetadata(get(appStore).appId, { snippets })
-    syncMetadata(app)
-  }
-
-  const deleteSnippet = async snippetName => {
-    const snippets = get(store).filter(snippet => snippet.name !== snippetName)
-    const app = await API.saveAppMetadata(get(appStore).appId, { snippets })
-    syncMetadata(app)
-  }
-
-  return {
-    ...store,
-    syncMetadata,
-    saveSnippet,
-    deleteSnippet,
-  }
-}
-
-export const snippets = createsnippets()
diff --git a/packages/builder/src/stores/builder/snippets.ts b/packages/builder/src/stores/builder/snippets.ts
new file mode 100644
index 0000000000..a6a63f7c89
--- /dev/null
+++ b/packages/builder/src/stores/builder/snippets.ts
@@ -0,0 +1,32 @@
+import { get } from "svelte/store"
+import { API } from "@/api"
+import { appStore } from "./app"
+import { BudiStore } from "../BudiStore"
+import { Snippet, UpdateAppResponse } from "@budibase/types"
+
+export class SnippetStore extends BudiStore<Snippet[]> {
+  constructor() {
+    super([])
+  }
+
+  syncMetadata = (metadata: UpdateAppResponse) => {
+    this.set(metadata?.snippets || [])
+  }
+
+  saveSnippet = async (updatedSnippet: Snippet) => {
+    const snippets = [
+      ...get(this).filter(snippet => snippet.name !== updatedSnippet.name),
+      updatedSnippet,
+    ]
+    const app = await API.saveAppMetadata(get(appStore).appId, { snippets })
+    this.syncMetadata(app)
+  }
+
+  deleteSnippet = async (snippetName: string) => {
+    const snippets = get(this).filter(snippet => snippet.name !== snippetName)
+    const app = await API.saveAppMetadata(get(appStore).appId, { snippets })
+    this.syncMetadata(app)
+  }
+}
+
+export const snippets = new SnippetStore()
diff --git a/packages/builder/src/stores/builder/theme.js b/packages/builder/src/stores/builder/theme.js
deleted file mode 100644
index ed46e9095a..0000000000
--- a/packages/builder/src/stores/builder/theme.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import { writable, get } from "svelte/store"
-import { API } from "@/api"
-import { ensureValidTheme, DefaultAppTheme } from "@budibase/shared-core"
-
-export const createThemeStore = () => {
-  const store = writable({
-    theme: DefaultAppTheme,
-    customTheme: {},
-  })
-
-  const syncAppTheme = app => {
-    store.update(state => {
-      const theme = ensureValidTheme(app.theme, DefaultAppTheme)
-      return {
-        ...state,
-        theme,
-        customTheme: app.customTheme,
-      }
-    })
-  }
-
-  const save = async (theme, appId) => {
-    const app = await API.saveAppMetadata(appId, { theme })
-    store.update(state => {
-      state.theme = app.theme
-      return state
-    })
-  }
-
-  const saveCustom = async (theme, appId) => {
-    const updated = { ...get(store).customTheme, ...theme }
-    const app = await API.saveAppMetadata(appId, { customTheme: updated })
-    store.update(state => {
-      state.customTheme = app.customTheme
-      return state
-    })
-  }
-
-  const syncMetadata = metadata => {
-    const { theme, customTheme } = metadata
-    store.update(state => ({
-      ...state,
-      theme: ensureValidTheme(theme, DefaultAppTheme),
-      customTheme,
-    }))
-  }
-
-  return {
-    subscribe: store.subscribe,
-    update: store.update,
-    syncMetadata,
-    syncAppTheme,
-    save,
-    saveCustom,
-  }
-}
-
-export const themeStore = createThemeStore()
diff --git a/packages/builder/src/stores/builder/theme.ts b/packages/builder/src/stores/builder/theme.ts
new file mode 100644
index 0000000000..b3ac202a5d
--- /dev/null
+++ b/packages/builder/src/stores/builder/theme.ts
@@ -0,0 +1,67 @@
+import { get } from "svelte/store"
+import { API } from "@/api"
+import { BudiStore } from "../BudiStore"
+import { ensureValidTheme, DefaultAppTheme } from "@budibase/shared-core"
+import { App, UpdateAppResponse, Theme, AppCustomTheme } from "@budibase/types"
+
+interface ThemeState {
+  theme: Theme
+  customTheme: AppCustomTheme
+}
+
+export class ThemeStore extends BudiStore<ThemeState> {
+  constructor() {
+    super({
+      theme: DefaultAppTheme as Theme,
+      customTheme: {},
+    })
+  }
+
+  syncAppTheme = (app: App) => {
+    this.update(state => {
+      const theme = ensureValidTheme(
+        app.theme as Theme | undefined,
+        DefaultAppTheme
+      ) as Theme
+      return {
+        ...state,
+        theme,
+        customTheme: app.customTheme || {},
+      }
+    })
+  }
+
+  save = async (theme: Theme, appId: string) => {
+    const app = await API.saveAppMetadata(appId, { theme })
+    this.update(state => ({
+      ...state,
+      theme: ensureValidTheme(
+        app.theme as Theme | undefined,
+        DefaultAppTheme
+      ) as Theme,
+    }))
+  }
+
+  saveCustom = async (theme: Partial<AppCustomTheme>, appId: string) => {
+    const updated = { ...get(this).customTheme, ...theme }
+    const app = await API.saveAppMetadata(appId, { customTheme: updated })
+    this.update(state => ({
+      ...state,
+      customTheme: app.customTheme || {},
+    }))
+  }
+
+  syncMetadata = (metadata: UpdateAppResponse) => {
+    const { theme, customTheme } = metadata
+    this.update(state => ({
+      ...state,
+      theme: ensureValidTheme(
+        theme as Theme | undefined,
+        DefaultAppTheme
+      ) as Theme,
+      customTheme: customTheme || {},
+    }))
+  }
+}
+
+export const themeStore = new ThemeStore()

From 47c87a6650ef5b25b07d3be9b0d5d91ccfcfdcd5 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 4 Jan 2025 02:27:34 +0000
Subject: [PATCH 2/8] Bump next from 14.2.15 to 14.2.21 in
 /examples/nextjs-api-sales

Bumps [next](https://github.com/vercel/next.js) from 14.2.15 to 14.2.21.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.15...v14.2.21)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 examples/nextjs-api-sales/package.json |   2 +-
 examples/nextjs-api-sales/yarn.lock    | 108 ++++++++++++-------------
 2 files changed, 55 insertions(+), 55 deletions(-)

diff --git a/examples/nextjs-api-sales/package.json b/examples/nextjs-api-sales/package.json
index 1050c6b75e..d50e7d4eb5 100644
--- a/examples/nextjs-api-sales/package.json
+++ b/examples/nextjs-api-sales/package.json
@@ -10,7 +10,7 @@
   },
   "dependencies": {
     "bulma": "^0.9.3",
-    "next": "14.2.15",
+    "next": "14.2.21",
     "node-fetch": "^3.2.10",
     "sass": "^1.52.3",
     "react": "17.0.2",
diff --git a/examples/nextjs-api-sales/yarn.lock b/examples/nextjs-api-sales/yarn.lock
index b595a148bf..a10fccb2e9 100644
--- a/examples/nextjs-api-sales/yarn.lock
+++ b/examples/nextjs-api-sales/yarn.lock
@@ -46,10 +46,10 @@
   resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
   integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
 
-"@next/env@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.15.tgz#06d984e37e670d93ddd6790af1844aeb935f332f"
-  integrity sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==
+"@next/env@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.21.tgz#09ff0813d29c596397e141205d4f5fd5c236bdd0"
+  integrity sha512-lXcwcJd5oR01tggjWJ6SrNNYFGuOOMB9c251wUNkjCpkoXOPkDeF/15c3mnVlBqrW4JJXb2kVxDFhC4GduJt2A==
 
 "@next/eslint-plugin-next@12.1.0":
   version "12.1.0"
@@ -58,50 +58,50 @@
   dependencies:
     glob "7.1.7"
 
-"@next/swc-darwin-arm64@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.15.tgz#6386d585f39a1c490c60b72b1f76612ba4434347"
-  integrity sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==
+"@next/swc-darwin-arm64@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.21.tgz#32a31992aace1440981df9cf7cb3af7845d94fec"
+  integrity sha512-HwEjcKsXtvszXz5q5Z7wCtrHeTTDSTgAbocz45PHMUjU3fBYInfvhR+ZhavDRUYLonm53aHZbB09QtJVJj8T7g==
 
-"@next/swc-darwin-x64@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.15.tgz#b7baeedc6a28f7545ad2bc55adbab25f7b45cb89"
-  integrity sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==
+"@next/swc-darwin-x64@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.21.tgz#5ab4b3f6685b6b52f810d0f5cf6e471480ddffdb"
+  integrity sha512-TSAA2ROgNzm4FhKbTbyJOBrsREOMVdDIltZ6aZiKvCi/v0UwFmwigBGeqXDA97TFMpR3LNNpw52CbVelkoQBxA==
 
-"@next/swc-linux-arm64-gnu@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.15.tgz#fa13c59d3222f70fb4cb3544ac750db2c6e34d02"
-  integrity sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==
+"@next/swc-linux-arm64-gnu@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.21.tgz#8a0e1fa887aef19ca218af2af515d0a5ee67ba3f"
+  integrity sha512-0Dqjn0pEUz3JG+AImpnMMW/m8hRtl1GQCNbO66V1yp6RswSTiKmnHf3pTX6xMdJYSemf3O4Q9ykiL0jymu0TuA==
 
-"@next/swc-linux-arm64-musl@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.15.tgz#30e45b71831d9a6d6d18d7ac7d611a8d646a17f9"
-  integrity sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==
+"@next/swc-linux-arm64-musl@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.21.tgz#ddad844406b42fa8965fe11250abc85c1fe0fd05"
+  integrity sha512-Ggfw5qnMXldscVntwnjfaQs5GbBbjioV4B4loP+bjqNEb42fzZlAaK+ldL0jm2CTJga9LynBMhekNfV8W4+HBw==
 
-"@next/swc-linux-x64-gnu@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.15.tgz#5065db17fc86f935ad117483f21f812dc1b39254"
-  integrity sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==
+"@next/swc-linux-x64-gnu@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.21.tgz#db55fd666f9ba27718f65caa54b622a912cdd16b"
+  integrity sha512-uokj0lubN1WoSa5KKdThVPRffGyiWlm/vCc/cMkWOQHw69Qt0X1o3b2PyLLx8ANqlefILZh1EdfLRz9gVpG6tg==
 
-"@next/swc-linux-x64-musl@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.15.tgz#3c4a4568d8be7373a820f7576cf33388b5dab47e"
-  integrity sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==
+"@next/swc-linux-x64-musl@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.21.tgz#dddb850353624efcd58c4c4e30ad8a1aab379642"
+  integrity sha512-iAEBPzWNbciah4+0yI4s7Pce6BIoxTQ0AGCkxn/UBuzJFkYyJt71MadYQkjPqCQCJAFQ26sYh7MOKdU+VQFgPg==
 
-"@next/swc-win32-arm64-msvc@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.15.tgz#fb812cc4ca0042868e32a6a021da91943bb08b98"
-  integrity sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==
+"@next/swc-win32-arm64-msvc@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.21.tgz#290012ee57b196d3d2d04853e6bf0179cae9fbaf"
+  integrity sha512-plykgB3vL2hB4Z32W3ktsfqyuyGAPxqwiyrAi2Mr8LlEUhNn9VgkiAl5hODSBpzIfWweX3er1f5uNpGDygfQVQ==
 
-"@next/swc-win32-ia32-msvc@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.15.tgz#ec26e6169354f8ced240c1427be7fd485c5df898"
-  integrity sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==
+"@next/swc-win32-ia32-msvc@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.21.tgz#c959135a78cab18cca588d11d1e33bcf199590d4"
+  integrity sha512-w5bacz4Vxqrh06BjWgua3Yf7EMDb8iMcVhNrNx8KnJXt8t+Uu0Zg4JHLDL/T7DkTCEEfKXO/Er1fcfWxn2xfPA==
 
-"@next/swc-win32-x64-msvc@14.2.15":
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.15.tgz#18d68697002b282006771f8d92d79ade9efd35c4"
-  integrity sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==
+"@next/swc-win32-x64-msvc@14.2.21":
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.21.tgz#21ff892286555b90538a7d1b505ea21a005d6ead"
+  integrity sha512-sT6+llIkzpsexGYZq8cjjthRyRGe5cJVhqh12FmlbxHqna6zsDDK8UNaV7g41T6atFHCJUPeLb3uyAwrBwy0NA==
 
 "@nodelib/fs.scandir@2.1.5":
   version "2.1.5"
@@ -1253,12 +1253,12 @@ natural-compare@^1.4.0:
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
 
-next@14.2.15:
-  version "14.2.15"
-  resolved "https://registry.yarnpkg.com/next/-/next-14.2.15.tgz#348e5603e22649775d19c785c09a89c9acb5189a"
-  integrity sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==
+next@14.2.21:
+  version "14.2.21"
+  resolved "https://registry.yarnpkg.com/next/-/next-14.2.21.tgz#f6da9e2abba1a0e4ca7a5273825daf06632554ba"
+  integrity sha512-rZmLwucLHr3/zfDMYbJXbw0ZeoBpirxkXuvsJbk7UPorvPYZhP7vq7aHbKnU7dQNCYIimRrbB2pp3xmf+wsYUg==
   dependencies:
-    "@next/env" "14.2.15"
+    "@next/env" "14.2.21"
     "@swc/helpers" "0.5.5"
     busboy "1.6.0"
     caniuse-lite "^1.0.30001579"
@@ -1266,15 +1266,15 @@ next@14.2.15:
     postcss "8.4.31"
     styled-jsx "5.1.1"
   optionalDependencies:
-    "@next/swc-darwin-arm64" "14.2.15"
-    "@next/swc-darwin-x64" "14.2.15"
-    "@next/swc-linux-arm64-gnu" "14.2.15"
-    "@next/swc-linux-arm64-musl" "14.2.15"
-    "@next/swc-linux-x64-gnu" "14.2.15"
-    "@next/swc-linux-x64-musl" "14.2.15"
-    "@next/swc-win32-arm64-msvc" "14.2.15"
-    "@next/swc-win32-ia32-msvc" "14.2.15"
-    "@next/swc-win32-x64-msvc" "14.2.15"
+    "@next/swc-darwin-arm64" "14.2.21"
+    "@next/swc-darwin-x64" "14.2.21"
+    "@next/swc-linux-arm64-gnu" "14.2.21"
+    "@next/swc-linux-arm64-musl" "14.2.21"
+    "@next/swc-linux-x64-gnu" "14.2.21"
+    "@next/swc-linux-x64-musl" "14.2.21"
+    "@next/swc-win32-arm64-msvc" "14.2.21"
+    "@next/swc-win32-ia32-msvc" "14.2.21"
+    "@next/swc-win32-x64-msvc" "14.2.21"
 
 node-domexception@^1.0.0:
   version "1.0.0"

From a58c75e328f0b4aa0f2ebfb47a1df3275d2b541d Mon Sep 17 00:00:00 2001
From: Sam Rose <hello@samwho.dev>
Date: Mon, 6 Jan 2025 12:11:14 +0000
Subject: [PATCH 3/8] Expose feature flags type in packages/type so it can be
 exposed to the frontend.

---
 .../backend-core/src/context/mainContext.ts   |   8 +-
 packages/backend-core/src/context/types.ts    |   2 +-
 .../backend-core/src/features/features.ts     | 135 ++++--------------
 .../src/features/tests/features.spec.ts       |  28 +---
 packages/pro                                  |   2 +-
 .../src/api/controllers/row/staticFormula.ts  |   4 +-
 packages/server/src/automations/actions.ts    |   6 +-
 .../server/src/automations/steps/openai.ts    |   4 +-
 .../server/src/middleware/zod-validator.ts    |   2 +-
 packages/types/src/api/web/global/self.ts     |   3 +-
 packages/types/src/sdk/featureFlag.ts         |  10 +-
 .../worker/src/api/controllers/global/self.ts |   3 +-
 12 files changed, 57 insertions(+), 150 deletions(-)

diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts
index 64ba240fa5..e5f20882d3 100644
--- a/packages/backend-core/src/context/mainContext.ts
+++ b/packages/backend-core/src/context/mainContext.ts
@@ -385,17 +385,17 @@ export function getCurrentContext(): ContextMap | undefined {
   }
 }
 
-export function getFeatureFlags<T extends Record<string, any>>(
+export function getFeatureFlags(
   key: string
-): T | undefined {
+): Record<string, boolean> | undefined {
   const context = getCurrentContext()
   if (!context) {
     return undefined
   }
-  return context.featureFlagCache?.[key] as T
+  return context.featureFlagCache?.[key]
 }
 
-export function setFeatureFlags(key: string, value: Record<string, any>) {
+export function setFeatureFlags(key: string, value: Record<string, boolean>) {
   const context = getCurrentContext()
   if (!context) {
     return
diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts
index 5549a47ff7..23598b951e 100644
--- a/packages/backend-core/src/context/types.ts
+++ b/packages/backend-core/src/context/types.ts
@@ -20,7 +20,7 @@ export type ContextMap = {
     clients: Record<string, GoogleSpreadsheet>
   }
   featureFlagCache?: {
-    [key: string]: Record<string, any>
+    [key: string]: Record<string, boolean>
   }
   viewToTableCache?: Record<string, Table>
 }
diff --git a/packages/backend-core/src/features/features.ts b/packages/backend-core/src/features/features.ts
index 650254fcb2..772bcf5860 100644
--- a/packages/backend-core/src/features/features.ts
+++ b/packages/backend-core/src/features/features.ts
@@ -2,9 +2,10 @@ import env from "../environment"
 import * as crypto from "crypto"
 import * as context from "../context"
 import { PostHog, PostHogOptions } from "posthog-node"
-import { FeatureFlag } from "@budibase/types"
 import tracer from "dd-trace"
 import { Duration } from "../utils"
+import { cloneDeep } from "lodash"
+import { FeatureFlagDefaults } from "@budibase/types"
 
 let posthog: PostHog | undefined
 export function init(opts?: PostHogOptions) {
@@ -30,74 +31,6 @@ export function shutdown() {
   posthog?.shutdown()
 }
 
-export abstract class Flag<T> {
-  static boolean(defaultValue: boolean): Flag<boolean> {
-    return new BooleanFlag(defaultValue)
-  }
-
-  static string(defaultValue: string): Flag<string> {
-    return new StringFlag(defaultValue)
-  }
-
-  static number(defaultValue: number): Flag<number> {
-    return new NumberFlag(defaultValue)
-  }
-
-  protected constructor(public defaultValue: T) {}
-
-  abstract parse(value: any): T
-}
-
-type UnwrapFlag<F> = F extends Flag<infer U> ? U : never
-
-export type FlagValues<T> = {
-  [K in keyof T]: UnwrapFlag<T[K]>
-}
-
-type KeysOfType<T, U> = {
-  [K in keyof T]: T[K] extends Flag<U> ? K : never
-}[keyof T]
-
-class BooleanFlag extends Flag<boolean> {
-  parse(value: any) {
-    if (typeof value === "string") {
-      return ["true", "t", "1"].includes(value.toLowerCase())
-    }
-
-    if (typeof value === "boolean") {
-      return value
-    }
-
-    throw new Error(`could not parse value "${value}" as boolean`)
-  }
-}
-
-class StringFlag extends Flag<string> {
-  parse(value: any) {
-    if (typeof value === "string") {
-      return value
-    }
-    throw new Error(`could not parse value "${value}" as string`)
-  }
-}
-
-class NumberFlag extends Flag<number> {
-  parse(value: any) {
-    if (typeof value === "number") {
-      return value
-    }
-
-    if (typeof value === "string") {
-      const parsed = parseFloat(value)
-      if (!isNaN(parsed)) {
-        return parsed
-      }
-    }
-
-    throw new Error(`could not parse value "${value}" as number`)
-  }
-}
-
 export interface EnvFlagEntry {
   tenantId: string
   key: string
@@ -120,7 +53,7 @@ export function parseEnvFlags(flags: string): EnvFlagEntry[] {
   return result
 }
 
-export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
+export class FlagSet<T extends { [name: string]: boolean }> {
   // This is used to safely cache flags sets in the current request context.
   // Because multiple sets could theoretically exist, we don't want the cache of
   // one to leak into another.
@@ -130,34 +63,25 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
     this.setId = crypto.randomUUID()
   }
 
-  defaults(): FlagValues<T> {
-    return Object.keys(this.flagSchema).reduce((acc, key) => {
-      const typedKey = key as keyof T
-      acc[typedKey] = this.flagSchema[key].defaultValue
-      return acc
-    }, {} as FlagValues<T>)
+  defaults(): T {
+    return cloneDeep(this.flagSchema)
   }
 
   isFlagName(name: string | number | symbol): name is keyof T {
     return this.flagSchema[name as keyof T] !== undefined
   }
 
-  async get<K extends keyof T>(key: K): Promise<FlagValues<T>[K]> {
+  async isEnabled<K extends keyof T>(key: K): Promise<T[K]> {
     const flags = await this.fetch()
     return flags[key]
   }
 
-  async isEnabled<K extends KeysOfType<T, boolean>>(key: K): Promise<boolean> {
-    const flags = await this.fetch()
-    return flags[key]
-  }
-
-  async fetch(): Promise<FlagValues<T>> {
+  async fetch(): Promise<T> {
     return await tracer.trace("features.fetch", async span => {
-      const cachedFlags = context.getFeatureFlags<FlagValues<T>>(this.setId)
+      const cachedFlags = context.getFeatureFlags(this.setId)
       if (cachedFlags) {
         span?.addTags({ fromCache: true })
-        return cachedFlags
+        return cachedFlags as T
       }
 
       const tags: Record<string, any> = {}
@@ -189,7 +113,7 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
 
         // @ts-expect-error - TS does not like you writing into a generic type,
         // but we know that it's okay in this case because it's just an object.
-        flagValues[key as keyof FlagValues] = value
+        flagValues[key as keyof T] = value
         tags[`flags.${key}.source`] = "environment"
       }
 
@@ -217,11 +141,11 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
         tags[`readFromPostHog`] = true
 
         const personProperties: Record<string, string> = { tenantId }
-        const posthogFlags = await posthog.getAllFlagsAndPayloads(userId, {
+        const posthogFlags = await posthog.getAllFlags(userId, {
           personProperties,
         })
 
-        for (const [name, value] of Object.entries(posthogFlags.featureFlags)) {
+        for (const [name, value] of Object.entries(posthogFlags)) {
           if (!this.isFlagName(name)) {
             // We don't want an unexpected PostHog flag to break the app, so we
             // just log it and continue.
@@ -229,19 +153,20 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
             continue
           }
 
+          if (typeof value !== "boolean") {
+            console.warn(`Invalid value for posthog flag "${name}": ${value}`)
+            continue
+          }
+
           if (flagValues[name] === true || specificallySetFalse.has(name)) {
             // If the flag is already set to through environment variables, we
             // don't want to override it back to false here.
             continue
           }
 
-          const payload = posthogFlags.featureFlagPayloads?.[name]
-          const flag = this.flagSchema[name]
           try {
-            // @ts-expect-error - TS does not like you writing into a generic
-            // type, but we know that it's okay in this case because it's just
-            // an object.
-            flagValues[name] = flag.parse(payload || value)
+            // @ts-expect-error - TS does not like you writing into a generic type.
+            flagValues[name] = value
             tags[`flags.${name}.source`] = "posthog"
           } catch (err) {
             // We don't want an invalid PostHog flag to break the app, so we just
@@ -262,18 +187,12 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
   }
 }
 
-// This is the primary source of truth for feature flags. If you want to add a
-// new flag, add it here and use the `fetch` and `get` functions to access it.
-// All of the machinery in this file is to make sure that flags have their
-// default values set correctly and their types flow through the system.
-const flagsConfig: Record<FeatureFlag, Flag<any>> = {
-  [FeatureFlag.DEFAULT_VALUES]: Flag.boolean(true),
-  [FeatureFlag.AUTOMATION_BRANCHING]: Flag.boolean(true),
-  [FeatureFlag.AI_CUSTOM_CONFIGS]: Flag.boolean(true),
-  [FeatureFlag.BUDIBASE_AI]: Flag.boolean(true),
-  [FeatureFlag.USE_ZOD_VALIDATOR]: Flag.boolean(env.isDev()),
-}
-export const flags = new FlagSet(flagsConfig)
+export const flags = new FlagSet(FeatureFlagDefaults)
 
-type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
-export type FeatureFlags = UnwrapPromise<ReturnType<typeof flags.fetch>>
+export async function isEnabled(flag: keyof typeof FeatureFlagDefaults) {
+  return await flags.isEnabled(flag)
+}
+
+export async function all() {
+  return await flags.fetch()
+}
diff --git a/packages/backend-core/src/features/tests/features.spec.ts b/packages/backend-core/src/features/tests/features.spec.ts
index ced874f4af..f918347eea 100644
--- a/packages/backend-core/src/features/tests/features.spec.ts
+++ b/packages/backend-core/src/features/tests/features.spec.ts
@@ -1,5 +1,5 @@
 import { IdentityContext, IdentityType } from "@budibase/types"
-import { Flag, FlagSet, FlagValues, init, shutdown } from "../"
+import { FlagSet, init, shutdown } from "../"
 import * as context from "../../context"
 import environment, { withEnv } from "../../environment"
 import nodeFetch from "node-fetch"
@@ -7,10 +7,8 @@ import nock from "nock"
 import * as crypto from "crypto"
 
 const schema = {
-  TEST_BOOLEAN: Flag.boolean(false),
-  TEST_STRING: Flag.string("default value"),
-  TEST_NUMBER: Flag.number(0),
-  TEST_BOOLEAN_DEFAULT_TRUE: Flag.boolean(true),
+  TEST_BOOLEAN: false,
+  TEST_BOOLEAN_DEFAULT_TRUE: true,
 }
 const flags = new FlagSet(schema)
 
@@ -19,7 +17,7 @@ interface TestCase {
   identity?: Partial<IdentityContext>
   environmentFlags?: string
   posthogFlags?: PostHogFlags
-  expected?: Partial<FlagValues<typeof schema>>
+  expected?: Partial<typeof schema>
   errorMessage?: string | RegExp
 }
 
@@ -83,22 +81,6 @@ describe("feature flags", () => {
       },
       expected: { TEST_BOOLEAN: true },
     },
-    {
-      it: "should be able to read string flags from PostHog",
-      posthogFlags: {
-        featureFlags: { TEST_STRING: true },
-        featureFlagPayloads: { TEST_STRING: "test" },
-      },
-      expected: { TEST_STRING: "test" },
-    },
-    {
-      it: "should be able to read numeric flags from PostHog",
-      posthogFlags: {
-        featureFlags: { TEST_NUMBER: true },
-        featureFlagPayloads: { TEST_NUMBER: "123" },
-      },
-      expected: { TEST_NUMBER: 123 },
-    },
     {
       it: "should not be able to override a negative environment flag from PostHog",
       environmentFlags: "default:!TEST_BOOLEAN",
@@ -177,7 +159,7 @@ describe("feature flags", () => {
             expect(values).toMatchObject(expected)
 
             for (const [key, expectedValue] of Object.entries(expected)) {
-              const value = await flags.get(key as keyof typeof schema)
+              const value = await flags.isEnabled(key as keyof typeof schema)
               expect(value).toBe(expectedValue)
             }
           } else {
diff --git a/packages/pro b/packages/pro
index ae786121d9..9091868986 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit ae786121d923449b0ad5fcbd123d0a9fec28f65e
+Subproject commit 9091868986fbc6aae580280f48853482e0b06c6a
diff --git a/packages/server/src/api/controllers/row/staticFormula.ts b/packages/server/src/api/controllers/row/staticFormula.ts
index 4464b7f44a..b81a164807 100644
--- a/packages/server/src/api/controllers/row/staticFormula.ts
+++ b/packages/server/src/api/controllers/row/staticFormula.ts
@@ -163,9 +163,9 @@ export async function finaliseRow(
     contextRows: [enrichedRow],
   })
   const aiEnabled =
-    ((await features.flags.isEnabled(FeatureFlag.BUDIBASE_AI)) &&
+    ((await features.isEnabled(FeatureFlag.BUDIBASE_AI)) &&
       (await pro.features.isBudibaseAIEnabled())) ||
-    ((await features.flags.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS)) &&
+    ((await features.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS)) &&
       (await pro.features.isAICustomConfigsEnabled()))
   if (aiEnabled) {
     row = await processAIColumns(table, row, {
diff --git a/packages/server/src/automations/actions.ts b/packages/server/src/automations/actions.ts
index e5a1c63b7d..1c201d1f64 100644
--- a/packages/server/src/automations/actions.ts
+++ b/packages/server/src/automations/actions.ts
@@ -105,13 +105,13 @@ if (env.SELF_HOSTED) {
 export async function getActionDefinitions(): Promise<
   Record<keyof typeof AutomationActionStepId, AutomationStepDefinition>
 > {
-  if (await features.flags.isEnabled(FeatureFlag.AUTOMATION_BRANCHING)) {
+  if (await features.isEnabled(FeatureFlag.AUTOMATION_BRANCHING)) {
     BUILTIN_ACTION_DEFINITIONS["BRANCH"] = branch.definition
   }
   if (
     env.SELF_HOSTED ||
-    (await features.flags.isEnabled(FeatureFlag.BUDIBASE_AI)) ||
-    (await features.flags.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS))
+    (await features.isEnabled(FeatureFlag.BUDIBASE_AI)) ||
+    (await features.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS))
   ) {
     BUILTIN_ACTION_DEFINITIONS["OPENAI"] = openai.definition
   }
diff --git a/packages/server/src/automations/steps/openai.ts b/packages/server/src/automations/steps/openai.ts
index 48eaa93057..19595cc0d0 100644
--- a/packages/server/src/automations/steps/openai.ts
+++ b/packages/server/src/automations/steps/openai.ts
@@ -100,10 +100,10 @@ export async function run({
   try {
     let response
     const customConfigsEnabled =
-      (await features.flags.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS)) &&
+      (await features.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS)) &&
       (await pro.features.isAICustomConfigsEnabled())
     const budibaseAIEnabled =
-      (await features.flags.isEnabled(FeatureFlag.BUDIBASE_AI)) &&
+      (await features.isEnabled(FeatureFlag.BUDIBASE_AI)) &&
       (await pro.features.isBudibaseAIEnabled())
 
     let llmWrapper
diff --git a/packages/server/src/middleware/zod-validator.ts b/packages/server/src/middleware/zod-validator.ts
index e8cc2c470a..d57e1c48ff 100644
--- a/packages/server/src/middleware/zod-validator.ts
+++ b/packages/server/src/middleware/zod-validator.ts
@@ -7,7 +7,7 @@ import { fromZodError } from "zod-validation-error"
 function validate(schema: AnyZodObject, property: "body" | "params") {
   // Return a Koa middleware function
   return async (ctx: Ctx, next: any) => {
-    if (!(await features.flags.isEnabled(FeatureFlag.USE_ZOD_VALIDATOR))) {
+    if (!(await features.isEnabled(FeatureFlag.USE_ZOD_VALIDATOR))) {
       return next()
     }
 
diff --git a/packages/types/src/api/web/global/self.ts b/packages/types/src/api/web/global/self.ts
index 5f21a8ddc5..d2c79bdba3 100644
--- a/packages/types/src/api/web/global/self.ts
+++ b/packages/types/src/api/web/global/self.ts
@@ -1,3 +1,4 @@
+import { FeatureFlags } from "packages/types/src/sdk"
 import { DevInfo, User } from "../../../documents"
 
 export interface GenerateAPIKeyRequest {
@@ -8,5 +9,5 @@ export interface GenerateAPIKeyResponse extends DevInfo {}
 export interface FetchAPIKeyResponse extends DevInfo {}
 
 export interface GetGlobalSelfResponse extends User {
-  flags?: Record<string, string>
+  flags?: FeatureFlags
 }
diff --git a/packages/types/src/sdk/featureFlag.ts b/packages/types/src/sdk/featureFlag.ts
index 98e744324c..725ae0feb1 100644
--- a/packages/types/src/sdk/featureFlag.ts
+++ b/packages/types/src/sdk/featureFlag.ts
@@ -6,6 +6,12 @@ export enum FeatureFlag {
   USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
 }
 
-export interface TenantFeatureFlags {
-  [key: string]: FeatureFlag[]
+export const FeatureFlagDefaults = {
+  [FeatureFlag.DEFAULT_VALUES]: true,
+  [FeatureFlag.AUTOMATION_BRANCHING]: true,
+  [FeatureFlag.AI_CUSTOM_CONFIGS]: true,
+  [FeatureFlag.BUDIBASE_AI]: true,
+  [FeatureFlag.USE_ZOD_VALIDATOR]: false,
 }
+
+export type FeatureFlags = typeof FeatureFlagDefaults
diff --git a/packages/worker/src/api/controllers/global/self.ts b/packages/worker/src/api/controllers/global/self.ts
index f8488f526b..eb704ccf02 100644
--- a/packages/worker/src/api/controllers/global/self.ts
+++ b/packages/worker/src/api/controllers/global/self.ts
@@ -111,8 +111,7 @@ export async function getSelf(ctx: UserCtx<void, GetGlobalSelfResponse>) {
   ctx.body = await groups.enrichUserRolesFromGroups(user)
 
   // add the feature flags for this tenant
-  const flags = await features.flags.fetch()
-  ctx.body.flags = flags
+  ctx.body.flags = await features.flags.fetch()
 
   addSessionAttributesToUser(ctx)
 }

From c3564528b7c989e8069449251d474651ee9cb2c7 Mon Sep 17 00:00:00 2001
From: Sam Rose <hello@samwho.dev>
Date: Mon, 6 Jan 2025 12:15:41 +0000
Subject: [PATCH 4/8] Fix types.

---
 packages/backend-core/src/features/tests/utils.ts | 3 ++-
 packages/types/src/api/web/global/self.ts         | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/packages/backend-core/src/features/tests/utils.ts b/packages/backend-core/src/features/tests/utils.ts
index cc633c083d..b9281b7f19 100644
--- a/packages/backend-core/src/features/tests/utils.ts
+++ b/packages/backend-core/src/features/tests/utils.ts
@@ -1,5 +1,6 @@
-import { FeatureFlags, parseEnvFlags } from ".."
+import { FeatureFlags } from "@budibase/types"
 import { setEnv } from "../../environment"
+import { parseEnvFlags } from "../features"
 
 function getCurrentFlags(): Record<string, Record<string, boolean>> {
   const result: Record<string, Record<string, boolean>> = {}
diff --git a/packages/types/src/api/web/global/self.ts b/packages/types/src/api/web/global/self.ts
index d2c79bdba3..9e879e6c3c 100644
--- a/packages/types/src/api/web/global/self.ts
+++ b/packages/types/src/api/web/global/self.ts
@@ -1,4 +1,4 @@
-import { FeatureFlags } from "packages/types/src/sdk"
+import { FeatureFlags } from "@budibase/types"
 import { DevInfo, User } from "../../../documents"
 
 export interface GenerateAPIKeyRequest {

From 511328002ee5ff4a33c73df764b3528468eed37d Mon Sep 17 00:00:00 2001
From: Sam Rose <hello@samwho.dev>
Date: Mon, 6 Jan 2025 12:35:55 +0000
Subject: [PATCH 5/8] Update pro reference.

---
 packages/pro | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/pro b/packages/pro
index 9091868986..32d84f109d 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit 9091868986fbc6aae580280f48853482e0b06c6a
+Subproject commit 32d84f109d4edc526145472a7446327312151442

From a09caf52d8c3fe4bd19f853f10cc30f5f3969ce5 Mon Sep 17 00:00:00 2001
From: Peter Clement <peter@budibase.com>
Date: Mon, 6 Jan 2025 12:43:15 +0000
Subject: [PATCH 6/8] remove unnecessary casts

---
 packages/builder/src/stores/builder/theme.ts | 15 +++------------
 packages/types/src/documents/app/app.ts      |  4 ++--
 2 files changed, 5 insertions(+), 14 deletions(-)

diff --git a/packages/builder/src/stores/builder/theme.ts b/packages/builder/src/stores/builder/theme.ts
index b3ac202a5d..f2675fe06b 100644
--- a/packages/builder/src/stores/builder/theme.ts
+++ b/packages/builder/src/stores/builder/theme.ts
@@ -19,10 +19,7 @@ export class ThemeStore extends BudiStore<ThemeState> {
 
   syncAppTheme = (app: App) => {
     this.update(state => {
-      const theme = ensureValidTheme(
-        app.theme as Theme | undefined,
-        DefaultAppTheme
-      ) as Theme
+      const theme = ensureValidTheme(app.theme, DefaultAppTheme)
       return {
         ...state,
         theme,
@@ -35,10 +32,7 @@ export class ThemeStore extends BudiStore<ThemeState> {
     const app = await API.saveAppMetadata(appId, { theme })
     this.update(state => ({
       ...state,
-      theme: ensureValidTheme(
-        app.theme as Theme | undefined,
-        DefaultAppTheme
-      ) as Theme,
+      theme: ensureValidTheme(app.theme, DefaultAppTheme),
     }))
   }
 
@@ -55,10 +49,7 @@ export class ThemeStore extends BudiStore<ThemeState> {
     const { theme, customTheme } = metadata
     this.update(state => ({
       ...state,
-      theme: ensureValidTheme(
-        theme as Theme | undefined,
-        DefaultAppTheme
-      ) as Theme,
+      theme: ensureValidTheme(theme, DefaultAppTheme),
       customTheme: customTheme || {},
     }))
   }
diff --git a/packages/types/src/documents/app/app.ts b/packages/types/src/documents/app/app.ts
index e31dd1e9ac..7486a3fa7e 100644
--- a/packages/types/src/documents/app/app.ts
+++ b/packages/types/src/documents/app/app.ts
@@ -1,4 +1,4 @@
-import { User, Document, Plugin, Snippet } from "../"
+import { User, Document, Plugin, Snippet, Theme } from "../"
 import { SocketSession } from "../../sdk"
 
 export type AppMetadataErrors = { [key: string]: string[] }
@@ -14,7 +14,7 @@ export interface App extends Document {
   instance: AppInstance
   tenantId: string
   status: string
-  theme?: string
+  theme?: Theme
   customTheme?: AppCustomTheme
   revertableVersion?: string
   lockedBy?: User

From 48948712b9438042704dcb4facbdb3228f197007 Mon Sep 17 00:00:00 2001
From: Peter Clement <peter@budibase.com>
Date: Mon, 6 Jan 2025 12:53:10 +0000
Subject: [PATCH 7/8] remove further cast

---
 packages/builder/src/stores/builder/theme.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/builder/src/stores/builder/theme.ts b/packages/builder/src/stores/builder/theme.ts
index f2675fe06b..604add9410 100644
--- a/packages/builder/src/stores/builder/theme.ts
+++ b/packages/builder/src/stores/builder/theme.ts
@@ -12,7 +12,7 @@ interface ThemeState {
 export class ThemeStore extends BudiStore<ThemeState> {
   constructor() {
     super({
-      theme: DefaultAppTheme as Theme,
+      theme: DefaultAppTheme,
       customTheme: {},
     })
   }

From 30b543b6b727f25d46ec5d19a26091c76eb9b901 Mon Sep 17 00:00:00 2001
From: Peter Clement <peter@budibase.com>
Date: Mon, 6 Jan 2025 13:35:00 +0000
Subject: [PATCH 8/8] error message for no row action name

---
 packages/builder/src/stores/builder/rowActions.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/builder/src/stores/builder/rowActions.ts b/packages/builder/src/stores/builder/rowActions.ts
index 378579e41b..9576eccd1b 100644
--- a/packages/builder/src/stores/builder/rowActions.ts
+++ b/packages/builder/src/stores/builder/rowActions.ts
@@ -66,7 +66,7 @@ export class RowActionStore extends BudiStore<RowActionState> {
     }
 
     if (!name) {
-      return
+      throw new Error("Failed to generate a unique name for the row action")
     }
 
     // Create the action