Merge branch 'master' into cache-get-helpers-list

This commit is contained in:
Sam Rose 2024-01-02 13:59:59 +00:00 committed by GitHub
commit a2e0d3245a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1909 additions and 1185 deletions

View File

@ -76,6 +76,18 @@ jobs:
yarn check:types
fi
helm-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Use Node.js 18.x
uses: azure/setup-helm@v3
- run: cd charts/budibase && helm lint .
test-libraries:
runs-on: ubuntu-latest
steps:

View File

@ -227,6 +227,14 @@ spec:
resources:
{{- toYaml . | nindent 10 }}
{{ end }}
{{ if .Values.services.apps.command }}
command:
{{- toYaml .Values.services.apps.command | nindent 10 }}
{{ end }}
{{ if .Values.services.apps.args }}
args:
{{- toYaml .Values.services.apps.args | nindent 10 }}
{{ end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
@ -244,12 +252,4 @@ spec:
{{ end }}
restartPolicy: Always
serviceAccountName: ""
{{ if .Values.services.apps.command }}
command:
{{- toYaml .Values.services.apps.command | nindent 8 }}
{{ end }}
{{ if .Values.services.apps.args }}
args:
{{- toYaml .Values.services.apps.args | nindent 8 }}
{{ end }}
status: {}

View File

@ -227,6 +227,13 @@ spec:
resources:
{{- toYaml . | nindent 10 }}
{{ end }}
command:
{{- toYaml .Values.services.automationWorkers.command | nindent 10 }}
{{ end }}
{{ if .Values.services.automationWorkers.args }}
args:
{{- toYaml .Values.services.automationWorkers.args | nindent 10 }}
{{ end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
@ -245,12 +252,5 @@ spec:
restartPolicy: Always
serviceAccountName: ""
{{ if .Values.services.automationWorkers.command }}}
command:
{{- toYaml .Values.services.automationWorkers.command | nindent 8 }}
{{ end }}
{{ if .Values.services.automationWorkers.args }}
args:
{{- toYaml .Values.services.automationWorkers.args | nindent 8 }}
{{ end }}
status: {}
{{- end }}

View File

@ -213,6 +213,14 @@ spec:
resources:
{{- toYaml . | nindent 10 }}
{{ end }}
{{ if .Values.services.worker.command }}
command:
{{- toYaml .Values.services.worker.command | nindent 10 }}
{{ end }}
{{ if .Values.services.worker.args }}
args:
{{- toYaml .Values.services.worker.args | nindent 10 }}
{{ end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
@ -230,12 +238,4 @@ spec:
{{ end }}
restartPolicy: Always
serviceAccountName: ""
{{ if .Values.services.worker.command }}
command:
{{- toYaml .Values.services.worker.command | nindent 8 }}
{{ end }}
{{ if .Values.services.worker.args }}
args:
{{- toYaml .Values.services.worker.args | nindent 8 }}
{{ end }}
status: {}

View File

@ -79,7 +79,7 @@ redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
/bbcouch-runner.sh &
# only start minio if use s3 isn't passed
if [[ -z "${USE_S3}"]]; then
if [[ -z "${USE_S3}" ]]; then
/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
fi

View File

@ -1,5 +1,5 @@
{
"version": "2.13.48",
"version": "2.13.51",
"npmClient": "yarn",
"packages": [
"packages/*",

@ -1 +1 @@
Subproject commit 09dae295e3ba6149c4e1d7fe567870c3a38bd277
Subproject commit c1a53bb2f4cafcb4c55ad7181146617b449907f2

View File

@ -32,7 +32,7 @@
"bcryptjs": "2.4.3",
"bull": "4.10.1",
"correlation-id": "4.0.0",
"dd-trace": "4.20.0",
"dd-trace": "3.13.2",
"dotenv": "16.0.1",
"ioredis": "5.3.2",
"joi": "17.6.0",

View File

@ -18,14 +18,15 @@ export enum TTL {
ONE_DAY = 86400,
}
function performExport(funcName: string) {
// @ts-ignore
return (...args: any) => GENERIC[funcName](...args)
}
export const keys = performExport("keys")
export const get = performExport("get")
export const store = performExport("store")
export const destroy = performExport("delete")
export const withCache = performExport("withCache")
export const bustCache = performExport("bustCache")
export const keys = (...args: Parameters<typeof GENERIC.keys>) =>
GENERIC.keys(...args)
export const get = (...args: Parameters<typeof GENERIC.get>) =>
GENERIC.get(...args)
export const store = (...args: Parameters<typeof GENERIC.store>) =>
GENERIC.store(...args)
export const destroy = (...args: Parameters<typeof GENERIC.delete>) =>
GENERIC.delete(...args)
export const withCache = (...args: Parameters<typeof GENERIC.withCache>) =>
GENERIC.withCache(...args)
export const bustCache = (...args: Parameters<typeof GENERIC.bustCache>) =>
GENERIC.bustCache(...args)

View File

@ -1,6 +1,6 @@
import * as redis from "../redis/init"
import * as utils from "../utils"
import { Duration, DurationType } from "../utils"
import { Duration } from "../utils"
const TTL_SECONDS = Duration.fromHours(1).toSeconds()
@ -32,7 +32,18 @@ export async function getCode(code: string): Promise<PasswordReset> {
const client = await redis.getPasswordResetClient()
const value = (await client.get(code)) as PasswordReset | undefined
if (!value) {
throw "Provided information is not valid, cannot reset password - please try again."
throw new Error(
"Provided information is not valid, cannot reset password - please try again."
)
}
return value
}
/**
* Given a reset code this will invalidate it.
* @param code The code provided via the email link.
*/
export async function invalidateCode(code: string): Promise<void> {
const client = await redis.getPasswordResetClient()
await client.delete(code)
}

View File

@ -37,7 +37,7 @@ export function DatabaseWithConnection(
opts?: DatabaseOpts
) {
const db = new DatabaseImpl(dbName, opts, connection)
return new DDInstrumentedDatabase(db, "couchdb")
return new DDInstrumentedDatabase(db)
}
export class DatabaseImpl implements Database {

View File

@ -3,7 +3,7 @@ import { CouchFindOptions, Database, DatabaseOpts } from "@budibase/types"
import { DDInstrumentedDatabase } from "./instrumentation"
export function getDB(dbName: string, opts?: DatabaseOpts): Database {
return new DDInstrumentedDatabase(new DatabaseImpl(dbName, opts), "couchdb")
return new DDInstrumentedDatabase(new DatabaseImpl(dbName, opts))
}
// we have to use a callback for this so that we can close

View File

@ -18,31 +18,28 @@ import tracer from "dd-trace"
import { Writable } from "stream"
export class DDInstrumentedDatabase implements Database {
constructor(
private readonly db: Database,
private readonly resource: string
) {}
constructor(private readonly db: Database) {}
get name(): string {
return this.db.name
}
exists(): Promise<boolean> {
return tracer.trace("exists", { resource: this.resource }, span => {
return tracer.trace("db.exists", span => {
span?.addTags({ db_name: this.name })
return this.db.exists()
})
}
checkSetup(): Promise<DocumentScope<any>> {
return tracer.trace("checkSetup", { resource: this.resource }, span => {
return tracer.trace("db.checkSetup", span => {
span?.addTags({ db_name: this.name })
return this.db.checkSetup()
})
}
get<T extends Document>(id?: string | undefined): Promise<T> {
return tracer.trace("get", { resource: this.resource }, span => {
return tracer.trace("db.get", span => {
span?.addTags({ db_name: this.name, doc_id: id })
return this.db.get(id)
})
@ -52,7 +49,7 @@ export class DDInstrumentedDatabase implements Database {
ids: string[],
opts?: { allowMissing?: boolean | undefined } | undefined
): Promise<T[]> {
return tracer.trace("getMultiple", { resource: this.resource }, span => {
return tracer.trace("db.getMultiple", span => {
span?.addTags({
db_name: this.name,
num_docs: ids.length,
@ -66,7 +63,7 @@ export class DDInstrumentedDatabase implements Database {
id: string | Document,
rev?: string | undefined
): Promise<DocumentDestroyResponse> {
return tracer.trace("remove", { resource: this.resource }, span => {
return tracer.trace("db.remove", span => {
span?.addTags({ db_name: this.name, doc_id: id })
return this.db.remove(id, rev)
})
@ -76,14 +73,14 @@ export class DDInstrumentedDatabase implements Database {
document: AnyDocument,
opts?: DatabasePutOpts | undefined
): Promise<DocumentInsertResponse> {
return tracer.trace("put", { resource: this.resource }, span => {
return tracer.trace("db.put", span => {
span?.addTags({ db_name: this.name, doc_id: document._id })
return this.db.put(document, opts)
})
}
bulkDocs(documents: AnyDocument[]): Promise<DocumentBulkResponse[]> {
return tracer.trace("bulkDocs", { resource: this.resource }, span => {
return tracer.trace("db.bulkDocs", span => {
span?.addTags({ db_name: this.name, num_docs: documents.length })
return this.db.bulkDocs(documents)
})
@ -92,7 +89,7 @@ export class DDInstrumentedDatabase implements Database {
allDocs<T extends Document>(
params: DatabaseQueryOpts
): Promise<AllDocsResponse<T>> {
return tracer.trace("allDocs", { resource: this.resource }, span => {
return tracer.trace("db.allDocs", span => {
span?.addTags({ db_name: this.name })
return this.db.allDocs(params)
})
@ -102,56 +99,56 @@ export class DDInstrumentedDatabase implements Database {
viewName: string,
params: DatabaseQueryOpts
): Promise<AllDocsResponse<T>> {
return tracer.trace("query", { resource: this.resource }, span => {
return tracer.trace("db.query", span => {
span?.addTags({ db_name: this.name, view_name: viewName })
return this.db.query(viewName, params)
})
}
destroy(): Promise<void | OkResponse> {
return tracer.trace("destroy", { resource: this.resource }, span => {
return tracer.trace("db.destroy", span => {
span?.addTags({ db_name: this.name })
return this.db.destroy()
})
}
compact(): Promise<void | OkResponse> {
return tracer.trace("compact", { resource: this.resource }, span => {
return tracer.trace("db.compact", span => {
span?.addTags({ db_name: this.name })
return this.db.compact()
})
}
dump(stream: Writable, opts?: DatabaseDumpOpts | undefined): Promise<any> {
return tracer.trace("dump", { resource: this.resource }, span => {
return tracer.trace("db.dump", span => {
span?.addTags({ db_name: this.name })
return this.db.dump(stream, opts)
})
}
load(...args: any[]): Promise<any> {
return tracer.trace("load", { resource: this.resource }, span => {
return tracer.trace("db.load", span => {
span?.addTags({ db_name: this.name })
return this.db.load(...args)
})
}
createIndex(...args: any[]): Promise<any> {
return tracer.trace("createIndex", { resource: this.resource }, span => {
return tracer.trace("db.createIndex", span => {
span?.addTags({ db_name: this.name })
return this.db.createIndex(...args)
})
}
deleteIndex(...args: any[]): Promise<any> {
return tracer.trace("deleteIndex", { resource: this.resource }, span => {
return tracer.trace("db.deleteIndex", span => {
span?.addTags({ db_name: this.name })
return this.db.deleteIndex(...args)
})
}
getIndexes(...args: any[]): Promise<any> {
return tracer.trace("getIndexes", { resource: this.resource }, span => {
return tracer.trace("db.getIndexes", span => {
span?.addTags({ db_name: this.name })
return this.db.getIndexes(...args)
})

View File

@ -15,6 +15,7 @@ function newJob(queue: string, message: any) {
timestamp: Date.now(),
queue: queue,
data: message,
opts: {},
}
}

View File

@ -47,7 +47,7 @@ export function createQueue<T>(
cleanupInterval = timers.set(cleanup, CLEANUP_PERIOD_MS)
// fire off an initial cleanup
cleanup().catch(err => {
console.error(`Unable to cleanup automation queue initially - ${err}`)
console.error(`Unable to cleanup ${jobQueue} initially - ${err}`)
})
}
return queue

View File

@ -18,6 +18,7 @@ import {
SelectableDatabase,
getRedisConnectionDetails,
} from "./utils"
import { logAlert } from "../logging"
import * as timers from "../timers"
const RETRY_PERIOD_MS = 2000
@ -39,21 +40,16 @@ function pickClient(selectDb: number): any {
return CLIENTS[selectDb]
}
function connectionError(
selectDb: number,
timeout: NodeJS.Timeout,
err: Error | string
) {
function connectionError(timeout: NodeJS.Timeout, err: Error | string) {
// manually shut down, ignore errors
if (CLOSED) {
return
}
pickClient(selectDb).disconnect()
CLOSED = true
// always clear this on error
clearTimeout(timeout)
CONNECTED = false
console.error("Redis connection failed - " + err)
logAlert("Redis connection failed", err)
setTimeout(() => {
init()
}, RETRY_PERIOD_MS)
@ -79,11 +75,7 @@ function init(selectDb = DEFAULT_SELECT_DB) {
// start the timer - only allowed 5 seconds to connect
timeout = setTimeout(() => {
if (!CONNECTED) {
connectionError(
selectDb,
timeout,
"Did not successfully connect in timeout"
)
connectionError(timeout, "Did not successfully connect in timeout")
}
}, STARTUP_TIMEOUT_MS)
@ -106,12 +98,13 @@ function init(selectDb = DEFAULT_SELECT_DB) {
// allow the process to exit
return
}
connectionError(selectDb, timeout, err)
connectionError(timeout, err)
})
client.on("error", (err: Error) => {
connectionError(selectDb, timeout, err)
connectionError(timeout, err)
})
client.on("connect", () => {
console.log(`Connected to Redis DB: ${selectDb}`)
clearTimeout(timeout)
CONNECTED = true
})

View File

@ -30,7 +30,7 @@ export class ExecutionTimeTracker {
return new ExecutionTimeTracker(limitMs)
}
constructor(private limitMs: number) {}
constructor(readonly limitMs: number) {}
private totalTimeMs = 0
@ -46,6 +46,10 @@ export class ExecutionTimeTracker {
}
}
get elapsedMS() {
return this.totalTimeMs
}
private checkLimit() {
if (this.totalTimeMs > this.limitMs) {
throw new ExecutionTimeoutError(

View File

@ -2,7 +2,7 @@ import env from "../environment"
import * as eventHelpers from "./events"
import * as accountSdk from "../accounts"
import * as cache from "../cache"
import { doInTenant, getGlobalDB, getIdentity, getTenantId } from "../context"
import { getGlobalDB, getIdentity, getTenantId } from "../context"
import * as dbUtils from "../db"
import { EmailUnavailableError, HTTPError } from "../errors"
import * as platform from "../platform"

View File

@ -65,7 +65,7 @@
"cookies": "0.8.0",
"csvtojson": "2.0.10",
"curlconverter": "3.21.0",
"dd-trace": "4.20.0",
"dd-trace": "3.13.2",
"dotenv": "8.2.0",
"form-data": "4.0.0",
"global-agent": "3.0.0",

View File

@ -0,0 +1,196 @@
#!/bin/node
const {
createApp,
getTable,
createRow,
createTable,
getApp,
getRows,
} = require("./utils")
const Chance = require("chance")
const generator = new Chance()
const STUDENT_COUNT = 500
const SUBJECT_COUNT = 10
let { apiKey, appId } = require("yargs")
.demandOption(["apiKey"])
.option("appId").argv
const start = Date.now()
async function batchCreate(apiKey, appId, table, items, batchSize = 100) {
let i = 0
let errors = 0
async function createSingleRow(item) {
try {
const row = await createRow(apiKey, appId, table, item)
console.log(
`${table.name} - ${++i} of ${items.length} created (${
(Date.now() - start) / 1000
}s)`
)
return row
} catch {
errors++
}
}
const rows = []
const maxConcurrency = Math.min(batchSize, items.length)
const inFlight = {}
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
const item = items[itemIndex]
const promise = createSingleRow(item)
.then(result => {
rows.push(result)
})
.finally(() => {
delete inFlight[itemIndex]
})
inFlight[itemIndex] = promise
if (Object.keys(inFlight).length >= maxConcurrency) {
await Promise.race(Object.values(inFlight))
}
}
await Promise.all(Object.values(inFlight))
if (errors) {
console.error(
`${table.name} - ${errors} creation errored (${
(Date.now() - start) / 1000
}s)`
)
}
return rows
}
const useExistingApp = !!appId
async function upsertTable(appId, tableName, tableData) {
if (useExistingApp) {
return await getTable(apiKey, appId, tableName)
}
const table = await createTable(apiKey, appId, {
...tableData,
name: tableName,
})
return table
}
async function run() {
if (!appId) {
const app = appId ? await getApp(apiKey, appId) : await createApp(apiKey)
appId = app._id
console.log(`App created. Url: http://localhost:10000/builder/app/${appId}`)
} else {
console.log(
`App retrieved. Url: http://localhost:10000/builder/app/${appId}`
)
}
const studentsTable = await getTable(apiKey, appId, "Students")
let studentNumber = studentsTable.schema["Auto ID"].lastID
const students = await batchCreate(
apiKey,
appId,
studentsTable,
Array.from({ length: STUDENT_COUNT }).map(() => ({
"Student Number": (++studentNumber).toString(),
"First Name": generator.first(),
"Last Name": generator.last(),
Gender: generator.pickone(["M", "F"]),
Grade: generator.pickone(["8", "9", "10", "11"]),
"Tardiness (Days)": generator.integer({ min: 1, max: 100 }),
"Home Number": generator.phone(),
"Attendance_(%)": generator.integer({ min: 0, max: 100 }),
}))
)
const subjectTable = await upsertTable(appId, "Subjects", {
schema: {
Name: {
name: "Name",
type: "string",
},
},
primaryDisplay: "Name",
})
const subjects = useExistingApp
? await getRows(apiKey, appId, subjectTable._id)
: await batchCreate(
apiKey,
appId,
subjectTable,
Array.from({ length: SUBJECT_COUNT }).map(() => ({
Name: generator.profession(),
}))
)
const gradesTable = await upsertTable(appId, "Grades", {
schema: {
Score: {
name: "Score",
type: "number",
},
Student: {
name: "Student",
tableId: studentsTable._id,
constraints: {
presence: true,
type: "array",
},
fieldName: "Grades",
relationshipType: "one-to-many",
type: "link",
},
Subject: {
name: "Subject",
tableId: subjectTable._id,
constraints: {
presence: true,
type: "array",
},
fieldName: "Grades",
relationshipType: "one-to-many",
type: "link",
},
},
})
await batchCreate(
apiKey,
appId,
gradesTable,
students.flatMap(student =>
subjects.map(subject => ({
Score: generator.integer({ min: 0, max: 100 }),
Student: [student],
Subject: [subject],
}))
)
)
console.log(
`Access the app here: http://localhost:10000/builder/app/${appId}`
)
}
run()
.then(() => {
console.log(`Done in ${(Date.now() - start) / 1000} seconds`)
})
.catch(err => {
console.error(err)
})

View File

@ -0,0 +1,29 @@
#!/bin/node
const { searchApps, deleteApp } = require("./utils")
if (!process.argv[2]) {
console.error("Please specify an API key as script argument.")
process.exit(-1)
}
async function run() {
const apiKey = process.argv[2]
const apps = await searchApps(apiKey)
console.log(`Deleting ${apps.length} apps`)
let deletedApps = 0
await Promise.all(
apps.map(async app => {
await deleteApp(apiKey, app._id)
console.log(`App ${++deletedApps} of ${apps.length} deleted`)
})
)
}
run()
.then(() => {
console.log("Done!")
})
.catch(err => {
console.error(err)
})

View File

@ -2,7 +2,8 @@ const fetch = require("node-fetch")
const uuid = require("uuid/v4")
const URL_APP = "http://localhost:10000/api/public/v1/applications"
const URL_TABLE = "http://localhost:10000/api/public/v1/tables/search"
const URL_TABLE = "http://localhost:10000/api/public/v1/tables"
const URL_SEARCH_TABLE = "http://localhost:10000/api/public/v1/tables/search"
async function request(apiKey, url, method, body, appId = undefined) {
const headers = {
@ -37,30 +38,64 @@ exports.createApp = async apiKey => {
return json.data
}
exports.getTable = async (apiKey, appId) => {
const res = await request(apiKey, URL_TABLE, "POST", {}, appId)
exports.getApp = async (apiKey, appId) => {
const res = await request(apiKey, `${URL_APP}/${appId}`, "GET")
const json = await res.json()
return json.data[0]
return json.data
}
exports.searchApps = async apiKey => {
const res = await request(apiKey, `${URL_APP}/search`, "POST", {})
const json = await res.json()
return json.data
}
exports.createRow = async (apiKey, appId, table) => {
const body = {}
for (let [key, schema] of Object.entries(table.schema)) {
let fake
switch (schema.type) {
default:
case "string":
fake = schema.constraints.inclusion
? schema.constraints.inclusion[0]
: "a"
break
case "number":
fake = 1
break
exports.deleteApp = async (apiKey, appId) => {
const res = await request(apiKey, `${URL_APP}/${appId}`, "DELETE")
return res
}
exports.getTable = async (apiKey, appId, tableName) => {
const res = await request(apiKey, URL_SEARCH_TABLE, "POST", {}, appId)
const json = await res.json()
const table = json.data.find(t => t.name === tableName)
if (!table) {
throw `Table '${tableName} not found`
}
return table
}
exports.createRow = async (apiKey, appId, table, body) => {
if (!body) {
body = {}
for (let [key, schema] of Object.entries(table.schema)) {
let fake
switch (schema.type) {
default:
case "string":
fake = schema.constraints?.inclusion
? schema.constraints.inclusion[0]
: "a"
break
case "number":
fake = 1
break
}
body[key] = fake
}
body[key] = fake
}
const url = `http://localhost:10000/api/public/v1/tables/${table._id}/rows`
const res = await request(apiKey, url, "POST", body, appId)
return (await res.json()).data
}
exports.getRows = async (apiKey, appId, tableId) => {
const url = `${URL_TABLE}/${tableId}/rows/search`
const res = await request(apiKey, url, "POST", {}, appId)
return (await res.json()).data
}
exports.createTable = async (apiKey, appId, config) => {
const res = await request(apiKey, URL_TABLE, "POST", config, appId)
const json = await res.json()
return json.data
}

View File

@ -26,7 +26,7 @@ import {
inputProcessing,
outputProcessing,
} from "../../../utilities/rowProcessor"
import { cloneDeep, isEqual } from "lodash"
import { cloneDeep } from "lodash"
export async function handleRequest<T extends Operation>(
operation: T,
@ -86,50 +86,6 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
}
}
export async function save(ctx: UserCtx) {
const inputs = ctx.request.body
const tableId = utils.getTableId(ctx)
const table = await sdk.tables.getTable(tableId)
const { table: updatedTable, row } = await inputProcessing(
ctx.user?._id,
cloneDeep(table),
inputs
)
const validateResult = await sdk.rows.utils.validate({
row,
tableId,
})
if (!validateResult.valid) {
throw { validation: validateResult.errors }
}
const response = await handleRequest(Operation.CREATE, tableId, {
row,
})
if (!isEqual(table, updatedTable)) {
await sdk.tables.saveTable(updatedTable)
}
const rowId = response.row._id
if (rowId) {
const row = await sdk.rows.external.getRow(tableId, rowId, {
relationships: true,
})
return {
...response,
row: await outputProcessing(table, row, {
preserveLinks: true,
squash: true,
}),
}
} else {
return response
}
}
export async function find(ctx: UserCtx): Promise<Row> {
const id = ctx.params.rowId
const tableId = utils.getTableId(ctx)

View File

@ -30,7 +30,7 @@ import { Format } from "../view/exporters"
export * as views from "./views"
function pickApi(tableId: any) {
function pickApi(tableId: string) {
if (isExternalTableID(tableId)) {
return external
}
@ -84,9 +84,12 @@ export const save = async (ctx: UserCtx<Row, Row>) => {
return patch(ctx as UserCtx<PatchRowRequest, PatchRowResponse>)
}
const { row, table, squashed } = await quotas.addRow(() =>
quotas.addQuery(() => pickApi(tableId).save(ctx), {
datasourceId: tableId,
})
quotas.addQuery(
() => sdk.rows.save(tableId, ctx.request.body, ctx.user?._id),
{
datasourceId: tableId,
}
)
)
ctx.status = 200
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)

View File

@ -1,5 +1,5 @@
import * as linkRows from "../../../db/linkedRows"
import { generateRowID, InternalTables } from "../../../db/utils"
import { InternalTables } from "../../../db/utils"
import * as userController from "../user"
import {
AttachmentCleanup,
@ -94,45 +94,6 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
})
}
export async function save(ctx: UserCtx) {
let inputs = ctx.request.body
inputs.tableId = utils.getTableId(ctx)
if (!inputs._rev && !inputs._id) {
inputs._id = generateRowID(inputs.tableId)
}
// this returns the table and row incase they have been updated
const dbTable = await sdk.tables.getTable(inputs.tableId)
// need to copy the table so it can be differenced on way out
const tableClone = cloneDeep(dbTable)
let { table, row } = await inputProcessing(ctx.user?._id, tableClone, inputs)
const validateResult = await sdk.rows.utils.validate({
row,
table,
})
if (!validateResult.valid) {
throw { validation: validateResult.errors }
}
// make sure link rows are up-to-date
row = (await linkRows.updateLinks({
eventType: linkRows.EventType.ROW_SAVE,
row,
tableId: row.tableId,
table,
})) as Row
return finaliseRow(table, row, {
oldTable: dbTable,
updateFormula: true,
})
}
export async function find(ctx: UserCtx): Promise<Row> {
const tableId = utils.getTableId(ctx),
rowId = ctx.params.rowId

View File

@ -5,8 +5,8 @@ import {
processFormulas,
} from "../../../utilities/rowProcessor"
import { FieldTypes, FormulaTypes } from "../../../constants"
import { context } from "@budibase/backend-core"
import { Table, Row } from "@budibase/types"
import { context, locks } from "@budibase/backend-core"
import { Table, Row, LockType, LockName } from "@budibase/types"
import * as linkRows from "../../../db/linkedRows"
import sdk from "../../../sdk"
import isEqual from "lodash/isEqual"
@ -149,12 +149,22 @@ export async function finaliseRow(
await db.put(table)
} catch (err: any) {
if (err.status === 409) {
const updatedTable = await sdk.tables.getTable(table._id!)
let response = processAutoColumn(null, updatedTable, row, {
reprocessing: true,
})
await db.put(response.table)
row = response.row
// Some conflicts with the autocolumns occurred, we need to refetch the table and recalculate
await locks.doWithLock(
{
type: LockType.AUTO_EXTEND,
name: LockName.PROCESS_AUTO_COLUMNS,
resource: table._id,
},
async () => {
const latestTable = await sdk.tables.getTable(table._id!)
let response = processAutoColumn(null, latestTable, row, {
reprocessing: true,
})
await db.put(response.table)
row = response.row
}
)
} else {
throw err
}

View File

@ -77,7 +77,7 @@ const publicRouter = new Router({
prefix: PREFIX,
})
if (limiter) {
if (limiter && !env.isDev()) {
publicRouter.use(limiter)
}

View File

@ -16,6 +16,7 @@ import {
} from "@budibase/types"
import sdk from "../sdk"
import { automationsEnabled } from "../features"
import tracer from "dd-trace"
const REBOOT_CRON = "@reboot"
const WH_STEP_ID = definitions.WEBHOOK.stepId
@ -39,26 +40,62 @@ function loggingArgs(job: AutomationJob) {
}
export async function processEvent(job: AutomationJob) {
const appId = job.data.event.appId!
const automationId = job.data.automation._id!
const task = async () => {
try {
// need to actually await these so that an error can be captured properly
console.log("automation running", ...loggingArgs(job))
return tracer.trace(
"processEvent",
{ resource: "automation" },
async span => {
const appId = job.data.event.appId!
const automationId = job.data.automation._id!
const runFn = () => Runner.run(job)
const result = await quotas.addAutomation(runFn, {
span?.addTags({
appId,
automationId,
job: {
id: job.id,
name: job.name,
attemptsMade: job.attemptsMade,
opts: {
attempts: job.opts.attempts,
priority: job.opts.priority,
delay: job.opts.delay,
repeat: job.opts.repeat,
backoff: job.opts.backoff,
lifo: job.opts.lifo,
timeout: job.opts.timeout,
jobId: job.opts.jobId,
removeOnComplete: job.opts.removeOnComplete,
removeOnFail: job.opts.removeOnFail,
stackTraceLimit: job.opts.stackTraceLimit,
preventParsingData: job.opts.preventParsingData,
},
},
})
console.log("automation completed", ...loggingArgs(job))
return result
} catch (err) {
console.error(`automation was unable to run`, err, ...loggingArgs(job))
return { err }
}
}
return await context.doInAutomationContext({ appId, automationId, task })
const task = async () => {
try {
// need to actually await these so that an error can be captured properly
console.log("automation running", ...loggingArgs(job))
const runFn = () => Runner.run(job)
const result = await quotas.addAutomation(runFn, {
automationId,
})
console.log("automation completed", ...loggingArgs(job))
return result
} catch (err) {
span?.addTags({ error: true })
console.error(
`automation was unable to run`,
err,
...loggingArgs(job)
)
return { err }
}
}
return await context.doInAutomationContext({ appId, automationId, task })
}
)
}
export async function updateTestHistory(

View File

@ -131,7 +131,10 @@ class RestIntegration implements IntegrationBase {
let data, raw, headers
const contentType = response.headers.get("content-type") || ""
try {
if (contentType.includes("application/json")) {
if (response.status === 204) {
data = []
raw = []
} else if (contentType.includes("application/json")) {
data = await response.json()
raw = JSON.stringify(data)
} else if (

View File

@ -186,9 +186,15 @@ describe("REST Integration", () => {
})
describe("response", () => {
function buildInput(json: any, text: any, header: any) {
const contentTypes = ["application/json", "text/plain", "application/xml"]
function buildInput(
json: any,
text: any,
header: any,
status: number = 200
) {
return {
status: 200,
status,
json: json ? async () => json : undefined,
text: text ? async () => text : undefined,
headers: {
@ -225,6 +231,18 @@ describe("REST Integration", () => {
expect(output.extra.raw).toEqual(text)
expect(output.extra.headers["content-type"]).toEqual("application/xml")
})
test.each(contentTypes)(
"should not throw an error on 204 no content",
async contentType => {
const input = buildInput(undefined, null, contentType, 204)
const output = await config.integration.parseResponse(input)
expect(output.data).toEqual([])
expect(output.extra.raw).toEqual([])
expect(output.info.code).toEqual(204)
expect(output.extra.headers["content-type"]).toEqual(contentType)
}
)
})
describe("authentication", () => {

View File

@ -2,35 +2,44 @@ import vm from "vm"
import env from "./environment"
import { setJSRunner } from "@budibase/string-templates"
import { context, timers } from "@budibase/backend-core"
import tracer from "dd-trace"
type TrackerFn = <T>(f: () => T) => T
export function init() {
setJSRunner((js: string, ctx: vm.Context) => {
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
let track: TrackerFn = f => f()
if (perRequestLimit) {
const bbCtx = context.getCurrentContext()
if (bbCtx) {
if (!bbCtx.jsExecutionTracker) {
bbCtx.jsExecutionTracker =
timers.ExecutionTimeTracker.withLimit(perRequestLimit)
return tracer.trace("runJS", {}, span => {
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
let track: TrackerFn = f => f()
if (perRequestLimit) {
const bbCtx = context.getCurrentContext()
if (bbCtx) {
if (!bbCtx.jsExecutionTracker) {
bbCtx.jsExecutionTracker =
timers.ExecutionTimeTracker.withLimit(perRequestLimit)
}
track = bbCtx.jsExecutionTracker.track.bind(bbCtx.jsExecutionTracker)
span?.addTags({
js: {
limitMS: bbCtx.jsExecutionTracker.limitMs,
elapsedMS: bbCtx.jsExecutionTracker.elapsedMS,
},
})
}
track = bbCtx.jsExecutionTracker.track.bind(bbCtx.jsExecutionTracker)
}
}
ctx = {
...ctx,
alert: undefined,
setInterval: undefined,
setTimeout: undefined,
}
vm.createContext(ctx)
return track(() =>
vm.runInNewContext(js, ctx, {
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
})
)
ctx = {
...ctx,
alert: undefined,
setInterval: undefined,
setTimeout: undefined,
}
vm.createContext(ctx)
return track(() =>
vm.runInNewContext(js, ctx, {
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
})
)
})
})
}

View File

@ -23,7 +23,7 @@ export default async (ctx: UserCtx, next: any) => {
if (requestAppId) {
const span = tracer.scope().active()
span?.setBaggageItem("appId", requestAppId)
span?.setTag("appId", requestAppId)
}
// deny access to application preview
@ -79,9 +79,9 @@ export default async (ctx: UserCtx, next: any) => {
if (ctx.user) {
const span = tracer.scope().active()
if (ctx.user._id) {
span?.setBaggageItem("userId", ctx.user._id)
span?.setTag("userId", ctx.user._id)
}
span?.setBaggageItem("tenantId", ctx.user.tenantId)
span?.setTag("tenantId", ctx.user.tenantId)
}
const userId = ctx.user ? generateUserMetadataID(ctx.user._id!) : undefined

View File

@ -1,6 +1,13 @@
import { IncludeRelationship, Operation } from "@budibase/types"
import { IncludeRelationship, Operation, Row } from "@budibase/types"
import { handleRequest } from "../../../api/controllers/row/external"
import { breakRowIdField } from "../../../integrations/utils"
import sdk from "../../../sdk"
import {
inputProcessing,
outputProcessing,
} from "../../../utilities/rowProcessor"
import cloneDeep from "lodash/fp/cloneDeep"
import isEqual from "lodash/fp/isEqual"
export async function getRow(
tableId: string,
@ -15,3 +22,48 @@ export async function getRow(
})
return response ? response[0] : response
}
export async function save(
tableId: string,
inputs: Row,
userId: string | undefined
) {
const table = await sdk.tables.getTable(tableId)
const { table: updatedTable, row } = await inputProcessing(
userId,
cloneDeep(table),
inputs
)
const validateResult = await sdk.rows.utils.validate({
row,
tableId,
})
if (!validateResult.valid) {
throw { validation: validateResult.errors }
}
const response = await handleRequest(Operation.CREATE, tableId, {
row,
})
if (!isEqual(table, updatedTable)) {
await sdk.tables.saveTable(updatedTable)
}
const rowId = response.row._id
if (rowId) {
const row = await sdk.rows.external.getRow(tableId, rowId, {
relationships: true,
})
return {
...response,
row: await outputProcessing(table, row, {
preserveLinks: true,
squash: true,
}),
}
} else {
return response
}
}

View File

@ -0,0 +1,49 @@
import { db } from "@budibase/backend-core"
import { Row } from "@budibase/types"
import sdk from "../../../sdk"
import cloneDeep from "lodash/fp/cloneDeep"
import { finaliseRow } from "../../../api/controllers/row/staticFormula"
import { inputProcessing } from "../../../utilities/rowProcessor"
import * as linkRows from "../../../db/linkedRows"
export async function save(
tableId: string,
inputs: Row,
userId: string | undefined
) {
inputs.tableId = tableId
if (!inputs._rev && !inputs._id) {
inputs._id = db.generateRowID(inputs.tableId)
}
// this returns the table and row incase they have been updated
const dbTable = await sdk.tables.getTable(inputs.tableId)
// need to copy the table so it can be differenced on way out
const tableClone = cloneDeep(dbTable)
let { table, row } = await inputProcessing(userId, tableClone, inputs)
const validateResult = await sdk.rows.utils.validate({
row,
table,
})
if (!validateResult.valid) {
throw { validation: validateResult.errors }
}
// make sure link rows are up-to-date
row = (await linkRows.updateLinks({
eventType: linkRows.EventType.ROW_SAVE,
row,
tableId: row.tableId,
table,
})) as Row
return finaliseRow(table, row, {
oldTable: dbTable,
updateFormula: true,
})
}

View File

@ -1,6 +1,9 @@
import { db as dbCore, context } from "@budibase/backend-core"
import { Database, Row } from "@budibase/types"
import { getRowParams } from "../../../db/utils"
import { isExternalTableID } from "../../../integrations/utils"
import * as internal from "./internal"
import * as external from "./external"
export async function getAllInternalRows(appId?: string) {
let db: Database
@ -16,3 +19,18 @@ export async function getAllInternalRows(appId?: string) {
)
return response.rows.map(row => row.doc) as Row[]
}
function pickApi(tableId: any) {
if (isExternalTableID(tableId)) {
return external
}
return internal
}
export async function save(
tableId: string,
row: Row,
userId: string | undefined
) {
return pickApi(tableId).save(tableId, row, userId)
}

View File

@ -0,0 +1,220 @@
import tk from "timekeeper"
import * as internalSdk from "../internal"
import { generator } from "@budibase/backend-core/tests"
import {
INTERNAL_TABLE_SOURCE_ID,
TableSourceType,
FieldType,
Table,
AutoFieldSubTypes,
} from "@budibase/types"
import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
import { cache } from "@budibase/backend-core"
tk.freeze(Date.now())
describe("sdk >> rows >> internal", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.init()
})
function makeRow() {
return {
name: generator.first(),
surname: generator.last(),
age: generator.age(),
address: generator.address(),
}
}
describe("save", () => {
const tableData: Table = {
name: generator.word(),
type: "table",
sourceId: INTERNAL_TABLE_SOURCE_ID,
sourceType: TableSourceType.INTERNAL,
schema: {
name: {
name: "name",
type: FieldType.STRING,
constraints: {
type: FieldType.STRING,
},
},
surname: {
name: "surname",
type: FieldType.STRING,
constraints: {
type: FieldType.STRING,
},
},
age: {
name: "age",
type: FieldType.NUMBER,
constraints: {
type: FieldType.NUMBER,
},
},
address: {
name: "address",
type: FieldType.STRING,
constraints: {
type: FieldType.STRING,
},
},
},
}
beforeEach(() => {
jest.clearAllMocks()
})
it("save will persist the row properly", async () => {
const table = await config.createTable(tableData)
const row = makeRow()
await config.doInContext(config.appId, async () => {
const response = await internalSdk.save(
table._id!,
row,
config.user._id
)
expect(response).toEqual({
table,
row: {
...row,
type: "row",
_rev: expect.stringMatching("1-.*"),
},
squashed: {
...row,
type: "row",
_rev: expect.stringMatching("1-.*"),
},
})
const persistedRow = await config.getRow(table._id!, response.row._id!)
expect(persistedRow).toEqual({
...row,
type: "row",
_rev: expect.stringMatching("1-.*"),
createdAt: expect.any(String),
updatedAt: expect.any(String),
})
})
})
it("auto ids will update when creating new rows", async () => {
const table = await config.createTable({
...tableData,
schema: {
...tableData.schema,
id: {
name: "id",
type: FieldType.AUTO,
subtype: AutoFieldSubTypes.AUTO_ID,
autocolumn: true,
lastID: 0,
},
},
})
const row = makeRow()
await config.doInContext(config.appId, async () => {
const response = await internalSdk.save(
table._id!,
row,
config.user._id
)
expect(response).toEqual({
table: {
...table,
schema: {
...table.schema,
id: {
...table.schema.id,
lastID: 1,
},
},
},
row: {
...row,
id: 1,
type: "row",
_rev: expect.stringMatching("1-.*"),
},
squashed: {
...row,
id: 1,
type: "row",
_rev: expect.stringMatching("1-.*"),
},
})
const persistedRow = await config.getRow(table._id!, response.row._id!)
expect(persistedRow).toEqual({
...row,
type: "row",
id: 1,
_rev: expect.stringMatching("1-.*"),
createdAt: expect.any(String),
updatedAt: expect.any(String),
})
})
})
it("auto ids will update when creating new rows in parallel", async () => {
function makeRows(count: number) {
return Array.from({ length: count }, () => makeRow())
}
const table = await config.createTable({
...tableData,
schema: {
...tableData.schema,
id: {
name: "id",
type: FieldType.AUTO,
subtype: AutoFieldSubTypes.AUTO_ID,
autocolumn: true,
lastID: 0,
},
},
})
await config.doInContext(config.appId, async () => {
for (const row of makeRows(5)) {
await internalSdk.save(table._id!, row, config.user._id)
}
await Promise.all(
makeRows(10).map(row =>
internalSdk.save(table._id!, row, config.user._id)
)
)
for (const row of makeRows(5)) {
await internalSdk.save(table._id!, row, config.user._id)
}
})
const persistedRows = await config.getRows(table._id!)
expect(persistedRows).toHaveLength(20)
expect(persistedRows).toEqual(
expect.arrayContaining(
Array.from({ length: 20 }).map((_, i) =>
expect.objectContaining({ id: i + 1 })
)
)
)
const persistedTable = await config.getTable(table._id)
expect((table as any).schema.id.lastID).toBe(0)
expect(persistedTable.schema.id.lastID).toBe(20)
})
})
})

View File

@ -34,6 +34,7 @@ import { cloneDeep } from "lodash/fp"
import { performance } from "perf_hooks"
import * as sdkUtils from "../sdk/utils"
import env from "../environment"
import tracer from "dd-trace"
threadUtils.threadSetup()
const FILTER_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.FILTER.stepId
@ -242,278 +243,347 @@ class Orchestrator {
}
async execute(): Promise<any> {
// this will retrieve from context created at start of thread
this._context.env = await sdkUtils.getEnvironmentVariables()
let automation = this._automation
let stopped = false
let loopStep: AutomationStep | undefined = undefined
return tracer.trace(
"Orchestrator.execute",
{ resource: "automation" },
async span => {
span?.addTags({
appId: this._appId,
automationId: this._automation._id,
})
let stepCount = 0
let loopStepNumber: any = undefined
let loopSteps: LoopStep[] | undefined = []
let metadata
let timeoutFlag = false
let wasLoopStep = false
let timeout = this._job.data.event.timeout
// check if this is a recurring automation,
if (isProdAppID(this._appId) && isRecurring(automation)) {
metadata = await this.getMetadata()
const shouldStop = await this.checkIfShouldStop(metadata)
if (shouldStop) {
return
}
}
const start = performance.now()
for (let step of automation.definition.steps) {
if (timeoutFlag) {
break
}
// this will retrieve from context created at start of thread
this._context.env = await sdkUtils.getEnvironmentVariables()
let automation = this._automation
let stopped = false
let loopStep: AutomationStep | undefined = undefined
if (timeout) {
setTimeout(() => {
timeoutFlag = true
}, timeout || 12000)
}
stepCount++
let input: any,
iterations = 1,
iterationCount = 0
if (step.stepId === LOOP_STEP_ID) {
loopStep = step
loopStepNumber = stepCount
continue
}
if (loopStep) {
input = await processObject(loopStep.inputs, this._context)
iterations = getLoopIterations(loopStep as LoopStep)
}
for (let index = 0; index < iterations; index++) {
let originalStepInput = cloneDeep(step.inputs)
// Handle if the user has set a max iteration count or if it reaches the max limit set by us
if (loopStep && input.binding) {
let tempOutput = { items: loopSteps, iterations: iterationCount }
try {
loopStep.inputs.binding = automationUtils.typecastForLooping(
loopStep as LoopStep,
loopStep.inputs as LoopInput
)
} catch (err) {
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
status: AutomationErrors.INCORRECT_TYPE,
success: false,
})
loopSteps = undefined
loopStep = undefined
break
}
let item = []
if (
typeof loopStep.inputs.binding === "string" &&
loopStep.inputs.option === "String"
) {
item = automationUtils.stringSplit(loopStep.inputs.binding)
} else if (Array.isArray(loopStep.inputs.binding)) {
item = loopStep.inputs.binding
}
this._context.steps[loopStepNumber] = {
currentItem: item[index],
}
// The "Loop" binding in the front end is "fake", so replace it here so the context can understand it
// Pretty hacky because we need to account for the row object
for (let [key, value] of Object.entries(originalStepInput)) {
if (typeof value === "object") {
for (let [innerKey, innerValue] of Object.entries(
originalStepInput[key]
)) {
if (typeof innerValue === "string") {
originalStepInput[key][innerKey] =
automationUtils.substituteLoopStep(
innerValue,
`steps.${loopStepNumber}`
)
} else if (typeof value === "object") {
for (let [innerObject, innerValue] of Object.entries(
originalStepInput[key][innerKey]
)) {
originalStepInput[key][innerKey][innerObject] =
automationUtils.substituteLoopStep(
innerValue as string,
`steps.${loopStepNumber}`
)
}
}
}
} else {
if (typeof value === "string") {
originalStepInput[key] = automationUtils.substituteLoopStep(
value,
`steps.${loopStepNumber}`
)
}
}
}
if (
index === env.AUTOMATION_MAX_ITERATIONS ||
index === parseInt(loopStep.inputs.iterations)
) {
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
status: AutomationErrors.MAX_ITERATIONS,
success: true,
})
loopSteps = undefined
loopStep = undefined
break
}
let isFailure = false
const currentItem = this._context.steps[loopStepNumber]?.currentItem
if (currentItem && typeof currentItem === "object") {
isFailure = Object.keys(currentItem).some(value => {
return currentItem[value] === loopStep?.inputs.failure
})
} else {
isFailure = currentItem && currentItem === loopStep.inputs.failure
}
if (isFailure) {
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
status: AutomationErrors.FAILURE_CONDITION,
success: false,
})
loopSteps = undefined
loopStep = undefined
break
let stepCount = 0
let loopStepNumber: any = undefined
let loopSteps: LoopStep[] | undefined = []
let metadata
let timeoutFlag = false
let wasLoopStep = false
let timeout = this._job.data.event.timeout
// check if this is a recurring automation,
if (isProdAppID(this._appId) && isRecurring(automation)) {
span?.addTags({ recurring: true })
metadata = await this.getMetadata()
const shouldStop = await this.checkIfShouldStop(metadata)
if (shouldStop) {
span?.addTags({ shouldStop: true })
return
}
}
// execution stopped, record state for that
if (stopped) {
this.updateExecutionOutput(step.id, step.stepId, {}, STOPPED_STATUS)
continue
}
// If it's a loop step, we need to manually add the bindings to the context
let stepFn = await this.getStepFunctionality(step.stepId)
let inputs = await processObject(originalStepInput, this._context)
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
try {
// appId is always passed
const outputs = await stepFn({
inputs: inputs,
appId: this._appId,
emitter: this._emitter,
context: this._context,
const start = performance.now()
for (let step of automation.definition.steps) {
const stepSpan = tracer.startSpan("Orchestrator.execute.step", {
childOf: span,
})
stepSpan.addTags({
resource: "automation",
step: {
stepId: step.stepId,
id: step.id,
name: step.name,
type: step.type,
title: step.stepTitle,
internal: step.internal,
deprecated: step.deprecated,
},
})
this._context.steps[stepCount] = outputs
// if filter causes us to stop execution don't break the loop, set a var
// so that we can finish iterating through the steps and record that it stopped
if (step.stepId === FILTER_STEP_ID && !outputs.result) {
stopped = true
this.updateExecutionOutput(step.id, step.stepId, step.inputs, {
...outputs,
...STOPPED_STATUS,
})
continue
}
if (loopStep && loopSteps) {
loopSteps.push(outputs)
} else {
this.updateExecutionOutput(
step.id,
step.stepId,
step.inputs,
outputs
)
}
} catch (err) {
console.error(`Automation error - ${step.stepId} - ${err}`)
return err
}
let input: any,
iterations = 1,
iterationCount = 0
if (loopStep) {
iterationCount++
if (index === iterations - 1) {
try {
if (timeoutFlag) {
span?.addTags({ timedOut: true })
break
}
if (timeout) {
setTimeout(() => {
timeoutFlag = true
}, timeout || 12000)
}
stepCount++
if (step.stepId === LOOP_STEP_ID) {
loopStep = step
loopStepNumber = stepCount
continue
}
if (loopStep) {
input = await processObject(loopStep.inputs, this._context)
iterations = getLoopIterations(loopStep as LoopStep)
stepSpan?.addTags({ step: { iterations } })
}
for (let index = 0; index < iterations; index++) {
let originalStepInput = cloneDeep(step.inputs)
// Handle if the user has set a max iteration count or if it reaches the max limit set by us
if (loopStep && input.binding) {
let tempOutput = {
items: loopSteps,
iterations: iterationCount,
}
try {
loopStep.inputs.binding = automationUtils.typecastForLooping(
loopStep as LoopStep,
loopStep.inputs as LoopInput
)
} catch (err) {
this.updateContextAndOutput(
loopStepNumber,
step,
tempOutput,
{
status: AutomationErrors.INCORRECT_TYPE,
success: false,
}
)
loopSteps = undefined
loopStep = undefined
break
}
let item = []
if (
typeof loopStep.inputs.binding === "string" &&
loopStep.inputs.option === "String"
) {
item = automationUtils.stringSplit(loopStep.inputs.binding)
} else if (Array.isArray(loopStep.inputs.binding)) {
item = loopStep.inputs.binding
}
this._context.steps[loopStepNumber] = {
currentItem: item[index],
}
// The "Loop" binding in the front end is "fake", so replace it here so the context can understand it
// Pretty hacky because we need to account for the row object
for (let [key, value] of Object.entries(originalStepInput)) {
if (typeof value === "object") {
for (let [innerKey, innerValue] of Object.entries(
originalStepInput[key]
)) {
if (typeof innerValue === "string") {
originalStepInput[key][innerKey] =
automationUtils.substituteLoopStep(
innerValue,
`steps.${loopStepNumber}`
)
} else if (typeof value === "object") {
for (let [innerObject, innerValue] of Object.entries(
originalStepInput[key][innerKey]
)) {
originalStepInput[key][innerKey][innerObject] =
automationUtils.substituteLoopStep(
innerValue as string,
`steps.${loopStepNumber}`
)
}
}
}
} else {
if (typeof value === "string") {
originalStepInput[key] =
automationUtils.substituteLoopStep(
value,
`steps.${loopStepNumber}`
)
}
}
}
if (
index === env.AUTOMATION_MAX_ITERATIONS ||
index === parseInt(loopStep.inputs.iterations)
) {
this.updateContextAndOutput(
loopStepNumber,
step,
tempOutput,
{
status: AutomationErrors.MAX_ITERATIONS,
success: true,
}
)
loopSteps = undefined
loopStep = undefined
break
}
let isFailure = false
const currentItem =
this._context.steps[loopStepNumber]?.currentItem
if (currentItem && typeof currentItem === "object") {
isFailure = Object.keys(currentItem).some(value => {
return currentItem[value] === loopStep?.inputs.failure
})
} else {
isFailure =
currentItem && currentItem === loopStep.inputs.failure
}
if (isFailure) {
this.updateContextAndOutput(
loopStepNumber,
step,
tempOutput,
{
status: AutomationErrors.FAILURE_CONDITION,
success: false,
}
)
loopSteps = undefined
loopStep = undefined
break
}
}
// execution stopped, record state for that
if (stopped) {
this.updateExecutionOutput(
step.id,
step.stepId,
{},
STOPPED_STATUS
)
continue
}
// If it's a loop step, we need to manually add the bindings to the context
let stepFn = await this.getStepFunctionality(step.stepId)
let inputs = await processObject(originalStepInput, this._context)
inputs = automationUtils.cleanInputValues(
inputs,
step.schema.inputs
)
try {
// appId is always passed
const outputs = await stepFn({
inputs: inputs,
appId: this._appId,
emitter: this._emitter,
context: this._context,
})
this._context.steps[stepCount] = outputs
// if filter causes us to stop execution don't break the loop, set a var
// so that we can finish iterating through the steps and record that it stopped
if (step.stepId === FILTER_STEP_ID && !outputs.result) {
stopped = true
this.updateExecutionOutput(
step.id,
step.stepId,
step.inputs,
{
...outputs,
...STOPPED_STATUS,
}
)
continue
}
if (loopStep && loopSteps) {
loopSteps.push(outputs)
} else {
this.updateExecutionOutput(
step.id,
step.stepId,
step.inputs,
outputs
)
}
} catch (err) {
console.error(`Automation error - ${step.stepId} - ${err}`)
return err
}
if (loopStep) {
iterationCount++
if (index === iterations - 1) {
loopStep = undefined
this._context.steps.splice(loopStepNumber, 1)
break
}
}
}
} finally {
stepSpan?.finish()
}
if (loopStep && iterations === 0) {
loopStep = undefined
this.executionOutput.steps.splice(loopStepNumber + 1, 0, {
id: step.id,
stepId: step.stepId,
outputs: {
status: AutomationStepStatus.NO_ITERATIONS,
success: true,
},
inputs: {},
})
this._context.steps.splice(loopStepNumber, 1)
break
iterations = 1
}
// Delete the step after the loop step as it's irrelevant, since information is included
// in the loop step
if (wasLoopStep && !loopStep) {
this._context.steps.splice(loopStepNumber + 1, 1)
wasLoopStep = false
}
if (loopSteps && loopSteps.length) {
let tempOutput = {
success: true,
items: loopSteps,
iterations: iterationCount,
}
this.executionOutput.steps.splice(loopStepNumber + 1, 0, {
id: step.id,
stepId: step.stepId,
outputs: tempOutput,
inputs: step.inputs,
})
this._context.steps[loopStepNumber] = tempOutput
wasLoopStep = true
loopSteps = []
}
}
}
if (loopStep && iterations === 0) {
loopStep = undefined
this.executionOutput.steps.splice(loopStepNumber + 1, 0, {
id: step.id,
stepId: step.stepId,
outputs: {
status: AutomationStepStatus.NO_ITERATIONS,
success: true,
},
inputs: {},
})
const end = performance.now()
const executionTime = end - start
this._context.steps.splice(loopStepNumber, 1)
iterations = 1
}
console.info(
`Automation ID: ${automation._id} Execution time: ${executionTime} milliseconds`,
{
_logKey: "automation",
executionTime,
}
)
// Delete the step after the loop step as it's irrelevant, since information is included
// in the loop step
if (wasLoopStep && !loopStep) {
this._context.steps.splice(loopStepNumber + 1, 1)
wasLoopStep = false
}
if (loopSteps && loopSteps.length) {
let tempOutput = {
success: true,
items: loopSteps,
iterations: iterationCount,
// store the logs for the automation run
try {
await storeLog(this._automation, this.executionOutput)
} catch (e: any) {
if (e.status === 413 && e.request?.data) {
// if content is too large we shouldn't log it
delete e.request.data
e.request.data = { message: "removed due to large size" }
}
logging.logAlert("Error writing automation log", e)
}
this.executionOutput.steps.splice(loopStepNumber + 1, 0, {
id: step.id,
stepId: step.stepId,
outputs: tempOutput,
inputs: step.inputs,
})
this._context.steps[loopStepNumber] = tempOutput
wasLoopStep = true
loopSteps = []
}
}
const end = performance.now()
const executionTime = end - start
console.info(
`Automation ID: ${automation._id} Execution time: ${executionTime} milliseconds`,
{
_logKey: "automation",
executionTime,
if (isProdAppID(this._appId) && isRecurring(automation) && metadata) {
await this.updateMetadata(metadata)
}
return this.executionOutput
}
)
// store the logs for the automation run
try {
await storeLog(this._automation, this.executionOutput)
} catch (e: any) {
if (e.status === 413 && e.request?.data) {
// if content is too large we shouldn't log it
delete e.request.data
e.request.data = { message: "removed due to large size" }
}
logging.logAlert("Error writing automation log", e)
}
if (isProdAppID(this._appId) && isRecurring(automation) && metadata) {
await this.updateMetadata(metadata)
}
return this.executionOutput
}
}

View File

@ -21,6 +21,7 @@ export enum LockName {
PERSIST_WRITETHROUGH = "persist_writethrough",
QUOTA_USAGE_EVENT = "quota_usage_event",
APP_MIGRATION = "app_migrations",
PROCESS_AUTO_COLUMNS = "process_auto_columns",
}
export type LockOptions = {

View File

@ -48,7 +48,7 @@
"bcrypt": "5.1.0",
"bcryptjs": "2.4.3",
"bull": "4.10.1",
"dd-trace": "4.20.0",
"dd-trace": "3.13.2",
"dotenv": "8.6.0",
"global-agent": "3.0.0",
"ical-generator": "4.1.0",

View File

@ -1,6 +1,6 @@
import tk from "timekeeper"
import _ from "lodash"
import { mocks, structures } from "@budibase/backend-core/tests"
import { generator, mocks, structures } from "@budibase/backend-core/tests"
import {
ScimCreateUserRequest,
ScimGroupResponse,
@ -14,9 +14,14 @@ import { events } from "@budibase/backend-core"
jest.retryTimes(2, { logErrorsBeforeRetry: true })
jest.setTimeout(30000)
mocks.licenses.useScimIntegration()
describe("scim", () => {
beforeAll(async () => {
tk.freeze(mocks.date.MOCK_DATE)
mocks.licenses.useScimIntegration()
await config.setSCIMConfig(true)
})
beforeEach(async () => {
jest.resetAllMocks()
tk.freeze(mocks.date.MOCK_DATE)
@ -570,8 +575,15 @@ describe("scim", () => {
beforeAll(async () => {
groups = []
for (let i = 0; i < groupCount; i++) {
const body = structures.scim.createGroupRequest()
const groupNames = generator.unique(
() => generator.word(),
groupCount
)
for (const groupName of groupNames) {
const body = structures.scim.createGroupRequest({
displayName: groupName,
})
groups.push(await config.api.scimGroupsAPI.post({ body }))
}

View File

@ -79,6 +79,9 @@ export const resetUpdate = async (resetCode: string, password: string) => {
user.password = password
user = await userSdk.db.save(user)
await cache.passwordReset.invalidateCode(resetCode)
await sessions.invalidateSessions(userId)
// remove password from the user before sending events
delete user.password
await events.user.passwordReset(user)

View File

@ -0,0 +1,70 @@
import { cache, context, sessions, utils } from "@budibase/backend-core"
import { loginUser, resetUpdate } from "../auth"
import { generator, structures } from "@budibase/backend-core/tests"
import { TestConfiguration } from "../../../tests"
describe("auth", () => {
const config = new TestConfiguration()
describe("resetUpdate", () => {
it("providing a valid code will update the password", async () => {
await context.doInTenant(structures.tenant.id(), async () => {
const user = await config.createUser()
const previousPassword = user.password
const code = await cache.passwordReset.createCode(user._id!, {})
const newPassword = generator.hash()
await resetUpdate(code, newPassword)
const persistedUser = await config.getUser(user.email)
expect(persistedUser.password).not.toBe(previousPassword)
expect(
await utils.compare(newPassword, persistedUser.password!)
).toBeTruthy()
})
})
it("wrong code will not allow to reset the password", async () => {
await context.doInTenant(structures.tenant.id(), async () => {
const code = generator.hash()
const newPassword = generator.hash()
await expect(resetUpdate(code, newPassword)).rejects.toThrow(
"Provided information is not valid, cannot reset password - please try again."
)
})
})
it("the same code cannot be used twice", async () => {
await context.doInTenant(structures.tenant.id(), async () => {
const user = await config.createUser()
const code = await cache.passwordReset.createCode(user._id!, {})
const newPassword = generator.hash()
await resetUpdate(code, newPassword)
await expect(resetUpdate(code, newPassword)).rejects.toThrow(
"Provided information is not valid, cannot reset password - please try again."
)
})
})
it("updating the password will invalidate all the sessions", async () => {
await context.doInTenant(structures.tenant.id(), async () => {
const user = await config.createUser()
await loginUser(user)
expect(await sessions.getSessionsForUser(user._id!)).toHaveLength(1)
const code = await cache.passwordReset.createCode(user._id!, {})
const newPassword = generator.hash()
await resetUpdate(code, newPassword)
expect(await sessions.getSessionsForUser(user._id!)).toHaveLength(0)
})
})
})
})

View File

@ -1,6 +1,5 @@
import { structures, mocks } from "../../../tests"
import { env, context } from "@budibase/backend-core"
import * as users from "../users"
import { db as userDb } from "../"
import { CloudAccount } from "@budibase/types"

File diff suppressed because it is too large Load Diff

290
yarn.lock
View File

@ -829,12 +829,13 @@
events "^3.0.0"
tslib "^2.2.0"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.22.5", "@babel/code-frame@^7.23.5":
version "7.23.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244"
integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==
dependencies:
"@babel/highlight" "^7.22.5"
"@babel/highlight" "^7.23.4"
chalk "^2.4.2"
"@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.5":
version "7.22.5"
@ -880,12 +881,12 @@
"@jridgewell/gen-mapping" "^0.3.0"
jsesc "^2.5.1"
"@babel/generator@^7.22.5", "@babel/generator@^7.7.2":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7"
integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==
"@babel/generator@^7.22.5", "@babel/generator@^7.23.6", "@babel/generator@^7.7.2":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e"
integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/types" "^7.23.6"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
@ -963,18 +964,18 @@
resolve "^1.14.2"
semver "^6.1.2"
"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-function-name@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be"
integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==
"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
"@babel/template" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/template" "^7.22.15"
"@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
@ -1059,22 +1060,22 @@
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-split-export-declaration@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08"
integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==
"@babel/helper-split-export-declaration@^7.22.5", "@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-string-parser@^7.23.4":
version "7.23.4"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83"
integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==
"@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
"@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.22.5":
version "7.22.5"
@ -1100,13 +1101,13 @@
"@babel/traverse" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/highlight@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031"
integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==
"@babel/highlight@^7.23.4":
version "7.23.4"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==
dependencies:
"@babel/helper-validator-identifier" "^7.22.5"
chalk "^2.0.0"
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@7.18.4":
@ -1114,10 +1115,10 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef"
integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.5", "@babel/parser@^7.23.6":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b"
integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5":
version "7.22.5"
@ -1999,43 +2000,36 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885"
integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.13.10":
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d"
integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.5", "@babel/template@^7.3.3":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==
"@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies:
"@babel/code-frame" "^7.22.5"
"@babel/parser" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/code-frame" "^7.22.13"
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/traverse@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1"
integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5"
integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==
dependencies:
"@babel/code-frame" "^7.22.5"
"@babel/generator" "^7.22.5"
"@babel/helper-environment-visitor" "^7.22.5"
"@babel/helper-function-name" "^7.22.5"
"@babel/code-frame" "^7.23.5"
"@babel/generator" "^7.23.6"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.5"
"@babel/parser" "^7.22.5"
"@babel/types" "^7.22.5"
debug "^4.1.0"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.6"
"@babel/types" "^7.23.6"
debug "^4.3.1"
globals "^11.1.0"
"@babel/types@7.18.4":
@ -2046,13 +2040,13 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.0.0", "@babel/types@^7.16.8", "@babel/types@^7.18.2", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe"
integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==
"@babel/types@^7.0.0", "@babel/types@^7.16.8", "@babel/types@^7.18.2", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd"
integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.5"
"@babel/helper-string-parser" "^7.23.4"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@balena/dockerignore@^1.0.2":
@ -2265,6 +2259,13 @@
enabled "2.0.x"
kuler "^2.0.0"
"@datadog/native-appsec@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-2.0.0.tgz#ad65ba19bfd68e6b6c6cf64bb8ef55d099af8edc"
integrity sha512-XHARZ6MVgbnfOUO6/F3ZoZ7poXHJCNYFlgcyS2Xetuk9ITA5bfcooX2B2F7tReVB+RLJ+j8bsm0t55SyF04KDw==
dependencies:
node-gyp-build "^3.9.0"
"@datadog/native-appsec@4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-4.0.0.tgz#ee08138b987dec557eac3650a43a972dac85b6a6"
@ -2272,6 +2273,13 @@
dependencies:
node-gyp-build "^3.9.0"
"@datadog/native-iast-rewriter@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@datadog/native-iast-rewriter/-/native-iast-rewriter-1.1.2.tgz#793cbf92d218ec80d645be0830023656b81018ea"
integrity sha512-pigRfRtAjZjMjqIXyXb98S4aDnuHz/EmqpoxAajFZsNjBLM87YonwSY5zoBdCsOyA46ddKOJRoCQd5ZalpOFMQ==
dependencies:
node-gyp-build "^4.5.0"
"@datadog/native-iast-rewriter@2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@datadog/native-iast-rewriter/-/native-iast-rewriter-2.2.1.tgz#3c74c5a8caa0b876e091e9c5a95256add0d73e1c"
@ -2280,6 +2288,13 @@
lru-cache "^7.14.0"
node-gyp-build "^4.5.0"
"@datadog/native-iast-taint-tracking@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-1.1.0.tgz#8f7d0016157b32dbf5c01b15b8afb1c4286b4a18"
integrity sha512-TOrngpt6Qh52zWFOz1CkFXw0g43rnuUziFBtIMUsOLGzSHr9wdnTnE6HAyuvKy3f3ecAoZESlMfilGRKP93hXQ==
dependencies:
node-gyp-build "^3.9.0"
"@datadog/native-iast-taint-tracking@1.6.4":
version "1.6.4"
resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-1.6.4.tgz#16c21ad7c36a53420c0d3c5a3720731809cc7e98"
@ -2287,6 +2302,13 @@
dependencies:
node-gyp-build "^3.9.0"
"@datadog/native-metrics@^1.5.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@datadog/native-metrics/-/native-metrics-1.6.0.tgz#1c7958964460149911f6964c32b1a8692ee3ce8f"
integrity sha512-+8jBzd0nlLV+ay3Vb87DLwz8JHAS817hRhSRQ6zxhud9TyvvcNTNN+VA2sb2fe5UK4aMDvj/sGVJjEtgr4RHew==
dependencies:
node-gyp-build "^3.9.0"
"@datadog/native-metrics@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@datadog/native-metrics/-/native-metrics-2.0.0.tgz#65bf03313ee419956361e097551db36173e85712"
@ -2306,6 +2328,20 @@
pprof-format "^2.0.7"
source-map "^0.7.4"
"@datadog/pprof@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-1.1.1.tgz#17e86035140523ac3a96f3662e5dd29822042d61"
integrity sha512-5lYXUpikQhrJwzODtJ7aFM0oKmPccISnTCecuWhjxIj4/7UJv0DamkLak634bgEW+kiChgkKFDapHSesuXRDXQ==
dependencies:
delay "^5.0.0"
findit2 "^2.2.3"
node-gyp-build "^3.9.0"
p-limit "^3.1.0"
pify "^5.0.0"
protobufjs "^7.0.0"
source-map "^0.7.3"
split "^1.0.1"
"@datadog/sketches-js@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@datadog/sketches-js/-/sketches-js-2.1.0.tgz#8c7e8028a5fc22ad102fa542b0a446c956830455"
@ -3795,16 +3831,11 @@
dependencies:
"@octokit/openapi-types" "^18.0.0"
"@opentelemetry/api@^1.0.0":
"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.0.1":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.7.0.tgz#b139c81999c23e3c8d3c0a7234480e945920fc40"
integrity sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==
"@opentelemetry/api@^1.0.1":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.0.tgz#2c91791a9ba6ca0a0f4aaac5e45d58df13639ac8"
integrity sha512-IgMK9i3sFGNUqPMbjABm0G26g0QCKCUBfglhQ7rQq6WcxbKfEHRcmwsoER4hZcuYqJgkYn2OeuoJIv7Jsftp7g==
"@opentelemetry/core@^1.14.0":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.19.0.tgz#6563bb65465bf232d8435553b9a122d9351c0fbb"
@ -5678,10 +5709,10 @@
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.13.4", "@types/node@>=13.7.0":
version "20.8.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.9.tgz#646390b4fab269abce59c308fc286dcd818a2b08"
integrity sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.13.4", "@types/node@>=13.7.0", "@types/node@>=8.1.0":
version "20.10.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2"
integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==
dependencies:
undici-types "~5.26.4"
@ -5705,13 +5736,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.37.tgz#0bfcd173e8e1e328337473a8317e37b3b14fd30d"
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
"@types/node@>=8.1.0":
version "20.10.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2"
integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==
dependencies:
undici-types "~5.26.4"
"@types/nodemailer@^6.4.4":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.14.tgz#5c81a5e856db7f8ede80013e6dbad7c5fb2283e2"
@ -7176,9 +7200,9 @@ axios@^0.26.0:
follow-redirects "^1.14.8"
axios@^1.0.0, axios@^1.1.3, axios@^1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.1.tgz#11fbaa11fc35f431193a9564109c88c1f27b585f"
integrity sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==
version "1.6.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2"
integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
@ -8031,7 +8055,7 @@ chalk@4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^2.0.0, chalk@^2.4.2:
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -9094,6 +9118,39 @@ dc-polyfill@^0.1.2:
resolved "https://registry.yarnpkg.com/dc-polyfill/-/dc-polyfill-0.1.3.tgz#fe9eefc86813439dd46d6f9ad9582ec079c39720"
integrity sha512-Wyk5n/5KUj3GfVKV2jtDbtChC/Ff9fjKsBcg4ZtYW1yQe3DXNHcGURvmoxhqQdfOQ9TwyMjnfyv1lyYcOkFkFA==
dd-trace@3.13.2:
version "3.13.2"
resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-3.13.2.tgz#95b1ec480ab9ac406e1da7591a8c6f678d3799fd"
integrity sha512-POO9nEcAufe5pgp2xV1X3PfWip6wh+6TpEcRSlSgZJCIIMvWVCkcIVL/J2a6KAZq6V3Yjbkl8Ktfe+MOzQf5kw==
dependencies:
"@datadog/native-appsec" "2.0.0"
"@datadog/native-iast-rewriter" "1.1.2"
"@datadog/native-iast-taint-tracking" "1.1.0"
"@datadog/native-metrics" "^1.5.0"
"@datadog/pprof" "^1.1.1"
"@datadog/sketches-js" "^2.1.0"
crypto-randomuuid "^1.0.0"
diagnostics_channel "^1.1.0"
ignore "^5.2.0"
import-in-the-middle "^1.3.4"
ipaddr.js "^2.0.1"
istanbul-lib-coverage "3.2.0"
koalas "^1.0.2"
limiter "^1.1.4"
lodash.kebabcase "^4.1.1"
lodash.pick "^4.4.0"
lodash.sortby "^4.7.0"
lodash.uniq "^4.5.0"
lru-cache "^7.14.0"
methods "^1.1.2"
module-details-from-path "^1.0.3"
node-abort-controller "^3.0.1"
opentracing ">=0.12.1"
path-to-regexp "^0.1.2"
protobufjs "^7.1.2"
retry "^0.10.1"
semver "^5.5.0"
dd-trace@4.20.0:
version "4.20.0"
resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-4.20.0.tgz#9a2cc3f28ff558c5605927b1362eb64605df76c1"
@ -9655,6 +9712,11 @@ dezalgo@^1.0.4:
asap "^2.0.0"
wrappy "1"
diagnostics_channel@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/diagnostics_channel/-/diagnostics_channel-1.1.0.tgz#bd66c49124ce3bac697dff57466464487f57cea5"
integrity sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw==
diff-match-patch@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
@ -11180,6 +11242,11 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
findit2@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6"
integrity sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==
flat-cache@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@ -12585,12 +12652,7 @@ ignore-walk@^6.0.0:
dependencies:
minimatch "^7.4.2"
ignore@^5.0.4, ignore@^5.2.0:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
ignore@^5.2.4:
ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4:
version "5.3.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
@ -12639,7 +12701,7 @@ import-from@^3.0.0:
dependencies:
resolve-from "^5.0.0"
import-in-the-middle@^1.4.2:
import-in-the-middle@^1.3.4, import-in-the-middle@^1.4.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.7.2.tgz#31c44088271b50ecb9cacbdfb1e5732c802e0658"
integrity sha512-coz7AjRnPyKW36J6JX5Bjz1mcX7MX1H2XsEGseVcnXMdzsAbbAu0HBZhiAem+3SAmuZdi+p8OwoB2qUpTRgjOQ==
@ -12861,7 +12923,7 @@ ip@^2.0.0:
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
ipaddr.js@^2.1.0:
ipaddr.js@^2.0.1, ipaddr.js@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f"
integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==
@ -18203,20 +18265,13 @@ postgres-interval@^1.1.0:
dependencies:
xtend "^4.0.0"
posthog-js@^1.13.4:
posthog-js@^1.13.4, posthog-js@^1.36.0:
version "1.96.1"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.96.1.tgz#4f9719a24e4e14037b0e72d430194d7cdb576447"
integrity sha512-kv1vQqYMt2BV3YHS+wxsbGuP+tz+M3y1AzNhz8TfkpY1HT8W/ONT0i0eQpeRr9Y+d4x/fZ6M4cXG5GMvi9lRCA==
dependencies:
fflate "^0.4.1"
posthog-js@^1.36.0:
version "1.87.2"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.87.2.tgz#0e9395ea93d50bb624f0be19f254e62e54ae504d"
integrity sha512-pdxEylfxwEDwwz7g5dunPucvAN51RAOWWQmkcqHsLNHlV5o5bTaTwcAXaWB1IUn3xKPuKYE2lqbdB3vC4H4rFQ==
dependencies:
fflate "^0.4.1"
posthog-node@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/posthog-node/-/posthog-node-1.3.0.tgz#804ed2f213a2f05253f798bf9569d55a9cad94f7"
@ -18726,7 +18781,7 @@ protobufjs@7.2.4:
"@types/node" ">=13.7.0"
long "^5.0.0"
protobufjs@^7.0.0, protobufjs@^7.2.4, protobufjs@^7.2.5:
protobufjs@^7.0.0, protobufjs@^7.1.2, protobufjs@^7.2.4, protobufjs@^7.2.5:
version "7.2.5"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d"
integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==
@ -19500,7 +19555,7 @@ retry@0.13.1, retry@^0.13.1:
resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
integrity "sha1-GFsVh6z2eRnWOzVzSeA1N7JIRlg= sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="
retry@^0.10.0:
retry@^0.10.0, retry@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4"
integrity sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==
@ -20398,7 +20453,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.4:
source-map@^0.7.3, source-map@^0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
@ -22236,12 +22291,7 @@ uuid@^7.0.3:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
uuid@^9.0.1:
uuid@^9.0.0, uuid@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==