diff --git a/packages/client/src/components/preview/SettingsPicker.svelte b/packages/client/src/components/preview/SettingsPicker.svelte
index 8b83729fde..3900d065e8 100644
--- a/packages/client/src/components/preview/SettingsPicker.svelte
+++ b/packages/client/src/components/preview/SettingsPicker.svelte
@@ -1,12 +1,13 @@
diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js
index d3f1ab8be9..f7e3e86d40 100644
--- a/packages/client/src/constants.js
+++ b/packages/client/src/constants.js
@@ -15,3 +15,6 @@ export const ActionTypes = {
export const DNDPlaceholderID = "dnd-placeholder"
export const ScreenslotType = "screenslot"
+export const GridRowHeight = 24
+export const GridColumns = 12
+export const GridSpacing = 4
diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js
index 5440fc3a79..faa37eddca 100644
--- a/packages/client/src/stores/builder.js
+++ b/packages/client/src/stores/builder.js
@@ -41,13 +41,20 @@ const createBuilderStore = () => {
eventStore.actions.dispatchEvent("update-prop", { prop, value })
},
updateStyles: async (styles, id) => {
- await eventStore.actions.dispatchEvent("update-styles", { styles, id })
+ await eventStore.actions.dispatchEvent("update-styles", {
+ styles,
+ id,
+ })
},
keyDown: (key, ctrlKey) => {
eventStore.actions.dispatchEvent("key-down", { key, ctrlKey })
},
- duplicateComponent: id => {
- eventStore.actions.dispatchEvent("duplicate-component", { id })
+ duplicateComponent: (id, mode = "below", selectComponent = true) => {
+ eventStore.actions.dispatchEvent("duplicate-component", {
+ id,
+ mode,
+ selectComponent,
+ })
},
deleteComponent: id => {
eventStore.actions.dispatchEvent("delete-component", { id })
diff --git a/packages/client/src/stores/components.js b/packages/client/src/stores/components.js
index b7f7d98197..d4afa6c7f1 100644
--- a/packages/client/src/stores/components.js
+++ b/packages/client/src/stores/components.js
@@ -142,9 +142,6 @@ const createComponentStore = () => {
}
const getComponentInstance = id => {
- if (!id) {
- return null
- }
return derived(store, $store => $store.mountedComponents[id])
}
diff --git a/packages/client/src/stores/screens.js b/packages/client/src/stores/screens.js
index 9cb0d536de..3c5ece0a6c 100644
--- a/packages/client/src/stores/screens.js
+++ b/packages/client/src/stores/screens.js
@@ -129,29 +129,30 @@ const createScreenStore = () => {
// If we don't have a legacy custom layout, build a layout structure
// from the screen navigation settings
if (!activeLayout) {
- let navigationSettings = {
+ let layoutSettings = {
navigation: "None",
pageWidth: activeScreen?.width || "Large",
+ embedded: $appStore.embedded,
}
if (activeScreen?.showNavigation) {
- navigationSettings = {
- ...navigationSettings,
+ layoutSettings = {
+ ...layoutSettings,
...($builderStore.navigation || $appStore.application?.navigation),
}
// Default navigation to top
- if (!navigationSettings.navigation) {
- navigationSettings.navigation = "Top"
+ if (!layoutSettings.navigation) {
+ layoutSettings.navigation = "Top"
}
// Default title to app name
- if (!navigationSettings.title && !navigationSettings.hideTitle) {
- navigationSettings.title = $appStore.application?.name
+ if (!layoutSettings.title && !layoutSettings.hideTitle) {
+ layoutSettings.title = $appStore.application?.name
}
// Default to the org logo
- if (!navigationSettings.logoUrl) {
- navigationSettings.logoUrl = $orgStore?.logoUrl
+ if (!layoutSettings.logoUrl) {
+ layoutSettings.logoUrl = $orgStore?.logoUrl
}
}
activeLayout = {
@@ -173,8 +174,7 @@ const createScreenStore = () => {
},
},
],
- ...navigationSettings,
- embedded: $appStore.embedded,
+ ...layoutSettings,
},
}
}
diff --git a/packages/client/src/utils/domDebounce.js b/packages/client/src/utils/domDebounce.js
deleted file mode 100644
index b15d2698b4..0000000000
--- a/packages/client/src/utils/domDebounce.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export const domDebounce = (callback, extractParams = x => x) => {
- let active = false
- let lastParams
- return (...params) => {
- lastParams = extractParams(...params)
- if (!active) {
- active = true
- requestAnimationFrame(() => {
- callback(lastParams)
- active = false
- })
- }
- }
-}
diff --git a/packages/client/src/utils/grid.js b/packages/client/src/utils/grid.js
new file mode 100644
index 0000000000..1727b904ca
--- /dev/null
+++ b/packages/client/src/utils/grid.js
@@ -0,0 +1,183 @@
+import { GridSpacing, GridRowHeight } from "constants"
+import { builderStore } from "stores"
+import { buildStyleString } from "utils/styleable.js"
+
+/**
+ * We use CSS variables on components to control positioning and layout of
+ * components inside grids.
+ * --grid-[mobile/desktop]-[row/col]-[start-end]: for positioning
+ * --grid-[mobile/desktop]-[h/v]-align: for layout of inner components within
+ * the components grid bounds
+ *
+ * Component definitions define their default layout preference via the
+ * `grid.hAlign` and `grid.vAlign` keys in the manifest.
+ *
+ * We also apply grid-[mobile/desktop]-grow CSS classes to component wrapper
+ * DOM nodes to use later in selectors, to control the sizing of children.
+ */
+
+// Enum representing the different CSS variables we use for grid metadata
+export const GridParams = {
+ HAlign: "h-align",
+ VAlign: "v-align",
+ ColStart: "col-start",
+ ColEnd: "col-end",
+ RowStart: "row-start",
+ RowEnd: "row-end",
+}
+
+// Classes used in selectors inside grid containers to control child styles
+export const GridClasses = {
+ DesktopFill: "grid-desktop-grow",
+ MobileFill: "grid-mobile-grow",
+}
+
+// Enum for device preview type, included in grid CSS variables
+export const Devices = {
+ Desktop: "desktop",
+ Mobile: "mobile",
+}
+
+export const GridDragModes = {
+ Resize: "resize",
+ Move: "move",
+}
+
+// Builds a CSS variable name for a certain piece of grid metadata
+export const getGridVar = (device, param) => `--grid-${device}-${param}`
+
+// Determines whether a JS event originated from immediately within a grid
+export const isGridEvent = e => {
+ return (
+ e.target.dataset?.indicator === "true" ||
+ e.target
+ .closest?.(".component")
+ ?.parentNode.closest(".component")
+ ?.childNodes[0]?.classList?.contains("grid")
+ )
+}
+
+// Svelte action to apply required class names and styles to our component
+// wrappers
+export const gridLayout = (node, metadata) => {
+ let selectComponent
+
+ // Applies the required listeners, CSS and classes to a component DOM node
+ const applyMetadata = metadata => {
+ const {
+ id,
+ styles,
+ interactive,
+ errored,
+ definition,
+ draggable,
+ insideGrid,
+ ignoresLayout,
+ } = metadata
+ if (!insideGrid) {
+ return
+ }
+
+ // If this component ignores layout, flag it as such so that we can avoid
+ // selecting it later
+ if (ignoresLayout) {
+ node.classList.add("ignores-layout")
+ return
+ }
+
+ // Callback to select the component when clicking on the wrapper
+ selectComponent = e => {
+ e.stopPropagation()
+ builderStore.actions.selectComponent(id)
+ }
+
+ // Determine default width and height of component
+ let width = errored ? 500 : definition?.size?.width || 200
+ let height = errored ? 60 : definition?.size?.height || 200
+ width += 2 * GridSpacing
+ height += 2 * GridSpacing
+ let vars = {
+ "--default-width": width,
+ "--default-height": height,
+ }
+
+ // Generate defaults for all grid params
+ const defaults = {
+ [GridParams.HAlign]: definition?.grid?.hAlign || "stretch",
+ [GridParams.VAlign]: definition?.grid?.vAlign || "center",
+ [GridParams.ColStart]: 1,
+ [GridParams.ColEnd]:
+ "round(up, calc((var(--grid-spacing) * 2 + var(--default-width)) / var(--col-size) + 1))",
+ [GridParams.RowStart]: 1,
+ [GridParams.RowEnd]: Math.max(2, Math.ceil(height / GridRowHeight) + 1),
+ }
+
+ // Specify values for all grid params for all devices, and strip these CSS
+ // variables from the styles being applied to the inner component, as we
+ // want to apply these to the wrapper instead
+ for (let param of Object.values(GridParams)) {
+ let dVar = getGridVar(Devices.Desktop, param)
+ let mVar = getGridVar(Devices.Mobile, param)
+ vars[dVar] = styles[dVar] ?? styles[mVar] ?? defaults[param]
+ vars[mVar] = styles[mVar] ?? styles[dVar] ?? defaults[param]
+ }
+
+ // Apply some overrides depending on component state
+ if (errored) {
+ vars[getGridVar(Devices.Desktop, GridParams.HAlign)] = "stretch"
+ vars[getGridVar(Devices.Mobile, GridParams.HAlign)] = "stretch"
+ vars[getGridVar(Devices.Desktop, GridParams.VAlign)] = "stretch"
+ vars[getGridVar(Devices.Mobile, GridParams.VAlign)] = "stretch"
+ }
+
+ // Apply some metadata to data attributes to speed up lookups
+ const addDataTag = (tagName, device, param) => {
+ const val = `${vars[getGridVar(device, param)]}`
+ if (node.dataset[tagName] !== val) {
+ node.dataset[tagName] = val
+ }
+ }
+ addDataTag("gridDesktopRowEnd", Devices.Desktop, GridParams.RowEnd)
+ addDataTag("gridMobileRowEnd", Devices.Mobile, GridParams.RowEnd)
+ addDataTag("gridDesktopHAlign", Devices.Desktop, GridParams.HAlign)
+ addDataTag("gridMobileHAlign", Devices.Mobile, GridParams.HAlign)
+ addDataTag("gridDesktopVAlign", Devices.Desktop, GridParams.VAlign)
+ addDataTag("gridMobileVAlign", Devices.Mobile, GridParams.VAlign)
+ if (node.dataset.insideGrid !== true) {
+ node.dataset.insideGrid = true
+ }
+
+ // Apply all CSS variables to the wrapper
+ node.style = buildStyleString(vars)
+
+ // Add a listener to select this node on click
+ if (interactive) {
+ node.addEventListener("click", selectComponent, false)
+ }
+
+ // Add draggable attribute
+ node.setAttribute("draggable", !!draggable)
+ }
+
+ // Removes the previously set up listeners
+ const removeListeners = () => {
+ // By checking if this is defined we can avoid trying to remove event
+ // listeners on every component
+ if (selectComponent) {
+ node.removeEventListener("click", selectComponent, false)
+ selectComponent = null
+ }
+ }
+
+ applyMetadata(metadata)
+
+ return {
+ update(newMetadata) {
+ removeListeners()
+ applyMetadata(newMetadata)
+ },
+ destroy() {
+ removeListeners()
+ },
+ }
+}
diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js
index 3fccae0be5..0f484a9ab9 100644
--- a/packages/client/src/utils/styleable.js
+++ b/packages/client/src/utils/styleable.js
@@ -3,13 +3,13 @@ import { builderStore } from "stores"
/**
* Helper to build a CSS string from a style object.
*/
-const buildStyleString = (styleObject, customStyles) => {
+export const buildStyleString = (styleObject, customStyles) => {
let str = ""
- Object.entries(styleObject || {}).forEach(([style, value]) => {
- if (style && value != null) {
- str += `${style}: ${value}; `
+ for (let key of Object.keys(styleObject || {})) {
+ if (styleObject[key] != null) {
+ str += `${key}:${styleObject[key]};`
}
- })
+ }
return str + (customStyles || "")
}
diff --git a/packages/frontend-core/src/components/ClientAppSkeleton.svelte b/packages/frontend-core/src/components/ClientAppSkeleton.svelte
index a1c90d2db7..f867fccddb 100644
--- a/packages/frontend-core/src/components/ClientAppSkeleton.svelte
+++ b/packages/frontend-core/src/components/ClientAppSkeleton.svelte
@@ -58,7 +58,6 @@
height: 100%;
display: flex;
flex-direction: column;
- border-radius: 4px;
overflow: hidden;
background-color: var(--spectrum-global-color-gray-200);
}
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index f3a7678cf2..8c66d9ecfc 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -1,95 +1,17 @@
@@ -106,51 +28,5 @@
-
-
- {#each displayColumns as column}
-
-
toggleColumn(column, e.detail)}
- value={columnToPermissionOptions(column)}
- options={column.options}
- />
- {/each}
-
-
+
-
-
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingContent.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingContent.svelte
new file mode 100644
index 0000000000..4f0e4424d4
--- /dev/null
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingContent.svelte
@@ -0,0 +1,134 @@
+
+
+
+
+ {#each displayColumns as column}
+
+
toggleColumn(column, e.detail)}
+ value={columnToPermissionOptions(column)}
+ options={column.options}
+ />
+ {/each}
+
+
+
+
diff --git a/packages/frontend-core/src/utils/index.js b/packages/frontend-core/src/utils/index.js
index 9eb7206012..5f21e7db99 100644
--- a/packages/frontend-core/src/utils/index.js
+++ b/packages/frontend-core/src/utils/index.js
@@ -9,3 +9,4 @@ export { memo, derivedMemo } from "./memo"
export { createWebsocket } from "./websocket"
export * from "./download"
export * from "./theme"
+export * from "./settings"
diff --git a/packages/frontend-core/src/utils/memo.js b/packages/frontend-core/src/utils/memo.js
index ba0e3f3490..8192be6790 100644
--- a/packages/frontend-core/src/utils/memo.js
+++ b/packages/frontend-core/src/utils/memo.js
@@ -4,32 +4,23 @@ import { writable, get, derived } from "svelte/store"
// subscribed children will only fire when a new value is actually set
export const memo = initialValue => {
const store = writable(initialValue)
+ let currentJSON = JSON.stringify(initialValue)
- const tryUpdateValue = (newValue, currentValue) => {
- // Sanity check for primitive equality
- if (currentValue === newValue) {
- return
- }
-
- // Otherwise deep compare via JSON stringify
- const currentString = JSON.stringify(currentValue)
- const newString = JSON.stringify(newValue)
- if (currentString !== newString) {
+ const tryUpdateValue = newValue => {
+ const newJSON = JSON.stringify(newValue)
+ if (newJSON !== currentJSON) {
store.set(newValue)
+ currentJSON = newJSON
}
}
return {
subscribe: store.subscribe,
- set: newValue => {
- const currentValue = get(store)
- tryUpdateValue(newValue, currentValue)
- },
+ set: tryUpdateValue,
update: updateFn => {
- const currentValue = get(store)
- let mutableCurrentValue = JSON.parse(JSON.stringify(currentValue))
+ let mutableCurrentValue = JSON.parse(currentJSON)
const newValue = updateFn(mutableCurrentValue)
- tryUpdateValue(newValue, currentValue)
+ tryUpdateValue(newValue)
},
}
}
diff --git a/packages/frontend-core/src/utils/settings.js b/packages/frontend-core/src/utils/settings.js
new file mode 100644
index 0000000000..0e312c70e6
--- /dev/null
+++ b/packages/frontend-core/src/utils/settings.js
@@ -0,0 +1,43 @@
+import { helpers } from "@budibase/shared-core"
+
+// Util to check if a setting can be rendered for a certain instance, based on
+// the "dependsOn" metadata in the manifest
+export const shouldDisplaySetting = (instance, setting) => {
+ let dependsOn = setting.dependsOn
+ if (dependsOn && !Array.isArray(dependsOn)) {
+ dependsOn = [dependsOn]
+ }
+ if (!dependsOn?.length) {
+ return true
+ }
+
+ // Ensure all conditions are met
+ return dependsOn.every(condition => {
+ let dependantSetting = condition
+ let dependantValues = null
+ let invert = !!condition.invert
+ if (typeof condition === "object") {
+ dependantSetting = condition.setting
+ dependantValues = condition.value
+ }
+ if (!dependantSetting) {
+ return false
+ }
+
+ // Ensure values is an array
+ if (!Array.isArray(dependantValues)) {
+ dependantValues = [dependantValues]
+ }
+
+ // If inverting, we want to ensure that we don't have any matches.
+ // If not inverting, we want to ensure that we do have any matches.
+ const currentVal = helpers.deepGet(instance, dependantSetting)
+ const anyMatches = dependantValues.some(dependantVal => {
+ if (dependantVal == null) {
+ return currentVal != null && currentVal !== false && currentVal !== ""
+ }
+ return dependantVal === currentVal
+ })
+ return anyMatches !== invert
+ })
+}
diff --git a/packages/frontend-core/src/utils/utils.js b/packages/frontend-core/src/utils/utils.js
index 60752a35fe..6c9acb0f89 100644
--- a/packages/frontend-core/src/utils/utils.js
+++ b/packages/frontend-core/src/utils/utils.js
@@ -156,6 +156,7 @@ export const buildFormBlockButtonConfig = props => {
providerId: formId,
tableId: resourceId,
notificationOverride,
+ confirm: null,
},
},
{
diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts
index 5ee2d0fe2b..ac2d1e8c39 100644
--- a/packages/server/src/api/controllers/row/ExternalRequest.ts
+++ b/packages/server/src/api/controllers/row/ExternalRequest.ts
@@ -45,6 +45,7 @@ import { db as dbCore } from "@budibase/backend-core"
import sdk from "../../../sdk"
import env from "../../../environment"
import { makeExternalQuery } from "../../../integrations/base/query"
+import { dataFilters } from "@budibase/shared-core"
export interface ManyRelationship {
tableId?: string
@@ -195,29 +196,33 @@ export class ExternalRequest
{
if (filters) {
// need to map over the filters and make sure the _id field isn't present
let prefix = 1
- for (const [operatorType, operator] of Object.entries(filters)) {
- const isArrayOp = sdk.rows.utils.isArrayFilter(operatorType)
- for (const field of Object.keys(operator || {})) {
- if (dbCore.removeKeyNumbering(field) === "_id") {
- if (primary) {
- const parts = breakRowIdField(operator[field])
- if (primary.length > 1 && isArrayOp) {
- operator[InternalSearchFilterOperator.COMPLEX_ID_OPERATOR] = {
- id: primary,
- values: parts[0],
+ const checkFilters = (innerFilters: SearchFilters): SearchFilters => {
+ for (const [operatorType, operator] of Object.entries(innerFilters)) {
+ const isArrayOp = sdk.rows.utils.isArrayFilter(operatorType)
+ for (const field of Object.keys(operator || {})) {
+ if (dbCore.removeKeyNumbering(field) === "_id") {
+ if (primary) {
+ const parts = breakRowIdField(operator[field])
+ if (primary.length > 1 && isArrayOp) {
+ operator[InternalSearchFilterOperator.COMPLEX_ID_OPERATOR] = {
+ id: primary,
+ values: parts[0],
+ }
+ } else {
+ for (let field of primary) {
+ operator[`${prefix}:${field}`] = parts.shift()
+ }
+ prefix++
}
- } else {
- for (let field of primary) {
- operator[`${prefix}:${field}`] = parts.shift()
- }
- prefix++
}
+ // make sure this field doesn't exist on any filter
+ delete operator[field]
}
- // make sure this field doesn't exist on any filter
- delete operator[field]
}
}
+ return dataFilters.recurseLogicalOperators(innerFilters, checkFilters)
}
+ checkFilters(filters)
}
// there is no id, just use the user provided filters
if (!idCopy || !table) {
diff --git a/packages/server/src/api/controllers/row/utils/sqlUtils.ts b/packages/server/src/api/controllers/row/utils/sqlUtils.ts
index 32124fa79d..a24ec17c26 100644
--- a/packages/server/src/api/controllers/row/utils/sqlUtils.ts
+++ b/packages/server/src/api/controllers/row/utils/sqlUtils.ts
@@ -151,7 +151,10 @@ export function buildExternalRelationships(
return relationships
}
-export function buildInternalRelationships(table: Table): RelationshipsJson[] {
+export function buildInternalRelationships(
+ table: Table,
+ allTables: Table[]
+): RelationshipsJson[] {
const relationships: RelationshipsJson[] = []
const links = Object.values(table.schema).filter(
column => column.type === FieldType.LINK
@@ -164,6 +167,10 @@ export function buildInternalRelationships(table: Table): RelationshipsJson[] {
const linkTableId = link.tableId!
const junctionTableId = generateJunctionTableID(tableId, linkTableId)
const isFirstTable = tableId > linkTableId
+ // skip relationships with missing table definitions
+ if (!allTables.find(table => table._id === linkTableId)) {
+ continue
+ }
relationships.push({
through: junctionTableId,
column: link.name,
@@ -192,10 +199,10 @@ export function buildSqlFieldList(
function extractRealFields(table: Table, existing: string[] = []) {
return Object.entries(table.schema)
.filter(
- column =>
- column[1].type !== FieldType.LINK &&
- column[1].type !== FieldType.FORMULA &&
- !existing.find((field: string) => field === column[0])
+ ([columnName, column]) =>
+ column.type !== FieldType.LINK &&
+ column.type !== FieldType.FORMULA &&
+ !existing.find((field: string) => field === columnName)
)
.map(column => `${table.name}.${column[0]}`)
}
diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts
index c38a415aa2..0b2bb8c203 100644
--- a/packages/server/src/api/controllers/row/views.ts
+++ b/packages/server/src/api/controllers/row/views.ts
@@ -38,7 +38,6 @@ export async function searchView(
let query = dataFilters.buildQuery(view.query || [])
if (body.query) {
// Delete extraneous search params that cannot be overridden
- delete body.query.allOr
delete body.query.onEmptyFilter
if (!isExternalTableID(view.tableId) && !db.isSqsEnabledForTenant()) {
@@ -57,13 +56,12 @@ export async function searchView(
}
})
})
- } else {
+ } else
query = {
$and: {
conditions: [query, body.query],
},
}
- }
}
await context.ensureSnippetContext(true)
diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts
index 9351976d66..95e714fbd6 100644
--- a/packages/server/src/api/routes/tests/row.spec.ts
+++ b/packages/server/src/api/routes/tests/row.spec.ts
@@ -1664,7 +1664,7 @@ describe.each([
isInternal &&
describe("attachments and signatures", () => {
const coreAttachmentEnrichment = async (
- schema: any,
+ schema: TableSchema,
field: string,
attachmentCfg: string | string[]
) => {
@@ -1691,7 +1691,7 @@ describe.each([
await withEnv({ SELF_HOSTED: "true" }, async () => {
return context.doInAppContext(config.getAppId(), async () => {
- const enriched: Row[] = await outputProcessing(table, [row])
+ const enriched: Row[] = await outputProcessing(testTable, [row])
const [targetRow] = enriched
const attachmentEntries = Array.isArray(targetRow[field])
? targetRow[field]
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index 9de97747e5..bac9b6f774 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -2762,6 +2762,57 @@ describe.each([
})
})
+ isSql &&
+ describe("primaryDisplay", () => {
+ beforeAll(async () => {
+ let toRelateTable = await createTable({
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ },
+ })
+ table = await config.api.table.save(
+ tableForDatasource(datasource, {
+ schema: {
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ },
+ link: {
+ name: "link",
+ type: FieldType.LINK,
+ relationshipType: RelationshipType.MANY_TO_ONE,
+ tableId: toRelateTable._id!,
+ fieldName: "link",
+ },
+ },
+ })
+ )
+ toRelateTable = await config.api.table.get(toRelateTable._id!)
+ await config.api.table.save({
+ ...toRelateTable,
+ primaryDisplay: "link",
+ })
+ const relatedRows = await Promise.all([
+ config.api.row.save(toRelateTable._id!, { name: "test" }),
+ ])
+ await Promise.all([
+ config.api.row.save(table._id!, {
+ name: "test",
+ link: relatedRows.map(row => row._id),
+ }),
+ ])
+ })
+
+ it("should be able to query, primary display on related table shouldn't be used", async () => {
+ // this test makes sure that if a relationship has been specified as the primary display on a table
+ // it is ignored and another column is used instead
+ await expectQuery({}).toContain([
+ { name: "test", link: [{ primaryDisplay: "test" }] },
+ ])
+ })
+ })
+
!isLucene &&
describe("$and", () => {
beforeAll(async () => {
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index e18cffeaa8..4401efc480 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -30,6 +30,7 @@ import {
withEnv as withCoreEnv,
setEnv as setCoreEnv,
} from "@budibase/backend-core"
+import sdk from "../../../sdk"
describe.each([
["lucene", undefined],
@@ -120,6 +121,7 @@ describe.each([
})
beforeEach(() => {
+ jest.clearAllMocks()
mocks.licenses.useCloudFree()
})
@@ -1490,83 +1492,189 @@ describe.each([
)
})
- isLucene &&
- it("in lucene, cannot override a view filter", async () => {
- await config.api.row.save(table._id!, {
- one: "foo",
- two: "bar",
- })
- const two = await config.api.row.save(table._id!, {
- one: "foo2",
- two: "bar2",
- })
-
- const view = await config.api.viewV2.create({
- tableId: table._id!,
- name: generator.guid(),
- query: [
- {
- operator: BasicOperator.EQUAL,
- field: "two",
- value: "bar2",
- },
- ],
- schema: {
- id: { visible: true },
- one: { visible: false },
- two: { visible: true },
- },
- })
-
- const response = await config.api.viewV2.search(view.id, {
- query: {
- equal: {
- two: "bar",
- },
- },
- })
- expect(response.rows).toHaveLength(1)
- expect(response.rows).toEqual([
- expect.objectContaining({ _id: two._id }),
- ])
+ it("can query on top of the view filters", async () => {
+ await config.api.row.save(table._id!, {
+ one: "foo",
+ two: "bar",
+ })
+ await config.api.row.save(table._id!, {
+ one: "foo2",
+ two: "bar2",
+ })
+ const three = await config.api.row.save(table._id!, {
+ one: "foo3",
+ two: "bar3",
})
- !isLucene &&
- it("can filter a view without a view filter", async () => {
- const one = await config.api.row.save(table._id!, {
- one: "foo",
- two: "bar",
- })
- await config.api.row.save(table._id!, {
- one: "foo2",
- two: "bar2",
- })
-
- const view = await config.api.viewV2.create({
- tableId: table._id!,
- name: generator.guid(),
- schema: {
- id: { visible: true },
- one: { visible: false },
- two: { visible: true },
+ const view = await config.api.viewV2.create({
+ tableId: table._id!,
+ name: generator.guid(),
+ query: [
+ {
+ operator: BasicOperator.NOT_EQUAL,
+ field: "one",
+ value: "foo2",
},
- })
+ ],
+ schema: {
+ id: { visible: true },
+ one: { visible: true },
+ two: { visible: true },
+ },
+ })
- const response = await config.api.viewV2.search(view.id, {
- query: {
- equal: {
- two: "bar",
- },
+ const response = await config.api.viewV2.search(view.id, {
+ query: {
+ [BasicOperator.EQUAL]: {
+ two: "bar3",
},
- })
- expect(response.rows).toHaveLength(1)
- expect(response.rows).toEqual([
+ [BasicOperator.NOT_EMPTY]: {
+ two: null,
+ },
+ },
+ })
+ expect(response.rows).toHaveLength(1)
+ expect(response.rows).toEqual(
+ expect.arrayContaining([expect.objectContaining({ _id: three._id })])
+ )
+ })
+
+ it("can query on top of the view filters (using or filters)", async () => {
+ const one = await config.api.row.save(table._id!, {
+ one: "foo",
+ two: "bar",
+ })
+ await config.api.row.save(table._id!, {
+ one: "foo2",
+ two: "bar2",
+ })
+ const three = await config.api.row.save(table._id!, {
+ one: "foo3",
+ two: "bar3",
+ })
+
+ const view = await config.api.viewV2.create({
+ tableId: table._id!,
+ name: generator.guid(),
+ query: [
+ {
+ operator: BasicOperator.NOT_EQUAL,
+ field: "two",
+ value: "bar2",
+ },
+ ],
+ schema: {
+ id: { visible: true },
+ one: { visible: false },
+ two: { visible: true },
+ },
+ })
+
+ const response = await config.api.viewV2.search(view.id, {
+ query: {
+ allOr: true,
+ [BasicOperator.NOT_EQUAL]: {
+ two: "bar",
+ },
+ [BasicOperator.NOT_EMPTY]: {
+ two: null,
+ },
+ },
+ })
+ expect(response.rows).toHaveLength(2)
+ expect(response.rows).toEqual(
+ expect.arrayContaining([
expect.objectContaining({ _id: one._id }),
+ expect.objectContaining({ _id: three._id }),
])
- })
+ )
+ })
+
+ isLucene &&
+ it.each([true, false])(
+ "in lucene, cannot override a view filter",
+ async allOr => {
+ await config.api.row.save(table._id!, {
+ one: "foo",
+ two: "bar",
+ })
+ const two = await config.api.row.save(table._id!, {
+ one: "foo2",
+ two: "bar2",
+ })
+
+ const view = await config.api.viewV2.create({
+ tableId: table._id!,
+ name: generator.guid(),
+ query: [
+ {
+ operator: BasicOperator.EQUAL,
+ field: "two",
+ value: "bar2",
+ },
+ ],
+ schema: {
+ id: { visible: true },
+ one: { visible: false },
+ two: { visible: true },
+ },
+ })
+
+ const response = await config.api.viewV2.search(view.id, {
+ query: {
+ allOr,
+ equal: {
+ two: "bar",
+ },
+ },
+ })
+ expect(response.rows).toHaveLength(1)
+ expect(response.rows).toEqual([
+ expect.objectContaining({ _id: two._id }),
+ ])
+ }
+ )
!isLucene &&
- it("cannot bypass a view filter", async () => {
+ it.each([true, false])(
+ "can filter a view without a view filter",
+ async allOr => {
+ const one = await config.api.row.save(table._id!, {
+ one: "foo",
+ two: "bar",
+ })
+ await config.api.row.save(table._id!, {
+ one: "foo2",
+ two: "bar2",
+ })
+
+ const view = await config.api.viewV2.create({
+ tableId: table._id!,
+ name: generator.guid(),
+ schema: {
+ id: { visible: true },
+ one: { visible: false },
+ two: { visible: true },
+ },
+ })
+
+ const response = await config.api.viewV2.search(view.id, {
+ query: {
+ allOr,
+ equal: {
+ two: "bar",
+ },
+ },
+ })
+ expect(response.rows).toHaveLength(1)
+ expect(response.rows).toEqual([
+ expect.objectContaining({ _id: one._id }),
+ ])
+ }
+ )
+
+ !isLucene &&
+ it.each([true, false])("cannot bypass a view filter", async allOr => {
await config.api.row.save(table._id!, {
one: "foo",
two: "bar",
@@ -1595,6 +1703,7 @@ describe.each([
const response = await config.api.viewV2.search(view.id, {
query: {
+ allOr,
equal: {
two: "bar",
},
@@ -1602,6 +1711,28 @@ describe.each([
})
expect(response.rows).toHaveLength(0)
})
+
+ it("queries the row api passing the view fields only", async () => {
+ const searchSpy = jest.spyOn(sdk.rows, "search")
+
+ const view = await config.api.viewV2.create({
+ tableId: table._id!,
+ name: generator.guid(),
+ schema: {
+ id: { visible: true },
+ one: { visible: false },
+ },
+ })
+
+ await config.api.viewV2.search(view.id, { query: {} })
+ expect(searchSpy).toHaveBeenCalledTimes(1)
+
+ expect(searchSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ fields: ["id"],
+ })
+ )
+ })
})
describe("permissions", () => {
diff --git a/packages/server/src/db/linkedRows/index.ts b/packages/server/src/db/linkedRows/index.ts
index 87f980600a..2da7e212b9 100644
--- a/packages/server/src/db/linkedRows/index.ts
+++ b/packages/server/src/db/linkedRows/index.ts
@@ -1,10 +1,10 @@
import LinkController from "./LinkController"
import {
getLinkDocuments,
- getUniqueByProp,
- getRelatedTableForField,
- getLinkedTableIDs,
getLinkedTable,
+ getLinkedTableIDs,
+ getRelatedTableForField,
+ getUniqueByProp,
} from "./linkUtils"
import flatten from "lodash/flatten"
import { USER_METDATA_PREFIX } from "../utils"
@@ -13,16 +13,25 @@ import { getGlobalUsersFromMetadata } from "../../utilities/global"
import { processFormulas } from "../../utilities/rowProcessor"
import { context } from "@budibase/backend-core"
import {
- Table,
- Row,
- LinkDocumentValue,
- FieldType,
ContextUser,
+ FieldType,
+ LinkDocumentValue,
+ Row,
+ Table,
} from "@budibase/types"
import sdk from "../../sdk"
export { IncludeDocs, getLinkDocuments, createLinkView } from "./linkUtils"
+const INVALID_DISPLAY_COLUMN_TYPE = [
+ FieldType.LINK,
+ FieldType.ATTACHMENTS,
+ FieldType.ATTACHMENT_SINGLE,
+ FieldType.SIGNATURE_SINGLE,
+ FieldType.BB_REFERENCE,
+ FieldType.BB_REFERENCE_SINGLE,
+]
+
/**
* This functionality makes sure that when rows with links are created, updated or deleted they are processed
* correctly - making sure that no stale links are left around and that all links have been made successfully.
@@ -206,6 +215,31 @@ export async function attachFullLinkedDocs(
return rows
}
+/**
+ * Finds a valid value for the primary display, avoiding columns which break things
+ * like relationships (can be circular).
+ * @param row The row to lift a value from for the primary display.
+ * @param table The related table to attempt to work out the primary display column from.
+ */
+function getPrimaryDisplayValue(row: Row, table?: Table) {
+ const primaryDisplay = table?.primaryDisplay
+ let invalid = true
+ if (primaryDisplay) {
+ const primaryDisplaySchema = table?.schema[primaryDisplay]
+ invalid = INVALID_DISPLAY_COLUMN_TYPE.includes(primaryDisplaySchema.type)
+ }
+ if (invalid || !primaryDisplay) {
+ const validKey = Object.keys(table?.schema || {}).find(
+ key =>
+ table?.schema[key].type &&
+ !INVALID_DISPLAY_COLUMN_TYPE.includes(table?.schema[key].type)
+ )
+ return validKey ? row[validKey] : undefined
+ } else {
+ return row[primaryDisplay]
+ }
+}
+
/**
* This function will take the given enriched rows and squash the links to only contain the primary display field.
* @param table The table from which the rows originated.
@@ -232,9 +266,7 @@ export async function squashLinksToPrimaryDisplay(
const linkTblId = link.tableId || getRelatedTableForField(table, column)
const linkedTable = await getLinkedTable(linkTblId!, linkedTables)
const obj: any = { _id: link._id }
- if (linkedTable?.primaryDisplay && link[linkedTable.primaryDisplay]) {
- obj.primaryDisplay = link[linkedTable.primaryDisplay]
- }
+ obj.primaryDisplay = getPrimaryDisplayValue(link, linkedTable)
newLinks.push(obj)
}
row[column] = newLinks
diff --git a/packages/server/src/integrations/tests/sql.spec.ts b/packages/server/src/integrations/tests/sql.spec.ts
index c4b2a69f7d..a6e63c434d 100644
--- a/packages/server/src/integrations/tests/sql.spec.ts
+++ b/packages/server/src/integrations/tests/sql.spec.ts
@@ -194,8 +194,8 @@ describe("SQL query builder", () => {
})
)
expect(query).toEqual({
- bindings: ["john%", limit, 5000],
- sql: `select * from (select * from (select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2) "test" order by "test"."id" asc) where rownum <= :3`,
+ bindings: ["john%", limit, "john%", 5000],
+ sql: `select * from (select * from (select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2) "test" where LOWER("test"."name") LIKE :3 order by "test"."id" asc) where rownum <= :4`,
})
query = new Sql(SqlClient.ORACLE, limit)._query(
@@ -208,9 +208,10 @@ describe("SQL query builder", () => {
},
})
)
+ const filterSet = [`%20%`, `%25%`, `%"john"%`, `%"mary"%`]
expect(query).toEqual({
- bindings: ["%20%", "%25%", `%"john"%`, `%"mary"%`, limit, 5000],
- sql: `select * from (select * from (select * from (select * from "test" where COALESCE(LOWER("test"."age"), '') LIKE :1 AND COALESCE(LOWER("test"."age"), '') LIKE :2 and COALESCE(LOWER("test"."name"), '') LIKE :3 AND COALESCE(LOWER("test"."name"), '') LIKE :4 order by "test"."id" asc) where rownum <= :5) "test" order by "test"."id" asc) where rownum <= :6`,
+ bindings: [...filterSet, limit, ...filterSet, 5000],
+ sql: `select * from (select * from (select * from (select * from "test" where COALESCE(LOWER("test"."age"), '') LIKE :1 AND COALESCE(LOWER("test"."age"), '') LIKE :2 and COALESCE(LOWER("test"."name"), '') LIKE :3 AND COALESCE(LOWER("test"."name"), '') LIKE :4 order by "test"."id" asc) where rownum <= :5) "test" where COALESCE(LOWER("test"."age"), '') LIKE :6 AND COALESCE(LOWER("test"."age"), '') LIKE :7 and COALESCE(LOWER("test"."name"), '') LIKE :8 AND COALESCE(LOWER("test"."name"), '') LIKE :9 order by "test"."id" asc) where rownum <= :10`,
})
query = new Sql(SqlClient.ORACLE, limit)._query(
@@ -223,8 +224,8 @@ describe("SQL query builder", () => {
})
)
expect(query).toEqual({
- bindings: [`%jo%`, limit, 5000],
- sql: `select * from (select * from (select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2) "test" order by "test"."id" asc) where rownum <= :3`,
+ bindings: [`%jo%`, limit, `%jo%`, 5000],
+ sql: `select * from (select * from (select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2) "test" where LOWER("test"."name") LIKE :3 order by "test"."id" asc) where rownum <= :4`,
})
})
@@ -241,8 +242,8 @@ describe("SQL query builder", () => {
)
expect(query).toEqual({
- bindings: ["John", limit, 5000],
- sql: `select * from (select * from (select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") = :1) order by "test"."id" asc) where rownum <= :2) "test" order by "test"."id" asc) where rownum <= :3`,
+ bindings: ["John", limit, "John", 5000],
+ sql: `select * from (select * from (select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") = :1) order by "test"."id" asc) where rownum <= :2) "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") = :3) order by "test"."id" asc) where rownum <= :4`,
})
})
@@ -259,8 +260,8 @@ describe("SQL query builder", () => {
)
expect(query).toEqual({
- bindings: ["John", limit, 5000],
- sql: `select * from (select * from (select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") != :1) OR to_char("test"."name") IS NULL order by "test"."id" asc) where rownum <= :2) "test" order by "test"."id" asc) where rownum <= :3`,
+ bindings: ["John", limit, "John", 5000],
+ sql: `select * from (select * from (select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") != :1) OR to_char("test"."name") IS NULL order by "test"."id" asc) where rownum <= :2) "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") != :3) OR to_char("test"."name") IS NULL order by "test"."id" asc) where rownum <= :4`,
})
})
})
diff --git a/packages/server/src/integrations/tests/sqlAlias.spec.ts b/packages/server/src/integrations/tests/sqlAlias.spec.ts
index 0b433896bf..6f34f4eb89 100644
--- a/packages/server/src/integrations/tests/sqlAlias.spec.ts
+++ b/packages/server/src/integrations/tests/sqlAlias.spec.ts
@@ -97,13 +97,14 @@ describe("Captures of real examples", () => {
const filters = queryJson.filters?.oneOf?.taskid as number[]
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
expect(query).toEqual({
- bindings: [...filters, limit, limit],
- sql: multiline(`select "a"."executorid" as "a.executorid", "a"."taskname" as "a.taskname",
- "a"."taskid" as "a.taskid", "a"."completed" as "a.completed", "a"."qaid" as "a.qaid",
- "b"."productname" as "b.productname", "b"."productid" as "b.productid"
- from (select * from "tasks" as "a" where "a"."taskid" in ($1, $2) order by "a"."taskid" asc limit $3) as "a"
- left join "products_tasks" as "c" on "a"."taskid" = "c"."taskid"
- left join "products" as "b" on "b"."productid" = "c"."productid" order by "a"."taskid" asc limit $4`),
+ bindings: [...filters, limit, ...filters, limit],
+ sql: multiline(
+ `select "a"."executorid" as "a.executorid", "a"."taskname" as "a.taskname", "a"."taskid" as "a.taskid",
+ "a"."completed" as "a.completed", "a"."qaid" as "a.qaid", "b"."productname" as "b.productname", "b"."productid" as "b.productid"
+ from (select * from "tasks" as "a" where "a"."taskid" in ($1, $2) order by "a"."taskid" asc limit $3) as "a"
+ left join "products_tasks" as "c" on "a"."taskid" = "c"."taskid" left join "products" as "b" on "b"."productid" = "c"."productid"
+ where "a"."taskid" in ($4, $5) order by "a"."taskid" asc limit $6`
+ ),
})
})
@@ -123,6 +124,7 @@ describe("Captures of real examples", () => {
rangeValue.low,
rangeValue.high,
equalValue,
+ true,
limit,
],
sql: expect.stringContaining(
@@ -186,8 +188,9 @@ describe("Captures of real examples", () => {
}, queryJson)
expect(returningQuery).toEqual({
sql: multiline(`select top (@p0) * from (select top (@p1) * from [people] where CASE WHEN [people].[name] = @p2
- THEN 1 ELSE 0 END = 1 and CASE WHEN [people].[age] = @p3 THEN 1 ELSE 0 END = 1 order by [people].[name] asc) as [people]`),
- bindings: [5000, 1, "Test", 22],
+ THEN 1 ELSE 0 END = 1 and CASE WHEN [people].[age] = @p3 THEN 1 ELSE 0 END = 1 order by [people].[name] asc) as [people]
+ where CASE WHEN [people].[name] = @p4 THEN 1 ELSE 0 END = 1 and CASE WHEN [people].[age] = @p5 THEN 1 ELSE 0 END = 1`),
+ bindings: [5000, 1, "Test", 22, "Test", 22],
})
})
})
diff --git a/packages/server/src/sdk/app/rows/queryUtils.ts b/packages/server/src/sdk/app/rows/queryUtils.ts
new file mode 100644
index 0000000000..65f400a1d9
--- /dev/null
+++ b/packages/server/src/sdk/app/rows/queryUtils.ts
@@ -0,0 +1,116 @@
+import { db } from "@budibase/backend-core"
+import {
+ FieldType,
+ isLogicalSearchOperator,
+ SearchFilters,
+ Table,
+} from "@budibase/types"
+import { cloneDeep } from "lodash/fp"
+import sdk from "../../../sdk"
+
+export const removeInvalidFilters = (
+ filters: SearchFilters,
+ validFields: string[]
+) => {
+ const result = cloneDeep(filters)
+
+ validFields = validFields.map(f => f.toLowerCase())
+ for (const filterKey of Object.keys(result) as (keyof SearchFilters)[]) {
+ const filter = result[filterKey]
+ if (!filter || typeof filter !== "object") {
+ continue
+ }
+ if (isLogicalSearchOperator(filterKey)) {
+ const resultingConditions: SearchFilters[] = []
+ for (const condition of filter.conditions) {
+ const resultingCondition = removeInvalidFilters(condition, validFields)
+ if (Object.keys(resultingCondition).length) {
+ resultingConditions.push(resultingCondition)
+ }
+ }
+ if (resultingConditions.length) {
+ filter.conditions = resultingConditions
+ } else {
+ delete result[filterKey]
+ }
+ continue
+ }
+
+ for (const columnKey of Object.keys(filter)) {
+ const possibleKeys = [columnKey, db.removeKeyNumbering(columnKey)].map(
+ c => c.toLowerCase()
+ )
+ if (!validFields.some(f => possibleKeys.includes(f.toLowerCase()))) {
+ delete filter[columnKey as keyof typeof filter]
+ }
+ }
+ if (!Object.keys(filter).length) {
+ delete result[filterKey]
+ }
+ }
+
+ return result
+}
+
+export const getQueryableFields = async (
+ fields: string[],
+ table: Table
+): Promise => {
+ const extractTableFields = async (
+ table: Table,
+ allowedFields: string[],
+ fromTables: string[],
+ opts?: { noRelationships?: boolean }
+ ): Promise => {
+ const result = []
+ for (const field of Object.keys(table.schema).filter(
+ f => allowedFields.includes(f) && table.schema[f].visible !== false
+ )) {
+ const subSchema = table.schema[field]
+ const isRelationship = subSchema.type === FieldType.LINK
+ // avoid relationship loops
+ if (
+ isRelationship &&
+ (opts?.noRelationships || fromTables.includes(subSchema.tableId))
+ ) {
+ continue
+ }
+ if (isRelationship) {
+ try {
+ const relatedTable = await sdk.tables.getTable(subSchema.tableId)
+ const relatedFields = await extractTableFields(
+ relatedTable,
+ Object.keys(relatedTable.schema),
+ [...fromTables, subSchema.tableId],
+ // don't let it recurse back and forth between relationships
+ { noRelationships: true }
+ )
+
+ result.push(
+ ...relatedFields.flatMap(f => [
+ `${subSchema.name}.${f}`,
+ // should be able to filter by relationship using table name
+ `${relatedTable.name}.${f}`,
+ ])
+ )
+ } catch (err: any) {
+ // if related table is removed, ignore
+ if (err.status !== 404) {
+ throw err
+ }
+ }
+ } else {
+ result.push(field)
+ }
+ }
+ return result
+ }
+
+ const result = [
+ "_id", // Querying by _id is always allowed, even if it's never part of the schema
+ ]
+
+ result.push(...(await extractTableFields(table, fields, [table._id!])))
+
+ return result
+}
diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts
index 1ccd89639b..6a4286814d 100644
--- a/packages/server/src/sdk/app/rows/search.ts
+++ b/packages/server/src/sdk/app/rows/search.ts
@@ -14,6 +14,7 @@ import sdk from "../../index"
import { searchInputMapping } from "./search/utils"
import { db as dbCore } from "@budibase/backend-core"
import tracer from "dd-trace"
+import { getQueryableFields, removeInvalidFilters } from "./queryUtils"
export { isValidFilter } from "../../../integrations/utils"
@@ -73,6 +74,18 @@ export async function search(
const table = await sdk.tables.getTable(options.tableId)
options = searchInputMapping(table, options)
+ if (options.query) {
+ const tableFields = Object.keys(table.schema).filter(
+ f => table.schema[f].visible !== false
+ )
+
+ const queriableFields = await getQueryableFields(
+ options.fields?.filter(f => tableFields.includes(f)) ?? tableFields,
+ table
+ )
+ options.query = removeInvalidFilters(options.query, queriableFields)
+ }
+
let result: SearchResponse
if (isExternalTable) {
span?.addTags({ searchType: "external" })
diff --git a/packages/server/src/sdk/app/rows/search/filters.ts b/packages/server/src/sdk/app/rows/search/filters.ts
index ccce0ab86a..4049fc5352 100644
--- a/packages/server/src/sdk/app/rows/search/filters.ts
+++ b/packages/server/src/sdk/app/rows/search/filters.ts
@@ -5,6 +5,7 @@ import {
Table,
} from "@budibase/types"
import { isPlainObject } from "lodash"
+import { dataFilters } from "@budibase/shared-core"
export function getRelationshipColumns(table: Table): {
name: string
@@ -58,5 +59,7 @@ export function updateFilterKeys(
}
}
}
- return filters
+ return dataFilters.recurseLogicalOperators(filters, (f: SearchFilters) => {
+ return updateFilterKeys(f, updates)
+ })
}
diff --git a/packages/server/src/sdk/app/rows/search/internal/sqs.ts b/packages/server/src/sdk/app/rows/search/internal/sqs.ts
index 4bfa8f8fa5..6736ff6abf 100644
--- a/packages/server/src/sdk/app/rows/search/internal/sqs.ts
+++ b/packages/server/src/sdk/app/rows/search/internal/sqs.ts
@@ -297,7 +297,7 @@ export async function search(
throw new Error("Unable to find table")
}
- const relationships = buildInternalRelationships(table)
+ const relationships = buildInternalRelationships(table, allTables)
const searchFilters: SearchFilters = {
...cleanupFilters(query, table, allTables),
diff --git a/packages/server/src/sdk/app/rows/search/tests/search.spec.ts b/packages/server/src/sdk/app/rows/search/tests/search.spec.ts
index 252b9a6556..db1104bcf6 100644
--- a/packages/server/src/sdk/app/rows/search/tests/search.spec.ts
+++ b/packages/server/src/sdk/app/rows/search/tests/search.spec.ts
@@ -1,4 +1,11 @@
-import { Datasource, FieldType, Row, Table } from "@budibase/types"
+import {
+ AutoColumnFieldMetadata,
+ AutoFieldSubType,
+ Datasource,
+ FieldType,
+ NumberFieldMetadata,
+ Table,
+} from "@budibase/types"
import TestConfiguration from "../../../../../tests/utilities/TestConfiguration"
import { search } from "../../../../../sdk/app/rows/search"
@@ -32,7 +39,6 @@ describe.each([
let envCleanup: (() => void) | undefined
let datasource: Datasource | undefined
let table: Table
- let rows: Row[]
beforeAll(async () => {
await withCoreEnv({ SQS_SEARCH_ENABLE: isSqs ? "true" : "false" }, () =>
@@ -51,16 +57,28 @@ describe.each([
datasource: await dsProvider,
})
}
+ })
+
+ beforeEach(async () => {
+ const idFieldSchema: NumberFieldMetadata | AutoColumnFieldMetadata =
+ isInternal
+ ? {
+ name: "id",
+ type: FieldType.AUTO,
+ subtype: AutoFieldSubType.AUTO_ID,
+ autocolumn: true,
+ }
+ : {
+ name: "id",
+ type: FieldType.NUMBER,
+ autocolumn: true,
+ }
table = await config.api.table.save(
tableForDatasource(datasource, {
primary: ["id"],
schema: {
- id: {
- name: "id",
- type: FieldType.NUMBER,
- autocolumn: true,
- },
+ id: idFieldSchema,
name: {
name: "name",
type: FieldType.STRING,
@@ -81,16 +99,13 @@ describe.each([
})
)
- rows = []
for (let i = 0; i < 10; i++) {
- rows.push(
- await config.api.row.save(table._id!, {
- name: generator.first(),
- surname: generator.last(),
- age: generator.age(),
- address: generator.address(),
- })
- )
+ await config.api.row.save(table._id!, {
+ name: generator.first(),
+ surname: generator.last(),
+ age: generator.age(),
+ address: generator.address(),
+ })
}
})
@@ -138,4 +153,100 @@ describe.each([
)
})
})
+
+ it("does not allow accessing hidden fields", async () => {
+ await config.doInContext(config.appId, async () => {
+ await config.api.table.save({
+ ...table,
+ schema: {
+ ...table.schema,
+ name: {
+ ...table.schema.name,
+ visible: true,
+ },
+ age: {
+ ...table.schema.age,
+ visible: false,
+ },
+ },
+ })
+ const result = await search({
+ tableId: table._id!,
+ query: {},
+ })
+ expect(result.rows).toHaveLength(10)
+ for (const row of result.rows) {
+ const keys = Object.keys(row)
+ expect(keys).toContain("name")
+ expect(keys).toContain("surname")
+ expect(keys).toContain("address")
+ expect(keys).not.toContain("age")
+ }
+ })
+ })
+
+ it("does not allow accessing hidden fields even if requested", async () => {
+ await config.doInContext(config.appId, async () => {
+ await config.api.table.save({
+ ...table,
+ schema: {
+ ...table.schema,
+ name: {
+ ...table.schema.name,
+ visible: true,
+ },
+ age: {
+ ...table.schema.age,
+ visible: false,
+ },
+ },
+ })
+ const result = await search({
+ tableId: table._id!,
+ query: {},
+ fields: ["name", "age"],
+ })
+ expect(result.rows).toHaveLength(10)
+ for (const row of result.rows) {
+ const keys = Object.keys(row)
+ expect(keys).toContain("name")
+ expect(keys).not.toContain("age")
+ expect(keys).not.toContain("surname")
+ expect(keys).not.toContain("address")
+ }
+ })
+ })
+
+ !isLucene &&
+ it.each([
+ [["id", "name", "age"], 3],
+ [["name", "age"], 10],
+ ])(
+ "cannot query by non search fields (fields: %s)",
+ async (queryFields, expectedRows) => {
+ await config.doInContext(config.appId, async () => {
+ const { rows } = await search({
+ tableId: table._id!,
+ query: {
+ $or: {
+ conditions: [
+ {
+ $and: {
+ conditions: [
+ { range: { id: { low: 2, high: 4 } } },
+ { range: { id: { low: 3, high: 5 } } },
+ ],
+ },
+ },
+ { equal: { id: 7 } },
+ ],
+ },
+ },
+ fields: queryFields,
+ })
+
+ expect(rows).toHaveLength(expectedRows)
+ })
+ }
+ )
})
diff --git a/packages/server/src/sdk/app/rows/sqlAlias.ts b/packages/server/src/sdk/app/rows/sqlAlias.ts
index 664e64057b..535709791d 100644
--- a/packages/server/src/sdk/app/rows/sqlAlias.ts
+++ b/packages/server/src/sdk/app/rows/sqlAlias.ts
@@ -12,6 +12,7 @@ import { getSQLClient } from "./utils"
import { cloneDeep } from "lodash"
import datasources from "../datasources"
import { BudibaseInternalDB } from "../../../db/utils"
+import { dataFilters } from "@budibase/shared-core"
type PerformQueryFunction = (
datasource: Datasource,
@@ -199,16 +200,20 @@ export default class AliasTables {
)
}
if (json.filters) {
- for (let [filterKey, filter] of Object.entries(json.filters)) {
- if (typeof filter !== "object") {
- continue
+ const aliasFilters = (filters: SearchFilters): SearchFilters => {
+ for (let [filterKey, filter] of Object.entries(filters)) {
+ if (typeof filter !== "object") {
+ continue
+ }
+ const aliasedFilters: typeof filter = {}
+ for (let key of Object.keys(filter)) {
+ aliasedFilters[this.aliasField(key)] = filter[key]
+ }
+ filters[filterKey as keyof SearchFilters] = aliasedFilters
}
- const aliasedFilters: typeof filter = {}
- for (let key of Object.keys(filter)) {
- aliasedFilters[this.aliasField(key)] = filter[key]
- }
- json.filters[filterKey as keyof SearchFilters] = aliasedFilters
+ return dataFilters.recurseLogicalOperators(filters, aliasFilters)
}
+ json.filters = aliasFilters(json.filters)
}
if (json.meta?.table) {
this.getAlias(json.meta.table.name)
diff --git a/packages/server/src/sdk/app/rows/tests/queryUtils.spec.ts b/packages/server/src/sdk/app/rows/tests/queryUtils.spec.ts
new file mode 100644
index 0000000000..aabc359484
--- /dev/null
+++ b/packages/server/src/sdk/app/rows/tests/queryUtils.spec.ts
@@ -0,0 +1,505 @@
+import {
+ FieldType,
+ RelationshipType,
+ SearchFilters,
+ Table,
+} from "@budibase/types"
+import { getQueryableFields, removeInvalidFilters } from "../queryUtils"
+import { structures } from "../../../../api/routes/tests/utilities"
+import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
+
+describe("query utils", () => {
+ describe("removeInvalidFilters", () => {
+ const fullFilters: SearchFilters = {
+ equal: { one: "foo" },
+ $or: {
+ conditions: [
+ {
+ equal: { one: "foo2", two: "bar" },
+ notEmpty: { one: null },
+ $and: {
+ conditions: [
+ {
+ equal: { three: "baz" },
+ notEmpty: { forth: null },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ $and: {
+ conditions: [{ equal: { one: "foo2" }, notEmpty: { one: null } }],
+ },
+ }
+
+ it("can filter empty queries", () => {
+ const filters: SearchFilters = {}
+ const result = removeInvalidFilters(filters, [])
+ expect(result).toEqual({})
+ })
+
+ it("does not trim any valid field", () => {
+ const result = removeInvalidFilters(fullFilters, [
+ "one",
+ "two",
+ "three",
+ "forth",
+ ])
+ expect(result).toEqual(fullFilters)
+ })
+
+ it("trims invalid field", () => {
+ const result = removeInvalidFilters(fullFilters, [
+ "one",
+ "three",
+ "forth",
+ ])
+ expect(result).toEqual({
+ equal: { one: "foo" },
+ $or: {
+ conditions: [
+ {
+ equal: { one: "foo2" },
+ notEmpty: { one: null },
+ $and: {
+ conditions: [
+ {
+ equal: { three: "baz" },
+ notEmpty: { forth: null },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ $and: {
+ conditions: [{ equal: { one: "foo2" }, notEmpty: { one: null } }],
+ },
+ })
+ })
+
+ it("trims invalid field keeping a valid fields", () => {
+ const result = removeInvalidFilters(fullFilters, ["three", "forth"])
+ const expected: SearchFilters = {
+ $or: {
+ conditions: [
+ {
+ $and: {
+ conditions: [
+ {
+ equal: { three: "baz" },
+ notEmpty: { forth: null },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ }
+ expect(result).toEqual(expected)
+ })
+
+ it("keeps filter key numering", () => {
+ const prefixedFilters: SearchFilters = {
+ equal: { "1:one": "foo" },
+ $or: {
+ conditions: [
+ {
+ equal: { "2:one": "foo2", "3:two": "bar" },
+ notEmpty: { "4:one": null },
+ $and: {
+ conditions: [
+ {
+ equal: { "5:three": "baz", two: "bar2" },
+ notEmpty: { forth: null },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ $and: {
+ conditions: [{ equal: { "6:one": "foo2" }, notEmpty: { one: null } }],
+ },
+ }
+
+ const result = removeInvalidFilters(prefixedFilters, [
+ "one",
+ "three",
+ "forth",
+ ])
+ expect(result).toEqual({
+ equal: { "1:one": "foo" },
+ $or: {
+ conditions: [
+ {
+ equal: { "2:one": "foo2" },
+ notEmpty: { "4:one": null },
+ $and: {
+ conditions: [
+ {
+ equal: { "5:three": "baz" },
+ notEmpty: { forth: null },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ $and: {
+ conditions: [{ equal: { "6:one": "foo2" }, notEmpty: { one: null } }],
+ },
+ })
+ })
+
+ it("handles relationship filters", () => {
+ const prefixedFilters: SearchFilters = {
+ $or: {
+ conditions: [
+ { equal: { "1:other.one": "foo" } },
+ {
+ equal: {
+ "2:other.one": "foo2",
+ "3:other.two": "bar",
+ "4:other.three": "baz",
+ },
+ },
+ { equal: { "another.three": "baz2" } },
+ ],
+ },
+ }
+
+ const result = removeInvalidFilters(prefixedFilters, [
+ "other.one",
+ "other.two",
+ "another.three",
+ ])
+ expect(result).toEqual({
+ $or: {
+ conditions: [
+ { equal: { "1:other.one": "foo" } },
+ { equal: { "2:other.one": "foo2", "3:other.two": "bar" } },
+ { equal: { "another.three": "baz2" } },
+ ],
+ },
+ })
+ })
+ })
+
+ describe("getQueryableFields", () => {
+ const config = new TestConfiguration()
+
+ beforeAll(async () => {
+ await config.init()
+ })
+
+ it("returns table schema fields and _id", async () => {
+ const table: Table = await config.api.table.save({
+ ...structures.basicTable(),
+ schema: {
+ name: { name: "name", type: FieldType.STRING },
+ age: { name: "age", type: FieldType.NUMBER },
+ },
+ })
+
+ const result = await getQueryableFields(Object.keys(table.schema), table)
+ expect(result).toEqual(["_id", "name", "age"])
+ })
+
+ it("excludes hidden fields", async () => {
+ const table: Table = await config.api.table.save({
+ ...structures.basicTable(),
+ schema: {
+ name: { name: "name", type: FieldType.STRING },
+ age: { name: "age", type: FieldType.NUMBER, visible: false },
+ },
+ })
+
+ const result = await getQueryableFields(Object.keys(table.schema), table)
+ expect(result).toEqual(["_id", "name"])
+ })
+
+ it("includes relationship fields", async () => {
+ const aux: Table = await config.api.table.save({
+ ...structures.basicTable(),
+ name: "auxTable",
+ schema: {
+ title: { name: "title", type: FieldType.STRING },
+ name: { name: "name", type: FieldType.STRING },
+ },
+ })
+
+ const table: Table = await config.api.table.save({
+ ...structures.basicTable(),
+ schema: {
+ name: { name: "name", type: FieldType.STRING },
+ aux: {
+ name: "aux",
+ type: FieldType.LINK,
+ tableId: aux._id!,
+ relationshipType: RelationshipType.ONE_TO_MANY,
+ fieldName: "table",
+ },
+ },
+ })
+
+ const result = await config.doInContext(config.appId, () => {
+ return getQueryableFields(Object.keys(table.schema), table)
+ })
+ expect(result).toEqual([
+ "_id",
+ "name",
+ "aux.title",
+ "auxTable.title",
+ "aux.name",
+ "auxTable.name",
+ ])
+ })
+
+ it("excludes hidden relationship fields", async () => {
+ const aux: Table = await config.api.table.save({
+ ...structures.basicTable(),
+ name: "auxTable",
+ schema: {
+ title: { name: "title", type: FieldType.STRING, visible: false },
+ name: { name: "name", type: FieldType.STRING, visible: true },
+ },
+ })
+
+ const table: Table = await config.api.table.save({
+ ...structures.basicTable(),
+ schema: {
+ name: { name: "name", type: FieldType.STRING },
+ aux: {
+ name: "aux",
+ type: FieldType.LINK,
+ tableId: aux._id!,
+ relationshipType: RelationshipType.ONE_TO_MANY,
+ fieldName: "table",
+ },
+ },
+ })
+
+ const result = await config.doInContext(config.appId, () => {
+ return getQueryableFields(Object.keys(table.schema), table)
+ })
+ expect(result).toEqual(["_id", "name", "aux.name", "auxTable.name"])
+ })
+
+ it("excludes all relationship fields if hidden", async () => {
+ const aux: Table = await config.api.table.save({
+ ...structures.basicTable(),
+ name: "auxTable",
+ schema: {
+ title: { name: "title", type: FieldType.STRING, visible: false },
+ name: { name: "name", type: FieldType.STRING, visible: true },
+ },
+ })
+
+ const table: Table = await config.api.table.save({
+ ...structures.basicTable(),
+ schema: {
+ name: { name: "name", type: FieldType.STRING },
+ aux: {
+ name: "aux",
+ type: FieldType.LINK,
+ tableId: aux._id!,
+ relationshipType: RelationshipType.ONE_TO_MANY,
+ fieldName: "table",
+ visible: false,
+ },
+ },
+ })
+
+ const result = await config.doInContext(config.appId, () => {
+ return getQueryableFields(Object.keys(table.schema), table)
+ })
+ expect(result).toEqual(["_id", "name"])
+ })
+
+ describe("nested relationship", () => {
+ describe("one-to-many", () => {
+ let table: Table, aux1: Table, aux2: Table
+
+ beforeAll(async () => {
+ const { _id: aux1Id } = await config.api.table.save({
+ ...structures.basicTable(),
+ name: "aux1Table",
+ schema: {
+ name: { name: "name", type: FieldType.STRING },
+ },
+ })
+ const { _id: aux2Id } = await config.api.table.save({
+ ...structures.basicTable(),
+ name: "aux2Table",
+ schema: {
+ title: { name: "title", type: FieldType.STRING },
+ aux1_1: {
+ name: "aux1_1",
+ type: FieldType.LINK,
+ tableId: aux1Id!,
+ relationshipType: RelationshipType.ONE_TO_MANY,
+ fieldName: "aux2_1",
+ },
+ aux1_2: {
+ name: "aux1_2",
+ type: FieldType.LINK,
+ tableId: aux1Id!,
+ relationshipType: RelationshipType.ONE_TO_MANY,
+ fieldName: "aux2_2",
+ },
+ },
+ })
+
+ const { _id: tableId } = await config.api.table.save({
+ ...structures.basicTable(),
+ schema: {
+ name: { name: "name", type: FieldType.STRING },
+ aux1: {
+ name: "aux1",
+ type: FieldType.LINK,
+ tableId: aux1Id!,
+ relationshipType: RelationshipType.ONE_TO_MANY,
+ fieldName: "table",
+ },
+ aux2: {
+ name: "aux2",
+ type: FieldType.LINK,
+ tableId: aux2Id!,
+ relationshipType: RelationshipType.ONE_TO_MANY,
+ fieldName: "table",
+ },
+ },
+ })
+
+ // We need to refech them to get the updated foreign keys
+ aux1 = await config.api.table.get(aux1Id!)
+ aux2 = await config.api.table.get(aux2Id!)
+ table = await config.api.table.get(tableId!)
+ })
+
+ it("includes nested relationship fields from main table", async () => {
+ const result = await config.doInContext(config.appId, () => {
+ return getQueryableFields(Object.keys(table.schema), table)
+ })
+ expect(result).toEqual([
+ "_id",
+ "name",
+ // aux1 primitive props
+ "aux1.name",
+ "aux1Table.name",
+
+ // aux2 primitive props
+ "aux2.title",
+ "aux2Table.title",
+ ])
+ })
+
+ it("includes nested relationship fields from aux 1 table", async () => {
+ const result = await config.doInContext(config.appId, () => {
+ return getQueryableFields(Object.keys(aux1.schema), aux1)
+ })
+ expect(result).toEqual([
+ "_id",
+ "name",
+
+ // aux2_1 primitive props
+ "aux2_1.title",
+ "aux2Table.title",
+
+ // aux2_2 primitive props
+ "aux2_2.title",
+ "aux2Table.title",
+
+ // table primitive props
+ "table.name",
+ "TestTable.name",
+ ])
+ })
+
+ it("includes nested relationship fields from aux 2 table", async () => {
+ const result = await config.doInContext(config.appId, () => {
+ return getQueryableFields(Object.keys(aux2.schema), aux2)
+ })
+ expect(result).toEqual([
+ "_id",
+ "title",
+
+ // aux1_1 primitive props
+ "aux1_1.name",
+ "aux1Table.name",
+
+ // aux1_2 primitive props
+ "aux1_2.name",
+ "aux1Table.name",
+
+ // table primitive props
+ "table.name",
+ "TestTable.name",
+ ])
+ })
+ })
+
+ describe("many-to-many", () => {
+ let table: Table, aux: Table
+
+ beforeAll(async () => {
+ const { _id: auxId } = await config.api.table.save({
+ ...structures.basicTable(),
+ name: "auxTable",
+ schema: {
+ title: { name: "title", type: FieldType.STRING },
+ },
+ })
+
+ const { _id: tableId } = await config.api.table.save({
+ ...structures.basicTable(),
+ schema: {
+ name: { name: "name", type: FieldType.STRING },
+ aux: {
+ name: "aux",
+ type: FieldType.LINK,
+ tableId: auxId!,
+ relationshipType: RelationshipType.MANY_TO_MANY,
+ fieldName: "table",
+ },
+ },
+ })
+
+ // We need to refech them to get the updated foreign keys
+ aux = await config.api.table.get(auxId!)
+ table = await config.api.table.get(tableId!)
+ })
+
+ it("includes nested relationship fields from main table", async () => {
+ const result = await config.doInContext(config.appId, () => {
+ return getQueryableFields(Object.keys(table.schema), table)
+ })
+ expect(result).toEqual([
+ "_id",
+ "name",
+
+ // deep 1 aux primitive props
+ "aux.title",
+ "auxTable.title",
+ ])
+ })
+
+ it("includes nested relationship fields from aux table", async () => {
+ const result = await config.doInContext(config.appId, () => {
+ return getQueryableFields(Object.keys(aux.schema), aux)
+ })
+ expect(result).toEqual([
+ "_id",
+ "title",
+
+ // deep 1 dependency primitive props
+ "table.name",
+ "TestTable.name",
+ ])
+ })
+ })
+ })
+ })
+})
diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts
index 62a3b2dd74..4b2fd83882 100644
--- a/packages/server/src/utilities/rowProcessor/index.ts
+++ b/packages/server/src/utilities/rowProcessor/index.ts
@@ -26,8 +26,13 @@ import {
processOutputBBReferences,
} from "./bbReferenceProcessor"
import { isExternalTableID } from "../../integrations/utils"
-import { helpers } from "@budibase/shared-core"
+import {
+ helpers,
+ PROTECTED_EXTERNAL_COLUMNS,
+ PROTECTED_INTERNAL_COLUMNS,
+} from "@budibase/shared-core"
import { processString } from "@budibase/string-templates"
+import { isUserMetadataTable } from "../../api/controllers/row/utils"
export * from "./utils"
export * from "./attachments"
@@ -53,9 +58,9 @@ export async function processAutoColumn(
row: Row,
opts?: AutoColumnProcessingOpts
) {
- let noUser = !userId
- let isUserTable = table._id === InternalTables.USER_METADATA
- let now = new Date().toISOString()
+ const noUser = !userId
+ const isUserTable = table._id === InternalTables.USER_METADATA
+ const now = new Date().toISOString()
// if a row doesn't have a revision then it doesn't exist yet
const creating = !row._rev
// check its not user table, or whether any of the processing options have been disabled
@@ -111,7 +116,7 @@ async function processDefaultValues(table: Table, row: Row) {
ctx.user = user
}
- for (let [key, schema] of Object.entries(table.schema)) {
+ for (const [key, schema] of Object.entries(table.schema)) {
if ("default" in schema && schema.default != null && row[key] == null) {
const processed = await processString(schema.default, ctx)
@@ -165,10 +170,10 @@ export async function inputProcessing(
row: Row,
opts?: AutoColumnProcessingOpts
) {
- let clonedRow = cloneDeep(row)
+ const clonedRow = cloneDeep(row)
const dontCleanseKeys = ["type", "_id", "_rev", "tableId"]
- for (let [key, value] of Object.entries(clonedRow)) {
+ for (const [key, value] of Object.entries(clonedRow)) {
const field = table.schema[key]
// cleanse fields that aren't in the schema
if (!field) {
@@ -268,13 +273,13 @@ export async function outputProcessing(
}
// process complex types: attachments, bb references...
- for (let [property, column] of Object.entries(table.schema)) {
+ for (const [property, column] of Object.entries(table.schema)) {
if (
column.type === FieldType.ATTACHMENTS ||
column.type === FieldType.ATTACHMENT_SINGLE ||
column.type === FieldType.SIGNATURE_SINGLE
) {
- for (let row of enriched) {
+ for (const row of enriched) {
if (row[property] == null) {
continue
}
@@ -299,7 +304,7 @@ export async function outputProcessing(
!opts.skipBBReferences &&
column.type == FieldType.BB_REFERENCE
) {
- for (let row of enriched) {
+ for (const row of enriched) {
row[property] = await processOutputBBReferences(
row[property],
column.subtype
@@ -309,14 +314,14 @@ export async function outputProcessing(
!opts.skipBBReferences &&
column.type == FieldType.BB_REFERENCE_SINGLE
) {
- for (let row of enriched) {
+ for (const row of enriched) {
row[property] = await processOutputBBReference(
row[property],
column.subtype
)
}
} else if (column.type === FieldType.DATETIME && column.timeOnly) {
- for (let row of enriched) {
+ for (const row of enriched) {
if (row[property] instanceof Date) {
const hours = row[property].getUTCHours().toString().padStart(2, "0")
const minutes = row[property]
@@ -343,14 +348,36 @@ export async function outputProcessing(
)) as Row[]
}
// remove null properties to match internal API
- if (isExternalTableID(table._id!)) {
- for (let row of enriched) {
- for (let key of Object.keys(row)) {
+ const isExternal = isExternalTableID(table._id!)
+ if (isExternal) {
+ for (const row of enriched) {
+ for (const key of Object.keys(row)) {
if (row[key] === null) {
delete row[key]
}
}
}
}
+
+ if (!isUserMetadataTable(table._id!)) {
+ const protectedColumns = isExternal
+ ? PROTECTED_EXTERNAL_COLUMNS
+ : PROTECTED_INTERNAL_COLUMNS
+
+ const tableFields = Object.keys(table.schema).filter(
+ f => table.schema[f].visible !== false
+ )
+ const fields = [...tableFields, ...protectedColumns].map(f =>
+ f.toLowerCase()
+ )
+ for (const row of enriched) {
+ for (const key of Object.keys(row)) {
+ if (!fields.includes(key.toLowerCase())) {
+ delete row[key]
+ }
+ }
+ }
+ }
+
return (wasArray ? enriched : enriched[0]) as T
}
diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts
index d695859979..1cad32e790 100644
--- a/packages/shared-core/src/filters.ts
+++ b/packages/shared-core/src/filters.ts
@@ -116,6 +116,20 @@ export const NoEmptyFilterStrings = [
OperatorOptions.In.value,
] as (keyof SearchQueryFields)[]
+export function recurseLogicalOperators(
+ filters: SearchFilters,
+ fn: (f: SearchFilters) => SearchFilters
+) {
+ for (const logical of Object.values(LogicalOperator)) {
+ if (filters[logical]) {
+ filters[logical]!.conditions = filters[logical]!.conditions.map(
+ condition => fn(condition)
+ )
+ }
+ }
+ return filters
+}
+
/**
* Removes any fields that contain empty strings that would cause inconsistent
* behaviour with how backend tables are filtered (no value means no filter).
@@ -148,6 +162,7 @@ export const cleanupQuery = (query: SearchFilters) => {
}
}
}
+ query = recurseLogicalOperators(query, cleanupQuery)
return query
}
@@ -562,6 +577,7 @@ export function fixupFilterArrays(filters: SearchFilters) {
}
}
}
+ recurseLogicalOperators(filters, fixupFilterArrays)
return filters
}
diff --git a/yarn.lock b/yarn.lock
index a636a62c87..713a1845b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4208,160 +4208,160 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27"
integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==
-"@rollup/rollup-android-arm-eabi@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz#7746deb85e4a8fb54fbfda8ac5c102692f102476"
- integrity sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==
+"@rollup/rollup-android-arm-eabi@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz#d941173f82f9b041c61b0dc1a2a91dcd06e4b31e"
+ integrity sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==
"@rollup/rollup-android-arm64@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203"
integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==
-"@rollup/rollup-android-arm64@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz#93de4d867709d3313794723b5afd91e1e174f906"
- integrity sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==
+"@rollup/rollup-android-arm64@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz#7e7157c8543215245ceffc445134d9e843ba51c0"
+ integrity sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==
"@rollup/rollup-darwin-arm64@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096"
integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==
-"@rollup/rollup-darwin-arm64@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz#e41e6a81673260ab196e0f59462b9940a6ac03cd"
- integrity sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==
+"@rollup/rollup-darwin-arm64@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz#f0a18a4fc8dc6eb1e94a51fa2adb22876f477947"
+ integrity sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==
"@rollup/rollup-darwin-x64@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c"
integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==
-"@rollup/rollup-darwin-x64@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz#2b0a0aef6e8c5317d494cfc9076d7a16b099bdcb"
- integrity sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==
+"@rollup/rollup-darwin-x64@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz#34b7867613e5cc42d2b85ddc0424228cc33b43f0"
+ integrity sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==
"@rollup/rollup-linux-arm-gnueabihf@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8"
integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==
-"@rollup/rollup-linux-arm-gnueabihf@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz#e22319deb5367384ef315e66bc6de80d2bf2b3ae"
- integrity sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==
+"@rollup/rollup-linux-arm-gnueabihf@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz#422b19ff9ae02b05d3395183d1d43b38c7c8be0b"
+ integrity sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==
"@rollup/rollup-linux-arm-musleabihf@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549"
integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==
-"@rollup/rollup-linux-arm-musleabihf@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz#d5dd68f5d7ae21b345a5c87208c94e5c813f54b8"
- integrity sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==
+"@rollup/rollup-linux-arm-musleabihf@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz#568aa29195ef6fc57ec6ed3f518923764406a8ee"
+ integrity sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==
"@rollup/rollup-linux-arm64-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577"
integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==
-"@rollup/rollup-linux-arm64-gnu@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz#1703d3a418d33f8f025acaf93f39ca1efcd5b645"
- integrity sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==
+"@rollup/rollup-linux-arm64-gnu@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz#22309c8bcba9a73114f69165c72bc94b2fbec085"
+ integrity sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==
"@rollup/rollup-linux-arm64-musl@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c"
integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==
-"@rollup/rollup-linux-arm64-musl@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz#3f59c2c6e60f75ce8b1090bd841c555e3bb01f0e"
- integrity sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==
+"@rollup/rollup-linux-arm64-musl@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz#c93c388af6d33f082894b8a60839d7265b2b9bc5"
+ integrity sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==
"@rollup/rollup-linux-powerpc64le-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf"
integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==
-"@rollup/rollup-linux-powerpc64le-gnu@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz#3f99a0921596a6f539121a312df29af52a205f15"
- integrity sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==
+"@rollup/rollup-linux-powerpc64le-gnu@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz#493c5e19e395cf3c6bd860c7139c8a903dea72b4"
+ integrity sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==
"@rollup/rollup-linux-riscv64-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9"
integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==
-"@rollup/rollup-linux-riscv64-gnu@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz#c08fb3e629d50d2eac31329347cfc559a1cf81d1"
- integrity sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==
+"@rollup/rollup-linux-riscv64-gnu@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz#a2eab4346fbe5909165ce99adb935ba30c9fb444"
+ integrity sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==
"@rollup/rollup-linux-s390x-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec"
integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==
-"@rollup/rollup-linux-s390x-gnu@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz#173722cd745779d730d4b24d21386185e0e12de8"
- integrity sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==
+"@rollup/rollup-linux-s390x-gnu@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz#0bc49a79db4345d78d757bb1b05e73a1b42fa5c3"
+ integrity sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==
"@rollup/rollup-linux-x64-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942"
integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==
-"@rollup/rollup-linux-x64-gnu@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz#0af2b6541ab0f4954d2c4f96bcdc7947420dd28c"
- integrity sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==
+"@rollup/rollup-linux-x64-gnu@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz#4fd36a6a41f3406d8693321b13d4f9b7658dd4b9"
+ integrity sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==
"@rollup/rollup-linux-x64-musl@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d"
integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==
-"@rollup/rollup-linux-x64-musl@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz#f973f9552744764b221128f7c3629222216ace69"
- integrity sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==
+"@rollup/rollup-linux-x64-musl@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz#10ebb13bd4469cbad1a5d9b073bd27ec8a886200"
+ integrity sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==
"@rollup/rollup-win32-arm64-msvc@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf"
integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==
-"@rollup/rollup-win32-arm64-msvc@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz#21ac5ed84d914bc31821fec3dd909f7257cfb17b"
- integrity sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==
+"@rollup/rollup-win32-arm64-msvc@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz#2fef1a90f1402258ef915ae5a94cc91a5a1d5bfc"
+ integrity sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==
"@rollup/rollup-win32-ia32-msvc@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54"
integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==
-"@rollup/rollup-win32-ia32-msvc@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz#0cfe740063b35dcd5a62c4e243226631a846ce11"
- integrity sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==
+"@rollup/rollup-win32-ia32-msvc@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz#a18ad47a95c5f264defb60acdd8c27569f816fc1"
+ integrity sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==
"@rollup/rollup-win32-x64-msvc@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4"
integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==
-"@rollup/rollup-win32-x64-msvc@4.19.1":
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz#5f2c40d3f1b53ede80fb4e6964f840c0f8936832"
- integrity sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==
+"@rollup/rollup-win32-x64-msvc@4.21.0":
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz#20c09cf44dcb082140cc7f439dd679fe4bba3375"
+ integrity sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==
"@roxi/routify@2.18.0":
version "2.18.0"
@@ -5127,6 +5127,11 @@
resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.2.12.tgz#9b08f23d5aa881b3441af7757800c7173e5685ff"
integrity sha512-rPFUW9SSW4+3/UJ3UrtY2/l3sQvlqB1fqxHLPDjgykvbfrnMejcCTNV4ZrFNHXpE/6+kGnk+yVViSPtWGwJzkA==
+"@spectrum-css/tag@3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@spectrum-css/tag/-/tag-3.0.0.tgz#b2e335dc526713b83f3e995e8d1d4fc84a3fc4df"
+ integrity sha512-a9z7ZTAWPonkWRNY5kxVaO6bxu9de3qUZWJ9Bl1YBlwWc8Fy1L7XqT4Wq3pW+4sktUbUUqqPYPIXK9xEFDofEw==
+
"@spectrum-css/tags@3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.2.tgz#5bf35fb79c97cd9344de485bd4626ad5b9f07757"
@@ -5874,11 +5879,11 @@
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
"@types/node@>=8.1.0":
- version "22.0.0"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-22.0.0.tgz#04862a2a71e62264426083abe1e27e87cac05a30"
- integrity sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==
+ version "22.4.2"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-22.4.2.tgz#55fefb1c3dba2ecd7eb76738c6b80da75760523f"
+ integrity sha512-nAvM3Ey230/XzxtyDcJ+VjvlzpzoHwLsF7JaDRfoI0ytO0mVheerNmM45CtA0yOILXwXXxOrcUWH3wltX+7PSw==
dependencies:
- undici-types "~6.11.1"
+ undici-types "~6.19.2"
"@types/node@^18.11.18":
version "18.19.10"
@@ -6878,39 +6883,34 @@ acorn-import-assertions@^1.9.0:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
-acorn-jsx-walk@2.0.0:
+acorn-jsx-walk@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz#a5ed648264e68282d7c2aead80216bfdf232573a"
integrity sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==
-acorn-jsx@5.3.2, acorn-jsx@^5.3.2:
+acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-acorn-loose@8.4.0:
+acorn-loose@^8.4.0:
version "8.4.0"
resolved "https://registry.yarnpkg.com/acorn-loose/-/acorn-loose-8.4.0.tgz#26d3e219756d1e180d006f5bcc8d261a28530f55"
integrity sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ==
dependencies:
acorn "^8.11.0"
-acorn-walk@8.3.3, acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0:
- version "8.3.3"
- resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e"
- integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==
- dependencies:
- acorn "^8.11.0"
-
acorn-walk@^7.1.1:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
-acorn@8.12.1, acorn@^8.12.0:
- version "8.12.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
- integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
+acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0, acorn-walk@^8.3.3:
+ version "8.3.3"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e"
+ integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==
+ dependencies:
+ acorn "^8.11.0"
acorn@^5.2.1, acorn@^5.7.3:
version "5.7.4"
@@ -6927,6 +6927,11 @@ acorn@^8.1.0, acorn@^8.10.0, acorn@^8.11.0, acorn@^8.11.3, acorn@^8.2.4, acorn@^
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c"
integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==
+acorn@^8.12.0, acorn@^8.12.1:
+ version "8.12.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
+ integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
+
add-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa"
@@ -6979,16 +6984,6 @@ ajv-formats@^2.0.2:
dependencies:
ajv "^8.0.0"
-ajv@8.17.1:
- version "8.17.1"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
- integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
- dependencies:
- fast-deep-equal "^3.1.3"
- fast-uri "^3.0.1"
- json-schema-traverse "^1.0.0"
- require-from-string "^2.0.2"
-
ajv@^6.12.3, ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -7009,6 +7004,16 @@ ajv@^8.0.0, ajv@^8.1.0, ajv@^8.4.0:
require-from-string "^2.0.2"
uri-js "^4.2.2"
+ajv@^8.17.1:
+ version "8.17.1"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
+ integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ fast-uri "^3.0.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+
amdefine@>=0.0.4:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
@@ -8690,11 +8695,6 @@ combos@^0.2.0:
resolved "https://registry.yarnpkg.com/combos/-/combos-0.2.0.tgz#dc31c5a899b42293d55fe19c064d3e6e207ba4f7"
integrity sha512-Z6YfvgiTCERWJTj3wQiXamFhssdvz1n4ok447rS330lw3uL72WAx8IvrLU7xiE71uyb5WF8JEP+BWB5KhOoGeg==
-commander@12.1.0:
- version "12.1.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
- integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
-
commander@6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
@@ -8710,6 +8710,11 @@ commander@^11.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906"
integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==
+commander@^12.1.0:
+ version "12.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
+ integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
+
commander@^2.16.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.5.0, commander@^2.7.1, commander@^2.8.1:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -9440,9 +9445,9 @@ dayjs@^1.10.8:
integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==
dayjs@^1.8.15:
- version "1.11.12"
- resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.12.tgz#5245226cc7f40a15bf52e0b99fd2a04669ccac1d"
- integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==
+ version "1.11.13"
+ resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
+ integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
dc-polyfill@^0.1.2:
version "0.1.3"
@@ -9780,32 +9785,32 @@ depd@^1.1.0, depd@~1.1.2:
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
dependency-cruiser@^16.3.7:
- version "16.3.10"
- resolved "https://registry.yarnpkg.com/dependency-cruiser/-/dependency-cruiser-16.3.10.tgz#fe26a50d5e10a4496bc2b70d027fca6ded48814f"
- integrity sha512-WkCnibHBfvaiaQ+S46LZ6h4AR6oj42Vsf5/0Vgtrwdwn7ZekMJdZ/ALoTwNp/RaGlKW+MbV/fhSZOvmhAWVWzQ==
+ version "16.4.0"
+ resolved "https://registry.yarnpkg.com/dependency-cruiser/-/dependency-cruiser-16.4.0.tgz#a1b7d452acddf05045ae4f7942a2e9337aedad35"
+ integrity sha512-la/NnD23m6esCox8KMiZ/pcmtec6G/r7LgnJvkBepcErdzlGaxWnyaxtpoYB3fgODrU/7E2u81/nX5FNu5zfyw==
dependencies:
- acorn "8.12.1"
- acorn-jsx "5.3.2"
- acorn-jsx-walk "2.0.0"
- acorn-loose "8.4.0"
- acorn-walk "8.3.3"
- ajv "8.17.1"
- commander "12.1.0"
- enhanced-resolve "5.17.1"
- ignore "5.3.1"
+ acorn "^8.12.1"
+ acorn-jsx "^5.3.2"
+ acorn-jsx-walk "^2.0.0"
+ acorn-loose "^8.4.0"
+ acorn-walk "^8.3.3"
+ ajv "^8.17.1"
+ commander "^12.1.0"
+ enhanced-resolve "^5.17.1"
+ ignore "^5.3.2"
interpret "^3.1.1"
- is-installed-globally "1.0.0"
- json5 "2.2.3"
- memoize "10.0.0"
- picocolors "1.0.1"
- picomatch "4.0.2"
- prompts "2.4.2"
+ is-installed-globally "^1.0.0"
+ json5 "^2.2.3"
+ memoize "^10.0.0"
+ picocolors "^1.0.1"
+ picomatch "^4.0.2"
+ prompts "^2.4.2"
rechoir "^0.8.0"
- safe-regex "2.1.1"
+ safe-regex "^2.1.1"
semver "^7.6.3"
- teamcity-service-messages "0.1.14"
- tsconfig-paths-webpack-plugin "4.1.0"
- watskeburt "4.1.0"
+ teamcity-service-messages "^0.1.14"
+ tsconfig-paths-webpack-plugin "^4.1.0"
+ watskeburt "^4.1.0"
dependency-tree@^9.0.0:
version "9.0.0"
@@ -10358,23 +10363,10 @@ electron-to-chromium@^1.4.284:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.317.tgz#9a3d38a1a37f26a417d3d95dafe198ff11ed072b"
integrity sha512-JhCRm9v30FMNzQSsjl4kXaygU+qHBD0Yh7mKxyjmF0V8VwYVB6qpBRX28GyAucrM9wDCpSUctT6FpMUQxbyKuA==
-elliptic@^6.5.3:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
- integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
- dependencies:
- bn.js "^4.11.9"
- brorand "^1.1.0"
- hash.js "^1.0.0"
- hmac-drbg "^1.0.1"
- inherits "^2.0.4"
- minimalistic-assert "^1.0.1"
- minimalistic-crypto-utils "^1.0.1"
-
-elliptic@^6.5.5:
- version "6.5.5"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded"
- integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==
+elliptic@^6.5.3, elliptic@^6.5.5:
+ version "6.5.7"
+ resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
+ integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==
dependencies:
bn.js "^4.11.9"
brorand "^1.1.0"
@@ -10477,7 +10469,7 @@ engine.io@~6.5.2:
engine.io-parser "~5.2.1"
ws "~8.17.1"
-enhanced-resolve@5.17.1, enhanced-resolve@^5.7.0:
+enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0:
version "5.17.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
@@ -12852,9 +12844,9 @@ husky@^8.0.3:
integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==
husky@^9.1.4:
- version "9.1.4"
- resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.4.tgz#926fd19c18d345add5eab0a42b2b6d9a80259b34"
- integrity sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==
+ version "9.1.5"
+ resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.5.tgz#2b6edede53ee1adbbd3a3da490628a23f5243b83"
+ integrity sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==
ical-generator@4.1.0:
version "4.1.0"
@@ -12928,17 +12920,12 @@ ignore-walk@^6.0.0:
dependencies:
minimatch "^7.4.2"
-ignore@5.3.1:
- version "5.3.1"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
- integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
-
ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4:
version "5.3.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
-ignore@^5.3.1:
+ignore@^5.3.1, ignore@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
@@ -13389,14 +13376,6 @@ is-immutable-type@^4.0.0:
ts-api-utils "^1.3.0"
ts-declaration-location "^1.0.0"
-is-installed-globally@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-1.0.0.tgz#08952c43758c33d815692392f7f8437b9e436d5a"
- integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==
- dependencies:
- global-directory "^4.0.1"
- is-path-inside "^4.0.0"
-
is-installed-globally@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
@@ -13405,6 +13384,14 @@ is-installed-globally@^0.4.0:
global-dirs "^3.0.0"
is-path-inside "^3.0.2"
+is-installed-globally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-1.0.0.tgz#08952c43758c33d815692392f7f8437b9e436d5a"
+ integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==
+ dependencies:
+ global-directory "^4.0.1"
+ is-path-inside "^4.0.0"
+
is-interactive@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
@@ -14516,11 +14503,6 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
-json5@2.2.3, json5@^2.2.1, json5@^2.2.2, json5@^2.2.3:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
- integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
-
json5@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
@@ -14528,6 +14510,11 @@ json5@^1.0.2:
dependencies:
minimist "^1.2.0"
+json5@^2.2.1, json5@^2.2.2, json5@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
jsonc-parser@3.2.0, jsonc-parser@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
@@ -15872,7 +15859,7 @@ memdown@^5.1.0:
ltgt "~2.2.0"
safe-buffer "~5.2.0"
-memoize@10.0.0:
+memoize@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/memoize/-/memoize-10.0.0.tgz#43fa66b2022363c7c50cf5dfab732a808a3d7147"
integrity sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==
@@ -17860,26 +17847,26 @@ phin@^2.9.1:
resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c"
integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==
-picocolors@1.0.1, picocolors@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
- integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
-
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
-picomatch@4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
- integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
+picocolors@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
+ integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+picomatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
+ integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
+
pify@5.0.0, pify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f"
@@ -18367,10 +18354,10 @@ postcss@^8.1.7, postcss@^8.2.9, postcss@^8.3.11, postcss@^8.4.12, postcss@^8.4.2
picocolors "^1.0.0"
source-map-js "^1.2.0"
-postcss@^8.4.39:
- version "8.4.40"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8"
- integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==
+postcss@^8.4.41:
+ version "8.4.41"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681"
+ integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==
dependencies:
nanoid "^3.3.7"
picocolors "^1.0.1"
@@ -18407,9 +18394,9 @@ posthog-js@^1.118.0:
preact "^10.19.3"
posthog-js@^1.13.4:
- version "1.150.1"
- resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.150.1.tgz#ce2e0aa0dc30369bf1b1b9a38b9fbf25e5c01ba0"
- integrity sha512-jHSnqtAWkUQkiedQgHpD00+z8RUF0loDq7ORakBKfQjdntTJIEk16ewqTNRxnpE86guWDoy2J3iAqLgAYfFaLA==
+ version "1.157.2"
+ resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.157.2.tgz#dc2515818ead408aefb900e90c535fb57beb1f59"
+ integrity sha512-ATYKGs+Q51u26nHHhrhWNh1whqFm7j/rwQQYw+y6/YzNmRlo+YsqrGZji9nqXb9/4fo0ModDr+ZmuOI3hKkUXA==
dependencies:
fflate "^0.4.8"
preact "^10.19.3"
@@ -18837,7 +18824,7 @@ promise.series@^0.2.0:
resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd"
integrity sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==
-prompts@2.4.2, prompts@^2.0.1:
+prompts@^2.0.1, prompts@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
@@ -19003,9 +18990,9 @@ q@^1.1.2:
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
qs@^6.10.3:
- version "6.12.3"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754"
- integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
+ integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
side-channel "^1.0.6"
@@ -19661,9 +19648,9 @@ rimraf@^4.4.1:
glob "^9.2.0"
rimraf@^5.0.7:
- version "5.0.9"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.9.tgz#c3baa1b886eadc2ec7981a06a593c3d01134ffe9"
- integrity sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==
+ version "5.0.10"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c"
+ integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==
dependencies:
glob "^10.3.7"
@@ -19829,29 +19816,29 @@ rollup@^3.27.1:
optionalDependencies:
fsevents "~2.3.2"
-rollup@^4.13.0:
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.19.1.tgz#21d865cd60d4a325172ce8b082e60caccd97b309"
- integrity sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==
+rollup@^4.20.0:
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.0.tgz#28db5f5c556a5180361d35009979ccc749560b9d"
+ integrity sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==
dependencies:
"@types/estree" "1.0.5"
optionalDependencies:
- "@rollup/rollup-android-arm-eabi" "4.19.1"
- "@rollup/rollup-android-arm64" "4.19.1"
- "@rollup/rollup-darwin-arm64" "4.19.1"
- "@rollup/rollup-darwin-x64" "4.19.1"
- "@rollup/rollup-linux-arm-gnueabihf" "4.19.1"
- "@rollup/rollup-linux-arm-musleabihf" "4.19.1"
- "@rollup/rollup-linux-arm64-gnu" "4.19.1"
- "@rollup/rollup-linux-arm64-musl" "4.19.1"
- "@rollup/rollup-linux-powerpc64le-gnu" "4.19.1"
- "@rollup/rollup-linux-riscv64-gnu" "4.19.1"
- "@rollup/rollup-linux-s390x-gnu" "4.19.1"
- "@rollup/rollup-linux-x64-gnu" "4.19.1"
- "@rollup/rollup-linux-x64-musl" "4.19.1"
- "@rollup/rollup-win32-arm64-msvc" "4.19.1"
- "@rollup/rollup-win32-ia32-msvc" "4.19.1"
- "@rollup/rollup-win32-x64-msvc" "4.19.1"
+ "@rollup/rollup-android-arm-eabi" "4.21.0"
+ "@rollup/rollup-android-arm64" "4.21.0"
+ "@rollup/rollup-darwin-arm64" "4.21.0"
+ "@rollup/rollup-darwin-x64" "4.21.0"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.21.0"
+ "@rollup/rollup-linux-arm-musleabihf" "4.21.0"
+ "@rollup/rollup-linux-arm64-gnu" "4.21.0"
+ "@rollup/rollup-linux-arm64-musl" "4.21.0"
+ "@rollup/rollup-linux-powerpc64le-gnu" "4.21.0"
+ "@rollup/rollup-linux-riscv64-gnu" "4.21.0"
+ "@rollup/rollup-linux-s390x-gnu" "4.21.0"
+ "@rollup/rollup-linux-x64-gnu" "4.21.0"
+ "@rollup/rollup-linux-x64-musl" "4.21.0"
+ "@rollup/rollup-win32-arm64-msvc" "4.21.0"
+ "@rollup/rollup-win32-ia32-msvc" "4.21.0"
+ "@rollup/rollup-win32-x64-msvc" "4.21.0"
fsevents "~2.3.2"
rollup@^4.9.4, rollup@^4.9.6:
@@ -19954,7 +19941,7 @@ safe-regex-test@^1.0.3:
es-errors "^1.3.0"
is-regex "^1.1.4"
-safe-regex@2.1.1:
+safe-regex@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2"
integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==
@@ -21345,7 +21332,7 @@ tarn@^3.0.1, tarn@^3.0.2:
resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693"
integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==
-teamcity-service-messages@0.1.14:
+teamcity-service-messages@^0.1.14:
version "0.1.14"
resolved "https://registry.yarnpkg.com/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz#193d420a5e4aef8e5e50b8c39e7865e08fbb5d8a"
integrity sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w==
@@ -21557,9 +21544,9 @@ tinypool@^0.4.0:
integrity sha512-2ksntHOKf893wSAH4z/+JbPpi92esw8Gn9N2deXX+B0EO92hexAVI9GIZZPx7P5aYo5KULfeOSt3kMOmSOy6uA==
tinypool@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.0.tgz#a68965218e04f4ad9de037d2a1cd63cda9afb238"
- integrity sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe"
+ integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==
tinyrainbow@^1.2.0:
version "1.2.0"
@@ -21787,7 +21774,7 @@ tsconfck@^3.0.3:
resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.1.tgz#c7284913262c293b43b905b8b034f524de4a3162"
integrity sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==
-tsconfig-paths-webpack-plugin@4.1.0:
+tsconfig-paths-webpack-plugin@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz#3c6892c5e7319c146eee1e7302ed9e6f2be4f763"
integrity sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==
@@ -22102,10 +22089,10 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
-undici-types@~6.11.1:
- version "6.11.1"
- resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.11.1.tgz#432ea6e8efd54a48569705a699e62d8f4981b197"
- integrity sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==
+undici-types@~6.19.2:
+ version "6.19.8"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
+ integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
undici@^4.14.1:
version "4.16.0"
@@ -22469,13 +22456,13 @@ vite-tsconfig-paths@^4.3.2:
fsevents "~2.3.2"
vite@^5.0.0:
- version "5.3.5"
- resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8"
- integrity sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.2.tgz#8acb6ec4bfab823cdfc1cb2d6c53ed311bc4e47e"
+ integrity sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==
dependencies:
esbuild "^0.21.3"
- postcss "^8.4.39"
- rollup "^4.13.0"
+ postcss "^8.4.41"
+ rollup "^4.20.0"
optionalDependencies:
fsevents "~2.3.3"
@@ -22587,7 +22574,7 @@ walker@^1.0.8:
dependencies:
makeerror "1.0.12"
-watskeburt@4.1.0:
+watskeburt@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/watskeburt/-/watskeburt-4.1.0.tgz#3c0227669be646a97424b631164b1afe3d4d5344"
integrity sha512-KkY5H51ajqy9HYYI+u9SIURcWnqeVVhdH0I+ab6aXPGHfZYxgRCwnR6Lm3+TYB6jJVt5jFqw4GAKmwf1zHmGQw==
@@ -22610,9 +22597,9 @@ web-streams-polyfill@^3.2.1:
integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
web-vitals@^4.0.1:
- version "4.2.2"
- resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.2.tgz#e883245180b95e175eb75a5ca8903b1a11597d7a"
- integrity sha512-nYfoOqb4EmElljyXU2qdeE76KsvoHdftQKY4DzA9Aw8DervCg2bG634pHLrJ/d6+B4mE3nWTSJv8Mo7B2mbZkw==
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.3.tgz#270c4baecfbc6ec6fc15da1989e465e5f9b94fb7"
+ integrity sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q==
webfinger@^0.4.2:
version "0.4.2"