diff --git a/.eslintrc.json b/.eslintrc.json index 4b2b523137..2a40c6cc29 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -36,13 +36,14 @@ "files": ["**/*.ts"], "excludedFiles": ["qa-core/**"], "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], "extends": ["eslint:recommended"], + "globals": { + "NodeJS": true + }, "rules": { "no-unused-vars": "off", - "no-inner-declarations": "off", - "no-case-declarations": "off", - "no-undef": "off", - "no-prototype-builtins": "off", + "@typescript-eslint/no-unused-vars": "error", "local-rules/no-budibase-imports": "error" } }, @@ -50,17 +51,17 @@ "files": ["**/*.spec.ts"], "excludedFiles": ["qa-core/**"], "parser": "@typescript-eslint/parser", - "plugins": ["jest"], + "plugins": ["jest", "@typescript-eslint"], "extends": ["eslint:recommended", "plugin:jest/recommended"], "env": { "jest/globals": true }, + "globals": { + "NodeJS": true + }, "rules": { "no-unused-vars": "off", - "no-inner-declarations": "off", - "no-case-declarations": "off", - "no-undef": "off", - "no-prototype-builtins": "off", + "@typescript-eslint/no-unused-vars": "error", "local-rules/no-test-com": "error", "local-rules/email-domain-example-com": "error", "no-console": "warn", diff --git a/lerna.json b/lerna.json index 01e56982d5..a5b45c51f2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.22.3", + "version": "2.22.11", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/package.json b/package.json index af7aac0025..7de22ab456 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "svelte": "^4.2.10", "svelte-eslint-parser": "^0.33.1", "typescript": "5.2.2", + "typescript-eslint": "^7.3.1", "yargs": "^17.7.2" }, "scripts": { diff --git a/packages/account-portal b/packages/account-portal index 6465dc9c2a..f5b467b6b1 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit 6465dc9c2a38e1380b32204cad4ae0c1f33e065a +Subproject commit f5b467b6b1c55c48847545db41be7b1c035e167a diff --git a/packages/backend-core/src/auth/auth.ts b/packages/backend-core/src/auth/auth.ts index 1951c7986c..87ac46cf1c 100644 --- a/packages/backend-core/src/auth/auth.ts +++ b/packages/backend-core/src/auth/auth.ts @@ -133,7 +133,7 @@ export async function refreshOAuthToken( configId?: string ): Promise { switch (providerType) { - case SSOProviderType.OIDC: + case SSOProviderType.OIDC: { if (!configId) { return { err: { data: "OIDC config id not provided" } } } @@ -142,12 +142,14 @@ export async function refreshOAuthToken( return { err: { data: "OIDC configuration not found" } } } return refreshOIDCAccessToken(oidcConfig, refreshToken) - case SSOProviderType.GOOGLE: + } + case SSOProviderType.GOOGLE: { let googleConfig = await configs.getGoogleConfig() if (!googleConfig) { return { err: { data: "Google configuration not found" } } } return refreshGoogleAccessToken(googleConfig, refreshToken) + } } } diff --git a/packages/backend-core/src/cache/base/index.ts b/packages/backend-core/src/cache/base/index.ts index 74da4fe0d2..433941b5c7 100644 --- a/packages/backend-core/src/cache/base/index.ts +++ b/packages/backend-core/src/cache/base/index.ts @@ -129,7 +129,7 @@ export default class BaseCache { } } - async bustCache(key: string, opts = { client: null }) { + async bustCache(key: string) { const client = await this.getClient() try { await client.delete(generateTenantKey(key)) diff --git a/packages/backend-core/src/cache/invite.ts b/packages/backend-core/src/cache/invite.ts index e43ebc4aa8..e3d698bcc6 100644 --- a/packages/backend-core/src/cache/invite.ts +++ b/packages/backend-core/src/cache/invite.ts @@ -1,5 +1,5 @@ import * as utils from "../utils" -import { Duration, DurationType } from "../utils" +import { Duration } from "../utils" import env from "../environment" import { getTenantId } from "../context" import * as redis from "../redis/init" diff --git a/packages/backend-core/src/cache/writethrough.ts b/packages/backend-core/src/cache/writethrough.ts index 5cafe418d7..cd7409ca15 100644 --- a/packages/backend-core/src/cache/writethrough.ts +++ b/packages/backend-core/src/cache/writethrough.ts @@ -8,7 +8,7 @@ const DEFAULT_WRITE_RATE_MS = 10000 let CACHE: BaseCache | null = null interface CacheItem { - doc: any + doc: T lastWrite: number } diff --git a/packages/backend-core/src/db/lucene.ts b/packages/backend-core/src/db/lucene.ts index 7055ec8031..987d750d45 100644 --- a/packages/backend-core/src/db/lucene.ts +++ b/packages/backend-core/src/db/lucene.ts @@ -10,10 +10,6 @@ interface SearchResponse { totalRows: number } -interface PaginatedSearchResponse extends SearchResponse { - hasNextPage: boolean -} - export type SearchParams = { tableId?: string sort?: string diff --git a/packages/backend-core/src/db/searchIndexes/searchIndexes.ts b/packages/backend-core/src/db/searchIndexes/searchIndexes.ts index b953e3516e..8742d405f2 100644 --- a/packages/backend-core/src/db/searchIndexes/searchIndexes.ts +++ b/packages/backend-core/src/db/searchIndexes/searchIndexes.ts @@ -34,12 +34,12 @@ export async function createUserIndex() { } let idxKey = prev != null ? `${prev}.${key}` : key if (typeof input[key] === "string") { + // @ts-expect-error index is available in a CouchDB map function // eslint-disable-next-line no-undef - // @ts-ignore index(idxKey, input[key].toLowerCase(), { facet: true }) } else if (typeof input[key] !== "object") { + // @ts-expect-error index is available in a CouchDB map function // eslint-disable-next-line no-undef - // @ts-ignore index(idxKey, input[key], { facet: true }) } else { idx(input[key], idxKey) diff --git a/packages/backend-core/src/docUpdates/index.ts b/packages/backend-core/src/docUpdates/index.ts index 3971f8de12..bd34f4f0cd 100644 --- a/packages/backend-core/src/docUpdates/index.ts +++ b/packages/backend-core/src/docUpdates/index.ts @@ -17,13 +17,8 @@ export function init(processors: ProcessorMap) { // if not processing in this instance, kick it off if (!processingPromise) { processingPromise = asyncEventQueue.process(async job => { - const { event, identity, properties, timestamp } = job.data - await documentProcessor.processEvent( - event, - identity, - properties, - timestamp - ) + const { event, identity, properties } = job.data + await documentProcessor.processEvent(event, identity, properties) }) } } diff --git a/packages/backend-core/src/events/processors/AuditLogsProcessor.ts b/packages/backend-core/src/events/processors/AuditLogsProcessor.ts index 94b4e1b09f..3dd2ab9d10 100644 --- a/packages/backend-core/src/events/processors/AuditLogsProcessor.ts +++ b/packages/backend-core/src/events/processors/AuditLogsProcessor.ts @@ -1,7 +1,6 @@ import { Event, Identity, - Group, IdentityType, AuditLogQueueEvent, AuditLogFn, @@ -79,11 +78,11 @@ export default class AuditLogsProcessor implements EventProcessor { } } - async identify(identity: Identity, timestamp?: string | number) { + async identify() { // no-op } - async identifyGroup(group: Group, timestamp?: string | number) { + async identifyGroup() { // no-op } diff --git a/packages/backend-core/src/events/processors/LoggingProcessor.ts b/packages/backend-core/src/events/processors/LoggingProcessor.ts index 0f4d02b99c..9f2dc5a473 100644 --- a/packages/backend-core/src/events/processors/LoggingProcessor.ts +++ b/packages/backend-core/src/events/processors/LoggingProcessor.ts @@ -8,8 +8,7 @@ export default class LoggingProcessor implements EventProcessor { async processEvent( event: Event, identity: Identity, - properties: any, - timestamp?: string + properties: any ): Promise { if (skipLogging) { return @@ -17,14 +16,14 @@ export default class LoggingProcessor implements EventProcessor { console.log(`[audit] [identityType=${identity.type}] ${event}`, properties) } - async identify(identity: Identity, timestamp?: string | number) { + async identify(identity: Identity) { if (skipLogging) { return } console.log(`[audit] identified`, identity) } - async identifyGroup(group: Group, timestamp?: string | number) { + async identifyGroup(group: Group) { if (skipLogging) { return } diff --git a/packages/backend-core/src/events/processors/async/DocumentUpdateProcessor.ts b/packages/backend-core/src/events/processors/async/DocumentUpdateProcessor.ts index 54304ee21b..92afcdc637 100644 --- a/packages/backend-core/src/events/processors/async/DocumentUpdateProcessor.ts +++ b/packages/backend-core/src/events/processors/async/DocumentUpdateProcessor.ts @@ -14,12 +14,7 @@ export default class DocumentUpdateProcessor implements EventProcessor { this.processors = processors } - async processEvent( - event: Event, - identity: Identity, - properties: any, - timestamp?: string | number - ) { + async processEvent(event: Event, identity: Identity, properties: any) { const tenantId = identity.realTenantId const docId = getDocumentId(event, properties) if (!tenantId || !docId) { diff --git a/packages/backend-core/src/logging/pino/logger.ts b/packages/backend-core/src/logging/pino/logger.ts index 7a051e7f12..0a8470a453 100644 --- a/packages/backend-core/src/logging/pino/logger.ts +++ b/packages/backend-core/src/logging/pino/logger.ts @@ -10,6 +10,18 @@ import { formats } from "dd-trace/ext" import { localFileDestination } from "../system" +function isPlainObject(obj: any) { + return typeof obj === "object" && obj !== null && !(obj instanceof Error) +} + +function isError(obj: any) { + return obj instanceof Error +} + +function isMessage(obj: any) { + return typeof obj === "string" +} + // LOGGER let pinoInstance: pino.Logger | undefined @@ -71,23 +83,11 @@ if (!env.DISABLE_PINO_LOGGER) { err?: Error } - function isPlainObject(obj: any) { - return typeof obj === "object" && obj !== null && !(obj instanceof Error) - } - - function isError(obj: any) { - return obj instanceof Error - } - - function isMessage(obj: any) { - return typeof obj === "string" - } - /** * Backwards compatibility between console logging statements * and pino logging requirements. */ - function getLogParams(args: any[]): [MergingObject, string] { + const getLogParams = (args: any[]): [MergingObject, string] => { let error = undefined let objects: any[] = [] let message = "" diff --git a/packages/backend-core/src/middleware/matchers.ts b/packages/backend-core/src/middleware/matchers.ts index 8bede1cc6a..757d93a60d 100644 --- a/packages/backend-core/src/middleware/matchers.ts +++ b/packages/backend-core/src/middleware/matchers.ts @@ -28,7 +28,7 @@ export const buildMatcherRegex = ( } export const matches = (ctx: BBContext, options: RegexMatcher[]) => { - return options.find(({ regex, method, route }) => { + return options.find(({ regex, method }) => { const urlMatch = regex.test(ctx.request.url) const methodMatch = method === "ALL" diff --git a/packages/backend-core/src/middleware/passport/datasource/google.ts b/packages/backend-core/src/middleware/passport/datasource/google.ts index ab4ffee9d2..7f768f1623 100644 --- a/packages/backend-core/src/middleware/passport/datasource/google.ts +++ b/packages/backend-core/src/middleware/passport/datasource/google.ts @@ -3,7 +3,7 @@ import { Cookie } from "../../../constants" import * as configs from "../../../configs" import * as cache from "../../../cache" import * as utils from "../../../utils" -import { UserCtx, SSOProfile, DatasourceAuthCookie } from "@budibase/types" +import { UserCtx, SSOProfile } from "@budibase/types" import { ssoSaveUserNoOp } from "../sso/sso" const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy diff --git a/packages/backend-core/src/middleware/passport/sso/sso.ts b/packages/backend-core/src/middleware/passport/sso/sso.ts index 2fc1184722..ee84f03dae 100644 --- a/packages/backend-core/src/middleware/passport/sso/sso.ts +++ b/packages/backend-core/src/middleware/passport/sso/sso.ts @@ -5,7 +5,6 @@ import * as context from "../../../context" import fetch from "node-fetch" import { SaveSSOUserFunction, - SaveUserOpts, SSOAuthDetails, SSOUser, User, @@ -14,10 +13,8 @@ import { // no-op function for user save // - this allows datasource auth and access token refresh to work correctly // - prefer no-op over an optional argument to ensure function is provided to login flows -export const ssoSaveUserNoOp: SaveSSOUserFunction = ( - user: SSOUser, - opts: SaveUserOpts -) => Promise.resolve(user) +export const ssoSaveUserNoOp: SaveSSOUserFunction = (user: SSOUser) => + Promise.resolve(user) /** * Common authentication logic for third parties. e.g. OAuth, OIDC. diff --git a/packages/backend-core/src/migrations/migrations.ts b/packages/backend-core/src/migrations/migrations.ts index c750bc4882..3f033b8cdb 100644 --- a/packages/backend-core/src/migrations/migrations.ts +++ b/packages/backend-core/src/migrations/migrations.ts @@ -45,10 +45,6 @@ export const runMigration = async ( options: MigrationOptions = {} ) => { const migrationType = migration.type - let tenantId: string | undefined - if (migrationType !== MigrationType.INSTALLATION) { - tenantId = context.getTenantId() - } const migrationName = migration.name const silent = migration.silent diff --git a/packages/backend-core/src/objectStore/buckets/tests/app.spec.ts b/packages/backend-core/src/objectStore/buckets/tests/app.spec.ts index cbbbee6255..4a132ce54d 100644 --- a/packages/backend-core/src/objectStore/buckets/tests/app.spec.ts +++ b/packages/backend-core/src/objectStore/buckets/tests/app.spec.ts @@ -126,7 +126,7 @@ describe("app", () => { it("gets url with embedded minio", async () => { testEnv.withMinio() - await testEnv.withTenant(tenantId => { + await testEnv.withTenant(() => { const url = getAppFileUrl() expect(url).toBe( "/files/signed/prod-budi-app-assets/app_123/attachments/image.jpeg" @@ -136,7 +136,7 @@ describe("app", () => { it("gets url with custom S3", async () => { testEnv.withS3() - await testEnv.withTenant(tenantId => { + await testEnv.withTenant(() => { const url = getAppFileUrl() expect(url).toBe( "http://s3.example.com/prod-budi-app-assets/app_123/attachments/image.jpeg" @@ -146,7 +146,7 @@ describe("app", () => { it("gets url with cloudfront + s3", async () => { testEnv.withCloudfront() - await testEnv.withTenant(tenantId => { + await testEnv.withTenant(() => { const url = getAppFileUrl() // omit rest of signed params expect( diff --git a/packages/backend-core/src/platform/tests/tenants.spec.ts b/packages/backend-core/src/platform/tests/tenants.spec.ts index b2ab75c954..e22003fd45 100644 --- a/packages/backend-core/src/platform/tests/tenants.spec.ts +++ b/packages/backend-core/src/platform/tests/tenants.spec.ts @@ -3,7 +3,7 @@ import { DBTestConfiguration } from "../../../tests/extra" import * as tenants from "../tenants" describe("tenants", () => { - const config = new DBTestConfiguration() + new DBTestConfiguration() describe("addTenant", () => { it("concurrently adds multiple tenants safely", async () => { diff --git a/packages/backend-core/src/queue/inMemoryQueue.ts b/packages/backend-core/src/queue/inMemoryQueue.ts index afb5592562..87e43b324d 100644 --- a/packages/backend-core/src/queue/inMemoryQueue.ts +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -39,7 +39,7 @@ class InMemoryQueue implements Partial { _opts?: QueueOptions _messages: JobMessage[] _queuedJobIds: Set - _emitter: EventEmitter + _emitter: NodeJS.EventEmitter _runCount: number _addCount: number @@ -166,7 +166,7 @@ class InMemoryQueue implements Partial { return [] } - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars async removeJobs(pattern: string) { // no-op } diff --git a/packages/backend-core/src/queue/listeners.ts b/packages/backend-core/src/queue/listeners.ts index 14dce5fe8d..cd25ff2254 100644 --- a/packages/backend-core/src/queue/listeners.ts +++ b/packages/backend-core/src/queue/listeners.ts @@ -132,7 +132,7 @@ function logging(queue: Queue, jobQueue: JobQueue) { // A Job is waiting to be processed as soon as a worker is idling. console.info(...getLogParams(eventType, BullEvent.WAITING, { jobId })) }) - .on(BullEvent.ACTIVE, async (job: Job, jobPromise: any) => { + .on(BullEvent.ACTIVE, async (job: Job) => { // A job has started. You can use `jobPromise.cancel()`` to abort it. await doInJobContext(job, () => { console.info(...getLogParams(eventType, BullEvent.ACTIVE, { job })) diff --git a/packages/backend-core/src/redis/init.ts b/packages/backend-core/src/redis/init.ts index 7920dfed2d..44ba28a83c 100644 --- a/packages/backend-core/src/redis/init.ts +++ b/packages/backend-core/src/redis/init.ts @@ -40,6 +40,7 @@ export async function shutdown() { if (inviteClient) await inviteClient.finish() if (passwordResetClient) await passwordResetClient.finish() if (socketClient) await socketClient.finish() + if (docWritethroughClient) await docWritethroughClient.finish() } process.on("exit", async () => { diff --git a/packages/backend-core/src/redis/tests/redis.spec.ts b/packages/backend-core/src/redis/tests/redis.spec.ts index e2076ad698..4d11caf220 100644 --- a/packages/backend-core/src/redis/tests/redis.spec.ts +++ b/packages/backend-core/src/redis/tests/redis.spec.ts @@ -120,7 +120,7 @@ describe("redis", () => { await redis.bulkStore(data, ttl) - for (const [key, value] of Object.entries(data)) { + for (const key of Object.keys(data)) { expect(await redis.get(key)).toBe(null) } diff --git a/packages/backend-core/src/users/test/utils.spec.ts b/packages/backend-core/src/users/test/utils.spec.ts index 0fe27f57a6..cb98b8972b 100644 --- a/packages/backend-core/src/users/test/utils.spec.ts +++ b/packages/backend-core/src/users/test/utils.spec.ts @@ -45,7 +45,7 @@ describe("Users", () => { ...{ _id: groupId, roles: { app1: "ADMIN" } }, } const users: User[] = [] - for (const _ of Array.from({ length: usersInGroup })) { + for (let i = 0; i < usersInGroup; i++) { const userId = `us_${generator.guid()}` const user: User = structures.users.user({ _id: userId, diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js index 12c4c4d002..eafca657f3 100644 --- a/packages/bbui/src/Actions/click_outside.js +++ b/packages/bbui/src/Actions/click_outside.js @@ -39,19 +39,23 @@ const handleClick = event => { return } + if (handler.allowedType && event.type !== handler.allowedType) { + return + } + handler.callback?.(event) }) } document.documentElement.addEventListener("click", handleClick, true) -document.documentElement.addEventListener("contextmenu", handleClick, true) +document.documentElement.addEventListener("mousedown", handleClick, true) /** * Adds or updates a click handler */ -const updateHandler = (id, element, anchor, callback) => { +const updateHandler = (id, element, anchor, callback, allowedType) => { let existingHandler = clickHandlers.find(x => x.id === id) if (!existingHandler) { - clickHandlers.push({ id, element, anchor, callback }) + clickHandlers.push({ id, element, anchor, callback, allowedType }) } else { existingHandler.callback = callback } @@ -75,9 +79,11 @@ const removeHandler = id => { export default (element, opts) => { const id = Math.random() const update = newOpts => { - const callback = newOpts?.callback || newOpts + const callback = + newOpts?.callback || (typeof newOpts === "function" ? newOpts : null) const anchor = newOpts?.anchor || element - updateHandler(id, element, anchor, callback) + const allowedType = newOpts?.allowedType || "click" + updateHandler(id, element, anchor, callback, allowedType) } update(opts) return { diff --git a/packages/bbui/src/Drawer/DrawerContent.svelte b/packages/bbui/src/Drawer/DrawerContent.svelte index 490dfecc31..f7345afb11 100644 --- a/packages/bbui/src/Drawer/DrawerContent.svelte +++ b/packages/bbui/src/Drawer/DrawerContent.svelte @@ -42,7 +42,6 @@ .main { height: 100%; overflow: auto; - overflow-x: hidden; } .padding .main { padding: var(--spacing-xl); diff --git a/packages/bbui/src/Table/CellRenderer.svelte b/packages/bbui/src/Table/CellRenderer.svelte index 4ad6e22d7e..eff1178f6d 100644 --- a/packages/bbui/src/Table/CellRenderer.svelte +++ b/packages/bbui/src/Table/CellRenderer.svelte @@ -12,6 +12,7 @@ export let schema export let value export let customRenderers = [] + export let snippets let renderer const typeMap = { @@ -44,7 +45,7 @@ if (!template) { return value } - return processStringSync(template, { value }) + return processStringSync(template, { value, snippets }) } diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte index 33b9bd9a7e..868f7b3a0b 100644 --- a/packages/bbui/src/Table/Table.svelte +++ b/packages/bbui/src/Table/Table.svelte @@ -42,6 +42,7 @@ export let customPlaceholder = false export let showHeaderBorder = true export let placeholderText = "No rows found" + export let snippets = [] const dispatch = createEventDispatcher() @@ -425,6 +426,7 @@ {:else if editableColumn.type === FieldType.ATTACHMENT} { if (!e.detail) { - editableColumn.constraints ??= { length: {} } - editableColumn.constraints.length ??= {} - editableColumn.constraints.length.maximum = 1 - editableColumn.constraints.length.message = - "cannot contain multiple files" + editableColumn.subtype = FieldTypeSubtypes.ATTACHMENT.SINGLE } else { - delete editableColumn.constraints?.length?.maximum - delete editableColumn.constraints?.length?.message + delete editableColumn.subtype } }} thin diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte index c2cda1f2d8..f2c726c8bf 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte @@ -28,7 +28,6 @@ let deleteTableName $: externalTable = table?.sourceType === DB_TYPE_EXTERNAL - $: allowDeletion = !externalTable || table?.created function showDeleteModal() { templateScreens = $screenStore.screens.filter( @@ -56,7 +55,7 @@ $goto(`./datasource/${table.datasourceId}`) } } catch (error) { - notifications.error("Error deleting table") + notifications.error(`Error deleting table - ${error.message}`) } } @@ -86,17 +85,15 @@ } -{#if allowDeletion} - -
- -
- {#if !externalTable} - Edit - {/if} - Delete -
-{/if} + +
+ +
+ {#if !externalTable} + Edit + {/if} + Delete +
{ ...bindingByCategory[catKey].reduce((acc, binding) => { let displayType = binding.fieldSchema?.type || binding.display?.type acc.push({ - label: binding.display?.name || "NO NAME", + label: binding.display?.name || binding.readableBinding || "NO NAME", info: completion => { return buildBindingInfoNode(completion, binding) }, diff --git a/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte b/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte index cb65d2bbe4..4e5789b563 100644 --- a/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte @@ -8,6 +8,7 @@ export let allowJS = false export let allowHelpers = true export let autofocusEditor = false + export let context = null $: enrichedBindings = enrichBindings(bindings) @@ -27,7 +28,7 @@ app.devId === application) $: selectedApp = filteredApps?.length ? filteredApps[0] : null @@ -57,7 +58,7 @@ $appStore.version && $appStore.upgradableVersion !== $appStore.version $: canPublish = !publishing && loaded && $sortedScreens.length > 0 - $: lastDeployed = getLastDeployedString($deploymentStore) + $: lastDeployed = getLastDeployedString($deploymentStore, lastOpened) const initialiseApp = async () => { const applicationPkg = await API.fetchAppPackage($appStore.devId) @@ -201,6 +202,7 @@ class="app-action-button publish app-action-popover" on:click={() => { if (!appActionPopoverOpen) { + lastOpened = new Date() appActionPopover.show() } else { appActionPopover.hide() diff --git a/packages/builder/src/components/design/settings/controls/ColumnEditor/CellDrawer.svelte b/packages/builder/src/components/design/settings/controls/ColumnEditor/CellDrawer.svelte index f6f25b2512..d2e0ddc1b0 100644 --- a/packages/builder/src/components/design/settings/controls/ColumnEditor/CellDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/ColumnEditor/CellDrawer.svelte @@ -7,10 +7,13 @@ Layout, Label, } from "@budibase/bbui" - import { themeStore } from "stores/builder" + import { themeStore, previewStore } from "stores/builder" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" export let column + + $: columnValue = + $previewStore.selectedComponentContext?.eventContext?.row?.[column.name] @@ -41,6 +44,9 @@ icon: "TableColumnMerge", }, ]} + context={{ + value: columnValue, + }} /> diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index d714bafc70..035dc5a2ef 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -129,10 +129,7 @@ filteredUsers = $usersFetch.rows .filter(user => user.email !== $auth.user.email) .map(user => { - const isAdminOrGlobalBuilder = sdk.users.isAdminOrGlobalBuilder( - user, - prodAppId - ) + const isAdminOrGlobalBuilder = sdk.users.isAdminOrGlobalBuilder(user) const isAppBuilder = user.builder?.apps?.includes(prodAppId) let role if (isAdminOrGlobalBuilder) { diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json index 7621aa763d..a2ab961c25 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json @@ -3,8 +3,6 @@ "name": "Blocks", "icon": "Article", "children": [ - "gridblock", - "tableblock", "cardsblock", "repeaterblock", "formblock", @@ -16,7 +14,7 @@ { "name": "Layout", "icon": "ClassicGridView", - "children": ["container", "section", "grid", "sidepanel"] + "children": ["container", "section", "sidepanel"] }, { "name": "Data", @@ -24,7 +22,7 @@ "children": [ "dataprovider", "repeater", - "table", + "gridblock", "spreadsheet", "dynamicfilter", "daterangepicker" diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index 8c1a11289d..b49e38d9cd 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -19,7 +19,8 @@ import { goto } from "@roxi/routify" import { TOUR_KEYS } from "components/portal/onboarding/tours.js" import formScreen from "templates/formScreen" - import rowListScreen from "templates/rowListScreen" + import gridListScreen from "templates/gridListScreen" + import gridDetailsScreen from "templates/gridDetailsScreen" let mode let pendingScreen @@ -127,7 +128,7 @@ screenAccessRole = Roles.BASIC formType = null - if (mode === "table" || mode === "grid" || mode === "form") { + if (mode === "grid" || mode === "gridDetails" || mode === "form") { datasourceModal.show() } else if (mode === "blank") { let templates = getTemplates($tables.list) @@ -153,7 +154,10 @@ // Handler for Datasource Screen Creation const completeDatasourceScreenCreation = async () => { - templates = rowListScreen(selectedDatasources, mode) + templates = + mode === "grid" + ? gridListScreen(selectedDatasources) + : gridDetailsScreen(selectedDatasources) const screens = templates.map(template => { let screenTemplate = template.create() diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableDetails.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableDetails.png new file mode 100644 index 0000000000..f67495f3aa Binary files /dev/null and b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableDetails.png differ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.png new file mode 100644 index 0000000000..905294a9ae Binary files /dev/null and b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.png differ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte index b1ff66cb8d..ef07b277ef 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte @@ -2,8 +2,8 @@ import { Body } from "@budibase/bbui" import CreationPage from "components/common/CreationPage.svelte" import blankImage from "./images/blank.png" - import tableImage from "./images/table.png" - import gridImage from "./images/grid.png" + import tableInline from "./images/tableInline.png" + import tableDetails from "./images/tableDetails.png" import formImage from "./images/form.png" import CreateScreenModal from "./CreateScreenModal.svelte" import { screenStore } from "stores/builder" @@ -38,23 +38,23 @@ -
createScreenModal.show("table")}> +
createScreenModal.show("grid")}>
- +
- Table - View, edit and delete rows on a table + Table with inline editing + View, edit and delete rows inline
-
createScreenModal.show("grid")}> +
createScreenModal.show("gridDetails")}>
- +
- Grid - View and manipulate rows on a grid + Table with details panel + Manage your row details in a side panel
@@ -113,6 +113,11 @@ width: 100%; } + .card .image { + min-height: 130px; + min-width: 235px; + } + .text { border: 1px solid var(--grey-4); border-radius: 0 0 4px 4px; diff --git a/packages/builder/src/stores/builder/components.js b/packages/builder/src/stores/builder/components.js index 0d7da1ba58..fe5f4e8a05 100644 --- a/packages/builder/src/stores/builder/components.js +++ b/packages/builder/src/stores/builder/components.js @@ -279,12 +279,10 @@ export class ComponentStore extends BudiStore { else { if (setting.type === "dataProvider") { // Validate data provider exists, or else clear it - const treeId = parent?._id || component._id - const path = findComponentPath(screen?.props, treeId) - const providers = path.filter(component => - component._component?.endsWith("/dataprovider") + const providers = findAllMatchingComponents( + screen?.props, + x => x._component === "@budibase/standard-components/dataprovider" ) - // Validate non-empty values const valid = providers?.some(dp => value.includes?.(dp._id)) if (!valid) { if (providers.length) { diff --git a/packages/builder/src/stores/builder/hover.js b/packages/builder/src/stores/builder/hover.js index f0444a7da5..98cdc9e416 100644 --- a/packages/builder/src/stores/builder/hover.js +++ b/packages/builder/src/stores/builder/hover.js @@ -7,12 +7,25 @@ export const INITIAL_HOVER_STATE = { } export class HoverStore extends BudiStore { + hoverTimeout + constructor() { super({ ...INITIAL_HOVER_STATE }) this.hover = this.hover.bind(this) } hover(componentId, notifyClient = true) { + clearTimeout(this.hoverTimeout) + if (componentId) { + this.processHover(componentId, notifyClient) + } else { + this.hoverTimeout = setTimeout(() => { + this.processHover(componentId, notifyClient) + }, 10) + } + } + + processHover(componentId, notifyClient) { if (componentId === get(this.store).componentId) { return } diff --git a/packages/builder/src/templates/gridDetailsScreen.js b/packages/builder/src/templates/gridDetailsScreen.js new file mode 100644 index 0000000000..35ab651268 --- /dev/null +++ b/packages/builder/src/templates/gridDetailsScreen.js @@ -0,0 +1,158 @@ +import sanitizeUrl from "helpers/sanitizeUrl" +import { Screen } from "./Screen" +import { Component } from "./Component" +import { generate } from "shortid" +import { makePropSafe as safe } from "@budibase/string-templates" +import { Utils } from "@budibase/frontend-core" + +export default function (datasources) { + if (!Array.isArray(datasources)) { + return [] + } + return datasources.map(datasource => { + return { + name: `${datasource.label} - List with panel`, + create: () => createScreen(datasource), + id: GRID_DETAILS_TEMPLATE, + resourceId: datasource.resourceId, + } + }) +} + +export const GRID_DETAILS_TEMPLATE = "GRID_DETAILS_TEMPLATE" +export const gridDetailsUrl = datasource => sanitizeUrl(`/${datasource.label}`) + +const createScreen = datasource => { + /* + Create Row + */ + const createRowSidePanel = new Component( + "@budibase/standard-components/sidepanel" + ).instanceName("New row side panel") + + const buttonGroup = new Component("@budibase/standard-components/buttongroup") + const createButton = new Component("@budibase/standard-components/button") + + createButton.customProps({ + onClick: [ + { + id: 0, + "##eventHandlerType": "Open Side Panel", + parameters: { + id: createRowSidePanel._json._id, + }, + }, + ], + text: "Create row", + type: "cta", + }) + + buttonGroup.instanceName(`${datasource.label} - Create`).customProps({ + hAlign: "right", + buttons: [createButton.json()], + }) + + const gridHeader = new Component("@budibase/standard-components/container") + .instanceName("Heading container") + .customProps({ + direction: "row", + hAlign: "stretch", + }) + + const heading = new Component("@budibase/standard-components/heading") + .instanceName("Table heading") + .customProps({ + text: datasource?.label, + }) + + gridHeader.addChild(heading) + gridHeader.addChild(buttonGroup) + + const createFormBlock = new Component( + "@budibase/standard-components/formblock" + ) + createFormBlock.instanceName("Create row form block").customProps({ + dataSource: datasource, + labelPosition: "left", + buttonPosition: "top", + actionType: "Create", + title: "Create row", + buttons: Utils.buildFormBlockButtonConfig({ + _id: createFormBlock._json._id, + showDeleteButton: false, + showSaveButton: true, + saveButtonLabel: "Save", + actionType: "Create", + dataSource: datasource, + }), + }) + + createRowSidePanel.addChild(createFormBlock) + + /* + Edit Row + */ + const stateKey = `ID_${generate()}` + const detailsSidePanel = new Component( + "@budibase/standard-components/sidepanel" + ).instanceName("Edit row side panel") + + const editFormBlock = new Component("@budibase/standard-components/formblock") + editFormBlock.instanceName("Edit row form block").customProps({ + dataSource: datasource, + labelPosition: "left", + buttonPosition: "top", + actionType: "Update", + title: "Edit", + rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`, + buttons: Utils.buildFormBlockButtonConfig({ + _id: editFormBlock._json._id, + showDeleteButton: true, + showSaveButton: true, + saveButtonLabel: "Save", + deleteButtonLabel: "Delete", + actionType: "Update", + dataSource: datasource, + }), + }) + + detailsSidePanel.addChild(editFormBlock) + + const gridBlock = new Component("@budibase/standard-components/gridblock") + gridBlock + .customProps({ + table: datasource, + allowAddRows: false, + allowEditRows: false, + allowDeleteRows: false, + onRowClick: [ + { + id: 0, + "##eventHandlerType": "Update State", + parameters: { + key: stateKey, + type: "set", + persist: false, + value: `{{ ${safe("eventContext")}.${safe("row")}._id }}`, + }, + }, + { + id: 1, + "##eventHandlerType": "Open Side Panel", + parameters: { + id: detailsSidePanel._json._id, + }, + }, + ], + }) + .instanceName(`${datasource.label} - Table`) + + return new Screen() + .route(gridDetailsUrl(datasource)) + .instanceName(`${datasource.label} - List and details`) + .addChild(gridHeader) + .addChild(gridBlock) + .addChild(createRowSidePanel) + .addChild(detailsSidePanel) + .json() +} diff --git a/packages/builder/src/templates/gridListScreen.js b/packages/builder/src/templates/gridListScreen.js new file mode 100644 index 0000000000..c98d5d4baf --- /dev/null +++ b/packages/builder/src/templates/gridListScreen.js @@ -0,0 +1,41 @@ +import sanitizeUrl from "helpers/sanitizeUrl" +import { Screen } from "./Screen" +import { Component } from "./Component" + +export default function (datasources) { + if (!Array.isArray(datasources)) { + return [] + } + return datasources.map(datasource => { + return { + name: `${datasource.label} - List`, + create: () => createScreen(datasource), + id: GRID_LIST_TEMPLATE, + resourceId: datasource.resourceId, + } + }) +} + +export const GRID_LIST_TEMPLATE = "GRID_LIST_TEMPLATE" +export const gridListUrl = datasource => sanitizeUrl(`/${datasource.label}`) + +const createScreen = datasource => { + const heading = new Component("@budibase/standard-components/heading") + .instanceName("Table heading") + .customProps({ + text: datasource?.label, + }) + + const gridBlock = new Component("@budibase/standard-components/gridblock") + .instanceName(`${datasource.label} - Table`) + .customProps({ + table: datasource, + }) + + return new Screen() + .route(gridListUrl(datasource)) + .instanceName(`${datasource.label} - List`) + .addChild(heading) + .addChild(gridBlock) + .json() +} diff --git a/packages/builder/src/templates/index.js b/packages/builder/src/templates/index.js index fff31cc070..b00b8cb621 100644 --- a/packages/builder/src/templates/index.js +++ b/packages/builder/src/templates/index.js @@ -1,9 +1,11 @@ -import rowListScreen from "./rowListScreen" +import gridListScreen from "./gridListScreen" +import gridDetailsScreen from "./gridDetailsScreen" import createFromScratchScreen from "./createFromScratchScreen" import formScreen from "./formScreen" const allTemplates = datasources => [ - ...rowListScreen(datasources), + ...gridListScreen(datasources), + ...gridDetailsScreen(datasources), ...formScreen(datasources), ] diff --git a/packages/builder/src/templates/rowListScreen.js b/packages/builder/src/templates/rowListScreen.js deleted file mode 100644 index 7781a3d067..0000000000 --- a/packages/builder/src/templates/rowListScreen.js +++ /dev/null @@ -1,63 +0,0 @@ -import sanitizeUrl from "helpers/sanitizeUrl" -import { Screen } from "./Screen" -import { Component } from "./Component" - -export default function (datasources, mode = "table") { - if (!Array.isArray(datasources)) { - return [] - } - return datasources.map(datasource => { - return { - name: `${datasource.label} - List`, - create: () => createScreen(datasource, mode), - id: ROW_LIST_TEMPLATE, - resourceId: datasource.resourceId, - } - }) -} - -export const ROW_LIST_TEMPLATE = "ROW_LIST_TEMPLATE" -export const rowListUrl = datasource => sanitizeUrl(`/${datasource.label}`) - -const generateTableBlock = datasource => { - const tableBlock = new Component("@budibase/standard-components/tableblock") - tableBlock - .customProps({ - title: datasource.label, - dataSource: datasource, - sortOrder: "Ascending", - size: "spectrum--medium", - paginate: true, - rowCount: 8, - clickBehaviour: "details", - showTitleButton: true, - titleButtonText: "Create row", - titleButtonClickBehaviour: "new", - sidePanelSaveLabel: "Save", - sidePanelDeleteLabel: "Delete", - }) - .instanceName(`${datasource.label} - Table block`) - return tableBlock -} - -const generateGridBlock = datasource => { - const gridBlock = new Component("@budibase/standard-components/gridblock") - gridBlock - .customProps({ - table: datasource, - }) - .instanceName(`${datasource.label} - Grid block`) - return gridBlock -} - -const createScreen = (datasource, mode) => { - return new Screen() - .route(rowListUrl(datasource)) - .instanceName(`${datasource.label} - List`) - .addChild( - mode === "table" - ? generateTableBlock(datasource) - : generateGridBlock(datasource) - ) - .json() -} diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 3dd308a27b..8e3dfd111a 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -4722,6 +4722,7 @@ } }, "table": { + "deprecated": true, "name": "Table", "icon": "Table", "illegalChildren": ["section"], @@ -5467,6 +5468,7 @@ ] }, "tableblock": { + "deprecated": true, "block": true, "name": "Table Block", "icon": "Table", @@ -6644,7 +6646,7 @@ ] }, "gridblock": { - "name": "Grid Block", + "name": "Table", "icon": "Table", "styles": ["size"], "size": { diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 7dbe0c0e44..378fa64b73 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -246,15 +246,18 @@ return } + const cacheId = `${definition.name}${ + definition?.deprecated === true ? "_deprecated" : "" + }` // Get the settings definition for this component, and cache it - if (SettingsDefinitionCache[definition.name]) { - settingsDefinition = SettingsDefinitionCache[definition.name] - settingsDefinitionMap = SettingsDefinitionMapCache[definition.name] + if (SettingsDefinitionCache[cacheId]) { + settingsDefinition = SettingsDefinitionCache[cacheId] + settingsDefinitionMap = SettingsDefinitionMapCache[cacheId] } else { settingsDefinition = getSettingsDefinition(definition) settingsDefinitionMap = getSettingsDefinitionMap(settingsDefinition) - SettingsDefinitionCache[definition.name] = settingsDefinition - SettingsDefinitionMapCache[definition.name] = settingsDefinitionMap + SettingsDefinitionCache[cacheId] = settingsDefinition + SettingsDefinitionMapCache[cacheId] = settingsDefinitionMap } // Parse the instance settings, and cache them diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index 1b92c0fefc..992a166143 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -291,7 +291,10 @@
diff --git a/packages/client/src/components/app/blocks/MultiStepFormblock.svelte b/packages/client/src/components/app/blocks/MultiStepFormblock.svelte index 762afc1579..bcc62b5229 100644 --- a/packages/client/src/components/app/blocks/MultiStepFormblock.svelte +++ b/packages/client/src/components/app/blocks/MultiStepFormblock.svelte @@ -26,9 +26,12 @@ let schema + $: id = $component.id + $: selected = $component.selected + $: builderStep = $builderStore.metadata?.step $: fetchSchema(dataSource) - $: enrichedSteps = enrichSteps(steps, schema, $component.id, $currentStep) - $: updateCurrentStep(enrichedSteps, $builderStore, $component) + $: enrichedSteps = enrichSteps(steps, schema, id) + $: updateCurrentStep(enrichedSteps, selected, builderStep) // Provide additional data context for live binding eval export const getAdditionalDataContext = () => { @@ -40,30 +43,22 @@ } } - const updateCurrentStep = (steps, builderStore, component) => { - const { componentId, step } = builderStore.metadata || {} - - // If we aren't in the builder or aren't selected then don't update the step - // context at all, allowing the normal form to take control. - if ( - !component.selected || - !builderStore.inBuilder || - componentId !== component.id - ) { + const updateCurrentStep = (steps, selected, builderStep) => { + // If we aren't selected in the builder then just allowing the normal form + // to take control. + if (!selected) { return } // Ensure we have a valid step selected - let newStep = Math.min(step || 0, steps.length - 1) - - // Sanity check + let newStep = Math.min(builderStep || 0, steps.length - 1) newStep = Math.max(newStep, 0) // Add 1 because the form component expects 1 indexed rather than 0 indexed currentStep.set(newStep + 1) } - const fetchSchema = async () => { + const fetchSchema = async dataSource => { schema = (await fetchDatasourceSchema(dataSource)) || {} } diff --git a/packages/client/src/components/app/blocks/form/FormBlock.svelte b/packages/client/src/components/app/blocks/form/FormBlock.svelte index 8c084a71ab..d249569731 100644 --- a/packages/client/src/components/app/blocks/form/FormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/FormBlock.svelte @@ -34,6 +34,7 @@ $: formattedFields = convertOldFieldFormat(fields) $: fieldsOrDefault = getDefaultFields(formattedFields, schema) $: fetchSchema(dataSource) + $: id = $component.id // We could simply spread $$props into the inner form and append our // additions, but that would create svelte warnings about unused props and // make maintenance in future more confusing as we typically always have a @@ -53,7 +54,7 @@ buttons: buttons || Utils.buildFormBlockButtonConfig({ - _id: $component.id, + _id: id, showDeleteButton, showSaveButton, saveButtonLabel, diff --git a/packages/client/src/components/app/blocks/index.js b/packages/client/src/components/app/blocks/index.js index 2c8d81cf96..c1df620285 100644 --- a/packages/client/src/components/app/blocks/index.js +++ b/packages/client/src/components/app/blocks/index.js @@ -1,4 +1,3 @@ -export { default as tableblock } from "./TableBlock.svelte" export { default as cardsblock } from "./CardsBlock.svelte" export { default as repeaterblock } from "./RepeaterBlock.svelte" export { default as formblock } from "./form/FormBlock.svelte" diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/deprecated/TableBlock.svelte similarity index 94% rename from packages/client/src/components/app/blocks/TableBlock.svelte rename to packages/client/src/components/app/deprecated/TableBlock.svelte index 7c58f90508..5fd197bc02 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/deprecated/TableBlock.svelte @@ -1,5 +1,6 @@
({ + // Cached props + componentId, + color, + zIndex, + prefix, + allowResizeAnchors, + + // Computed state + indicators: [], + text: null, + icon: null, + insideGrid: false, + error: false, + }) + let interval - let text - let icon - let insideGrid = false - let errorState = false - - $: visibleIndicators = indicators.filter(x => x.visible) - $: offset = $builderStore.inBuilder ? 0 : 2 - + let state = defaultState() + let nextState = null let updating = false let observers = [] let callbackCount = 0 - let nextIndicators = [] + + $: visibleIndicators = state.indicators.filter(x => x.visible) + $: offset = $builderStore.inBuilder ? 0 : 2 + $: $$props, debouncedUpdate() const checkInsideGrid = id => { const component = document.getElementsByClassName(id)[0] @@ -44,10 +56,10 @@ if (callbackCount >= observers.length) { return } - nextIndicators[idx].visible = - nextIndicators[idx].insideSidePanel || entries[0].isIntersecting + nextState.indicators[idx].visible = + nextState.indicators[idx].insideSidePanel || entries[0].isIntersecting if (++callbackCount === observers.length) { - indicators = nextIndicators + state = nextState updating = false } } @@ -59,7 +71,7 @@ // Sanity check if (!componentId) { - indicators = [] + state = defaultState() return } @@ -68,25 +80,25 @@ callbackCount = 0 observers.forEach(o => o.disconnect()) observers = [] - nextIndicators = [] + nextState = defaultState() // Check if we're inside a grid if (allowResizeAnchors) { - insideGrid = checkInsideGrid(componentId) + nextState.insideGrid = checkInsideGrid(componentId) } // Determine next set of indicators const parents = document.getElementsByClassName(componentId) if (parents.length) { - text = parents[0].dataset.name - if (prefix) { - text = `${prefix} ${text}` + nextState.text = parents[0].dataset.name + if (nextState.prefix) { + nextState.text = `${nextState.prefix} ${nextState.text}` } if (parents[0].dataset.icon) { - icon = parents[0].dataset.icon + nextState.icon = parents[0].dataset.icon } } - errorState = parents?.[0]?.classList.contains("error") + nextState.error = parents?.[0]?.classList.contains("error") // Batch reads to minimize reflow const scrollX = window.scrollX @@ -102,8 +114,9 @@ // If there aren't any nodes then reset if (!children.length) { - indicators = [] + state = defaultState() updating = false + return } const device = document.getElementById("app-root") @@ -119,7 +132,7 @@ observers.push(observer) const elBounds = child.getBoundingClientRect() - nextIndicators.push({ + nextState.indicators.push({ top: elBounds.top + scrollY - deviceBounds.top - offset, left: elBounds.left + scrollX - deviceBounds.left - offset, width: elBounds.width + 4, @@ -144,20 +157,17 @@ }) -{#key componentId} - {#each visibleIndicators as indicator, idx} - - {/each} -{/key} +{#each visibleIndicators as indicator, idx} + +{/each} diff --git a/packages/client/src/components/preview/SelectionIndicator.svelte b/packages/client/src/components/preview/SelectionIndicator.svelte index bca0341628..a271389cbd 100644 --- a/packages/client/src/components/preview/SelectionIndicator.svelte +++ b/packages/client/src/components/preview/SelectionIndicator.svelte @@ -10,7 +10,6 @@ diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 9c249dd5b3..de6d1bdc12 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -98,7 +98,7 @@ const loadBudibase = async () => { context: stringifiedContext, }) } else if (type === "hover-component") { - hoverStore.actions.hoverComponent(data) + hoverStore.actions.hoverComponent(data, false) } else if (type === "builder-meta") { builderStore.actions.setMetadata(data) } diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js index d86d635970..1f996bf656 100644 --- a/packages/client/src/sdk.js +++ b/packages/client/src/sdk.js @@ -34,6 +34,8 @@ import { LuceneUtils, Constants, RowUtils, + memo, + derivedMemo, } from "@budibase/frontend-core" export default { @@ -71,6 +73,8 @@ export default { makePropSafe, createContextStore, generateGoldenSample: RowUtils.generateGoldenSample, + memo, + derivedMemo, // Components Provider, diff --git a/packages/client/src/stores/hover.js b/packages/client/src/stores/hover.js index 24f315a126..014a9f1aa0 100644 --- a/packages/client/src/stores/hover.js +++ b/packages/client/src/stores/hover.js @@ -5,13 +5,27 @@ const createHoverStore = () => { const store = writable({ hoveredComponentId: null, }) + let hoverTimeout - const hoverComponent = id => { + const hoverComponent = (id, notifyBuilder = true) => { + clearTimeout(hoverTimeout) + if (id) { + processHover(id, notifyBuilder) + } else { + hoverTimeout = setTimeout(() => { + processHover(id, notifyBuilder) + }, 10) + } + } + + const processHover = (id, notifyBuilder = true) => { if (id === get(store).hoveredComponentId) { return } store.set({ hoveredComponentId: id }) - eventStore.actions.dispatchEvent("hover-component", { id }) + if (notifyBuilder) { + eventStore.actions.dispatchEvent("hover-component", { id }) + } } return { diff --git a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte index 5e3a035d89..ed09301bb9 100644 --- a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte +++ b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte @@ -40,16 +40,18 @@ } } + // Handle certain key presses regardless of selection state + if (e.key === "Enter" && (e.ctrlKey || e.metaKey) && $config.canAddRows) { + e.preventDefault() + dispatch("add-row-inline") + return + } + // If nothing selected avoid processing further key presses if (!$focusedCellId) { if (e.key === "Tab" || e.key?.startsWith("Arrow")) { e.preventDefault() focusFirstCell() - } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { - if ($config.canAddRows) { - e.preventDefault() - dispatch("add-row-inline") - } } else if (e.key === "Delete" || e.key === "Backspace") { if (Object.keys($selectedRows).length && $config.canDeleteRows) { dispatch("request-bulk-delete") diff --git a/packages/pro b/packages/pro index 7d1b3eaf33..dd748e045f 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 7d1b3eaf33e560d19d591813e5bba91d75ef3953 +Subproject commit dd748e045ffdbc6662c5d2b76075f01d65a96a2f diff --git a/packages/server/__mocks__/@google-cloud/firestore.ts b/packages/server/__mocks__/@google-cloud/firestore.ts index a438d6a7c5..a5bccb1fa8 100644 --- a/packages/server/__mocks__/@google-cloud/firestore.ts +++ b/packages/server/__mocks__/@google-cloud/firestore.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars module FirebaseMock { const firebase: any = {} diff --git a/packages/server/__mocks__/@sendgrid/mail.ts b/packages/server/__mocks__/@sendgrid/mail.ts index 030a2dbd4c..8613ae4b16 100644 --- a/packages/server/__mocks__/@sendgrid/mail.ts +++ b/packages/server/__mocks__/@sendgrid/mail.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars module SendgridMock { class Email { constructor() { diff --git a/packages/server/__mocks__/airtable.ts b/packages/server/__mocks__/airtable.ts index ee4b38ffcd..fed30ca069 100644 --- a/packages/server/__mocks__/airtable.ts +++ b/packages/server/__mocks__/airtable.ts @@ -1,8 +1,5 @@ -module AirtableMock { - function Airtable() { - // @ts-ignore - this.base = jest.fn() - } - - module.exports = Airtable +class Airtable { + base = jest.fn() } + +module.exports = Airtable diff --git a/packages/server/__mocks__/arangojs.ts b/packages/server/__mocks__/arangojs.ts index 5f980a7f97..77ec7a9d42 100644 --- a/packages/server/__mocks__/arangojs.ts +++ b/packages/server/__mocks__/arangojs.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars module ArangoMock { const arangodb: any = {} diff --git a/packages/server/__mocks__/aws-sdk.ts b/packages/server/__mocks__/aws-sdk.ts index 3cf4bba007..d6d33f6c46 100644 --- a/packages/server/__mocks__/aws-sdk.ts +++ b/packages/server/__mocks__/aws-sdk.ts @@ -1,102 +1,81 @@ import fs from "fs" import { join } from "path" -module AwsMock { - const aws: any = {} +const response = (body: any, extra?: any) => () => ({ + promise: () => body, + ...extra, +}) - const response = (body: any, extra?: any) => () => ({ - promise: () => body, - ...extra, - }) - - function DocumentClient() { - // @ts-ignore - this.put = jest.fn(response({})) - // @ts-ignore - this.query = jest.fn( - response({ - Items: [], - }) - ) - // @ts-ignore - this.scan = jest.fn( - response({ - Items: [ - { - Name: "test", - }, - ], - }) - ) - // @ts-ignore - this.get = jest.fn(response({})) - // @ts-ignore - this.update = jest.fn(response({})) - // @ts-ignore - this.delete = jest.fn(response({})) - } - - function S3() { - // @ts-ignore - this.listObjects = jest.fn( - response({ - Contents: [], - }) - ) - - // @ts-ignore - this.createBucket = jest.fn( - response({ - Contents: {}, - }) - ) - - // @ts-ignore - this.deleteObjects = jest.fn( - response({ - Contents: {}, - }) - ) - - // @ts-ignore - this.getSignedUrl = (operation, params) => { - return `http://example.com/${params.Bucket}/${params.Key}` - } - - // @ts-ignore - this.headBucket = jest.fn( - response({ - Contents: {}, - }) - ) - - // @ts-ignore - this.upload = jest.fn( - response({ - Contents: {}, - }) - ) - - // @ts-ignore - this.getObject = jest.fn( - response( +class DocumentClient { + put = jest.fn(response({})) + query = jest.fn( + response({ + Items: [], + }) + ) + scan = jest.fn( + response({ + Items: [ { - Body: "", + Name: "test", }, - { - createReadStream: jest - .fn() - .mockReturnValue( - fs.createReadStream(join(__dirname, "aws-sdk.ts")) - ), - } - ) - ) - } - - aws.DynamoDB = { DocumentClient } - aws.S3 = S3 - aws.config = { update: jest.fn() } - - module.exports = aws + ], + }) + ) + get = jest.fn(response({})) + update = jest.fn(response({})) + delete = jest.fn(response({})) +} + +class S3 { + listObjects = jest.fn( + response({ + Contents: [], + }) + ) + createBucket = jest.fn( + response({ + Contents: {}, + }) + ) + deleteObjects = jest.fn( + response({ + Contents: {}, + }) + ) + getSignedUrl = jest.fn((operation, params) => { + return `http://example.com/${params.Bucket}/${params.Key}` + }) + headBucket = jest.fn( + response({ + Contents: {}, + }) + ) + upload = jest.fn( + response({ + Contents: {}, + }) + ) + getObject = jest.fn( + response( + { + Body: "", + }, + { + createReadStream: jest + .fn() + .mockReturnValue(fs.createReadStream(join(__dirname, "aws-sdk.ts"))), + } + ) + ) +} + +module.exports = { + DynamoDB: { + DocumentClient, + }, + S3, + config: { + update: jest.fn(), + }, } diff --git a/packages/server/__mocks__/mongodb.ts b/packages/server/__mocks__/mongodb.ts index 01b6e76fc4..659d35f5b3 100644 --- a/packages/server/__mocks__/mongodb.ts +++ b/packages/server/__mocks__/mongodb.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars module MongoMock { const mongodb: any = {} diff --git a/packages/server/__mocks__/mssql.ts b/packages/server/__mocks__/mssql.ts deleted file mode 100644 index cdfb7d520e..0000000000 --- a/packages/server/__mocks__/mssql.ts +++ /dev/null @@ -1,24 +0,0 @@ -module MsSqlMock { - const mssql: any = {} - - mssql.query = jest.fn(() => ({ - recordset: [ - { - a: "string", - b: 1, - }, - ], - })) - - // mssql.connect = jest.fn(() => ({ recordset: [] })) - - mssql.ConnectionPool = jest.fn(() => ({ - connect: jest.fn(() => ({ - request: jest.fn(() => ({ - query: jest.fn(sql => ({ recordset: [sql] })), - })), - })), - })) - - module.exports = mssql -} diff --git a/packages/server/__mocks__/mysql2.ts b/packages/server/__mocks__/mysql2.ts deleted file mode 100644 index 9d42a35dd8..0000000000 --- a/packages/server/__mocks__/mysql2.ts +++ /dev/null @@ -1,14 +0,0 @@ -module MySQLMock { - const mysql: any = {} - - const client = { - connect: jest.fn(), - query: jest.fn((query, bindings, fn) => { - fn(null, []) - }), - } - - mysql.createConnection = jest.fn(() => client) - - module.exports = mysql -} diff --git a/packages/server/__mocks__/mysql2/promise.ts b/packages/server/__mocks__/mysql2/promise.ts deleted file mode 100644 index 8a8fb7fcf0..0000000000 --- a/packages/server/__mocks__/mysql2/promise.ts +++ /dev/null @@ -1,17 +0,0 @@ -module MySQLMock { - const mysql: any = {} - - const client = { - connect: jest.fn(), - end: jest.fn(), - query: jest.fn(async () => { - return [[]] - }), - } - - mysql.createConnection = jest.fn(async () => { - return client - }) - - module.exports = mysql -} diff --git a/packages/server/__mocks__/node-fetch.ts b/packages/server/__mocks__/node-fetch.ts index 98c75bb84f..c5073499a7 100644 --- a/packages/server/__mocks__/node-fetch.ts +++ b/packages/server/__mocks__/node-fetch.ts @@ -1,6 +1,7 @@ // @ts-ignore import fs from "fs" +// eslint-disable-next-line @typescript-eslint/no-unused-vars module FetchMock { // @ts-ignore const fetch = jest.requireActual("node-fetch") diff --git a/packages/server/__mocks__/oracledb.ts b/packages/server/__mocks__/oracledb.ts index fd19845eee..0172ace0e6 100644 --- a/packages/server/__mocks__/oracledb.ts +++ b/packages/server/__mocks__/oracledb.ts @@ -1,31 +1,21 @@ -module OracleDbMock { - // mock execute - const execute = jest.fn(() => ({ - rows: [ - { - a: "string", - b: 1, - }, - ], - })) +const executeMock = jest.fn(() => ({ + rows: [ + { + a: "string", + b: 1, + }, + ], +})) - const close = jest.fn() +const closeMock = jest.fn() - // mock connection - function Connection() {} - Connection.prototype.execute = execute - Connection.prototype.close = close - - // mock oracledb - const oracleDb: any = {} - oracleDb.getConnection = jest.fn(() => { - // @ts-ignore - return new Connection() - }) - - // expose mocks - oracleDb.executeMock = execute - oracleDb.closeMock = close - - module.exports = oracleDb +class Connection { + execute = executeMock + close = closeMock +} + +module.exports = { + getConnection: jest.fn(() => new Connection()), + executeMock, + closeMock, } diff --git a/packages/server/__mocks__/pg.ts b/packages/server/__mocks__/pg.ts index 110933ad52..50a7c7349e 100644 --- a/packages/server/__mocks__/pg.ts +++ b/packages/server/__mocks__/pg.ts @@ -1,30 +1,25 @@ -module PgMock { - const pg: any = {} +const query = jest.fn(() => ({ + rows: [ + { + a: "string", + b: 1, + }, + ], +})) - const query = jest.fn(() => ({ - rows: [ - { - a: "string", - b: 1, - }, - ], - })) - - // constructor - function Client() {} - - Client.prototype.query = query - Client.prototype.end = jest.fn(cb => { +class Client { + query = query + end = jest.fn(cb => { if (cb) cb() }) - Client.prototype.connect = jest.fn() - Client.prototype.release = jest.fn() - - const on = jest.fn() - - pg.Client = Client - pg.queryMock = query - pg.on = on - - module.exports = pg + connect = jest.fn() + release = jest.fn() +} + +const on = jest.fn() + +module.exports = { + Client, + queryMock: query, + on, } diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 8e62954d88..40db259289 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -26,7 +26,6 @@ import { env as envCore, ErrorCode, events, - HTTPError, migrations, objectStore, roles, diff --git a/packages/server/src/api/controllers/plugin/index.ts b/packages/server/src/api/controllers/plugin/index.ts index a8a451c98c..c7d4912db3 100644 --- a/packages/server/src/api/controllers/plugin/index.ts +++ b/packages/server/src/api/controllers/plugin/index.ts @@ -39,25 +39,28 @@ export async function create(ctx: any) { let name = "PLUGIN_" + Math.floor(100000 + Math.random() * 900000) switch (source) { - case PluginSource.NPM: + case PluginSource.NPM: { const { metadata: metadataNpm, directory: directoryNpm } = await npmUpload(url, name) metadata = metadataNpm directory = directoryNpm break - case PluginSource.GITHUB: + } + case PluginSource.GITHUB: { const { metadata: metadataGithub, directory: directoryGithub } = await githubUpload(url, name, githubToken) metadata = metadataGithub directory = directoryGithub break - case PluginSource.URL: + } + case PluginSource.URL: { const headersObj = headers || {} const { metadata: metadataUrl, directory: directoryUrl } = await urlUpload(url, name, headersObj) metadata = metadataUrl directory = directoryUrl break + } } pluginCore.validate(metadata?.schema) diff --git a/packages/server/src/api/controllers/query/import/sources/openapi2.ts b/packages/server/src/api/controllers/query/import/sources/openapi2.ts index 230647475e..6eb4766c70 100644 --- a/packages/server/src/api/controllers/query/import/sources/openapi2.ts +++ b/packages/server/src/api/controllers/query/import/sources/openapi2.ts @@ -109,13 +109,14 @@ export class OpenAPI2 extends OpenAPISource { for (let param of allParams) { if (parameterNotRef(param)) { switch (param.in) { - case "query": + case "query": { let prefix = "" if (queryString) { prefix = "&" } queryString = `${queryString}${prefix}${param.name}={{${param.name}}}` break + } case "header": headers[param.name] = `{{${param.name}}}` break @@ -125,7 +126,7 @@ export class OpenAPI2 extends OpenAPISource { case "formData": // future enhancement break - case "body": + case "body": { // set the request body to the example provided // future enhancement: generate an example from the schema let bodyParam: OpenAPIV2.InBodyParameterObject = @@ -135,6 +136,7 @@ export class OpenAPI2 extends OpenAPISource { requestBody = schema.example } break + } } // add the parameter if it can be bound in our config diff --git a/packages/server/src/api/controllers/query/import/sources/openapi3.ts b/packages/server/src/api/controllers/query/import/sources/openapi3.ts index f86f684c32..f6755c69ad 100644 --- a/packages/server/src/api/controllers/query/import/sources/openapi3.ts +++ b/packages/server/src/api/controllers/query/import/sources/openapi3.ts @@ -161,13 +161,14 @@ export class OpenAPI3 extends OpenAPISource { for (let param of allParams) { if (parameterNotRef(param)) { switch (param.in) { - case "query": + case "query": { let prefix = "" if (queryString) { prefix = "&" } queryString = `${queryString}${prefix}${param.name}={{${param.name}}}` break + } case "header": headers[param.name] = `{{${param.name}}}` break diff --git a/packages/server/src/api/controllers/role.ts b/packages/server/src/api/controllers/role.ts index 2f5340d2e6..3398c8102c 100644 --- a/packages/server/src/api/controllers/role.ts +++ b/packages/server/src/api/controllers/role.ts @@ -116,7 +116,7 @@ export async function save(ctx: UserCtx) { target: prodDb.name, }) await replication.replicate({ - filter: (doc: any, params: any) => { + filter: (doc: any) => { return doc._id && doc._id.startsWith("role_") }, }) diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 814b57567f..f89c9dc51a 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -7,13 +7,11 @@ import { FilterType, IncludeRelationship, ManyToManyRelationshipFieldMetadata, - ManyToOneRelationshipFieldMetadata, OneToManyRelationshipFieldMetadata, Operation, PaginationJson, RelationshipFieldMetadata, RelationshipsJson, - RelationshipType, Row, SearchFilters, SortJson, @@ -717,7 +715,7 @@ export class ExternalRequest { const rows = related[key]?.rows || [] - function relationshipMatchPredicate({ + const relationshipMatchPredicate = ({ row, linkPrimary, linkSecondary, @@ -725,7 +723,7 @@ export class ExternalRequest { row: Row linkPrimary: string linkSecondary?: string - }) { + }) => { const matchesPrimaryLink = row[linkPrimary] === relationship.id || row[linkPrimary] === body?.[linkPrimary] diff --git a/packages/server/src/api/controllers/row/alias.ts b/packages/server/src/api/controllers/row/alias.ts index 1d586c54fd..60c207c8ce 100644 --- a/packages/server/src/api/controllers/row/alias.ts +++ b/packages/server/src/api/controllers/row/alias.ts @@ -23,6 +23,12 @@ const DISABLED_WRITE_CLIENTS: SqlClient[] = [ SqlClient.ORACLE, ] +const DISABLED_OPERATIONS: Operation[] = [ + Operation.CREATE_TABLE, + Operation.UPDATE_TABLE, + Operation.DELETE_TABLE, +] + class CharSequence { static alphabet = "abcdefghijklmnopqrstuvwxyz" counters: number[] @@ -59,13 +65,18 @@ export default class AliasTables { } isAliasingEnabled(json: QueryJson, datasource: Datasource) { + const operation = json.endpoint.operation const fieldLength = json.resource?.fields?.length - if (!fieldLength || fieldLength <= 0) { + if ( + !fieldLength || + fieldLength <= 0 || + DISABLED_OPERATIONS.includes(operation) + ) { return false } try { const sqlClient = getSQLClient(datasource) - const isWrite = WRITE_OPERATIONS.includes(json.endpoint.operation) + const isWrite = WRITE_OPERATIONS.includes(operation) const isDisabledClient = DISABLED_WRITE_CLIENTS.includes(sqlClient) if (isWrite && isDisabledClient) { return false diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts index 188fe86f17..2c6cb4b17a 100644 --- a/packages/server/src/api/controllers/row/views.ts +++ b/packages/server/src/api/controllers/row/views.ts @@ -1,4 +1,3 @@ -import { quotas } from "@budibase/pro" import { UserCtx, ViewV2, diff --git a/packages/server/src/api/controllers/table/external.ts b/packages/server/src/api/controllers/table/external.ts index c85b46a95c..7c036bec9d 100644 --- a/packages/server/src/api/controllers/table/external.ts +++ b/packages/server/src/api/controllers/table/external.ts @@ -61,9 +61,6 @@ export async function destroy(ctx: UserCtx) { const tableToDelete: TableRequest = await sdk.tables.getTable( ctx.params.tableId ) - if (!tableToDelete || !tableToDelete.created) { - ctx.throw(400, "Cannot delete tables which weren't created in Budibase.") - } const datasourceId = getDatasourceId(tableToDelete) try { const { datasource, table } = await sdk.tables.external.destroy( diff --git a/packages/server/src/api/controllers/table/utils.ts b/packages/server/src/api/controllers/table/utils.ts index 9e1bb59e9d..0c9933a4cf 100644 --- a/packages/server/src/api/controllers/table/utils.ts +++ b/packages/server/src/api/controllers/table/utils.ts @@ -30,6 +30,8 @@ import { View, RelationshipFieldMetadata, FieldType, + FieldTypeSubtypes, + AttachmentFieldMetadata, } from "@budibase/types" export async function clearColumns(table: Table, columnNames: string[]) { @@ -88,6 +90,27 @@ export async function checkForColumnUpdates( // Update views await checkForViewUpdates(updatedTable, deletedColumns, columnRename) } + + const changedAttachmentSubtypeColumns = Object.values( + updatedTable.schema + ).filter( + (column): column is AttachmentFieldMetadata => + column.type === FieldType.ATTACHMENT && + column.subtype !== oldTable?.schema[column.name]?.subtype + ) + for (const attachmentColumn of changedAttachmentSubtypeColumns) { + if (attachmentColumn.subtype === FieldTypeSubtypes.ATTACHMENT.SINGLE) { + attachmentColumn.constraints ??= { length: {} } + attachmentColumn.constraints.length ??= {} + attachmentColumn.constraints.length.maximum = 1 + attachmentColumn.constraints.length.message = + "cannot contain multiple files" + } else { + delete attachmentColumn.constraints?.length?.maximum + delete attachmentColumn.constraints?.length?.message + } + } + return { rows: updatedRows, table: updatedTable } } diff --git a/packages/server/src/api/controllers/user.ts b/packages/server/src/api/controllers/user.ts index 7fc5c6e1bc..108e29fd3d 100644 --- a/packages/server/src/api/controllers/user.ts +++ b/packages/server/src/api/controllers/user.ts @@ -1,6 +1,6 @@ import { generateUserFlagID, InternalTables } from "../../db/utils" import { getFullUser } from "../../utilities/users" -import { cache, context } from "@budibase/backend-core" +import { context } from "@budibase/backend-core" import { ContextUserMetadata, Ctx, diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index f4f969622a..a386ac303f 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -24,7 +24,7 @@ async function parseSchema(view: CreateViewRequest) { icon: schemaValue.icon, } Object.entries(fieldSchema) - .filter(([_, val]) => val === undefined) + .filter(([, val]) => val === undefined) .forEach(([key]) => { delete fieldSchema[key as keyof UIFieldMetadata] }) diff --git a/packages/server/src/api/routes/index.ts b/packages/server/src/api/routes/index.ts index 22d0a20c9b..5a42c258cf 100644 --- a/packages/server/src/api/routes/index.ts +++ b/packages/server/src/api/routes/index.ts @@ -33,7 +33,6 @@ export { default as staticRoutes } from "./static" export { default as publicRoutes } from "./public" const appBackupRoutes = pro.appBackups -const scheduleRoutes = pro.schedules const environmentVariableRoutes = pro.environmentVariables export const mainRoutes: Router[] = [ @@ -65,7 +64,6 @@ export const mainRoutes: Router[] = [ pluginRoutes, opsRoutes, debugRoutes, - scheduleRoutes, environmentVariableRoutes, // these need to be handled last as they still use /api/:tableId // this could be breaking as koa may recognise other routes as this diff --git a/packages/server/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap b/packages/server/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap index 8dc472173c..57d79db24b 100644 --- a/packages/server/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap +++ b/packages/server/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap @@ -81,6 +81,7 @@ exports[`/datasources fetch returns all the datasources from the server 1`] = ` { "config": {}, "createdAt": "2020-01-01T00:00:00.000Z", + "isSQL": true, "name": "Test", "source": "POSTGRES", "type": "datasource", diff --git a/packages/server/src/api/routes/tests/appImport.spec.ts b/packages/server/src/api/routes/tests/appImport.spec.ts index b2ef39838d..75e9f91d63 100644 --- a/packages/server/src/api/routes/tests/appImport.spec.ts +++ b/packages/server/src/api/routes/tests/appImport.spec.ts @@ -16,7 +16,7 @@ describe("/applications/:appId/import", () => { it("should be able to perform import", async () => { const appId = config.getAppId() - const res = await request + await request .post(`/api/applications/${appId}/import`) .field("encryptionPassword", PASSWORD) .attach("appExport", path.join(__dirname, "assets", "export.tar.gz")) diff --git a/packages/server/src/api/routes/tests/appSync.spec.ts b/packages/server/src/api/routes/tests/appSync.spec.ts index 0773b2b008..93b8175c48 100644 --- a/packages/server/src/api/routes/tests/appSync.spec.ts +++ b/packages/server/src/api/routes/tests/appSync.spec.ts @@ -2,7 +2,6 @@ import * as setup from "./utilities" import { roles, db as dbCore } from "@budibase/backend-core" describe("/api/applications/:appId/sync", () => { - let request = setup.getRequest() let config = setup.getConfig() let app diff --git a/packages/server/src/api/routes/tests/application.spec.ts b/packages/server/src/api/routes/tests/application.spec.ts index 6b0ba89350..7f89a5cac2 100644 --- a/packages/server/src/api/routes/tests/application.spec.ts +++ b/packages/server/src/api/routes/tests/application.spec.ts @@ -19,6 +19,7 @@ import env from "../../../environment" import { type App } from "@budibase/types" import tk from "timekeeper" import * as uuid from "uuid" +import { structures } from "@budibase/backend-core/tests" describe("/applications", () => { let config = setup.getConfig() @@ -356,7 +357,7 @@ describe("/applications", () => { it("should reject an unknown app id with a 404", async () => { await config.api.application.duplicateApp( - app.appId.slice(0, -1) + "a", + structures.db.id(), { name: "to-dupe 123", url: "/to-dupe-123", @@ -368,7 +369,7 @@ describe("/applications", () => { }) it("should reject with a known name", async () => { - const resp = await config.api.application.duplicateApp( + await config.api.application.duplicateApp( app.appId, { name: app.name, @@ -380,7 +381,7 @@ describe("/applications", () => { }) it("should reject with a known url", async () => { - const resp = await config.api.application.duplicateApp( + await config.api.application.duplicateApp( app.appId, { name: "this is fine", diff --git a/packages/server/src/api/routes/tests/debug.spec.ts b/packages/server/src/api/routes/tests/debug.spec.ts index 53e1f67823..546344a646 100644 --- a/packages/server/src/api/routes/tests/debug.spec.ts +++ b/packages/server/src/api/routes/tests/debug.spec.ts @@ -1,13 +1,5 @@ -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const setup = require("./utilities") - -import os from "os" - -jest.mock("process", () => ({ - arch: "arm64", - version: "v14.20.1", - platform: "darwin", -})) +import * as setup from "./utilities" +import { checkBuilderEndpoint } from "./utilities/TestFunctions" describe("/component", () => { let request = setup.getRequest() @@ -17,21 +9,6 @@ describe("/component", () => { beforeAll(async () => { await config.init() - os.cpus = () => [ - { - model: "test", - speed: 12323, - times: { - user: 0, - nice: 0, - sys: 0, - idle: 0, - irq: 0, - }, - }, - ] - os.uptime = () => 123123123123 - os.totalmem = () => 10000000000 }) describe("/api/debug", () => { @@ -43,14 +20,16 @@ describe("/component", () => { .expect(200) expect(res.body).toEqual({ budibaseVersion: "0.0.0+jest", - cpuArch: "arm64", - cpuCores: 1, - cpuInfo: "test", + cpuArch: expect.any(String), + cpuCores: expect.any(Number), + cpuInfo: expect.any(String), hosting: "docker-compose", - nodeVersion: "v14.20.1", - platform: "darwin", - totalMemory: "9.313225746154785GB", - uptime: "1425036 day(s), 3 hour(s), 32 minute(s)", + nodeVersion: expect.stringMatching(/^v\d+\.\d+\.\d+$/), + platform: expect.any(String), + totalMemory: expect.stringMatching(/^[0-9\\.]+GB$/), + uptime: expect.stringMatching( + /^\d+ day\(s\), \d+ hour\(s\), \d+ minute\(s\)$/ + ), }) }) diff --git a/packages/server/src/api/routes/tests/permissions.spec.ts b/packages/server/src/api/routes/tests/permissions.spec.ts index af7a2a578e..bee794da47 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.ts +++ b/packages/server/src/api/routes/tests/permissions.spec.ts @@ -156,7 +156,7 @@ describe("/permission", () => { level: PermissionLevel.READ, }) - const response = await config.api.permission.revoke( + await config.api.permission.revoke( { roleId: STD_ROLE_ID, resourceId: table._id, diff --git a/packages/server/src/api/routes/tests/queries/generic-sql.spec.ts b/packages/server/src/api/routes/tests/queries/generic-sql.spec.ts new file mode 100644 index 0000000000..1fc0ecb382 --- /dev/null +++ b/packages/server/src/api/routes/tests/queries/generic-sql.spec.ts @@ -0,0 +1,401 @@ +import { Datasource, Query, SourceName } from "@budibase/types" +import * as setup from "../utilities" +import { databaseTestProviders } from "../../../../integrations/tests/utils" +import pg from "pg" +import mysql from "mysql2/promise" +import mssql from "mssql" + +jest.unmock("pg") + +const createTableSQL: Record = { + [SourceName.POSTGRES]: ` + CREATE TABLE test_table ( + id serial PRIMARY KEY, + name VARCHAR ( 50 ) NOT NULL, + birthday TIMESTAMP + );`, + [SourceName.MYSQL]: ` + CREATE TABLE test_table ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) NOT NULL, + birthday TIMESTAMP + );`, + [SourceName.SQL_SERVER]: ` + CREATE TABLE test_table ( + id INT IDENTITY(1,1) PRIMARY KEY, + name NVARCHAR(50) NOT NULL, + birthday DATETIME + );`, +} + +const insertSQL = `INSERT INTO test_table (name) VALUES ('one'), ('two'), ('three'), ('four'), ('five')` +const dropTableSQL = `DROP TABLE test_table;` + +describe.each([ + ["postgres", databaseTestProviders.postgres], + ["mysql", databaseTestProviders.mysql], + ["mssql", databaseTestProviders.mssql], + ["mariadb", databaseTestProviders.mariadb], +])("queries (%s)", (__, dsProvider) => { + const config = setup.getConfig() + let datasource: Datasource + + async function createQuery(query: Partial): Promise { + const defaultQuery: Query = { + datasourceId: datasource._id!, + name: "New Query", + parameters: [], + fields: {}, + schema: {}, + queryVerb: "read", + transformer: "return data", + readable: true, + } + return await config.api.query.create({ ...defaultQuery, ...query }) + } + + async function rawQuery(sql: string): Promise { + // We re-fetch the datasource here because the one returned by + // config.api.datasource.create has the password field blanked out, and we + // need the password to connect to the database. + const ds = await dsProvider.datasource() + switch (ds.source) { + case SourceName.POSTGRES: { + const client = new pg.Client(ds.config!) + await client.connect() + try { + const { rows } = await client.query(sql) + return rows + } finally { + await client.end() + } + } + case SourceName.MYSQL: { + const con = await mysql.createConnection(ds.config!) + try { + const [rows] = await con.query(sql) + return rows + } finally { + con.end() + } + } + case SourceName.SQL_SERVER: { + const pool = new mssql.ConnectionPool(ds.config! as mssql.config) + const client = await pool.connect() + try { + const { recordset } = await client.query(sql) + return recordset + } finally { + await pool.close() + } + } + } + } + + beforeAll(async () => { + await config.init() + datasource = await config.api.datasource.create( + await dsProvider.datasource() + ) + }) + + beforeEach(async () => { + await rawQuery(createTableSQL[datasource.source]) + await rawQuery(insertSQL) + }) + + afterEach(async () => { + await rawQuery(dropTableSQL) + }) + + afterAll(async () => { + await dsProvider.stop() + setup.afterAll() + }) + + describe("create", () => { + it("should be able to insert with bindings", async () => { + const query = await createQuery({ + fields: { + sql: "INSERT INTO test_table (name) VALUES ({{ foo }})", + }, + parameters: [ + { + name: "foo", + default: "bar", + }, + ], + queryVerb: "create", + }) + + const result = await config.api.query.execute(query._id!, { + parameters: { + foo: "baz", + }, + }) + + expect(result.data).toEqual([ + { + created: true, + }, + ]) + + const rows = await rawQuery("SELECT * FROM test_table WHERE name = 'baz'") + expect(rows).toHaveLength(1) + }) + + it.each(["2021-02-05T12:01:00.000Z", "2021-02-05"])( + "should coerce %s into a date", + async datetimeStr => { + const date = new Date(datetimeStr) + const query = await createQuery({ + fields: { + sql: `INSERT INTO test_table (name, birthday) VALUES ('foo', {{ birthday }})`, + }, + parameters: [ + { + name: "birthday", + default: "", + }, + ], + queryVerb: "create", + }) + + const result = await config.api.query.execute(query._id!, { + parameters: { birthday: datetimeStr }, + }) + + expect(result.data).toEqual([{ created: true }]) + + const rows = await rawQuery( + `SELECT * FROM test_table WHERE birthday = '${date.toISOString()}'` + ) + expect(rows).toHaveLength(1) + } + ) + + it.each(["2021,02,05", "202205-1500"])( + "should not coerce %s as a date", + async notDateStr => { + const query = await createQuery({ + fields: { + sql: "INSERT INTO test_table (name) VALUES ({{ name }})", + }, + parameters: [ + { + name: "name", + default: "", + }, + ], + queryVerb: "create", + }) + + const result = await config.api.query.execute(query._id!, { + parameters: { + name: notDateStr, + }, + }) + + expect(result.data).toEqual([{ created: true }]) + + const rows = await rawQuery( + `SELECT * FROM test_table WHERE name = '${notDateStr}'` + ) + expect(rows).toHaveLength(1) + } + ) + }) + + describe("read", () => { + it("should execute a query", async () => { + const query = await createQuery({ + fields: { + sql: "SELECT * FROM test_table ORDER BY id", + }, + }) + + const result = await config.api.query.execute(query._id!) + + expect(result.data).toEqual([ + { + id: 1, + name: "one", + birthday: null, + }, + { + id: 2, + name: "two", + birthday: null, + }, + { + id: 3, + name: "three", + birthday: null, + }, + { + id: 4, + name: "four", + birthday: null, + }, + { + id: 5, + name: "five", + birthday: null, + }, + ]) + }) + + it("should be able to transform a query", async () => { + const query = await createQuery({ + fields: { + sql: "SELECT * FROM test_table WHERE id = 1", + }, + transformer: ` + data[0].id = data[0].id + 1; + return data; + `, + }) + + const result = await config.api.query.execute(query._id!) + + expect(result.data).toEqual([ + { + id: 2, + name: "one", + birthday: null, + }, + ]) + }) + + it("should coerce numeric bindings", async () => { + const query = await createQuery({ + fields: { + sql: "SELECT * FROM test_table WHERE id = {{ id }}", + }, + parameters: [ + { + name: "id", + default: "", + }, + ], + }) + + const result = await config.api.query.execute(query._id!, { + parameters: { + id: "1", + }, + }) + + expect(result.data).toEqual([ + { + id: 1, + name: "one", + birthday: null, + }, + ]) + }) + }) + + describe("update", () => { + it("should be able to update rows", async () => { + const query = await createQuery({ + fields: { + sql: "UPDATE test_table SET name = {{ name }} WHERE id = {{ id }}", + }, + parameters: [ + { + name: "id", + default: "", + }, + { + name: "name", + default: "updated", + }, + ], + queryVerb: "update", + }) + + const result = await config.api.query.execute(query._id!, { + parameters: { + id: "1", + name: "foo", + }, + }) + + expect(result.data).toEqual([ + { + updated: true, + }, + ]) + + const rows = await rawQuery("SELECT * FROM test_table WHERE id = 1") + expect(rows).toEqual([{ id: 1, name: "foo", birthday: null }]) + }) + + it("should be able to execute an update that updates no rows", async () => { + const query = await createQuery({ + fields: { + sql: "UPDATE test_table SET name = 'updated' WHERE id = 100", + }, + queryVerb: "update", + }) + + const result = await config.api.query.execute(query._id!) + + expect(result.data).toEqual([ + { + updated: true, + }, + ]) + }) + + it("should be able to execute a delete that deletes no rows", async () => { + const query = await createQuery({ + fields: { + sql: "DELETE FROM test_table WHERE id = 100", + }, + queryVerb: "delete", + }) + + const result = await config.api.query.execute(query._id!) + + expect(result.data).toEqual([ + { + deleted: true, + }, + ]) + }) + }) + + describe("delete", () => { + it("should be able to delete rows", async () => { + const query = await createQuery({ + fields: { + sql: "DELETE FROM test_table WHERE id = {{ id }}", + }, + parameters: [ + { + name: "id", + default: "", + }, + ], + queryVerb: "delete", + }) + + const result = await config.api.query.execute(query._id!, { + parameters: { + id: "1", + }, + }) + + expect(result.data).toEqual([ + { + deleted: true, + }, + ]) + + const rows = await rawQuery("SELECT * FROM test_table WHERE id = 1") + expect(rows).toHaveLength(0) + }) + }) +}) diff --git a/packages/server/src/api/routes/tests/queries/mysql.spec.ts b/packages/server/src/api/routes/tests/queries/mysql.spec.ts deleted file mode 100644 index 1c9c1d3865..0000000000 --- a/packages/server/src/api/routes/tests/queries/mysql.spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { Datasource, Query } from "@budibase/types" -import * as setup from "../utilities" -import { databaseTestProviders } from "../../../../integrations/tests/utils" -import mysql from "mysql2/promise" - -jest.unmock("mysql2") -jest.unmock("mysql2/promise") - -const createTableSQL = ` -CREATE TABLE test_table ( - id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(50) NOT NULL -) -` - -const insertSQL = ` -INSERT INTO test_table (name) VALUES ('one'), ('two'), ('three'), ('four'), ('five') -` - -const dropTableSQL = ` -DROP TABLE test_table -` - -describe("/queries", () => { - let config = setup.getConfig() - let datasource: Datasource - - async function createQuery(query: Partial): Promise { - const defaultQuery: Query = { - datasourceId: datasource._id!, - name: "New Query", - parameters: [], - fields: {}, - schema: {}, - queryVerb: "read", - transformer: "return data", - readable: true, - } - return await config.api.query.create({ ...defaultQuery, ...query }) - } - - async function withConnection( - callback: (client: mysql.Connection) => Promise - ): Promise { - const ds = await databaseTestProviders.mysql.datasource() - const con = await mysql.createConnection(ds.config!) - try { - await callback(con) - } finally { - con.end() - } - } - - afterAll(async () => { - await databaseTestProviders.mysql.stop() - setup.afterAll() - }) - - beforeAll(async () => { - await config.init() - datasource = await config.api.datasource.create( - await databaseTestProviders.mysql.datasource() - ) - }) - - beforeEach(async () => { - await withConnection(async connection => { - const resp = await connection.query(createTableSQL) - await connection.query(insertSQL) - }) - }) - - afterEach(async () => { - await withConnection(async connection => { - await connection.query(dropTableSQL) - }) - }) - - it("should execute a query", async () => { - const query = await createQuery({ - fields: { - sql: "SELECT * FROM test_table ORDER BY id", - }, - }) - - const result = await config.api.query.execute(query._id!) - - expect(result.data).toEqual([ - { - id: 1, - name: "one", - }, - { - id: 2, - name: "two", - }, - { - id: 3, - name: "three", - }, - { - id: 4, - name: "four", - }, - { - id: 5, - name: "five", - }, - ]) - }) - - it("should be able to transform a query", async () => { - const query = await createQuery({ - fields: { - sql: "SELECT * FROM test_table WHERE id = 1", - }, - transformer: ` - data[0].id = data[0].id + 1; - return data; - `, - }) - - const result = await config.api.query.execute(query._id!) - - expect(result.data).toEqual([ - { - id: 2, - name: "one", - }, - ]) - }) - - it("should be able to insert with bindings", async () => { - const query = await createQuery({ - fields: { - sql: "INSERT INTO test_table (name) VALUES ({{ foo }})", - }, - parameters: [ - { - name: "foo", - default: "bar", - }, - ], - queryVerb: "create", - }) - - const result = await config.api.query.execute(query._id!, { - parameters: { - foo: "baz", - }, - }) - - expect(result.data).toEqual([ - { - created: true, - }, - ]) - - await withConnection(async connection => { - const [rows] = await connection.query( - "SELECT * FROM test_table WHERE name = 'baz'" - ) - expect(rows).toHaveLength(1) - }) - }) - - it("should be able to update rows", async () => { - const query = await createQuery({ - fields: { - sql: "UPDATE test_table SET name = {{ name }} WHERE id = {{ id }}", - }, - parameters: [ - { - name: "id", - default: "", - }, - { - name: "name", - default: "updated", - }, - ], - queryVerb: "update", - }) - - const result = await config.api.query.execute(query._id!, { - parameters: { - id: "1", - name: "foo", - }, - }) - - expect(result.data).toEqual([ - { - updated: true, - }, - ]) - - await withConnection(async connection => { - const [rows] = await connection.query( - "SELECT * FROM test_table WHERE id = 1" - ) - expect(rows).toEqual([{ id: 1, name: "foo" }]) - }) - }) - - it("should be able to delete rows", async () => { - const query = await createQuery({ - fields: { - sql: "DELETE FROM test_table WHERE id = {{ id }}", - }, - parameters: [ - { - name: "id", - default: "", - }, - ], - queryVerb: "delete", - }) - - const result = await config.api.query.execute(query._id!, { - parameters: { - id: "1", - }, - }) - - expect(result.data).toEqual([ - { - deleted: true, - }, - ]) - - await withConnection(async connection => { - const [rows] = await connection.query( - "SELECT * FROM test_table WHERE id = 1" - ) - expect(rows).toHaveLength(0) - }) - }) -}) diff --git a/packages/server/src/api/routes/tests/queries/postgres.spec.ts b/packages/server/src/api/routes/tests/queries/postgres.spec.ts deleted file mode 100644 index fd6a2b7d3c..0000000000 --- a/packages/server/src/api/routes/tests/queries/postgres.spec.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { Datasource, Query } from "@budibase/types" -import * as setup from "../utilities" -import { databaseTestProviders } from "../../../../integrations/tests/utils" -import { Client } from "pg" - -jest.unmock("pg") - -const createTableSQL = ` -CREATE TABLE test_table ( - id serial PRIMARY KEY, - name VARCHAR ( 50 ) NOT NULL -); -` - -const insertSQL = ` -INSERT INTO test_table (name) VALUES ('one'); -INSERT INTO test_table (name) VALUES ('two'); -INSERT INTO test_table (name) VALUES ('three'); -INSERT INTO test_table (name) VALUES ('four'); -INSERT INTO test_table (name) VALUES ('five'); -` - -const dropTableSQL = ` -DROP TABLE test_table; -` - -describe("/queries", () => { - let config = setup.getConfig() - let datasource: Datasource - - async function createQuery(query: Partial): Promise { - const defaultQuery: Query = { - datasourceId: datasource._id!, - name: "New Query", - parameters: [], - fields: {}, - schema: {}, - queryVerb: "read", - transformer: "return data", - readable: true, - } - return await config.api.query.create({ ...defaultQuery, ...query }) - } - - async function withClient( - callback: (client: Client) => Promise - ): Promise { - const ds = await databaseTestProviders.postgres.datasource() - const client = new Client(ds.config!) - await client.connect() - try { - await callback(client) - } finally { - await client.end() - } - } - - afterAll(async () => { - await databaseTestProviders.postgres.stop() - setup.afterAll() - }) - - beforeAll(async () => { - await config.init() - datasource = await config.api.datasource.create( - await databaseTestProviders.postgres.datasource() - ) - }) - - beforeEach(async () => { - await withClient(async client => { - await client.query(createTableSQL) - await client.query(insertSQL) - }) - }) - - afterEach(async () => { - await withClient(async client => { - await client.query(dropTableSQL) - }) - }) - - it("should execute a query", async () => { - const query = await createQuery({ - fields: { - sql: "SELECT * FROM test_table ORDER BY id", - }, - }) - - const result = await config.api.query.execute(query._id!) - - expect(result.data).toEqual([ - { - id: 1, - name: "one", - }, - { - id: 2, - name: "two", - }, - { - id: 3, - name: "three", - }, - { - id: 4, - name: "four", - }, - { - id: 5, - name: "five", - }, - ]) - }) - - it("should be able to transform a query", async () => { - const query = await createQuery({ - fields: { - sql: "SELECT * FROM test_table WHERE id = 1", - }, - transformer: ` - data[0].id = data[0].id + 1; - return data; - `, - }) - - const result = await config.api.query.execute(query._id!) - - expect(result.data).toEqual([ - { - id: 2, - name: "one", - }, - ]) - }) - - it("should be able to insert with bindings", async () => { - const query = await createQuery({ - fields: { - sql: "INSERT INTO test_table (name) VALUES ({{ foo }})", - }, - parameters: [ - { - name: "foo", - default: "bar", - }, - ], - queryVerb: "create", - }) - - const result = await config.api.query.execute(query._id!, { - parameters: { - foo: "baz", - }, - }) - - expect(result.data).toEqual([ - { - created: true, - }, - ]) - - await withClient(async client => { - const { rows } = await client.query( - "SELECT * FROM test_table WHERE name = 'baz'" - ) - expect(rows).toHaveLength(1) - }) - }) - - it("should be able to update rows", async () => { - const query = await createQuery({ - fields: { - sql: "UPDATE test_table SET name = {{ name }} WHERE id = {{ id }}", - }, - parameters: [ - { - name: "id", - default: "", - }, - { - name: "name", - default: "updated", - }, - ], - queryVerb: "update", - }) - - const result = await config.api.query.execute(query._id!, { - parameters: { - id: "1", - name: "foo", - }, - }) - - expect(result.data).toEqual([ - { - updated: true, - }, - ]) - - await withClient(async client => { - const { rows } = await client.query( - "SELECT * FROM test_table WHERE id = 1" - ) - expect(rows).toEqual([{ id: 1, name: "foo" }]) - }) - }) - - it("should be able to delete rows", async () => { - const query = await createQuery({ - fields: { - sql: "DELETE FROM test_table WHERE id = {{ id }}", - }, - parameters: [ - { - name: "id", - default: "", - }, - ], - queryVerb: "delete", - }) - - const result = await config.api.query.execute(query._id!, { - parameters: { - id: "1", - }, - }) - - expect(result.data).toEqual([ - { - deleted: true, - }, - ]) - - await withClient(async client => { - const { rows } = await client.query( - "SELECT * FROM test_table WHERE id = 1" - ) - expect(rows).toHaveLength(0) - }) - }) -}) diff --git a/packages/server/src/api/routes/tests/queries/query.seq.spec.ts b/packages/server/src/api/routes/tests/queries/query.seq.spec.ts index 0ca424c48b..10b90eafb1 100644 --- a/packages/server/src/api/routes/tests/queries/query.seq.spec.ts +++ b/packages/server/src/api/routes/tests/queries/query.seq.spec.ts @@ -330,6 +330,7 @@ describe("/queries", () => { ], }, ] + pg.queryMock.mockImplementation(() => ({ rows, })) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 854410dcf6..f638f2c4bf 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -3,7 +3,7 @@ import { databaseTestProviders } from "../../../integrations/tests/utils" import tk from "timekeeper" import { outputProcessing } from "../../../utilities/rowProcessor" import * as setup from "./utilities" -import { context, InternalTable, roles, tenancy } from "@budibase/backend-core" +import { context, InternalTable, tenancy } from "@budibase/backend-core" import { quotas } from "@budibase/pro" import { AutoFieldSubType, @@ -14,33 +14,21 @@ import { FieldTypeSubtypes, FormulaType, INTERNAL_TABLE_SOURCE_ID, - PermissionLevel, QuotaUsageType, RelationshipType, Row, SaveTableRequest, - SearchQueryOperators, - SortOrder, - SortType, StaticQuotaName, Table, TableSourceType, - ViewV2, } from "@budibase/types" -import { - expectAnyExternalColsAttributes, - expectAnyInternalColsAttributes, - generator, - mocks, -} from "@budibase/backend-core/tests" +import { generator, mocks } from "@budibase/backend-core/tests" import _, { merge } from "lodash" import * as uuid from "uuid" const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString() tk.freeze(timestamp) -jest.unmock("mysql2") -jest.unmock("mysql2/promise") jest.unmock("mssql") jest.unmock("pg") @@ -392,6 +380,23 @@ describe.each([ expect(row.arrayFieldArrayStrKnown).toEqual(["One"]) expect(row.optsFieldStrKnown).toEqual("Alpha") }) + + isInternal && + it("doesn't allow creating in user table", async () => { + const userTableId = InternalTable.USER_METADATA + const response = await config.api.row.save( + userTableId, + { + tableId: userTableId, + firstName: "Joe", + lastName: "Joe", + email: "joe@joe.com", + roles: {}, + }, + { status: 400 } + ) + expect(response.message).toBe("Cannot create new user entry.") + }) }) describe("get", () => { @@ -890,642 +895,6 @@ describe.each([ }) }) - describe("view 2.0", () => { - async function userTable(): Promise { - return saveTableRequest({ - name: `users_${uuid.v4()}`, - type: "table", - schema: { - name: { - type: FieldType.STRING, - name: "name", - }, - surname: { - type: FieldType.STRING, - name: "surname", - }, - age: { - type: FieldType.NUMBER, - name: "age", - }, - address: { - type: FieldType.STRING, - name: "address", - }, - jobTitle: { - type: FieldType.STRING, - name: "jobTitle", - }, - }, - }) - } - - const randomRowData = () => ({ - name: generator.first(), - surname: generator.last(), - age: generator.age(), - address: generator.address(), - jobTitle: generator.word(), - }) - - describe("create", () => { - it("should persist a new row with only the provided view fields", async () => { - const table = await config.api.table.save(await userTable()) - const view = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - schema: { - name: { visible: true }, - surname: { visible: true }, - address: { visible: true }, - }, - }) - - const data = randomRowData() - const newRow = await config.api.row.save(view.id, { - tableId: table!._id, - _viewId: view.id, - ...data, - }) - - const row = await config.api.row.get(table._id!, newRow._id!) - expect(row).toEqual({ - name: data.name, - surname: data.surname, - address: data.address, - tableId: table!._id, - _id: newRow._id, - _rev: newRow._rev, - id: newRow.id, - ...defaultRowFields, - }) - expect(row._viewId).toBeUndefined() - expect(row.age).toBeUndefined() - expect(row.jobTitle).toBeUndefined() - }) - }) - - describe("patch", () => { - it("should update only the view fields for a row", async () => { - const table = await config.api.table.save(await userTable()) - const tableId = table._id! - const view = await config.api.viewV2.create({ - tableId: tableId, - name: generator.guid(), - schema: { - name: { visible: true }, - address: { visible: true }, - }, - }) - - const newRow = await config.api.row.save(view.id, { - tableId, - _viewId: view.id, - ...randomRowData(), - }) - const newData = randomRowData() - await config.api.row.patch(view.id, { - tableId, - _viewId: view.id, - _id: newRow._id!, - _rev: newRow._rev!, - ...newData, - }) - - const row = await config.api.row.get(tableId, newRow._id!) - expect(row).toEqual({ - ...newRow, - name: newData.name, - address: newData.address, - _id: newRow._id, - _rev: expect.any(String), - id: newRow.id, - ...defaultRowFields, - }) - expect(row._viewId).toBeUndefined() - expect(row.age).toBeUndefined() - expect(row.jobTitle).toBeUndefined() - }) - }) - - describe("destroy", () => { - it("should be able to delete a row", async () => { - const table = await config.api.table.save(await userTable()) - const tableId = table._id! - const view = await config.api.viewV2.create({ - tableId: tableId, - name: generator.guid(), - schema: { - name: { visible: true }, - address: { visible: true }, - }, - }) - - const createdRow = await config.api.row.save(table._id!, {}) - const rowUsage = await getRowUsage() - - await config.api.row.bulkDelete(view.id, { rows: [createdRow] }) - - await assertRowUsage(rowUsage - 1) - - await config.api.row.get(tableId, createdRow._id!, { - status: 404, - }) - }) - - it("should be able to delete multiple rows", async () => { - const table = await config.api.table.save(await userTable()) - const tableId = table._id! - const view = await config.api.viewV2.create({ - tableId: tableId, - name: generator.guid(), - schema: { - name: { visible: true }, - address: { visible: true }, - }, - }) - - const rows = await Promise.all([ - config.api.row.save(table._id!, {}), - config.api.row.save(table._id!, {}), - config.api.row.save(table._id!, {}), - ]) - const rowUsage = await getRowUsage() - - await config.api.row.bulkDelete(view.id, { rows: [rows[0], rows[2]] }) - - await assertRowUsage(rowUsage - 2) - - await config.api.row.get(tableId, rows[0]._id!, { - status: 404, - }) - await config.api.row.get(tableId, rows[2]._id!, { - status: 404, - }) - await config.api.row.get(tableId, rows[1]._id!, { status: 200 }) - }) - }) - - describe("view search", () => { - let table: Table - const viewSchema = { age: { visible: true }, name: { visible: true } } - - beforeAll(async () => { - table = await config.api.table.save( - saveTableRequest({ - name: `users_${uuid.v4()}`, - schema: { - name: { - type: FieldType.STRING, - name: "name", - constraints: { type: "string" }, - }, - age: { - type: FieldType.NUMBER, - name: "age", - constraints: {}, - }, - }, - }) - ) - }) - - it("returns empty rows from view when no schema is passed", async () => { - const rows = await Promise.all( - Array.from({ length: 10 }, () => - config.api.row.save(table._id!, { tableId: table._id }) - ) - ) - - const createViewResponse = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - }) - const response = await config.api.viewV2.search(createViewResponse.id) - - expect(response.rows).toHaveLength(10) - expect(response).toEqual({ - rows: expect.arrayContaining( - rows.map(r => ({ - _viewId: createViewResponse.id, - tableId: table._id, - _id: r._id, - _rev: r._rev, - ...defaultRowFields, - })) - ), - ...(isInternal - ? {} - : { - hasNextPage: false, - bookmark: null, - }), - }) - }) - - it("searching respects the view filters", async () => { - await Promise.all( - Array.from({ length: 10 }, () => - config.api.row.save(table._id!, { - tableId: table._id, - name: generator.name(), - age: generator.integer({ min: 10, max: 30 }), - }) - ) - ) - - const expectedRows = await Promise.all( - Array.from({ length: 5 }, () => - config.api.row.save(table._id!, { - tableId: table._id, - name: generator.name(), - age: 40, - }) - ) - ) - - const createViewResponse = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - query: [ - { operator: SearchQueryOperators.EQUAL, field: "age", value: 40 }, - ], - schema: viewSchema, - }) - - const response = await config.api.viewV2.search(createViewResponse.id) - - expect(response.rows).toHaveLength(5) - expect(response).toEqual({ - rows: expect.arrayContaining( - expectedRows.map(r => ({ - _viewId: createViewResponse.id, - tableId: table._id, - name: r.name, - age: r.age, - _id: r._id, - _rev: r._rev, - ...defaultRowFields, - })) - ), - ...(isInternal - ? {} - : { - hasNextPage: false, - bookmark: null, - }), - }) - }) - - const sortTestOptions: [ - { - field: string - order?: SortOrder - type?: SortType - }, - string[] - ][] = [ - [ - { - field: "name", - order: SortOrder.ASCENDING, - type: SortType.STRING, - }, - ["Alice", "Bob", "Charly", "Danny"], - ], - [ - { - field: "name", - }, - ["Alice", "Bob", "Charly", "Danny"], - ], - [ - { - field: "name", - order: SortOrder.DESCENDING, - }, - ["Danny", "Charly", "Bob", "Alice"], - ], - [ - { - field: "name", - order: SortOrder.DESCENDING, - type: SortType.STRING, - }, - ["Danny", "Charly", "Bob", "Alice"], - ], - [ - { - field: "age", - order: SortOrder.ASCENDING, - type: SortType.number, - }, - ["Danny", "Alice", "Charly", "Bob"], - ], - [ - { - field: "age", - order: SortOrder.ASCENDING, - }, - ["Danny", "Alice", "Charly", "Bob"], - ], - [ - { - field: "age", - order: SortOrder.DESCENDING, - }, - ["Bob", "Charly", "Alice", "Danny"], - ], - [ - { - field: "age", - order: SortOrder.DESCENDING, - type: SortType.number, - }, - ["Bob", "Charly", "Alice", "Danny"], - ], - ] - - describe("sorting", () => { - let table: Table - beforeAll(async () => { - table = await config.api.table.save(await userTable()) - const users = [ - { name: "Alice", age: 25 }, - { name: "Bob", age: 30 }, - { name: "Charly", age: 27 }, - { name: "Danny", age: 15 }, - ] - await Promise.all( - users.map(u => - config.api.row.save(table._id!, { - tableId: table._id, - ...u, - }) - ) - ) - }) - - it.each(sortTestOptions)( - "allow sorting (%s)", - async (sortParams, expected) => { - const createViewResponse = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - sort: sortParams, - schema: viewSchema, - }) - - const response = await config.api.viewV2.search( - createViewResponse.id - ) - - expect(response.rows).toHaveLength(4) - expect(response.rows).toEqual( - expected.map(name => expect.objectContaining({ name })) - ) - } - ) - - it.each(sortTestOptions)( - "allow override the default view sorting (%s)", - async (sortParams, expected) => { - const createViewResponse = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - sort: { - field: "name", - order: SortOrder.ASCENDING, - type: SortType.STRING, - }, - schema: viewSchema, - }) - - const response = await config.api.viewV2.search( - createViewResponse.id, - { - sort: sortParams.field, - sortOrder: sortParams.order, - sortType: sortParams.type, - query: {}, - } - ) - - expect(response.rows).toHaveLength(4) - expect(response.rows).toEqual( - expected.map(name => expect.objectContaining({ name })) - ) - } - ) - }) - - it("when schema is defined, defined columns and row attributes are returned", async () => { - const table = await config.api.table.save(await userTable()) - const rows = await Promise.all( - Array.from({ length: 10 }, () => - config.api.row.save(table._id!, { - tableId: table._id, - name: generator.name(), - age: generator.age(), - }) - ) - ) - - const view = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - schema: { name: { visible: true } }, - }) - const response = await config.api.viewV2.search(view.id) - - expect(response.rows).toHaveLength(10) - expect(response.rows).toEqual( - expect.arrayContaining( - rows.map(r => ({ - ...(isInternal - ? expectAnyInternalColsAttributes - : expectAnyExternalColsAttributes), - _viewId: view.id, - name: r.name, - })) - ) - ) - }) - - it("views without data can be returned", async () => { - const table = await config.api.table.save(await userTable()) - const createViewResponse = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - }) - const response = await config.api.viewV2.search(createViewResponse.id) - expect(response.rows).toHaveLength(0) - }) - - it("respects the limit parameter", async () => { - const table = await config.api.table.save(await userTable()) - await Promise.all( - Array.from({ length: 10 }, () => config.api.row.save(table._id!, {})) - ) - - const limit = generator.integer({ min: 1, max: 8 }) - - const createViewResponse = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - }) - const response = await config.api.viewV2.search(createViewResponse.id, { - limit, - query: {}, - }) - - expect(response.rows).toHaveLength(limit) - }) - - it("can handle pagination", async () => { - const table = await config.api.table.save(await userTable()) - await Promise.all( - Array.from({ length: 10 }, () => config.api.row.save(table._id!, {})) - ) - const view = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - }) - const rows = (await config.api.viewV2.search(view.id)).rows - - const page1 = await config.api.viewV2.search(view.id, { - paginate: true, - limit: 4, - query: {}, - }) - expect(page1).toEqual({ - rows: expect.arrayContaining(rows.slice(0, 4)), - totalRows: isInternal ? 10 : undefined, - hasNextPage: true, - bookmark: expect.anything(), - }) - - const page2 = await config.api.viewV2.search(view.id, { - paginate: true, - limit: 4, - bookmark: page1.bookmark, - - query: {}, - }) - expect(page2).toEqual({ - rows: expect.arrayContaining(rows.slice(4, 8)), - totalRows: isInternal ? 10 : undefined, - hasNextPage: true, - bookmark: expect.anything(), - }) - - const page3 = await config.api.viewV2.search(view.id, { - paginate: true, - limit: 4, - bookmark: page2.bookmark, - query: {}, - }) - expect(page3).toEqual({ - rows: expect.arrayContaining(rows.slice(8)), - totalRows: isInternal ? 10 : undefined, - hasNextPage: false, - bookmark: expect.anything(), - }) - }) - - isInternal && - it("doesn't allow creating in user table", async () => { - const userTableId = InternalTable.USER_METADATA - const response = await config.api.row.save( - userTableId, - { - tableId: userTableId, - firstName: "Joe", - lastName: "Joe", - email: "joe@joe.com", - roles: {}, - }, - { status: 400 } - ) - expect(response.message).toBe("Cannot create new user entry.") - }) - - describe("permissions", () => { - let table: Table - let view: ViewV2 - - beforeAll(async () => { - table = await config.api.table.save(await userTable()) - await Promise.all( - Array.from({ length: 10 }, () => - config.api.row.save(table._id!, {}) - ) - ) - - view = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - }) - }) - - beforeEach(() => { - mocks.licenses.useViewPermissions() - }) - - it("does not allow public users to fetch by default", async () => { - await config.publish() - await config.api.viewV2.publicSearch(view.id, undefined, { - status: 403, - }) - }) - - it("allow public users to fetch when permissions are explicit", async () => { - await config.api.permission.add({ - roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, - level: PermissionLevel.READ, - resourceId: view.id, - }) - await config.publish() - - const response = await config.api.viewV2.publicSearch(view.id) - - expect(response.rows).toHaveLength(10) - }) - - it("allow public users to fetch when permissions are inherited", async () => { - await config.api.permission.add({ - roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, - level: PermissionLevel.READ, - resourceId: table._id!, - }) - await config.publish() - - const response = await config.api.viewV2.publicSearch(view.id) - - expect(response.rows).toHaveLength(10) - }) - - it("respects inherited permissions, not allowing not public views from public tables", async () => { - await config.api.permission.add({ - roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, - level: PermissionLevel.READ, - resourceId: table._id!, - }) - await config.api.permission.add({ - roleId: roles.BUILTIN_ROLE_IDS.POWER, - level: PermissionLevel.READ, - resourceId: view.id, - }) - await config.publish() - - await config.api.viewV2.publicSearch(view.id, undefined, { - status: 403, - }) - }) - }) - }) - }) - let o2mTable: Table let m2mTable: Table beforeAll(async () => { diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts index d5a4dd6aeb..4f38603691 100644 --- a/packages/server/src/api/routes/tests/view.spec.ts +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -74,7 +74,7 @@ describe("/views", () => { describe("create", () => { it("returns a success message when the view is successfully created", async () => { - const res = await saveView() + await saveView() expect(events.view.created).toHaveBeenCalledTimes(1) }) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index ded5e08d29..f9d213a26b 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -5,23 +5,26 @@ import { FieldSchema, FieldType, INTERNAL_TABLE_SOURCE_ID, + PermissionLevel, + QuotaUsageType, SaveTableRequest, SearchQueryOperators, SortOrder, SortType, + StaticQuotaName, Table, TableSourceType, UIFieldMetadata, UpdateViewRequest, ViewV2, } from "@budibase/types" -import { generator } from "@budibase/backend-core/tests" +import { generator, mocks } from "@budibase/backend-core/tests" import * as uuid from "uuid" import { databaseTestProviders } from "../../../integrations/tests/utils" import merge from "lodash/merge" +import { quotas } from "@budibase/pro" +import { roles } from "@budibase/backend-core" -jest.unmock("mysql2") -jest.unmock("mysql2/promise") jest.unmock("mssql") jest.unmock("pg") @@ -33,6 +36,7 @@ describe.each([ ["mariadb", databaseTestProviders.mariadb], ])("/v2/views (%s)", (_, dsProvider) => { const config = setup.getConfig() + const isInternal = !dsProvider let table: Table let datasource: Datasource @@ -99,6 +103,18 @@ describe.each([ setup.afterAll() }) + const getRowUsage = async () => { + const { total } = await config.doInContext(undefined, () => + quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS) + ) + return total + } + + const assertRowUsage = async (expected: number) => { + const usage = await getRowUsage() + expect(usage).toBe(expected) + } + describe("create", () => { it("persist the view when the view is successfully created", async () => { const newView: CreateViewRequest = { @@ -525,4 +541,468 @@ describe.each([ expect(row.Country).toEqual("Aussy") }) }) + + describe("row operations", () => { + let table: Table, view: ViewV2 + beforeEach(async () => { + table = await config.api.table.save( + saveTableRequest({ + schema: { + one: { type: FieldType.STRING, name: "one" }, + two: { type: FieldType.STRING, name: "two" }, + }, + }) + ) + view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + schema: { + two: { visible: true }, + }, + }) + }) + + describe("create", () => { + it("should persist a new row with only the provided view fields", async () => { + const newRow = await config.api.row.save(view.id, { + tableId: table!._id, + _viewId: view.id, + one: "foo", + two: "bar", + }) + + const row = await config.api.row.get(table._id!, newRow._id!) + expect(row.one).toBeUndefined() + expect(row.two).toEqual("bar") + }) + }) + + describe("patch", () => { + it("should update only the view fields for a row", async () => { + const newRow = await config.api.row.save(table._id!, { + one: "foo", + two: "bar", + }) + await config.api.row.patch(view.id, { + tableId: table._id!, + _id: newRow._id!, + _rev: newRow._rev!, + one: "newFoo", + two: "newBar", + }) + + const row = await config.api.row.get(table._id!, newRow._id!) + expect(row.one).toEqual("foo") + expect(row.two).toEqual("newBar") + }) + }) + + describe("destroy", () => { + it("should be able to delete a row", async () => { + const createdRow = await config.api.row.save(table._id!, {}) + const rowUsage = await getRowUsage() + await config.api.row.bulkDelete(view.id, { rows: [createdRow] }) + await assertRowUsage(rowUsage - 1) + await config.api.row.get(table._id!, createdRow._id!, { + status: 404, + }) + }) + + it("should be able to delete multiple rows", async () => { + const rows = await Promise.all([ + config.api.row.save(table._id!, {}), + config.api.row.save(table._id!, {}), + config.api.row.save(table._id!, {}), + ]) + const rowUsage = await getRowUsage() + + await config.api.row.bulkDelete(view.id, { rows: [rows[0], rows[2]] }) + + await assertRowUsage(rowUsage - 2) + + await config.api.row.get(table._id!, rows[0]._id!, { + status: 404, + }) + await config.api.row.get(table._id!, rows[2]._id!, { + status: 404, + }) + await config.api.row.get(table._id!, rows[1]._id!, { status: 200 }) + }) + }) + + describe("search", () => { + it("returns empty rows from view when no schema is passed", async () => { + const rows = await Promise.all( + Array.from({ length: 10 }, () => config.api.row.save(table._id!, {})) + ) + const response = await config.api.viewV2.search(view.id) + expect(response.rows).toHaveLength(10) + expect(response).toEqual({ + rows: expect.arrayContaining( + rows.map(r => ({ + _viewId: view.id, + tableId: table._id, + _id: r._id, + _rev: r._rev, + ...(isInternal + ? { + type: "row", + updatedAt: expect.any(String), + createdAt: expect.any(String), + } + : {}), + })) + ), + ...(isInternal + ? {} + : { + hasNextPage: false, + bookmark: null, + }), + }) + }) + + it("searching respects the view filters", 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: SearchQueryOperators.EQUAL, + field: "two", + value: "bar2", + }, + ], + schema: { + two: { visible: true }, + }, + }) + + const response = await config.api.viewV2.search(view.id) + expect(response.rows).toHaveLength(1) + expect(response).toEqual({ + rows: expect.arrayContaining([ + { + _viewId: view.id, + tableId: table._id, + two: two.two, + _id: two._id, + _rev: two._rev, + ...(isInternal + ? { + type: "row", + createdAt: expect.any(String), + updatedAt: expect.any(String), + } + : {}), + }, + ]), + ...(isInternal + ? {} + : { + hasNextPage: false, + bookmark: null, + }), + }) + }) + + it("views without data can be returned", async () => { + const response = await config.api.viewV2.search(view.id) + expect(response.rows).toHaveLength(0) + }) + + it("respects the limit parameter", async () => { + await Promise.all( + Array.from({ length: 10 }, () => config.api.row.save(table._id!, {})) + ) + const limit = generator.integer({ min: 1, max: 8 }) + const response = await config.api.viewV2.search(view.id, { + limit, + query: {}, + }) + expect(response.rows).toHaveLength(limit) + }) + + it("can handle pagination", async () => { + await Promise.all( + Array.from({ length: 10 }, () => config.api.row.save(table._id!, {})) + ) + const rows = (await config.api.viewV2.search(view.id)).rows + + const page1 = await config.api.viewV2.search(view.id, { + paginate: true, + limit: 4, + query: {}, + }) + expect(page1).toEqual({ + rows: expect.arrayContaining(rows.slice(0, 4)), + totalRows: isInternal ? 10 : undefined, + hasNextPage: true, + bookmark: expect.anything(), + }) + + const page2 = await config.api.viewV2.search(view.id, { + paginate: true, + limit: 4, + bookmark: page1.bookmark, + query: {}, + }) + expect(page2).toEqual({ + rows: expect.arrayContaining(rows.slice(4, 8)), + totalRows: isInternal ? 10 : undefined, + hasNextPage: true, + bookmark: expect.anything(), + }) + + const page3 = await config.api.viewV2.search(view.id, { + paginate: true, + limit: 4, + bookmark: page2.bookmark, + query: {}, + }) + expect(page3).toEqual({ + rows: expect.arrayContaining(rows.slice(8)), + totalRows: isInternal ? 10 : undefined, + hasNextPage: false, + bookmark: expect.anything(), + }) + }) + + const sortTestOptions: [ + { + field: string + order?: SortOrder + type?: SortType + }, + string[] + ][] = [ + [ + { + field: "name", + order: SortOrder.ASCENDING, + type: SortType.STRING, + }, + ["Alice", "Bob", "Charly", "Danny"], + ], + [ + { + field: "name", + }, + ["Alice", "Bob", "Charly", "Danny"], + ], + [ + { + field: "name", + order: SortOrder.DESCENDING, + }, + ["Danny", "Charly", "Bob", "Alice"], + ], + [ + { + field: "name", + order: SortOrder.DESCENDING, + type: SortType.STRING, + }, + ["Danny", "Charly", "Bob", "Alice"], + ], + [ + { + field: "age", + order: SortOrder.ASCENDING, + type: SortType.number, + }, + ["Danny", "Alice", "Charly", "Bob"], + ], + [ + { + field: "age", + order: SortOrder.ASCENDING, + }, + ["Danny", "Alice", "Charly", "Bob"], + ], + [ + { + field: "age", + order: SortOrder.DESCENDING, + }, + ["Bob", "Charly", "Alice", "Danny"], + ], + [ + { + field: "age", + order: SortOrder.DESCENDING, + type: SortType.number, + }, + ["Bob", "Charly", "Alice", "Danny"], + ], + ] + + describe("sorting", () => { + let table: Table + const viewSchema = { age: { visible: true }, name: { visible: true } } + + beforeAll(async () => { + table = await config.api.table.save( + saveTableRequest({ + name: `users_${uuid.v4()}`, + type: "table", + schema: { + name: { + type: FieldType.STRING, + name: "name", + }, + surname: { + type: FieldType.STRING, + name: "surname", + }, + age: { + type: FieldType.NUMBER, + name: "age", + }, + address: { + type: FieldType.STRING, + name: "address", + }, + jobTitle: { + type: FieldType.STRING, + name: "jobTitle", + }, + }, + }) + ) + + const users = [ + { name: "Alice", age: 25 }, + { name: "Bob", age: 30 }, + { name: "Charly", age: 27 }, + { name: "Danny", age: 15 }, + ] + await Promise.all( + users.map(u => + config.api.row.save(table._id!, { + tableId: table._id, + ...u, + }) + ) + ) + }) + + it.each(sortTestOptions)( + "allow sorting (%s)", + async (sortParams, expected) => { + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + sort: sortParams, + schema: viewSchema, + }) + + const response = await config.api.viewV2.search(view.id) + + expect(response.rows).toHaveLength(4) + expect(response.rows).toEqual( + expected.map(name => expect.objectContaining({ name })) + ) + } + ) + + it.each(sortTestOptions)( + "allow override the default view sorting (%s)", + async (sortParams, expected) => { + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + sort: { + field: "name", + order: SortOrder.ASCENDING, + type: SortType.STRING, + }, + schema: viewSchema, + }) + + const response = await config.api.viewV2.search(view.id, { + sort: sortParams.field, + sortOrder: sortParams.order, + sortType: sortParams.type, + query: {}, + }) + + expect(response.rows).toHaveLength(4) + expect(response.rows).toEqual( + expected.map(name => expect.objectContaining({ name })) + ) + } + ) + }) + }) + + describe("permissions", () => { + beforeEach(async () => { + mocks.licenses.useViewPermissions() + await Promise.all( + Array.from({ length: 10 }, () => config.api.row.save(table._id!, {})) + ) + }) + + it("does not allow public users to fetch by default", async () => { + await config.publish() + await config.api.viewV2.publicSearch(view.id, undefined, { + status: 403, + }) + }) + + it("allow public users to fetch when permissions are explicit", async () => { + await config.api.permission.add({ + roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, + level: PermissionLevel.READ, + resourceId: view.id, + }) + await config.publish() + + const response = await config.api.viewV2.publicSearch(view.id) + + expect(response.rows).toHaveLength(10) + }) + + it("allow public users to fetch when permissions are inherited", async () => { + await config.api.permission.add({ + roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, + level: PermissionLevel.READ, + resourceId: table._id!, + }) + await config.publish() + + const response = await config.api.viewV2.publicSearch(view.id) + + expect(response.rows).toHaveLength(10) + }) + + it("respects inherited permissions, not allowing not public views from public tables", async () => { + await config.api.permission.add({ + roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, + level: PermissionLevel.READ, + resourceId: table._id!, + }) + await config.api.permission.add({ + roleId: roles.BUILTIN_ROLE_IDS.POWER, + level: PermissionLevel.READ, + resourceId: view.id, + }) + await config.publish() + + await config.api.viewV2.publicSearch(view.id, undefined, { + status: 403, + }) + }) + }) + }) }) diff --git a/packages/server/src/automations/automationUtils.ts b/packages/server/src/automations/automationUtils.ts index 8a298a49a3..6730e04494 100644 --- a/packages/server/src/automations/automationUtils.ts +++ b/packages/server/src/automations/automationUtils.ts @@ -5,7 +5,7 @@ import { } from "@budibase/string-templates" import sdk from "../sdk" import { Row } from "@budibase/types" -import { LoopInput, LoopStep, LoopStepType } from "../definitions/automations" +import { LoopInput, LoopStepType } from "../definitions/automations" /** * When values are input to the system generally they will be of type string as this is required for template strings. diff --git a/packages/server/src/automations/steps/collect.ts b/packages/server/src/automations/steps/collect.ts index ef1fe64772..035bd36a46 100644 --- a/packages/server/src/automations/steps/collect.ts +++ b/packages/server/src/automations/steps/collect.ts @@ -4,7 +4,6 @@ import { AutomationStepInput, AutomationStepType, AutomationIOType, - AutomationFeature, } from "@budibase/types" export const definition: AutomationStepSchema = { diff --git a/packages/server/src/automations/steps/executeQuery.ts b/packages/server/src/automations/steps/executeQuery.ts index ea0737c86a..a9517b01a0 100644 --- a/packages/server/src/automations/steps/executeQuery.ts +++ b/packages/server/src/automations/steps/executeQuery.ts @@ -10,8 +10,6 @@ import { AutomationStepSchema, AutomationStepType, } from "@budibase/types" -import { utils } from "@budibase/backend-core" -import env from "../../environment" export const definition: AutomationStepSchema = { name: "External Data Connector", diff --git a/packages/server/src/automations/steps/openai.ts b/packages/server/src/automations/steps/openai.ts index 8ae211579e..bc926de7b7 100644 --- a/packages/server/src/automations/steps/openai.ts +++ b/packages/server/src/automations/steps/openai.ts @@ -58,7 +58,7 @@ export const definition: AutomationStepSchema = { }, } -export async function run({ inputs, context }: AutomationStepInput) { +export async function run({ inputs }: AutomationStepInput) { if (!environment.OPENAI_API_KEY) { return { success: false, diff --git a/packages/server/src/automations/steps/triggerAutomationRun.ts b/packages/server/src/automations/steps/triggerAutomationRun.ts index 83e1722877..f45a60600f 100644 --- a/packages/server/src/automations/steps/triggerAutomationRun.ts +++ b/packages/server/src/automations/steps/triggerAutomationRun.ts @@ -62,6 +62,7 @@ export const definition: AutomationStepSchema = { } export async function run({ inputs }: AutomationStepInput) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { automationId, ...fieldParams } = inputs.automation if (await features.isTriggerAutomationRunEnabled()) { diff --git a/packages/server/src/automations/tests/loop.spec.ts b/packages/server/src/automations/tests/loop.spec.ts index 68ab694c5d..2c6ae9749d 100644 --- a/packages/server/src/automations/tests/loop.spec.ts +++ b/packages/server/src/automations/tests/loop.spec.ts @@ -3,19 +3,18 @@ import * as triggers from "../triggers" import { loopAutomation } from "../../tests/utilities/structures" import { context } from "@budibase/backend-core" import * as setup from "./utilities" -import { Row, Table } from "@budibase/types" +import { Table } from "@budibase/types" import { LoopInput, LoopStepType } from "../../definitions/automations" describe("Attempt to run a basic loop automation", () => { let config = setup.getConfig(), - table: Table, - row: Row + table: Table beforeEach(async () => { await automation.init() await config.init() table = await config.createTable() - row = await config.createRow() + await config.createRow() }) afterAll(setup.afterAll) diff --git a/packages/server/src/automations/unitTests/automationUtils.spec.ts b/packages/server/src/automations/unitTests/automationUtils.spec.ts index 7de4a2e35b..afb6219ef2 100644 --- a/packages/server/src/automations/unitTests/automationUtils.spec.ts +++ b/packages/server/src/automations/unitTests/automationUtils.spec.ts @@ -1,4 +1,4 @@ -import { LoopStep, LoopStepType } from "../../definitions/automations" +import { LoopStepType } from "../../definitions/automations" import { typecastForLooping, cleanInputValues, diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index 49f1d01afb..d2bc3c44e3 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -6,6 +6,10 @@ import { TableSourceType, } from "@budibase/types" +import env from "../environment" + +export const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1" + export enum FilterTypes { STRING = "string", FUZZY = "fuzzy", diff --git a/packages/server/src/db/dynamoClient.ts b/packages/server/src/db/dynamoClient.ts deleted file mode 100644 index 148c433c41..0000000000 --- a/packages/server/src/db/dynamoClient.ts +++ /dev/null @@ -1,147 +0,0 @@ -import merge from "lodash/merge" -import env from "../environment" - -export const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1" - -const TableInfo = { - API_KEYS: { - name: "beta-api-key-table", - primary: "pk", - }, - USERS: { - name: "prod-budi-table", - primary: "pk", - sort: "sk", - }, -} - -let docClient: any = null - -type GetOpts = { - primary: string - sort?: string - otherProps?: any -} - -type UpdateOpts = { - primary: string - sort?: string - expression?: string - condition?: string - names?: string[] - values?: any[] - exists?: boolean - otherProps?: any -} - -type PutOpts = { - item: any - otherProps?: any -} - -class Table { - _name: string - _primary: string - _sort?: string - - constructor(tableInfo: { name: string; primary: string; sort?: string }) { - if (!tableInfo.name || !tableInfo.primary) { - throw "Table info must specify a name and a primary key" - } - this._name = tableInfo.name - this._primary = tableInfo.primary - this._sort = tableInfo.sort - } - - async get({ primary, sort, otherProps }: GetOpts) { - let params = { - TableName: this._name, - Key: { - [this._primary]: primary, - }, - } - if (this._sort && sort) { - params.Key[this._sort] = sort - } - if (otherProps) { - params = merge(params, otherProps) - } - let response = await docClient.get(params).promise() - return response.Item - } - - async update({ - primary, - sort, - expression, - condition, - names, - values, - exists, - otherProps, - }: UpdateOpts) { - let params: any = { - TableName: this._name, - Key: { - [this._primary]: primary, - }, - ExpressionAttributeNames: names, - ExpressionAttributeValues: values, - UpdateExpression: expression, - } - if (condition) { - params.ConditionExpression = condition - } - if (this._sort && sort) { - params.Key[this._sort] = sort - } - if (exists) { - params.ExpressionAttributeNames["#PRIMARY"] = this._primary - if (params.ConditionExpression) { - params.ConditionExpression += " AND " - } - params.ConditionExpression += "attribute_exists(#PRIMARY)" - } - if (otherProps) { - params = merge(params, otherProps) - } - return docClient.update(params).promise() - } - - async put({ item, otherProps }: PutOpts) { - if ( - item[this._primary] == null || - (this._sort && item[this._sort] == null) - ) { - throw "Cannot put item without primary and sort key (if required)" - } - let params = { - TableName: this._name, - Item: item, - } - if (otherProps) { - params = merge(params, otherProps) - } - return docClient.put(params).promise() - } -} - -export function init(endpoint: string) { - let AWS = require("aws-sdk") - let docClientParams: any = { - correctClockSkew: true, - region: AWS_REGION, - } - if (endpoint) { - docClientParams.endpoint = endpoint - } else if (env.DYNAMO_ENDPOINT) { - docClientParams.endpoint = env.DYNAMO_ENDPOINT - } - docClient = new AWS.DynamoDB.DocumentClient(docClientParams) -} - -if (!env.isProd() && !env.isJest()) { - env._set("AWS_ACCESS_KEY_ID", "KEY_ID") - env._set("AWS_SECRET_ACCESS_KEY", "SECRET_KEY") - init("http://localhost:8333") -} diff --git a/packages/server/src/db/linkedRows/index.ts b/packages/server/src/db/linkedRows/index.ts index 513e0c0df2..1eb534a7f8 100644 --- a/packages/server/src/db/linkedRows/index.ts +++ b/packages/server/src/db/linkedRows/index.ts @@ -18,7 +18,6 @@ import { Row, LinkDocumentValue, FieldType, - LinkDocument, ContextUser, } from "@budibase/types" import sdk from "../../sdk" diff --git a/packages/server/src/db/views/staticViews.ts b/packages/server/src/db/views/staticViews.ts index 5b1b66d8e3..700bd2568a 100644 --- a/packages/server/src/db/views/staticViews.ts +++ b/packages/server/src/db/views/staticViews.ts @@ -30,8 +30,8 @@ export async function createLinkView() { if (doc.type === "link") { let doc1 = doc.doc1 let doc2 = doc.doc2 + // @ts-expect-error emit is available in a CouchDB map function // eslint-disable-next-line no-undef - // @ts-ignore emit([doc1.tableId, doc1.rowId], { id: doc2.rowId, thisId: doc1.rowId, @@ -39,8 +39,8 @@ export async function createLinkView() { }) // if linking to same table can't emit twice if (doc1.tableId !== doc2.tableId) { + // @ts-expect-error emit is available in a CouchDB map function // eslint-disable-next-line no-undef - // @ts-ignore emit([doc2.tableId, doc2.rowId], { id: doc1.rowId, thisId: doc2.rowId, @@ -101,8 +101,8 @@ export async function createAllSearchIndex() { if (Array.isArray(input[key])) { for (let val of input[key]) { if (typeof val !== "object") { + // @ts-expect-error index is available in a CouchDB map function // eslint-disable-next-line no-undef - // @ts-ignore index(idxKey, val, { store: true }) } } @@ -110,12 +110,12 @@ export async function createAllSearchIndex() { continue } if (typeof input[key] === "string") { + // @ts-expect-error index is available in a CouchDB map function // eslint-disable-next-line no-undef - // @ts-ignore index(idxKey, input[key].toLowerCase(), { store: true }) } else if (typeof input[key] !== "object") { + // @ts-expect-error index is available in a CouchDB map function // eslint-disable-next-line no-undef - // @ts-ignore index(idxKey, input[key], { store: true }) } else { idx(input[key], idxKey) @@ -123,8 +123,8 @@ export async function createAllSearchIndex() { } } if (doc._id!.startsWith("ro_")) { + // @ts-expect-error index is available in a CouchDB map function // eslint-disable-next-line no-undef - // @ts-ignore index("default", doc._id) idx(doc) } diff --git a/packages/server/src/features.ts b/packages/server/src/features.ts index f040cf82a2..3ab9410f53 100644 --- a/packages/server/src/features.ts +++ b/packages/server/src/features.ts @@ -1,8 +1,11 @@ import { features } from "@budibase/backend-core" import env from "./environment" +// eslint-disable-next-line no-unused-vars enum AppFeature { + // eslint-disable-next-line no-unused-vars API = "api", + // eslint-disable-next-line no-unused-vars AUTOMATIONS = "automations", } diff --git a/packages/server/src/integration-test/mysql.spec.ts b/packages/server/src/integration-test/mysql.spec.ts index 2af886fe4c..92420fb336 100644 --- a/packages/server/src/integration-test/mysql.spec.ts +++ b/packages/server/src/integration-test/mysql.spec.ts @@ -12,7 +12,6 @@ import { TableRequest, TableSourceType, } from "@budibase/types" -import _ from "lodash" import { databaseTestProviders } from "../integrations/tests/utils" import mysql from "mysql2/promise" import { builderSocket } from "../websockets" @@ -21,7 +20,6 @@ fetch.mockSearch() const config = setup.getConfig()! -jest.unmock("mysql2/promise") jest.mock("../websockets", () => ({ clientAppSocket: jest.fn(), gridAppSocket: jest.fn(), @@ -106,6 +104,7 @@ describe("mysql integrations", () => { plus: true, source: "MYSQL", type: "datasource_plus", + isSQL: true, _id: expect.any(String), _rev: expect.any(String), createdAt: expect.any(String), diff --git a/packages/server/src/integration-test/postgres.spec.ts b/packages/server/src/integration-test/postgres.spec.ts index 7c14bc2b69..107c4ade1e 100644 --- a/packages/server/src/integration-test/postgres.spec.ts +++ b/packages/server/src/integration-test/postgres.spec.ts @@ -319,6 +319,7 @@ describe("postgres integrations", () => { }, plus: true, source: "POSTGRES", + isSQL: true, type: "datasource_plus", _id: expect.any(String), _rev: expect.any(String), diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index 8342c45fd7..2093d455b9 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -699,13 +699,15 @@ class SqlQueryBuilder extends SqlTableQueryBuilder { convertJsonStringColumns( table: Table, - results: Record[] + results: Record[], + aliases?: Record ): Record[] { for (const [name, field] of Object.entries(table.schema)) { if (!this._isJsonColumn(field)) { continue } - const fullName = `${table.name}.${name}` + const tableName = aliases?.[table.name] || table.name + const fullName = `${tableName}.${name}` for (let row of results) { if (typeof row[fullName] === "string") { row[fullName] = JSON.parse(row[fullName]) diff --git a/packages/server/src/integrations/base/sqlTable.ts b/packages/server/src/integrations/base/sqlTable.ts index f3560791e6..0feecefb89 100644 --- a/packages/server/src/integrations/base/sqlTable.ts +++ b/packages/server/src/integrations/base/sqlTable.ts @@ -59,8 +59,8 @@ function generateSchema( case FieldType.BARCODEQR: schema.text(key) break - case FieldType.BB_REFERENCE: - const subtype = column.subtype as FieldSubtype + case FieldType.BB_REFERENCE: { + const subtype = column.subtype switch (subtype) { case FieldSubtype.USER: schema.text(key) @@ -72,6 +72,7 @@ function generateSchema( throw utils.unreachable(subtype) } break + } case FieldType.NUMBER: // if meta is specified then this is a junction table entry if (column.meta && column.meta.toKey && column.meta.toTable) { diff --git a/packages/server/src/integrations/dynamodb.ts b/packages/server/src/integrations/dynamodb.ts index bede4a7f7c..424a3dfce0 100644 --- a/packages/server/src/integrations/dynamodb.ts +++ b/packages/server/src/integrations/dynamodb.ts @@ -8,7 +8,7 @@ import { } from "@budibase/types" import AWS from "aws-sdk" -import { AWS_REGION } from "../db/dynamoClient" +import { AWS_REGION } from "../constants" import { DocumentClient } from "aws-sdk/clients/dynamodb" interface DynamoDBConfig { diff --git a/packages/server/src/integrations/googlesheets.ts b/packages/server/src/integrations/googlesheets.ts index 32398bde41..bc0e25e267 100644 --- a/packages/server/src/integrations/googlesheets.ts +++ b/packages/server/src/integrations/googlesheets.ts @@ -168,6 +168,7 @@ class GoogleSheetsIntegration implements DatasourcePlus { return "" } + // eslint-disable-next-line @typescript-eslint/no-unused-vars getStringConcat(parts: string[]) { return "" } diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index c79eb136ed..831fcd3d20 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -14,8 +14,6 @@ import { Schema, TableSourceType, DatasourcePlusQueryResponse, - FieldType, - FieldSubtype, } from "@budibase/types" import { getSqlQuery, @@ -254,7 +252,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { } switch (this.config.authType) { - case MSSQLConfigAuthType.AZURE_ACTIVE_DIRECTORY: + case MSSQLConfigAuthType.AZURE_ACTIVE_DIRECTORY: { const { clientId, tenantId, clientSecret } = this.config.adConfig || {} const clientApp = new ConfidentialClientApplication({ @@ -276,7 +274,8 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { }, } break - case MSSQLConfigAuthType.NTLM: + } + case MSSQLConfigAuthType.NTLM: { const { domain, trustServerCertificate } = this.config.ntlmConfig || {} clientCfg.authentication = { @@ -288,6 +287,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { clientCfg.options ??= {} clientCfg.options.trustServerCertificate = !!trustServerCertificate break + } case null: case undefined: break @@ -506,7 +506,11 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { const queryFn = (query: any, op: string) => this.internalQuery(query, op) const processFn = (result: any) => { if (json?.meta?.table && result.recordset) { - return this.convertJsonStringColumns(json.meta.table, result.recordset) + return this.convertJsonStringColumns( + json.meta.table, + result.recordset, + json.tableAliases + ) } else if (result.recordset) { return result.recordset } diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 9638afa8ea..db3819f468 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -13,8 +13,6 @@ import { Schema, TableSourceType, DatasourcePlusQueryResponse, - FieldType, - FieldSubtype, } from "@budibase/types" import { getSqlQuery, @@ -390,7 +388,11 @@ class MySQLIntegration extends Sql implements DatasourcePlus { this.internalQuery(query, { connect: false, disableCoercion: true }) const processFn = (result: any) => { if (json?.meta?.table && Array.isArray(result)) { - return this.convertJsonStringColumns(json.meta.table, result) + return this.convertJsonStringColumns( + json.meta.table, + result, + json.tableAliases + ) } return result } diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts index 44c62f60b7..5fa35cc667 100644 --- a/packages/server/src/integrations/rest.ts +++ b/packages/server/src/integrations/rest.ts @@ -283,7 +283,7 @@ class RestIntegration implements IntegrationBase { // content type defaults to plaintext input.body = string break - case BodyTypes.ENCODED: + case BodyTypes.ENCODED: { const params = new URLSearchParams() for (let [key, value] of Object.entries(object)) { params.append(key, value as string) @@ -293,7 +293,8 @@ class RestIntegration implements IntegrationBase { }) input.body = params break - case BodyTypes.FORM_DATA: + } + case BodyTypes.FORM_DATA: { const form = new FormData() for (let [key, value] of Object.entries(object)) { form.append(key, value) @@ -303,6 +304,7 @@ class RestIntegration implements IntegrationBase { }) input.body = form break + } case BodyTypes.XML: if (object != null && Object.keys(object).length) { string = new XmlBuilder().buildObject(object) diff --git a/packages/server/src/integrations/tests/airtable.spec.ts b/packages/server/src/integrations/tests/airtable.spec.ts index eeb3d65046..367e31e8a0 100644 --- a/packages/server/src/integrations/tests/airtable.spec.ts +++ b/packages/server/src/integrations/tests/airtable.spec.ts @@ -28,7 +28,7 @@ describe("Airtable Integration", () => { }) it("calls the create method with the correct params", async () => { - const response = await config.integration.create({ + await config.integration.create({ table: "test", json: {}, }) @@ -40,7 +40,7 @@ describe("Airtable Integration", () => { }) it("calls the read method with the correct params", async () => { - const response = await config.integration.read({ + await config.integration.read({ table: "test", view: "Grid view", }) @@ -51,7 +51,7 @@ describe("Airtable Integration", () => { }) it("calls the update method with the correct params", async () => { - const response = await config.integration.update({ + await config.integration.update({ table: "table", id: "123", json: { @@ -68,7 +68,7 @@ describe("Airtable Integration", () => { it("calls the delete method with the correct params", async () => { const ids = [1, 2, 3, 4] - const response = await config.integration.delete({ + await config.integration.delete({ ids, }) expect(config.client.destroy).toHaveBeenCalledWith(ids) diff --git a/packages/server/src/integrations/tests/arangodb.spec.ts b/packages/server/src/integrations/tests/arangodb.spec.ts index c7339299dd..6ac242d8db 100644 --- a/packages/server/src/integrations/tests/arangodb.spec.ts +++ b/packages/server/src/integrations/tests/arangodb.spec.ts @@ -12,7 +12,6 @@ class TestConfiguration { describe("ArangoDB Integration", () => { let config: any - let indexName = "Users" beforeEach(() => { config = new TestConfiguration() @@ -23,7 +22,7 @@ describe("ArangoDB Integration", () => { json: "Hello", } - const response = await config.integration.create(body) + await config.integration.create(body) expect(config.integration.client.query).toHaveBeenCalledWith( `INSERT Hello INTO collection RETURN NEW` ) @@ -33,7 +32,7 @@ describe("ArangoDB Integration", () => { const query = { sql: `test`, } - const response = await config.integration.read(query) + await config.integration.read(query) expect(config.integration.client.query).toHaveBeenCalledWith(query.sql) }) }) diff --git a/packages/server/src/integrations/tests/couchdb.spec.ts b/packages/server/src/integrations/tests/couchdb.spec.ts index 66735d7b74..f27713a81f 100644 --- a/packages/server/src/integrations/tests/couchdb.spec.ts +++ b/packages/server/src/integrations/tests/couchdb.spec.ts @@ -79,7 +79,7 @@ describe("CouchDB Integration", () => { it("calls the delete method with the correct params", async () => { const id = "1234" - const response = await config.integration.delete({ id }) + await config.integration.delete({ id }) expect(config.integration.client.get).toHaveBeenCalledWith(id) expect(config.integration.client.remove).toHaveBeenCalled() }) diff --git a/packages/server/src/integrations/tests/dynamodb.spec.ts b/packages/server/src/integrations/tests/dynamodb.spec.ts index a7a2767469..0215817907 100644 --- a/packages/server/src/integrations/tests/dynamodb.spec.ts +++ b/packages/server/src/integrations/tests/dynamodb.spec.ts @@ -19,7 +19,7 @@ describe("DynamoDB Integration", () => { }) it("calls the create method with the correct params", async () => { - const response = await config.integration.create({ + await config.integration.create({ table: tableName, json: { Name: "John", @@ -66,7 +66,7 @@ describe("DynamoDB Integration", () => { }) it("calls the get method with the correct params", async () => { - const response = await config.integration.get({ + await config.integration.get({ table: tableName, json: { Id: 123, @@ -80,7 +80,7 @@ describe("DynamoDB Integration", () => { }) it("calls the update method with the correct params", async () => { - const response = await config.integration.update({ + await config.integration.update({ table: tableName, json: { Name: "John", @@ -93,7 +93,7 @@ describe("DynamoDB Integration", () => { }) it("calls the delete method with the correct params", async () => { - const response = await config.integration.delete({ + await config.integration.delete({ table: tableName, json: { Name: "John", diff --git a/packages/server/src/integrations/tests/elasticsearch.spec.ts b/packages/server/src/integrations/tests/elasticsearch.spec.ts index e21e8dec69..f8a1dd8013 100644 --- a/packages/server/src/integrations/tests/elasticsearch.spec.ts +++ b/packages/server/src/integrations/tests/elasticsearch.spec.ts @@ -22,7 +22,7 @@ describe("Elasticsearch Integration", () => { const body = { name: "Hello", } - const response = await config.integration.create({ + await config.integration.create({ index: indexName, json: body, }) diff --git a/packages/server/src/integrations/tests/firebase.spec.ts b/packages/server/src/integrations/tests/firebase.spec.ts index f2cea8898f..c5c214d232 100644 --- a/packages/server/src/integrations/tests/firebase.spec.ts +++ b/packages/server/src/integrations/tests/firebase.spec.ts @@ -81,7 +81,7 @@ describe("Firebase Integration", () => { }) it("calls the delete method with the correct params", async () => { - const response = await config.integration.delete({ + await config.integration.delete({ table: tableName, json: { id: "test", diff --git a/packages/server/src/integrations/tests/microsoftSqlServer.spec.ts b/packages/server/src/integrations/tests/microsoftSqlServer.spec.ts deleted file mode 100644 index eaaa79f7c9..0000000000 --- a/packages/server/src/integrations/tests/microsoftSqlServer.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { default as MSSQLIntegration } from "../microsoftSqlServer" - -jest.mock("mssql") - -class TestConfiguration { - integration: any - - constructor(config: any = {}) { - this.integration = new MSSQLIntegration.integration(config) - } -} - -describe("MS SQL Server Integration", () => { - let config: any - - beforeEach(async () => { - config = new TestConfiguration() - }) - - describe("check sql used", () => { - beforeEach(async () => { - await config.integration.connect() - }) - - it("calls the create method with the correct params", async () => { - const sql = "insert into users (name, age) values ('Joe', 123);" - const response = await config.integration.create({ - sql, - }) - expect(config.integration.client.request).toHaveBeenCalledWith() - expect(response[0]).toEqual(sql) - }) - - it("calls the read method with the correct params", async () => { - const sql = "select * from users;" - const response = await config.integration.read({ - sql, - }) - expect(config.integration.client.request).toHaveBeenCalledWith() - expect(response[0]).toEqual(sql) - }) - }) - - describe("no rows returned", () => { - beforeEach(async () => { - await config.integration.connect() - }) - - it("returns the correct response when the create response has no rows", async () => { - const sql = "insert into users (name, age) values ('Joe', 123);" - const response = await config.integration.create({ - sql, - }) - expect(response[0]).toEqual(sql) - }) - }) -}) diff --git a/packages/server/src/integrations/tests/mysql.spec.ts b/packages/server/src/integrations/tests/mysql.spec.ts deleted file mode 100644 index 5180645885..0000000000 --- a/packages/server/src/integrations/tests/mysql.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { default as MySQLIntegration, bindingTypeCoerce } from "../mysql" - -jest.mock("mysql2") - -class TestConfiguration { - integration: any - - constructor(config: any = { ssl: {} }) { - this.integration = new MySQLIntegration.integration(config) - } -} - -describe("MySQL Integration", () => { - let config: any - - beforeEach(() => { - config = new TestConfiguration() - }) - - it("calls the create method with the correct params", async () => { - const sql = "insert into users (name, age) values ('Joe', 123);" - await config.integration.create({ - sql, - }) - expect(config.integration.client.query).toHaveBeenCalledWith(sql, []) - }) - - it("calls the read method with the correct params", async () => { - const sql = "select * from users;" - await config.integration.read({ - sql, - }) - expect(config.integration.client.query).toHaveBeenCalledWith(sql, []) - }) - - it("calls the update method with the correct params", async () => { - const sql = "update table users set name = 'test';" - await config.integration.update({ - sql, - }) - expect(config.integration.client.query).toHaveBeenCalledWith(sql, []) - }) - - it("calls the delete method with the correct params", async () => { - const sql = "delete from users where name = 'todelete';" - await config.integration.delete({ - sql, - }) - expect(config.integration.client.query).toHaveBeenCalledWith(sql, []) - }) - - describe("no rows returned", () => { - it("returns the correct response when the create response has no rows", async () => { - const sql = "insert into users (name, age) values ('Joe', 123);" - const response = await config.integration.create({ - sql, - }) - expect(response).toEqual([{ created: true }]) - }) - - it("returns the correct response when the update response has no rows", async () => { - const sql = "update table users set name = 'test';" - const response = await config.integration.update({ - sql, - }) - expect(response).toEqual([{ updated: true }]) - }) - - it("returns the correct response when the delete response has no rows", async () => { - const sql = "delete from users where name = 'todelete';" - const response = await config.integration.delete({ - sql, - }) - expect(response).toEqual([{ deleted: true }]) - }) - }) - - describe("binding type coerce", () => { - it("ignores non-string types", async () => { - const sql = "select * from users;" - const date = new Date() - await config.integration.read({ - sql, - bindings: [11, date, ["a", "b", "c"], { id: 1 }], - }) - expect(config.integration.client.query).toHaveBeenCalledWith(sql, [ - 11, - date, - ["a", "b", "c"], - { id: 1 }, - ]) - }) - - it("parses strings matching a number regex", async () => { - const sql = "select * from users;" - await config.integration.read({ - sql, - bindings: ["101", "3.14"], - }) - expect(config.integration.client.query).toHaveBeenCalledWith( - sql, - [101, 3.14] - ) - }) - - it("parses strings matching a valid date format", async () => { - const sql = "select * from users;" - await config.integration.read({ - sql, - bindings: [ - "2001-10-30", - "2010-09-01T13:30:59.123Z", - "2021-02-05 12:01 PM", - ], - }) - expect(config.integration.client.query).toHaveBeenCalledWith(sql, [ - new Date("2001-10-30T00:00:00.000Z"), - new Date("2010-09-01T13:30:59.123Z"), - new Date("2021-02-05T12:01:00.000Z"), - ]) - }) - - it("does not parse string matching a valid array of numbers as date", async () => { - const sql = "select * from users;" - await config.integration.read({ - sql, - bindings: ["1,2,2017"], - }) - expect(config.integration.client.query).toHaveBeenCalledWith(sql, [ - "1,2,2017", - ]) - }) - }) -}) - -describe("bindingTypeCoercion", () => { - it("shouldn't coerce something that looks like a date", () => { - const response = bindingTypeCoerce(["202205-1500"]) - expect(response[0]).toBe("202205-1500") - }) - - it("should coerce an actual date", () => { - const date = new Date("2023-06-13T14:24:22.620Z") - const response = bindingTypeCoerce(["2023-06-13T14:24:22.620Z"]) - expect(response[0]).toEqual(date) - }) - - it("should coerce numbers", () => { - const response = bindingTypeCoerce(["0"]) - expect(response[0]).toEqual(0) - }) -}) diff --git a/packages/server/src/integrations/tests/oracle.spec.ts b/packages/server/src/integrations/tests/oracle.spec.ts index 80ad969e3b..7b620d68ad 100644 --- a/packages/server/src/integrations/tests/oracle.spec.ts +++ b/packages/server/src/integrations/tests/oracle.spec.ts @@ -44,7 +44,7 @@ describe("Oracle Integration", () => { it("calls the update method with the correct params", async () => { const sql = "update table users set name = 'test';" - const response = await config.integration.update({ + await config.integration.update({ sql, }) expect(oracledb.executeMock).toHaveBeenCalledWith(sql, [], options) diff --git a/packages/server/src/integrations/tests/postgres.spec.ts b/packages/server/src/integrations/tests/postgres.spec.ts deleted file mode 100644 index 1acdff6bcf..0000000000 --- a/packages/server/src/integrations/tests/postgres.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -const pg = require("pg") - -import { default as PostgresIntegration } from "../postgres" - -jest.mock("pg") - -class TestConfiguration { - integration: any - - constructor(config: any = {}) { - this.integration = new PostgresIntegration.integration(config) - } -} - -describe("Postgres Integration", () => { - let config: any - - beforeEach(() => { - config = new TestConfiguration() - }) - - it("calls the create method with the correct params", async () => { - const sql = "insert into users (name, age) values ('Joe', 123);" - await config.integration.create({ - sql, - }) - expect(pg.queryMock).toHaveBeenCalledWith(sql, []) - }) - - it("calls the read method with the correct params", async () => { - const sql = "select * from users;" - await config.integration.read({ - sql, - }) - expect(pg.queryMock).toHaveBeenCalledWith(sql, []) - }) - - it("calls the update method with the correct params", async () => { - const sql = "update table users set name = 'test';" - const response = await config.integration.update({ - sql, - }) - expect(pg.queryMock).toHaveBeenCalledWith(sql, []) - }) - - it("calls the delete method with the correct params", async () => { - const sql = "delete from users where name = 'todelete';" - await config.integration.delete({ - sql, - }) - expect(pg.queryMock).toHaveBeenCalledWith(sql, []) - }) - - describe("no rows returned", () => { - beforeEach(() => { - pg.queryMock.mockImplementation(() => ({ rows: [] })) - }) - - it("returns the correct response when the create response has no rows", async () => { - const sql = "insert into users (name, age) values ('Joe', 123);" - const response = await config.integration.create({ - sql, - }) - expect(response).toEqual([{ created: true }]) - }) - - it("returns the correct response when the update response has no rows", async () => { - const sql = "update table users set name = 'test';" - const response = await config.integration.update({ - sql, - }) - expect(response).toEqual([{ updated: true }]) - }) - - it("returns the correct response when the delete response has no rows", async () => { - const sql = "delete from users where name = 'todelete';" - const response = await config.integration.delete({ - sql, - }) - expect(response).toEqual([{ deleted: true }]) - }) - }) -}) diff --git a/packages/server/src/integrations/tests/rest.spec.ts b/packages/server/src/integrations/tests/rest.spec.ts index 182a6d3f13..3335f44a7b 100644 --- a/packages/server/src/integrations/tests/rest.spec.ts +++ b/packages/server/src/integrations/tests/rest.spec.ts @@ -70,7 +70,7 @@ describe("REST Integration", () => { Accept: "text/html", }, } - const response = await config.integration.read(query) + await config.integration.read(query) expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, { headers: { Accept: "text/html", @@ -91,7 +91,7 @@ describe("REST Integration", () => { name: "test", }), } - const response = await config.integration.update(query) + await config.integration.update(query) expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, { method: "PUT", body: '{"name":"test"}', @@ -111,7 +111,7 @@ describe("REST Integration", () => { name: "test", }), } - const response = await config.integration.delete(query) + await config.integration.delete(query) expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, { method: "DELETE", headers: HEADERS, diff --git a/packages/server/src/integrations/tests/s3.spec.ts b/packages/server/src/integrations/tests/s3.spec.ts index 40f73928f2..b75340ab20 100644 --- a/packages/server/src/integrations/tests/s3.spec.ts +++ b/packages/server/src/integrations/tests/s3.spec.ts @@ -1,5 +1,3 @@ -const AWS = require("aws-sdk") - import { default as S3Integration } from "../s3" jest.mock("aws-sdk") diff --git a/packages/server/src/integrations/tests/utils/mssql.ts b/packages/server/src/integrations/tests/utils/mssql.ts index f548f0c42c..6bd4290a90 100644 --- a/packages/server/src/integrations/tests/utils/mssql.ts +++ b/packages/server/src/integrations/tests/utils/mssql.ts @@ -41,6 +41,9 @@ export async function datasource(): Promise { port, user: "sa", password: "Password_123", + options: { + encrypt: false, + }, }, } } diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index 91966658a6..f73f522357 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -330,7 +330,7 @@ function copyExistingPropsOver( const existingTableSchema = entities[tableName].schema for (let key in existingTableSchema) { - if (!existingTableSchema.hasOwnProperty(key)) { + if (!Object.prototype.hasOwnProperty.call(existingTableSchema, key)) { continue } const column = existingTableSchema[key] diff --git a/packages/server/src/jsRunner/bundles/index-helpers.ivm.bundle.js b/packages/server/src/jsRunner/bundles/index-helpers.ivm.bundle.js index ff8d7265c6..55af2db6e2 100644 --- a/packages/server/src/jsRunner/bundles/index-helpers.ivm.bundle.js +++ b/packages/server/src/jsRunner/bundles/index-helpers.ivm.bundle.js @@ -1,4 +1,4 @@ -"use strict";var helpers=(()=>{var hn=Object.create;var Se=Object.defineProperty;var pn=Object.getOwnPropertyDescriptor;var mn=Object.getOwnPropertyNames;var gn=Object.getPrototypeOf,yn=Object.prototype.hasOwnProperty;var fe=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var Z=(e,t)=>()=>(e&&(t=e(e=0)),t);var U=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),yt=(e,t)=>{for(var r in t)Se(e,r,{get:t[r],enumerable:!0})},vt=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of mn(t))!yn.call(e,i)&&i!==r&&Se(e,i,{get:()=>t[i],enumerable:!(n=pn(t,i))||n.enumerable});return e};var Ne=(e,t,r)=>(r=e!=null?hn(gn(e)):{},vt(t||!e||!e.__esModule?Se(r,"default",{value:e,enumerable:!0}):r,e)),$t=e=>vt(Se({},"__esModule",{value:!0}),e);var bt=U((qe,Ye)=>{(function(e,t){typeof qe=="object"&&typeof Ye<"u"?Ye.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs=t()})(qe,function(){"use strict";var e=1e3,t=6e4,r=36e5,n="millisecond",i="second",u="minute",s="hour",f="day",w="week",g="month",l="quarter",x="year",v="date",a="Invalid Date",T=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,E=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,I={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function($){var h=["th","st","nd","rd"],c=$%100;return"["+$+(h[(c-20)%10]||h[c]||h[0])+"]"}},Y=function($,h,c){var b=String($);return!b||b.length>=h?$:""+Array(h+1-b.length).join(c)+$},F={s:Y,z:function($){var h=-$.utcOffset(),c=Math.abs(h),b=Math.floor(c/60),d=c%60;return(h<=0?"+":"-")+Y(b,2,"0")+":"+Y(d,2,"0")},m:function $(h,c){if(h.date()1)return $(j[0])}else{var _=h.name;S[_]=h,d=_}return!b&&d&&(q=d),d||!b&&q},N=function($,h){if(o($))return $.clone();var c=typeof h=="object"?h:{};return c.date=$,c.args=arguments,new B(c)},O=F;O.l=D,O.i=o,O.w=function($,h){return N($,{locale:h.$L,utc:h.$u,x:h.$x,$offset:h.$offset})};var B=function(){function $(c){this.$L=D(c.locale,null,!0),this.parse(c),this.$x=this.$x||c.x||{},this[p]=!0}var h=$.prototype;return h.parse=function(c){this.$d=function(b){var d=b.date,M=b.utc;if(d===null)return new Date(NaN);if(O.u(d))return new Date;if(d instanceof Date)return new Date(d);if(typeof d=="string"&&!/Z$/i.test(d)){var j=d.match(T);if(j){var _=j[2]-1||0,H=(j[7]||"0").substring(0,3);return M?new Date(Date.UTC(j[1],_,j[3]||1,j[4]||0,j[5]||0,j[6]||0,H)):new Date(j[1],_,j[3]||1,j[4]||0,j[5]||0,j[6]||0,H)}}return new Date(d)}(c),this.init()},h.init=function(){var c=this.$d;this.$y=c.getFullYear(),this.$M=c.getMonth(),this.$D=c.getDate(),this.$W=c.getDay(),this.$H=c.getHours(),this.$m=c.getMinutes(),this.$s=c.getSeconds(),this.$ms=c.getMilliseconds()},h.$utils=function(){return O},h.isValid=function(){return this.$d.toString()!==a},h.isSame=function(c,b){var d=N(c);return this.startOf(b)<=d&&d<=this.endOf(b)},h.isAfter=function(c,b){return N(c){(function(e,t){typeof Ce=="object"&&typeof Ee<"u"?Ee.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_duration=t()})(Ce,function(){"use strict";var e,t,r=1e3,n=6e4,i=36e5,u=864e5,s=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,f=31536e6,w=2628e6,g=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/,l={years:f,months:w,days:u,hours:i,minutes:n,seconds:r,milliseconds:1,weeks:6048e5},x=function(S){return S instanceof F},v=function(S,p,o){return new F(S,o,p.$l)},a=function(S){return t.p(S)+"s"},T=function(S){return S<0},E=function(S){return T(S)?Math.ceil(S):Math.floor(S)},I=function(S){return Math.abs(S)},Y=function(S,p){return S?T(S)?{negative:!0,format:""+I(S)+p}:{negative:!1,format:""+S+p}:{negative:!1,format:""}},F=function(){function S(o,D,N){var O=this;if(this.$d={},this.$l=N,o===void 0&&(this.$ms=0,this.parseFromMilliseconds()),D)return v(o*l[a(D)],this);if(typeof o=="number")return this.$ms=o,this.parseFromMilliseconds(),this;if(typeof o=="object")return Object.keys(o).forEach(function($){O.$d[a($)]=o[$]}),this.calMilliseconds(),this;if(typeof o=="string"){var B=o.match(g);if(B){var J=B.slice(2).map(function($){return $!=null?Number($):0});return this.$d.years=J[0],this.$d.months=J[1],this.$d.weeks=J[2],this.$d.days=J[3],this.$d.hours=J[4],this.$d.minutes=J[5],this.$d.seconds=J[6],this.calMilliseconds(),this}}return this}var p=S.prototype;return p.calMilliseconds=function(){var o=this;this.$ms=Object.keys(this.$d).reduce(function(D,N){return D+(o.$d[N]||0)*l[N]},0)},p.parseFromMilliseconds=function(){var o=this.$ms;this.$d.years=E(o/f),o%=f,this.$d.months=E(o/w),o%=w,this.$d.days=E(o/u),o%=u,this.$d.hours=E(o/i),o%=i,this.$d.minutes=E(o/n),o%=n,this.$d.seconds=E(o/r),o%=r,this.$d.milliseconds=o},p.toISOString=function(){var o=Y(this.$d.years,"Y"),D=Y(this.$d.months,"M"),N=+this.$d.days||0;this.$d.weeks&&(N+=7*this.$d.weeks);var O=Y(N,"D"),B=Y(this.$d.hours,"H"),J=Y(this.$d.minutes,"M"),$=this.$d.seconds||0;this.$d.milliseconds&&($+=this.$d.milliseconds/1e3,$=Math.round(1e3*$)/1e3);var h=Y($,"S"),c=o.negative||D.negative||O.negative||B.negative||J.negative||h.negative,b=B.format||J.format||h.format?"T":"",d=(c?"-":"")+"P"+o.format+D.format+O.format+b+B.format+J.format+h.format;return d==="P"||d==="-P"?"P0D":d},p.toJSON=function(){return this.toISOString()},p.format=function(o){var D=o||"YYYY-MM-DDTHH:mm:ss",N={Y:this.$d.years,YY:t.s(this.$d.years,2,"0"),YYYY:t.s(this.$d.years,4,"0"),M:this.$d.months,MM:t.s(this.$d.months,2,"0"),D:this.$d.days,DD:t.s(this.$d.days,2,"0"),H:this.$d.hours,HH:t.s(this.$d.hours,2,"0"),m:this.$d.minutes,mm:t.s(this.$d.minutes,2,"0"),s:this.$d.seconds,ss:t.s(this.$d.seconds,2,"0"),SSS:t.s(this.$d.milliseconds,3,"0")};return D.replace(s,function(O,B){return B||String(N[O])})},p.as=function(o){return this.$ms/l[a(o)]},p.get=function(o){var D=this.$ms,N=a(o);return N==="milliseconds"?D%=1e3:D=N==="weeks"?E(D/l[N]):this.$d[N],D||0},p.add=function(o,D,N){var O;return O=D?o*l[a(D)]:x(o)?o.$ms:v(o,this).$ms,v(this.$ms+O*(N?-1:1),this)},p.subtract=function(o,D){return this.add(o,D,!0)},p.locale=function(o){var D=this.clone();return D.$l=o,D},p.clone=function(){return v(this.$ms,this)},p.humanize=function(o){return e().add(this.$ms,"ms").locale(this.$l).fromNow(!o)},p.valueOf=function(){return this.asMilliseconds()},p.milliseconds=function(){return this.get("milliseconds")},p.asMilliseconds=function(){return this.as("milliseconds")},p.seconds=function(){return this.get("seconds")},p.asSeconds=function(){return this.as("seconds")},p.minutes=function(){return this.get("minutes")},p.asMinutes=function(){return this.as("minutes")},p.hours=function(){return this.get("hours")},p.asHours=function(){return this.as("hours")},p.days=function(){return this.get("days")},p.asDays=function(){return this.as("days")},p.weeks=function(){return this.get("weeks")},p.asWeeks=function(){return this.as("weeks")},p.months=function(){return this.get("months")},p.asMonths=function(){return this.as("months")},p.years=function(){return this.get("years")},p.asYears=function(){return this.as("years")},S}(),q=function(S,p,o){return S.add(p.years()*o,"y").add(p.months()*o,"M").add(p.days()*o,"d").add(p.hours()*o,"h").add(p.minutes()*o,"m").add(p.seconds()*o,"s").add(p.milliseconds()*o,"ms")};return function(S,p,o){e=o,t=o().$utils(),o.duration=function(O,B){var J=o.locale();return v(O,{$l:J},B)},o.isDuration=x;var D=p.prototype.add,N=p.prototype.subtract;p.prototype.add=function(O,B){return x(O)?q(this,O,1):D.bind(this)(O,B)},p.prototype.subtract=function(O,B){return x(O)?q(this,O,-1):N.bind(this)(O,B)}}})});var Ot=U((Ie,_e)=>{(function(e,t){typeof Ie=="object"&&typeof _e<"u"?_e.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_advancedFormat=t()})(Ie,function(){"use strict";return function(e,t){var r=t.prototype,n=r.format;r.format=function(i){var u=this,s=this.$locale();if(!this.isValid())return n.bind(this)(i);var f=this.$utils(),w=(i||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(g){switch(g){case"Q":return Math.ceil((u.$M+1)/3);case"Do":return s.ordinal(u.$D);case"gggg":return u.weekYear();case"GGGG":return u.isoWeekYear();case"wo":return s.ordinal(u.week(),"W");case"w":case"ww":return f.s(u.week(),g==="w"?1:2,"0");case"W":case"WW":return f.s(u.isoWeek(),g==="W"?1:2,"0");case"k":case"kk":return f.s(String(u.$H===0?24:u.$H),g==="k"?1:2,"0");case"X":return Math.floor(u.$d.getTime()/1e3);case"x":return u.$d.getTime();case"z":return"["+u.offsetName()+"]";case"zzz":return"["+u.offsetName("long")+"]";default:return g}});return n.bind(this)(w)}}})});var xt=U((We,Fe)=>{(function(e,t){typeof We=="object"&&typeof Fe<"u"?Fe.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_isoWeek=t()})(We,function(){"use strict";var e="day";return function(t,r,n){var i=function(f){return f.add(4-f.isoWeekday(),e)},u=r.prototype;u.isoWeekYear=function(){return i(this).year()},u.isoWeek=function(f){if(!this.$utils().u(f))return this.add(7*(f-this.isoWeek()),e);var w,g,l,x,v=i(this),a=(w=this.isoWeekYear(),g=this.$u,l=(g?n.utc:n)().year(w).startOf("year"),x=4-l.isoWeekday(),l.isoWeekday()>4&&(x+=7),l.add(x,e));return v.diff(a,"week")+1},u.isoWeekday=function(f){return this.$utils().u(f)?this.day()||7:this.day(this.day()%7?f:f-7)};var s=u.startOf;u.startOf=function(f,w){var g=this.$utils(),l=!!g.u(w)||w;return g.p(f)==="isoweek"?l?this.date(this.date()-(this.isoWeekday()-1)).startOf("day"):this.date(this.date()-1-(this.isoWeekday()-1)+7).endOf("day"):s.bind(this)(f,w)}}})});var St=U((He,Be)=>{(function(e,t){typeof He=="object"&&typeof Be<"u"?Be.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_weekYear=t()})(He,function(){"use strict";return function(e,t){t.prototype.weekYear=function(){var r=this.month(),n=this.week(),i=this.year();return n===1&&r===11?i+1:r===0&&n>=52?i-1:i}}})});var Nt=U((Le,ze)=>{(function(e,t){typeof Le=="object"&&typeof ze<"u"?ze.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_weekOfYear=t()})(Le,function(){"use strict";var e="week",t="year";return function(r,n,i){var u=n.prototype;u.week=function(s){if(s===void 0&&(s=null),s!==null)return this.add(7*(s-this.week()),"day");var f=this.$locale().yearStart||1;if(this.month()===11&&this.date()>25){var w=i(this).startOf(t).add(1,t).date(f),g=i(this).endOf(e);if(w.isBefore(g))return 1}var l=i(this).startOf(t).date(f).startOf(e).subtract(1,"millisecond"),x=this.diff(l,e,!0);return x<0?i(this).startOf("week").week():Math.ceil(x)},u.weeks=function(s){return s===void 0&&(s=null),this.week(s)}}})});var Mt=U((Pe,Re)=>{(function(e,t){typeof Pe=="object"&&typeof Re<"u"?Re.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_relativeTime=t()})(Pe,function(){"use strict";return function(e,t,r){e=e||{};var n=t.prototype,i={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function u(f,w,g,l){return n.fromToBase(f,w,g,l)}r.en.relativeTime=i,n.fromToBase=function(f,w,g,l,x){for(var v,a,T,E=g.$locale().relativeTime||i,I=e.thresholds||[{l:"s",r:44,d:"second"},{l:"m",r:89},{l:"mm",r:44,d:"minute"},{l:"h",r:89},{l:"hh",r:21,d:"hour"},{l:"d",r:35},{l:"dd",r:25,d:"day"},{l:"M",r:45},{l:"MM",r:10,d:"month"},{l:"y",r:17},{l:"yy",d:"year"}],Y=I.length,F=0;F0,S<=q.r||!q.r){S<=1&&F>0&&(q=I[F-1]);var p=E[q.l];x&&(S=x(""+S)),a=typeof p=="string"?p.replace("%d",S):p(S,w,q.l,T);break}}if(w)return a;var o=T?E.future:E.past;return typeof o=="function"?o(a):o.replace("%s",a)},n.to=function(f,w){return u(f,w,this,!0)},n.from=function(f,w){return u(f,w,this)};var s=function(f){return f.$u?r.utc():r()};n.toNow=function(f){return this.to(s(this),f)},n.fromNow=function(f){return this.from(s(this),f)}}})});var jt=U((Je,Ge)=>{(function(e,t){typeof Je=="object"&&typeof Ge<"u"?Ge.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_utc=t()})(Je,function(){"use strict";var e="minute",t=/[+-]\d\d(?::?\d\d)?/g,r=/([+-]|\d\d)/g;return function(n,i,u){var s=i.prototype;u.utc=function(a){var T={date:a,utc:!0,args:arguments};return new i(T)},s.utc=function(a){var T=u(this.toDate(),{locale:this.$L,utc:!0});return a?T.add(this.utcOffset(),e):T},s.local=function(){return u(this.toDate(),{locale:this.$L,utc:!1})};var f=s.parse;s.parse=function(a){a.utc&&(this.$u=!0),this.$utils().u(a.$offset)||(this.$offset=a.$offset),f.call(this,a)};var w=s.init;s.init=function(){if(this.$u){var a=this.$d;this.$y=a.getUTCFullYear(),this.$M=a.getUTCMonth(),this.$D=a.getUTCDate(),this.$W=a.getUTCDay(),this.$H=a.getUTCHours(),this.$m=a.getUTCMinutes(),this.$s=a.getUTCSeconds(),this.$ms=a.getUTCMilliseconds()}else w.call(this)};var g=s.utcOffset;s.utcOffset=function(a,T){var E=this.$utils().u;if(E(a))return this.$u?0:E(this.$offset)?g.call(this):this.$offset;if(typeof a=="string"&&(a=function(q){q===void 0&&(q="");var S=q.match(t);if(!S)return null;var p=(""+S[0]).match(r)||["-",0,0],o=p[0],D=60*+p[1]+ +p[2];return D===0?0:o==="+"?D:-D}(a),a===null))return this;var I=Math.abs(a)<=16?60*a:a,Y=this;if(T)return Y.$offset=I,Y.$u=a===0,Y;if(a!==0){var F=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(Y=this.local().add(I+F,e)).$offset=I,Y.$x.$localOffset=F}else Y=this.utc();return Y};var l=s.format;s.format=function(a){var T=a||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return l.call(this,T)},s.valueOf=function(){var a=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*a},s.isUTC=function(){return!!this.$u},s.toISOString=function(){return this.toDate().toISOString()},s.toString=function(){return this.toDate().toUTCString()};var x=s.toDate;s.toDate=function(a){return a==="s"&&this.$offset?u(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():x.call(this)};var v=s.diff;s.diff=function(a,T,E){if(a&&this.$u===a.$u)return v.call(this,a,T,E);var I=this.local(),Y=u(a).local();return v.call(I,Y,T,E)}}})});var At=U((Ze,Ve)=>{(function(e,t){typeof Ze=="object"&&typeof Ve<"u"?Ve.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_timezone=t()})(Ze,function(){"use strict";var e={year:0,month:1,day:2,hour:3,minute:4,second:5},t={};return function(r,n,i){var u,s=function(l,x,v){v===void 0&&(v={});var a=new Date(l),T=function(E,I){I===void 0&&(I={});var Y=I.timeZoneName||"short",F=E+"|"+Y,q=t[F];return q||(q=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:E,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:Y}),t[F]=q),q}(x,v);return T.formatToParts(a)},f=function(l,x){for(var v=s(l,x),a=[],T=0;T=0&&(a[F]=parseInt(Y,10))}var q=a[3],S=q===24?0:q,p=a[0]+"-"+a[1]+"-"+a[2]+" "+S+":"+a[4]+":"+a[5]+":000",o=+l;return(i.utc(p).valueOf()-(o-=o%1e3))/6e4},w=n.prototype;w.tz=function(l,x){l===void 0&&(l=u);var v=this.utcOffset(),a=this.toDate(),T=a.toLocaleString("en-US",{timeZone:l}),E=Math.round((a-new Date(T))/1e3/60),I=i(T,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-E,!0);if(x){var Y=I.utcOffset();I=I.add(v-Y,"minute")}return I.$x.$timezone=l,I},w.offsetName=function(l){var x=this.$x.$timezone||i.tz.guess(),v=s(this.valueOf(),x,{timeZoneName:l}).find(function(a){return a.type.toLowerCase()==="timezonename"});return v&&v.value};var g=w.startOf;w.startOf=function(l,x){if(!this.$x||!this.$x.$timezone)return g.call(this,l,x);var v=i(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return g.call(v,l,x).tz(this.$x.$timezone,!0)},i.tz=function(l,x,v){var a=v&&x,T=v||x||u,E=f(+i(),T);if(typeof l!="string")return i(l).tz(T);var I=function(S,p,o){var D=S-60*p*1e3,N=f(D,o);if(p===N)return[D,p];var O=f(D-=60*(N-p)*1e3,o);return N===O?[D,N]:[S-60*Math.min(N,O)*1e3,Math.max(N,O)]}(i.utc(l,a).valueOf(),E,T),Y=I[0],F=I[1],q=i(Y).utcOffset(F);return q.$x.$timezone=T,q},i.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},i.tz.setDefault=function(l){u=l}}})});var Dt=U((Ui,et)=>{var X=bt();X.extend(wt());X.extend(Ot());X.extend(xt());X.extend(St());X.extend(Nt());X.extend(Mt());X.extend(jt());X.extend(At());function oe(e){return typeof e=="object"&&typeof e.hash=="object"}function Qe(e){return typeof e=="object"&&typeof e.options=="object"&&typeof e.app=="object"}function Xe(e,t,r){if(oe(e))return Xe({},t,e);if(oe(t))return Xe(e,r,t);let n=Qe(e)?e.context:{};r=r||{},oe(r)||(t=Object.assign({},t,r)),oe(r)&&r.hash.root===!0&&(t=Object.assign({},r.data.root,t));let i=Object.assign({},n,t,r.hash);return Qe(e)||(i=Object.assign({},e,i)),Qe(e)&&e.view&&e.view.data&&(i=Object.assign({},i,e.view.data)),i}function Ke(e,t,r){return oe(t)&&(r=t,t=null),oe(e)&&(r=e,t=null,e=null),{str:e,pattern:t,options:r}}function kt(e,t,r){let n=Ke(e,t,r),i={lang:"en",date:new Date(n.str)},u=Xe(this,i,{});X.locale(u.lang||u.language)}et.exports.date=(e,t,r)=>{let n=Ke(e,t,r);if(n.str==null&&n.pattern==null)return X.locale("en"),X().format("MMMM DD, YYYY");kt(n.str,n.pattern,n.options);let i=X(new Date(n.str));return typeof n.options=="string"?i=n.options.toLowerCase()==="utc"?i.utc():i.tz(n.options):i=i.tz(X.tz.guess()),n.pattern===""?i.toISOString():i.format(n.pattern)};et.exports.duration=(e,t,r)=>{let n=Ke(e,t);kt(n.str,n.pattern);let i=X.duration(n.str,n.pattern);return r&&!oe(r)?i.format(r):i.humanize()}});var tt=U((Ti,Tt)=>{Tt.exports=function(e){return e!=null&&(Ut(e)||vn(e)||!!e._isBuffer)};function Ut(e){return!!e.constructor&&typeof e.constructor.isBuffer=="function"&&e.constructor.isBuffer(e)}function vn(e){return typeof e.readFloatLE=="function"&&typeof e.slice=="function"&&Ut(e.slice(0,0))}});var Yt=U((qi,qt)=>{var $n=tt(),bn=Object.prototype.toString;qt.exports=function(t){if(typeof t>"u")return"undefined";if(t===null)return"null";if(t===!0||t===!1||t instanceof Boolean)return"boolean";if(typeof t=="string"||t instanceof String)return"string";if(typeof t=="number"||t instanceof Number)return"number";if(typeof t=="function"||t instanceof Function)return"function";if(typeof Array.isArray<"u"&&Array.isArray(t))return"array";if(t instanceof RegExp)return"regexp";if(t instanceof Date)return"date";var r=bn.call(t);return r==="[object RegExp]"?"regexp":r==="[object Date]"?"date":r==="[object Arguments]"?"arguments":r==="[object Error]"?"error":$n(t)?"buffer":r==="[object Set]"?"set":r==="[object WeakSet]"?"weakset":r==="[object Map]"?"map":r==="[object WeakMap]"?"weakmap":r==="[object Symbol]"?"symbol":r==="[object Int8Array]"?"int8array":r==="[object Uint8Array]"?"uint8array":r==="[object Uint8ClampedArray]"?"uint8clampedarray":r==="[object Int16Array]"?"int16array":r==="[object Uint16Array]"?"uint16array":r==="[object Int32Array]"?"int32array":r==="[object Uint32Array]"?"uint32array":r==="[object Float32Array]"?"float32array":r==="[object Float64Array]"?"float64array":"object"}});var _t=U((Yi,It)=>{"use strict";var Ct=Yt(),Et={arguments:"an arguments object",array:"an array",boolean:"a boolean",buffer:"a buffer",date:"a date",error:"an error",float32array:"a float32array",float64array:"a float64array",function:"a function",int16array:"an int16array",int32array:"an int32array",int8array:"an int8array",map:"a Map",null:"null",number:"a number",object:"an object",regexp:"a regular expression",set:"a Set",string:"a string",symbol:"a symbol",uint16array:"an uint16array",uint32array:"an uint32array",uint8array:"an uint8array",uint8clampedarray:"an uint8clampedarray",undefined:"undefined",weakmap:"a WeakMap",weakset:"a WeakSet"};function rt(e){return Et[Ct(e)]}rt.types=Et;rt.typeOf=Ct;It.exports=rt});var Me=U((Ci,Ft)=>{var wn=Object.prototype.toString;Ft.exports=function(t){if(t===void 0)return"undefined";if(t===null)return"null";var r=typeof t;if(r==="boolean")return"boolean";if(r==="string")return"string";if(r==="number")return"number";if(r==="symbol")return"symbol";if(r==="function")return Mn(t)?"generatorfunction":"function";if(On(t))return"array";if(kn(t))return"buffer";if(An(t))return"arguments";if(Sn(t))return"date";if(xn(t))return"error";if(Nn(t))return"regexp";switch(Wt(t)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(jn(t))return"generator";switch(r=wn.call(t),r){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return r.slice(8,-1).toLowerCase().replace(/\s/g,"")};function Wt(e){return typeof e.constructor=="function"?e.constructor.name:null}function On(e){return Array.isArray?Array.isArray(e):e instanceof Array}function xn(e){return e instanceof Error||typeof e.message=="string"&&e.constructor&&typeof e.constructor.stackTraceLimit=="number"}function Sn(e){return e instanceof Date?!0:typeof e.toDateString=="function"&&typeof e.getDate=="function"&&typeof e.setDate=="function"}function Nn(e){return e instanceof RegExp?!0:typeof e.flags=="string"&&typeof e.ignoreCase=="boolean"&&typeof e.multiline=="boolean"&&typeof e.global=="boolean"}function Mn(e,t){return Wt(e)==="GeneratorFunction"}function jn(e){return typeof e.throw=="function"&&typeof e.return=="function"&&typeof e.next=="function"}function An(e){try{if(typeof e.length=="number"&&typeof e.callee=="function")return!0}catch(t){if(t.message.indexOf("callee")!==-1)return!0}return!1}function kn(e){return e.constructor&&typeof e.constructor.isBuffer=="function"?e.constructor.isBuffer(e):!1}});var ee=U((Lt,zt)=>{"use strict";var Dn=fe("util"),Ht=_t(),Un=Me(),m=Lt=zt.exports;m.extend=Bt;m.indexOf=In;m.escapeExpression=_n;m.isEmpty=Bn;m.createFrame=Wn;m.blockParams=Fn;m.appendContextPath=Hn;var Tn={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"="},qn=/[&<>"'`=]/g,Yn=/[&<>"'`=]/;function Cn(e){return Tn[e]}function Bt(e){for(var t=1;t{"use strict";var Ln=ee();pe.contains=function(e,t,r){return e==null||t==null||isNaN(e.length)?!1:e.indexOf(t,r)!==-1};pe.chop=function(e){if(typeof e!="string")return"";var t=/^[-_.\W\s]+|[-_.\W\s]+$/g;return e.trim().replace(t,"")};pe.changecase=function(e,t){if(typeof e!="string")return"";if(e.length===1)return e.toLowerCase();e=pe.chop(e).toLowerCase(),typeof t!="function"&&(t=Ln.identity);var r=/[-_.\W\s]+(\w|$)/g;return e.replace(r,function(n,i){return t(i)})};pe.random=function(e,t){return e+Math.floor(Math.random()*(t-e+1))}});var Rt=U((Ii,Pt)=>{"use strict";var zn=je(),V=Pt.exports;V.abs=function(e){if(isNaN(e))throw new TypeError("expected a number");return Math.abs(e)};V.add=function(e,t){return!isNaN(e)&&!isNaN(t)?Number(e)+Number(t):typeof e=="string"&&typeof t=="string"?e+t:""};V.avg=function(){let e=[].concat.apply([],arguments);return e.pop(),V.sum(e)/e.length};V.ceil=function(e){if(isNaN(e))throw new TypeError("expected a number");return Math.ceil(e)};V.divide=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)/Number(t)};V.floor=function(e){if(isNaN(e))throw new TypeError("expected a number");return Math.floor(e)};V.minus=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)-Number(t)};V.modulo=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)%Number(t)};V.multiply=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)*Number(t)};V.plus=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)+Number(t)};V.random=function(e,t){if(isNaN(e))throw new TypeError("expected minimum to be a number");if(isNaN(t))throw new TypeError("expected maximum to be a number");return zn.random(e,t)};V.remainder=function(e,t){return e%t};V.round=function(e){if(isNaN(e))throw new TypeError("expected a number");return Math.round(e)};V.subtract=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)-Number(t)};V.sum=function(){for(var e=[].concat.apply([],arguments),t=e.length,r=0;t--;)isNaN(e[t])||(r+=Number(e[t]));return r}});var Gt=U((_i,Jt)=>{"use strict";Jt.exports=function(t){return t!=null&&typeof t=="object"&&Array.isArray(t)===!1}});var Ae=U((Wi,Xt)=>{var Qt=Gt();Xt.exports=function(e,t,r){if(Qt(r)||(r={default:r}),!Vt(e))return typeof r.default<"u"?r.default:e;typeof t=="number"&&(t=String(t));let n=Array.isArray(t),i=typeof t=="string",u=r.separator||".",s=r.joinChar||(typeof u=="string"?u:".");if(!i&&!n)return e;if(i&&t in e)return ut(t,e,r)?e[t]:r.default;let f=n?t:Pn(t,u,r),w=f.length,g=0;do{let l=f[g];for(typeof l=="number"&&(l=String(l));l&&l.slice(-1)==="\\";)l=Zt([l.slice(0,-1),f[++g]||""],s,r);if(l in e){if(!ut(l,e,r))return r.default;e=e[l]}else{let x=!1,v=g+1;for(;v{"use strict";Kt.exports=function(t){if(typeof t!="object")throw new TypeError("createFrame expects data to be an object");var r=Object.assign({},t);if(r._parent=t,r.extend=function(s){Object.assign(this,s)},arguments.length>1)for(var n=[].slice.call(arguments,1),i=n.length,u=-1;++u{"use strict";var y=ee(),C=er.exports,ae=Ae(),Rn=st();C.after=function(e,t){return y.isUndefined(e)?"":(e=y.result(e),Array.isArray(e)?e.slice(t):"")};C.arrayify=function(e){return y.isUndefined(e)?[]:e?Array.isArray(e)?e:[e]:[]};C.before=function(e,t){return y.isUndefined(e)?"":(e=y.result(e),Array.isArray(e)?e.slice(0,t-1):"")};C.eachIndex=function(e,t){var r="";if(y.isUndefined(e))return"";if(e=y.result(e),Array.isArray(e))for(var n=0;n0){for(var s=0;s-1,this,r):"")};C.isArray=function(e){return Array.isArray(e)};C.itemAt=function(e,t){if(y.isUndefined(e))return null;if(e=y.result(e),Array.isArray(e)){if(t=isNaN(t)?0:+t,t<0)return e[e.length+t];if(ti[t]>u[t]?1:-1)}return""};C.withAfter=function(e,t,r){if(y.isUndefined(e))return"";if(e=y.result(e),Array.isArray(e)){e=e.slice(t);for(var n="",i=0;i0){for(var i=[],u=0;u0&&u%t===0&&(n+=r.fn(i),i=[]),i.push(e[u]);n+=r.fn(i)}return n};C.withLast=function(e,t,r){if(y.isUndefined(e))return"";if(e=y.result(e),Array.isArray(e)){if(y.isUndefined(t)||(t=parseFloat(y.result(t))),y.isUndefined(t))return r=t,r.fn(e[e.length-1]);e=e.slice(-t);for(var n=e.length,i=-1,u="";++ig?1:w{"use strict";var tr=ee(),te=rr.exports;te.bytes=function(e,t,r){if(e==null||isNaN(e)&&(e=e.length,!e))return"0 B";isNaN(t)&&(t=2);var n=["B","kB","MB","GB","TB","PB","EB","ZB","YB"];t=Math.pow(10,t),e=Number(e);for(var i=n.length-1;i-->=0;){var u=Math.pow(10,i*3);if(u<=e+1){e=Math.round(e*t/u)/t,e+=" "+n[i];break}}return e};te.addCommas=function(e){return e.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g,"$1,")};te.phoneNumber=function(e){return e=e.toString(),"("+e.substr(0,3)+") "+e.substr(3,3)+"-"+e.substr(6,4)};te.toAbbr=function(e,t){isNaN(e)&&(e=0),tr.isUndefined(t)&&(t=2),e=Number(e),t=Math.pow(10,t);for(var r=["k","m","b","t","q"],n=r.length-1;n>=0;){var i=Math.pow(10,(n+1)*3);if(i<=e+1){e=Math.round(e*t/i)/t,e+=r[n];break}n--}return e};te.toExponential=function(e,t){return isNaN(e)&&(e=0),tr.isUndefined(t)&&(t=0),Number(e).toExponential(t)};te.toFixed=function(e,t){return isNaN(e)&&(e=0),isNaN(t)&&(t=0),Number(e).toFixed(t)};te.toFloat=function(e){return parseFloat(e)};te.toInt=function(e){return parseInt(e,10)};te.toPrecision=function(e,t){return isNaN(e)&&(e=0),isNaN(t)&&(t=1),Number(e).toPrecision(t)}});var ur=U((Li,ir)=>{"use strict";var ft=fe("url"),ye=ee(),Jn=fe("querystring"),ce=ir.exports;ce.encodeURI=function(e){if(ye.isString(e))return encodeURIComponent(e)};ce.escape=function(e){if(ye.isString(e))return Jn.escape(e)};ce.decodeURI=function(e){if(ye.isString(e))return decodeURIComponent(e)};ce.urlResolve=function(e,t){return ft.resolve(e,t)};ce.urlParse=function(e){return ft.parse(e)};ce.stripQuerystring=function(e){if(ye.isString(e))return e.split("?")[0]};ce.stripProtocol=function(e){if(ye.isString(e)){var t=ft.parse(e);return t.protocol="",t.format()}}});var or=U((sr,fr)=>{"use strict";var z=ee(),le=je(),A=fr.exports;A.append=function(e,t){return typeof e=="string"&&typeof t=="string"?e+t:e};A.camelcase=function(e){return typeof e!="string"?"":le.changecase(e,function(t){return t.toUpperCase()})};A.capitalize=function(e){return typeof e!="string"?"":e.charAt(0).toUpperCase()+e.slice(1)};A.capitalizeAll=function(e){if(typeof e!="string")return"";if(z.isString(e))return e.replace(/\w\S*/g,function(t){return A.capitalize(t)})};A.center=function(e,t){if(typeof e!="string")return"";for(var r="",n=0;n-1;)i++,n+=r;return i};A.pascalcase=function(e){return typeof e!="string"?"":(e=le.changecase(e,function(t){return t.toUpperCase()}),e.charAt(0).toUpperCase()+e.slice(1))};A.pathcase=function(e){return typeof e!="string"?"":le.changecase(e,function(t){return"/"+t})};A.plusify=function(e,t){return typeof e!="string"?"":(z.isString(t)||(t=" "),e.split(t).join("+"))};A.prepend=function(e,t){return typeof e=="string"&&typeof t=="string"?t+e:e};A.raw=function(e){var t=e.fn(),r=z.options(this,e);if(r.escape!==!1)for(var n=0;(n=t.indexOf("{{",n))!==-1;)t[n-1]!=="\\"&&(t=t.slice(0,n)+"\\"+t.slice(n)),n+=3;return t};A.remove=function(e,t){return typeof e!="string"?"":z.isString(t)?e.split(t).join(""):e};A.removeFirst=function(e,t){return typeof e!="string"?"":z.isString(t)?e.replace(t,""):e};A.replace=function(e,t,r){return typeof e!="string"?"":z.isString(t)?(z.isString(r)||(r=""),e.split(t).join(r)):e};A.replaceFirst=function(e,t,r){return typeof e!="string"?"":z.isString(t)?(z.isString(r)||(r=""),e.replace(t,r)):e};A.reverse=ke().reverse;A.sentence=function(e){return typeof e!="string"?"":e.replace(/((?:\S[^\.\?\!]*)[\.\?\!]*)/g,function(t){return t.charAt(0).toUpperCase()+t.substr(1).toLowerCase()})};A.snakecase=function(e){return typeof e!="string"?"":le.changecase(e,function(t){return"_"+t})};A.split=function(e,t){return typeof e!="string"?"":(z.isString(t)||(t=","),e.split(t))};A.startsWith=function(e,t,r){var n=[].slice.call(arguments);return r=n.pop(),z.isString(t)&&t.indexOf(e)===0?r.fn(this):typeof r.inverse=="function"?r.inverse(this):""};A.titleize=function(e){if(typeof e!="string")return"";for(var t=e.replace(/[- _]+/g," "),r=t.split(" "),n=r.length,i=[],u=0;n--;){var s=r[u++];i.push(sr.capitalize(s))}return i.join(" ")};A.trim=function(e){return typeof e=="string"?e.trim():""};A.trimLeft=function(e){if(z.isString(e))return e.replace(/^\s+/,"")};A.trimRight=function(e){if(z.isString(e))return e.replace(/\s+$/,"")};A.truncate=function(e,t,r){if(z.isString(e))return typeof r!="string"&&(r=""),e.length>t?e.slice(0,t-r.length)+r:e};A.truncateWords=function(e,t,r){if(z.isString(e)&&!isNaN(t)){typeof r!="string"&&(r="\u2026");var n=Number(t),i=e.split(/[ \t]/);if(n>=i.length)return e;i=i.slice(0,n);var u=i.join(" ").trim();return u+r}};A.upcase=function(){return A.uppercase.apply(this,arguments)};A.uppercase=function(e){return z.isObject(e)&&e.fn?e.fn(this).toUpperCase():typeof e!="string"?"":e.toUpperCase()}});var cr=U((zi,ar)=>{"use strict";var Gn=Me();ar.exports=function e(t){switch(Gn(t)){case"boolean":case"date":case"function":case"null":case"number":return!0;case"undefined":return!1;case"regexp":return t.source!=="(?:)"&&t.source!=="";case"buffer":return t.toString()!=="";case"error":return t.message!=="";case"string":case"arguments":return t.length!==0;case"file":case"map":case"set":return t.size!==0;case"array":case"object":for(let r of Object.keys(t))if(e(t[r]))return!0;return!1;default:return!0}}});var dr=U((Pi,lr)=>{"use strict";var Zn=Ae(),Vn=cr();lr.exports=function(e,t,r){return Qn(e)&&(typeof t=="string"||Array.isArray(t))?Vn(Zn(e,t,r)):!1};function Qn(e){return e!=null&&(typeof e=="object"||typeof e=="function"||Array.isArray(e))}});var pr=U((Ri,hr)=>{"use strict";function ot(e,t){if(!e)return!0;let r=t||ot.keywords;Array.isArray(r)||(r=[r]);let n=typeof e=="string"?e.toLowerCase():null;for(let i of r)if(i===e||i===n)return!0;return!1}ot.keywords=["0","false","nada","nil","nay","nah","negative","no","none","nope","nul","null","nix","nyet","uh-uh","veto","zero"];hr.exports=ot});var gr=U((Ji,mr)=>{"use strict";mr.exports=function(t){let r=Math.abs(t);if(isNaN(r))throw new TypeError("expected a number");if(!Number.isInteger(r))throw new Error("expected an integer");if(!Number.isSafeInteger(r))throw new Error("value exceeds maximum safe integer");return r%2===1}});var br=U((Gi,$r)=>{"use strict";var Xn=dr(),k=ee(),Kn=je(),yr=pr(),vr=gr(),W=$r.exports;W.and=function(){for(var e=arguments.length-1,t=arguments[e],r=!0,n=0;n":i=e>r;break;case"<=":i=e<=r;break;case">=":i=e>=r;break;case"typeof":i=typeof e===r;break;default:throw new Error("helper {{compare}}: invalid operator: `"+t+"`")}return k.value(i,this,n)};W.contains=function(e,t,r,n){typeof r=="object"&&(n=r,r=void 0);var i=Kn.contains(e,t,r);return k.value(i,this,n)};W.default=function(){for(var e=0;et,this,r)};W.gte=function(e,t,r){return arguments.length===2&&(r=t,t=r.hash.compare),k.value(e>=t,this,r)};W.has=function(e,t,r){return k.isOptions(e)&&(r=e,t=null,e=null),k.isOptions(t)&&(r=t,t=null),e===null?k.value(!1,this,r):arguments.length===2?k.value(Xn(this,e),this,r):(Array.isArray(e)||k.isString(e))&&k.isString(t)&&e.indexOf(t)>-1?k.fn(!0,this,r):k.isObject(e)&&k.isString(t)&&t in e?k.fn(!0,this,r):k.inverse(!1,this,r)};W.isFalsey=function(e,t){return k.value(yr(e),this,t)};W.isTruthy=function(e,t){return k.value(!yr(e),this,t)};W.ifEven=function(e,t){return k.value(!vr(e),this,t)};W.ifNth=function(e,t,r){var n=!isNaN(e)&&!isNaN(t)&&t%e===0;return k.value(n,this,r)};W.ifOdd=function(e,t){return k.value(vr(e),this,t)};W.is=function(e,t,r){return arguments.length===2&&(r=t,t=r.hash.compare),k.value(e==t,this,r)};W.isnt=function(e,t,r){return arguments.length===2&&(r=t,t=r.hash.compare),k.value(e!=t,this,r)};W.lt=function(e,t,r){return arguments.length===2&&(r=t,t=r.hash.compare),k.value(e=t,this,r)};W.unlessGteq=function(e,t,r){return k.isOptions(t)&&(r=t,t=r.hash.compare),k.value(et,this,r)}});var Or=U((Zi,wr)=>{var ei=tt(),ti=Object.prototype.toString;wr.exports=function(t){if(typeof t>"u")return"undefined";if(t===null)return"null";if(t===!0||t===!1||t instanceof Boolean)return"boolean";if(typeof t=="string"||t instanceof String)return"string";if(typeof t=="number"||t instanceof Number)return"number";if(typeof t=="function"||t instanceof Function)return"function";if(typeof Array.isArray<"u"&&Array.isArray(t))return"array";if(t instanceof RegExp)return"regexp";if(t instanceof Date)return"date";var r=ti.call(t);return r==="[object RegExp]"?"regexp":r==="[object Date]"?"date":r==="[object Arguments]"?"arguments":r==="[object Error]"?"error":ei(t)?"buffer":r==="[object Set]"?"set":r==="[object WeakSet]"?"weakset":r==="[object Map]"?"map":r==="[object WeakMap]"?"weakmap":r==="[object Symbol]"?"symbol":r==="[object Int8Array]"?"int8array":r==="[object Uint8Array]"?"uint8array":r==="[object Uint8ClampedArray]"?"uint8clampedarray":r==="[object Int16Array]"?"int16array":r==="[object Uint16Array]"?"uint16array":r==="[object Int32Array]"?"int32array":r==="[object Uint32Array]"?"uint32array":r==="[object Float32Array]"?"float32array":r==="[object Float64Array]"?"float64array":"object"}});var Sr=U((Vi,xr)=>{"use strict";var ri=Or();xr.exports=function(t){var r=ri(t);if(r!=="number"&&r!=="string")return!1;var n=+t;return n-n+1>=0&&t!==""}});var Mr=U((Qi,Nr)=>{"use strict";var ni=Sr();Nr.exports=function(t,r){if(!r)return t;if(!t)return{};for(var n=String(r).split(/[[.\]]/).filter(Boolean),i=n[n.length-1],u={};r=n.shift();)if(t=t[r],!t)return{};return ni(i)?[t]:(u[i]=t,u)}});var Dr=U((Xi,kr)=>{"use strict";var ii=Object.hasOwnProperty,ve=ee(),ui=ke(),P=kr.exports,si=Ae(),jr=Mr(),Ar=st();P.extend=function(){var e=[].slice.call(arguments),t={};ve.isOptions(e[e.length-1])&&(t=e.pop().hash,e.push(t));for(var r={},n=0;n{"use strict";var fi=ee(),Ur=Tr.exports,oi=Me();Ur.toRegex=function(e,t,r){var n=fi.options({},t,r);return new RegExp(e,n.flags)};Ur.test=function(e,t){if(typeof e!="string")return!1;if(oi(t)!=="regexp")throw new TypeError("expected a regular expression");return t.test(e)}});function $e(){return De>Ue.length-16&&(Yr.default.randomFillSync(Ue),De=0),Ue.slice(De,De+=16)}var Yr,Ue,De,at=Z(()=>{Yr=Ne(fe("crypto")),Ue=new Uint8Array(256),De=Ue.length});var Cr,Er=Z(()=>{Cr=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i});function ai(e){return typeof e=="string"&&Cr.test(e)}var ne,be=Z(()=>{Er();ne=ai});function de(e,t=0){return R[e[t+0]]+R[e[t+1]]+R[e[t+2]]+R[e[t+3]]+"-"+R[e[t+4]]+R[e[t+5]]+"-"+R[e[t+6]]+R[e[t+7]]+"-"+R[e[t+8]]+R[e[t+9]]+"-"+R[e[t+10]]+R[e[t+11]]+R[e[t+12]]+R[e[t+13]]+R[e[t+14]]+R[e[t+15]]}function ci(e,t=0){let r=de(e,t);if(!ne(r))throw TypeError("Stringified UUID is invalid");return r}var R,Ir,we=Z(()=>{be();R=[];for(let e=0;e<256;++e)R.push((e+256).toString(16).slice(1));Ir=ci});function li(e,t,r){let n=t&&r||0,i=t||new Array(16);e=e||{};let u=e.node||_r,s=e.clockseq!==void 0?e.clockseq:ct;if(u==null||s==null){let v=e.random||(e.rng||$e)();u==null&&(u=_r=[v[0]|1,v[1],v[2],v[3],v[4],v[5]]),s==null&&(s=ct=(v[6]<<8|v[7])&16383)}let f=e.msecs!==void 0?e.msecs:Date.now(),w=e.nsecs!==void 0?e.nsecs:dt+1,g=f-lt+(w-dt)/1e4;if(g<0&&e.clockseq===void 0&&(s=s+1&16383),(g<0||f>lt)&&e.nsecs===void 0&&(w=0),w>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");lt=f,dt=w,ct=s,f+=122192928e5;let l=((f&268435455)*1e4+w)%4294967296;i[n++]=l>>>24&255,i[n++]=l>>>16&255,i[n++]=l>>>8&255,i[n++]=l&255;let x=f/4294967296*1e4&268435455;i[n++]=x>>>8&255,i[n++]=x&255,i[n++]=x>>>24&15|16,i[n++]=x>>>16&255,i[n++]=s>>>8|128,i[n++]=s&255;for(let v=0;v<6;++v)i[n+v]=u[v];return t||de(i)}var _r,ct,lt,dt,Wr,Fr=Z(()=>{at();we();lt=0,dt=0;Wr=li});function di(e){if(!ne(e))throw TypeError("Invalid UUID");let t,r=new Uint8Array(16);return r[0]=(t=parseInt(e.slice(0,8),16))>>>24,r[1]=t>>>16&255,r[2]=t>>>8&255,r[3]=t&255,r[4]=(t=parseInt(e.slice(9,13),16))>>>8,r[5]=t&255,r[6]=(t=parseInt(e.slice(14,18),16))>>>8,r[7]=t&255,r[8]=(t=parseInt(e.slice(19,23),16))>>>8,r[9]=t&255,r[10]=(t=parseInt(e.slice(24,36),16))/1099511627776&255,r[11]=t/4294967296&255,r[12]=t>>>24&255,r[13]=t>>>16&255,r[14]=t>>>8&255,r[15]=t&255,r}var Te,ht=Z(()=>{be();Te=di});function hi(e){e=unescape(encodeURIComponent(e));let t=[];for(let r=0;r{we();ht();pi="6ba7b810-9dad-11d1-80b4-00c04fd430c8",mi="6ba7b811-9dad-11d1-80b4-00c04fd430c8"});function gi(e){return Array.isArray(e)?e=Buffer.from(e):typeof e=="string"&&(e=Buffer.from(e,"utf8")),Hr.default.createHash("md5").update(e).digest()}var Hr,Br,Lr=Z(()=>{Hr=Ne(fe("crypto"));Br=gi});var yi,zr,Pr=Z(()=>{pt();Lr();yi=Oe("v3",48,Br),zr=yi});var Rr,mt,Jr=Z(()=>{Rr=Ne(fe("crypto")),mt={randomUUID:Rr.default.randomUUID}});function vi(e,t,r){if(mt.randomUUID&&!t&&!e)return mt.randomUUID();e=e||{};let n=e.random||(e.rng||$e)();if(n[6]=n[6]&15|64,n[8]=n[8]&63|128,t){r=r||0;for(let i=0;i<16;++i)t[r+i]=n[i];return t}return de(n)}var Gr,Zr=Z(()=>{Jr();at();we();Gr=vi});function $i(e){return Array.isArray(e)?e=Buffer.from(e):typeof e=="string"&&(e=Buffer.from(e,"utf8")),Vr.default.createHash("sha1").update(e).digest()}var Vr,Qr,Xr=Z(()=>{Vr=Ne(fe("crypto"));Qr=$i});var bi,Kr,en=Z(()=>{pt();Xr();bi=Oe("v5",80,Qr),Kr=bi});var tn,rn=Z(()=>{tn="00000000-0000-0000-0000-000000000000"});function wi(e){if(!ne(e))throw TypeError("Invalid UUID");return parseInt(e.slice(14,15),16)}var nn,un=Z(()=>{be();nn=wi});var sn={};yt(sn,{NIL:()=>tn,parse:()=>Te,stringify:()=>Ir,v1:()=>Wr,v3:()=>zr,v4:()=>Gr,v5:()=>Kr,validate:()=>ne,version:()=>nn});var fn=Z(()=>{Fr();Pr();Zr();en();rn();un();be();we();ht()});var an=U((Wu,on)=>{var Oi=(fn(),$t(sn)),xi=on.exports;xi.uuid=function(){return Oi.v4()}});var dn=U((Fu,gt)=>{var{date:Si,duration:Ni}=Dt(),Mi={math:Rt(),array:ke(),number:nr(),url:ur(),string:or(),comparison:br(),object:Dr(),regex:qr(),uuid:an()},ln=["sortBy"];gt.exports.helpersToRemoveForJs=ln;var cn={date:Si,duration:Ni},ie;gt.exports.getJsHelperList=()=>{if(ie)return ie;ie={};for(let e of Object.values(Mi))for(let[t,r]of Object.entries(e))ie[t]=(...n)=>r(...n,{});for(let e of Object.keys(cn))ie[e]=cn[e];for(let e of ln)delete ie[e];return Object.freeze(ie),ie}});var ki={};yt(ki,{default:()=>Ai});var{getJsHelperList:ji}=dn(),Ai={...ji(),stripProtocol:helpersStripProtocol};return $t(ki);})(); +"use strict";var helpers=(()=>{var Mn=Object.create;var je=Object.defineProperty;var An=Object.getOwnPropertyDescriptor;var kn=Object.getOwnPropertyNames;var Dn=Object.getPrototypeOf,Un=Object.prototype.hasOwnProperty;var oe=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var V=(e,t)=>()=>(e&&(t=e(e=0)),t);var T=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),mt=(e,t)=>{for(var r in t)je(e,r,{get:t[r],enumerable:!0})},yt=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of kn(t))!Un.call(e,i)&&i!==r&&je(e,i,{get:()=>t[i],enumerable:!(n=An(t,i))||n.enumerable});return e};var F=(e,t,r)=>(r=e!=null?Mn(Dn(e)):{},yt(t||!e||!e.__esModule?je(r,"default",{value:e,enumerable:!0}):r,e)),gt=e=>yt(je({},"__esModule",{value:!0}),e);var vt=T((Ye,Ce)=>{(function(e,t){typeof Ye=="object"&&typeof Ce<"u"?Ce.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs=t()})(Ye,function(){"use strict";var e=1e3,t=6e4,r=36e5,n="millisecond",i="second",u="minute",s="hour",f="day",w="week",y="month",l="quarter",x="year",v="date",a="Invalid Date",U=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,q=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,I={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function($){var h=["th","st","nd","rd"],c=$%100;return"["+$+(h[(c-20)%10]||h[c]||h[0])+"]"}},C=function($,h,c){var b=String($);return!b||b.length>=h?$:""+Array(h+1-b.length).join(c)+$},H={s:C,z:function($){var h=-$.utcOffset(),c=Math.abs(h),b=Math.floor(c/60),d=c%60;return(h<=0?"+":"-")+C(b,2,"0")+":"+C(d,2,"0")},m:function $(h,c){if(h.date()1)return $(M[0])}else{var W=h.name;S[W]=h,d=W}return!b&&d&&(Y=d),d||!b&&Y},j=function($,h){if(o($))return $.clone();var c=typeof h=="object"?h:{};return c.date=$,c.args=arguments,new z(c)},O=H;O.l=D,O.i=o,O.w=function($,h){return j($,{locale:h.$L,utc:h.$u,x:h.$x,$offset:h.$offset})};var z=function(){function $(c){this.$L=D(c.locale,null,!0),this.parse(c),this.$x=this.$x||c.x||{},this[p]=!0}var h=$.prototype;return h.parse=function(c){this.$d=function(b){var d=b.date,N=b.utc;if(d===null)return new Date(NaN);if(O.u(d))return new Date;if(d instanceof Date)return new Date(d);if(typeof d=="string"&&!/Z$/i.test(d)){var M=d.match(U);if(M){var W=M[2]-1||0,B=(M[7]||"0").substring(0,3);return N?new Date(Date.UTC(M[1],W,M[3]||1,M[4]||0,M[5]||0,M[6]||0,B)):new Date(M[1],W,M[3]||1,M[4]||0,M[5]||0,M[6]||0,B)}}return new Date(d)}(c),this.init()},h.init=function(){var c=this.$d;this.$y=c.getFullYear(),this.$M=c.getMonth(),this.$D=c.getDate(),this.$W=c.getDay(),this.$H=c.getHours(),this.$m=c.getMinutes(),this.$s=c.getSeconds(),this.$ms=c.getMilliseconds()},h.$utils=function(){return O},h.isValid=function(){return this.$d.toString()!==a},h.isSame=function(c,b){var d=j(c);return this.startOf(b)<=d&&d<=this.endOf(b)},h.isAfter=function(c,b){return j(c){(function(e,t){typeof Ee=="object"&&typeof qe<"u"?qe.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_duration=t()})(Ee,function(){"use strict";var e,t,r=1e3,n=6e4,i=36e5,u=864e5,s=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,f=31536e6,w=2628e6,y=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/,l={years:f,months:w,days:u,hours:i,minutes:n,seconds:r,milliseconds:1,weeks:6048e5},x=function(S){return S instanceof H},v=function(S,p,o){return new H(S,o,p.$l)},a=function(S){return t.p(S)+"s"},U=function(S){return S<0},q=function(S){return U(S)?Math.ceil(S):Math.floor(S)},I=function(S){return Math.abs(S)},C=function(S,p){return S?U(S)?{negative:!0,format:""+I(S)+p}:{negative:!1,format:""+S+p}:{negative:!1,format:""}},H=function(){function S(o,D,j){var O=this;if(this.$d={},this.$l=j,o===void 0&&(this.$ms=0,this.parseFromMilliseconds()),D)return v(o*l[a(D)],this);if(typeof o=="number")return this.$ms=o,this.parseFromMilliseconds(),this;if(typeof o=="object")return Object.keys(o).forEach(function($){O.$d[a($)]=o[$]}),this.calMilliseconds(),this;if(typeof o=="string"){var z=o.match(y);if(z){var J=z.slice(2).map(function($){return $!=null?Number($):0});return this.$d.years=J[0],this.$d.months=J[1],this.$d.weeks=J[2],this.$d.days=J[3],this.$d.hours=J[4],this.$d.minutes=J[5],this.$d.seconds=J[6],this.calMilliseconds(),this}}return this}var p=S.prototype;return p.calMilliseconds=function(){var o=this;this.$ms=Object.keys(this.$d).reduce(function(D,j){return D+(o.$d[j]||0)*l[j]},0)},p.parseFromMilliseconds=function(){var o=this.$ms;this.$d.years=q(o/f),o%=f,this.$d.months=q(o/w),o%=w,this.$d.days=q(o/u),o%=u,this.$d.hours=q(o/i),o%=i,this.$d.minutes=q(o/n),o%=n,this.$d.seconds=q(o/r),o%=r,this.$d.milliseconds=o},p.toISOString=function(){var o=C(this.$d.years,"Y"),D=C(this.$d.months,"M"),j=+this.$d.days||0;this.$d.weeks&&(j+=7*this.$d.weeks);var O=C(j,"D"),z=C(this.$d.hours,"H"),J=C(this.$d.minutes,"M"),$=this.$d.seconds||0;this.$d.milliseconds&&($+=this.$d.milliseconds/1e3,$=Math.round(1e3*$)/1e3);var h=C($,"S"),c=o.negative||D.negative||O.negative||z.negative||J.negative||h.negative,b=z.format||J.format||h.format?"T":"",d=(c?"-":"")+"P"+o.format+D.format+O.format+b+z.format+J.format+h.format;return d==="P"||d==="-P"?"P0D":d},p.toJSON=function(){return this.toISOString()},p.format=function(o){var D=o||"YYYY-MM-DDTHH:mm:ss",j={Y:this.$d.years,YY:t.s(this.$d.years,2,"0"),YYYY:t.s(this.$d.years,4,"0"),M:this.$d.months,MM:t.s(this.$d.months,2,"0"),D:this.$d.days,DD:t.s(this.$d.days,2,"0"),H:this.$d.hours,HH:t.s(this.$d.hours,2,"0"),m:this.$d.minutes,mm:t.s(this.$d.minutes,2,"0"),s:this.$d.seconds,ss:t.s(this.$d.seconds,2,"0"),SSS:t.s(this.$d.milliseconds,3,"0")};return D.replace(s,function(O,z){return z||String(j[O])})},p.as=function(o){return this.$ms/l[a(o)]},p.get=function(o){var D=this.$ms,j=a(o);return j==="milliseconds"?D%=1e3:D=j==="weeks"?q(D/l[j]):this.$d[j],D||0},p.add=function(o,D,j){var O;return O=D?o*l[a(D)]:x(o)?o.$ms:v(o,this).$ms,v(this.$ms+O*(j?-1:1),this)},p.subtract=function(o,D){return this.add(o,D,!0)},p.locale=function(o){var D=this.clone();return D.$l=o,D},p.clone=function(){return v(this.$ms,this)},p.humanize=function(o){return e().add(this.$ms,"ms").locale(this.$l).fromNow(!o)},p.valueOf=function(){return this.asMilliseconds()},p.milliseconds=function(){return this.get("milliseconds")},p.asMilliseconds=function(){return this.as("milliseconds")},p.seconds=function(){return this.get("seconds")},p.asSeconds=function(){return this.as("seconds")},p.minutes=function(){return this.get("minutes")},p.asMinutes=function(){return this.as("minutes")},p.hours=function(){return this.get("hours")},p.asHours=function(){return this.as("hours")},p.days=function(){return this.get("days")},p.asDays=function(){return this.as("days")},p.weeks=function(){return this.get("weeks")},p.asWeeks=function(){return this.as("weeks")},p.months=function(){return this.get("months")},p.asMonths=function(){return this.as("months")},p.years=function(){return this.get("years")},p.asYears=function(){return this.as("years")},S}(),Y=function(S,p,o){return S.add(p.years()*o,"y").add(p.months()*o,"M").add(p.days()*o,"d").add(p.hours()*o,"h").add(p.minutes()*o,"m").add(p.seconds()*o,"s").add(p.milliseconds()*o,"ms")};return function(S,p,o){e=o,t=o().$utils(),o.duration=function(O,z){var J=o.locale();return v(O,{$l:J},z)},o.isDuration=x;var D=p.prototype.add,j=p.prototype.subtract;p.prototype.add=function(O,z){return x(O)?Y(this,O,1):D.bind(this)(O,z)},p.prototype.subtract=function(O,z){return x(O)?Y(this,O,-1):j.bind(this)(O,z)}}})});var bt=T((Ie,We)=>{(function(e,t){typeof Ie=="object"&&typeof We<"u"?We.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_advancedFormat=t()})(Ie,function(){"use strict";return function(e,t){var r=t.prototype,n=r.format;r.format=function(i){var u=this,s=this.$locale();if(!this.isValid())return n.bind(this)(i);var f=this.$utils(),w=(i||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(y){switch(y){case"Q":return Math.ceil((u.$M+1)/3);case"Do":return s.ordinal(u.$D);case"gggg":return u.weekYear();case"GGGG":return u.isoWeekYear();case"wo":return s.ordinal(u.week(),"W");case"w":case"ww":return f.s(u.week(),y==="w"?1:2,"0");case"W":case"WW":return f.s(u.isoWeek(),y==="W"?1:2,"0");case"k":case"kk":return f.s(String(u.$H===0?24:u.$H),y==="k"?1:2,"0");case"X":return Math.floor(u.$d.getTime()/1e3);case"x":return u.$d.getTime();case"z":return"["+u.offsetName()+"]";case"zzz":return"["+u.offsetName("long")+"]";default:return y}});return n.bind(this)(w)}}})});var wt=T((_e,Fe)=>{(function(e,t){typeof _e=="object"&&typeof Fe<"u"?Fe.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_isoWeek=t()})(_e,function(){"use strict";var e="day";return function(t,r,n){var i=function(f){return f.add(4-f.isoWeekday(),e)},u=r.prototype;u.isoWeekYear=function(){return i(this).year()},u.isoWeek=function(f){if(!this.$utils().u(f))return this.add(7*(f-this.isoWeek()),e);var w,y,l,x,v=i(this),a=(w=this.isoWeekYear(),y=this.$u,l=(y?n.utc:n)().year(w).startOf("year"),x=4-l.isoWeekday(),l.isoWeekday()>4&&(x+=7),l.add(x,e));return v.diff(a,"week")+1},u.isoWeekday=function(f){return this.$utils().u(f)?this.day()||7:this.day(this.day()%7?f:f-7)};var s=u.startOf;u.startOf=function(f,w){var y=this.$utils(),l=!!y.u(w)||w;return y.p(f)==="isoweek"?l?this.date(this.date()-(this.isoWeekday()-1)).startOf("day"):this.date(this.date()-1-(this.isoWeekday()-1)+7).endOf("day"):s.bind(this)(f,w)}}})});var Ot=T((He,Be)=>{(function(e,t){typeof He=="object"&&typeof Be<"u"?Be.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_weekYear=t()})(He,function(){"use strict";return function(e,t){t.prototype.weekYear=function(){var r=this.month(),n=this.week(),i=this.year();return n===1&&r===11?i+1:r===0&&n>=52?i-1:i}}})});var xt=T((ze,Le)=>{(function(e,t){typeof ze=="object"&&typeof Le<"u"?Le.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_weekOfYear=t()})(ze,function(){"use strict";var e="week",t="year";return function(r,n,i){var u=n.prototype;u.week=function(s){if(s===void 0&&(s=null),s!==null)return this.add(7*(s-this.week()),"day");var f=this.$locale().yearStart||1;if(this.month()===11&&this.date()>25){var w=i(this).startOf(t).add(1,t).date(f),y=i(this).endOf(e);if(w.isBefore(y))return 1}var l=i(this).startOf(t).date(f).startOf(e).subtract(1,"millisecond"),x=this.diff(l,e,!0);return x<0?i(this).startOf("week").week():Math.ceil(x)},u.weeks=function(s){return s===void 0&&(s=null),this.week(s)}}})});var St=T((Pe,Re)=>{(function(e,t){typeof Pe=="object"&&typeof Re<"u"?Re.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_relativeTime=t()})(Pe,function(){"use strict";return function(e,t,r){e=e||{};var n=t.prototype,i={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function u(f,w,y,l){return n.fromToBase(f,w,y,l)}r.en.relativeTime=i,n.fromToBase=function(f,w,y,l,x){for(var v,a,U,q=y.$locale().relativeTime||i,I=e.thresholds||[{l:"s",r:44,d:"second"},{l:"m",r:89},{l:"mm",r:44,d:"minute"},{l:"h",r:89},{l:"hh",r:21,d:"hour"},{l:"d",r:35},{l:"dd",r:25,d:"day"},{l:"M",r:45},{l:"MM",r:10,d:"month"},{l:"y",r:17},{l:"yy",d:"year"}],C=I.length,H=0;H0,S<=Y.r||!Y.r){S<=1&&H>0&&(Y=I[H-1]);var p=q[Y.l];x&&(S=x(""+S)),a=typeof p=="string"?p.replace("%d",S):p(S,w,Y.l,U);break}}if(w)return a;var o=U?q.future:q.past;return typeof o=="function"?o(a):o.replace("%s",a)},n.to=function(f,w){return u(f,w,this,!0)},n.from=function(f,w){return u(f,w,this)};var s=function(f){return f.$u?r.utc():r()};n.toNow=function(f){return this.to(s(this),f)},n.fromNow=function(f){return this.from(s(this),f)}}})});var jt=T((Ge,Je)=>{(function(e,t){typeof Ge=="object"&&typeof Je<"u"?Je.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_utc=t()})(Ge,function(){"use strict";var e="minute",t=/[+-]\d\d(?::?\d\d)?/g,r=/([+-]|\d\d)/g;return function(n,i,u){var s=i.prototype;u.utc=function(a){var U={date:a,utc:!0,args:arguments};return new i(U)},s.utc=function(a){var U=u(this.toDate(),{locale:this.$L,utc:!0});return a?U.add(this.utcOffset(),e):U},s.local=function(){return u(this.toDate(),{locale:this.$L,utc:!1})};var f=s.parse;s.parse=function(a){a.utc&&(this.$u=!0),this.$utils().u(a.$offset)||(this.$offset=a.$offset),f.call(this,a)};var w=s.init;s.init=function(){if(this.$u){var a=this.$d;this.$y=a.getUTCFullYear(),this.$M=a.getUTCMonth(),this.$D=a.getUTCDate(),this.$W=a.getUTCDay(),this.$H=a.getUTCHours(),this.$m=a.getUTCMinutes(),this.$s=a.getUTCSeconds(),this.$ms=a.getUTCMilliseconds()}else w.call(this)};var y=s.utcOffset;s.utcOffset=function(a,U){var q=this.$utils().u;if(q(a))return this.$u?0:q(this.$offset)?y.call(this):this.$offset;if(typeof a=="string"&&(a=function(Y){Y===void 0&&(Y="");var S=Y.match(t);if(!S)return null;var p=(""+S[0]).match(r)||["-",0,0],o=p[0],D=60*+p[1]+ +p[2];return D===0?0:o==="+"?D:-D}(a),a===null))return this;var I=Math.abs(a)<=16?60*a:a,C=this;if(U)return C.$offset=I,C.$u=a===0,C;if(a!==0){var H=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(C=this.local().add(I+H,e)).$offset=I,C.$x.$localOffset=H}else C=this.utc();return C};var l=s.format;s.format=function(a){var U=a||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return l.call(this,U)},s.valueOf=function(){var a=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*a},s.isUTC=function(){return!!this.$u},s.toISOString=function(){return this.toDate().toISOString()},s.toString=function(){return this.toDate().toUTCString()};var x=s.toDate;s.toDate=function(a){return a==="s"&&this.$offset?u(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():x.call(this)};var v=s.diff;s.diff=function(a,U,q){if(a&&this.$u===a.$u)return v.call(this,a,U,q);var I=this.local(),C=u(a).local();return v.call(I,C,U,q)}}})});var Nt=T((Ze,Ve)=>{(function(e,t){typeof Ze=="object"&&typeof Ve<"u"?Ve.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self).dayjs_plugin_timezone=t()})(Ze,function(){"use strict";var e={year:0,month:1,day:2,hour:3,minute:4,second:5},t={};return function(r,n,i){var u,s=function(l,x,v){v===void 0&&(v={});var a=new Date(l),U=function(q,I){I===void 0&&(I={});var C=I.timeZoneName||"short",H=q+"|"+C,Y=t[H];return Y||(Y=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:q,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:C}),t[H]=Y),Y}(x,v);return U.formatToParts(a)},f=function(l,x){for(var v=s(l,x),a=[],U=0;U=0&&(a[H]=parseInt(C,10))}var Y=a[3],S=Y===24?0:Y,p=a[0]+"-"+a[1]+"-"+a[2]+" "+S+":"+a[4]+":"+a[5]+":000",o=+l;return(i.utc(p).valueOf()-(o-=o%1e3))/6e4},w=n.prototype;w.tz=function(l,x){l===void 0&&(l=u);var v=this.utcOffset(),a=this.toDate(),U=a.toLocaleString("en-US",{timeZone:l}),q=Math.round((a-new Date(U))/1e3/60),I=i(U,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-q,!0);if(x){var C=I.utcOffset();I=I.add(v-C,"minute")}return I.$x.$timezone=l,I},w.offsetName=function(l){var x=this.$x.$timezone||i.tz.guess(),v=s(this.valueOf(),x,{timeZoneName:l}).find(function(a){return a.type.toLowerCase()==="timezonename"});return v&&v.value};var y=w.startOf;w.startOf=function(l,x){if(!this.$x||!this.$x.$timezone)return y.call(this,l,x);var v=i(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return y.call(v,l,x).tz(this.$x.$timezone,!0)},i.tz=function(l,x,v){var a=v&&x,U=v||x||u,q=f(+i(),U);if(typeof l!="string")return i(l).tz(U);var I=function(S,p,o){var D=S-60*p*1e3,j=f(D,o);if(p===j)return[D,p];var O=f(D-=60*(j-p)*1e3,o);return j===O?[D,j]:[S-60*Math.min(j,O)*1e3,Math.max(j,O)]}(i.utc(l,a).valueOf(),q,U),C=I[0],H=I[1],Y=i(C).utcOffset(H);return Y.$x.$timezone=U,Y},i.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},i.tz.setDefault=function(l){u=l}}})});var et=T((Pi,_t)=>{_t.exports=function(e){return e!=null&&(Wt(e)||Tn(e)||!!e._isBuffer)};function Wt(e){return!!e.constructor&&typeof e.constructor.isBuffer=="function"&&e.constructor.isBuffer(e)}function Tn(e){return typeof e.readFloatLE=="function"&&typeof e.slice=="function"&&Wt(e.slice(0,0))}});var Ht=T((Ri,Ft)=>{var Yn=et(),Cn=Object.prototype.toString;Ft.exports=function(t){if(typeof t>"u")return"undefined";if(t===null)return"null";if(t===!0||t===!1||t instanceof Boolean)return"boolean";if(typeof t=="string"||t instanceof String)return"string";if(typeof t=="number"||t instanceof Number)return"number";if(typeof t=="function"||t instanceof Function)return"function";if(typeof Array.isArray<"u"&&Array.isArray(t))return"array";if(t instanceof RegExp)return"regexp";if(t instanceof Date)return"date";var r=Cn.call(t);return r==="[object RegExp]"?"regexp":r==="[object Date]"?"date":r==="[object Arguments]"?"arguments":r==="[object Error]"?"error":Yn(t)?"buffer":r==="[object Set]"?"set":r==="[object WeakSet]"?"weakset":r==="[object Map]"?"map":r==="[object WeakMap]"?"weakmap":r==="[object Symbol]"?"symbol":r==="[object Int8Array]"?"int8array":r==="[object Uint8Array]"?"uint8array":r==="[object Uint8ClampedArray]"?"uint8clampedarray":r==="[object Int16Array]"?"int16array":r==="[object Uint16Array]"?"uint16array":r==="[object Int32Array]"?"int32array":r==="[object Uint32Array]"?"uint32array":r==="[object Float32Array]"?"float32array":r==="[object Float64Array]"?"float64array":"object"}});var Pt=T((Gi,Lt)=>{"use strict";var Bt=Ht(),zt={arguments:"an arguments object",array:"an array",boolean:"a boolean",buffer:"a buffer",date:"a date",error:"an error",float32array:"a float32array",float64array:"a float64array",function:"a function",int16array:"an int16array",int32array:"an int32array",int8array:"an int8array",map:"a Map",null:"null",number:"a number",object:"an object",regexp:"a regular expression",set:"a Set",string:"a string",symbol:"a symbol",uint16array:"an uint16array",uint32array:"an uint32array",uint8array:"an uint8array",uint8clampedarray:"an uint8clampedarray",undefined:"undefined",weakmap:"a WeakMap",weakset:"a WeakSet"};function tt(e){return zt[Bt(e)]}tt.types=zt;tt.typeOf=Bt;Lt.exports=tt});var Ne=T((Ji,Gt)=>{var En=Object.prototype.toString;Gt.exports=function(t){if(t===void 0)return"undefined";if(t===null)return"null";var r=typeof t;if(r==="boolean")return"boolean";if(r==="string")return"string";if(r==="number")return"number";if(r==="symbol")return"symbol";if(r==="function")return Fn(t)?"generatorfunction":"function";if(qn(t))return"array";if(zn(t))return"buffer";if(Bn(t))return"arguments";if(Wn(t))return"date";if(In(t))return"error";if(_n(t))return"regexp";switch(Rt(t)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(Hn(t))return"generator";switch(r=En.call(t),r){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return r.slice(8,-1).toLowerCase().replace(/\s/g,"")};function Rt(e){return typeof e.constructor=="function"?e.constructor.name:null}function qn(e){return Array.isArray?Array.isArray(e):e instanceof Array}function In(e){return e instanceof Error||typeof e.message=="string"&&e.constructor&&typeof e.constructor.stackTraceLimit=="number"}function Wn(e){return e instanceof Date?!0:typeof e.toDateString=="function"&&typeof e.getDate=="function"&&typeof e.setDate=="function"}function _n(e){return e instanceof RegExp?!0:typeof e.flags=="string"&&typeof e.ignoreCase=="boolean"&&typeof e.multiline=="boolean"&&typeof e.global=="boolean"}function Fn(e,t){return Rt(e)==="GeneratorFunction"}function Hn(e){return typeof e.throw=="function"&&typeof e.return=="function"&&typeof e.next=="function"}function Bn(e){try{if(typeof e.length=="number"&&typeof e.callee=="function")return!0}catch(t){if(t.message.indexOf("callee")!==-1)return!0}return!1}function zn(e){return e.constructor&&typeof e.constructor.isBuffer=="function"?e.constructor.isBuffer(e):!1}});var te=T((Vt,Qt)=>{"use strict";var Ln=oe("util"),Jt=Pt(),Pn=Ne(),m=Vt=Qt.exports;m.extend=Zt;m.indexOf=Qn;m.escapeExpression=Xn;m.isEmpty=ri;m.createFrame=Kn;m.blockParams=ei;m.appendContextPath=ti;var Rn={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"="},Gn=/[&<>"'`=]/g,Jn=/[&<>"'`=]/;function Zn(e){return Rn[e]}function Zt(e){for(var t=1;t{"use strict";var ni=te();me.contains=function(e,t,r){return e==null||t==null||isNaN(e.length)?!1:e.indexOf(t,r)!==-1};me.chop=function(e){if(typeof e!="string")return"";var t=/^[-_.\W\s]+|[-_.\W\s]+$/g;return e.trim().replace(t,"")};me.changecase=function(e,t){if(typeof e!="string")return"";if(e.length===1)return e.toLowerCase();e=me.chop(e).toLowerCase(),typeof t!="function"&&(t=ni.identity);var r=/[-_.\W\s]+(\w|$)/g;return e.replace(r,function(n,i){return t(i)})};me.random=function(e,t){return e+Math.floor(Math.random()*(t-e+1))}});var Kt=T((Vi,Xt)=>{"use strict";var ii=Me(),Q=Xt.exports;Q.abs=function(e){if(isNaN(e))throw new TypeError("expected a number");return Math.abs(e)};Q.add=function(e,t){return!isNaN(e)&&!isNaN(t)?Number(e)+Number(t):typeof e=="string"&&typeof t=="string"?e+t:""};Q.avg=function(){let e=[].concat.apply([],arguments);return e.pop(),Q.sum(e)/e.length};Q.ceil=function(e){if(isNaN(e))throw new TypeError("expected a number");return Math.ceil(e)};Q.divide=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)/Number(t)};Q.floor=function(e){if(isNaN(e))throw new TypeError("expected a number");return Math.floor(e)};Q.minus=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)-Number(t)};Q.modulo=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)%Number(t)};Q.multiply=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)*Number(t)};Q.plus=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)+Number(t)};Q.random=function(e,t){if(isNaN(e))throw new TypeError("expected minimum to be a number");if(isNaN(t))throw new TypeError("expected maximum to be a number");return ii.random(e,t)};Q.remainder=function(e,t){return e%t};Q.round=function(e){if(isNaN(e))throw new TypeError("expected a number");return Math.round(e)};Q.subtract=function(e,t){if(isNaN(e))throw new TypeError("expected the first argument to be a number");if(isNaN(t))throw new TypeError("expected the second argument to be a number");return Number(e)-Number(t)};Q.sum=function(){for(var e=[].concat.apply([],arguments),t=e.length,r=0;t--;)isNaN(e[t])||(r+=Number(e[t]));return r}});var tr=T((Qi,er)=>{"use strict";er.exports=function(t){return t!=null&&typeof t=="object"&&Array.isArray(t)===!1}});var Ae=T((Xi,ur)=>{var ir=tr();ur.exports=function(e,t,r){if(ir(r)||(r={default:r}),!nr(e))return typeof r.default<"u"?r.default:e;typeof t=="number"&&(t=String(t));let n=Array.isArray(t),i=typeof t=="string",u=r.separator||".",s=r.joinChar||(typeof u=="string"?u:".");if(!i&&!n)return e;if(i&&t in e)return it(t,e,r)?e[t]:r.default;let f=n?t:ui(t,u,r),w=f.length,y=0;do{let l=f[y];for(typeof l=="number"&&(l=String(l));l&&l.slice(-1)==="\\";)l=rr([l.slice(0,-1),f[++y]||""],s,r);if(l in e){if(!it(l,e,r))return r.default;e=e[l]}else{let x=!1,v=y+1;for(;v{"use strict";sr.exports=function(t){if(typeof t!="object")throw new TypeError("createFrame expects data to be an object");var r=Object.assign({},t);if(r._parent=t,r.extend=function(s){Object.assign(this,s)},arguments.length>1)for(var n=[].slice.call(arguments,1),i=n.length,u=-1;++u{"use strict";var g=te(),E=fr.exports,ce=Ae(),si=ut();E.after=function(e,t){return g.isUndefined(e)?"":(e=g.result(e),Array.isArray(e)?e.slice(t):"")};E.arrayify=function(e){return g.isUndefined(e)?[]:e?Array.isArray(e)?e:[e]:[]};E.before=function(e,t){return g.isUndefined(e)?"":(e=g.result(e),Array.isArray(e)?e.slice(0,t-1):"")};E.eachIndex=function(e,t){var r="";if(g.isUndefined(e))return"";if(e=g.result(e),Array.isArray(e))for(var n=0;n0){for(var s=0;s-1,this,r):"")};E.isArray=function(e){return Array.isArray(e)};E.itemAt=function(e,t){if(g.isUndefined(e))return null;if(e=g.result(e),Array.isArray(e)){if(t=isNaN(t)?0:+t,t<0)return e[e.length+t];if(ti[t]>u[t]?1:-1)}return""};E.withAfter=function(e,t,r){if(g.isUndefined(e))return"";if(e=g.result(e),Array.isArray(e)){e=e.slice(t);for(var n="",i=0;i0){for(var i=[],u=0;u0&&u%t===0&&(n+=r.fn(i),i=[]),i.push(e[u]);n+=r.fn(i)}return n};E.withLast=function(e,t,r){if(g.isUndefined(e))return"";if(e=g.result(e),Array.isArray(e)){if(g.isUndefined(t)||(t=parseFloat(g.result(t))),g.isUndefined(t))return r=t,r.fn(e[e.length-1]);e=e.slice(-t);for(var n=e.length,i=-1,u="";++iy?1:w{"use strict";var or=te(),re=ar.exports;re.bytes=function(e,t,r){if(e==null||isNaN(e)&&(e=e.length,!e))return"0 B";isNaN(t)&&(t=2);var n=["B","kB","MB","GB","TB","PB","EB","ZB","YB"];t=Math.pow(10,t),e=Number(e);for(var i=n.length-1;i-->=0;){var u=Math.pow(10,i*3);if(u<=e+1){e=Math.round(e*t/u)/t,e+=" "+n[i];break}}return e};re.addCommas=function(e){return e.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g,"$1,")};re.phoneNumber=function(e){return e=e.toString(),"("+e.substr(0,3)+") "+e.substr(3,3)+"-"+e.substr(6,4)};re.toAbbr=function(e,t){isNaN(e)&&(e=0),or.isUndefined(t)&&(t=2),e=Number(e),t=Math.pow(10,t);for(var r=["k","m","b","t","q"],n=r.length-1;n>=0;){var i=Math.pow(10,(n+1)*3);if(i<=e+1){e=Math.round(e*t/i)/t,e+=r[n];break}n--}return e};re.toExponential=function(e,t){return isNaN(e)&&(e=0),or.isUndefined(t)&&(t=0),Number(e).toExponential(t)};re.toFixed=function(e,t){return isNaN(e)&&(e=0),isNaN(t)&&(t=0),Number(e).toFixed(t)};re.toFloat=function(e){return parseFloat(e)};re.toInt=function(e){return parseInt(e,10)};re.toPrecision=function(e,t){return isNaN(e)&&(e=0),isNaN(t)&&(t=1),Number(e).toPrecision(t)}});var dr=T((ru,lr)=>{"use strict";var st=oe("url"),ve=te(),fi=oe("querystring"),le=lr.exports;le.encodeURI=function(e){if(ve.isString(e))return encodeURIComponent(e)};le.escape=function(e){if(ve.isString(e))return fi.escape(e)};le.decodeURI=function(e){if(ve.isString(e))return decodeURIComponent(e)};le.urlResolve=function(e,t){return st.resolve(e,t)};le.urlParse=function(e){return st.parse(e)};le.stripQuerystring=function(e){if(ve.isString(e))return e.split("?")[0]};le.stripProtocol=function(e){if(ve.isString(e)){var t=st.parse(e);return t.protocol="",t.format()}}});var mr=T((hr,pr)=>{"use strict";var P=te(),de=Me(),A=pr.exports;A.append=function(e,t){return typeof e=="string"&&typeof t=="string"?e+t:e};A.camelcase=function(e){return typeof e!="string"?"":de.changecase(e,function(t){return t.toUpperCase()})};A.capitalize=function(e){return typeof e!="string"?"":e.charAt(0).toUpperCase()+e.slice(1)};A.capitalizeAll=function(e){if(typeof e!="string")return"";if(P.isString(e))return e.replace(/\w\S*/g,function(t){return A.capitalize(t)})};A.center=function(e,t){if(typeof e!="string")return"";for(var r="",n=0;n-1;)i++,n+=r;return i};A.pascalcase=function(e){return typeof e!="string"?"":(e=de.changecase(e,function(t){return t.toUpperCase()}),e.charAt(0).toUpperCase()+e.slice(1))};A.pathcase=function(e){return typeof e!="string"?"":de.changecase(e,function(t){return"/"+t})};A.plusify=function(e,t){return typeof e!="string"?"":(P.isString(t)||(t=" "),e.split(t).join("+"))};A.prepend=function(e,t){return typeof e=="string"&&typeof t=="string"?t+e:e};A.raw=function(e){var t=e.fn(),r=P.options(this,e);if(r.escape!==!1)for(var n=0;(n=t.indexOf("{{",n))!==-1;)t[n-1]!=="\\"&&(t=t.slice(0,n)+"\\"+t.slice(n)),n+=3;return t};A.remove=function(e,t){return typeof e!="string"?"":P.isString(t)?e.split(t).join(""):e};A.removeFirst=function(e,t){return typeof e!="string"?"":P.isString(t)?e.replace(t,""):e};A.replace=function(e,t,r){return typeof e!="string"?"":P.isString(t)?(P.isString(r)||(r=""),e.split(t).join(r)):e};A.replaceFirst=function(e,t,r){return typeof e!="string"?"":P.isString(t)?(P.isString(r)||(r=""),e.replace(t,r)):e};A.reverse=ke().reverse;A.sentence=function(e){return typeof e!="string"?"":e.replace(/((?:\S[^\.\?\!]*)[\.\?\!]*)/g,function(t){return t.charAt(0).toUpperCase()+t.substr(1).toLowerCase()})};A.snakecase=function(e){return typeof e!="string"?"":de.changecase(e,function(t){return"_"+t})};A.split=function(e,t){return typeof e!="string"?"":(P.isString(t)||(t=","),e.split(t))};A.startsWith=function(e,t,r){var n=[].slice.call(arguments);return r=n.pop(),P.isString(t)&&t.indexOf(e)===0?r.fn(this):typeof r.inverse=="function"?r.inverse(this):""};A.titleize=function(e){if(typeof e!="string")return"";for(var t=e.replace(/[- _]+/g," "),r=t.split(" "),n=r.length,i=[],u=0;n--;){var s=r[u++];i.push(hr.capitalize(s))}return i.join(" ")};A.trim=function(e){return typeof e=="string"?e.trim():""};A.trimLeft=function(e){if(P.isString(e))return e.replace(/^\s+/,"")};A.trimRight=function(e){if(P.isString(e))return e.replace(/\s+$/,"")};A.truncate=function(e,t,r){if(P.isString(e))return typeof r!="string"&&(r=""),e.length>t?e.slice(0,t-r.length)+r:e};A.truncateWords=function(e,t,r){if(P.isString(e)&&!isNaN(t)){typeof r!="string"&&(r="\u2026");var n=Number(t),i=e.split(/[ \t]/);if(n>=i.length)return e;i=i.slice(0,n);var u=i.join(" ").trim();return u+r}};A.upcase=function(){return A.uppercase.apply(this,arguments)};A.uppercase=function(e){return P.isObject(e)&&e.fn?e.fn(this).toUpperCase():typeof e!="string"?"":e.toUpperCase()}});var gr=T((nu,yr)=>{"use strict";var oi=Ne();yr.exports=function e(t){switch(oi(t)){case"boolean":case"date":case"function":case"null":case"number":return!0;case"undefined":return!1;case"regexp":return t.source!=="(?:)"&&t.source!=="";case"buffer":return t.toString()!=="";case"error":return t.message!=="";case"string":case"arguments":return t.length!==0;case"file":case"map":case"set":return t.size!==0;case"array":case"object":for(let r of Object.keys(t))if(e(t[r]))return!0;return!1;default:return!0}}});var $r=T((iu,vr)=>{"use strict";var ai=Ae(),ci=gr();vr.exports=function(e,t,r){return li(e)&&(typeof t=="string"||Array.isArray(t))?ci(ai(e,t,r)):!1};function li(e){return e!=null&&(typeof e=="object"||typeof e=="function"||Array.isArray(e))}});var wr=T((uu,br)=>{"use strict";function ft(e,t){if(!e)return!0;let r=t||ft.keywords;Array.isArray(r)||(r=[r]);let n=typeof e=="string"?e.toLowerCase():null;for(let i of r)if(i===e||i===n)return!0;return!1}ft.keywords=["0","false","nada","nil","nay","nah","negative","no","none","nope","nul","null","nix","nyet","uh-uh","veto","zero"];br.exports=ft});var xr=T((su,Or)=>{"use strict";Or.exports=function(t){let r=Math.abs(t);if(isNaN(r))throw new TypeError("expected a number");if(!Number.isInteger(r))throw new Error("expected an integer");if(!Number.isSafeInteger(r))throw new Error("value exceeds maximum safe integer");return r%2===1}});var Mr=T((fu,Nr)=>{"use strict";var di=$r(),k=te(),hi=Me(),Sr=wr(),jr=xr(),_=Nr.exports;_.and=function(){for(var e=arguments.length-1,t=arguments[e],r=!0,n=0;n":i=e>r;break;case"<=":i=e<=r;break;case">=":i=e>=r;break;case"typeof":i=typeof e===r;break;default:throw new Error("helper {{compare}}: invalid operator: `"+t+"`")}return k.value(i,this,n)};_.contains=function(e,t,r,n){typeof r=="object"&&(n=r,r=void 0);var i=hi.contains(e,t,r);return k.value(i,this,n)};_.default=function(){for(var e=0;et,this,r)};_.gte=function(e,t,r){return arguments.length===2&&(r=t,t=r.hash.compare),k.value(e>=t,this,r)};_.has=function(e,t,r){return k.isOptions(e)&&(r=e,t=null,e=null),k.isOptions(t)&&(r=t,t=null),e===null?k.value(!1,this,r):arguments.length===2?k.value(di(this,e),this,r):(Array.isArray(e)||k.isString(e))&&k.isString(t)&&e.indexOf(t)>-1?k.fn(!0,this,r):k.isObject(e)&&k.isString(t)&&t in e?k.fn(!0,this,r):k.inverse(!1,this,r)};_.isFalsey=function(e,t){return k.value(Sr(e),this,t)};_.isTruthy=function(e,t){return k.value(!Sr(e),this,t)};_.ifEven=function(e,t){return k.value(!jr(e),this,t)};_.ifNth=function(e,t,r){var n=!isNaN(e)&&!isNaN(t)&&t%e===0;return k.value(n,this,r)};_.ifOdd=function(e,t){return k.value(jr(e),this,t)};_.is=function(e,t,r){return arguments.length===2&&(r=t,t=r.hash.compare),k.value(e==t,this,r)};_.isnt=function(e,t,r){return arguments.length===2&&(r=t,t=r.hash.compare),k.value(e!=t,this,r)};_.lt=function(e,t,r){return arguments.length===2&&(r=t,t=r.hash.compare),k.value(e=t,this,r)};_.unlessGteq=function(e,t,r){return k.isOptions(t)&&(r=t,t=r.hash.compare),k.value(et,this,r)}});var kr=T((ou,Ar)=>{var pi=et(),mi=Object.prototype.toString;Ar.exports=function(t){if(typeof t>"u")return"undefined";if(t===null)return"null";if(t===!0||t===!1||t instanceof Boolean)return"boolean";if(typeof t=="string"||t instanceof String)return"string";if(typeof t=="number"||t instanceof Number)return"number";if(typeof t=="function"||t instanceof Function)return"function";if(typeof Array.isArray<"u"&&Array.isArray(t))return"array";if(t instanceof RegExp)return"regexp";if(t instanceof Date)return"date";var r=mi.call(t);return r==="[object RegExp]"?"regexp":r==="[object Date]"?"date":r==="[object Arguments]"?"arguments":r==="[object Error]"?"error":pi(t)?"buffer":r==="[object Set]"?"set":r==="[object WeakSet]"?"weakset":r==="[object Map]"?"map":r==="[object WeakMap]"?"weakmap":r==="[object Symbol]"?"symbol":r==="[object Int8Array]"?"int8array":r==="[object Uint8Array]"?"uint8array":r==="[object Uint8ClampedArray]"?"uint8clampedarray":r==="[object Int16Array]"?"int16array":r==="[object Uint16Array]"?"uint16array":r==="[object Int32Array]"?"int32array":r==="[object Uint32Array]"?"uint32array":r==="[object Float32Array]"?"float32array":r==="[object Float64Array]"?"float64array":"object"}});var Ur=T((au,Dr)=>{"use strict";var yi=kr();Dr.exports=function(t){var r=yi(t);if(r!=="number"&&r!=="string")return!1;var n=+t;return n-n+1>=0&&t!==""}});var Yr=T((cu,Tr)=>{"use strict";var gi=Ur();Tr.exports=function(t,r){if(!r)return t;if(!t)return{};for(var n=String(r).split(/[[.\]]/).filter(Boolean),i=n[n.length-1],u={};r=n.shift();)if(t=t[r],!t)return{};return gi(i)?[t]:(u[i]=t,u)}});var Ir=T((lu,qr)=>{"use strict";var vi=Object.hasOwnProperty,$e=te(),$i=ke(),R=qr.exports,bi=Ae(),Cr=Yr(),Er=ut();R.extend=function(){var e=[].slice.call(arguments),t={};$e.isOptions(e[e.length-1])&&(t=e.pop().hash,e.push(t));for(var r={},n=0;n{"use strict";var wi=te(),Wr=_r.exports,Oi=Ne();Wr.toRegex=function(e,t,r){var n=wi.options({},t,r);return new RegExp(e,n.flags)};Wr.test=function(e,t){if(typeof e!="string")return!1;if(Oi(t)!=="regexp")throw new TypeError("expected a regular expression");return t.test(e)}});function be(){return De>Ue.length-16&&(Hr.default.randomFillSync(Ue),De=0),Ue.slice(De,De+=16)}var Hr,Ue,De,ot=V(()=>{Hr=F(oe("crypto")),Ue=new Uint8Array(256),De=Ue.length});var Br,zr=V(()=>{Br=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i});function xi(e){return typeof e=="string"&&Br.test(e)}var ue,we=V(()=>{zr();ue=xi});function he(e,t=0){return G[e[t+0]]+G[e[t+1]]+G[e[t+2]]+G[e[t+3]]+"-"+G[e[t+4]]+G[e[t+5]]+"-"+G[e[t+6]]+G[e[t+7]]+"-"+G[e[t+8]]+G[e[t+9]]+"-"+G[e[t+10]]+G[e[t+11]]+G[e[t+12]]+G[e[t+13]]+G[e[t+14]]+G[e[t+15]]}function Si(e,t=0){let r=he(e,t);if(!ue(r))throw TypeError("Stringified UUID is invalid");return r}var G,Lr,Oe=V(()=>{we();G=[];for(let e=0;e<256;++e)G.push((e+256).toString(16).slice(1));Lr=Si});function ji(e,t,r){let n=t&&r||0,i=t||new Array(16);e=e||{};let u=e.node||Pr,s=e.clockseq!==void 0?e.clockseq:at;if(u==null||s==null){let v=e.random||(e.rng||be)();u==null&&(u=Pr=[v[0]|1,v[1],v[2],v[3],v[4],v[5]]),s==null&&(s=at=(v[6]<<8|v[7])&16383)}let f=e.msecs!==void 0?e.msecs:Date.now(),w=e.nsecs!==void 0?e.nsecs:lt+1,y=f-ct+(w-lt)/1e4;if(y<0&&e.clockseq===void 0&&(s=s+1&16383),(y<0||f>ct)&&e.nsecs===void 0&&(w=0),w>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");ct=f,lt=w,at=s,f+=122192928e5;let l=((f&268435455)*1e4+w)%4294967296;i[n++]=l>>>24&255,i[n++]=l>>>16&255,i[n++]=l>>>8&255,i[n++]=l&255;let x=f/4294967296*1e4&268435455;i[n++]=x>>>8&255,i[n++]=x&255,i[n++]=x>>>24&15|16,i[n++]=x>>>16&255,i[n++]=s>>>8|128,i[n++]=s&255;for(let v=0;v<6;++v)i[n+v]=u[v];return t||he(i)}var Pr,at,ct,lt,Rr,Gr=V(()=>{ot();Oe();ct=0,lt=0;Rr=ji});function Ni(e){if(!ue(e))throw TypeError("Invalid UUID");let t,r=new Uint8Array(16);return r[0]=(t=parseInt(e.slice(0,8),16))>>>24,r[1]=t>>>16&255,r[2]=t>>>8&255,r[3]=t&255,r[4]=(t=parseInt(e.slice(9,13),16))>>>8,r[5]=t&255,r[6]=(t=parseInt(e.slice(14,18),16))>>>8,r[7]=t&255,r[8]=(t=parseInt(e.slice(19,23),16))>>>8,r[9]=t&255,r[10]=(t=parseInt(e.slice(24,36),16))/1099511627776&255,r[11]=t/4294967296&255,r[12]=t>>>24&255,r[13]=t>>>16&255,r[14]=t>>>8&255,r[15]=t&255,r}var Te,dt=V(()=>{we();Te=Ni});function Mi(e){e=unescape(encodeURIComponent(e));let t=[];for(let r=0;r{Oe();dt();Ai="6ba7b810-9dad-11d1-80b4-00c04fd430c8",ki="6ba7b811-9dad-11d1-80b4-00c04fd430c8"});function Di(e){return Array.isArray(e)?e=Buffer.from(e):typeof e=="string"&&(e=Buffer.from(e,"utf8")),Jr.default.createHash("md5").update(e).digest()}var Jr,Zr,Vr=V(()=>{Jr=F(oe("crypto"));Zr=Di});var Ui,Qr,Xr=V(()=>{ht();Vr();Ui=xe("v3",48,Zr),Qr=Ui});var Kr,pt,en=V(()=>{Kr=F(oe("crypto")),pt={randomUUID:Kr.default.randomUUID}});function Ti(e,t,r){if(pt.randomUUID&&!t&&!e)return pt.randomUUID();e=e||{};let n=e.random||(e.rng||be)();if(n[6]=n[6]&15|64,n[8]=n[8]&63|128,t){r=r||0;for(let i=0;i<16;++i)t[r+i]=n[i];return t}return he(n)}var tn,rn=V(()=>{en();ot();Oe();tn=Ti});function Yi(e){return Array.isArray(e)?e=Buffer.from(e):typeof e=="string"&&(e=Buffer.from(e,"utf8")),nn.default.createHash("sha1").update(e).digest()}var nn,un,sn=V(()=>{nn=F(oe("crypto"));un=Yi});var Ci,fn,on=V(()=>{ht();sn();Ci=xe("v5",80,un),fn=Ci});var an,cn=V(()=>{an="00000000-0000-0000-0000-000000000000"});function Ei(e){if(!ue(e))throw TypeError("Invalid UUID");return parseInt(e.slice(14,15),16)}var ln,dn=V(()=>{we();ln=Ei});var hn={};mt(hn,{NIL:()=>an,parse:()=>Te,stringify:()=>Lr,v1:()=>Rr,v3:()=>Qr,v4:()=>tn,v5:()=>fn,validate:()=>ue,version:()=>ln});var pn=V(()=>{Gr();Xr();rn();on();cn();dn();we();Oe();dt()});var yn=T((Xu,mn)=>{var qi=(pn(),gt(hn)),Ii=mn.exports;Ii.uuid=function(){return qi.v4()}});var Bi={};mt(Bi,{default:()=>Hi});var X=F(vt()),Mt=F($t()),At=F(bt()),kt=F(wt()),Dt=F(Ot()),Ut=F(xt()),Tt=F(St()),Yt=F(jt()),Ct=F(Nt());X.default.extend(Mt.default);X.default.extend(At.default);X.default.extend(kt.default);X.default.extend(Dt.default);X.default.extend(Ut.default);X.default.extend(Tt.default);X.default.extend(Yt.default);X.default.extend(Ct.default);function ae(e){return typeof e=="object"&&typeof e.hash=="object"}function Qe(e){return typeof e=="object"&&typeof e.options=="object"&&typeof e.app=="object"}function Xe(e,t,r){if(ae(e))return Xe({},t,e);if(ae(t))return Xe(e,r,t);let n=Qe(e)?e.context:{};r=r||{},ae(r)||(t=Object.assign({},t,r)),ae(r)&&r.hash.root===!0&&(t=Object.assign({},r.data.root,t));let i=Object.assign({},n,t,r.hash);return Qe(e)||(i=Object.assign({},e,i)),Qe(e)&&e.view&&e.view.data&&(i=Object.assign({},i,e.view.data)),i}function Ke(e,t,r){return ae(t)&&(r=t,t=null),ae(e)&&(r=e,t=null,e=null),{str:e,pattern:t,options:r}}function Et(e,t,r){let n=Ke(e,t,r),i={lang:"en",date:new Date(n.str)},u=Xe(this,i,{});X.default.locale(u.lang||u.language)}var qt=(e,t,r)=>{let n=Ke(e,t,r);if(n.str==null&&n.pattern==null)return X.default.locale("en"),(0,X.default)().format("MMMM DD, YYYY");Et(n.str,n.pattern,n.options);let i=(0,X.default)(new Date(n.str));return typeof n.options=="string"?i=n.options.toLowerCase()==="utc"?i.utc():i.tz(n.options):i=i.tz(X.default.tz.guess()),n.pattern===""?i.toISOString():i.format(n.pattern)},It=(e,t,r)=>{let n=Ke(e,t);Et(n.str,n.pattern);let i=X.default.duration(n.str,n.pattern);return r&&!ae(r)?i.format(r):i.humanize()};var gn=F(Kt()),vn=F(ke()),$n=F(cr()),bn=F(dr()),wn=F(mr()),On=F(Mr()),xn=F(Ir()),Sn=F(Fr()),jn=F(yn()),Wi={math:gn.default,array:vn.default,number:$n.default,url:bn.default,string:wn.default,comparison:On.default,object:xn.default,regex:Sn.default,uuid:jn.default},_i=["sortBy"],Fi={date:qt,duration:It},ne;function Nn(){if(ne)return ne;ne={};for(let e of Object.values(Wi))for(let[t,r]of Object.entries(e))ne[t]=(...n)=>r(...n,{});ne={...ne,...Fi};for(let e of _i)delete ne[e];return Object.freeze(ne),ne}var Hi={...Nn(),stripProtocol:helpersStripProtocol};return gt(Bi);})(); /*! Bundled license information: is-buffer/index.js: diff --git a/packages/server/src/jsRunner/bundles/index-helpers.ts b/packages/server/src/jsRunner/bundles/index-helpers.ts index a8992294a9..9e95dbf0a4 100644 --- a/packages/server/src/jsRunner/bundles/index-helpers.ts +++ b/packages/server/src/jsRunner/bundles/index-helpers.ts @@ -1,6 +1,5 @@ -const { - getJsHelperList, -} = require("../../../../string-templates/src/helpers/list.js") +// eslint-disable-next-line local-rules/no-budibase-imports +import { getJsHelperList } from "@budibase/string-templates/src/helpers/list" export default { ...getJsHelperList(), diff --git a/packages/server/src/jsRunner/bundles/index.ts b/packages/server/src/jsRunner/bundles/index.ts index f7685206a6..94a40fea29 100644 --- a/packages/server/src/jsRunner/bundles/index.ts +++ b/packages/server/src/jsRunner/bundles/index.ts @@ -1,5 +1,3 @@ -import { utils } from "@budibase/shared-core" -import environment from "../../environment" import fs from "fs" export const enum BundleType { diff --git a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js index bad9049af0..4dff030c0b 100644 --- a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js +++ b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js @@ -1,3 +1,3 @@ -"use strict";var snippets=(()=>{var u=Object.create;var n=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var l=(i,e)=>()=>(e||i((e={exports:{}}).exports,e),e.exports),W=(i,e)=>{for(var p in e)n(i,p,{get:e[p],enumerable:!0})},f=(i,e,p,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of h(e))!C.call(i,t)&&t!==p&&n(i,t,{get:()=>e[t],enumerable:!(r=a(e,t))||r.enumerable});return i};var d=(i,e,p)=>(p=i!=null?u(x(i)):{},f(e||!i||!i.__esModule?n(p,"default",{value:i,enumerable:!0}):p,i)),g=i=>f(n({},"__esModule",{value:!0}),i);var s=l((D,o)=>{o.exports.iifeWrapper=i=>`(function(){ +"use strict";var snippets=(()=>{var p=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var s=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var u=(i,e)=>{for(var n in e)p(i,n,{get:e[n],enumerable:!0})},a=(i,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of s(e))!c.call(i,t)&&t!==n&&p(i,t,{get:()=>e[t],enumerable:!(r=f(e,t))||r.enumerable});return i};var h=i=>a(p({},"__esModule",{value:!0}),i);var C={};u(C,{default:()=>x});var o=i=>`(function(){ ${i} -})();`});var w={};W(w,{default:()=>v});var c=d(s()),v=new Proxy({},{get:function(i,e){return e in snippetCache||(snippetCache[e]=[eval][0]((0,c.iifeWrapper)(snippetDefinitions[e]))),snippetCache[e]}});return g(w);})(); +})();`;var x=new Proxy({},{get:function(i,e){return e in snippetCache||(snippetCache[e]=[eval][0](o(snippetDefinitions[e]))),snippetCache[e]}});return h(C);})(); diff --git a/packages/server/src/jsRunner/bundles/snippets.ts b/packages/server/src/jsRunner/bundles/snippets.ts index 258d501a27..8244b2eef8 100644 --- a/packages/server/src/jsRunner/bundles/snippets.ts +++ b/packages/server/src/jsRunner/bundles/snippets.ts @@ -12,12 +12,15 @@ export default new Proxy( // See https://esbuild.github.io/content-types/#direct-eval for info on // why eval is being called this way. // Snippets are cached and reused once they have been evaluated. - // @ts-ignore + // @ts-expect-error snippetDefinitions and snippetCache are injected to the global scope + // eslint-disable-next-line no-undef if (!(name in snippetCache)) { - // @ts-ignore + // @ts-expect-error snippetDefinitions and snippetCache are injected to the global scope + // eslint-disable-next-line no-undef snippetCache[name] = [eval][0](iifeWrapper(snippetDefinitions[name])) } - // @ts-ignore + // @ts-expect-error snippetDefinitions and snippetCache are injected to the global scope + // eslint-disable-next-line no-undef return snippetCache[name] }, } diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index b5c8e036f6..7065febcb4 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -8,11 +8,10 @@ import { import { context, logging } from "@budibase/backend-core" import tracer from "dd-trace" import { IsolatedVM } from "./vm" -import type { VM } from "@budibase/types" export function init() { setJSRunner((js: string, ctx: Record) => { - return tracer.trace("runJS", {}, span => { + return tracer.trace("runJS", {}, () => { try { // Reuse an existing isolate from context, or make a new one const bbCtx = context.getCurrentContext() @@ -36,6 +35,7 @@ export function init() { // Because we can't pass functions into an Isolate, we remove them from // the passed context and rely on the withHelpers() method to add them // back in. + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { helpers, snippets, ...rest } = ctx return vm.withContext(rest, () => vm.execute(js)) } catch (error: any) { diff --git a/packages/server/src/jsRunner/vm/isolated-vm.ts b/packages/server/src/jsRunner/vm/isolated-vm.ts index e89d420ec5..833faa3ebd 100644 --- a/packages/server/src/jsRunner/vm/isolated-vm.ts +++ b/packages/server/src/jsRunner/vm/isolated-vm.ts @@ -170,7 +170,8 @@ export class IsolatedVM implements VM { } decode(...input: any) { - // @ts-ignore + // @ts-expect-error - this is going to run in the isolate, where this function will be available + // eslint-disable-next-line no-undef return textDecoderCb({ constructorArgs: this.constructorArgs, functionArgs: input, diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index fe7f6bb6fe..ec8a3711cf 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -120,7 +120,7 @@ const authorized = !!subResourceId && (await sdk.permissions.getResourcePerms(subResourceId)) - function getPermLevel(permLevel: string) { + const getPermLevel = (permLevel: string) => { let result: string[] = [] if (permissions[permLevel]) { result.push(permissions[permLevel].role) diff --git a/packages/server/src/middleware/cleanup.ts b/packages/server/src/middleware/cleanup.ts index 43f945ab6b..fa04743cee 100644 --- a/packages/server/src/middleware/cleanup.ts +++ b/packages/server/src/middleware/cleanup.ts @@ -13,7 +13,7 @@ export default async (ctx: Ctx, next: any) => { let errors = [] for (let fn of current.cleanup) { try { - await tracer.trace("cleanup", async span => { + await tracer.trace("cleanup", async () => { await fn() }) } catch (e) { diff --git a/packages/server/src/middleware/tests/authorized.spec.ts b/packages/server/src/middleware/tests/authorized.spec.ts index 72c286cb01..79cfeca54e 100644 --- a/packages/server/src/middleware/tests/authorized.spec.ts +++ b/packages/server/src/middleware/tests/authorized.spec.ts @@ -11,7 +11,6 @@ import { import authorizedMiddleware from "../authorized" import env from "../../environment" -import { generateTableID, generateViewID } from "../../db/utils" import { generator, mocks } from "@budibase/backend-core/tests" import { initProMocks } from "../../tests/utilities/mocks/pro" import { getResourcePerms } from "../../sdk/app/permissions" diff --git a/packages/server/src/middleware/trimViewRowInfo.ts b/packages/server/src/middleware/trimViewRowInfo.ts index 95b085a08f..4c382e6123 100644 --- a/packages/server/src/middleware/trimViewRowInfo.ts +++ b/packages/server/src/middleware/trimViewRowInfo.ts @@ -32,10 +32,7 @@ export default async (ctx: Ctx, next: Next) => { } // have to mutate the koa context, can't return -export async function trimViewFields( - body: Row, - viewId: string -): Promise { +export async function trimViewFields(body: Row, viewId: string): Promise { const view = await sdk.views.get(viewId) const allowedKeys = sdk.views.allowedFields(view) // have to mutate the context, can't update reference diff --git a/packages/server/src/migrations/functions/backfill/global/users.ts b/packages/server/src/migrations/functions/backfill/global/users.ts index 9f536a97a5..b3dae822d7 100644 --- a/packages/server/src/migrations/functions/backfill/global/users.ts +++ b/packages/server/src/migrations/functions/backfill/global/users.ts @@ -43,7 +43,7 @@ export const backfill = async ( } if (user.roles) { - for (const [appId, role] of Object.entries(user.roles)) { + for (const [, role] of Object.entries(user.roles)) { await events.role.assigned(user, role, timestamp) } } diff --git a/packages/server/src/migrations/index.ts b/packages/server/src/migrations/index.ts index 61ae75adf1..a66d793142 100644 --- a/packages/server/src/migrations/index.ts +++ b/packages/server/src/migrations/index.ts @@ -11,7 +11,6 @@ import env from "../environment" // migration functions import * as userEmailViewCasing from "./functions/userEmailViewCasing" import * as syncQuotas from "./functions/syncQuotas" -import * as syncUsers from "./functions/usageQuotas/syncUsers" import * as appUrls from "./functions/appUrls" import * as tableSettings from "./functions/tableSettings" import * as backfill from "./functions/backfill" diff --git a/packages/server/src/sdk/app/applications/sync.ts b/packages/server/src/sdk/app/applications/sync.ts index e73b3396d9..44e44a5aaa 100644 --- a/packages/server/src/sdk/app/applications/sync.ts +++ b/packages/server/src/sdk/app/applications/sync.ts @@ -3,11 +3,7 @@ import { db as dbCore, context, logging, roles } from "@budibase/backend-core" import { User, ContextUser, UserGroup } from "@budibase/types" import { sdk as proSdk } from "@budibase/pro" import sdk from "../../" -import { - getGlobalUsers, - getRawGlobalUsers, - processUser, -} from "../../../utilities/global" +import { getRawGlobalUsers, processUser } from "../../../utilities/global" import { generateUserMetadataID, InternalTables } from "../../../db/utils" type DeletedUser = { _id: string; deleted: boolean } diff --git a/packages/server/src/sdk/app/applications/tests/sync.spec.ts b/packages/server/src/sdk/app/applications/tests/sync.spec.ts index a53bdb0bd7..69ff2bfaf3 100644 --- a/packages/server/src/sdk/app/applications/tests/sync.spec.ts +++ b/packages/server/src/sdk/app/applications/tests/sync.spec.ts @@ -6,7 +6,7 @@ import EventEmitter from "events" import { UserGroup, UserMetadata, UserRoles, User } from "@budibase/types" const config = new TestConfiguration() -let app, group: UserGroup, groupUser: User +let group: UserGroup, groupUser: User const ROLE_ID = roles.BUILTIN_ROLE_IDS.BASIC const emitter = new EventEmitter() @@ -36,7 +36,7 @@ function waitForUpdate(opts: { group?: boolean }) { } beforeAll(async () => { - app = await config.init("syncApp") + await config.init("syncApp") }) async function createUser(email: string, roles: UserRoles, builder?: boolean) { diff --git a/packages/server/src/sdk/app/datasources/datasources.ts b/packages/server/src/sdk/app/datasources/datasources.ts index fd0d291d91..336a94636b 100644 --- a/packages/server/src/sdk/app/datasources/datasources.ts +++ b/packages/server/src/sdk/app/datasources/datasources.ts @@ -30,9 +30,15 @@ import { } from "../../../db/utils" import sdk from "../../index" import { setupCreationAuth as googleSetupCreationAuth } from "../../../integrations/googlesheets" +import { helpers } from "@budibase/shared-core" const ENV_VAR_PREFIX = "env." +function addDatasourceFlags(datasource: Datasource) { + datasource.isSQL = helpers.isSQL(datasource) + return datasource +} + export async function fetch(opts?: { enriched: boolean }): Promise { @@ -56,7 +62,7 @@ export async function fetch(opts?: { } as Datasource // Get external datasources - const datasources = ( + let datasources = ( await db.allDocs( getDatasourceParams(null, { include_docs: true, @@ -75,6 +81,7 @@ export async function fetch(opts?: { } } + datasources = datasources.map(datasource => addDatasourceFlags(datasource)) if (opts?.enriched) { const envVars = await getEnvironmentVariables() const promises = datasources.map(datasource => @@ -150,6 +157,7 @@ async function enrichDatasourceWithValues( } export async function enrich(datasource: Datasource) { + datasource = addDatasourceFlags(datasource) const { datasource: response } = await enrichDatasourceWithValues(datasource) return response } @@ -159,7 +167,8 @@ export async function get( opts?: { enriched: boolean } ): Promise { const appDb = context.getAppDB() - const datasource = await appDb.get(datasourceId) + let datasource = await appDb.get(datasourceId) + datasource = addDatasourceFlags(datasource) if (opts?.enriched) { return (await enrichDatasourceWithValues(datasource)).datasource } else { @@ -271,13 +280,14 @@ export function mergeConfigs(update: Datasource, old: Datasource) { export async function getExternalDatasources(): Promise { const db = context.getAppDB() - const externalDatasources = await db.allDocs( + let dsResponse = await db.allDocs( getDatasourcePlusParams(undefined, { include_docs: true, }) ) - return externalDatasources.rows.map(r => r.doc!) + const externalDatasources = dsResponse.rows.map(r => r.doc!) + return externalDatasources.map(datasource => addDatasourceFlags(datasource)) } export async function save( @@ -290,11 +300,11 @@ export async function save( const fetchSchema = opts?.fetchSchema || false const tablesFilter = opts?.tablesFilter || [] - datasource = { + datasource = addDatasourceFlags({ _id: generateDatasourceID({ plus }), ...datasource, type: plus ? DocumentType.DATASOURCE_PLUS : DocumentType.DATASOURCE, - } + }) let errors: Record = {} if (fetchSchema) { diff --git a/packages/server/src/sdk/app/permissions/index.ts b/packages/server/src/sdk/app/permissions/index.ts index a6d8f338a1..18a376aaf0 100644 --- a/packages/server/src/sdk/app/permissions/index.ts +++ b/packages/server/src/sdk/app/permissions/index.ts @@ -1,4 +1,4 @@ -import { db, env, roles } from "@budibase/backend-core" +import { db, roles } from "@budibase/backend-core" import { features } from "@budibase/pro" import { DocumentType, @@ -133,7 +133,7 @@ export async function getDependantResources( } const permissions = await getResourcePerms(view.id) - for (const [level, roleInfo] of Object.entries(permissions)) { + for (const [, roleInfo] of Object.entries(permissions)) { if (roleInfo.type === PermissionSource.INHERITED) { dependants[VirtualDocumentType.VIEW] ??= new Set() dependants[VirtualDocumentType.VIEW].add(view.id) diff --git a/packages/server/src/sdk/app/rows/search/tests/external.spec.ts b/packages/server/src/sdk/app/rows/search/tests/external.spec.ts index bae84592ca..8ecec784dd 100644 --- a/packages/server/src/sdk/app/rows/search/tests/external.spec.ts +++ b/packages/server/src/sdk/app/rows/search/tests/external.spec.ts @@ -17,8 +17,6 @@ import { generator, } from "@budibase/backend-core/tests" -jest.unmock("mysql2/promise") - jest.setTimeout(30000) describe("external search", () => { diff --git a/packages/server/src/sdk/app/rows/search/utils.ts b/packages/server/src/sdk/app/rows/search/utils.ts index 5d93dcaca2..086599665b 100644 --- a/packages/server/src/sdk/app/rows/search/utils.ts +++ b/packages/server/src/sdk/app/rows/search/utils.ts @@ -66,8 +66,8 @@ export function searchInputMapping(table: Table, options: SearchParams) { } for (let [key, column] of Object.entries(table.schema)) { switch (column.type) { - case FieldType.BB_REFERENCE: - const subtype = column.subtype as FieldSubtype + case FieldType.BB_REFERENCE: { + const subtype = column.subtype switch (subtype) { case FieldSubtype.USER: case FieldSubtype.USERS: @@ -77,6 +77,7 @@ export function searchInputMapping(table: Table, options: SearchParams) { utils.unreachable(subtype) } break + } } } return options diff --git a/packages/server/src/sdk/app/rows/utils.ts b/packages/server/src/sdk/app/rows/utils.ts index 6e3e25364e..8aa017d238 100644 --- a/packages/server/src/sdk/app/rows/utils.ts +++ b/packages/server/src/sdk/app/rows/utils.ts @@ -14,7 +14,7 @@ import { makeExternalQuery } from "../../../integrations/base/query" import { Format } from "../../../api/controllers/view/exporters" import sdk from "../.." import { isRelationshipColumn } from "../../../db/utils" -import { SqlClient } from "../../../integrations/utils" +import { SqlClient, isSQL } from "../../../integrations/utils" const SQL_CLIENT_SOURCE_MAP: Record = { [SourceName.POSTGRES]: SqlClient.POSTGRES, @@ -37,7 +37,7 @@ const SQL_CLIENT_SOURCE_MAP: Record = { } export function getSQLClient(datasource: Datasource): SqlClient { - if (!datasource.isSQL) { + if (!isSQL(datasource)) { throw new Error("Cannot get SQL Client for non-SQL datasource") } const lookup = SQL_CLIENT_SOURCE_MAP[datasource.source] diff --git a/packages/server/src/sdk/app/views/tests/views.spec.ts b/packages/server/src/sdk/app/views/tests/views.spec.ts index 508285651a..a610d34ec2 100644 --- a/packages/server/src/sdk/app/views/tests/views.spec.ts +++ b/packages/server/src/sdk/app/views/tests/views.spec.ts @@ -351,6 +351,7 @@ describe("table sdk", () => { const view: ViewV2 = { ...basicView, } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { name, description, ...newTableSchema } = basicTable.schema const result = syncSchema(_.cloneDeep(view), newTableSchema, undefined) @@ -364,6 +365,7 @@ describe("table sdk", () => { const view: ViewV2 = { ...basicView, } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { description, ...newTableSchema } = { ...basicTable.schema, updatedDescription: { @@ -448,6 +450,7 @@ describe("table sdk", () => { hiddenField: { visible: false }, }, } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { name, description, ...newTableSchema } = basicTable.schema const result = syncSchema(_.cloneDeep(view), newTableSchema, undefined) @@ -471,6 +474,7 @@ describe("table sdk", () => { hiddenField: { visible: false }, }, } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { name, description, ...newTableSchema } = { ...basicTable.schema, newField1: { @@ -502,6 +506,7 @@ describe("table sdk", () => { hiddenField: { visible: false }, }, } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { description, ...newTableSchema } = { ...basicTable.schema, updatedDescription: { diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index d17535a72f..22c03016c7 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -49,7 +49,6 @@ import { AuthToken, Automation, CreateViewRequest, - Ctx, Datasource, FieldType, INTERNAL_TABLE_SOURCE_ID, diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index bd6241b7cd..2bc2357551 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -6,7 +6,6 @@ import { PaginatedSearchRowResponse, } from "@budibase/types" import { Expectations, TestAPI } from "./base" -import { generator } from "@budibase/backend-core/tests" import sdk from "../../../sdk" export class ViewV2API extends TestAPI { diff --git a/packages/server/src/utilities/csv.ts b/packages/server/src/utilities/csv.ts index 477ebb896d..2fab1d11a4 100644 --- a/packages/server/src/utilities/csv.ts +++ b/packages/server/src/utilities/csv.ts @@ -9,9 +9,7 @@ export async function jsonFromCsvString(csvString: string) { // ignoreEmpty will remove the key completly if empty, so creating this empty object will ensure we return the values with the keys but empty values const result = await csv({ ignoreEmpty: false }).fromString(csvString) result.forEach((r, i) => { - for (const [key] of Object.entries(r).filter( - ([key, value]) => value === "" - )) { + for (const [key] of Object.entries(r).filter(([, value]) => value === "")) { if (castedWithEmptyValues[i][key] === undefined) { r[key] = null } diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts index c7b8998bad..a5fbfa981d 100644 --- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts +++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts @@ -7,7 +7,7 @@ const ROW_PREFIX = DocumentType.ROW + SEPARATOR export async function processInputBBReferences( value: string | string[] | { _id: string } | { _id: string }[], - subtype: FieldSubtype + subtype: FieldSubtype.USER | FieldSubtype.USERS ): Promise { let referenceIds: string[] = [] @@ -41,7 +41,7 @@ export async function processInputBBReferences( switch (subtype) { case FieldSubtype.USER: - case FieldSubtype.USERS: + case FieldSubtype.USERS: { const { notFoundIds } = await cache.user.getUsers(referenceIds) if (notFoundIds?.length) { @@ -53,7 +53,7 @@ export async function processInputBBReferences( } return referenceIds.join(",") || null - + } default: throw utils.unreachable(subtype) } @@ -61,7 +61,7 @@ export async function processInputBBReferences( export async function processOutputBBReferences( value: string | string[], - subtype: FieldSubtype + subtype: FieldSubtype.USER | FieldSubtype.USERS ) { if (value === null || value === undefined) { // Already processed or nothing to process @@ -73,7 +73,7 @@ export async function processOutputBBReferences( switch (subtype) { case FieldSubtype.USER: - case FieldSubtype.USERS: + case FieldSubtype.USERS: { const { users } = await cache.user.getUsers(ids) if (!users.length) { return undefined @@ -86,7 +86,7 @@ export async function processOutputBBReferences( firstName: u.firstName, lastName: u.lastName, })) - + } default: throw utils.unreachable(subtype) } diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index d956a94d0b..0015680e77 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -6,7 +6,6 @@ import { TYPE_TRANSFORM_MAP } from "./map" import { FieldType, AutoFieldSubType, - FieldSubtype, Row, RowAttachment, Table, @@ -159,10 +158,7 @@ export async function inputProcessing( } if (field.type === FieldType.BB_REFERENCE && value) { - clonedRow[key] = await processInputBBReferences( - value, - field.subtype as FieldSubtype - ) + clonedRow[key] = await processInputBBReferences(value, field.subtype) } } @@ -238,7 +234,7 @@ export async function outputProcessing( for (let row of enriched) { row[property] = await processOutputBBReferences( row[property], - column.subtype as FieldSubtype + column.subtype ) } } diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts index 500e922d65..85dfdd3506 100644 --- a/packages/server/src/utilities/schema.ts +++ b/packages/server/src/utilities/schema.ts @@ -1,26 +1,14 @@ -import { FieldType, FieldSubtype } from "@budibase/types" +import { + FieldType, + FieldSubtype, + TableSchema, + FieldSchema, + Row, +} from "@budibase/types" import { ValidColumnNameRegex, utils } from "@budibase/shared-core" import { db } from "@budibase/backend-core" import { parseCsvExport } from "../api/controllers/view/exporters" -interface SchemaColumn { - readonly name: string - readonly type: FieldType - readonly subtype: FieldSubtype - readonly autocolumn?: boolean - readonly constraints?: { - presence: boolean - } -} - -interface Schema { - readonly [index: string]: SchemaColumn -} - -interface Row { - [index: string]: any -} - type Rows = Array interface SchemaValidation { @@ -34,27 +22,10 @@ interface ValidationResults { errors: Record } -const PARSERS: any = { - [FieldType.NUMBER]: (attribute?: string) => { - if (!attribute) { - return attribute - } - return Number(attribute) - }, - [FieldType.DATETIME]: (attribute?: string) => { - if (!attribute) { - return attribute - } - return new Date(attribute).toISOString() - }, -} - -export function isSchema(schema: any): schema is Schema { +export function isSchema(schema: any): schema is TableSchema { return ( typeof schema === "object" && - Object.values(schema).every(rawColumn => { - const column = rawColumn as SchemaColumn - + Object.values(schema).every(column => { return ( column !== null && typeof column === "object" && @@ -69,7 +40,7 @@ export function isRows(rows: any): rows is Rows { return Array.isArray(rows) && rows.every(row => typeof row === "object") } -export function validate(rows: Rows, schema: Schema): ValidationResults { +export function validate(rows: Rows, schema: TableSchema): ValidationResults { const results: ValidationResults = { schemaValidation: {}, allValid: false, @@ -79,9 +50,11 @@ export function validate(rows: Rows, schema: Schema): ValidationResults { rows.forEach(row => { Object.entries(row).forEach(([columnName, columnData]) => { - const columnType = schema[columnName]?.type - const columnSubtype = schema[columnName]?.subtype - const isAutoColumn = schema[columnName]?.autocolumn + const { + type: columnType, + subtype: columnSubtype, + autocolumn: isAutoColumn, + } = schema[columnName] // If the column had an invalid value we don't want to override it if (results.schemaValidation[columnName] === false) { @@ -138,7 +111,7 @@ export function validate(rows: Rows, schema: Schema): ValidationResults { return results } -export function parse(rows: Rows, schema: Schema): Rows { +export function parse(rows: Rows, schema: TableSchema): Rows { return rows.map(row => { const parsedRow: Row = {} @@ -148,9 +121,7 @@ export function parse(rows: Rows, schema: Schema): Rows { return } - const columnType = schema[columnName].type - const columnSubtype = schema[columnName].subtype - + const { type: columnType, subtype: columnSubtype } = schema[columnName] if (columnType === FieldType.NUMBER) { // If provided must be a valid number parsedRow[columnName] = columnData ? Number(columnData) : columnData @@ -187,11 +158,11 @@ export function parse(rows: Rows, schema: Schema): Rows { function isValidBBReference( columnData: any, - columnSubtype: FieldSubtype + columnSubtype: FieldSubtype.USER | FieldSubtype.USERS ): boolean { switch (columnSubtype) { case FieldSubtype.USER: - case FieldSubtype.USERS: + case FieldSubtype.USERS: { if (typeof columnData !== "string") { return false } @@ -208,7 +179,7 @@ function isValidBBReference( user => !db.isGlobalUserID(user._id) ) return !constainsWrongId - + } default: throw utils.unreachable(columnSubtype) } diff --git a/packages/server/src/utilities/usageQuota/usageQuoteReset.ts b/packages/server/src/utilities/usageQuota/usageQuoteReset.ts deleted file mode 100644 index 579fa130d0..0000000000 --- a/packages/server/src/utilities/usageQuota/usageQuoteReset.ts +++ /dev/null @@ -1,18 +0,0 @@ -// UNUSED CODE -// Preserved for future use - -/* eslint-disable no-unused-vars */ - -function getNewQuotaReset() { - return Date.now() + 2592000000 -} - -function resetQuotasIfRequired(quota: { quotaReset: number; usageQuota: any }) { - // Check if the quota needs reset - if (Date.now() >= quota.quotaReset) { - quota.quotaReset = getNewQuotaReset() - for (let prop of Object.keys(quota.usageQuota)) { - quota.usageQuota[prop] = 0 - } - } -} diff --git a/packages/server/src/utilities/workerRequests.ts b/packages/server/src/utilities/workerRequests.ts index 91340c8d78..c3a0b0abfa 100644 --- a/packages/server/src/utilities/workerRequests.ts +++ b/packages/server/src/utilities/workerRequests.ts @@ -1,10 +1,4 @@ -import { - Response, - default as fetch, - type RequestInit, - Headers, - HeadersInit, -} from "node-fetch" +import { Response, default as fetch, type RequestInit } from "node-fetch" import env from "../environment" import { checkSlashesInUrl } from "./index" import { @@ -13,7 +7,6 @@ import { tenancy, logging, env as coreEnv, - utils, } from "@budibase/backend-core" import { Ctx, User, EmailInvite } from "@budibase/types" diff --git a/packages/server/src/websockets/websocket.ts b/packages/server/src/websockets/websocket.ts index 2861757de1..871122678d 100644 --- a/packages/server/src/websockets/websocket.ts +++ b/packages/server/src/websockets/websocket.ts @@ -262,10 +262,12 @@ export class BaseSocket { } } + // eslint-disable-next-line @typescript-eslint/no-unused-vars async onConnect(socket: Socket) { // Override } + // eslint-disable-next-line @typescript-eslint/no-unused-vars async onDisconnect(socket: Socket) { // Override } diff --git a/packages/server/tsconfig.build.json b/packages/server/tsconfig.build.json index e4552bdb18..c4b9b8788e 100644 --- a/packages/server/tsconfig.build.json +++ b/packages/server/tsconfig.build.json @@ -18,7 +18,8 @@ "@budibase/backend-core/*": ["../backend-core/*"], "@budibase/shared-core": ["../shared-core/src"], "@budibase/pro": ["../pro/src"], - "@budibase/string-templates": ["../string-templates/src"] + "@budibase/string-templates": ["../string-templates/src"], + "@budibase/string-templates/*": ["../string-templates/*"] }, "allowArbitraryExtensions": true }, diff --git a/packages/shared-core/jest.config.ts b/packages/shared-core/jest.config.ts new file mode 100644 index 0000000000..5c7c870b58 --- /dev/null +++ b/packages/shared-core/jest.config.ts @@ -0,0 +1,10 @@ +export default { + preset: "ts-jest", + testEnvironment: "node", + transform: { + "^.+\\.ts?$": "@swc/jest", + }, + moduleNameMapper: { + "@budibase/types": "/../types/src", + }, +} diff --git a/packages/shared-core/package.json b/packages/shared-core/package.json index 777896a7f7..c024d1b819 100644 --- a/packages/shared-core/package.json +++ b/packages/shared-core/package.json @@ -11,7 +11,9 @@ "build": "node ../../scripts/build.js && tsc -p tsconfig.build.json --emitDeclarationOnly --paths null", "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput", "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput", - "check:types": "tsc -p tsconfig.json --noEmit --paths null" + "check:types": "tsc -p tsconfig.json --noEmit --paths null", + "test": "jest", + "test:watch": "yarn test --watchAll" }, "dependencies": { "@budibase/types": "0.0.0", @@ -20,29 +22,5 @@ "devDependencies": { "rimraf": "3.0.2", "typescript": "5.2.2" - }, - "nx": { - "targets": { - "build": { - "dependsOn": [ - { - "projects": [ - "@budibase/types" - ], - "target": "build" - } - ] - }, - "dev": { - "dependsOn": [ - { - "projects": [ - "@budibase/types" - ], - "target": "build" - } - ] - } - } } } diff --git a/packages/shared-core/src/sdk/documents/users.ts b/packages/shared-core/src/sdk/documents/users.ts index 11e80dcf29..17aa8a1e58 100644 --- a/packages/shared-core/src/sdk/documents/users.ts +++ b/packages/shared-core/src/sdk/documents/users.ts @@ -46,10 +46,7 @@ export function isAdminOrBuilder( return isBuilder(user, appId) || isAdmin(user) } -export function isAdminOrGlobalBuilder( - user: User | ContextUser, - appId?: string -): boolean { +export function isAdminOrGlobalBuilder(user: User | ContextUser): boolean { return isGlobalBuilder(user) || isAdmin(user) } diff --git a/packages/shared-core/src/tests/cron.test.ts b/packages/shared-core/src/tests/cron.test.ts index d56165b2b8..3945acb565 100644 --- a/packages/shared-core/src/tests/cron.test.ts +++ b/packages/shared-core/src/tests/cron.test.ts @@ -1,4 +1,3 @@ -import { expect, describe, it } from "vitest" import { cron } from "../helpers" describe("check valid and invalid crons", () => { diff --git a/packages/shared-core/src/tests/filters.test.ts b/packages/shared-core/src/tests/filters.test.ts index de969562af..e74e37d681 100644 --- a/packages/shared-core/src/tests/filters.test.ts +++ b/packages/shared-core/src/tests/filters.test.ts @@ -5,7 +5,6 @@ import { SearchFilter, } from "@budibase/types" import { buildLuceneQuery, runLuceneQuery } from "../filters" -import { expect, describe, it, test } from "vitest" describe("runLuceneQuery", () => { const docs = [ @@ -194,7 +193,7 @@ describe("runLuceneQuery", () => { expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([2, 3]) }) - test.each([[523, 259], "523,259"])( + it.each([[523, 259], "523,259"])( "should return rows with matches on numeric oneOf filter", input => { const query = buildQuery({ @@ -209,7 +208,7 @@ describe("runLuceneQuery", () => { } ) - test.each([ + it.each([ [false, []], [true, [1, 2, 3]], ])("should return %s if allOr is %s ", (allOr, expectedResult) => { diff --git a/packages/shared-core/tsconfig.build.json b/packages/shared-core/tsconfig.build.json index 8a7f0ea216..13e298d71c 100644 --- a/packages/shared-core/tsconfig.build.json +++ b/packages/shared-core/tsconfig.build.json @@ -18,13 +18,6 @@ }, "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" }, - "include": ["**/*.js", "**/*.ts"], - "exclude": [ - "node_modules", - "dist", - "**/*.spec.ts", - "**/*.spec.js", - "__mocks__", - "src/tests" - ] + "include": ["src/**/*"], + "exclude": ["**/*.spec.ts", "**/*.spec.js", "__mocks__", "src/tests"] } diff --git a/packages/shared-core/tsconfig.json b/packages/shared-core/tsconfig.json index 5e3d49c8b9..d0c5134f1c 100644 --- a/packages/shared-core/tsconfig.json +++ b/packages/shared-core/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "baseUrl": "..", "rootDir": "src", - "composite": true + "composite": true, + "types": ["node", "jest"] }, "exclude": ["node_modules", "dist"] } diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 4a26b263a9..8cf8d92692 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -28,7 +28,7 @@ "dependencies": { "@budibase/handlebars-helpers": "^0.13.1", "dayjs": "^1.10.8", - "handlebars": "^4.7.6", + "handlebars": "^4.7.8", "lodash.clonedeep": "^4.5.0" }, "devDependencies": { diff --git a/packages/string-templates/src/helpers/Helper.ts b/packages/string-templates/src/helpers/Helper.ts index a1722ac2c8..f6ee3bfd06 100644 --- a/packages/string-templates/src/helpers/Helper.ts +++ b/packages/string-templates/src/helpers/Helper.ts @@ -1,3 +1,5 @@ +import Handlebars from "handlebars" + export default class Helper { private name: any private fn: any diff --git a/packages/string-templates/src/helpers/external.ts b/packages/string-templates/src/helpers/external.ts index 3a95406549..36d5ac0f54 100644 --- a/packages/string-templates/src/helpers/external.ts +++ b/packages/string-templates/src/helpers/external.ts @@ -3,6 +3,7 @@ import helpers from "@budibase/handlebars-helpers" import { date, duration } from "./date" import { HelperFunctionBuiltin } from "./constants" +import Handlebars from "handlebars" /** * full list of supported helpers can be found here: diff --git a/packages/string-templates/src/helpers/index.ts b/packages/string-templates/src/helpers/index.ts index 595440ad55..fe74a6d711 100644 --- a/packages/string-templates/src/helpers/index.ts +++ b/packages/string-templates/src/helpers/index.ts @@ -1,5 +1,5 @@ import Helper from "./Helper" -import { SafeString } from "handlebars" +import Handlebars from "handlebars" import * as externalHandlebars from "./external" import { processJS } from "./javascript" import { @@ -28,7 +28,7 @@ function isObject(value: string | any[]) { const HELPERS = [ // external helpers new Helper(HelperFunctionNames.OBJECT, (value: any) => { - return new SafeString(JSON.stringify(value)) + return new Handlebars.SafeString(JSON.stringify(value)) }), // javascript helper new Helper(HelperFunctionNames.JS, processJS, false), @@ -38,7 +38,7 @@ const HELPERS = [ (value: string, inputs: { __opts: any }) => { const { __opts } = inputs if (isObject(value)) { - return new SafeString(JSON.stringify(value)) + return new Handlebars.SafeString(JSON.stringify(value)) } // null/undefined values produce bad results if (__opts && __opts.onlyFound && value == null) { @@ -55,7 +55,7 @@ const HELPERS = [ if (__opts && __opts.escapeNewlines) { text = value.replace(/\n/g, "\\n") } - text = new SafeString(text.replace(/&/g, "&")) + text = new Handlebars.SafeString(text.replace(/&/g, "&")) if (text == null || typeof text !== "string") { return text } diff --git a/packages/string-templates/src/helpers/list.ts b/packages/string-templates/src/helpers/list.ts index 361558e04d..a7d6a502e7 100644 --- a/packages/string-templates/src/helpers/list.ts +++ b/packages/string-templates/src/helpers/list.ts @@ -1,17 +1,42 @@ import { date, duration } from "./date" +/* +@budibase/handlebars-helpers is not treeshakeable, so we can't use the barrel files. +Otherwise, we have issues when generating the isolated-vm bundle because of the treeshaking +*/ +/* eslint-disable local-rules/no-budibase-imports */ +// @ts-expect-error +import math from "@budibase/handlebars-helpers/lib/math" +// @ts-expect-error +import array from "@budibase/handlebars-helpers/lib/array" +// @ts-expect-error +import number from "@budibase/handlebars-helpers/lib/number" +// @ts-expect-error +import url from "@budibase/handlebars-helpers/lib/url" +// @ts-expect-error +import string from "@budibase/handlebars-helpers/lib/string" +// @ts-expect-error +import comparison from "@budibase/handlebars-helpers/lib/comparison" +// @ts-expect-error +import object from "@budibase/handlebars-helpers/lib/object" +// @ts-expect-error +import regex from "@budibase/handlebars-helpers/lib/regex" +// @ts-expect-error +import uuid from "@budibase/handlebars-helpers/lib/uuid" +/* eslint-enable local-rules/no-budibase-imports */ + // https://github.com/evanw/esbuild/issues/56 -const getExternalCollections = (): Record any> => ({ - math: require("@budibase/handlebars-helpers/lib/math"), - array: require("@budibase/handlebars-helpers/lib/array"), - number: require("@budibase/handlebars-helpers/lib/number"), - url: require("@budibase/handlebars-helpers/lib/url"), - string: require("@budibase/handlebars-helpers/lib/string"), - comparison: require("@budibase/handlebars-helpers/lib/comparison"), - object: require("@budibase/handlebars-helpers/lib/object"), - regex: require("@budibase/handlebars-helpers/lib/regex"), - uuid: require("@budibase/handlebars-helpers/lib/uuid"), -}) +const externalCollections = { + math, + array, + number, + url, + string, + comparison, + object, + regex, + uuid, +} export const helpersToRemoveForJs = ["sortBy"] @@ -28,15 +53,15 @@ export function getJsHelperList() { } helpers = {} - for (let collection of Object.values(getExternalCollections())) { - for (let [key, func] of Object.entries(collection)) { + for (let collection of Object.values(externalCollections)) { + for (let [key, func] of Object.entries(collection)) { // Handlebars injects the hbs options to the helpers by default. We are adding an empty {} as a last parameter to simulate it helpers[key] = (...props: any) => func(...props, {}) } } helpers = { ...helpers, - addedHelpers, + ...addedHelpers, } for (const toRemove of helpersToRemoveForJs) { diff --git a/packages/string-templates/src/index.ts b/packages/string-templates/src/index.ts index 3b0d7c0115..1ac91edd28 100644 --- a/packages/string-templates/src/index.ts +++ b/packages/string-templates/src/index.ts @@ -1,5 +1,5 @@ import { Context, createContext, runInNewContext } from "vm" -import { create } from "handlebars" +import { create, TemplateDelegate } from "handlebars" import { registerAll, registerMinimum } from "./helpers/index" import { preprocess, postprocess } from "./processors" import { @@ -47,7 +47,7 @@ function testObject(object: any) { /** * Creates a HBS template function for a given string, and optionally caches it. */ -const templateCache: Record> = {} +const templateCache: Record> = {} function createTemplate(string: string, opts?: ProcessOptions) { opts = { ...defaultOpts, ...opts } @@ -94,7 +94,7 @@ export async function processObject>( for (const key of Object.keys(object || {})) { if (object[key] != null) { const val = object[key] - let parsedValue + let parsedValue = val if (typeof val === "string") { parsedValue = await processString(object[key], context, opts) } else if (typeof val === "object") { diff --git a/packages/string-templates/test/basic.spec.ts b/packages/string-templates/test/basic.spec.ts index ae006f06f9..00058d4ecd 100644 --- a/packages/string-templates/test/basic.spec.ts +++ b/packages/string-templates/test/basic.spec.ts @@ -104,6 +104,26 @@ describe("Test that the object processing works correctly", () => { } expect(error).toBeNull() }) + + it("should be able to handle booleans", async () => { + const output = await processObject( + { + first: true, + second: "true", + third: "another string", + forth: "with {{ template }}", + }, + { + template: "value", + } + ) + expect(output).toEqual({ + first: true, + second: "true", + third: "another string", + forth: "with value", + }) + }) }) describe("check returning objects", () => { diff --git a/packages/string-templates/test/utils.ts b/packages/string-templates/test/utils.ts index c683acf5b3..957366fc59 100644 --- a/packages/string-templates/test/utils.ts +++ b/packages/string-templates/test/utils.ts @@ -34,7 +34,7 @@ export const getParsedManifest = () => { requiresBlock: boolean }>(manifest[collection]) .filter( - ([_, details]) => + ([, details]) => details.example?.split("->").map(x => x.trim()).length > 1 ) .map(([name, details]): ExampleType => { @@ -93,7 +93,7 @@ export const runJsHelpersTests = ({ describe.each(Object.keys(jsExamples))("%s", collection => { const examplesToRun = jsExamples[collection] - .filter(([_, { requiresHbsBody }]) => !requiresHbsBody) + .filter(([, { requiresHbsBody }]) => !requiresHbsBody) .filter(([key]) => !testsToSkip?.includes(key)) examplesToRun.length && diff --git a/packages/types/package.json b/packages/types/package.json index f4c7b13344..67b923ed54 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "description": "Budibase types", "main": "dist/index.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "author": "Budibase", "license": "GPL-3.0", "scripts": { diff --git a/packages/types/src/api/web/global/auditLogs.ts b/packages/types/src/api/web/global/auditLogs.ts index a890f3a751..bc6ab5724a 100644 --- a/packages/types/src/api/web/global/auditLogs.ts +++ b/packages/types/src/api/web/global/auditLogs.ts @@ -1,9 +1,5 @@ -import { Event, AuditedEventFriendlyName } from "../../../sdk" -import { - PaginationResponse, - PaginationRequest, - BasicPaginationRequest, -} from "../" +import { Event } from "../../../sdk" +import { PaginationResponse, BasicPaginationRequest } from "../" import { User, App } from "../../../" export interface AuditLogSearchParams { diff --git a/packages/types/src/documents/app/query.ts b/packages/types/src/documents/app/query.ts index 3227666bf3..535c5dab3b 100644 --- a/packages/types/src/documents/app/query.ts +++ b/packages/types/src/documents/app/query.ts @@ -1,5 +1,4 @@ import { Document } from "../document" -import type { Row } from "./row" export interface QuerySchema { name?: string diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts index 5f99787faf..c587590319 100644 --- a/packages/types/src/documents/app/row.ts +++ b/packages/types/src/documents/app/row.ts @@ -39,11 +39,16 @@ export interface Row extends Document { export enum FieldSubtype { USER = "user", USERS = "users", + SINGLE = "single", } +// The 'as' are required for typescript not to type the outputs as generic FieldSubtype export const FieldTypeSubtypes = { BB_REFERENCE: { - USER: FieldSubtype.USER, - USERS: FieldSubtype.USERS, + USER: FieldSubtype.USER as FieldSubtype.USER, + USERS: FieldSubtype.USERS as FieldSubtype.USERS, + }, + ATTACHMENT: { + SINGLE: FieldSubtype.SINGLE as FieldSubtype.SINGLE, }, } diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index 17abf747b2..45e39268ac 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -112,6 +112,12 @@ export interface BBReferenceFieldMetadata relationshipType?: RelationshipType } +export interface AttachmentFieldMetadata + extends Omit { + type: FieldType.ATTACHMENT + subtype?: FieldSubtype.SINGLE +} + export interface FieldConstraints { type?: string email?: boolean @@ -119,6 +125,7 @@ export interface FieldConstraints { length?: { minimum?: string | number | null maximum?: string | number | null + message?: string } numericality?: { greaterThanOrEqualTo: string | null @@ -156,6 +163,8 @@ interface OtherFieldMetadata extends BaseFieldSchema { | FieldType.FORMULA | FieldType.NUMBER | FieldType.LONGFORM + | FieldType.BB_REFERENCE + | FieldType.ATTACHMENT > } @@ -169,6 +178,7 @@ export type FieldSchema = | LongFormFieldMetadata | BBReferenceFieldMetadata | JsonFieldMetadata + | AttachmentFieldMetadata export interface TableSchema { [key: string]: FieldSchema @@ -203,3 +213,9 @@ export function isBBReferenceField( ): field is BBReferenceFieldMetadata { return field.type === FieldType.BB_REFERENCE } + +export function isAttachmentField( + field: FieldSchema +): field is AttachmentFieldMetadata { + return field.type === FieldType.ATTACHMENT +} diff --git a/packages/types/src/sdk/db.ts b/packages/types/src/sdk/db.ts index 4d103d5be6..12c86bd9ba 100644 --- a/packages/types/src/sdk/db.ts +++ b/packages/types/src/sdk/db.ts @@ -7,6 +7,7 @@ import { ViewTemplateOpts, } from "../" import { Writable } from "stream" +import type PouchDB from "pouchdb-find" export enum SearchIndex { ROWS = "rows", diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index a2fd5207b4..ad5356c2dc 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -1,8 +1,6 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "baseUrl": ".", - "rootDir": "./src", "composite": true }, "exclude": ["node_modules", "dist"] diff --git a/packages/worker/__mocks__/node-fetch.ts b/packages/worker/__mocks__/node-fetch.ts index 27c9b507a4..b5440709da 100644 --- a/packages/worker/__mocks__/node-fetch.ts +++ b/packages/worker/__mocks__/node-fetch.ts @@ -1,5 +1,4 @@ -import fs from "fs" - +// eslint-disable-next-line @typescript-eslint/no-unused-vars module FetchMock { const fetch = jest.requireActual("node-fetch") diff --git a/packages/worker/scripts/test.sh b/packages/worker/scripts/test.sh index 17b3ee17f4..4191674538 100644 --- a/packages/worker/scripts/test.sh +++ b/packages/worker/scripts/test.sh @@ -4,10 +4,10 @@ set -e if [[ -n $CI ]] then # Running in ci, where resources are limited - echo "jest --coverage --maxWorkers=2 --forceExit --bail $@" - jest --coverage --maxWorkers=2 --forceExit --bail $@ + echo "jest --coverage --maxWorkers=2 --forceExit --bail $@" + jest --coverage --maxWorkers=2 --forceExit --bail $@ else # --maxWorkers performs better in development - echo "jest --coverage --maxWorkers=2 --forceExit $@" - jest --coverage --maxWorkers=2 --forceExit $@ + echo "jest --coverage --detectOpenHandles $@" + jest --coverage --detectOpenHandles $@ fi \ No newline at end of file diff --git a/packages/worker/src/api/controllers/global/auth.ts b/packages/worker/src/api/controllers/global/auth.ts index cfee8c00dc..86c90f9cd3 100644 --- a/packages/worker/src/api/controllers/global/auth.ts +++ b/packages/worker/src/api/controllers/global/auth.ts @@ -225,7 +225,7 @@ export async function oidcCallbackUrl() { return ssoCallbackUrl(ConfigType.OIDC) } -export const oidcStrategyFactory = async (ctx: any, configId: any) => { +export const oidcStrategyFactory = async (ctx: any) => { const config = await configs.getOIDCConfig() if (!config) { return ctx.throw(400, "OIDC config not found") @@ -247,7 +247,7 @@ export const oidcPreAuth = async (ctx: Ctx, next: any) => { if (!configId) { ctx.throw(400, "OIDC config id is required") } - const strategy = await oidcStrategyFactory(ctx, configId) + const strategy = await oidcStrategyFactory(ctx) setCookie(ctx, configId, Cookie.OIDC_CONFIG) @@ -268,8 +268,7 @@ export const oidcPreAuth = async (ctx: Ctx, next: any) => { } export const oidcCallback = async (ctx: any, next: any) => { - const configId = getCookie(ctx, Cookie.OIDC_CONFIG) - const strategy = await oidcStrategyFactory(ctx, configId) + const strategy = await oidcStrategyFactory(ctx) return passport.authenticate( strategy, diff --git a/packages/worker/src/api/routes/global/tests/auth.spec.ts b/packages/worker/src/api/routes/global/tests/auth.spec.ts index a81d5db435..c36f9bd029 100644 --- a/packages/worker/src/api/routes/global/tests/auth.spec.ts +++ b/packages/worker/src/api/routes/global/tests/auth.spec.ts @@ -168,10 +168,7 @@ describe("/api/global/auth", () => { let user: User async function testSSOUser() { - const { res } = await config.api.auth.requestPasswordReset( - sendMailMock, - user.email - ) + await config.api.auth.requestPasswordReset(sendMailMock, user.email) expect(sendMailMock).not.toHaveBeenCalled() } diff --git a/packages/worker/src/api/routes/global/tests/scim.spec.ts b/packages/worker/src/api/routes/global/tests/scim.spec.ts index 258702a3b3..85c70b7b63 100644 --- a/packages/worker/src/api/routes/global/tests/scim.spec.ts +++ b/packages/worker/src/api/routes/global/tests/scim.spec.ts @@ -704,6 +704,7 @@ describe("scim", () => { expect(response).toEqual({ Resources: expect.arrayContaining( groups.map(g => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { members, ...groupData } = g return groupData }) @@ -723,6 +724,7 @@ describe("scim", () => { expect(response).toEqual({ Resources: expect.arrayContaining( groups.map(g => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { members, displayName, ...groupData } = g return groupData }) @@ -872,6 +874,7 @@ describe("scim", () => { qs: "excludedAttributes=members", }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { members, ...expectedResponse } = group expect(response).toEqual(expectedResponse) diff --git a/packages/worker/src/features.ts b/packages/worker/src/features.ts index 075b3b81ca..33fce3aebe 100644 --- a/packages/worker/src/features.ts +++ b/packages/worker/src/features.ts @@ -1,6 +1,7 @@ import { features } from "@budibase/backend-core" import env from "./environment" +// eslint-disable-next-line no-unused-vars enum WorkerFeature {} const featureList: WorkerFeature[] = features.processFeatureEnvVar( diff --git a/packages/worker/src/migrations/functions/globalInfoSyncUsers.ts b/packages/worker/src/migrations/functions/globalInfoSyncUsers.ts index 4bb864a000..f09a01b449 100644 --- a/packages/worker/src/migrations/functions/globalInfoSyncUsers.ts +++ b/packages/worker/src/migrations/functions/globalInfoSyncUsers.ts @@ -9,7 +9,7 @@ import { platform } from "@budibase/backend-core" * Description: * Re-sync the global-db users to the global-info db users */ -export const run = async (globalDb: any) => { +export const run = async () => { const users = (await usersSdk.db.allUsers()) as User[] const promises = [] for (let user of users) { diff --git a/packages/worker/src/sdk/auth/tests/auth.spec.ts b/packages/worker/src/sdk/auth/tests/auth.spec.ts index e9f348f7c7..894a3e6eca 100644 --- a/packages/worker/src/sdk/auth/tests/auth.spec.ts +++ b/packages/worker/src/sdk/auth/tests/auth.spec.ts @@ -6,6 +6,8 @@ import { TestConfiguration } from "../../../tests" describe("auth", () => { const config = new TestConfiguration() + afterAll(config.afterAll) + describe("resetUpdate", () => { it("providing a valid code will update the password", async () => { await context.doInTenant(structures.tenant.id(), async () => { diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index 3ebfb5f020..e44e9e2923 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -19,7 +19,6 @@ import { users, context, sessions, - auth, constants, env as coreEnv, db as dbCore, diff --git a/packages/worker/src/tests/structures/groups.ts b/packages/worker/src/tests/structures/groups.ts index d39dd74eb8..2ce6545b23 100644 --- a/packages/worker/src/tests/structures/groups.ts +++ b/packages/worker/src/tests/structures/groups.ts @@ -5,7 +5,7 @@ import { UserGroup as UserGroupType, UserGroupRoles } from "@budibase/types" export function UserGroup(): UserGroupType { const appsCount = generator.integer({ min: 0, max: 3 }) const roles = Array.from({ length: appsCount }).reduce( - (p: UserGroupRoles, v) => { + (p: UserGroupRoles) => { return { ...p, [db.generateAppID()]: generator.pickone(["ADMIN", "POWER", "BASIC"]), diff --git a/packages/worker/src/utilities/templates.ts b/packages/worker/src/utilities/templates.ts index 13fc5ef0cf..c9e86ae4f9 100644 --- a/packages/worker/src/utilities/templates.ts +++ b/packages/worker/src/utilities/templates.ts @@ -1,12 +1,10 @@ import { tenancy, configs } from "@budibase/backend-core" -import { SettingsInnerConfig } from "@budibase/types" import { InternalTemplateBinding, LOGO_URL, EmailTemplatePurpose, } from "../constants" import { checkSlashesInUrl } from "./index" -import { getLicensedConfig } from "./configs" const BASE_COMPANY = "Budibase" import * as pro from "@budibase/pro" diff --git a/qa-core/src/integrations/external-schema/mysql.integration.spec.ts b/qa-core/src/integrations/external-schema/mysql.integration.spec.ts index c34651ea0e..5a7e1989d2 100644 --- a/qa-core/src/integrations/external-schema/mysql.integration.spec.ts +++ b/qa-core/src/integrations/external-schema/mysql.integration.spec.ts @@ -1,8 +1,6 @@ import { GenericContainer } from "testcontainers" import mysql from "../../../../packages/server/src/integrations/mysql" -jest.unmock("mysql2/promise") - describe("datasource validators", () => { describe("mysql", () => { let config: any diff --git a/qa-core/src/integrations/validators/mysql.integration.spec.ts b/qa-core/src/integrations/validators/mysql.integration.spec.ts index 0f0de132fe..e828d192af 100644 --- a/qa-core/src/integrations/validators/mysql.integration.spec.ts +++ b/qa-core/src/integrations/validators/mysql.integration.spec.ts @@ -1,8 +1,6 @@ import { GenericContainer } from "testcontainers" import mysql from "../../../../packages/server/src/integrations/mysql" -jest.unmock("mysql2/promise") - describe("datasource validators", () => { describe("mysql", () => { let host: string diff --git a/yarn.lock b/yarn.lock index 9d68117413..d035d65ba1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2526,14 +2526,14 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": version "4.10.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== @@ -5447,7 +5447,7 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -5966,7 +5966,7 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.7.tgz#b9eb89d7dfa70d5d1ce525bc1411a35347f533a3" integrity sha512-4g1jrL98mdOIwSOUh6LTlB0Cs9I0dQPwINUhBg7C6pN4HLr8GS8xsksJxilW6S6dQHVi2K/o+lQuQcg7LroCnw== -"@types/semver@^7.3.12": +"@types/semver@^7.3.12", "@types/semver@^7.5.0": version "7.5.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== @@ -6132,6 +6132,23 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz#0d8f38a6c8a1802139e62184ee7a68ed024f30a1" + integrity sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/type-utils" "7.3.1" + "@typescript-eslint/utils" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/parser@6.9.0": version "6.9.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.9.0.tgz#2b402cadeadd3f211c25820e5433413347b27391" @@ -6143,6 +6160,17 @@ "@typescript-eslint/visitor-keys" "6.9.0" debug "^4.3.4" +"@typescript-eslint/parser@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.3.1.tgz#c4ba7dc2744318a5e4506596cbc3a0086255c526" + integrity sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw== + dependencies: + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/typescript-estree" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" + debug "^4.3.4" + "@typescript-eslint/scope-manager@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" @@ -6159,6 +6187,24 @@ "@typescript-eslint/types" "6.9.0" "@typescript-eslint/visitor-keys" "6.9.0" +"@typescript-eslint/scope-manager@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz#73fd0cb4211a7be23e49e5b6efec8820caa6ec36" + integrity sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag== + dependencies: + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" + +"@typescript-eslint/type-utils@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz#cbf90d3d7e788466aa8a5c0ab3f46103f098aa0d" + integrity sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw== + dependencies: + "@typescript-eslint/typescript-estree" "7.3.1" + "@typescript-eslint/utils" "7.3.1" + debug "^4.3.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/types@4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" @@ -6179,6 +6225,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.9.0.tgz#86a0cbe7ac46c0761429f928467ff3d92f841098" integrity sha512-+KB0lbkpxBkBSiVCuQvduqMJy+I1FyDbdwSpM3IoBS7APl4Bu15lStPjgBIdykdRqQNYqYNMa8Kuidax6phaEw== +"@typescript-eslint/types@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.3.1.tgz#ae104de8efa4227a462c0874d856602c5994413c" + integrity sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw== + "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" @@ -6205,6 +6256,20 @@ semver "^7.5.4" ts-api-utils "^1.0.1" +"@typescript-eslint/typescript-estree@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz#598848195fad34c7aa73f548bd00a4d4e5f5e2bb" + integrity sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ== + dependencies: + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/typescript-estree@^4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" @@ -6231,6 +6296,19 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/utils@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.3.1.tgz#fc28fd508ccf89495012561b7c02a6fdad162460" + integrity sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/typescript-estree" "7.3.1" + semver "^7.5.4" + "@typescript-eslint/utils@^5.10.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" @@ -6277,6 +6355,14 @@ "@typescript-eslint/types" "6.9.0" eslint-visitor-keys "^3.4.1" +"@typescript-eslint/visitor-keys@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz#6ddef14a3ce2a79690f01176f5305c34d7b93d8c" + integrity sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw== + dependencies: + "@typescript-eslint/types" "7.3.1" + eslint-visitor-keys "^3.4.1" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -12046,7 +12132,7 @@ handlebars-utils@^1.0.6: kind-of "^6.0.0" typeof-article "^0.1.1" -handlebars@^4.7.6, handlebars@^4.7.7: +handlebars@^4.7.7: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== @@ -12058,6 +12144,18 @@ handlebars@^4.7.6, handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -15583,6 +15681,13 @@ minimatch@3.0.5: dependencies: brace-expansion "^1.1.7" +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" @@ -21506,6 +21611,14 @@ typeof@^1.0.0: resolved "https://registry.yarnpkg.com/typeof/-/typeof-1.0.0.tgz#9c84403f2323ae5399167275497638ea1d2f2440" integrity sha512-Pze0mIxYXhaJdpw1ayMzOA7rtGr1OmsTY/Z+FWtRKIqXFz6aoDLjqdbWE/tcIBSC8nhnVXiRrEXujodR/xiFAA== +typescript-eslint@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-7.3.1.tgz#9f4808abea3b33c4dd3bb51dd801471e91d1bd58" + integrity sha512-psqcnHPRCdVIDbgj6RvfpwUKqMcNxIw7eizgxYi46X2BmXK6LxYqPD+SbDfPuA9JW+yPItY6aKJLRNbW7lZ4rA== + dependencies: + "@typescript-eslint/eslint-plugin" "7.3.1" + "@typescript-eslint/parser" "7.3.1" + typescript@5.2.2, "typescript@>=3 < 6": version "5.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"