Merge remote-tracking branch 'origin/develop' into feature/binding-v2-updates

This commit is contained in:
Dean 2023-05-19 09:11:35 +01:00
commit 8ad4ed7d34
71 changed files with 1806 additions and 308 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "2.6.16-alpha.2", "version": "2.6.19-alpha.0",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/backend-core", "packages/backend-core",

View File

@ -104,6 +104,22 @@ async function newContext(updates: ContextMap, task: any) {
return Context.run(context, task) return Context.run(context, task)
} }
export async function doInAutomationContext(params: {
appId: string
automationId: string
task: any
}): Promise<any> {
const tenantId = getTenantIDFromAppID(params.appId)
return newContext(
{
tenantId,
appId: params.appId,
automationId: params.automationId,
},
params.task
)
}
export async function doInContext(appId: string, task: any): Promise<any> { export async function doInContext(appId: string, task: any): Promise<any> {
const tenantId = getTenantIDFromAppID(appId) const tenantId = getTenantIDFromAppID(appId)
return newContext( return newContext(
@ -187,6 +203,11 @@ export function getTenantId(): string {
return tenantId return tenantId
} }
export function getAutomationId(): string | undefined {
const context = Context.get()
return context?.automationId
}
export function getAppId(): string | undefined { export function getAppId(): string | undefined {
const context = Context.get() const context = Context.get()
const foundId = context?.appId const foundId = context?.appId

View File

@ -7,4 +7,5 @@ export type ContextMap = {
identity?: IdentityContext identity?: IdentityContext
environmentVariables?: Record<string, string> environmentVariables?: Record<string, string>
isScim?: boolean isScim?: boolean
automationId?: string
} }

View File

@ -12,7 +12,7 @@ import {
isDocument, isDocument,
} from "@budibase/types" } from "@budibase/types"
import { getCouchInfo } from "./connections" import { getCouchInfo } from "./connections"
import { directCouchCall } from "./utils" import { directCouchUrlCall } from "./utils"
import { getPouchDB } from "./pouchDB" import { getPouchDB } from "./pouchDB"
import { WriteStream, ReadStream } from "fs" import { WriteStream, ReadStream } from "fs"
import { newid } from "../../docIds/newid" import { newid } from "../../docIds/newid"
@ -46,6 +46,8 @@ export class DatabaseImpl implements Database {
private readonly instanceNano?: Nano.ServerScope private readonly instanceNano?: Nano.ServerScope
private readonly pouchOpts: DatabaseOpts private readonly pouchOpts: DatabaseOpts
private readonly couchInfo = getCouchInfo()
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) { constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
if (dbName == null) { if (dbName == null) {
throw new Error("Database name cannot be undefined.") throw new Error("Database name cannot be undefined.")
@ -53,8 +55,8 @@ export class DatabaseImpl implements Database {
this.name = dbName this.name = dbName
this.pouchOpts = opts || {} this.pouchOpts = opts || {}
if (connection) { if (connection) {
const couchInfo = getCouchInfo(connection) this.couchInfo = getCouchInfo(connection)
this.instanceNano = buildNano(couchInfo) this.instanceNano = buildNano(this.couchInfo)
} }
if (!DatabaseImpl.nano) { if (!DatabaseImpl.nano) {
DatabaseImpl.init() DatabaseImpl.init()
@ -67,7 +69,11 @@ export class DatabaseImpl implements Database {
} }
async exists() { async exists() {
let response = await directCouchCall(`/${this.name}`, "HEAD") const response = await directCouchUrlCall({
url: `${this.couchInfo.url}/${this.name}`,
method: "HEAD",
cookie: this.couchInfo.cookie,
})
return response.status === 200 return response.status === 200
} }

View File

@ -4,21 +4,21 @@ export const getCouchInfo = (connection?: string) => {
const urlInfo = getUrlInfo(connection) const urlInfo = getUrlInfo(connection)
let username let username
let password let password
if (env.COUCH_DB_USERNAME) { if (urlInfo.auth?.username) {
// set from env
username = env.COUCH_DB_USERNAME
} else if (urlInfo.auth.username) {
// set from url // set from url
username = urlInfo.auth.username username = urlInfo.auth.username
} else if (env.COUCH_DB_USERNAME) {
// set from env
username = env.COUCH_DB_USERNAME
} else if (!env.isTest()) { } else if (!env.isTest()) {
throw new Error("CouchDB username not set") throw new Error("CouchDB username not set")
} }
if (env.COUCH_DB_PASSWORD) { if (urlInfo.auth?.password) {
// set from env
password = env.COUCH_DB_PASSWORD
} else if (urlInfo.auth.password) {
// set from url // set from url
password = urlInfo.auth.password password = urlInfo.auth.password
} else if (env.COUCH_DB_PASSWORD) {
// set from env
password = env.COUCH_DB_PASSWORD
} else if (!env.isTest()) { } else if (!env.isTest()) {
throw new Error("CouchDB password not set") throw new Error("CouchDB password not set")
} }

View File

@ -9,6 +9,20 @@ export async function directCouchCall(
) { ) {
let { url, cookie } = getCouchInfo() let { url, cookie } = getCouchInfo()
const couchUrl = `${url}/${path}` const couchUrl = `${url}/${path}`
return await directCouchUrlCall({ url: couchUrl, cookie, method, body })
}
export async function directCouchUrlCall({
url,
cookie,
method,
body,
}: {
url: string
cookie: string
method: string
body?: any
}) {
const params: any = { const params: any = {
method: method, method: method,
headers: { headers: {
@ -19,7 +33,7 @@ export async function directCouchCall(
params.body = JSON.stringify(body) params.body = JSON.stringify(body)
params.headers["Content-Type"] = "application/json" params.headers["Content-Type"] = "application/json"
} }
return await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params) return await fetch(checkSlashesInUrl(encodeURI(url)), params)
} }
export async function directCouchQuery( export async function directCouchQuery(

View File

@ -39,6 +39,7 @@ if (!env.DISABLE_PINO_LOGGER) {
objects?: any[] objects?: any[]
tenantId?: string tenantId?: string
appId?: string appId?: string
automationId?: string
identityId?: string identityId?: string
identityType?: IdentityType identityType?: IdentityType
correlationId?: string correlationId?: string
@ -86,18 +87,44 @@ if (!env.DISABLE_PINO_LOGGER) {
contextObject = { contextObject = {
tenantId: getTenantId(), tenantId: getTenantId(),
appId: getAppId(), appId: getAppId(),
automationId: getAutomationId(),
identityId: identity?._id, identityId: identity?._id,
identityType: identity?.type, identityType: identity?.type,
correlationId: correlation.getId(), correlationId: correlation.getId(),
} }
} }
const mergingObject = { const mergingObject: any = {
objects: objects.length ? objects : undefined,
err: error, err: error,
...contextObject, ...contextObject,
} }
if (objects.length) {
// init generic data object for params supplied that don't have a
// '_logKey' field. This prints an object using argument index as the key
// e.g. { 0: {}, 1: {} }
const data: any = {}
let dataIndex = 0
for (let i = 0; i < objects.length; i++) {
const object = objects[i]
// the object has specified a log key
// use this instead of generic key
const logKey = object._logKey
if (logKey) {
delete object._logKey
mergingObject[logKey] = object
} else {
data[dataIndex] = object
dataIndex++
}
}
if (Object.keys(data).length) {
mergingObject.data = data
}
}
return [mergingObject, message] return [mergingObject, message]
} }
@ -159,6 +186,16 @@ if (!env.DISABLE_PINO_LOGGER) {
return appId return appId
} }
const getAutomationId = () => {
let appId
try {
appId = context.getAutomationId()
} catch (e) {
// do nothing
}
return appId
}
const getIdentity = () => { const getIdentity = () => {
let identity let identity
try { try {

View File

@ -128,6 +128,7 @@ class InMemoryQueue {
on() { on() {
// do nothing // do nothing
return this
} }
async waitForCompletion() { async waitForCompletion() {

View File

@ -1,5 +1,6 @@
import { Job, JobId, Queue } from "bull" import { Job, JobId, Queue } from "bull"
import { JobQueue } from "./constants" import { JobQueue } from "./constants"
import * as context from "../context"
export type StalledFn = (job: Job) => Promise<void> export type StalledFn = (job: Job) => Promise<void>
@ -31,77 +32,164 @@ function handleStalled(queue: Queue, removeStalledCb?: StalledFn) {
}) })
} }
function logging(queue: Queue, jobQueue: JobQueue) { function getLogParams(
let eventType: string eventType: QueueEventType,
switch (jobQueue) { event: BullEvent,
case JobQueue.AUTOMATION: opts: {
eventType = "automation-event" job?: Job
break jobId?: JobId
case JobQueue.APP_BACKUP: error?: Error
eventType = "app-backup-event" } = {},
break extra: any = {}
case JobQueue.AUDIT_LOG: ) {
eventType = "audit-log-event" const message = `[BULL] ${eventType}=${event}`
break const err = opts.error
case JobQueue.SYSTEM_EVENT_QUEUE:
eventType = "system-event" const bullLog = {
break _logKey: "bull",
eventType,
event,
job: opts.job,
jobId: opts.jobId || opts.job?.id,
...extra,
} }
let automationLog
if (opts.job?.data?.automation) {
automationLog = {
_logKey: "automation",
trigger: opts.job
? opts.job.data.automation.definition.trigger.event
: undefined,
}
}
return [message, err, bullLog, automationLog]
}
enum BullEvent {
ERROR = "error",
WAITING = "waiting",
ACTIVE = "active",
STALLED = "stalled",
PROGRESS = "progress",
COMPLETED = "completed",
FAILED = "failed",
PAUSED = "paused",
RESUMED = "resumed",
CLEANED = "cleaned",
DRAINED = "drained",
REMOVED = "removed",
}
enum QueueEventType {
AUTOMATION_EVENT = "automation-event",
APP_BACKUP_EVENT = "app-backup-event",
AUDIT_LOG_EVENT = "audit-log-event",
SYSTEM_EVENT = "system-event",
}
const EventTypeMap: { [key in JobQueue]: QueueEventType } = {
[JobQueue.AUTOMATION]: QueueEventType.AUTOMATION_EVENT,
[JobQueue.APP_BACKUP]: QueueEventType.APP_BACKUP_EVENT,
[JobQueue.AUDIT_LOG]: QueueEventType.AUDIT_LOG_EVENT,
[JobQueue.SYSTEM_EVENT_QUEUE]: QueueEventType.SYSTEM_EVENT,
}
function logging(queue: Queue, jobQueue: JobQueue) {
const eventType = EventTypeMap[jobQueue]
function doInJobContext(job: Job, task: any) {
// if this is an automation job try to get the app id
const appId = job.data.event?.appId
if (appId) {
return context.doInContext(appId, task)
} else {
task()
}
}
queue
.on(BullEvent.STALLED, async (job: Job) => {
// A job has been marked as stalled. This is useful for debugging job
// workers that crash or pause the event loop.
await doInJobContext(job, () => {
console.error(...getLogParams(eventType, BullEvent.STALLED, { job }))
})
})
.on(BullEvent.ERROR, (error: any) => {
// An error occurred.
console.error(...getLogParams(eventType, BullEvent.ERROR, { error }))
})
if (process.env.NODE_DEBUG?.includes("bull")) { if (process.env.NODE_DEBUG?.includes("bull")) {
queue queue
.on("error", (error: any) => { .on(BullEvent.WAITING, (jobId: JobId) => {
// An error occurred.
console.error(`${eventType}=error error=${JSON.stringify(error)}`)
})
.on("waiting", (jobId: JobId) => {
// A Job is waiting to be processed as soon as a worker is idling. // A Job is waiting to be processed as soon as a worker is idling.
console.log(`${eventType}=waiting jobId=${jobId}`) console.info(...getLogParams(eventType, BullEvent.WAITING, { jobId }))
}) })
.on("active", (job: Job, jobPromise: any) => { .on(BullEvent.ACTIVE, async (job: Job, jobPromise: any) => {
// A job has started. You can use `jobPromise.cancel()`` to abort it. // A job has started. You can use `jobPromise.cancel()`` to abort it.
console.log(`${eventType}=active jobId=${job.id}`) await doInJobContext(job, () => {
console.info(...getLogParams(eventType, BullEvent.ACTIVE, { job }))
})
}) })
.on("stalled", (job: Job) => { .on(BullEvent.PROGRESS, async (job: Job, progress: any) => {
// A job has been marked as stalled. This is useful for debugging job // A job's progress was updated
// workers that crash or pause the event loop. await doInJobContext(job, () => {
console.error( console.info(
`${eventType}=stalled jobId=${job.id} job=${JSON.stringify(job)}` ...getLogParams(
) eventType,
BullEvent.PROGRESS,
{ job },
{ progress }
)
)
})
}) })
.on("progress", (job: Job, progress: any) => { .on(BullEvent.COMPLETED, async (job: Job, result) => {
// A job's progress was updated!
console.log(
`${eventType}=progress jobId=${job.id} progress=${progress}`
)
})
.on("completed", (job: Job, result) => {
// A job successfully completed with a `result`. // A job successfully completed with a `result`.
console.log(`${eventType}=completed jobId=${job.id} result=${result}`) await doInJobContext(job, () => {
console.info(
...getLogParams(eventType, BullEvent.COMPLETED, { job }, { result })
)
})
}) })
.on("failed", (job, err: any) => { .on(BullEvent.FAILED, async (job: Job, error: any) => {
// A job failed with reason `err`! // A job failed with reason `err`!
console.log(`${eventType}=failed jobId=${job.id} error=${err}`) await doInJobContext(job, () => {
console.error(
...getLogParams(eventType, BullEvent.FAILED, { job, error })
)
})
}) })
.on("paused", () => { .on(BullEvent.PAUSED, () => {
// The queue has been paused. // The queue has been paused.
console.log(`${eventType}=paused`) console.info(...getLogParams(eventType, BullEvent.PAUSED))
}) })
.on("resumed", (job: Job) => { .on(BullEvent.RESUMED, () => {
// The queue has been resumed. // The queue has been resumed.
console.log(`${eventType}=paused jobId=${job.id}`) console.info(...getLogParams(eventType, BullEvent.RESUMED))
}) })
.on("cleaned", (jobs: Job[], type: string) => { .on(BullEvent.CLEANED, (jobs: Job[], type: string) => {
// Old jobs have been cleaned from the queue. `jobs` is an array of cleaned // Old jobs have been cleaned from the queue. `jobs` is an array of cleaned
// jobs, and `type` is the type of jobs cleaned. // jobs, and `type` is the type of jobs cleaned.
console.log(`${eventType}=cleaned length=${jobs.length} type=${type}`) console.info(
...getLogParams(
eventType,
BullEvent.CLEANED,
{},
{ length: jobs.length, type }
)
)
}) })
.on("drained", () => { .on(BullEvent.DRAINED, () => {
// Emitted every time the queue has processed all the waiting jobs (even if there can be some delayed jobs not yet processed) // Emitted every time the queue has processed all the waiting jobs (even if there can be some delayed jobs not yet processed)
console.log(`${eventType}=drained`) console.info(...getLogParams(eventType, BullEvent.DRAINED))
}) })
.on("removed", (job: Job) => { .on(BullEvent.REMOVED, (job: Job) => {
// A job successfully removed. // A job successfully removed.
console.log(`${eventType}=removed jobId=${job.id}`) console.info(...getLogParams(eventType, BullEvent.REMOVED, { job }))
}) })
} }
} }

View File

@ -84,7 +84,7 @@
"@spectrum-css/vars": "3.0.1", "@spectrum-css/vars": "3.0.1",
"dayjs": "^1.10.4", "dayjs": "^1.10.4",
"easymde": "^2.16.1", "easymde": "^2.16.1",
"svelte-flatpickr": "^3.3.2", "svelte-flatpickr": "3.2.3",
"svelte-portal": "^1.0.0" "svelte-portal": "^1.0.0"
}, },
"resolutions": { "resolutions": {

View File

@ -2,6 +2,7 @@
import "@spectrum-css/button/dist/index-vars.css" import "@spectrum-css/button/dist/index-vars.css"
import Tooltip from "../Tooltip/Tooltip.svelte" import Tooltip from "../Tooltip/Tooltip.svelte"
export let type
export let disabled = false export let disabled = false
export let size = "M" export let size = "M"
export let cta = false export let cta = false
@ -21,6 +22,7 @@
<button <button
{id} {id}
{type}
class:spectrum-Button--cta={cta} class:spectrum-Button--cta={cta}
class:spectrum-Button--primary={primary} class:spectrum-Button--primary={primary}
class:spectrum-Button--secondary={secondary} class:spectrum-Button--secondary={secondary}
@ -73,6 +75,7 @@
button { button {
position: relative; position: relative;
} }
.spectrum-Button-label { .spectrum-Button-label {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;

View File

@ -0,0 +1,19 @@
<script>
import { slide } from "svelte/transition"
export let error = null
</script>
<div transition:slide|local={{ duration: 130 }} class="error-message">
{error}
</div>
<style>
.error-message {
background: var(--spectrum-global-color-red-400);
color: white;
font-size: 14px;
padding: 6px 16px;
font-weight: 500;
}
</style>

View File

@ -1,7 +1,7 @@
<script> <script>
import Icon from "../Icon/Icon.svelte" import Icon from "../Icon/Icon.svelte"
import { getContext, onMount } from "svelte" import { getContext, onMount } from "svelte"
import { slide } from "svelte/transition" import ErrorMessage from "./ErrorMessage.svelte"
export let disabled = false export let disabled = false
export let error = null export let error = null
@ -55,9 +55,7 @@
{/if} {/if}
</div> </div>
{#if error} {#if error}
<div transition:slide|local={{ duration: 130 }} class="error-message"> <ErrorMessage {error} />
{error}
</div>
{/if} {/if}
</div> </div>
@ -110,13 +108,6 @@
.field { .field {
flex: 1 1 auto; flex: 1 1 auto;
} }
.error-message {
background: var(--spectrum-global-color-red-400);
color: white;
font-size: 14px;
padding: 6px 16px;
font-weight: 500;
}
.error-icon { .error-icon {
flex: 0 0 auto; flex: 0 0 auto;
} }

View File

@ -4,3 +4,4 @@ export { default as FancySelect } from "./FancySelect.svelte"
export { default as FancyButton } from "./FancyButton.svelte" export { default as FancyButton } from "./FancyButton.svelte"
export { default as FancyForm } from "./FancyForm.svelte" export { default as FancyForm } from "./FancyForm.svelte"
export { default as FancyButtonRadio } from "./FancyButtonRadio.svelte" export { default as FancyButtonRadio } from "./FancyButtonRadio.svelte"
export { default as ErrorMessage } from "./ErrorMessage.svelte"

View File

@ -0,0 +1,67 @@
<script>
import { Layout } from "@budibase/bbui"
import Bulgaria from "../../assets/bulgaria.png"
import Covanta from "../../assets/covanta.png"
import Schnellecke from "../../assets/schnellecke.png"
const testimonials = [
{
text: "Budibase was the only solution that checked all the boxes for Covanta. Covanta expects to realize $3.2MM in savings due to the elimination of redundant data entry.",
name: "Charles Link",
role: "Senior Director, Data and Analytics",
image: Covanta,
imageSize: 105,
},
{
text: "Budibase was mission-critical for us and went a long way in preventing what could have become a humanitarian crisis here in Bulgaria.",
name: "Bozhidar Bozhanov",
role: "Government of Bulgaria",
image: Bulgaria,
imageSize: 49,
},
{
text: "Centralization of authentication, quick turnaround time for requests, integration with different database systems has given it the edge and its now used daily for internal development for those apps that you know you need but dont feel value in losing days of development to reinvent the wheel.",
name: "Davide Lenzarini",
role: "IT manager",
image: Schnellecke,
imageSize: 141,
},
]
const testimonial = testimonials[Math.floor(Math.random() * 3)]
</script>
<div class="testimonial">
<Layout noPadding gap="S">
<img
width={testimonial.imageSize}
alt="a-happy-budibase-user"
src={testimonial.image}
/>
<div class="text">
"{testimonial.text}"
</div>
<div class="author">
<div class="name">{testimonial.name}</div>
<div class="company">{testimonial.role}</div>
</div>
</Layout>
</div>
<style>
.testimonial {
width: 380px;
padding: 40px;
}
.text {
font-size: var(--font-size-l);
font-style: italic;
}
.name {
font-weight: bold;
color: var(--spectrum-global-color-gray-900);
font-size: var(--font-size-l);
}
.company {
color: var(--spectrum-global-color-gray-700);
}
</style>

View File

@ -1,58 +1,15 @@
<script> <script>
import SplitPage from "./SplitPage.svelte" import SplitPage from "./SplitPage.svelte"
import { Layout } from "@budibase/bbui" import Testimonial from "./Testimonial.svelte"
import Bulgaria from "../../assets/bulgaria.png"
import Covanta from "../../assets/covanta.png"
import Schnellecke from "../../assets/schnellecke.png"
export let enabled = true export let enabled = true
const testimonials = [
{
text: "Budibase was the only solution that checked all the boxes for Covanta. Covanta expects to realize $3.2MM in savings due to the elimination of redundant data entry.",
name: "Charles Link",
role: "Senior Director, Data and Analytics",
image: Covanta,
imageSize: 105,
},
{
text: "Budibase was mission-critical for us and went a long way in preventing what could have become a humanitarian crisis here in Bulgaria.",
name: "Bozhidar Bozhanov",
role: "Government of Bulgaria",
image: Bulgaria,
imageSize: 49,
},
{
text: "Centralization of authentication, quick turnaround time for requests, integration with different database systems has given it the edge and its now used daily for internal development for those apps that you know you need but dont feel value in losing days of development to reinvent the wheel.",
name: "Davide Lenzarini",
role: "IT manager",
image: Schnellecke,
imageSize: 141,
},
]
const testimonial = testimonials[Math.floor(Math.random() * 3)]
</script> </script>
<SplitPage> <SplitPage>
<slot /> <slot />
<div class:wrapper={enabled} slot="right"> <div class:wrapper={enabled} slot="right">
{#if enabled} {#if enabled}
<div class="testimonial"> <Testimonial />
<Layout noPadding gap="S">
<img
width={testimonial.imageSize}
alt="a-happy-budibase-user"
src={testimonial.image}
/>
<div class="text">
"{testimonial.text}"
</div>
<div class="author">
<div class="name">{testimonial.name}</div>
<div class="company">{testimonial.role}</div>
</div>
</Layout>
</div>
{/if} {/if}
</div> </div>
</SplitPage> </SplitPage>
@ -64,20 +21,4 @@
display: grid; display: grid;
place-items: center; place-items: center;
} }
.testimonial {
width: 380px;
padding: 40px;
}
.text {
font-size: var(--font-size-l);
font-style: italic;
}
.name {
font-weight: bold;
color: var(--spectrum-global-color-gray-900);
font-size: var(--font-size-l);
}
.company {
color: var(--spectrum-global-color-gray-700);
}
</style> </style>

View File

@ -13,10 +13,10 @@
let flatpickr let flatpickr
let isOpen let isOpen
// adding the 0- will turn a string like 00:00:00 into a valid ISO // Adding the 0- will turn a string like 00:00:00 into a valid ISO
// date, but will make actual ISO dates invalid // date, but will make actual ISO dates invalid
$: time = new Date(`0-${value}`) $: isTimeValue = !isNaN(new Date(`0-${value}`))
$: timeOnly = !isNaN(time) || schema?.timeOnly $: timeOnly = isTimeValue || schema?.timeOnly
$: dateOnly = schema?.dateOnly $: dateOnly = schema?.dateOnly
$: format = timeOnly $: format = timeOnly
? "HH:mm:ss" ? "HH:mm:ss"
@ -24,6 +24,19 @@
? "MMM D YYYY" ? "MMM D YYYY"
: "MMM D YYYY, HH:mm" : "MMM D YYYY, HH:mm"
$: editable = focused && !readonly $: editable = focused && !readonly
$: displayValue = getDisplayValue(value, format, timeOnly, isTimeValue)
const getDisplayValue = (value, format, timeOnly, isTimeValue) => {
if (!value) {
return ""
}
// Parse full date strings
if (!timeOnly || !isTimeValue) {
return dayjs(value).format(format)
}
// Otherwise must be a time string
return dayjs(`0-${value}`).format(format)
}
// Ensure we close flatpickr when unselected // Ensure we close flatpickr when unselected
$: { $: {
@ -49,7 +62,7 @@
<div class="container"> <div class="container">
<div class="value"> <div class="value">
{#if value} {#if value}
{dayjs(timeOnly ? time : value).format(format)} {displayValue}
{/if} {/if}
</div> </div>
{#if editable} {#if editable}

View File

@ -1,3 +1,4 @@
export { default as SplitPage } from "./SplitPage.svelte" export { default as SplitPage } from "./SplitPage.svelte"
export { default as TestimonialPage } from "./TestimonialPage.svelte" export { default as TestimonialPage } from "./TestimonialPage.svelte"
export { default as Testimonial } from "./Testimonial.svelte"
export { Grid } from "./grid" export { Grid } from "./grid"

View File

@ -99,7 +99,7 @@
"mysql2": "2.3.3", "mysql2": "2.3.3",
"node-fetch": "2.6.7", "node-fetch": "2.6.7",
"open": "8.4.0", "open": "8.4.0",
"pg": "8.5.1", "pg": "8.10.0",
"posthog-node": "1.3.0", "posthog-node": "1.3.0",
"pouchdb": "7.3.0", "pouchdb": "7.3.0",
"pouchdb-adapter-memory": "7.2.2", "pouchdb-adapter-memory": "7.2.2",
@ -141,6 +141,7 @@
"@types/node": "14.18.20", "@types/node": "14.18.20",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/oracledb": "5.2.2", "@types/oracledb": "5.2.2",
"@types/pg": "8.6.6",
"@types/pouchdb": "6.4.0", "@types/pouchdb": "6.4.0",
"@types/redis": "4.0.11", "@types/redis": "4.0.11",
"@types/server-destroy": "1.0.1", "@types/server-destroy": "1.0.1",

View File

@ -18,11 +18,71 @@ import {
Row, Row,
CreateDatasourceResponse, CreateDatasourceResponse,
UpdateDatasourceResponse, UpdateDatasourceResponse,
UpdateDatasourceRequest,
CreateDatasourceRequest, CreateDatasourceRequest,
VerifyDatasourceRequest,
VerifyDatasourceResponse,
IntegrationBase,
DatasourcePlus,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../sdk" import sdk from "../../sdk"
function getErrorTables(errors: any, errorType: string) {
return Object.entries(errors)
.filter(entry => entry[1] === errorType)
.map(([name]) => name)
}
function updateError(error: any, newError: any, tables: string[]) {
if (!error) {
error = ""
}
if (error.length > 0) {
error += "\n"
}
error += `${newError} ${tables.join(", ")}`
return error
}
async function getConnector(
datasource: Datasource
): Promise<IntegrationBase | DatasourcePlus> {
const Connector = await getIntegration(datasource.source)
// can't enrich if it doesn't have an ID yet
if (datasource._id) {
datasource = await sdk.datasources.enrich(datasource)
}
// Connect to the DB and build the schema
return new Connector(datasource.config)
}
async function buildSchemaHelper(datasource: Datasource) {
const connector = (await getConnector(datasource)) as DatasourcePlus
await connector.buildSchema(datasource._id!, datasource.entities!)
const errors = connector.schemaErrors
let error = null
if (errors && Object.keys(errors).length > 0) {
const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
if (noKey.length) {
error = updateError(
error,
"No primary key constraint found for the following:",
noKey
)
}
if (invalidCol.length) {
const invalidCols = Object.values(InvalidColumns).join(", ")
error = updateError(
error,
`Cannot use columns ${invalidCols} found in following:`,
invalidCol
)
}
}
return { tables: connector.tables, error }
}
export async function fetch(ctx: UserCtx) { export async function fetch(ctx: UserCtx) {
// Get internal tables // Get internal tables
const db = context.getAppDB() const db = context.getAppDB()
@ -66,6 +126,33 @@ export async function fetch(ctx: UserCtx) {
ctx.body = [bbInternalDb, ...datasources] ctx.body = [bbInternalDb, ...datasources]
} }
export async function verify(
ctx: UserCtx<VerifyDatasourceRequest, VerifyDatasourceResponse>
) {
const { datasource } = ctx.request.body
let existingDatasource: undefined | Datasource
if (datasource._id) {
existingDatasource = await sdk.datasources.get(datasource._id)
}
let enrichedDatasource = datasource
if (existingDatasource) {
enrichedDatasource = sdk.datasources.mergeConfigs(
datasource,
existingDatasource
)
}
const connector = await getConnector(enrichedDatasource)
if (!connector.testConnection) {
ctx.throw(400, "Connection information verification not supported")
}
const response = await connector.testConnection()
ctx.body = {
connected: response.connected,
error: response.error,
}
}
export async function buildSchemaFromDb(ctx: UserCtx) { export async function buildSchemaFromDb(ctx: UserCtx) {
const db = context.getAppDB() const db = context.getAppDB()
const datasource = await sdk.datasources.get(ctx.params.datasourceId) const datasource = await sdk.datasources.get(ctx.params.datasourceId)
@ -311,51 +398,3 @@ export async function query(ctx: UserCtx) {
ctx.throw(400, err) ctx.throw(400, err)
} }
} }
function getErrorTables(errors: any, errorType: string) {
return Object.entries(errors)
.filter(entry => entry[1] === errorType)
.map(([name]) => name)
}
function updateError(error: any, newError: any, tables: string[]) {
if (!error) {
error = ""
}
if (error.length > 0) {
error += "\n"
}
error += `${newError} ${tables.join(", ")}`
return error
}
async function buildSchemaHelper(datasource: Datasource) {
const Connector = await getIntegration(datasource.source)
datasource = await sdk.datasources.enrich(datasource)
// Connect to the DB and build the schema
const connector = new Connector(datasource.config)
await connector.buildSchema(datasource._id, datasource.entities)
const errors = connector.schemaErrors
let error = null
if (errors && Object.keys(errors).length > 0) {
const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
if (noKey.length) {
error = updateError(
error,
"No primary key constraint found for the following:",
noKey
)
}
if (invalidCol.length) {
const invalidCols = Object.values(InvalidColumns).join(", ")
error = updateError(
error,
`Cannot use columns ${invalidCols} found in following:`,
invalidCol
)
}
}
return { tables: connector.tables, error }
}

View File

@ -1,4 +1,4 @@
import { getDefinitions } from "../../integrations" import { getDefinition, getDefinitions } from "../../integrations"
import { BBContext } from "@budibase/types" import { BBContext } from "@budibase/types"
export async function fetch(ctx: BBContext) { export async function fetch(ctx: BBContext) {
@ -7,7 +7,7 @@ export async function fetch(ctx: BBContext) {
} }
export async function find(ctx: BBContext) { export async function find(ctx: BBContext) {
const defs = await getDefinitions() const def = await getDefinition(ctx.params.type)
ctx.body = def
ctx.status = 200 ctx.status = 200
ctx.body = defs[ctx.params.type]
} }

View File

@ -15,6 +15,11 @@ router
authorized(permissions.BUILDER), authorized(permissions.BUILDER),
datasourceController.fetch datasourceController.fetch
) )
.post(
"/api/datasources/verify",
authorized(permissions.BUILDER),
datasourceController.verify
)
.get( .get(
"/api/datasources/:datasourceId", "/api/datasources/:datasourceId",
authorized( authorized(

View File

@ -9,7 +9,7 @@ import { checkTestFlag } from "../utilities/redis"
import * as utils from "./utils" import * as utils from "./utils"
import env from "../environment" import env from "../environment"
import { context, db as dbCore } from "@budibase/backend-core" import { context, db as dbCore } from "@budibase/backend-core"
import { Automation, Row } from "@budibase/types" import { Automation, Row, AutomationData, AutomationJob } from "@budibase/types"
export const TRIGGER_DEFINITIONS = definitions export const TRIGGER_DEFINITIONS = definitions
const JOB_OPTS = { const JOB_OPTS = {
@ -109,14 +109,16 @@ export async function externalTrigger(
} }
params.fields = coercedFields params.fields = coercedFields
} }
const data: Record<string, any> = { automation, event: params }
const data: AutomationData = { automation, event: params as any }
if (getResponses) { if (getResponses) {
data.event = { data.event = {
...data.event, ...data.event,
appId: context.getAppId(), appId: context.getAppId(),
automation, automation,
} }
return utils.processEvent({ data }) const job = { data } as AutomationJob
return utils.processEvent(job)
} else { } else {
return automationQueue.add(data, JOB_OPTS) return automationQueue.add(data, JOB_OPTS)
} }

View File

@ -8,7 +8,7 @@ import { db as dbCore, context } from "@budibase/backend-core"
import { getAutomationMetadataParams } from "../db/utils" import { getAutomationMetadataParams } from "../db/utils"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { quotas } from "@budibase/pro" import { quotas } from "@budibase/pro"
import { Automation, WebhookActionType } from "@budibase/types" import { Automation, AutomationJob, WebhookActionType } from "@budibase/types"
import sdk from "../sdk" import sdk from "../sdk"
const REBOOT_CRON = "@reboot" const REBOOT_CRON = "@reboot"
@ -16,27 +16,40 @@ const WH_STEP_ID = definitions.WEBHOOK.stepId
const CRON_STEP_ID = definitions.CRON.stepId const CRON_STEP_ID = definitions.CRON.stepId
const Runner = new Thread(ThreadType.AUTOMATION) const Runner = new Thread(ThreadType.AUTOMATION)
const jobMessage = (job: any, message: string) => { function loggingArgs(job: AutomationJob) {
return `app=${job.data.event.appId} automation=${job.data.automation._id} jobId=${job.id} trigger=${job.data.automation.definition.trigger.event} : ${message}` return [
{
_logKey: "automation",
trigger: job.data.automation.definition.trigger.event,
},
{
_logKey: "bull",
jobId: job.id,
},
]
} }
export async function processEvent(job: any) { export async function processEvent(job: AutomationJob) {
try { const appId = job.data.event.appId!
const automationId = job.data.automation._id const automationId = job.data.automation._id!
console.log(jobMessage(job, "running")) const task = async () => {
// need to actually await these so that an error can be captured properly try {
return await context.doInContext(job.data.event.appId, async () => { // 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 runFn = () => Runner.run(job)
return quotas.addAutomation(runFn, { const result = await quotas.addAutomation(runFn, {
automationId, automationId,
}) })
}) console.log("automation completed", ...loggingArgs(job))
} catch (err) { return result
const errJson = JSON.stringify(err) } catch (err) {
console.error(jobMessage(job, `was unable to run - ${errJson}`)) console.error(`automation was unable to run`, err, ...loggingArgs(job))
console.trace(err) return { err }
return { err } }
} }
return await context.doInAutomationContext({ appId, automationId, task })
} }
export async function updateTestHistory( export async function updateTestHistory(

View File

@ -140,7 +140,7 @@ export function init(endpoint: string) {
docClient = new AWS.DynamoDB.DocumentClient(docClientParams) docClient = new AWS.DynamoDB.DocumentClient(docClientParams)
} }
if (!env.isProd()) { if (!env.isProd() && !env.isJest()) {
env._set("AWS_ACCESS_KEY_ID", "KEY_ID") env._set("AWS_ACCESS_KEY_ID", "KEY_ID")
env._set("AWS_SECRET_ACCESS_KEY", "SECRET_KEY") env._set("AWS_SECRET_ACCESS_KEY", "SECRET_KEY")
init("http://localhost:8333") init("http://localhost:8333")

View File

@ -1,4 +1,4 @@
import { AutomationResults, AutomationStep, Document } from "@budibase/types" import { AutomationResults, AutomationStep } from "@budibase/types"
export enum LoopStepType { export enum LoopStepType {
ARRAY = "Array", ARRAY = "Array",
@ -27,7 +27,3 @@ export interface AutomationContext extends AutomationResults {
env?: Record<string, string> env?: Record<string, string>
trigger: any trigger: any
} }
export interface AutomationMetadata extends Document {
errorCount?: number
}

View File

@ -19,7 +19,6 @@ import _ from "lodash"
import { generator } from "@budibase/backend-core/tests" import { generator } from "@budibase/backend-core/tests"
import { utils } from "@budibase/backend-core" import { utils } from "@budibase/backend-core"
import { GenericContainer } from "testcontainers" import { GenericContainer } from "testcontainers"
import { generateRowIdField } from "../integrations/utils"
const config = setup.getConfig()! const config = setup.getConfig()!

View File

@ -1,11 +1,13 @@
import { import {
Integration, ConnectionInfo,
DatasourceFeature,
DatasourceFieldType, DatasourceFieldType,
QueryType, Integration,
IntegrationBase, IntegrationBase,
QueryType,
} from "@budibase/types" } from "@budibase/types"
const Airtable = require("airtable") import Airtable from "airtable"
interface AirtableConfig { interface AirtableConfig {
apiKey: string apiKey: string
@ -18,6 +20,7 @@ const SCHEMA: Integration = {
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.", "Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
friendlyName: "Airtable", friendlyName: "Airtable",
type: "Spreadsheet", type: "Spreadsheet",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
apiKey: { apiKey: {
type: DatasourceFieldType.PASSWORD, type: DatasourceFieldType.PASSWORD,
@ -81,13 +84,37 @@ const SCHEMA: Integration = {
class AirtableIntegration implements IntegrationBase { class AirtableIntegration implements IntegrationBase {
private config: AirtableConfig private config: AirtableConfig
private client: any private client
constructor(config: AirtableConfig) { constructor(config: AirtableConfig) {
this.config = config this.config = config
this.client = new Airtable(config).base(config.base) this.client = new Airtable(config).base(config.base)
} }
async testConnection(): Promise<ConnectionInfo> {
const mockTable = Date.now().toString()
try {
await this.client.makeRequest({
path: `/${mockTable}`,
})
return { connected: true }
} catch (e: any) {
if (
e.message ===
`Could not find table ${mockTable} in application ${this.config.base}`
) {
// The request managed to check the application, so the credentials are valid
return { connected: true }
}
return {
connected: false,
error: e.message as string,
}
}
}
async create(query: { table: any; json: any }) { async create(query: { table: any; json: any }) {
const { table, json } = query const { table, json } = query

View File

@ -3,9 +3,11 @@ import {
DatasourceFieldType, DatasourceFieldType,
QueryType, QueryType,
IntegrationBase, IntegrationBase,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
const { Database, aql } = require("arangojs") import { Database, aql } from "arangojs"
interface ArangodbConfig { interface ArangodbConfig {
url: string url: string
@ -21,6 +23,7 @@ const SCHEMA: Integration = {
type: "Non-relational", type: "Non-relational",
description: description:
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ", "ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
url: { url: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -58,7 +61,7 @@ const SCHEMA: Integration = {
class ArangoDBIntegration implements IntegrationBase { class ArangoDBIntegration implements IntegrationBase {
private config: ArangodbConfig private config: ArangodbConfig
private client: any private client
constructor(config: ArangodbConfig) { constructor(config: ArangodbConfig) {
const newConfig = { const newConfig = {
@ -74,6 +77,19 @@ class ArangoDBIntegration implements IntegrationBase {
this.client = new Database(newConfig) this.client = new Database(newConfig)
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
await this.client.get()
response.connected = true
} catch (e: any) {
response.error = e.message as string
}
return response
}
async read(query: { sql: any }) { async read(query: { sql: any }) {
try { try {
const result = await this.client.query(query.sql) const result = await this.client.query(query.sql)

View File

@ -1,4 +1,6 @@
import { import {
ConnectionInfo,
DatasourceFeature,
DatasourceFieldType, DatasourceFieldType,
Document, Document,
Integration, Integration,
@ -18,6 +20,7 @@ const SCHEMA: Integration = {
type: "Non-relational", type: "Non-relational",
description: description:
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.", "Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
url: { url: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -61,21 +64,32 @@ const SCHEMA: Integration = {
} }
class CouchDBIntegration implements IntegrationBase { class CouchDBIntegration implements IntegrationBase {
private config: CouchDBConfig private readonly client: dbCore.DatabaseImpl
private readonly client: any
constructor(config: CouchDBConfig) { constructor(config: CouchDBConfig) {
this.config = config
this.client = dbCore.DatabaseWithConnection(config.database, config.url) this.client = dbCore.DatabaseWithConnection(config.database, config.url)
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
const result = await this.query("exists", "validation error", {})
response.connected = result === true
} catch (e: any) {
response.error = e.message as string
}
return response
}
async query( async query(
command: string, command: string,
errorMsg: string, errorMsg: string,
query: { json?: object; id?: string } query: { json?: object; id?: string }
) { ) {
try { try {
return await this.client[command](query.id || query.json) return await (this.client as any)[command](query.id || query.json)
} catch (err) { } catch (err) {
console.error(errorMsg, err) console.error(errorMsg, err)
throw err throw err

View File

@ -3,10 +3,13 @@ import {
DatasourceFieldType, DatasourceFieldType,
QueryType, QueryType,
IntegrationBase, IntegrationBase,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
import AWS from "aws-sdk" import AWS from "aws-sdk"
import { AWS_REGION } from "../db/dynamoClient" import { AWS_REGION } from "../db/dynamoClient"
import { DocumentClient } from "aws-sdk/clients/dynamodb"
interface DynamoDBConfig { interface DynamoDBConfig {
region: string region: string
@ -22,6 +25,7 @@ const SCHEMA: Integration = {
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.", "Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
friendlyName: "DynamoDB", friendlyName: "DynamoDB",
type: "Non-relational", type: "Non-relational",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
region: { region: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -128,7 +132,7 @@ const SCHEMA: Integration = {
class DynamoDBIntegration implements IntegrationBase { class DynamoDBIntegration implements IntegrationBase {
private config: DynamoDBConfig private config: DynamoDBConfig
private client: any private client
constructor(config: DynamoDBConfig) { constructor(config: DynamoDBConfig) {
this.config = config this.config = config
@ -148,7 +152,23 @@ class DynamoDBIntegration implements IntegrationBase {
this.client = new AWS.DynamoDB.DocumentClient(this.config) this.client = new AWS.DynamoDB.DocumentClient(this.config)
} }
async create(query: { table: string; json: object }) { async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
const scanRes = await new AWS.DynamoDB(this.config).listTables().promise()
response.connected = !!scanRes.$response
} catch (e: any) {
response.error = e.message as string
}
return response
}
async create(query: {
table: string
json: Omit<DocumentClient.PutItemInput, "TableName">
}) {
const params = { const params = {
TableName: query.table, TableName: query.table,
...query.json, ...query.json,
@ -189,7 +209,10 @@ class DynamoDBIntegration implements IntegrationBase {
return new AWS.DynamoDB(this.config).describeTable(params).promise() return new AWS.DynamoDB(this.config).describeTable(params).promise()
} }
async get(query: { table: string; json: object }) { async get(query: {
table: string
json: Omit<DocumentClient.GetItemInput, "TableName">
}) {
const params = { const params = {
TableName: query.table, TableName: query.table,
...query.json, ...query.json,
@ -197,7 +220,10 @@ class DynamoDBIntegration implements IntegrationBase {
return this.client.get(params).promise() return this.client.get(params).promise()
} }
async update(query: { table: string; json: object }) { async update(query: {
table: string
json: Omit<DocumentClient.UpdateItemInput, "TableName">
}) {
const params = { const params = {
TableName: query.table, TableName: query.table,
...query.json, ...query.json,
@ -205,7 +231,10 @@ class DynamoDBIntegration implements IntegrationBase {
return this.client.update(params).promise() return this.client.update(params).promise()
} }
async delete(query: { table: string; json: object }) { async delete(query: {
table: string
json: Omit<DocumentClient.DeleteItemInput, "TableName">
}) {
const params = { const params = {
TableName: query.table, TableName: query.table,
...query.json, ...query.json,

View File

@ -3,6 +3,8 @@ import {
DatasourceFieldType, DatasourceFieldType,
QueryType, QueryType,
IntegrationBase, IntegrationBase,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
import { Client, ClientOptions } from "@elastic/elasticsearch" import { Client, ClientOptions } from "@elastic/elasticsearch"
@ -20,6 +22,7 @@ const SCHEMA: Integration = {
"Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.", "Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.",
friendlyName: "ElasticSearch", friendlyName: "ElasticSearch",
type: "Non-relational", type: "Non-relational",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
url: { url: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -95,7 +98,7 @@ const SCHEMA: Integration = {
class ElasticSearchIntegration implements IntegrationBase { class ElasticSearchIntegration implements IntegrationBase {
private config: ElasticsearchConfig private config: ElasticsearchConfig
private client: any private client
constructor(config: ElasticsearchConfig) { constructor(config: ElasticsearchConfig) {
this.config = config this.config = config
@ -114,6 +117,18 @@ class ElasticSearchIntegration implements IntegrationBase {
this.client = new Client(clientConfig) this.client = new Client(clientConfig)
} }
async testConnection(): Promise<ConnectionInfo> {
try {
await this.client.info()
return { connected: true }
} catch (e: any) {
return {
connected: false,
error: e.message as string,
}
}
}
async create(query: { index: string; json: object }) { async create(query: { index: string; json: object }) {
const { index, json } = query const { index, json } = query

View File

@ -3,6 +3,8 @@ import {
Integration, Integration,
QueryType, QueryType,
IntegrationBase, IntegrationBase,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
import { Firestore, WhereFilterOp } from "@google-cloud/firestore" import { Firestore, WhereFilterOp } from "@google-cloud/firestore"
@ -18,6 +20,7 @@ const SCHEMA: Integration = {
type: "Non-relational", type: "Non-relational",
description: description:
"Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.", "Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
email: { email: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -99,6 +102,18 @@ class FirebaseIntegration implements IntegrationBase {
}) })
} }
async testConnection(): Promise<ConnectionInfo> {
try {
await this.client.listCollections()
return { connected: true }
} catch (e: any) {
return {
connected: false,
error: e.message as string,
}
}
}
async create(query: { json: object; extra: { [key: string]: string } }) { async create(query: { json: object; extra: { [key: string]: string } }) {
try { try {
const documentReference = this.client const documentReference = this.client

View File

@ -1,4 +1,6 @@
import { import {
ConnectionInfo,
DatasourceFeature,
DatasourceFieldType, DatasourceFieldType,
DatasourcePlus, DatasourcePlus,
FieldType, FieldType,
@ -64,6 +66,7 @@ const SCHEMA: Integration = {
"Create and collaborate on online spreadsheets in real-time and from any device. ", "Create and collaborate on online spreadsheets in real-time and from any device. ",
friendlyName: "Google Sheets", friendlyName: "Google Sheets",
type: "Spreadsheet", type: "Spreadsheet",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
spreadsheetId: { spreadsheetId: {
display: "Google Sheet URL", display: "Google Sheet URL",
@ -139,6 +142,19 @@ class GoogleSheetsIntegration implements DatasourcePlus {
this.client = new GoogleSpreadsheet(spreadsheetId) this.client = new GoogleSpreadsheet(spreadsheetId)
} }
async testConnection(): Promise<ConnectionInfo> {
try {
await this.connect()
await this.client.loadInfo()
return { connected: true }
} catch (e: any) {
return {
connected: false,
error: e.message as string,
}
}
}
getBindingIdentifier() { getBindingIdentifier() {
return "" return ""
} }

View File

@ -20,7 +20,7 @@ import env from "../environment"
import { cloneDeep } from "lodash" import { cloneDeep } from "lodash"
import sdk from "../sdk" import sdk from "../sdk"
const DEFINITIONS: { [key: string]: Integration } = { const DEFINITIONS: Record<SourceName, Integration | undefined> = {
[SourceName.POSTGRES]: postgres.schema, [SourceName.POSTGRES]: postgres.schema,
[SourceName.DYNAMODB]: dynamodb.schema, [SourceName.DYNAMODB]: dynamodb.schema,
[SourceName.MONGODB]: mongodb.schema, [SourceName.MONGODB]: mongodb.schema,
@ -36,9 +36,10 @@ const DEFINITIONS: { [key: string]: Integration } = {
[SourceName.GOOGLE_SHEETS]: googlesheets.schema, [SourceName.GOOGLE_SHEETS]: googlesheets.schema,
[SourceName.REDIS]: redis.schema, [SourceName.REDIS]: redis.schema,
[SourceName.SNOWFLAKE]: snowflake.schema, [SourceName.SNOWFLAKE]: snowflake.schema,
[SourceName.ORACLE]: undefined,
} }
const INTEGRATIONS: { [key: string]: any } = { const INTEGRATIONS: Record<SourceName, any> = {
[SourceName.POSTGRES]: postgres.integration, [SourceName.POSTGRES]: postgres.integration,
[SourceName.DYNAMODB]: dynamodb.integration, [SourceName.DYNAMODB]: dynamodb.integration,
[SourceName.MONGODB]: mongodb.integration, [SourceName.MONGODB]: mongodb.integration,
@ -55,6 +56,7 @@ const INTEGRATIONS: { [key: string]: any } = {
[SourceName.REDIS]: redis.integration, [SourceName.REDIS]: redis.integration,
[SourceName.FIRESTORE]: firebase.integration, [SourceName.FIRESTORE]: firebase.integration,
[SourceName.SNOWFLAKE]: snowflake.integration, [SourceName.SNOWFLAKE]: snowflake.integration,
[SourceName.ORACLE]: undefined,
} }
// optionally add oracle integration if the oracle binary can be installed // optionally add oracle integration if the oracle binary can be installed
@ -67,10 +69,13 @@ if (
INTEGRATIONS[SourceName.ORACLE] = oracle.integration INTEGRATIONS[SourceName.ORACLE] = oracle.integration
} }
export async function getDefinition(source: SourceName): Promise<Integration> { export async function getDefinition(
source: SourceName
): Promise<Integration | undefined> {
// check if its integrated, faster // check if its integrated, faster
if (DEFINITIONS[source]) { const definition = DEFINITIONS[source]
return DEFINITIONS[source] if (definition) {
return definition
} }
const allDefinitions = await getDefinitions() const allDefinitions = await getDefinitions()
return allDefinitions[source] return allDefinitions[source]
@ -98,7 +103,7 @@ export async function getDefinitions() {
} }
} }
export async function getIntegration(integration: string) { export async function getIntegration(integration: SourceName) {
if (INTEGRATIONS[integration]) { if (INTEGRATIONS[integration]) {
return INTEGRATIONS[integration] return INTEGRATIONS[integration]
} }
@ -107,7 +112,7 @@ export async function getIntegration(integration: string) {
for (let plugin of plugins) { for (let plugin of plugins) {
if (plugin.name === integration) { if (plugin.name === integration) {
// need to use commonJS require due to its dynamic runtime nature // need to use commonJS require due to its dynamic runtime nature
const retrieved: any = await getDatasourcePlugin(plugin) const retrieved = await getDatasourcePlugin(plugin)
if (retrieved.integration) { if (retrieved.integration) {
return retrieved.integration return retrieved.integration
} else { } else {

View File

@ -8,6 +8,8 @@ import {
QueryType, QueryType,
SqlQuery, SqlQuery,
DatasourcePlus, DatasourcePlus,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
import { import {
getSqlQuery, getSqlQuery,
@ -39,6 +41,7 @@ const SCHEMA: Integration = {
"Microsoft SQL Server is a relational database management system developed by Microsoft. ", "Microsoft SQL Server is a relational database management system developed by Microsoft. ",
friendlyName: "MS SQL Server", friendlyName: "MS SQL Server",
type: "Relational", type: "Relational",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
user: { user: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -121,6 +124,19 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
} }
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
await this.connect()
response.connected = true
} catch (e: any) {
response.error = e.message as string
}
return response
}
getBindingIdentifier(): string { getBindingIdentifier(): string {
return `@p${this.index++}` return `@p${this.index++}`
} }

View File

@ -3,6 +3,8 @@ import {
DatasourceFieldType, DatasourceFieldType,
QueryType, QueryType,
IntegrationBase, IntegrationBase,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
import { import {
MongoClient, MongoClient,
@ -38,6 +40,7 @@ const getSchema = () => {
type: "Non-relational", type: "Non-relational",
description: description:
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.", "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
connectionString: { connectionString: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -358,6 +361,19 @@ class MongoIntegration implements IntegrationBase {
this.client = new MongoClient(config.connectionString, options) this.client = new MongoClient(config.connectionString, options)
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
await this.connect()
response.connected = true
} catch (e: any) {
response.error = e.message as string
}
return response
}
async connect() { async connect() {
return this.client.connect() return this.client.connect()
} }

View File

@ -7,6 +7,8 @@ import {
Table, Table,
TableSchema, TableSchema,
DatasourcePlus, DatasourcePlus,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
import { import {
getSqlQuery, getSqlQuery,
@ -20,18 +22,11 @@ import { NUMBER_REGEX } from "../utilities"
import Sql from "./base/sql" import Sql from "./base/sql"
import { MySQLColumn } from "./base/types" import { MySQLColumn } from "./base/types"
const mysql = require("mysql2/promise") import mysql from "mysql2/promise"
interface MySQLConfig { interface MySQLConfig extends mysql.ConnectionOptions {
host: string
port: number
user: string
password: string
database: string database: string
ssl?: { [key: string]: any }
rejectUnauthorized: boolean rejectUnauthorized: boolean
typeCast: Function
multipleStatements: boolean
} }
const SCHEMA: Integration = { const SCHEMA: Integration = {
@ -41,6 +36,7 @@ const SCHEMA: Integration = {
type: "Relational", type: "Relational",
description: description:
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ", "MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
host: { host: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -92,8 +88,6 @@ const SCHEMA: Integration = {
}, },
} }
const TimezoneAwareDateTypes = ["timestamp"]
function bindingTypeCoerce(bindings: any[]) { function bindingTypeCoerce(bindings: any[]) {
for (let i = 0; i < bindings.length; i++) { for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i] const binding = bindings[i]
@ -120,7 +114,7 @@ function bindingTypeCoerce(bindings: any[]) {
class MySQLIntegration extends Sql implements DatasourcePlus { class MySQLIntegration extends Sql implements DatasourcePlus {
private config: MySQLConfig private config: MySQLConfig
private client: any private client?: mysql.Connection
public tables: Record<string, Table> = {} public tables: Record<string, Table> = {}
public schemaErrors: Record<string, string> = {} public schemaErrors: Record<string, string> = {}
@ -134,7 +128,8 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
if ( if (
config.rejectUnauthorized != null && config.rejectUnauthorized != null &&
!config.rejectUnauthorized && !config.rejectUnauthorized &&
config.ssl config.ssl &&
typeof config.ssl !== "string"
) { ) {
config.ssl.rejectUnauthorized = config.rejectUnauthorized config.ssl.rejectUnauthorized = config.rejectUnauthorized
} }
@ -160,6 +155,22 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
} }
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
const [result] = await this.internalQuery(
{ sql: "SELECT 1+1 AS checkRes" },
{ connect: true }
)
response.connected = result?.checkRes == 2
} catch (e: any) {
response.error = e.message as string
}
return response
}
getBindingIdentifier(): string { getBindingIdentifier(): string {
return "?" return "?"
} }
@ -173,7 +184,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
} }
async disconnect() { async disconnect() {
await this.client.end() await this.client!.end()
} }
async internalQuery( async internalQuery(
@ -192,10 +203,10 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
? baseBindings ? baseBindings
: bindingTypeCoerce(baseBindings) : bindingTypeCoerce(baseBindings)
// Node MySQL is callback based, so we must wrap our call in a promise // Node MySQL is callback based, so we must wrap our call in a promise
const response = await this.client.query(query.sql, bindings) const response = await this.client!.query(query.sql, bindings)
return response[0] return response[0]
} finally { } finally {
if (opts?.connect) { if (opts?.connect && this.client) {
await this.disconnect() await this.disconnect()
} }
} }

View File

@ -7,6 +7,8 @@ import {
SqlQuery, SqlQuery,
Table, Table,
DatasourcePlus, DatasourcePlus,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
import { import {
buildExternalTableId, buildExternalTableId,
@ -24,12 +26,7 @@ import {
ExecuteOptions, ExecuteOptions,
Result, Result,
} from "oracledb" } from "oracledb"
import { import { OracleTable, OracleColumn, OracleColumnsResponse } from "./base/types"
OracleTable,
OracleColumn,
OracleColumnsResponse,
OracleConstraint,
} from "./base/types"
let oracledb: any let oracledb: any
try { try {
oracledb = require("oracledb") oracledb = require("oracledb")
@ -53,6 +50,7 @@ const SCHEMA: Integration = {
type: "Relational", type: "Relational",
description: description:
"Oracle Database is an object-relational database management system developed by Oracle Corporation", "Oracle Database is an object-relational database management system developed by Oracle Corporation",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
host: { host: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -325,6 +323,30 @@ class OracleIntegration extends Sql implements DatasourcePlus {
this.schemaErrors = final.errors this.schemaErrors = final.errors
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
let connection
try {
connection = await this.getConnection()
response.connected = true
} catch (err: any) {
response.connected = false
response.error = err.message
} finally {
if (connection) {
try {
await connection.close()
} catch (err: any) {
response.connected = false
response.error = err.message
}
}
}
return response
}
private async internalQuery<T>(query: SqlQuery): Promise<Result<T>> { private async internalQuery<T>(query: SqlQuery): Promise<Result<T>> {
let connection let connection
try { try {

View File

@ -6,6 +6,8 @@ import {
SqlQuery, SqlQuery,
Table, Table,
DatasourcePlus, DatasourcePlus,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
import { import {
getSqlQuery, getSqlQuery,
@ -18,7 +20,7 @@ import Sql from "./base/sql"
import { PostgresColumn } from "./base/types" import { PostgresColumn } from "./base/types"
import { escapeDangerousCharacters } from "../utilities" import { escapeDangerousCharacters } from "../utilities"
const { Client, types } = require("pg") import { Client, types } from "pg"
// Return "date" and "timestamp" types as plain strings. // Return "date" and "timestamp" types as plain strings.
// This lets us reference the original stored timezone. // This lets us reference the original stored timezone.
@ -50,6 +52,7 @@ const SCHEMA: Integration = {
type: "Relational", type: "Relational",
description: description:
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.", "PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
host: { host: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
@ -114,7 +117,7 @@ const SCHEMA: Integration = {
} }
class PostgresIntegration extends Sql implements DatasourcePlus { class PostgresIntegration extends Sql implements DatasourcePlus {
private readonly client: any private readonly client: Client
private readonly config: PostgresConfig private readonly config: PostgresConfig
private index: number = 1 private index: number = 1
private open: boolean private open: boolean
@ -150,6 +153,21 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
this.open = false this.open = false
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
await this.openConnection()
response.connected = true
} catch (e: any) {
response.error = e.message as string
} finally {
await this.closeConnection()
}
return response
}
getBindingIdentifier(): string { getBindingIdentifier(): string {
return `$${this.index++}` return `$${this.index++}`
} }
@ -163,7 +181,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
if (!this.config.schema) { if (!this.config.schema) {
this.config.schema = "public" this.config.schema = "public"
} }
this.client.query(`SET search_path TO ${this.config.schema}`) await this.client.query(`SET search_path TO ${this.config.schema}`)
this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'` this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'`
this.open = true this.open = true
} }

View File

@ -1,4 +1,10 @@
import { DatasourceFieldType, Integration, QueryType } from "@budibase/types" import {
ConnectionInfo,
DatasourceFeature,
DatasourceFieldType,
Integration,
QueryType,
} from "@budibase/types"
import Redis from "ioredis" import Redis from "ioredis"
interface RedisConfig { interface RedisConfig {
@ -11,9 +17,11 @@ interface RedisConfig {
const SCHEMA: Integration = { const SCHEMA: Integration = {
docs: "https://redis.io/docs/", docs: "https://redis.io/docs/",
description: "", description:
"Redis is a caching tool, providing powerful key-value store capabilities.",
friendlyName: "Redis", friendlyName: "Redis",
type: "Non-relational", type: "Non-relational",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
host: { host: {
type: "string", type: "string",
@ -86,7 +94,7 @@ const SCHEMA: Integration = {
class RedisIntegration { class RedisIntegration {
private readonly config: RedisConfig private readonly config: RedisConfig
private client: any private client
constructor(config: RedisConfig) { constructor(config: RedisConfig) {
this.config = config this.config = config
@ -99,6 +107,21 @@ class RedisIntegration {
}) })
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
await this.client.ping()
response.connected = true
} catch (e: any) {
response.error = e.message as string
} finally {
await this.disconnect()
}
return response
}
async disconnect() { async disconnect() {
return this.client.quit() return this.client.quit()
} }

View File

@ -3,10 +3,12 @@ import {
QueryType, QueryType,
IntegrationBase, IntegrationBase,
DatasourceFieldType, DatasourceFieldType,
DatasourceFeature,
ConnectionInfo,
} from "@budibase/types" } from "@budibase/types"
const AWS = require("aws-sdk") import AWS from "aws-sdk"
const csv = require("csvtojson") import csv from "csvtojson"
interface S3Config { interface S3Config {
region: string region: string
@ -22,6 +24,7 @@ const SCHEMA: Integration = {
"Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.", "Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.",
friendlyName: "Amazon S3", friendlyName: "Amazon S3",
type: "Object store", type: "Object store",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
region: { region: {
type: "string", type: "string",
@ -152,7 +155,7 @@ const SCHEMA: Integration = {
class S3Integration implements IntegrationBase { class S3Integration implements IntegrationBase {
private readonly config: S3Config private readonly config: S3Config
private client: any private client
constructor(config: S3Config) { constructor(config: S3Config) {
this.config = config this.config = config
@ -165,6 +168,19 @@ class S3Integration implements IntegrationBase {
this.client = new AWS.S3(this.config) this.client = new AWS.S3(this.config)
} }
async testConnection() {
const response: ConnectionInfo = {
connected: false,
}
try {
await this.client.listBuckets().promise()
response.connected = true
} catch (e: any) {
response.error = e.message as string
}
return response
}
async create(query: { async create(query: {
bucket: string bucket: string
location: string location: string

View File

@ -1,4 +1,10 @@
import { Integration, QueryType, SqlQuery } from "@budibase/types" import {
ConnectionInfo,
DatasourceFeature,
Integration,
QueryType,
SqlQuery,
} from "@budibase/types"
import { Snowflake } from "snowflake-promise" import { Snowflake } from "snowflake-promise"
interface SnowflakeConfig { interface SnowflakeConfig {
@ -16,6 +22,7 @@ const SCHEMA: Integration = {
"Snowflake is a solution for data warehousing, data lakes, data engineering, data science, data application development, and securely sharing and consuming shared data.", "Snowflake is a solution for data warehousing, data lakes, data engineering, data science, data application development, and securely sharing and consuming shared data.",
friendlyName: "Snowflake", friendlyName: "Snowflake",
type: "Relational", type: "Relational",
features: [DatasourceFeature.CONNECTION_CHECKING],
datasource: { datasource: {
account: { account: {
type: "string", type: "string",
@ -65,6 +72,18 @@ class SnowflakeIntegration {
this.client = new Snowflake(config) this.client = new Snowflake(config)
} }
async testConnection(): Promise<ConnectionInfo> {
try {
await this.client.connect()
return { connected: true }
} catch (e: any) {
return {
connected: false,
error: e.message as string,
}
}
}
async internalQuery(query: SqlQuery) { async internalQuery(query: SqlQuery) {
await this.client.connect() await this.client.connect()
try { try {

View File

@ -33,7 +33,7 @@ export const backfill = async (appDb: any, timestamp: string | number) => {
datasource = { datasource = {
type: "unknown", type: "unknown",
_id: query.datasourceId, _id: query.datasourceId,
source: SourceName.UNKNOWN, source: "unknown" as SourceName,
} }
} else { } else {
throw e throw e

View File

@ -13,6 +13,7 @@ import {
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { getEnvironmentVariables } from "../../utils" import { getEnvironmentVariables } from "../../utils"
import { getDefinitions, getDefinition } from "../../../integrations" import { getDefinitions, getDefinition } from "../../../integrations"
import _ from "lodash"
const ENV_VAR_PREFIX = "env." const ENV_VAR_PREFIX = "env."
@ -41,7 +42,7 @@ async function enrichDatasourceWithValues(datasource: Datasource) {
{ onlyFound: true } { onlyFound: true }
) as Datasource ) as Datasource
const definition = await getDefinition(processed.source) const definition = await getDefinition(processed.source)
processed.config = checkDatasourceTypes(definition, processed.config) processed.config = checkDatasourceTypes(definition!, processed.config)
return { return {
datasource: processed, datasource: processed,
envVars: env as Record<string, string>, envVars: env as Record<string, string>,
@ -147,6 +148,11 @@ export function mergeConfigs(update: Datasource, old: Datasource) {
} }
} }
} }
if (old.config?.auth) {
update.config = _.merge(old.config, update.config)
}
// update back to actual passwords for everything else // update back to actual passwords for everything else
for (let [key, value] of Object.entries(update.config)) { for (let [key, value] of Object.entries(update.config)) {
if (value !== PASSWORD_REPLACEMENT) { if (value !== PASSWORD_REPLACEMENT) {

View File

@ -13,13 +13,18 @@ import { generateAutomationMetadataID, isProdAppID } from "../db/utils"
import { definitions as triggerDefs } from "../automations/triggerInfo" import { definitions as triggerDefs } from "../automations/triggerInfo"
import { AutomationErrors, MAX_AUTOMATION_RECURRING_ERRORS } from "../constants" import { AutomationErrors, MAX_AUTOMATION_RECURRING_ERRORS } from "../constants"
import { storeLog } from "../automations/logging" import { storeLog } from "../automations/logging"
import { Automation, AutomationStep, AutomationStatus } from "@budibase/types" import {
Automation,
AutomationStep,
AutomationStatus,
AutomationMetadata,
AutomationJob,
} from "@budibase/types"
import { import {
LoopStep, LoopStep,
LoopInput, LoopInput,
TriggerOutput, TriggerOutput,
AutomationContext, AutomationContext,
AutomationMetadata,
} from "../definitions/automations" } from "../definitions/automations"
import { WorkerCallback } from "./definitions" import { WorkerCallback } from "./definitions"
import { context, logging } from "@budibase/backend-core" import { context, logging } from "@budibase/backend-core"
@ -60,11 +65,11 @@ class Orchestrator {
_job: Job _job: Job
executionOutput: AutomationContext executionOutput: AutomationContext
constructor(job: Job) { constructor(job: AutomationJob) {
let automation = job.data.automation, let automation = job.data.automation
triggerOutput = job.data.event let triggerOutput = job.data.event
const metadata = triggerOutput.metadata const metadata = triggerOutput.metadata
this._chainCount = metadata ? metadata.automationChainCount : 0 this._chainCount = metadata ? metadata.automationChainCount! : 0
this._appId = triggerOutput.appId as string this._appId = triggerOutput.appId as string
this._job = job this._job = job
const triggerStepId = automation.definition.trigger.stepId const triggerStepId = automation.definition.trigger.stepId

View File

@ -1,5 +1,3 @@
import { EnvironmentVariablesDecrypted } from "@budibase/types"
export type WorkerCallback = (error: any, response?: any) => void export type WorkerCallback = (error: any, response?: any) => void
export interface QueryEvent { export interface QueryEvent {

View File

@ -1,5 +1,7 @@
import workerFarm from "worker-farm" import workerFarm from "worker-farm"
import env from "../environment" import env from "../environment"
import { AutomationJob } from "@budibase/types"
import { QueryEvent } from "./definitions"
export const ThreadType = { export const ThreadType = {
QUERY: "query", QUERY: "query",
@ -64,11 +66,11 @@ export class Thread {
) )
} }
run(data: any) { run(job: AutomationJob | QueryEvent) {
const timeout = this.timeoutMs const timeout = this.timeoutMs
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
function fire(worker: any) { function fire(worker: any) {
worker.execute(data, (err: any, response: any) => { worker.execute(job, (err: any, response: any) => {
if (err && err.type === "TimeoutError") { if (err && err.type === "TimeoutError") {
reject( reject(
new Error(`Query response time exceeded ${timeout}ms timeout.`) new Error(`Query response time exceeded ${timeout}ms timeout.`)

View File

@ -14,6 +14,15 @@ export interface CreateDatasourceRequest {
fetchSchema?: boolean fetchSchema?: boolean
} }
export interface VerifyDatasourceRequest {
datasource: Datasource
}
export interface VerifyDatasourceResponse {
connected: boolean
error?: string
}
export interface UpdateDatasourceRequest extends Datasource { export interface UpdateDatasourceRequest extends Datasource {
datasource: Datasource datasource: Datasource
} }

View File

@ -178,3 +178,8 @@ export type AutomationStepInput = {
appId: string appId: string
apiKey?: string apiKey?: string
} }
export interface AutomationMetadata extends Document {
errorCount?: number
automationChainCount?: number
}

View File

@ -0,0 +1,15 @@
import { Automation, AutomationMetadata } from "../../documents"
import { Job } from "bull"
export interface AutomationDataEvent {
appId?: string
metadata?: AutomationMetadata
automation?: Automation
}
export interface AutomationData {
event: AutomationDataEvent
automation: Automation
}
export type AutomationJob = Job<AutomationData>

View File

@ -55,7 +55,6 @@ export enum SourceName {
FIRESTORE = "FIRESTORE", FIRESTORE = "FIRESTORE",
REDIS = "REDIS", REDIS = "REDIS",
SNOWFLAKE = "SNOWFLAKE", SNOWFLAKE = "SNOWFLAKE",
UNKNOWN = "unknown",
} }
export enum IncludeRelationship { export enum IncludeRelationship {
@ -74,6 +73,10 @@ export enum FilterType {
ONE_OF = "oneOf", ONE_OF = "oneOf",
} }
export enum DatasourceFeature {
CONNECTION_CHECKING = "connection",
}
export interface StepDefinition { export interface StepDefinition {
key: string key: string
template: string template: string
@ -112,6 +115,7 @@ export interface Integration {
docs: string docs: string
plus?: boolean plus?: boolean
auth?: { type: string } auth?: { type: string }
features?: DatasourceFeature[]
relationships?: boolean relationships?: boolean
description: string description: string
friendlyName: string friendlyName: string
@ -124,11 +128,17 @@ export interface Integration {
extra?: ExtraQueryConfig extra?: ExtraQueryConfig
} }
export type ConnectionInfo = {
connected: boolean
error?: string
}
export interface IntegrationBase { export interface IntegrationBase {
create?(query: any): Promise<any[] | any> create?(query: any): Promise<any[] | any>
read?(query: any): Promise<any[] | any> read?(query: any): Promise<any[] | any>
update?(query: any): Promise<any[] | any> update?(query: any): Promise<any[] | any>
delete?(query: any): Promise<any[] | any> delete?(query: any): Promise<any[] | any>
testConnection?(): Promise<ConnectionInfo>
} }
export interface DatasourcePlus extends IntegrationBase { export interface DatasourcePlus extends IntegrationBase {

View File

@ -1,3 +1,4 @@
export * from "./automations"
export * from "./hosting" export * from "./hosting"
export * from "./context" export * from "./context"
export * from "./events" export * from "./events"

View File

@ -14,12 +14,14 @@
"test:watch": "yarn run test --watch", "test:watch": "yarn run test --watch",
"test:debug": "DEBUG=1 yarn run test", "test:debug": "DEBUG=1 yarn run test",
"test:notify": "node scripts/testResultsWebhook", "test:notify": "node scripts/testResultsWebhook",
"test:smoke": "yarn run test --testPathIgnorePatterns=\\\"\\/dataSources\\/\\\"", "test:smoke": "yarn run test --testPathIgnorePatterns=/.+\\.integration\\.spec\\.ts",
"test:ci": "start-server-and-test dev:built http://localhost:4001/health test:smoke", "test:ci": "start-server-and-test dev:built http://localhost:4001/health test:smoke",
"serve": "start-server-and-test dev:built http://localhost:4001/health",
"dev:built": "cd ../ && yarn dev:built" "dev:built": "cd ../ && yarn dev:built"
}, },
"devDependencies": { "devDependencies": {
"@budibase/types": "^2.3.17", "@budibase/types": "^2.3.17",
"@trendyol/jest-testcontainers": "^2.1.1",
"@types/jest": "29.0.0", "@types/jest": "29.0.0",
"@types/node-fetch": "2.6.2", "@types/node-fetch": "2.6.2",
"chance": "1.1.8", "chance": "1.1.8",

View File

@ -0,0 +1,77 @@
import { GenericContainer, Wait } from "testcontainers"
import arangodb from "../../../../packages/server/src/integrations/arangodb"
import { generator } from "../../shared"
jest.unmock("arangojs")
describe("datasource validators", () => {
describe("arangodb", () => {
let connectionSettings: {
user: string
password: string
url: string
}
beforeAll(async () => {
const user = "root"
const password = generator.hash()
const container = await new GenericContainer("arangodb")
.withExposedPorts(8529)
.withEnv("ARANGO_ROOT_PASSWORD", password)
.withWaitStrategy(
Wait.forLogMessage("is ready for business. Have fun!")
)
.start()
connectionSettings = {
user,
password,
url: `http://${container.getContainerIpAddress()}:${container.getMappedPort(
8529
)}`,
}
})
it("test valid connection string", async () => {
const integration = new arangodb.integration({
url: connectionSettings.url,
username: connectionSettings.user,
password: connectionSettings.password,
databaseName: "",
collection: "",
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test wrong password", async () => {
const integration = new arangodb.integration({
url: connectionSettings.url,
username: connectionSettings.user,
password: "wrong",
databaseName: "",
collection: "",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: "not authorized to execute this request",
})
})
it("test wrong url", async () => {
const integration = new arangodb.integration({
url: "http://not.here",
username: connectionSettings.user,
password: connectionSettings.password,
databaseName: "",
collection: "",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: "getaddrinfo ENOTFOUND not.here",
})
})
})
})

View File

@ -0,0 +1,67 @@
import { GenericContainer } from "testcontainers"
import couchdb from "../../../../packages/server/src/integrations/couchdb"
import { generator } from "../../shared"
describe("datasource validators", () => {
describe("couchdb", () => {
let url: string
beforeAll(async () => {
const user = generator.first()
const password = generator.hash()
const container = await new GenericContainer("budibase/couchdb")
.withExposedPorts(5984)
.withEnv("COUCHDB_USER", user)
.withEnv("COUCHDB_PASSWORD", password)
.start()
const host = container.getContainerIpAddress()
const port = container.getMappedPort(5984)
await container.exec([
`curl`,
`-u`,
`${user}:${password}`,
`-X`,
`PUT`,
`localhost:5984/db`,
])
url = `http://${user}:${password}@${host}:${port}`
})
it("test valid connection string", async () => {
const integration = new couchdb.integration({
url,
database: "db",
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test invalid database", async () => {
const integration = new couchdb.integration({
url,
database: "random_db",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
})
})
it("test invalid url", async () => {
const integration = new couchdb.integration({
url: "http://invalid:123",
database: "any",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error:
"request to http://invalid:123/any failed, reason: getaddrinfo ENOTFOUND invalid",
})
})
})
})

View File

@ -0,0 +1,63 @@
import { GenericContainer } from "testcontainers"
import { env } from "@budibase/backend-core"
import dynamodb from "../../../../packages/server/src/integrations/dynamodb"
import { generator } from "../../shared"
jest.unmock("aws-sdk")
describe("datasource validators", () => {
describe("dynamodb", () => {
let connectionSettings: {
user: string
password: string
url: string
}
beforeAll(async () => {
const user = "root"
const password = generator.hash()
const container = await new GenericContainer("amazon/dynamodb-local")
.withExposedPorts(8000)
.start()
connectionSettings = {
user,
password,
url: `http://${container.getContainerIpAddress()}:${container.getMappedPort(
8000
)}`,
}
env._set("AWS_ACCESS_KEY_ID", "mocked_key")
env._set("AWS_SECRET_ACCESS_KEY", "mocked_secret")
})
it("test valid connection string", async () => {
const integration = new dynamodb.integration({
endpoint: connectionSettings.url,
region: "",
accessKeyId: "",
secretAccessKey: "",
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test wrong endpoint", async () => {
const integration = new dynamodb.integration({
endpoint: "http://wrong.url:2880",
region: "",
accessKeyId: "",
secretAccessKey: "",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error:
"Inaccessible host: `wrong.url' at port `undefined'. This service may not be available in the `eu-west-1' region.",
})
})
})
})

View File

@ -0,0 +1,34 @@
import { ElasticsearchContainer } from "testcontainers"
import elastic from "../../../../packages/server/src/integrations/elasticsearch"
jest.unmock("@elastic/elasticsearch")
describe("datasource validators", () => {
describe("elastic search", () => {
let url: string
beforeAll(async () => {
const container = await new ElasticsearchContainer().start()
url = container.getHttpUrl()
})
it("test valid connection string", async () => {
const integration = new elastic.integration({
url,
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test wrong connection string", async () => {
const integration = new elastic.integration({
url: `http://localhost:5656`,
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: "connect ECONNREFUSED 127.0.0.1:5656",
})
})
})
})

View File

@ -0,0 +1,100 @@
import { GenericContainer } from "testcontainers"
import mongo from "../../../../packages/server/src/integrations/mongodb"
import { generator } from "../../shared"
jest.unmock("mongodb")
describe("datasource validators", () => {
describe("mongo", () => {
let connectionSettings: {
user: string
password: string
host: string
port: number
}
function getConnectionString(
settings: Partial<typeof connectionSettings> = {}
) {
const { user, password, host, port } = {
...connectionSettings,
...settings,
}
return `mongodb://${user}:${password}@${host}:${port}`
}
beforeAll(async () => {
const user = generator.name()
const password = generator.hash()
const container = await new GenericContainer("mongo")
.withExposedPorts(27017)
.withEnv("MONGO_INITDB_ROOT_USERNAME", user)
.withEnv("MONGO_INITDB_ROOT_PASSWORD", password)
.start()
connectionSettings = {
user,
password,
host: container.getContainerIpAddress(),
port: container.getMappedPort(27017),
}
})
it("test valid connection string", async () => {
const integration = new mongo.integration({
connectionString: getConnectionString(),
db: "",
tlsCertificateFile: "",
tlsCertificateKeyFile: "",
tlsCAFile: "",
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test invalid password", async () => {
const integration = new mongo.integration({
connectionString: getConnectionString({ password: "wrong" }),
db: "",
tlsCertificateFile: "",
tlsCertificateKeyFile: "",
tlsCAFile: "",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: "Authentication failed.",
})
})
it("test invalid username", async () => {
const integration = new mongo.integration({
connectionString: getConnectionString({ user: "wrong" }),
db: "",
tlsCertificateFile: "",
tlsCertificateKeyFile: "",
tlsCAFile: "",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: "Authentication failed.",
})
})
it("test invalid connection", async () => {
const integration = new mongo.integration({
connectionString: getConnectionString({ host: "http://nothinghere" }),
db: "",
tlsCertificateFile: "",
tlsCertificateKeyFile: "",
tlsCAFile: "",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: "Error: getaddrinfo ENOTFOUND http",
})
})
})
})

View File

@ -0,0 +1,65 @@
import { GenericContainer, Wait } from "testcontainers"
import { Duration, TemporalUnit } from "node-duration"
import mssql from "../../../../packages/server/src/integrations/microsoftSqlServer"
jest.unmock("mssql")
describe("datasource validators", () => {
describe("mssql", () => {
let host: string, port: number
const password = "Str0Ng_p@ssW0rd!"
beforeAll(async () => {
const container = await new GenericContainer(
"mcr.microsoft.com/mssql/server"
)
.withExposedPorts(1433)
.withEnv("ACCEPT_EULA", "Y")
.withEnv("MSSQL_SA_PASSWORD", password)
.withEnv("MSSQL_PID", "Developer")
.withWaitStrategy(Wait.forHealthCheck())
.withHealthCheck({
test: `/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "${password}" -Q "SELECT 1" -b -o /dev/null`,
interval: new Duration(1000, TemporalUnit.MILLISECONDS),
timeout: new Duration(3, TemporalUnit.SECONDS),
retries: 20,
startPeriod: new Duration(100, TemporalUnit.MILLISECONDS),
})
.start()
host = container.getContainerIpAddress()
port = container.getMappedPort(1433)
})
it("test valid connection string", async () => {
const integration = new mssql.integration({
user: "sa",
password,
server: host,
port: port,
database: "master",
schema: "dbo",
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test invalid password", async () => {
const integration = new mssql.integration({
user: "sa",
password: "wrong_pwd",
server: host,
port: port,
database: "master",
schema: "dbo",
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: "ConnectionError: Login failed for user 'sa'.",
})
})
})
})

View File

@ -0,0 +1,70 @@
import { GenericContainer } from "testcontainers"
import mysql from "../../../../packages/server/src/integrations/mysql"
jest.unmock("mysql2/promise")
describe("datasource validators", () => {
describe("mysql", () => {
let host: string
let port: number
beforeAll(async () => {
const container = await new GenericContainer("mysql")
.withExposedPorts(3306)
.withEnv("MYSQL_ROOT_PASSWORD", "admin")
.withEnv("MYSQL_DATABASE", "db")
.withEnv("MYSQL_USER", "user")
.withEnv("MYSQL_PASSWORD", "password")
.start()
host = container.getContainerIpAddress()
port = container.getMappedPort(3306)
})
it("test valid connection string", async () => {
const integration = new mysql.integration({
host,
port,
user: "user",
database: "db",
password: "password",
rejectUnauthorized: true,
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test invalid database", async () => {
const integration = new mysql.integration({
host,
port,
user: "user",
database: "test",
password: "password",
rejectUnauthorized: true,
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: "Access denied for user 'user'@'%' to database 'test'",
})
})
it("test invalid password", async () => {
const integration = new mysql.integration({
host,
port,
user: "root",
database: "test",
password: "wrong",
rejectUnauthorized: true,
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error:
"Access denied for user 'root'@'172.17.0.1' (using password: YES)",
})
})
})
})

View File

@ -0,0 +1,53 @@
import { GenericContainer } from "testcontainers"
jest.unmock("pg")
describe("datasource validators", () => {
describe("postgres", () => {
let host: string
let port: number
beforeAll(async () => {
const container = await new GenericContainer("postgres")
.withExposedPorts(5432)
.withEnv("POSTGRES_PASSWORD", "password")
.start()
host = container.getContainerIpAddress()
port = container.getMappedPort(5432)
})
it("test valid connection string", async () => {
const integration = new postgres.integration({
host,
port,
database: "postgres",
user: "postgres",
password: "password",
schema: "public",
ssl: false,
rejectUnauthorized: false,
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test invalid connection string", async () => {
const integration = new postgres.integration({
host,
port,
database: "postgres",
user: "wrong",
password: "password",
schema: "public",
ssl: false,
rejectUnauthorized: false,
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error: 'password authentication failed for user "wrong"',
})
})
})
})

View File

@ -0,0 +1,72 @@
import redis from "../../../../packages/server/src/integrations/redis"
import { GenericContainer } from "testcontainers"
import { generator } from "../../shared"
describe("datasource validators", () => {
describe("redis", () => {
describe("unsecured", () => {
let host: string
let port: number
beforeAll(async () => {
const container = await new GenericContainer("redis")
.withExposedPorts(6379)
.start()
host = container.getContainerIpAddress()
port = container.getMappedPort(6379)
})
it("test valid connection", async () => {
const integration = new redis.integration({
host,
port,
username: "",
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test invalid connection even with wrong user/password", async () => {
const integration = new redis.integration({
host,
port,
username: generator.name(),
password: generator.hash(),
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error:
"WRONGPASS invalid username-password pair or user is disabled.",
})
})
})
describe("secured", () => {
let host: string
let port: number
beforeAll(async () => {
const container = await new GenericContainer("redis")
.withExposedPorts(6379)
.withCmd(["redis-server", "--requirepass", "P@ssW0rd!"])
.start()
host = container.getContainerIpAddress()
port = container.getMappedPort(6379)
})
it("test valid connection", async () => {
const integration = new redis.integration({
host,
port,
username: "",
password: "P@ssW0rd!",
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
})
})
})

View File

@ -0,0 +1,52 @@
import s3 from "../../../../packages/server/src/integrations/s3"
import { GenericContainer } from "testcontainers"
jest.unmock("aws-sdk")
describe("datasource validators", () => {
describe("s3", () => {
let host: string
let port: number
beforeAll(async () => {
const container = await new GenericContainer("localstack/localstack")
.withExposedPorts(4566)
.withEnv("SERVICES", "s3")
.withEnv("DEFAULT_REGION", "eu-west-1")
.withEnv("AWS_ACCESS_KEY_ID", "testkey")
.withEnv("AWS_SECRET_ACCESS_KEY", "testsecret")
.start()
host = container.getContainerIpAddress()
port = container.getMappedPort(4566)
})
it("test valid connection", async () => {
const integration = new s3.integration({
region: "eu-west-1",
accessKeyId: "testkey",
secretAccessKey: "testsecret",
s3ForcePathStyle: false,
endpoint: `http://${host}:${port}`,
})
const result = await integration.testConnection()
expect(result).toEqual({ connected: true })
})
it("test wrong endpoint", async () => {
const integration = new s3.integration({
region: "eu-west-2",
accessKeyId: "testkey",
secretAccessKey: "testsecret",
s3ForcePathStyle: false,
endpoint: `http://wrong:123`,
})
const result = await integration.testConnection()
expect(result).toEqual({
connected: false,
error:
"Inaccessible host: `wrong' at port `undefined'. This service may not be available in the `eu-west-2' region.",
})
})
})
})

View File

@ -1,3 +1,3 @@
const Chance = require("chance") import Chance from "chance"
export default new Chance() export default new Chance()

View File

@ -304,6 +304,11 @@
"@babel/helper-validator-identifier" "^7.18.6" "@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@balena/dockerignore@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d"
integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==
"@bcoe/v8-coverage@^0.2.3": "@bcoe/v8-coverage@^0.2.3":
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@ -779,6 +784,15 @@
request "^2.88.0" request "^2.88.0"
webfinger "^0.4.2" webfinger "^0.4.2"
"@trendyol/jest-testcontainers@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@trendyol/jest-testcontainers/-/jest-testcontainers-2.1.1.tgz#dced95cf9c37b75efe0a65db9b75ae8912f2f14a"
integrity sha512-4iAc2pMsev4BTUzoA7jO1VvbTOU2N3juQUYa8TwiSPXPuQtxKwV9WB9ZEP+JQ+Pj15YqfGOXp5H0WNMPtapjiA==
dependencies:
cwd "^0.10.0"
node-duration "^1.0.4"
testcontainers "4.7.0"
"@tsconfig/node10@^1.0.7": "@tsconfig/node10@^1.0.7":
version "1.0.9" version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
@ -832,6 +846,13 @@
dependencies: dependencies:
"@babel/types" "^7.3.0" "@babel/types" "^7.3.0"
"@types/dockerode@^2.5.34":
version "2.5.34"
resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-2.5.34.tgz#9adb884f7cc6c012a6eb4b2ad794cc5d01439959"
integrity sha512-LcbLGcvcBwBAvjH9UrUI+4qotY+A5WCer5r43DR5XHv2ZIEByNXFdPLo1XxR+v/BjkGjlggW8qUiXuVEhqfkpA==
dependencies:
"@types/node" "*"
"@types/graceful-fs@^4.1.3": "@types/graceful-fs@^4.1.3":
version "4.1.5" version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@ -1006,6 +1027,11 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
any-promise@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
anymatch@^3.0.3: anymatch@^3.0.3:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@ -1044,7 +1070,7 @@ argsarray@0.0.1:
resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb" resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb"
integrity sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg== integrity sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg==
asn1@~0.2.3: asn1@^0.2.6, asn1@~0.2.3:
version "0.2.6" version "0.2.6"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
@ -1199,7 +1225,7 @@ base64url@3.x.x, base64url@^3.0.1:
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
bcrypt-pbkdf@^1.0.0: bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
@ -1304,6 +1330,11 @@ buffer@^5.5.0, buffer@^5.6.0:
base64-js "^1.3.1" base64-js "^1.3.1"
ieee754 "^1.1.13" ieee754 "^1.1.13"
buildcheck@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==
bull@4.10.1: bull@4.10.1:
version "4.10.1" version "4.10.1"
resolved "https://registry.yarnpkg.com/bull/-/bull-4.10.1.tgz#f14974b6089358b62b495a2cbf838aadc098e43f" resolved "https://registry.yarnpkg.com/bull/-/bull-4.10.1.tgz#f14974b6089358b62b495a2cbf838aadc098e43f"
@ -1319,6 +1350,11 @@ bull@4.10.1:
semver "^7.3.2" semver "^7.3.2"
uuid "^8.3.0" uuid "^8.3.0"
byline@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==
cache-content-type@^1.0.0: cache-content-type@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c" resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c"
@ -1546,6 +1582,14 @@ correlation-id@4.0.0:
dependencies: dependencies:
uuid "^8.3.1" uuid "^8.3.1"
cpu-features@~0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.7.tgz#81ba93e1d0a729fd25132a54c3ff689c37b542f7"
integrity sha512-fjzFmsUKKCrC9GrM1eQTvQx18e+kjXFzjRLvJPNEDjk31+bJ6ZiV6uchv/hzbzXVIgbWdrEyyX1IFKwse65+8w==
dependencies:
buildcheck "~0.0.6"
nan "^2.17.0"
create-require@^1.1.0: create-require@^1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
@ -1572,6 +1616,14 @@ crypt@0.0.2:
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
cwd@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/cwd/-/cwd-0.10.0.tgz#172400694057c22a13b0cf16162c7e4b7a7fe567"
integrity sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==
dependencies:
find-pkg "^0.1.2"
fs-exists-sync "^0.1.0"
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@ -1676,6 +1728,32 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
docker-compose@^0.23.5:
version "0.23.19"
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.19.tgz#9947726e2fe67bdfa9e8efe1ff15aa0de2e10eb8"
integrity sha512-v5vNLIdUqwj4my80wxFDkNH+4S85zsRuH29SO7dCWVWPCMt/ohZBsGN6g6KXWifT0pzQ7uOxqEKCYCDPJ8Vz4g==
dependencies:
yaml "^1.10.2"
docker-modem@^3.0.0:
version "3.0.8"
resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-3.0.8.tgz#ef62c8bdff6e8a7d12f0160988c295ea8705e77a"
integrity sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==
dependencies:
debug "^4.1.1"
readable-stream "^3.5.0"
split-ca "^1.0.1"
ssh2 "^1.11.0"
dockerode@^3.2.1:
version "3.3.5"
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629"
integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==
dependencies:
"@balena/dockerignore" "^1.0.2"
docker-modem "^3.0.0"
tar-fs "~2.0.1"
dotenv@16.0.1: dotenv@16.0.1:
version "16.0.1" version "16.0.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
@ -1844,6 +1922,13 @@ exit@^0.1.2:
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
expand-tilde@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
integrity sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==
dependencies:
os-homedir "^1.0.1"
expect@^29.0.0: expect@^29.0.0:
version "29.0.2" version "29.0.2"
resolved "https://registry.yarnpkg.com/expect/-/expect-29.0.2.tgz#22c7132400f60444b427211f1d6bb604a9ab2420" resolved "https://registry.yarnpkg.com/expect/-/expect-29.0.2.tgz#22c7132400f60444b427211f1d6bb604a9ab2420"
@ -1919,6 +2004,21 @@ fill-range@^7.0.1:
dependencies: dependencies:
to-regex-range "^5.0.1" to-regex-range "^5.0.1"
find-file-up@^0.1.2:
version "0.1.3"
resolved "https://registry.yarnpkg.com/find-file-up/-/find-file-up-0.1.3.tgz#cf68091bcf9f300a40da411b37da5cce5a2fbea0"
integrity sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==
dependencies:
fs-exists-sync "^0.1.0"
resolve-dir "^0.1.0"
find-pkg@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/find-pkg/-/find-pkg-0.1.2.tgz#1bdc22c06e36365532e2a248046854b9788da557"
integrity sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==
dependencies:
find-file-up "^0.1.2"
find-up@^4.0.0, find-up@^4.1.0: find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@ -1984,6 +2084,11 @@ fs-constants@^1.0.0:
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-exists-sync@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
integrity sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==
fs-minipass@^2.0.0: fs-minipass@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@ -2062,7 +2167,7 @@ getpass@^0.1.1:
dependencies: dependencies:
assert-plus "^1.0.0" assert-plus "^1.0.0"
glob@^7.1.3, glob@^7.1.4: glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.2.3" version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -2074,6 +2179,24 @@ glob@^7.1.3, glob@^7.1.4:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
global-modules@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d"
integrity sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==
dependencies:
global-prefix "^0.1.4"
is-windows "^0.2.0"
global-prefix@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f"
integrity sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==
dependencies:
homedir-polyfill "^1.0.0"
ini "^1.3.4"
is-windows "^0.2.0"
which "^1.2.12"
globals@^11.1.0: globals@^11.1.0:
version "11.12.0" version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@ -2131,6 +2254,13 @@ has@^1.0.3:
dependencies: dependencies:
function-bind "^1.1.1" function-bind "^1.1.1"
homedir-polyfill@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
dependencies:
parse-passwd "^1.0.0"
html-escaper@^2.0.0: html-escaper@^2.0.0:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
@ -2230,6 +2360,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ini@^1.3.4:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
ioredis@4.28.0: ioredis@4.28.0:
version "4.28.0" version "4.28.0"
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.0.tgz#5a2be3f37ff2075e2332f280eaeb02ab4d9ff0d3" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.0.tgz#5a2be3f37ff2075e2332f280eaeb02ab4d9ff0d3"
@ -2318,6 +2453,11 @@ is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
is-windows@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
integrity sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==
isarray@0.0.1: isarray@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@ -3332,6 +3472,11 @@ msgpackr@^1.5.2:
optionalDependencies: optionalDependencies:
msgpackr-extract "^3.0.0" msgpackr-extract "^3.0.0"
nan@^2.17.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
napi-macros@~2.0.0: napi-macros@~2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
@ -3367,6 +3512,11 @@ node-addon-api@^3.1.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
node-duration@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/node-duration/-/node-duration-1.0.4.tgz#3e94ecc0e473691c89c4560074503362071cecac"
integrity sha512-eUXYNSY7DL53vqfTosggWkvyIW3bhAcqBDIlolgNYlZhianXTrCL50rlUJWD1eRqkIxMppXTfiFbp+9SjpPrgA==
node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.7: node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.7:
version "2.6.7" version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
@ -3490,6 +3640,11 @@ only@~0.0.2:
resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ== integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==
os-homedir@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==
p-finally@^1.0.0: p-finally@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@ -3543,6 +3698,11 @@ parse-json@^5.2.0:
json-parse-even-better-errors "^2.3.0" json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6" lines-and-columns "^1.1.6"
parse-passwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==
parseurl@^1.3.2, parseurl@^1.3.3: parseurl@^1.3.2, parseurl@^1.3.3:
version "1.3.3" version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@ -3982,6 +4142,15 @@ readable-stream@1.1.14, readable-stream@^1.0.27-1:
string_decoder "^1.1.1" string_decoder "^1.1.1"
util-deprecate "^1.0.1" util-deprecate "^1.0.1"
readable-stream@^3.5.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@~0.0.2: readable-stream@~0.0.2:
version "0.0.4" version "0.0.4"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-0.0.4.tgz#f32d76e3fb863344a548d79923007173665b3b8d" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-0.0.4.tgz#f32d76e3fb863344a548d79923007173665b3b8d"
@ -4077,6 +4246,14 @@ resolve-cwd@^3.0.0:
dependencies: dependencies:
resolve-from "^5.0.0" resolve-from "^5.0.0"
resolve-dir@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e"
integrity sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==
dependencies:
expand-tilde "^1.2.2"
global-modules "^0.2.3"
resolve-from@^5.0.0: resolve-from@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
@ -4238,6 +4415,11 @@ spark-md5@3.0.2:
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc"
integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==
split-ca@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6"
integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==
split2@^2.1.0: split2@^2.1.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493"
@ -4257,6 +4439,17 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
ssh2@^1.11.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.13.0.tgz#9b53a07534fa72283ada471b82395a3b3c875934"
integrity sha512-CIZBFRRY1y9mAZSqBGFE4EB4dNJad2ysT2PqO8OpkiI3UTB/gUZwE5EaN16qVyQ6s/M7EgC/iaV/MnjdlvnuzA==
dependencies:
asn1 "^0.2.6"
bcrypt-pbkdf "^1.0.2"
optionalDependencies:
cpu-features "~0.0.7"
nan "^2.17.0"
sshpk@^1.7.0: sshpk@^1.7.0:
version "1.17.0" version "1.17.0"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
@ -4314,6 +4507,13 @@ stream-combiner@~0.0.4:
dependencies: dependencies:
duplexer "~0.1.1" duplexer "~0.1.1"
stream-to-array@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353"
integrity sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==
dependencies:
any-promise "^1.1.0"
string-length@^4.0.1: string-length@^4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
@ -4403,7 +4603,7 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
tar-fs@2.1.1: tar-fs@2.1.1, tar-fs@^2.1.0:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
@ -4413,7 +4613,17 @@ tar-fs@2.1.1:
pump "^3.0.0" pump "^3.0.0"
tar-stream "^2.1.4" tar-stream "^2.1.4"
tar-stream@^2.1.4: tar-fs@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2"
integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.0.0"
tar-stream@^2.0.0, tar-stream@^2.1.4:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
@ -4445,6 +4655,23 @@ test-exclude@^6.0.0:
glob "^7.1.4" glob "^7.1.4"
minimatch "^3.0.4" minimatch "^3.0.4"
testcontainers@4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-4.7.0.tgz#5a9a864b1b0cc86984086dcc737c2f5e73490cf3"
integrity sha512-5SrG9RMfDRRZig34fDZeMcGD5i3lHCOJzn0kjouyK4TiEWjZB3h7kCk8524lwNRHROFE1j6DGjceonv/5hl5ag==
dependencies:
"@types/dockerode" "^2.5.34"
byline "^5.0.0"
debug "^4.1.1"
docker-compose "^0.23.5"
dockerode "^3.2.1"
get-port "^5.1.1"
glob "^7.1.6"
node-duration "^1.0.4"
slash "^3.0.0"
stream-to-array "^2.3.0"
tar-fs "^2.1.0"
through2@3.0.2: through2@3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4"
@ -4746,6 +4973,13 @@ whatwg-url@^5.0.0:
tr46 "~0.0.3" tr46 "~0.0.3"
webidl-conversions "^3.0.0" webidl-conversions "^3.0.0"
which@^1.2.12:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
which@^2.0.1: which@^2.0.1:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
@ -4824,6 +5058,11 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^21.0.0, yargs-parser@^21.0.1: yargs-parser@^21.0.0, yargs-parser@^21.0.1:
version "21.1.1" version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"

View File

@ -5316,6 +5316,15 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/pg@8.6.6":
version "8.6.6"
resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.6.tgz#21cdf873a3e345a6e78f394677e3b3b1b543cb80"
integrity sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==
dependencies:
"@types/node" "*"
pg-protocol "*"
pg-types "^2.2.0"
"@types/pouchdb-adapter-cordova-sqlite@*": "@types/pouchdb-adapter-cordova-sqlite@*":
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-cordova-sqlite/-/pouchdb-adapter-cordova-sqlite-1.0.1.tgz#49e5ee6df7cc0c23196fcb340f43a560e74eb1d6" resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-cordova-sqlite/-/pouchdb-adapter-cordova-sqlite-1.0.1.tgz#49e5ee6df7cc0c23196fcb340f43a560e74eb1d6"
@ -19195,7 +19204,7 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
pg-connection-string@2.5.0, pg-connection-string@^2.4.0: pg-connection-string@2.5.0, pg-connection-string@^2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
@ -19205,17 +19214,17 @@ pg-int8@1.0.1:
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
pg-pool@^3.2.2: pg-pool@^3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e" resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e"
integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ== integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==
pg-protocol@^1.4.0: pg-protocol@*, pg-protocol@^1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833"
integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==
pg-types@^2.1.0: pg-types@^2.1.0, pg-types@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
@ -19226,16 +19235,16 @@ pg-types@^2.1.0:
postgres-date "~1.0.4" postgres-date "~1.0.4"
postgres-interval "^1.1.0" postgres-interval "^1.1.0"
pg@8.5.1: pg@8.10.0:
version "8.5.1" version "8.10.0"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.5.1.tgz#34dcb15f6db4a29c702bf5031ef2e1e25a06a120" resolved "https://registry.yarnpkg.com/pg/-/pg-8.10.0.tgz#5b8379c9b4a36451d110fc8cd98fc325fe62ad24"
integrity sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw== integrity sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==
dependencies: dependencies:
buffer-writer "2.0.0" buffer-writer "2.0.0"
packet-reader "1.0.0" packet-reader "1.0.0"
pg-connection-string "^2.4.0" pg-connection-string "^2.5.0"
pg-pool "^3.2.2" pg-pool "^3.6.0"
pg-protocol "^1.4.0" pg-protocol "^1.6.0"
pg-types "^2.1.0" pg-types "^2.1.0"
pgpass "1.x" pgpass "1.x"
@ -23066,7 +23075,14 @@ svelte-dnd-action@^0.9.8:
resolved "https://registry.yarnpkg.com/svelte-dnd-action/-/svelte-dnd-action-0.9.22.tgz#003eee9dddb31d8c782f6832aec8b1507fff194d" resolved "https://registry.yarnpkg.com/svelte-dnd-action/-/svelte-dnd-action-0.9.22.tgz#003eee9dddb31d8c782f6832aec8b1507fff194d"
integrity sha512-lOQJsNLM1QWv5mdxIkCVtk6k4lHCtLgfE59y8rs7iOM6erchbLC9hMEFYSveZz7biJV0mpg7yDSs4bj/RT/YkA== integrity sha512-lOQJsNLM1QWv5mdxIkCVtk6k4lHCtLgfE59y8rs7iOM6erchbLC9hMEFYSveZz7biJV0mpg7yDSs4bj/RT/YkA==
svelte-flatpickr@^3.1.0, svelte-flatpickr@^3.2.3, svelte-flatpickr@^3.3.2: svelte-flatpickr@3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.2.3.tgz#db5dd7ad832ef83262b45e09737955ad3d591fc8"
integrity sha512-PNkqK4Napx8nTvCwkaUXdnKo8dISThaxEOK+szTUXcY6H0dQM0TSyuoMaVWY2yX7pM+PN5cpCQCcVe8YvTRFSw==
dependencies:
flatpickr "^4.5.2"
svelte-flatpickr@^3.1.0, svelte-flatpickr@^3.2.3:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.3.2.tgz#f08bcde83d439cb30df6fd07b974d87371f130c1" resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.3.2.tgz#f08bcde83d439cb30df6fd07b974d87371f130c1"
integrity sha512-VNJLYyLRDplI63oWX5hJylzAJc2VhTh3z9SNecfjtuPZmP6FZPpg9Fw7rXpkEV2DPovIWj2PtaVxB6Kp9r423w== integrity sha512-VNJLYyLRDplI63oWX5hJylzAJc2VhTh3z9SNecfjtuPZmP6FZPpg9Fw7rXpkEV2DPovIWj2PtaVxB6Kp9r423w==