diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 4c5cc94d2b..044e29b445 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -117,9 +117,9 @@ jobs: - name: Test run: | if ${{ env.ONLY_AFFECTED_TASKS }}; then - yarn test --ignore=@budibase/worker --ignore=@budibase/server --since=${{ env.NX_BASE_BRANCH }} + yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore @budibase/account-portal-server --since=${{ env.NX_BASE_BRANCH }} else - yarn test --ignore=@budibase/worker --ignore=@budibase/server + yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore @budibase/account-portal-server fi test-worker: diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index 186751ed59..de310a591d 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -42,7 +42,7 @@ services: couchdb-service: container_name: budi-couchdb3-dev restart: on-failure - image: budibase/couchdb:v3.3.3 + image: budibase/couchdb:v3.3.3-sqs-v2.1.1 environment: - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} - COUCHDB_USER=${COUCH_DB_USER} diff --git a/lerna.json b/lerna.json index 4a7cfc95cc..30a02755c5 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "2.32.0", + "version": "2.32.2", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/package.json b/package.json index f3cbd75836..582e35180e 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "build:docker:single": "./scripts/build-single-image.sh", "build:docker:single:sqs": "./scripts/build-single-image-sqs.sh", "build:docker:dependencies": "docker build -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest ./hosting", - "publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.3.3 --push ./hosting/couchdb", + "publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.3.3 -t budibase/couchdb:v3.3.3-sqs-v2.1.1 --push ./hosting/couchdb", "publish:docker:dependencies": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest -t budibase/dependencies:v3.2.1 --push ./hosting", "release:helm": "node scripts/releaseHelmChart", "env:multi:enable": "lerna run --stream env:multi:enable", diff --git a/packages/backend-core/src/cache/user.ts b/packages/backend-core/src/cache/user.ts index d319c5dcb6..9de09a431d 100644 --- a/packages/backend-core/src/cache/user.ts +++ b/packages/backend-core/src/cache/user.ts @@ -63,14 +63,25 @@ async function populateUsersFromDB( * If not present fallback to loading the user directly and re-caching. * @param userId the id of the user to get * @param tenantId the tenant of the user to get + * @param email the email of the user to populate from account if needed * @param populateUser function to provide the user for re-caching. default to couch db * @returns */ -export async function getUser( - userId: string, - tenantId?: string, - populateUser?: (userId: string, tenantId: string) => Promise -) { +export async function getUser({ + userId, + tenantId, + email, + populateUser, +}: { + userId: string + email?: string + tenantId?: string + populateUser?: ( + userId: string, + tenantId: string, + email?: string + ) => Promise +}) { if (!populateUser) { populateUser = populateFromDB } @@ -85,7 +96,7 @@ export async function getUser( // try cache let user: User = await client.get(userId) if (!user) { - user = await populateUser(userId, tenantId) + user = await populateUser(userId, tenantId, email) await client.store(userId, user, EXPIRY_SECONDS) } if (user && !user.tenantId && tenantId) { diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts index 51cd4ec2af..85aa2293ef 100644 --- a/packages/backend-core/src/middleware/authenticated.ts +++ b/packages/backend-core/src/middleware/authenticated.ts @@ -43,7 +43,11 @@ function finalise(ctx: any, opts: FinaliseOpts = {}) { async function checkApiKey( apiKey: string, - populateUser?: (userId: string, tenantId: string) => Promise + populateUser?: ( + userId: string, + tenantId: string, + email?: string + ) => Promise ) { // check both the primary and the fallback internal api keys // this allows for rotation @@ -70,7 +74,11 @@ async function checkApiKey( if (userId) { return { valid: true, - user: await getUser(userId, tenantId, populateUser), + user: await getUser({ + userId, + tenantId, + populateUser, + }), } } else { throw new InvalidAPIKeyError() @@ -123,13 +131,18 @@ export default function ( // getting session handles error checking (if session exists etc) session = await getSession(userId, sessionId) if (opts && opts.populateUser) { - user = await getUser( + user = await getUser({ userId, - session.tenantId, - opts.populateUser(ctx) - ) + tenantId: session.tenantId, + email: session.email, + populateUser: opts.populateUser(ctx), + }) } else { - user = await getUser(userId, session.tenantId) + user = await getUser({ + userId, + tenantId: session.tenantId, + email: session.email, + }) } // @ts-ignore user.csrfToken = session.csrfToken @@ -148,7 +161,11 @@ export default function ( } // this is an internal request, no user made it if (!authenticated && apiKey) { - const populateUser = opts.populateUser ? opts.populateUser(ctx) : null + const populateUser: ( + userId: string, + tenantId: string, + email?: string + ) => Promise = opts.populateUser ? opts.populateUser(ctx) : null const { valid, user: foundUser } = await checkApiKey( apiKey, populateUser diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index d8ad791829..2b5243f856 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -862,6 +862,21 @@ class InternalBuilder { return withSchema } + private buildJsonField(field: string): string { + const parts = field.split(".") + let tableField: string, unaliased: string + if (parts.length > 1) { + const alias = parts.shift()! + unaliased = parts.join(".") + tableField = `${this.quote(alias)}.${this.quote(unaliased)}` + } else { + unaliased = parts.join(".") + tableField = this.quote(unaliased) + } + const separator = this.client === SqlClient.ORACLE ? " VALUE " : "," + return `'${unaliased}'${separator}${tableField}` + } + addJsonRelationships( query: Knex.QueryBuilder, fromTable: string, @@ -871,27 +886,6 @@ class InternalBuilder { const knex = this.knex const { resource, tableAliases: aliases, endpoint } = this.query const fields = resource?.fields || [] - const jsonField = (field: string) => { - const parts = field.split(".") - let tableField: string, unaliased: string - if (parts.length > 1) { - const alias = parts.shift()! - unaliased = parts.join(".") - tableField = `${this.quote(alias)}.${this.quote(unaliased)}` - } else { - unaliased = parts.join(".") - tableField = this.quote(unaliased) - } - let separator = "," - switch (sqlClient) { - case SqlClient.ORACLE: - separator = " VALUE " - break - case SqlClient.MS_SQL: - separator = ":" - } - return `'${unaliased}'${separator}${tableField}` - } for (let relationship of relationships) { const { tableName: toTable, @@ -921,7 +915,7 @@ class InternalBuilder { ) } const fieldList: string = relationshipFields - .map(field => jsonField(field)) + .map(field => this.buildJsonField(field)) .join(",") // SQL Server uses TOP - which performs a little differently to the normal LIMIT syntax // it reduces the result set rather than limiting how much data it filters over @@ -1073,43 +1067,6 @@ class InternalBuilder { return query } - addRelationships( - query: Knex.QueryBuilder, - fromTable: string, - relationships: RelationshipsJson[] - ): Knex.QueryBuilder { - const tableSets: Record = {} - // aggregate into table sets (all the same to tables) - for (let relationship of relationships) { - const keyObj: { toTable: string; throughTable: string | undefined } = { - toTable: relationship.tableName, - throughTable: undefined, - } - if (relationship.through) { - keyObj.throughTable = relationship.through - } - const key = JSON.stringify(keyObj) - if (tableSets[key]) { - tableSets[key].push(relationship) - } else { - tableSets[key] = [relationship] - } - } - for (let [key, relationships] of Object.entries(tableSets)) { - const { toTable, throughTable } = JSON.parse(key) - query = this.addJoin( - query, - { - from: fromTable, - to: toTable, - through: throughTable, - }, - relationships - ) - } - return query - } - qualifiedKnex(opts?: { alias?: string | boolean }): Knex.QueryBuilder { let alias = this.query.tableAliases?.[this.query.endpoint.entityId] if (opts?.alias === false) { @@ -1193,8 +1150,7 @@ class InternalBuilder { if (!primary) { throw new Error("Primary key is required for upsert") } - const ret = query.insert(parsedBody).onConflict(primary).merge() - return ret + return query.insert(parsedBody).onConflict(primary).merge() } else if ( this.client === SqlClient.MS_SQL || this.client === SqlClient.ORACLE @@ -1253,12 +1209,29 @@ class InternalBuilder { if (!counting) { query = this.addSorting(query) } - // handle joins - if (relationships) { - query = this.addJsonRelationships(query, tableName, relationships) - } - return this.addFilters(query, filters, { relationship: true }) + query = this.addFilters(query, filters, { relationship: true }) + + // handle relationships with a CTE for all others + if (relationships?.length) { + const mainTable = + this.query.tableAliases?.[this.query.endpoint.entityId] || + this.query.endpoint.entityId + const cte = this.addSorting( + this.knex + .with("paginated", query) + .select(this.generateSelectStatement()) + .from({ + [mainTable]: "paginated", + }) + ) + // add JSON aggregations attached to the CTE + return this.addJsonRelationships(cte, tableName, relationships) + } + // no relationships found - return query + else { + return query + } } update(opts: QueryOptions): Knex.QueryBuilder { diff --git a/packages/pro b/packages/pro index ec1d2bda75..922431260e 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit ec1d2bda756f02c6b4efdee086e4c59b0c2a1b0c +Subproject commit 922431260e90d558a1ca55398475412e75088057 diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index 30507fc827..2206d49d9b 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-slim +FROM node:20-alpine LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh" LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh" @@ -15,37 +15,35 @@ ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU ENV ACCOUNT_PORTAL_URL=https://account.budibase.app ENV TOP_LEVEL_PATH=/ -# handle node-gyp -RUN apt-get update \ - && apt-get install -y --no-install-recommends g++ make python3 jq -RUN yarn global add pm2 +# handle node-gyp and install postgres client for pg_dump utils +RUN apk add --no-cache \ + g++ \ + make \ + python3 \ + jq \ + bash \ + postgresql-client \ + git -# Install postgres client for pg_dump utils -RUN apt update && apt upgrade -y \ - && apt install software-properties-common apt-transport-https curl gpg -y \ - && curl -fsSl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null \ - && echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main | tee /etc/apt/sources.list.d/postgresql.list \ - && apt update -y \ - && apt install postgresql-client-15 -y \ - && apt remove software-properties-common apt-transport-https curl gpg -y +RUN yarn global add pm2 WORKDIR / COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies.sh RUN chmod +x ./scripts/removeWorkspaceDependencies.sh - WORKDIR /app COPY packages/server/package.json . COPY packages/server/dist/yarn.lock . COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies.sh RUN chmod +x ./scripts/removeWorkspaceDependencies.sh -RUN ./scripts/removeWorkspaceDependencies.sh package.json +RUN ./scripts/removeWorkspaceDependencies.sh package.json +# Install yarn packages with caching RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 \ - # Remove unneeded data from file system to reduce image size - && yarn cache clean && apt-get remove -y --purge --auto-remove g++ make python jq \ + && yarn cache clean \ + && apk del g++ make python3 jq \ && rm -rf /tmp/* /root/.node-gyp /usr/local/lib/node_modules/npm/node_modules/node-gyp COPY packages/server/dist/ dist/ @@ -69,7 +67,7 @@ EXPOSE 4001 # due to this causing yarn to stop installing dev dependencies # which are actually needed to get this environment up and running ENV NODE_ENV=production -# this is required for isolated-vm to work on Node 20+ +# This is required for isolated-vm to work on Node 20+ ENV NODE_OPTIONS="--no-node-snapshot" ENV CLUSTER_MODE=${CLUSTER_MODE} ENV TOP_LEVEL_PATH=/app diff --git a/packages/server/src/api/controllers/row/utils/basic.ts b/packages/server/src/api/controllers/row/utils/basic.ts index 596cc90b20..9c49f5636a 100644 --- a/packages/server/src/api/controllers/row/utils/basic.ts +++ b/packages/server/src/api/controllers/row/utils/basic.ts @@ -145,35 +145,32 @@ export function basicProcessing({ : typeof value === "string" ? JSON.parse(value) : undefined - if (array) { + if (array && Array.isArray(array)) { thisRow[col] = array // make sure all of them have an _id - if (Array.isArray(thisRow[col])) { - const sortField = - relatedTable.primaryDisplay || relatedTable.primary![0]! - thisRow[col] = (thisRow[col] as Row[]) - .map(relatedRow => - basicProcessing({ - row: relatedRow, - table: relatedTable, - tables, - isLinked: false, - sqs, - }) - ) - .sort((a, b) => { - const aField = a?.[sortField], - bField = b?.[sortField] - if (!aField) { - return 1 - } else if (!bField) { - return -1 - } - return aField.localeCompare - ? aField.localeCompare(bField) - : aField - bField + const sortField = relatedTable.primaryDisplay || relatedTable.primary![0]! + thisRow[col] = (thisRow[col] as Row[]) + .map(relatedRow => + basicProcessing({ + row: relatedRow, + table: relatedTable, + tables, + isLinked: false, + sqs, }) - } + ) + .sort((a, b) => { + const aField = a?.[sortField], + bField = b?.[sortField] + if (!aField) { + return 1 + } else if (!bField) { + return -1 + } + return aField.localeCompare + ? aField.localeCompare(bField) + : aField - bField + }) } } return fixJsonTypes(thisRow, table) 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 index 4bd1951d67..e6da4693eb 100644 --- a/packages/server/src/api/routes/tests/queries/generic-sql.spec.ts +++ b/packages/server/src/api/routes/tests/queries/generic-sql.spec.ts @@ -832,12 +832,13 @@ describe.each( }, }) expect(res).toHaveLength(1) - expect(res[0]).toEqual( - expect.objectContaining({ - id: 2, - name: "two", - }) - ) + expect(res[0]).toEqual({ + id: 2, + name: "two", + // the use of table.* introduces the possibility of nulls being returned + birthday: null, + number: null, + }) }) // this parameter really only impacts SQL queries diff --git a/packages/server/src/automations/tests/scenarios/branching.spec.ts b/packages/server/src/automations/tests/scenarios/branching.spec.ts new file mode 100644 index 0000000000..df513e98c6 --- /dev/null +++ b/packages/server/src/automations/tests/scenarios/branching.spec.ts @@ -0,0 +1,199 @@ +import * as automation from "../../index" +import * as setup from "../utilities" +import { Table, AutomationStatus } from "@budibase/types" +import { createAutomationBuilder } from "../utilities/AutomationTestBuilder" + +describe("Branching automations", () => { + let config = setup.getConfig(), + table: Table + + beforeEach(async () => { + await automation.init() + await config.init() + table = await config.createTable() + await config.createRow() + }) + + afterAll(setup.afterAll) + + it("should run a multiple nested branching automation", async () => { + const builder = createAutomationBuilder({ + name: "Test Trigger with Loop and Create Row", + }) + + const results = await builder + .appAction({ fields: {} }) + .serverLog({ text: "Starting automation" }) + .branch({ + topLevelBranch1: { + steps: stepBuilder => + stepBuilder.serverLog({ text: "Branch 1" }).branch({ + branch1: { + steps: stepBuilder => + stepBuilder.serverLog({ text: "Branch 1.1" }), + condition: { + equal: { "steps.1.success": true }, + }, + }, + branch2: { + steps: stepBuilder => + stepBuilder.serverLog({ text: "Branch 1.2" }), + condition: { + equal: { "steps.1.success": false }, + }, + }, + }), + condition: { + equal: { "steps.1.success": true }, + }, + }, + topLevelBranch2: { + steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }), + condition: { + equal: { "steps.1.success": false }, + }, + }, + }) + .run() + expect(results.steps[3].outputs.status).toContain("branch1 branch taken") + expect(results.steps[4].outputs.message).toContain("Branch 1.1") + }) + + it("should execute correct branch based on string equality", async () => { + const builder = createAutomationBuilder({ + name: "String Equality Branching", + }) + + const results = await builder + .appAction({ fields: { status: "active" } }) + .branch({ + activeBranch: { + steps: stepBuilder => stepBuilder.serverLog({ text: "Active user" }), + condition: { + equal: { "trigger.fields.status": "active" }, + }, + }, + inactiveBranch: { + steps: stepBuilder => + stepBuilder.serverLog({ text: "Inactive user" }), + condition: { + equal: { "trigger.fields.status": "inactive" }, + }, + }, + }) + .run() + expect(results.steps[0].outputs.status).toContain( + "activeBranch branch taken" + ) + expect(results.steps[1].outputs.message).toContain("Active user") + }) + + it("should handle multiple conditions with AND operator", async () => { + const builder = createAutomationBuilder({ + name: "Multiple AND Conditions Branching", + }) + + const results = await builder + .appAction({ fields: { status: "active", role: "admin" } }) + .branch({ + activeAdminBranch: { + steps: stepBuilder => + stepBuilder.serverLog({ text: "Active admin user" }), + condition: { + $and: { + conditions: [ + { equal: { "trigger.fields.status": "active" } }, + { equal: { "trigger.fields.role": "admin" } }, + ], + }, + }, + }, + otherBranch: { + steps: stepBuilder => stepBuilder.serverLog({ text: "Other user" }), + condition: { + notEqual: { "trigger.fields.status": "active" }, + }, + }, + }) + .run() + + expect(results.steps[1].outputs.message).toContain("Active admin user") + }) + + it("should handle multiple conditions with OR operator", async () => { + const builder = createAutomationBuilder({ + name: "Multiple OR Conditions Branching", + }) + + const results = await builder + .appAction({ fields: { status: "test", role: "user" } }) + .branch({ + specialBranch: { + steps: stepBuilder => stepBuilder.serverLog({ text: "Special user" }), + condition: { + $or: { + conditions: [ + { equal: { "trigger.fields.status": "test" } }, + { equal: { "trigger.fields.role": "admin" } }, + ], + }, + }, + }, + regularBranch: { + steps: stepBuilder => stepBuilder.serverLog({ text: "Regular user" }), + condition: { + $and: { + conditions: [ + { notEqual: { "trigger.fields.status": "active" } }, + { notEqual: { "trigger.fields.role": "admin" } }, + ], + }, + }, + }, + }) + .run() + + expect(results.steps[1].outputs.message).toContain("Special user") + }) + + it("should stop the branch automation when no conditions are met", async () => { + const builder = createAutomationBuilder({ + name: "Multiple OR Conditions Branching", + }) + + const results = await builder + .appAction({ fields: { status: "test", role: "user" } }) + .createRow({ row: { name: "Test", tableId: table._id } }) + .branch({ + specialBranch: { + steps: stepBuilder => stepBuilder.serverLog({ text: "Special user" }), + condition: { + $or: { + conditions: [ + { equal: { "trigger.fields.status": "new" } }, + { equal: { "trigger.fields.role": "admin" } }, + ], + }, + }, + }, + regularBranch: { + steps: stepBuilder => stepBuilder.serverLog({ text: "Regular user" }), + condition: { + $and: { + conditions: [ + { equal: { "trigger.fields.status": "active" } }, + { equal: { "trigger.fields.role": "admin" } }, + ], + }, + }, + }, + }) + .serverLog({ text: "Test" }) + .run() + + expect(results.steps[1].outputs.status).toEqual( + AutomationStatus.NO_CONDITION_MET + ) + expect(results.steps[2]).toBeUndefined() + }) +}) diff --git a/packages/server/src/automations/tests/scenarios/looping.spec.ts b/packages/server/src/automations/tests/scenarios/looping.spec.ts new file mode 100644 index 0000000000..9bc382a187 --- /dev/null +++ b/packages/server/src/automations/tests/scenarios/looping.spec.ts @@ -0,0 +1,245 @@ +import * as automation from "../../index" +import * as setup from "../utilities" +import { + Table, + LoopStepType, + CreateRowStepOutputs, + ServerLogStepOutputs, +} from "@budibase/types" +import { createAutomationBuilder } from "../utilities/AutomationTestBuilder" + +describe("Loop automations", () => { + let config = setup.getConfig(), + table: Table + + beforeEach(async () => { + await automation.init() + await config.init() + table = await config.createTable() + await config.createRow() + }) + + afterAll(setup.afterAll) + + it("should run an automation with a trigger, loop, and create row step", async () => { + const builder = createAutomationBuilder({ + name: "Test Trigger with Loop and Create Row", + }) + + const results = await builder + .rowSaved( + { tableId: table._id! }, + { + row: { + name: "Trigger Row", + description: "This row triggers the automation", + }, + id: "1234", + revision: "1", + } + ) + .loop({ + option: LoopStepType.ARRAY, + binding: [1, 2, 3], + }) + .createRow({ + row: { + name: "Item {{ loop.currentItem }}", + description: "Created from loop", + tableId: table._id, + }, + }) + .run() + + expect(results.trigger).toBeDefined() + expect(results.steps).toHaveLength(1) + + expect(results.steps[0].outputs.iterations).toBe(3) + expect(results.steps[0].outputs.items).toHaveLength(3) + + results.steps[0].outputs.items.forEach((output: any, index: number) => { + expect(output).toMatchObject({ + success: true, + row: { + name: `Item ${index + 1}`, + description: "Created from loop", + }, + }) + }) + }) + + it("should run an automation where a loop step is between two normal steps to ensure context correctness", async () => { + const builder = createAutomationBuilder({ + name: "Test Trigger with Loop and Create Row", + }) + + const results = await builder + .rowSaved( + { tableId: table._id! }, + { + row: { + name: "Trigger Row", + description: "This row triggers the automation", + }, + id: "1234", + revision: "1", + } + ) + .queryRows({ + tableId: table._id!, + }) + .loop({ + option: LoopStepType.ARRAY, + binding: [1, 2, 3], + }) + .serverLog({ text: "Message {{loop.currentItem}}" }) + .serverLog({ text: "{{steps.1.rows.0._id}}" }) + .run() + + results.steps[1].outputs.items.forEach( + (output: ServerLogStepOutputs, index: number) => { + expect(output).toMatchObject({ + success: true, + }) + expect(output.message).toContain(`Message ${index + 1}`) + } + ) + + expect(results.steps[2].outputs.message).toContain("ro_ta") + }) + + it("if an incorrect type is passed to the loop it should return an error", async () => { + const builder = createAutomationBuilder({ + name: "Test Loop error", + }) + + const results = await builder + .appAction({ fields: {} }) + .loop({ + option: LoopStepType.ARRAY, + binding: "1, 2, 3", + }) + .serverLog({ text: "Message {{loop.currentItem}}" }) + .run() + + expect(results.steps[0].outputs).toEqual({ + success: false, + status: "INCORRECT_TYPE", + }) + }) + + it("ensure the loop stops if the failure condition is reached", async () => { + const builder = createAutomationBuilder({ + name: "Test Loop error", + }) + + const results = await builder + .appAction({ fields: {} }) + .loop({ + option: LoopStepType.ARRAY, + binding: ["test", "test2", "test3"], + failure: "test2", + }) + .serverLog({ text: "Message {{loop.currentItem}}" }) + .run() + + expect(results.steps[0].outputs).toEqual( + expect.objectContaining({ + status: "FAILURE_CONDITION_MET", + success: false, + }) + ) + }) + + it("should run an automation where a loop is successfully run twice", async () => { + const builder = createAutomationBuilder({ + name: "Test Trigger with Loop and Create Row", + }) + + const results = await builder + .rowSaved( + { tableId: table._id! }, + { + row: { + name: "Trigger Row", + description: "This row triggers the automation", + }, + id: "1234", + revision: "1", + } + ) + .loop({ + option: LoopStepType.ARRAY, + binding: [1, 2, 3], + }) + .createRow({ + row: { + name: "Item {{ loop.currentItem }}", + description: "Created from loop", + tableId: table._id, + }, + }) + .loop({ + option: LoopStepType.STRING, + binding: "Message 1,Message 2,Message 3", + }) + .serverLog({ text: "{{loop.currentItem}}" }) + .run() + + expect(results.trigger).toBeDefined() + expect(results.steps).toHaveLength(2) + + expect(results.steps[0].outputs.iterations).toBe(3) + expect(results.steps[0].outputs.items).toHaveLength(3) + + results.steps[0].outputs.items.forEach( + (output: CreateRowStepOutputs, index: number) => { + expect(output).toMatchObject({ + success: true, + row: { + name: `Item ${index + 1}`, + description: "Created from loop", + }, + }) + } + ) + + expect(results.steps[1].outputs.iterations).toBe(3) + expect(results.steps[1].outputs.items).toHaveLength(3) + + results.steps[1].outputs.items.forEach( + (output: ServerLogStepOutputs, index: number) => { + expect(output).toMatchObject({ + success: true, + }) + expect(output.message).toContain(`Message ${index + 1}`) + } + ) + }) + + it("should run an automation where a loop is used twice to ensure context correctness further down the tree", async () => { + const builder = createAutomationBuilder({ + name: "Test Trigger with Loop and Create Row", + }) + + const results = await builder + .appAction({ fields: {} }) + .loop({ + option: LoopStepType.ARRAY, + binding: [1, 2, 3], + }) + .serverLog({ text: "Message {{loop.currentItem}}" }) + .serverLog({ text: "{{steps.1.iterations}}" }) + .loop({ + option: LoopStepType.ARRAY, + binding: [1, 2, 3], + }) + .serverLog({ text: "{{loop.currentItem}}" }) + .serverLog({ text: "{{steps.3.iterations}}" }) + .run() + + // We want to ensure that bindings are corr + expect(results.steps[1].outputs.message).toContain("- 3") + expect(results.steps[3].outputs.message).toContain("- 3") + }) +}) diff --git a/packages/server/src/automations/tests/scenarios/scenarios.spec.ts b/packages/server/src/automations/tests/scenarios/scenarios.spec.ts index 7fe4776d54..40d6094525 100644 --- a/packages/server/src/automations/tests/scenarios/scenarios.spec.ts +++ b/packages/server/src/automations/tests/scenarios/scenarios.spec.ts @@ -1,397 +1,19 @@ import * as automation from "../../index" import * as setup from "../utilities" -import { - Table, - LoopStepType, - CreateRowStepOutputs, - ServerLogStepOutputs, - FieldType, -} from "@budibase/types" +import { LoopStepType, FieldType } from "@budibase/types" import { createAutomationBuilder } from "../utilities/AutomationTestBuilder" import { DatabaseName } from "../../../integrations/tests/utils" describe("Automation Scenarios", () => { - let config = setup.getConfig(), - table: Table + let config = setup.getConfig() beforeEach(async () => { await automation.init() await config.init() - table = await config.createTable() - await config.createRow() }) afterAll(setup.afterAll) - describe("Branching automations", () => { - it("should run a multiple nested branching automation", async () => { - const builder = createAutomationBuilder({ - name: "Test Trigger with Loop and Create Row", - }) - - const results = await builder - .appAction({ fields: {} }) - .serverLog({ text: "Starting automation" }) - .branch({ - topLevelBranch1: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Branch 1" }).branch({ - branch1: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Branch 1.1" }), - condition: { - equal: { "steps.1.success": true }, - }, - }, - branch2: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Branch 1.2" }), - condition: { - equal: { "steps.1.success": false }, - }, - }, - }), - condition: { - equal: { "steps.1.success": true }, - }, - }, - topLevelBranch2: { - steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }), - condition: { - equal: { "steps.1.success": false }, - }, - }, - }) - .run() - expect(results.steps[3].outputs.status).toContain("branch1 branch taken") - expect(results.steps[4].outputs.message).toContain("Branch 1.1") - }) - - it("should execute correct branch based on string equality", async () => { - const builder = createAutomationBuilder({ - name: "String Equality Branching", - }) - - const results = await builder - .appAction({ fields: { status: "active" } }) - .branch({ - activeBranch: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Active user" }), - condition: { - equal: { "trigger.fields.status": "active" }, - }, - }, - inactiveBranch: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Inactive user" }), - condition: { - equal: { "trigger.fields.status": "inactive" }, - }, - }, - }) - .run() - expect(results.steps[0].outputs.status).toContain( - "activeBranch branch taken" - ) - expect(results.steps[1].outputs.message).toContain("Active user") - }) - - it("should handle multiple conditions with AND operator", async () => { - const builder = createAutomationBuilder({ - name: "Multiple AND Conditions Branching", - }) - - const results = await builder - .appAction({ fields: { status: "active", role: "admin" } }) - .branch({ - activeAdminBranch: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Active admin user" }), - condition: { - $and: { - conditions: [ - { equal: { "trigger.fields.status": "active" } }, - { equal: { "trigger.fields.role": "admin" } }, - ], - }, - }, - }, - otherBranch: { - steps: stepBuilder => stepBuilder.serverLog({ text: "Other user" }), - condition: { - notEqual: { "trigger.fields.status": "active" }, - }, - }, - }) - .run() - - expect(results.steps[1].outputs.message).toContain("Active admin user") - }) - - it("should handle multiple conditions with OR operator", async () => { - const builder = createAutomationBuilder({ - name: "Multiple OR Conditions Branching", - }) - - const results = await builder - .appAction({ fields: { status: "test", role: "user" } }) - .branch({ - specialBranch: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Special user" }), - condition: { - $or: { - conditions: [ - { equal: { "trigger.fields.status": "test" } }, - { equal: { "trigger.fields.role": "admin" } }, - ], - }, - }, - }, - regularBranch: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Regular user" }), - condition: { - $and: { - conditions: [ - { notEqual: { "trigger.fields.status": "active" } }, - { notEqual: { "trigger.fields.role": "admin" } }, - ], - }, - }, - }, - }) - .run() - - expect(results.steps[1].outputs.message).toContain("Special user") - }) - }) - - describe("Loop automations", () => { - it("should run an automation with a trigger, loop, and create row step", async () => { - const builder = createAutomationBuilder({ - name: "Test Trigger with Loop and Create Row", - }) - - const results = await builder - .rowSaved( - { tableId: table._id! }, - { - row: { - name: "Trigger Row", - description: "This row triggers the automation", - }, - id: "1234", - revision: "1", - } - ) - .loop({ - option: LoopStepType.ARRAY, - binding: [1, 2, 3], - }) - .createRow({ - row: { - name: "Item {{ loop.currentItem }}", - description: "Created from loop", - tableId: table._id, - }, - }) - .run() - - expect(results.trigger).toBeDefined() - expect(results.steps).toHaveLength(1) - - expect(results.steps[0].outputs.iterations).toBe(3) - expect(results.steps[0].outputs.items).toHaveLength(3) - - results.steps[0].outputs.items.forEach((output: any, index: number) => { - expect(output).toMatchObject({ - success: true, - row: { - name: `Item ${index + 1}`, - description: "Created from loop", - }, - }) - }) - }) - - it("should run an automation where a loop step is between two normal steps to ensure context correctness", async () => { - const builder = createAutomationBuilder({ - name: "Test Trigger with Loop and Create Row", - }) - - const results = await builder - .rowSaved( - { tableId: table._id! }, - { - row: { - name: "Trigger Row", - description: "This row triggers the automation", - }, - id: "1234", - revision: "1", - } - ) - .queryRows({ - tableId: table._id!, - }) - .loop({ - option: LoopStepType.ARRAY, - binding: [1, 2, 3], - }) - .serverLog({ text: "Message {{loop.currentItem}}" }) - .serverLog({ text: "{{steps.1.rows.0._id}}" }) - .run() - - results.steps[1].outputs.items.forEach( - (output: ServerLogStepOutputs, index: number) => { - expect(output).toMatchObject({ - success: true, - }) - expect(output.message).toContain(`Message ${index + 1}`) - } - ) - - expect(results.steps[2].outputs.message).toContain("ro_ta") - }) - - it("if an incorrect type is passed to the loop it should return an error", async () => { - const builder = createAutomationBuilder({ - name: "Test Loop error", - }) - - const results = await builder - .appAction({ fields: {} }) - .loop({ - option: LoopStepType.ARRAY, - binding: "1, 2, 3", - }) - .serverLog({ text: "Message {{loop.currentItem}}" }) - .run() - - expect(results.steps[0].outputs).toEqual({ - success: false, - status: "INCORRECT_TYPE", - }) - }) - - it("ensure the loop stops if the failure condition is reached", async () => { - const builder = createAutomationBuilder({ - name: "Test Loop error", - }) - - const results = await builder - .appAction({ fields: {} }) - .loop({ - option: LoopStepType.ARRAY, - binding: ["test", "test2", "test3"], - failure: "test2", - }) - .serverLog({ text: "Message {{loop.currentItem}}" }) - .run() - - expect(results.steps[0].outputs).toEqual( - expect.objectContaining({ - status: "FAILURE_CONDITION_MET", - success: false, - }) - ) - }) - - it("should run an automation where a loop is successfully run twice", async () => { - const builder = createAutomationBuilder({ - name: "Test Trigger with Loop and Create Row", - }) - - const results = await builder - .rowSaved( - { tableId: table._id! }, - { - row: { - name: "Trigger Row", - description: "This row triggers the automation", - }, - id: "1234", - revision: "1", - } - ) - .loop({ - option: LoopStepType.ARRAY, - binding: [1, 2, 3], - }) - .createRow({ - row: { - name: "Item {{ loop.currentItem }}", - description: "Created from loop", - tableId: table._id, - }, - }) - .loop({ - option: LoopStepType.STRING, - binding: "Message 1,Message 2,Message 3", - }) - .serverLog({ text: "{{loop.currentItem}}" }) - .run() - - expect(results.trigger).toBeDefined() - expect(results.steps).toHaveLength(2) - - expect(results.steps[0].outputs.iterations).toBe(3) - expect(results.steps[0].outputs.items).toHaveLength(3) - - results.steps[0].outputs.items.forEach( - (output: CreateRowStepOutputs, index: number) => { - expect(output).toMatchObject({ - success: true, - row: { - name: `Item ${index + 1}`, - description: "Created from loop", - }, - }) - } - ) - - expect(results.steps[1].outputs.iterations).toBe(3) - expect(results.steps[1].outputs.items).toHaveLength(3) - - results.steps[1].outputs.items.forEach( - (output: ServerLogStepOutputs, index: number) => { - expect(output).toMatchObject({ - success: true, - }) - expect(output.message).toContain(`Message ${index + 1}`) - } - ) - }) - - it("should run an automation where a loop is used twice to ensure context correctness further down the tree", async () => { - const builder = createAutomationBuilder({ - name: "Test Trigger with Loop and Create Row", - }) - - const results = await builder - .appAction({ fields: {} }) - .loop({ - option: LoopStepType.ARRAY, - binding: [1, 2, 3], - }) - .serverLog({ text: "Message {{loop.currentItem}}" }) - .serverLog({ text: "{{steps.1.iterations}}" }) - .loop({ - option: LoopStepType.ARRAY, - binding: [1, 2, 3], - }) - .serverLog({ text: "{{loop.currentItem}}" }) - .serverLog({ text: "{{steps.3.iterations}}" }) - .run() - - // We want to ensure that bindings are corr - expect(results.steps[1].outputs.message).toContain("- 3") - expect(results.steps[3].outputs.message).toContain("- 3") - }) - }) - describe("Row Automations", () => { it("should trigger an automation which then creates a row", async () => { const table = await config.createTable() diff --git a/packages/server/src/integrations/tests/sql.spec.ts b/packages/server/src/integrations/tests/sql.spec.ts index f2bcc0bde0..cf5b88b0fd 100644 --- a/packages/server/src/integrations/tests/sql.spec.ts +++ b/packages/server/src/integrations/tests/sql.spec.ts @@ -161,16 +161,16 @@ describe("SQL query builder", () => { it("should add the schema to the LEFT JOIN", () => { const query = sql._query(generateRelationshipJson({ schema: "production" })) expect(query).toEqual({ - bindings: [relationshipLimit, limit], - sql: `select "brands".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name",'brand_id',"products"."brand_id")) from (select "products".* from "production"."products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $1) as "products") as "products" from "production"."brands" order by "test"."id" asc limit $2`, + bindings: [limit, relationshipLimit], + sql: `with "paginated" as (select "brands".* from "production"."brands" order by "test"."id" asc limit $1) select "brands".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name",'brand_id',"products"."brand_id")) from (select "products".* from "production"."products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $2) as "products") as "products" from "paginated" as "brands" order by "test"."id" asc`, }) }) it("should handle if the schema is not present when doing a LEFT JOIN", () => { const query = sql._query(generateRelationshipJson()) expect(query).toEqual({ - bindings: [relationshipLimit, limit], - sql: `select "brands".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name",'brand_id',"products"."brand_id")) from (select "products".* from "products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $1) as "products") as "products" from "brands" order by "test"."id" asc limit $2`, + bindings: [limit, relationshipLimit], + sql: `with "paginated" as (select "brands".* from "brands" order by "test"."id" asc limit $1) select "brands".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name",'brand_id',"products"."brand_id")) from (select "products".* from "products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $2) as "products") as "products" from "paginated" as "brands" order by "test"."id" asc`, }) }) @@ -179,8 +179,8 @@ describe("SQL query builder", () => { generateManyRelationshipJson({ schema: "production" }) ) expect(query).toEqual({ - bindings: [relationshipLimit, limit], - sql: `select "stores".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name")) from (select "products".* from "production"."products" as "products" inner join "production"."stocks" as "stocks" on "products"."product_id" = "stocks"."product_id" where "stocks"."store_id" = "stores"."store_id" order by "products"."product_id" asc limit $1) as "products") as "products" from "production"."stores" order by "test"."id" asc limit $2`, + bindings: [limit, relationshipLimit], + sql: `with "paginated" as (select "stores".* from "production"."stores" order by "test"."id" asc limit $1) select "stores".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name")) from (select "products".* from "production"."products" as "products" inner join "production"."stocks" as "stocks" on "products"."product_id" = "stocks"."product_id" where "stocks"."store_id" = "stores"."store_id" order by "products"."product_id" asc limit $2) as "products") as "products" from "paginated" as "stores" order by "test"."id" asc`, }) }) diff --git a/packages/server/src/integrations/tests/sqlAlias.spec.ts b/packages/server/src/integrations/tests/sqlAlias.spec.ts index 528e782543..9548499c65 100644 --- a/packages/server/src/integrations/tests/sqlAlias.spec.ts +++ b/packages/server/src/integrations/tests/sqlAlias.spec.ts @@ -60,7 +60,7 @@ describe("Captures of real examples", () => { queryJson ) expect(query).toEqual({ - bindings: [relationshipLimit, relationshipLimit, primaryLimit], + bindings: [primaryLimit, relationshipLimit, relationshipLimit], sql: expect.stringContaining( multiline( `select json_agg(json_build_object('executorid',"b"."executorid",'taskname',"b"."taskname",'taskid',"b"."taskid",'completed',"b"."completed",'qaid',"b"."qaid",'executorid',"b"."executorid",'taskname',"b"."taskname",'taskid',"b"."taskid",'completed',"b"."completed",'qaid',"b"."qaid")` @@ -75,11 +75,11 @@ describe("Captures of real examples", () => { queryJson ) expect(query).toEqual({ - bindings: [relationshipLimit, "assembling", primaryLimit], + bindings: ["assembling", primaryLimit, relationshipLimit], sql: expect.stringContaining( multiline( - `where exists (select 1 from "tasks" as "b" inner join "products_tasks" as "c" on "b"."taskid" = "c"."taskid" - where "c"."productid" = "a"."productid" and COALESCE("b"."taskname" = $2, FALSE)` + `where exists (select 1 from "tasks" as "b" inner join "products_tasks" as "c" on "b"."taskid" = "c"."taskid" where "c"."productid" = "a"."productid" + and COALESCE("b"."taskname" = $1, FALSE)` ) ), }) @@ -91,12 +91,13 @@ describe("Captures of real examples", () => { queryJson ) expect(query).toEqual({ - bindings: [relationshipLimit, primaryLimit], + bindings: [primaryLimit, relationshipLimit], sql: expect.stringContaining( multiline( - `select json_agg(json_build_object('executorid',"b"."executorid",'taskname',"b"."taskname",'taskid',"b"."taskid",'completed',"b"."completed",'qaid',"b"."qaid")) - from (select "b".* from "tasks" as "b" inner join "products_tasks" as "c" on "b"."taskid" = "c"."taskid" - where "c"."productid" = "a"."productid" order by "b"."taskid" asc limit $1` + `with "paginated" as (select "a".* from "products" as "a" order by "a"."productname" asc nulls first, "a"."productid" asc limit $1) + select "a".*, (select json_agg(json_build_object('executorid',"b"."executorid",'taskname',"b"."taskname",'taskid',"b"."taskid",'completed',"b"."completed",'qaid',"b"."qaid")) + from (select "b".* from "tasks" as "b" inner join "products_tasks" as "c" on "b"."taskid" = "c"."taskid" where "c"."productid" = "a"."productid" order by "b"."taskid" asc limit $2) as "b") as "tasks" + from "paginated" as "a" order by "a"."productname" asc nulls first, "a"."productid" asc` ) ), }) @@ -109,12 +110,12 @@ describe("Captures of real examples", () => { queryJson ) expect(query).toEqual({ - bindings: [relationshipLimit, ...filters, relationshipLimit], + bindings: [...filters, relationshipLimit, relationshipLimit], sql: multiline( - `select "a".*, (select json_agg(json_build_object('productname',"b"."productname",'productid',"b"."productid")) + `with "paginated" as (select "a".* from "tasks" as "a" where "a"."taskid" in ($1, $2) order by "a"."taskid" asc limit $3) + select "a".*, (select json_agg(json_build_object('productname',"b"."productname",'productid',"b"."productid")) from (select "b".* from "products" as "b" inner join "products_tasks" as "c" on "b"."productid" = "c"."productid" - where "c"."taskid" = "a"."taskid" order by "b"."productid" asc limit $1) as "b") as "products" - from "tasks" as "a" where "a"."taskid" in ($2, $3) order by "a"."taskid" asc limit $4` + where "c"."taskid" = "a"."taskid" order by "b"."productid" asc limit $4) as "b") as "products" from "paginated" as "a" order by "a"."taskid" asc` ), }) }) @@ -132,18 +133,18 @@ describe("Captures of real examples", () => { expect(query).toEqual({ bindings: [ - relationshipLimit, - relationshipLimit, - relationshipLimit, rangeValue.low, rangeValue.high, equalValue, notEqualsValue, primaryLimit, + relationshipLimit, + relationshipLimit, + relationshipLimit, ], sql: expect.stringContaining( multiline( - `where exists (select 1 from "persons" as "c" where "c"."personid" = "a"."executorid" and "c"."year" between $4 and $5)` + `where exists (select 1 from "persons" as "c" where "c"."personid" = "a"."executorid" and "c"."year" between $1 and $2)` ) ), }) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index d5ed2ce906..214c9498d6 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -333,6 +333,7 @@ export default class TestConfiguration { sessionId: this.sessionIdForUser(_id), tenantId: this.getTenantId(), csrfToken: this.csrfToken, + email, }) const resp = await db.put(user) await cache.user.invalidateUser(_id) @@ -396,16 +397,17 @@ export default class TestConfiguration { } // make sure the user exists in the global DB if (roleId !== roles.BUILTIN_ROLE_IDS.PUBLIC) { - await this.globalUser({ + const user = await this.globalUser({ _id: userId, builder: { global: builder }, roles: { [appId]: roleId || roles.BUILTIN_ROLE_IDS.BASIC }, }) + await sessions.createASession(userId, { + sessionId: this.sessionIdForUser(userId), + tenantId: this.getTenantId(), + email: user.email, + }) } - await sessions.createASession(userId, { - sessionId: this.sessionIdForUser(userId), - tenantId: this.getTenantId(), - }) // have to fake this const authObj = { userId, diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 288524b099..a59935c6aa 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -328,7 +328,9 @@ class Orchestrator { } else if (step.stepId === AutomationActionStepId.LOOP) { stepIndex = await this.executeLoopStep(step, steps, stepIndex) } else { - await this.executeStep(step) + if (!this.stopped) { + await this.executeStep(step) + } stepIndex++ } } @@ -473,7 +475,7 @@ class Orchestrator { for (const branch of branches) { const condition = await this.evaluateBranchCondition(branch.condition) if (condition) { - let branchStatus = { + const branchStatus = { status: `${branch.name} branch taken`, success: true, } @@ -488,9 +490,20 @@ class Orchestrator { const branchSteps = children?.[branch.name] || [] await this.executeSteps(branchSteps) - break + return } } + + this.stopped = true + this.updateExecutionOutput( + branchStep.id, + branchStep.stepId, + branchStep.inputs, + { + success: false, + status: AutomationStatus.NO_CONDITION_MET, + } + ) } private async evaluateBranchCondition( diff --git a/packages/server/src/threads/query.ts b/packages/server/src/threads/query.ts index ba451a3325..c7e28d3bf4 100644 --- a/packages/server/src/threads/query.ts +++ b/packages/server/src/threads/query.ts @@ -247,7 +247,9 @@ class QueryRunner { if (!resp.err) { const globalUserId = getGlobalIDFromUserMetadataID(_id) await auth.updateUserOAuth(globalUserId, resp) - this.ctx.user = await cache.user.getUser(globalUserId) + this.ctx.user = await cache.user.getUser({ + userId: globalUserId, + }) } else { // In this event the user may have oAuth issues that // could require re-authenticating with their provider. diff --git a/packages/server/src/utilities/global.ts b/packages/server/src/utilities/global.ts index bbb84c1882..c1fcf35634 100644 --- a/packages/server/src/utilities/global.ts +++ b/packages/server/src/utilities/global.ts @@ -77,7 +77,9 @@ export async function getCachedSelf( ): Promise { // this has to be tenant aware, can't depend on the context to find it out // running some middlewares before the tenancy causes context to break - const user = await cache.user.getUser(ctx.user?._id!) + const user = await cache.user.getUser({ + userId: ctx.user?._id!, + }) return processUser(user, { appId }) } diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts index 874113f6f1..b02ea2ff60 100644 --- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts +++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts @@ -35,7 +35,9 @@ export async function processInputBBReference( } try { - await cache.user.getUser(id) + await cache.user.getUser({ + userId: id, + }) return id } catch (e: any) { if (e.statusCode === 404) { @@ -125,7 +127,9 @@ export async function processOutputBBReference( case BBReferenceFieldSubType.USER: { let user try { - user = await cache.user.getUser(value as string) + user = await cache.user.getUser({ + userId: value as string, + }) } catch (err: any) { if (err.statusCode !== 404) { throw err diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index c2a63fd674..f6cf44d6d6 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -110,7 +110,9 @@ async function processDefaultValues(table: Table, row: Row) { const identity = context.getIdentity() if (identity?._id && identity.type === IdentityType.USER) { - const user = await cache.user.getUser(identity._id) + const user = await cache.user.getUser({ + userId: identity._id, + }) delete user.password ctx["Current User"] = user diff --git a/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts b/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts index c5a7b7fc84..28c16dad71 100644 --- a/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts +++ b/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts @@ -74,7 +74,9 @@ describe("bbReferenceProcessor", () => { expect(result).toEqual(userId) expect(cacheGetUserSpy).toHaveBeenCalledTimes(1) - expect(cacheGetUserSpy).toHaveBeenCalledWith(userId) + expect(cacheGetUserSpy).toHaveBeenCalledWith({ + userId, + }) }) it("throws an error given an invalid id", async () => { @@ -98,7 +100,9 @@ describe("bbReferenceProcessor", () => { expect(result).toEqual(userId) expect(cacheGetUserSpy).toHaveBeenCalledTimes(1) - expect(cacheGetUserSpy).toHaveBeenCalledWith(userId) + expect(cacheGetUserSpy).toHaveBeenCalledWith({ + userId, + }) }) it("empty strings will return null", async () => { @@ -243,7 +247,9 @@ describe("bbReferenceProcessor", () => { lastName: user.lastName, }) expect(cacheGetUserSpy).toHaveBeenCalledTimes(1) - expect(cacheGetUserSpy).toHaveBeenCalledWith(userId) + expect(cacheGetUserSpy).toHaveBeenCalledWith({ + userId, + }) }) it("returns undefined given an unexisting user", async () => { @@ -255,7 +261,9 @@ describe("bbReferenceProcessor", () => { expect(result).toBeUndefined() expect(cacheGetUserSpy).toHaveBeenCalledTimes(1) - expect(cacheGetUserSpy).toHaveBeenCalledWith(userId) + expect(cacheGetUserSpy).toHaveBeenCalledWith({ + userId, + }) }) }) }) diff --git a/packages/types/src/documents/app/automation/automation.ts b/packages/types/src/documents/app/automation/automation.ts index fcc3af445c..72f8a1aa7c 100644 --- a/packages/types/src/documents/app/automation/automation.ts +++ b/packages/types/src/documents/app/automation/automation.ts @@ -179,6 +179,7 @@ export enum AutomationStatus { ERROR = "error", STOPPED = "stopped", STOPPED_ERROR = "stopped_error", + NO_CONDITION_MET = "No branch condition met", } export enum AutomationStoppedReason { diff --git a/packages/types/src/sdk/auth.ts b/packages/types/src/sdk/auth.ts index edcaf197e2..5bc75e8377 100644 --- a/packages/types/src/sdk/auth.ts +++ b/packages/types/src/sdk/auth.ts @@ -10,6 +10,7 @@ export interface AuthToken { export interface CreateSession { sessionId: string tenantId: string + email: string csrfToken?: string hosting?: Hosting } diff --git a/packages/worker/src/sdk/auth/auth.ts b/packages/worker/src/sdk/auth/auth.ts index 3f24de440a..078ac441e9 100644 --- a/packages/worker/src/sdk/auth/auth.ts +++ b/packages/worker/src/sdk/auth/auth.ts @@ -19,12 +19,17 @@ import { EmailTemplatePurpose } from "../../constants" export async function loginUser(user: User) { const sessionId = coreUtils.newid() const tenantId = tenancy.getTenantId() - await sessions.createASession(user._id!, { sessionId, tenantId }) + await sessions.createASession(user._id!, { + sessionId, + tenantId, + email: user.email, + }) return jwt.sign( { userId: user._id, sessionId, tenantId, + email: user.email, }, coreEnv.JWT_SECRET! ) diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index c1c8aa8b19..c2cf005308 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -170,19 +170,26 @@ class TestConfiguration { async _createSession({ userId, tenantId, + email, }: { userId: string tenantId: string + email: string }) { await sessions.createASession(userId!, { sessionId: "sessionid", - tenantId: tenantId, + tenantId, csrfToken: CSRF_TOKEN, + email, }) } async createSession(user: User) { - return this._createSession({ userId: user._id!, tenantId: user.tenantId }) + return this._createSession({ + userId: user._id!, + tenantId: user.tenantId, + email: user.email, + }) } cookieHeader(cookies: any) { diff --git a/yarn.lock b/yarn.lock index 146fa74d5b..3727e08d0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2053,44 +2053,6 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.31.8": - version "0.0.0" - dependencies: - "@budibase/nano" "10.1.5" - "@budibase/pouchdb-replication-stream" "1.2.11" - "@budibase/shared-core" "0.0.0" - "@budibase/types" "0.0.0" - aws-cloudfront-sign "3.0.2" - aws-sdk "2.1030.0" - bcrypt "5.1.0" - bcryptjs "2.4.3" - bull "4.10.1" - correlation-id "4.0.0" - dd-trace "5.2.0" - dotenv "16.0.1" - ioredis "5.3.2" - joi "17.6.0" - jsonwebtoken "9.0.2" - knex "2.4.2" - koa-passport "^6.0.0" - koa-pino-logger "4.0.0" - lodash "4.17.21" - node-fetch "2.6.7" - passport-google-oauth "2.0.0" - passport-local "1.0.0" - passport-oauth2-refresh "^2.1.0" - pino "8.11.0" - pino-http "8.3.3" - posthog-node "4.0.1" - pouchdb "7.3.0" - pouchdb-find "7.2.2" - redlock "4.2.0" - rotating-file-stream "3.1.0" - sanitize-s3-objectkey "0.0.1" - semver "^7.5.4" - tar-fs "2.1.1" - uuid "^8.3.2" - "@budibase/handlebars-helpers@^0.13.2": version "0.13.2" resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.13.2.tgz#73ab51c464e91fd955b429017648e0257060db77" @@ -2133,45 +2095,6 @@ pouchdb-promise "^6.0.4" through2 "^2.0.0" -"@budibase/pro@npm:@budibase/pro@latest": - version "2.31.8" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.31.8.tgz#92b27f99f815f5d20bf58bfae916760b14a036da" - integrity sha512-nmNKVoMdUVqEIq6xqoBq0gVBCLkoPMszmn0Zu0SJ/Dc2SpsXhPz9S3n9xXfAA+FHUg9LgUAS+eKPCKPWZXtDHQ== - dependencies: - "@budibase/backend-core" "2.31.8" - "@budibase/shared-core" "2.31.8" - "@budibase/string-templates" "2.31.8" - "@budibase/types" "2.31.8" - "@koa/router" "8.0.8" - bull "4.10.1" - dd-trace "5.2.0" - joi "17.6.0" - jsonwebtoken "9.0.2" - lru-cache "^7.14.1" - memorystream "^0.3.1" - node-fetch "2.6.7" - scim-patch "^0.8.1" - scim2-parse-filter "^0.2.8" - -"@budibase/shared-core@2.31.8": - version "0.0.0" - dependencies: - "@budibase/types" "0.0.0" - cron-validate "1.4.5" - -"@budibase/string-templates@2.31.8": - version "0.0.0" - dependencies: - "@budibase/handlebars-helpers" "^0.13.2" - dayjs "^1.10.8" - handlebars "^4.7.8" - lodash.clonedeep "^4.5.0" - -"@budibase/types@2.31.8": - version "0.0.0" - dependencies: - scim-patch "^0.8.1" - "@bull-board/api@5.10.2": version "5.10.2" resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-5.10.2.tgz#ae8ff6918b23897bf879a6ead3683f964374c4b3" @@ -6117,6 +6040,11 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/qs@^6.9.15": + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + "@types/range-parser@*": version "1.2.4" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" @@ -7525,7 +7453,30 @@ axios-retry@^3.1.9: "@babel/runtime" "^7.15.4" is-retry-allowed "^2.2.0" -axios@0.24.0, axios@1.1.3, axios@1.6.3, axios@^0.21.1, axios@^1.0.0, axios@^1.1.3, axios@^1.4.0, axios@^1.5.0, axios@^1.6.2: +axios@0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== + dependencies: + follow-redirects "^1.14.4" + +axios@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35" + integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +axios@^0.21.1: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + +axios@^1.0.0, axios@^1.1.3, axios@^1.4.0, axios@^1.5.0, axios@^1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.3.tgz#7f50f23b3aa246eff43c54834272346c396613f4" integrity sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww== @@ -11635,6 +11586,11 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== +follow-redirects@^1.14.0, follow-redirects@^1.14.4: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + follow-redirects@^1.15.0: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" @@ -12728,7 +12684,12 @@ http-assert@^1.3.0: deep-equal "~1.0.1" http-errors "~1.8.0" -http-cache-semantics@3.8.1, http-cache-semantics@4.1.1, http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: +http-cache-semantics@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" + integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== + +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== @@ -13739,11 +13700,6 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -isobject@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" - integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== - isolated-vm@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/isolated-vm/-/isolated-vm-4.7.2.tgz#5670d5cce1d92004f9b825bec5b0b11fc7501b65" @@ -16303,7 +16259,7 @@ msgpackr-extract@^3.0.2: "@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.2" "@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.2" -msgpackr@1.10.1, msgpackr@^1.5.2: +msgpackr@^1.5.2: version "1.10.1" resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.10.1.tgz#51953bb4ce4f3494f0c4af3f484f01cfbb306555" integrity sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ== @@ -16497,13 +16453,25 @@ node-domexception@1.0.0: resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== -node-fetch@2.6.0, node-fetch@2.6.7, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7, node-fetch@^2.6.9, node-fetch@^2.7.0: +node-fetch@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + +node-fetch@2.6.7, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.9, node-fetch@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-forge@^1.2.1, node-forge@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -17132,6 +17100,21 @@ openai@^4.52.1: node-fetch "^2.6.7" web-streams-polyfill "^3.2.1" +openai@^4.59.0: + version "4.59.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.59.0.tgz#3961d11a9afb5920e1bd475948a87969e244fc08" + integrity sha512-3bn7FypMt2R1ZDuO0+GcXgBEnVFhIzrpUkb47pQRoYvyfdZ2fQXcuP14aOc4C8F9FvCtZ/ElzJmVzVqnP4nHNg== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + "@types/qs" "^6.9.15" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + qs "^6.10.3" + openapi-response-validator@^9.2.0: version "9.3.1" resolved "https://registry.yarnpkg.com/openapi-response-validator/-/openapi-response-validator-9.3.1.tgz#54284d8be608ef53283cbe7448accce8106b1c56" @@ -17654,7 +17637,15 @@ passport-strategy@1.x.x, passport-strategy@^1.0.0: resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== -passport@0.6.0, passport@^0.4.0, passport@^0.6.0: +passport@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" + integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + +passport@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/passport/-/passport-0.6.0.tgz#e869579fab465b5c0b291e841e6cc95c005fac9d" integrity sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug== @@ -17798,11 +17789,21 @@ periscopic@^3.1.0: estree-walker "^3.0.0" is-reference "^3.0.0" +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + pg-connection-string@2.5.0, pg-connection-string@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== +pg-connection-string@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.4.tgz#f543862adfa49fa4e14bc8a8892d2a84d754246d" + integrity sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA== + pg-int8@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" @@ -17813,11 +17814,21 @@ pg-pool@^3.6.0: resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e" integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ== +pg-pool@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2" + integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg== + pg-protocol@*, pg-protocol@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== +pg-protocol@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" + integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== + pg-types@^2.1.0, pg-types@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" @@ -17842,6 +17853,19 @@ pg@8.10.0: pg-types "^2.1.0" pgpass "1.x" +pg@^8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.12.0.tgz#9341724db571022490b657908f65aee8db91df79" + integrity sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ== + dependencies: + pg-connection-string "^2.6.4" + pg-pool "^3.6.2" + pg-protocol "^1.6.1" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + pgpass@1.x: version "1.0.5" resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" @@ -18927,7 +18951,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== -psl@^1.1.33: +psl@^1.1.28, psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== @@ -19999,6 +20023,11 @@ sax@1.2.1: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== +sax@>=0.1.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -20071,13 +20100,33 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5", semver@7.5.3, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1, semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3, semver@~2.3.1: +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@7.5.3, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3: version "7.5.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== dependencies: lru-cache "^6.0.0" +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +semver@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52" + integrity sha512-abLdIKCosKfpnmhS52NCTjO4RiLspDfsn37prjzGrp9im5DPJOgh82Os92vtwGh6XdQryKI/7SREZnV+aqiXrA== + seq-queue@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" @@ -21640,7 +21689,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@4.1.3, "tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0", tough-cookie@^4.0.0, tough-cookie@^4.1.2, tough-cookie@~2.5.0: +"tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0", tough-cookie@^4.0.0, tough-cookie@^4.1.2: version "4.1.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== @@ -21650,6 +21699,14 @@ tough-cookie@4.1.3, "tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0", tough-cookie@^4.0 universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" @@ -22178,14 +22235,6 @@ unpipe@1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -unset-value@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-2.0.1.tgz#57bed0c22d26f28d69acde5df9a11b77c74d2df3" - integrity sha512-2hvrBfjUE00PkqN+q0XP6yRAOGrR06uSiUoIQGZkc7GxvQ9H7v8quUPNtZjMg4uux69i8HWpIjLPUKwCuRGyNg== - dependencies: - has-value "^2.0.2" - isobject "^4.0.0" - untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -22965,10 +23014,33 @@ xml-parse-from-string@^1.0.0: resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" integrity sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g== -xml2js@0.1.x, xml2js@0.4.19, xml2js@0.5.0, xml2js@0.6.2, xml2js@^0.4.19, xml2js@^0.4.5: - version "0.6.2" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" - integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== +xml2js@0.1.x: + version "0.1.14" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c" + integrity sha512-pbdws4PPPNc1HPluSUKamY4GWMk592K7qwcj6BExbVOhhubub8+pMda/ql68b6L3luZs/OGjGSB5goV7SnmgnA== + dependencies: + sax ">=0.1.1" + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xml2js@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" + integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xml2js@^0.4.19, xml2js@^0.4.5: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== dependencies: sax ">=0.6.0" xmlbuilder "~11.0.0" @@ -22978,6 +23050,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ== + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"