Merge branch 'feature/sql-query-aliasing' of github.com:Budibase/budibase into labday/sqs
This commit is contained in:
commit
2d75e1e85c
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.20.10",
|
"version": "2.20.12",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Plugin } from "@budibase/types"
|
||||||
|
|
||||||
// URLS
|
// URLS
|
||||||
|
|
||||||
export function enrichPluginURLs(plugins: Plugin[]) {
|
export function enrichPluginURLs(plugins?: Plugin[]): Plugin[] {
|
||||||
if (!plugins || !plugins.length) {
|
if (!plugins || !plugins.length) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, Select, Body, Multiselect } from "@budibase/bbui"
|
import { Label, Select, Body } from "@budibase/bbui"
|
||||||
import { findAllMatchingComponents, findComponent } from "helpers/components"
|
|
||||||
import { selectedScreen } from "stores/builder"
|
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
|
import ColumnEditor from "../../ColumnEditor/ColumnEditor.svelte"
|
||||||
|
import { findAllMatchingComponents } from "helpers/components"
|
||||||
|
import { selectedScreen } from "stores/builder"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
|
||||||
|
@ -18,37 +18,65 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const DELIMITERS = [
|
||||||
|
{
|
||||||
|
label: ",",
|
||||||
|
value: ",",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ";",
|
||||||
|
value: ";",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ":",
|
||||||
|
value: ":",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "|",
|
||||||
|
value: "|",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "~",
|
||||||
|
value: "~",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "[tab]",
|
||||||
|
value: "\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "[space]",
|
||||||
|
value: " ",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
$: tables = findAllMatchingComponents($selectedScreen?.props, component =>
|
$: tables = findAllMatchingComponents($selectedScreen?.props, component =>
|
||||||
component._component.endsWith("table")
|
component._component.endsWith("table")
|
||||||
).map(table => ({
|
)
|
||||||
label: table._instanceName,
|
|
||||||
value: table._id,
|
|
||||||
}))
|
|
||||||
$: tableBlocks = findAllMatchingComponents(
|
$: tableBlocks = findAllMatchingComponents(
|
||||||
$selectedScreen?.props,
|
$selectedScreen?.props,
|
||||||
component => component._component.endsWith("tableblock")
|
component => component._component.endsWith("tableblock")
|
||||||
).map(block => ({
|
)
|
||||||
label: block._instanceName,
|
$: components = tables.concat(tableBlocks)
|
||||||
value: `${block._id}-table`,
|
$: componentOptions = components.map(table => ({
|
||||||
|
label: table._instanceName,
|
||||||
|
value: table._component.includes("tableblock")
|
||||||
|
? `${table._id}-table`
|
||||||
|
: table._id,
|
||||||
}))
|
}))
|
||||||
$: componentOptions = tables.concat(tableBlocks)
|
$: selectedTableId = parameters.tableComponentId?.includes("-")
|
||||||
$: columnOptions = getColumnOptions(parameters.tableComponentId)
|
? parameters.tableComponentId.split("-")[0]
|
||||||
|
: parameters.tableComponentId
|
||||||
const getColumnOptions = tableId => {
|
$: selectedTable = components.find(
|
||||||
// Strip block suffix if block component
|
component => component._id === selectedTableId
|
||||||
if (tableId?.includes("-")) {
|
)
|
||||||
tableId = tableId.split("-")[0]
|
|
||||||
}
|
|
||||||
const selectedTable = findComponent($selectedScreen?.props, tableId)
|
|
||||||
const datasource = getDatasourceForProvider($selectedScreen, selectedTable)
|
|
||||||
const { schema } = getSchemaForDatasource($selectedScreen, datasource)
|
|
||||||
return Object.keys(schema || {})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!parameters.type) {
|
if (!parameters.type) {
|
||||||
parameters.type = "csv"
|
parameters.type = "csv"
|
||||||
}
|
}
|
||||||
|
if (!parameters.delimiter) {
|
||||||
|
parameters.delimiter = ","
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -67,13 +95,30 @@
|
||||||
options={componentOptions}
|
options={componentOptions}
|
||||||
on:change={() => (parameters.columns = [])}
|
on:change={() => (parameters.columns = [])}
|
||||||
/>
|
/>
|
||||||
|
<span />
|
||||||
<Label small>Export as</Label>
|
<Label small>Export as</Label>
|
||||||
<Select bind:value={parameters.type} options={FORMATS} />
|
<Select bind:value={parameters.type} options={FORMATS} />
|
||||||
|
<Select
|
||||||
|
bind:value={parameters.delimiter}
|
||||||
|
placeholder={null}
|
||||||
|
options={DELIMITERS}
|
||||||
|
disabled={parameters.type !== "csv"}
|
||||||
|
/>
|
||||||
<Label small>Export columns</Label>
|
<Label small>Export columns</Label>
|
||||||
<Multiselect
|
<ColumnEditor
|
||||||
placeholder="All columns"
|
value={parameters.columns}
|
||||||
bind:value={parameters.columns}
|
allowCellEditing={false}
|
||||||
options={columnOptions}
|
componentInstance={selectedTable}
|
||||||
|
on:change={e => {
|
||||||
|
const columns = e.detail
|
||||||
|
parameters.columns = columns
|
||||||
|
parameters.customHeaders = columns.reduce((headerMap, column) => {
|
||||||
|
return {
|
||||||
|
[column.name]: column.displayName,
|
||||||
|
...headerMap,
|
||||||
|
}
|
||||||
|
}, {})
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,8 +142,8 @@
|
||||||
.params {
|
.params {
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: var(--spacing-xs);
|
column-gap: var(--spacing-xs);
|
||||||
row-gap: var(--spacing-s);
|
row-gap: var(--spacing-m);
|
||||||
grid-template-columns: 90px 1fr;
|
grid-template-columns: 90px 1fr 90px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -29,6 +29,12 @@
|
||||||
allowLinks: true,
|
allowLinks: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$: {
|
||||||
|
value = (value || []).filter(
|
||||||
|
column => (schema || {})[column.name || column] !== undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const getText = value => {
|
const getText = value => {
|
||||||
if (!value?.length) {
|
if (!value?.length) {
|
||||||
return "All columns"
|
return "All columns"
|
||||||
|
|
|
@ -17,6 +17,10 @@ export function breakQueryString(qs) {
|
||||||
return paramObj
|
return paramObj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEncoded(str) {
|
||||||
|
return typeof str == "string" && decodeURIComponent(str) !== str
|
||||||
|
}
|
||||||
|
|
||||||
export function buildQueryString(obj) {
|
export function buildQueryString(obj) {
|
||||||
let str = ""
|
let str = ""
|
||||||
if (obj) {
|
if (obj) {
|
||||||
|
@ -35,7 +39,7 @@ export function buildQueryString(obj) {
|
||||||
value = value.replace(binding, marker)
|
value = value.replace(binding, marker)
|
||||||
bindingMarkers[marker] = binding
|
bindingMarkers[marker] = binding
|
||||||
})
|
})
|
||||||
let encoded = encodeURIComponent(value || "")
|
let encoded = isEncoded(value) ? value : encodeURIComponent(value || "")
|
||||||
Object.entries(bindingMarkers).forEach(([marker, binding]) => {
|
Object.entries(bindingMarkers).forEach(([marker, binding]) => {
|
||||||
encoded = encoded.replace(marker, binding)
|
encoded = encoded.replace(marker, binding)
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,4 +39,11 @@ describe("check query string utils", () => {
|
||||||
expect(broken.key1).toBe(obj2.key1)
|
expect(broken.key1).toBe(obj2.key1)
|
||||||
expect(broken.key2).toBe(obj2.key2)
|
expect(broken.key2).toBe(obj2.key2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should not encode a URL more than once when building the query string", () => {
|
||||||
|
const queryString = buildQueryString({
|
||||||
|
values: "a%2Cb%2Cc",
|
||||||
|
})
|
||||||
|
expect(queryString).toBe("values=a%2Cb%2Cc")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,17 +12,11 @@
|
||||||
hoverStore,
|
hoverStore,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import {
|
import { Layout, Heading, Body, Icon, notifications } from "@budibase/bbui"
|
||||||
ProgressCircle,
|
|
||||||
Layout,
|
|
||||||
Heading,
|
|
||||||
Body,
|
|
||||||
Icon,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw"
|
import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw"
|
||||||
import { findComponent, findComponentPath } from "helpers/components"
|
import { findComponent, findComponentPath } from "helpers/components"
|
||||||
import { isActive, goto } from "@roxi/routify"
|
import { isActive, goto } from "@roxi/routify"
|
||||||
|
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
||||||
|
|
||||||
let iframe
|
let iframe
|
||||||
let layout
|
let layout
|
||||||
|
@ -240,8 +234,16 @@
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="component-container">
|
<div class="component-container">
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="center">
|
<div
|
||||||
<ProgressCircle />
|
class={`loading ${$builderStore.theme}`}
|
||||||
|
class:tablet={$previewStore.previewDevice === "tablet"}
|
||||||
|
class:mobile={$previewStore.previewDevice === "mobile"}
|
||||||
|
>
|
||||||
|
<ClientAppSkeleton
|
||||||
|
sideNav={$builderStore.navigation?.navigation === "Left"}
|
||||||
|
hideFooter
|
||||||
|
hideDevTools
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<div class="center error">
|
<div class="center error">
|
||||||
|
@ -258,8 +260,6 @@
|
||||||
bind:this={iframe}
|
bind:this={iframe}
|
||||||
src="/app/preview"
|
src="/app/preview"
|
||||||
class:hidden={loading || error}
|
class:hidden={loading || error}
|
||||||
class:tablet={$previewStore.previewDevice === "tablet"}
|
|
||||||
class:mobile={$previewStore.previewDevice === "mobile"}
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="add-component"
|
class="add-component"
|
||||||
|
@ -279,6 +279,25 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.loading {
|
||||||
|
position: absolute;
|
||||||
|
container-type: inline-size;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading.tablet {
|
||||||
|
width: calc(1024px + 6px);
|
||||||
|
max-height: calc(768px + 6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading.mobile {
|
||||||
|
width: calc(390px + 6px);
|
||||||
|
max-height: calc(844px + 6px);
|
||||||
|
}
|
||||||
|
|
||||||
.component-container {
|
.component-container {
|
||||||
grid-row-start: middle;
|
grid-row-start: middle;
|
||||||
grid-column-start: middle;
|
grid-column-start: middle;
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { params, goto } from "@roxi/routify"
|
import { params, goto } from "@roxi/routify"
|
||||||
import { apps, auth, sideBarCollapsed } from "stores/portal"
|
import { licensing, apps, auth, sideBarCollapsed } from "stores/portal"
|
||||||
import { Link, Body, ActionButton } from "@budibase/bbui"
|
import { Link, Body, ActionButton } from "@budibase/bbui"
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import ErrorSVG from "./ErrorSVG.svelte"
|
import ErrorSVG from "./ErrorSVG.svelte"
|
||||||
|
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
||||||
|
|
||||||
$: app = $apps.find(app => app.appId === $params.appId)
|
$: app = $apps.find(app => app.appId === $params.appId)
|
||||||
$: iframeUrl = getIframeURL(app)
|
$: iframeUrl = getIframeURL(app)
|
||||||
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
|
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
|
||||||
|
|
||||||
|
let loading = true
|
||||||
|
|
||||||
const getIframeURL = app => {
|
const getIframeURL = app => {
|
||||||
|
loading = true
|
||||||
|
|
||||||
if (app.status === "published") {
|
if (app.status === "published") {
|
||||||
return `/app${app.url}`
|
return `/app${app.url}`
|
||||||
}
|
}
|
||||||
|
@ -28,6 +34,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: fetchScreens(app?.devId)
|
$: fetchScreens(app?.devId)
|
||||||
|
|
||||||
|
const receiveMessage = async message => {
|
||||||
|
if (message.data.type === "docLoaded") {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener("message", receiveMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
window.removeEventListener("message", receiveMessage)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -78,7 +98,17 @@
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<iframe src={iframeUrl} title={app.name} />
|
<div class:hide={!loading} class="loading">
|
||||||
|
<div class={`loadingThemeWrapper ${app.theme}`}>
|
||||||
|
<ClientAppSkeleton
|
||||||
|
noAnimation
|
||||||
|
hideDevTools={app?.status === "published"}
|
||||||
|
sideNav={app?.navigation.navigation === "Left"}
|
||||||
|
hideFooter={$licensing.brandingEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<iframe class:hide={loading} src={iframeUrl} title={app.name} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -100,6 +130,23 @@
|
||||||
flex: 0 0 50px;
|
flex: 0 0 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: var(--spacing-s);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.loadingThemeWrapper {
|
||||||
|
height: 100%;
|
||||||
|
container-type: inline-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
visibility: hidden;
|
||||||
|
height: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
border-radius: var(--spacing-s);
|
border-radius: var(--spacing-s);
|
||||||
|
|
|
@ -80,11 +80,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fontsLoaded = false
|
||||||
|
|
||||||
// Load app config
|
// Load app config
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
document.fonts.ready.then(() => {
|
||||||
|
fontsLoaded = true
|
||||||
|
})
|
||||||
|
|
||||||
await initialise()
|
await initialise()
|
||||||
await authStore.actions.fetchUser()
|
await authStore.actions.fetchUser()
|
||||||
dataLoaded = true
|
dataLoaded = true
|
||||||
|
|
||||||
if (get(builderStore).inBuilder) {
|
if (get(builderStore).inBuilder) {
|
||||||
builderStore.actions.notifyLoaded()
|
builderStore.actions.notifyLoaded()
|
||||||
} else {
|
} else {
|
||||||
|
@ -93,6 +100,12 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (dataLoaded && fontsLoaded) {
|
||||||
|
document.getElementById("clientAppSkeletonLoader")?.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -103,140 +116,140 @@
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{#if dataLoaded}
|
<div
|
||||||
<div
|
id="spectrum-root"
|
||||||
id="spectrum-root"
|
lang="en"
|
||||||
lang="en"
|
dir="ltr"
|
||||||
dir="ltr"
|
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
||||||
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
class:builder={$builderStore.inBuilder}
|
||||||
class:builder={$builderStore.inBuilder}
|
class:show={fontsLoaded && dataLoaded}
|
||||||
>
|
>
|
||||||
<DeviceBindingsProvider>
|
<DeviceBindingsProvider>
|
||||||
<UserBindingsProvider>
|
<UserBindingsProvider>
|
||||||
<StateBindingsProvider>
|
<StateBindingsProvider>
|
||||||
<RowSelectionProvider>
|
<RowSelectionProvider>
|
||||||
<QueryParamsProvider>
|
<QueryParamsProvider>
|
||||||
<!-- Settings bar can be rendered outside of device preview -->
|
<!-- Settings bar can be rendered outside of device preview -->
|
||||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||||
{#key $builderStore.selectedComponentId}
|
{#key $builderStore.selectedComponentId}
|
||||||
{#if $builderStore.inBuilder}
|
{#if $builderStore.inBuilder}
|
||||||
<SettingsBar />
|
<SettingsBar />
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<!-- Clip boundary for selection indicators -->
|
<!-- Clip boundary for selection indicators -->
|
||||||
<div
|
<div
|
||||||
id="clip-root"
|
id="clip-root"
|
||||||
class:preview={$builderStore.inBuilder}
|
class:preview={$builderStore.inBuilder}
|
||||||
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
||||||
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
||||||
>
|
>
|
||||||
<!-- Actual app -->
|
<!-- Actual app -->
|
||||||
<div id="app-root">
|
<div id="app-root">
|
||||||
{#if showDevTools}
|
{#if showDevTools}
|
||||||
<DevToolsHeader />
|
<DevToolsHeader />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div id="app-body">
|
||||||
|
{#if permissionError}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
You don't have permission to use this app
|
||||||
|
</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Ask your administrator to grant you access
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else if !$screenStore.activeLayout}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
Something went wrong rendering your app
|
||||||
|
</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Get in touch with support if this issue persists
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else if embedNoScreens}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
This Budibase app is not publicly accessible
|
||||||
|
</Heading>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<CustomThemeWrapper>
|
||||||
|
{#key $screenStore.activeLayout._id}
|
||||||
|
<Component
|
||||||
|
isLayout
|
||||||
|
instance={$screenStore.activeLayout.props}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Flatpickr needs to be inside the theme wrapper.
|
||||||
|
It also needs its own container because otherwise it hijacks
|
||||||
|
key events on the whole page. It is painful to work with.
|
||||||
|
-->
|
||||||
|
<div id="flatpickr-root" />
|
||||||
|
|
||||||
|
<!-- Modal container to ensure they sit on top -->
|
||||||
|
<div class="modal-container" />
|
||||||
|
|
||||||
|
<!-- Layers on top of app -->
|
||||||
|
<NotificationDisplay />
|
||||||
|
<ConfirmationDisplay />
|
||||||
|
<PeekScreenDisplay />
|
||||||
|
</CustomThemeWrapper>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div id="app-body">
|
{#if showDevTools}
|
||||||
{#if permissionError}
|
<DevTools />
|
||||||
<div class="error">
|
|
||||||
<Layout justifyItems="center" gap="S">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html ErrorSVG}
|
|
||||||
<Heading size="L">
|
|
||||||
You don't have permission to use this app
|
|
||||||
</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Ask your administrator to grant you access
|
|
||||||
</Body>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{:else if !$screenStore.activeLayout}
|
|
||||||
<div class="error">
|
|
||||||
<Layout justifyItems="center" gap="S">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html ErrorSVG}
|
|
||||||
<Heading size="L">
|
|
||||||
Something went wrong rendering your app
|
|
||||||
</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Get in touch with support if this issue persists
|
|
||||||
</Body>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{:else if embedNoScreens}
|
|
||||||
<div class="error">
|
|
||||||
<Layout justifyItems="center" gap="S">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html ErrorSVG}
|
|
||||||
<Heading size="L">
|
|
||||||
This Budibase app is not publicly accessible
|
|
||||||
</Heading>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<CustomThemeWrapper>
|
|
||||||
{#key $screenStore.activeLayout._id}
|
|
||||||
<Component
|
|
||||||
isLayout
|
|
||||||
instance={$screenStore.activeLayout.props}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Flatpickr needs to be inside the theme wrapper.
|
|
||||||
It also needs its own container because otherwise it hijacks
|
|
||||||
key events on the whole page. It is painful to work with.
|
|
||||||
-->
|
|
||||||
<div id="flatpickr-root" />
|
|
||||||
|
|
||||||
<!-- Modal container to ensure they sit on top -->
|
|
||||||
<div class="modal-container" />
|
|
||||||
|
|
||||||
<!-- Layers on top of app -->
|
|
||||||
<NotificationDisplay />
|
|
||||||
<ConfirmationDisplay />
|
|
||||||
<PeekScreenDisplay />
|
|
||||||
</CustomThemeWrapper>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if showDevTools}
|
|
||||||
<DevTools />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !$builderStore.inBuilder && licensing.logoEnabled()}
|
|
||||||
<FreeFooter />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Preview and dev tools utilities -->
|
{#if !$builderStore.inBuilder && licensing.logoEnabled()}
|
||||||
{#if $appStore.isDevApp}
|
<FreeFooter />
|
||||||
<SelectionIndicator />
|
|
||||||
{/if}
|
|
||||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
|
||||||
<HoverIndicator />
|
|
||||||
{/if}
|
|
||||||
{#if $builderStore.inBuilder}
|
|
||||||
<DNDHandler />
|
|
||||||
<GridDNDHandler />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</QueryParamsProvider>
|
|
||||||
</RowSelectionProvider>
|
<!-- Preview and dev tools utilities -->
|
||||||
</StateBindingsProvider>
|
{#if $appStore.isDevApp}
|
||||||
</UserBindingsProvider>
|
<SelectionIndicator />
|
||||||
</DeviceBindingsProvider>
|
{/if}
|
||||||
</div>
|
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||||
<KeyboardManager />
|
<HoverIndicator />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $builderStore.inBuilder}
|
||||||
|
<DNDHandler />
|
||||||
|
<GridDNDHandler />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</QueryParamsProvider>
|
||||||
|
</RowSelectionProvider>
|
||||||
|
</StateBindingsProvider>
|
||||||
|
</UserBindingsProvider>
|
||||||
|
</DeviceBindingsProvider>
|
||||||
|
</div>
|
||||||
|
<KeyboardManager />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#spectrum-root {
|
#spectrum-root {
|
||||||
|
height: 0;
|
||||||
|
visibility: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -257,6 +270,11 @@
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#spectrum-root.show {
|
||||||
|
height: 100%;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
#app-root {
|
#app-root {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.free-footer {
|
.free-footer {
|
||||||
|
min-height: 51px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-top: 1px solid var(--spectrum-global-color-gray-300);
|
border-top: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
|
|
@ -341,7 +341,11 @@ const exportDataHandler = async action => {
|
||||||
tableId: selection.tableId,
|
tableId: selection.tableId,
|
||||||
rows: selection.selectedRows,
|
rows: selection.selectedRows,
|
||||||
format: action.parameters.type,
|
format: action.parameters.type,
|
||||||
columns: action.parameters.columns,
|
columns: action.parameters.columns?.map(
|
||||||
|
column => column.name || column
|
||||||
|
),
|
||||||
|
delimiter: action.parameters.delimiter,
|
||||||
|
customHeaders: action.parameters.customHeaders,
|
||||||
})
|
})
|
||||||
download(
|
download(
|
||||||
new Blob([data], { type: "text/plain" }),
|
new Blob([data], { type: "text/plain" }),
|
||||||
|
|
|
@ -89,13 +89,24 @@ export const buildRowEndpoints = API => ({
|
||||||
* @param rows the array of rows to export
|
* @param rows the array of rows to export
|
||||||
* @param format the format to export (csv or json)
|
* @param format the format to export (csv or json)
|
||||||
* @param columns which columns to export (all if undefined)
|
* @param columns which columns to export (all if undefined)
|
||||||
|
* @param delimiter how values should be separated in a CSV (default is comma)
|
||||||
*/
|
*/
|
||||||
exportRows: async ({ tableId, rows, format, columns, search }) => {
|
exportRows: async ({
|
||||||
|
tableId,
|
||||||
|
rows,
|
||||||
|
format,
|
||||||
|
columns,
|
||||||
|
search,
|
||||||
|
delimiter,
|
||||||
|
customHeaders,
|
||||||
|
}) => {
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: `/api/${tableId}/rows/exportRows?format=${format}`,
|
url: `/api/${tableId}/rows/exportRows?format=${format}`,
|
||||||
body: {
|
body: {
|
||||||
rows,
|
rows,
|
||||||
columns,
|
columns,
|
||||||
|
delimiter,
|
||||||
|
customHeaders,
|
||||||
...search,
|
...search,
|
||||||
},
|
},
|
||||||
parseResponse: async response => {
|
parseResponse: async response => {
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
<script>
|
||||||
|
export let sideNav = false
|
||||||
|
export let hideDevTools = false
|
||||||
|
export let hideFooter = false
|
||||||
|
export let noAnimation = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class:sideNav id="clientAppSkeletonLoader" class="skeleton">
|
||||||
|
<div class="animation" class:noAnimation />
|
||||||
|
|
||||||
|
{#if !hideDevTools}
|
||||||
|
<div class="devTools" />
|
||||||
|
{/if}
|
||||||
|
<div class="main">
|
||||||
|
<div class="nav" />
|
||||||
|
<div class="body">
|
||||||
|
<div class="bodyVerticalPadding" />
|
||||||
|
<div class="bodyHorizontal">
|
||||||
|
<div class="bodyHorizontalPadding" />
|
||||||
|
<svg
|
||||||
|
class="svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="240"
|
||||||
|
height="256"
|
||||||
|
>
|
||||||
|
<mask id="mask">
|
||||||
|
<rect x="0" y="0" width="240" height="256" fill="white" />
|
||||||
|
<rect x="0" y="0" width="240" height="32" rx="6" fill="black" />
|
||||||
|
<rect x="0" y="56" width="240" height="32" rx="6" fill="black" />
|
||||||
|
<rect x="0" y="112" width="240" height="32" rx="6" fill="black" />
|
||||||
|
<rect x="0" y="168" width="240" height="32" rx="6" fill="black" />
|
||||||
|
<rect x="71" y="224" width="98" height="32" rx="6" fill="black" />
|
||||||
|
</mask>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="240"
|
||||||
|
height="256"
|
||||||
|
fill="black"
|
||||||
|
mask="url(#mask)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="bodyHorizontalPadding" />
|
||||||
|
</div>
|
||||||
|
<div class="bodyVerticalPadding" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if !hideFooter}
|
||||||
|
<div class="footer" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.skeleton {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent 0%,
|
||||||
|
var(--spectrum-global-color-gray-300) 20%,
|
||||||
|
transparent 40%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
animation-duration: 1.3s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: shimmer;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noAnimation {
|
||||||
|
animation-name: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.devTools {
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: black;
|
||||||
|
height: 60px;
|
||||||
|
padding: 1px 24px 1px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
background: rgb(0 0 0);
|
||||||
|
font-size: 30px;
|
||||||
|
font-family: Source Sans Pro;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .main {
|
||||||
|
flex-direction: column;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .main {
|
||||||
|
flex-direction: column;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav .main {
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 141px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .nav {
|
||||||
|
height: 61px;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .nav {
|
||||||
|
height: 61px;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav .nav {
|
||||||
|
height: 100%;
|
||||||
|
width: 251px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .body {
|
||||||
|
width: initial;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .body {
|
||||||
|
width: initial;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav .body {
|
||||||
|
width: 100%;
|
||||||
|
height: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body :global(svg > rect) {
|
||||||
|
fill: var(--spectrum-alias-background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body :global(svg) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bodyHorizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bodyHorizontalPadding {
|
||||||
|
height: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: var(--spectrum-alias-background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bodyVerticalPadding {
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: var(--spectrum-alias-background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 1;
|
||||||
|
height: 52px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .footer {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .footer {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav .footer {
|
||||||
|
border-top: 3px solid var(--spectrum-alias-background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
left: -170%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
left: 170%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -5,3 +5,4 @@ export { default as UserAvatar } from "./UserAvatar.svelte"
|
||||||
export { default as UserAvatars } from "./UserAvatars.svelte"
|
export { default as UserAvatars } from "./UserAvatars.svelte"
|
||||||
export { default as Updating } from "./Updating.svelte"
|
export { default as Updating } from "./Updating.svelte"
|
||||||
export { Grid } from "./grid"
|
export { Grid } from "./grid"
|
||||||
|
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
||||||
|
|
|
@ -17,5 +17,8 @@
|
||||||
--modal-background: var(--spectrum-global-color-gray-50);
|
--modal-background: var(--spectrum-global-color-gray-50);
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.25) !important;
|
--drop-shadow: rgba(0, 0, 0, 0.25) !important;
|
||||||
--spectrum-global-color-blue-100: rgba(35, 40, 50) !important;
|
--spectrum-global-color-blue-100: rgba(35, 40, 50) !important;
|
||||||
|
|
||||||
|
--spectrum-alias-background-color-secondary: var(--spectrum-global-color-gray-75);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,4 +50,7 @@
|
||||||
--modal-background: var(--spectrum-global-color-gray-50);
|
--modal-background: var(--spectrum-global-color-gray-50);
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.15) !important;
|
--drop-shadow: rgba(0, 0, 0, 0.15) !important;
|
||||||
--spectrum-global-color-blue-100: rgb(56, 65, 84) !important;
|
--spectrum-global-color-blue-100: rgb(56, 65, 84) !important;
|
||||||
|
|
||||||
|
--spectrum-alias-background-color-secondary: var(--spectrum-global-color-gray-75);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
"@budibase/pro": "0.0.0",
|
"@budibase/pro": "0.0.0",
|
||||||
"@budibase/shared-core": "0.0.0",
|
"@budibase/shared-core": "0.0.0",
|
||||||
"@budibase/string-templates": "0.0.0",
|
"@budibase/string-templates": "0.0.0",
|
||||||
|
"@budibase/frontend-core": "0.0.0",
|
||||||
"@budibase/types": "0.0.0",
|
"@budibase/types": "0.0.0",
|
||||||
"@bull-board/api": "5.10.2",
|
"@bull-board/api": "5.10.2",
|
||||||
"@bull-board/koa": "5.10.2",
|
"@bull-board/koa": "5.10.2",
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker-compose down
|
docker-compose down -v
|
||||||
docker volume prune -f
|
docker volume prune -f
|
||||||
|
|
|
@ -3,12 +3,12 @@ set -e
|
||||||
|
|
||||||
if [[ -n $CI ]]
|
if [[ -n $CI ]]
|
||||||
then
|
then
|
||||||
export NODE_OPTIONS="--max-old-space-size=4096 --no-node-snapshot"
|
export NODE_OPTIONS="--max-old-space-size=4096 --no-node-snapshot $NODE_OPTIONS"
|
||||||
echo "jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@"
|
echo "jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@"
|
||||||
jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@
|
jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@
|
||||||
else
|
else
|
||||||
# --maxWorkers performs better in development
|
# --maxWorkers performs better in development
|
||||||
export NODE_OPTIONS="--no-node-snapshot"
|
export NODE_OPTIONS="--no-node-snapshot $NODE_OPTIONS"
|
||||||
echo "jest --coverage --maxWorkers=2 --forceExit $@"
|
echo "jest --coverage --maxWorkers=2 --forceExit $@"
|
||||||
jest --coverage --maxWorkers=2 --forceExit $@
|
jest --coverage --maxWorkers=2 --forceExit $@
|
||||||
fi
|
fi
|
|
@ -47,6 +47,9 @@ import {
|
||||||
PlanType,
|
PlanType,
|
||||||
Screen,
|
Screen,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
|
CreateAppRequest,
|
||||||
|
FetchAppDefinitionResponse,
|
||||||
|
FetchAppPackageResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
@ -58,23 +61,23 @@ import * as appMigrations from "../../appMigrations"
|
||||||
async function getLayouts() {
|
async function getLayouts() {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
return (
|
return (
|
||||||
await db.allDocs(
|
await db.allDocs<Layout>(
|
||||||
getLayoutParams(null, {
|
getLayoutParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).rows.map((row: any) => row.doc)
|
).rows.map(row => row.doc!)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getScreens() {
|
async function getScreens() {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
return (
|
return (
|
||||||
await db.allDocs(
|
await db.allDocs<Screen>(
|
||||||
getScreenParams(null, {
|
getScreenParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).rows.map((row: any) => row.doc)
|
).rows.map(row => row.doc!)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUserRoleId(ctx: UserCtx) {
|
function getUserRoleId(ctx: UserCtx) {
|
||||||
|
@ -116,8 +119,8 @@ function checkAppName(
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AppTemplate {
|
interface AppTemplate {
|
||||||
templateString: string
|
templateString?: string
|
||||||
useTemplate: string
|
useTemplate?: string
|
||||||
file?: {
|
file?: {
|
||||||
type: string
|
type: string
|
||||||
path: string
|
path: string
|
||||||
|
@ -174,14 +177,16 @@ export const addSampleData = async (ctx: UserCtx) => {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: UserCtx) {
|
export async function fetch(ctx: UserCtx<void, App[]>) {
|
||||||
ctx.body = await sdk.applications.fetch(
|
ctx.body = await sdk.applications.fetch(
|
||||||
ctx.query.status as AppStatus,
|
ctx.query.status as AppStatus,
|
||||||
ctx.user
|
ctx.user
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAppDefinition(ctx: UserCtx) {
|
export async function fetchAppDefinition(
|
||||||
|
ctx: UserCtx<void, FetchAppDefinitionResponse>
|
||||||
|
) {
|
||||||
const layouts = await getLayouts()
|
const layouts = await getLayouts()
|
||||||
const userRoleId = getUserRoleId(ctx)
|
const userRoleId = getUserRoleId(ctx)
|
||||||
const accessController = new roles.AccessController()
|
const accessController = new roles.AccessController()
|
||||||
|
@ -196,10 +201,12 @@ export async function fetchAppDefinition(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAppPackage(ctx: UserCtx) {
|
export async function fetchAppPackage(
|
||||||
|
ctx: UserCtx<void, FetchAppPackageResponse>
|
||||||
|
) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const appId = context.getAppId()
|
const appId = context.getAppId()
|
||||||
let application = await db.get<any>(DocumentType.APP_METADATA)
|
let application = await db.get<App>(DocumentType.APP_METADATA)
|
||||||
const layouts = await getLayouts()
|
const layouts = await getLayouts()
|
||||||
let screens = await getScreens()
|
let screens = await getScreens()
|
||||||
const license = await licensing.cache.getCachedLicense()
|
const license = await licensing.cache.getCachedLicense()
|
||||||
|
@ -231,17 +238,21 @@ export async function fetchAppPackage(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function performAppCreate(ctx: UserCtx) {
|
async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
|
||||||
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
|
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
|
||||||
const name = ctx.request.body.name,
|
const {
|
||||||
possibleUrl = ctx.request.body.url,
|
name,
|
||||||
encryptionPassword = ctx.request.body.encryptionPassword
|
url,
|
||||||
|
encryptionPassword,
|
||||||
|
useTemplate,
|
||||||
|
templateKey,
|
||||||
|
templateString,
|
||||||
|
} = ctx.request.body
|
||||||
|
|
||||||
checkAppName(ctx, apps, name)
|
checkAppName(ctx, apps, name)
|
||||||
const url = sdk.applications.getAppUrl({ name, url: possibleUrl })
|
const appUrl = sdk.applications.getAppUrl({ name, url })
|
||||||
checkAppUrl(ctx, apps, url)
|
checkAppUrl(ctx, apps, appUrl)
|
||||||
|
|
||||||
const { useTemplate, templateKey, templateString } = ctx.request.body
|
|
||||||
const instanceConfig: AppTemplate = {
|
const instanceConfig: AppTemplate = {
|
||||||
useTemplate,
|
useTemplate,
|
||||||
key: templateKey,
|
key: templateKey,
|
||||||
|
@ -268,7 +279,7 @@ async function performAppCreate(ctx: UserCtx) {
|
||||||
version: envCore.VERSION,
|
version: envCore.VERSION,
|
||||||
componentLibraries: ["@budibase/standard-components"],
|
componentLibraries: ["@budibase/standard-components"],
|
||||||
name: name,
|
name: name,
|
||||||
url: url,
|
url: appUrl,
|
||||||
template: templateKey,
|
template: templateKey,
|
||||||
instance,
|
instance,
|
||||||
tenantId: tenancy.getTenantId(),
|
tenantId: tenancy.getTenantId(),
|
||||||
|
@ -420,7 +431,9 @@ export async function create(ctx: UserCtx) {
|
||||||
|
|
||||||
// This endpoint currently operates as a PATCH rather than a PUT
|
// This endpoint currently operates as a PATCH rather than a PUT
|
||||||
// Thus name and url fields are handled only if present
|
// Thus name and url fields are handled only if present
|
||||||
export async function update(ctx: UserCtx) {
|
export async function update(
|
||||||
|
ctx: UserCtx<{ name?: string; url?: string }, App>
|
||||||
|
) {
|
||||||
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
|
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
|
||||||
// validation
|
// validation
|
||||||
const name = ctx.request.body.name,
|
const name = ctx.request.body.name,
|
||||||
|
@ -493,7 +506,7 @@ export async function revertClient(ctx: UserCtx) {
|
||||||
const revertedToVersion = application.revertableVersion
|
const revertedToVersion = application.revertableVersion
|
||||||
const appPackageUpdates = {
|
const appPackageUpdates = {
|
||||||
version: revertedToVersion,
|
version: revertedToVersion,
|
||||||
revertableVersion: null,
|
revertableVersion: undefined,
|
||||||
}
|
}
|
||||||
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
||||||
await events.app.versionReverted(app, currentVersion, revertedToVersion)
|
await events.app.versionReverted(app, currentVersion, revertedToVersion)
|
||||||
|
@ -613,12 +626,15 @@ export async function importToApp(ctx: UserCtx) {
|
||||||
ctx.body = { message: "app updated" }
|
ctx.body = { message: "app updated" }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateAppPackage(appPackage: any, appId: any) {
|
export async function updateAppPackage(
|
||||||
|
appPackage: Partial<App>,
|
||||||
|
appId: string
|
||||||
|
) {
|
||||||
return context.doInAppContext(appId, async () => {
|
return context.doInAppContext(appId, async () => {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const application = await db.get<App>(DocumentType.APP_METADATA)
|
const application = await db.get<App>(DocumentType.APP_METADATA)
|
||||||
|
|
||||||
const newAppPackage = { ...application, ...appPackage }
|
const newAppPackage: App = { ...application, ...appPackage }
|
||||||
if (appPackage._rev !== application._rev) {
|
if (appPackage._rev !== application._rev) {
|
||||||
newAppPackage._rev = application._rev
|
newAppPackage._rev = application._rev
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ import {
|
||||||
PaginationJson,
|
PaginationJson,
|
||||||
RelationshipFieldMetadata,
|
RelationshipFieldMetadata,
|
||||||
RelationshipsJson,
|
RelationshipsJson,
|
||||||
RelationshipType,
|
|
||||||
Row,
|
Row,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
SortJson,
|
SortJson,
|
||||||
SortType,
|
SortType,
|
||||||
Table,
|
Table,
|
||||||
|
isManyToOne,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
breakExternalTableId,
|
breakExternalTableId,
|
||||||
|
@ -24,6 +24,7 @@ import {
|
||||||
convertRowId,
|
convertRowId,
|
||||||
isRowId,
|
isRowId,
|
||||||
isSQL,
|
isSQL,
|
||||||
|
generateRowIdField,
|
||||||
} from "../../../integrations/utils"
|
} from "../../../integrations/utils"
|
||||||
import {
|
import {
|
||||||
buildExternalRelationships,
|
buildExternalRelationships,
|
||||||
|
@ -33,13 +34,16 @@ import {
|
||||||
updateRelationshipColumns,
|
updateRelationshipColumns,
|
||||||
fixArrayTypes,
|
fixArrayTypes,
|
||||||
isManyToMany,
|
isManyToMany,
|
||||||
|
processRelationshipFields,
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
import { getDatasourceAndQuery } from "../../../sdk/app/rows/utils"
|
import { getDatasourceAndQuery } from "../../../sdk/app/rows/utils"
|
||||||
import { processObjectSync } from "@budibase/string-templates"
|
import { processObjectSync } from "@budibase/string-templates"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
import { processDates, processFormulas } from "../../../utilities/rowProcessor"
|
import { processDates } from "../../../utilities/rowProcessor"
|
||||||
|
import AliasTables from "./alias"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
import env from "../../../environment"
|
||||||
|
|
||||||
export interface ManyRelationship {
|
export interface ManyRelationship {
|
||||||
tableId?: string
|
tableId?: string
|
||||||
|
@ -108,6 +112,39 @@ function buildFilters(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeManyToManyRelationships(
|
||||||
|
rowId: string,
|
||||||
|
table: Table,
|
||||||
|
colName: string
|
||||||
|
) {
|
||||||
|
const tableId = table._id!
|
||||||
|
const filters = buildFilters(rowId, {}, table)
|
||||||
|
// safety check, if there are no filters on deletion bad things happen
|
||||||
|
if (Object.keys(filters).length !== 0) {
|
||||||
|
return getDatasourceAndQuery({
|
||||||
|
endpoint: getEndpoint(tableId, Operation.DELETE),
|
||||||
|
body: { [colName]: null },
|
||||||
|
filters,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeOneToManyRelationships(rowId: string, table: Table) {
|
||||||
|
const tableId = table._id!
|
||||||
|
const filters = buildFilters(rowId, {}, table)
|
||||||
|
// safety check, if there are no filters on deletion bad things happen
|
||||||
|
if (Object.keys(filters).length !== 0) {
|
||||||
|
return getDatasourceAndQuery({
|
||||||
|
endpoint: getEndpoint(tableId, Operation.UPDATE),
|
||||||
|
filters,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function checks the incoming parameters to make sure all the inputs are
|
* This function checks the incoming parameters to make sure all the inputs are
|
||||||
* valid based on on the table schema. The main thing this is looking for is when a
|
* valid based on on the table schema. The main thing this is looking for is when a
|
||||||
|
@ -158,13 +195,13 @@ function cleanupConfig(config: RunConfig, table: Table): RunConfig {
|
||||||
|
|
||||||
function getEndpoint(tableId: string | undefined, operation: string) {
|
function getEndpoint(tableId: string | undefined, operation: string) {
|
||||||
if (!tableId) {
|
if (!tableId) {
|
||||||
return {}
|
throw new Error("Cannot get endpoint information - no table ID specified")
|
||||||
}
|
}
|
||||||
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
return {
|
return {
|
||||||
datasourceId,
|
datasourceId: datasourceId!,
|
||||||
entityId: tableName,
|
entityId: tableName!,
|
||||||
operation,
|
operation: operation as Operation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +301,18 @@ export class ExternalRequest<T extends Operation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRow(table: Table, rowId: string): Promise<Row> {
|
||||||
|
const response = await getDatasourceAndQuery({
|
||||||
|
endpoint: getEndpoint(table._id!, Operation.READ),
|
||||||
|
filters: buildFilters(rowId, {}, table),
|
||||||
|
})
|
||||||
|
if (Array.isArray(response) && response.length > 0) {
|
||||||
|
return response[0]
|
||||||
|
} else {
|
||||||
|
throw new Error(`Cannot fetch row by ID "${rowId}"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inputProcessing(row: Row | undefined, table: Table) {
|
inputProcessing(row: Row | undefined, table: Table) {
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return { row, manyRelationships: [] }
|
return { row, manyRelationships: [] }
|
||||||
|
@ -348,33 +397,6 @@ export class ExternalRequest<T extends Operation> {
|
||||||
return { row: newRow, manyRelationships }
|
return { row: newRow, manyRelationships }
|
||||||
}
|
}
|
||||||
|
|
||||||
processRelationshipFields(
|
|
||||||
table: Table,
|
|
||||||
row: Row,
|
|
||||||
relationships: RelationshipsJson[]
|
|
||||||
): Row {
|
|
||||||
for (let relationship of relationships) {
|
|
||||||
const linkedTable = this.tables[relationship.tableName]
|
|
||||||
if (!linkedTable || !row[relationship.column]) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for (let key of Object.keys(row[relationship.column])) {
|
|
||||||
let relatedRow: Row = row[relationship.column][key]
|
|
||||||
// add this row as context for the relationship
|
|
||||||
for (let col of Object.values(linkedTable.schema)) {
|
|
||||||
if (col.type === FieldType.LINK && col.tableId === table._id) {
|
|
||||||
relatedRow[col.name] = [row]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// process additional types
|
|
||||||
relatedRow = processDates(table, relatedRow)
|
|
||||||
relatedRow = processFormulas(linkedTable, relatedRow)
|
|
||||||
row[relationship.column][key] = relatedRow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
outputProcessing(
|
outputProcessing(
|
||||||
rows: Row[] = [],
|
rows: Row[] = [],
|
||||||
table: Table,
|
table: Table,
|
||||||
|
@ -419,7 +441,7 @@ export class ExternalRequest<T extends Operation> {
|
||||||
|
|
||||||
// make sure all related rows are correct
|
// make sure all related rows are correct
|
||||||
let finalRowArray = Object.values(finalRows).map(row =>
|
let finalRowArray = Object.values(finalRows).map(row =>
|
||||||
this.processRelationshipFields(table, row, relationships)
|
processRelationshipFields(table, this.tables, row, relationships)
|
||||||
)
|
)
|
||||||
|
|
||||||
// process some additional types
|
// process some additional types
|
||||||
|
@ -432,7 +454,9 @@ export class ExternalRequest<T extends Operation> {
|
||||||
* information.
|
* information.
|
||||||
*/
|
*/
|
||||||
async lookupRelations(tableId: string, row: Row) {
|
async lookupRelations(tableId: string, row: Row) {
|
||||||
const related: { [key: string]: any } = {}
|
const related: {
|
||||||
|
[key: string]: { rows: Row[]; isMany: boolean; tableId: string }
|
||||||
|
} = {}
|
||||||
const { tableName } = breakExternalTableId(tableId)
|
const { tableName } = breakExternalTableId(tableId)
|
||||||
if (!tableName) {
|
if (!tableName) {
|
||||||
return related
|
return related
|
||||||
|
@ -450,14 +474,26 @@ export class ExternalRequest<T extends Operation> {
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const isMany = field.relationshipType === RelationshipType.MANY_TO_MANY
|
let tableId: string | undefined,
|
||||||
const tableId = isMany ? field.through : field.tableId
|
lookupField: string | undefined,
|
||||||
|
fieldName: string | undefined
|
||||||
|
if (isManyToMany(field)) {
|
||||||
|
tableId = field.through
|
||||||
|
lookupField = primaryKey
|
||||||
|
fieldName = field.throughTo || primaryKey
|
||||||
|
} else if (isManyToOne(field)) {
|
||||||
|
tableId = field.tableId
|
||||||
|
lookupField = field.foreignKey
|
||||||
|
fieldName = field.fieldName
|
||||||
|
}
|
||||||
|
if (!tableId || !lookupField || !fieldName) {
|
||||||
|
throw new Error(
|
||||||
|
"Unable to lookup relationships - undefined column properties."
|
||||||
|
)
|
||||||
|
}
|
||||||
const { tableName: relatedTableName } = breakExternalTableId(tableId)
|
const { tableName: relatedTableName } = breakExternalTableId(tableId)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const linkPrimaryKey = this.tables[relatedTableName].primary[0]
|
const linkPrimaryKey = this.tables[relatedTableName].primary[0]
|
||||||
|
|
||||||
const lookupField = isMany ? primaryKey : field.foreignKey
|
|
||||||
const fieldName = isMany ? field.throughTo || primaryKey : field.fieldName
|
|
||||||
if (!lookupField || !row[lookupField]) {
|
if (!lookupField || !row[lookupField]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -470,9 +506,12 @@ export class ExternalRequest<T extends Operation> {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// this is the response from knex if no rows found
|
// this is the response from knex if no rows found
|
||||||
const rows = !response[0].read ? response : []
|
const rows: Row[] =
|
||||||
const storeTo = isMany ? field.throughFrom || linkPrimaryKey : fieldName
|
!Array.isArray(response) || response?.[0].read ? [] : response
|
||||||
related[storeTo] = { rows, isMany, tableId }
|
const storeTo = isManyToMany(field)
|
||||||
|
? field.throughFrom || linkPrimaryKey
|
||||||
|
: fieldName
|
||||||
|
related[storeTo] = { rows, isMany: isManyToMany(field), tableId }
|
||||||
}
|
}
|
||||||
return related
|
return related
|
||||||
}
|
}
|
||||||
|
@ -558,24 +597,43 @@ export class ExternalRequest<T extends Operation> {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
const filters = buildFilters(generateIdForRow(row, table), {}, table)
|
const rowId = generateIdForRow(row, table)
|
||||||
// safety check, if there are no filters on deletion bad things happen
|
const promise: Promise<any> = isMany
|
||||||
if (Object.keys(filters).length !== 0) {
|
? removeManyToManyRelationships(rowId, table, colName)
|
||||||
const op = isMany ? Operation.DELETE : Operation.UPDATE
|
: removeOneToManyRelationships(rowId, table)
|
||||||
const body = isMany ? null : { [colName]: null }
|
if (promise) {
|
||||||
promises.push(
|
promises.push(promise)
|
||||||
getDatasourceAndQuery({
|
|
||||||
endpoint: getEndpoint(tableId, op),
|
|
||||||
body,
|
|
||||||
filters,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async removeRelationshipsToRow(table: Table, rowId: string) {
|
||||||
|
const row = await this.getRow(table, rowId)
|
||||||
|
const related = await this.lookupRelations(table._id!, row)
|
||||||
|
for (let column of Object.values(table.schema)) {
|
||||||
|
const relationshipColumn = column as RelationshipFieldMetadata
|
||||||
|
if (!isManyToOne(relationshipColumn)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const { rows, isMany, tableId } = related[relationshipColumn.fieldName]
|
||||||
|
const table = this.getTable(tableId)!
|
||||||
|
await Promise.all(
|
||||||
|
rows.map(row => {
|
||||||
|
const rowId = generateIdForRow(row, table)
|
||||||
|
return isMany
|
||||||
|
? removeManyToManyRelationships(
|
||||||
|
rowId,
|
||||||
|
table,
|
||||||
|
relationshipColumn.fieldName
|
||||||
|
)
|
||||||
|
: removeOneToManyRelationships(rowId, table)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async run(config: RunConfig): Promise<ExternalRequestReturnType<T>> {
|
async run(config: RunConfig): Promise<ExternalRequestReturnType<T>> {
|
||||||
const { operation, tableId } = this
|
const { operation, tableId } = this
|
||||||
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
|
@ -632,7 +690,7 @@ export class ExternalRequest<T extends Operation> {
|
||||||
}
|
}
|
||||||
let json = {
|
let json = {
|
||||||
endpoint: {
|
endpoint: {
|
||||||
datasourceId,
|
datasourceId: datasourceId!,
|
||||||
entityId: tableName,
|
entityId: tableName,
|
||||||
operation,
|
operation,
|
||||||
},
|
},
|
||||||
|
@ -658,13 +716,26 @@ export class ExternalRequest<T extends Operation> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// can't really use response right now
|
// remove any relationships that could block deletion
|
||||||
const response = await getDatasourceAndQuery(json)
|
if (operation === Operation.DELETE && id) {
|
||||||
// handle many to many relationships now if we know the ID (could be auto increment)
|
await this.removeRelationshipsToRow(table, generateRowIdField(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// aliasing can be disabled fully if desired
|
||||||
|
let response
|
||||||
|
if (env.SQL_ALIASING_DISABLE) {
|
||||||
|
response = await getDatasourceAndQuery(json)
|
||||||
|
} else {
|
||||||
|
const aliasing = new AliasTables(Object.keys(this.tables))
|
||||||
|
response = await aliasing.queryWithAliasing(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseRows = Array.isArray(response) ? response : []
|
||||||
|
// handle many-to-many relationships now if we know the ID (could be auto increment)
|
||||||
if (operation !== Operation.READ) {
|
if (operation !== Operation.READ) {
|
||||||
await this.handleManyRelationships(
|
await this.handleManyRelationships(
|
||||||
table._id || "",
|
table._id || "",
|
||||||
response[0],
|
responseRows[0],
|
||||||
processed.manyRelationships
|
processed.manyRelationships
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
import {
|
||||||
|
QueryJson,
|
||||||
|
SearchFilters,
|
||||||
|
Table,
|
||||||
|
Row,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { getDatasourceAndQuery } from "../../../sdk/app/rows/utils"
|
||||||
|
import { cloneDeep } from "lodash"
|
||||||
|
|
||||||
|
class CharSequence {
|
||||||
|
static alphabet = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
counters: number[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.counters = [0]
|
||||||
|
}
|
||||||
|
|
||||||
|
getCharacter(): string {
|
||||||
|
const char = this.counters.map(i => CharSequence.alphabet[i]).join("")
|
||||||
|
for (let i = this.counters.length - 1; i >= 0; i--) {
|
||||||
|
if (this.counters[i] < CharSequence.alphabet.length - 1) {
|
||||||
|
this.counters[i]++
|
||||||
|
return char
|
||||||
|
}
|
||||||
|
this.counters[i] = 0
|
||||||
|
}
|
||||||
|
this.counters.unshift(0)
|
||||||
|
return char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AliasTables {
|
||||||
|
aliases: Record<string, string>
|
||||||
|
tableAliases: Record<string, string>
|
||||||
|
tableNames: string[]
|
||||||
|
charSeq: CharSequence
|
||||||
|
|
||||||
|
constructor(tableNames: string[]) {
|
||||||
|
this.tableNames = tableNames
|
||||||
|
this.aliases = {}
|
||||||
|
this.tableAliases = {}
|
||||||
|
this.charSeq = new CharSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
getAlias(tableName: string) {
|
||||||
|
if (this.aliases[tableName]) {
|
||||||
|
return this.aliases[tableName]
|
||||||
|
}
|
||||||
|
const char = this.charSeq.getCharacter()
|
||||||
|
this.aliases[tableName] = char
|
||||||
|
this.tableAliases[char] = tableName
|
||||||
|
return char
|
||||||
|
}
|
||||||
|
|
||||||
|
aliasField(field: string) {
|
||||||
|
const tableNames = this.tableNames
|
||||||
|
if (field.includes(".")) {
|
||||||
|
const [tableName, column] = field.split(".")
|
||||||
|
const foundTableName = tableNames.find(name => {
|
||||||
|
const idx = tableName.indexOf(name)
|
||||||
|
if (idx === -1 || idx > 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return Math.abs(tableName.length - name.length) <= 2
|
||||||
|
})
|
||||||
|
if (foundTableName) {
|
||||||
|
const aliasedTableName = tableName.replace(
|
||||||
|
foundTableName,
|
||||||
|
this.getAlias(foundTableName)
|
||||||
|
)
|
||||||
|
field = `${aliasedTableName}.${column}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse<T extends Row | Row[]>(rows: T): T {
|
||||||
|
const process = (row: Row) => {
|
||||||
|
const final: Row = {}
|
||||||
|
for (let [key, value] of Object.entries(row)) {
|
||||||
|
if (!key.includes(".")) {
|
||||||
|
final[key] = value
|
||||||
|
} else {
|
||||||
|
const [alias, column] = key.split(".")
|
||||||
|
const tableName = this.tableAliases[alias] || alias
|
||||||
|
final[`${tableName}.${column}`] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return final
|
||||||
|
}
|
||||||
|
if (Array.isArray(rows)) {
|
||||||
|
return rows.map(row => process(row)) as T
|
||||||
|
} else {
|
||||||
|
return process(rows) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aliasMap(tableNames: (string | undefined)[]) {
|
||||||
|
const map: Record<string, string> = {}
|
||||||
|
for (let tableName of tableNames) {
|
||||||
|
if (tableName) {
|
||||||
|
map[tableName] = this.getAlias(tableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryWithAliasing(
|
||||||
|
json: QueryJson
|
||||||
|
): Promise<DatasourcePlusQueryResponse> {
|
||||||
|
json = cloneDeep(json)
|
||||||
|
const aliasTable = (table: Table) => ({
|
||||||
|
...table,
|
||||||
|
name: this.getAlias(table.name),
|
||||||
|
})
|
||||||
|
// run through the query json to update anywhere a table may be used
|
||||||
|
if (json.resource?.fields) {
|
||||||
|
json.resource.fields = json.resource.fields.map(field =>
|
||||||
|
this.aliasField(field)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (json.filters) {
|
||||||
|
for (let [filterKey, filter] of Object.entries(json.filters)) {
|
||||||
|
if (typeof filter !== "object") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const aliasedFilters: typeof filter = {}
|
||||||
|
for (let key of Object.keys(filter)) {
|
||||||
|
aliasedFilters[this.aliasField(key)] = filter[key]
|
||||||
|
}
|
||||||
|
json.filters[filterKey as keyof SearchFilters] = aliasedFilters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json.relationships) {
|
||||||
|
json.relationships = json.relationships.map(relationship => ({
|
||||||
|
...relationship,
|
||||||
|
aliases: this.aliasMap([
|
||||||
|
relationship.through,
|
||||||
|
relationship.tableName,
|
||||||
|
json.endpoint.entityId,
|
||||||
|
]),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if (json.meta?.table) {
|
||||||
|
json.meta.table = aliasTable(json.meta.table)
|
||||||
|
}
|
||||||
|
if (json.meta?.tables) {
|
||||||
|
const aliasedTables: Record<string, Table> = {}
|
||||||
|
for (let [tableName, table] of Object.entries(json.meta.tables)) {
|
||||||
|
aliasedTables[this.getAlias(tableName)] = aliasTable(table)
|
||||||
|
}
|
||||||
|
json.meta.tables = aliasedTables
|
||||||
|
}
|
||||||
|
// invert and return
|
||||||
|
const invertedTableAliases: Record<string, string> = {}
|
||||||
|
for (let [key, value] of Object.entries(this.tableAliases)) {
|
||||||
|
invertedTableAliases[value] = key
|
||||||
|
}
|
||||||
|
json.tableAliases = invertedTableAliases
|
||||||
|
const response = await getDatasourceAndQuery(json)
|
||||||
|
if (Array.isArray(response)) {
|
||||||
|
return this.reverse(response)
|
||||||
|
} else {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -223,7 +223,8 @@ export const exportRows = async (
|
||||||
|
|
||||||
const format = ctx.query.format
|
const format = ctx.query.format
|
||||||
|
|
||||||
const { rows, columns, query, sort, sortOrder } = ctx.request.body
|
const { rows, columns, query, sort, sortOrder, delimiter, customHeaders } =
|
||||||
|
ctx.request.body
|
||||||
if (typeof format !== "string" || !exporters.isFormat(format)) {
|
if (typeof format !== "string" || !exporters.isFormat(format)) {
|
||||||
ctx.throw(
|
ctx.throw(
|
||||||
400,
|
400,
|
||||||
|
@ -241,6 +242,8 @@ export const exportRows = async (
|
||||||
query,
|
query,
|
||||||
sort,
|
sort,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
|
delimiter,
|
||||||
|
customHeaders,
|
||||||
})
|
})
|
||||||
ctx.attachment(fileName)
|
ctx.attachment(fileName)
|
||||||
ctx.body = apiFileReturn(content)
|
ctx.body = apiFileReturn(content)
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
import { InternalTables } from "../../../../db/utils"
|
import { InternalTables } from "../../../../db/utils"
|
||||||
import * as userController from "../../user"
|
import * as userController from "../../user"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { Ctx, RelationshipsJson, Row, Table, UserCtx } from "@budibase/types"
|
import {
|
||||||
|
Ctx,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
|
FieldType,
|
||||||
|
RelationshipsJson,
|
||||||
|
Row,
|
||||||
|
Table,
|
||||||
|
UserCtx,
|
||||||
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
processDates,
|
processDates,
|
||||||
processFormulas,
|
processFormulas,
|
||||||
|
@ -25,6 +33,34 @@ validateJs.extend(validateJs.validators.datetime, {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export function processRelationshipFields(
|
||||||
|
table: Table,
|
||||||
|
tables: Record<string, Table>,
|
||||||
|
row: Row,
|
||||||
|
relationships: RelationshipsJson[]
|
||||||
|
): Row {
|
||||||
|
for (let relationship of relationships) {
|
||||||
|
const linkedTable = tables[relationship.tableName]
|
||||||
|
if (!linkedTable || !row[relationship.column]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (let key of Object.keys(row[relationship.column])) {
|
||||||
|
let relatedRow: Row = row[relationship.column][key]
|
||||||
|
// add this row as context for the relationship
|
||||||
|
for (let col of Object.values(linkedTable.schema)) {
|
||||||
|
if (col.type === FieldType.LINK && col.tableId === table._id) {
|
||||||
|
relatedRow[col.name] = [row]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// process additional types
|
||||||
|
relatedRow = processDates(table, relatedRow)
|
||||||
|
relatedRow = processFormulas(linkedTable, relatedRow)
|
||||||
|
row[relationship.column][key] = relatedRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
export async function findRow(ctx: UserCtx, tableId: string, rowId: string) {
|
export async function findRow(ctx: UserCtx, tableId: string, rowId: string) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let row: Row
|
let row: Row
|
||||||
|
@ -80,17 +116,17 @@ export async function validate(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sqlOutputProcessing(
|
export function sqlOutputProcessing(
|
||||||
rows: Row[] = [],
|
rows: DatasourcePlusQueryResponse,
|
||||||
table: Table,
|
table: Table,
|
||||||
tables: Record<string, Table>,
|
tables: Record<string, Table>,
|
||||||
relationships: RelationshipsJson[],
|
relationships: RelationshipsJson[],
|
||||||
opts?: { internal?: boolean }
|
opts?: { internal?: boolean }
|
||||||
) {
|
) {
|
||||||
if (!rows || rows.length === 0 || rows[0].read === true) {
|
if (!Array.isArray(rows) || rows.length === 0 || rows[0].read === true) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let finalRows: { [key: string]: Row } = {}
|
let finalRows: { [key: string]: Row } = {}
|
||||||
for (let row of rows) {
|
for (let row of rows as Row[]) {
|
||||||
let rowId = row._id
|
let rowId = row._id
|
||||||
if (!rowId) {
|
if (!rowId) {
|
||||||
rowId = generateIdForRow(row, table)
|
rowId = generateIdForRow(row, table)
|
||||||
|
@ -103,7 +139,8 @@ export function sqlOutputProcessing(
|
||||||
tables,
|
tables,
|
||||||
row,
|
row,
|
||||||
finalRows,
|
finalRows,
|
||||||
relationships
|
relationships,
|
||||||
|
opts
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -126,19 +163,18 @@ export function sqlOutputProcessing(
|
||||||
tables,
|
tables,
|
||||||
row,
|
row,
|
||||||
finalRows,
|
finalRows,
|
||||||
relationships,
|
relationships
|
||||||
opts
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process some additional data types
|
// make sure all related rows are correct
|
||||||
let finalRowArray = Object.values(finalRows)
|
let finalRowArray = Object.values(finalRows).map(row =>
|
||||||
finalRowArray = processDates(table, finalRowArray)
|
processRelationshipFields(table, tables, row, relationships)
|
||||||
finalRowArray = processFormulas(table, finalRowArray) as Row[]
|
|
||||||
|
|
||||||
return finalRowArray.map((row: Row) =>
|
|
||||||
squashRelationshipColumns(table, tables, row, relationships)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// process some additional types
|
||||||
|
finalRowArray = processDates(table, finalRowArray)
|
||||||
|
return finalRowArray
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isUserMetadataTable(tableId: string) {
|
export function isUserMetadataTable(tableId: string) {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { InvalidFileExtensions } from "@budibase/shared-core"
|
import { InvalidFileExtensions } from "@budibase/shared-core"
|
||||||
|
|
||||||
import AppComponent from "./templates/BudibaseApp.svelte"
|
import AppComponent from "./templates/BudibaseApp.svelte"
|
||||||
|
|
||||||
import { join } from "../../../utilities/centralPath"
|
import { join } from "../../../utilities/centralPath"
|
||||||
import * as uuid from "uuid"
|
import * as uuid from "uuid"
|
||||||
import { ObjectStoreBuckets } from "../../../constants"
|
import { ObjectStoreBuckets } from "../../../constants"
|
||||||
|
@ -24,10 +22,12 @@ import fs from "fs"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import * as pro from "@budibase/pro"
|
import * as pro from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
|
DocumentType,
|
||||||
|
UserCtx,
|
||||||
App,
|
App,
|
||||||
Ctx,
|
Ctx,
|
||||||
DocumentType,
|
|
||||||
ProcessAttachmentResponse,
|
ProcessAttachmentResponse,
|
||||||
|
Feature,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getAppMigrationVersion,
|
getAppMigrationVersion,
|
||||||
|
@ -36,6 +36,61 @@ import {
|
||||||
|
|
||||||
import send from "koa-send"
|
import send from "koa-send"
|
||||||
|
|
||||||
|
const getThemeVariables = (theme: string) => {
|
||||||
|
if (theme === "spectrum--lightest") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
||||||
|
--spectrum-global-color-gray-200: rgb(244, 244, 244);
|
||||||
|
--spectrum-global-color-gray-300: rgb(234, 234, 234);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--light") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
||||||
|
--spectrum-global-color-gray-200: rgb(234, 234, 234);
|
||||||
|
--spectrum-global-color-gray-300: rgb(225, 225, 225);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--dark") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-100: rgb(50, 50, 50);
|
||||||
|
--spectrum-global-color-gray-200: rgb(62, 62, 62);
|
||||||
|
--spectrum-global-color-gray-300: rgb(74, 74, 74);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--darkest") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-100: rgb(30, 30, 30);
|
||||||
|
--spectrum-global-color-gray-200: rgb(44, 44, 44);
|
||||||
|
--spectrum-global-color-gray-300: rgb(57, 57, 57);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--nord") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-100: #3b4252;
|
||||||
|
|
||||||
|
--spectrum-global-color-gray-200: #424a5c;
|
||||||
|
--spectrum-global-color-gray-300: #4c566a;
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--midnight") {
|
||||||
|
return `
|
||||||
|
--hue: 220;
|
||||||
|
--sat: 10%;
|
||||||
|
--spectrum-global-color-gray-100: hsl(var(--hue), var(--sat), 17%);
|
||||||
|
--spectrum-global-color-gray-200: hsl(var(--hue), var(--sat), 20%);
|
||||||
|
--spectrum-global-color-gray-300: hsl(var(--hue), var(--sat), 24%);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const toggleBetaUiFeature = async function (ctx: Ctx) {
|
export const toggleBetaUiFeature = async function (ctx: Ctx) {
|
||||||
const cookieName = `beta:${ctx.params.feature}`
|
const cookieName = `beta:${ctx.params.feature}`
|
||||||
|
|
||||||
|
@ -150,7 +205,7 @@ const requiresMigration = async (ctx: Ctx) => {
|
||||||
return requiresMigrations
|
return requiresMigrations
|
||||||
}
|
}
|
||||||
|
|
||||||
export const serveApp = async function (ctx: Ctx) {
|
export const serveApp = async function (ctx: UserCtx) {
|
||||||
const needMigrations = await requiresMigration(ctx)
|
const needMigrations = await requiresMigration(ctx)
|
||||||
|
|
||||||
const bbHeaderEmbed =
|
const bbHeaderEmbed =
|
||||||
|
@ -171,9 +226,19 @@ export const serveApp = async function (ctx: Ctx) {
|
||||||
const appInfo = await db.get<any>(DocumentType.APP_METADATA)
|
const appInfo = await db.get<any>(DocumentType.APP_METADATA)
|
||||||
let appId = context.getAppId()
|
let appId = context.getAppId()
|
||||||
|
|
||||||
|
const hideDevTools = !!ctx.params.appUrl
|
||||||
|
const sideNav = appInfo.navigation.navigation === "Left"
|
||||||
|
const hideFooter =
|
||||||
|
ctx?.user?.license?.features?.includes(Feature.BRANDING) || false
|
||||||
|
const themeVariables = getThemeVariables(appInfo?.theme)
|
||||||
|
|
||||||
if (!env.isJest()) {
|
if (!env.isJest()) {
|
||||||
const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
|
const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
|
||||||
|
|
||||||
const { head, html, css } = AppComponent.render({
|
const { head, html, css } = AppComponent.render({
|
||||||
|
hideDevTools,
|
||||||
|
sideNav,
|
||||||
|
hideFooter,
|
||||||
metaImage:
|
metaImage:
|
||||||
branding?.metaImageUrl ||
|
branding?.metaImageUrl ||
|
||||||
"https://res.cloudinary.com/daog6scxm/image/upload/v1698759482/meta-images/plain-branded-meta-image-coral_ocxmgu.png",
|
"https://res.cloudinary.com/daog6scxm/image/upload/v1698759482/meta-images/plain-branded-meta-image-coral_ocxmgu.png",
|
||||||
|
@ -198,7 +263,7 @@ export const serveApp = async function (ctx: Ctx) {
|
||||||
ctx.body = await processString(appHbs, {
|
ctx.body = await processString(appHbs, {
|
||||||
head,
|
head,
|
||||||
body: html,
|
body: html,
|
||||||
style: css.code,
|
css: `:root{${themeVariables}} ${css.code}`,
|
||||||
appId,
|
appId,
|
||||||
embedded: bbHeaderEmbed,
|
embedded: bbHeaderEmbed,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
import ClientAppSkeleton from "@budibase/frontend-core/src/components/ClientAppSkeleton.svelte"
|
||||||
|
|
||||||
export let title = ""
|
export let title = ""
|
||||||
export let favicon = ""
|
export let favicon = ""
|
||||||
|
|
||||||
|
@ -9,6 +11,10 @@
|
||||||
export let clientLibPath
|
export let clientLibPath
|
||||||
export let usedPlugins
|
export let usedPlugins
|
||||||
export let appMigrating
|
export let appMigrating
|
||||||
|
|
||||||
|
export let hideDevTools
|
||||||
|
export let sideNav
|
||||||
|
export let hideFooter
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -96,6 +102,7 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<body id="app">
|
<body id="app">
|
||||||
|
<ClientAppSkeleton {hideDevTools} {sideNav} {hideFooter} />
|
||||||
<div id="error">
|
<div id="error">
|
||||||
{#if clientLibPath}
|
{#if clientLibPath}
|
||||||
<h1>There was an error loading your app</h1>
|
<h1>There was an error loading your app</h1>
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
<html>
|
<html>
|
||||||
|
<script>
|
||||||
|
document.fonts.ready.then(() => {
|
||||||
|
window.parent.postMessage({ type: "docLoaded" });
|
||||||
|
})
|
||||||
|
</script>
|
||||||
<head>
|
<head>
|
||||||
{{{head}}}
|
{{{head}}}
|
||||||
<style>{{{style}}}</style>
|
<style>{{{css}}}</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
import { Row, TableSchema } from "@budibase/types"
|
import { Row, TableSchema } from "@budibase/types"
|
||||||
|
|
||||||
export function csv(headers: string[], rows: Row[]) {
|
function getHeaders(
|
||||||
let csv = headers.map(key => `"${key}"`).join(",")
|
headers: string[],
|
||||||
|
customHeaders: { [key: string]: string }
|
||||||
|
) {
|
||||||
|
return headers.map(header => `"${customHeaders[header] || header}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function csv(
|
||||||
|
headers: string[],
|
||||||
|
rows: Row[],
|
||||||
|
delimiter: string = ",",
|
||||||
|
customHeaders: { [key: string]: string } = {}
|
||||||
|
) {
|
||||||
|
let csv = getHeaders(headers, customHeaders).join(delimiter)
|
||||||
|
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
csv = `${csv}\n${headers
|
csv = `${csv}\n${headers
|
||||||
|
@ -15,7 +27,7 @@ export function csv(headers: string[], rows: Row[]) {
|
||||||
: ""
|
: ""
|
||||||
return val.trim()
|
return val.trim()
|
||||||
})
|
})
|
||||||
.join(",")}`
|
.join(delimiter)}`
|
||||||
}
|
}
|
||||||
return csv
|
return csv
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import * as deploymentController from "../controllers/deploy"
|
||||||
import authorized from "../../middleware/authorized"
|
import authorized from "../../middleware/authorized"
|
||||||
import { permissions } from "@budibase/backend-core"
|
import { permissions } from "@budibase/backend-core"
|
||||||
import { applicationValidator } from "./utils/validators"
|
import { applicationValidator } from "./utils/validators"
|
||||||
import { importToApp } from "../controllers/application"
|
|
||||||
|
|
||||||
const router: Router = new Router()
|
const router: Router = new Router()
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,8 @@ router
|
||||||
controller.deleteObjects
|
controller.deleteObjects
|
||||||
)
|
)
|
||||||
.get("/app/preview", authorized(BUILDER), controller.serveBuilderPreview)
|
.get("/app/preview", authorized(BUILDER), controller.serveBuilderPreview)
|
||||||
.get("/:appId/:path*", controller.serveApp)
|
|
||||||
.get("/app/:appUrl/:path*", controller.serveApp)
|
.get("/app/:appUrl/:path*", controller.serveApp)
|
||||||
|
.get("/:appId/:path*", controller.serveApp)
|
||||||
.post(
|
.post(
|
||||||
"/api/attachments/:datasourceId/url",
|
"/api/attachments/:datasourceId/url",
|
||||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||||
|
|
|
@ -11,65 +11,54 @@ jest.mock("../../../utilities/redis", () => ({
|
||||||
checkDebounce: jest.fn(),
|
checkDebounce: jest.fn(),
|
||||||
shutdown: jest.fn(),
|
shutdown: jest.fn(),
|
||||||
}))
|
}))
|
||||||
import { clearAllApps, checkBuilderEndpoint } from "./utilities/TestFunctions"
|
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
import { AppStatus } from "../../../db/utils"
|
import { AppStatus } from "../../../db/utils"
|
||||||
import { events, utils, context } from "@budibase/backend-core"
|
import { events, utils, context } from "@budibase/backend-core"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
|
import type { App } from "@budibase/types"
|
||||||
jest.setTimeout(15000)
|
import tk from "timekeeper"
|
||||||
|
|
||||||
describe("/applications", () => {
|
describe("/applications", () => {
|
||||||
let request = setup.getRequest()
|
|
||||||
let config = setup.getConfig()
|
let config = setup.getConfig()
|
||||||
|
let app: App
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
beforeAll(async () => await config.init())
|
||||||
beforeAll(async () => {
|
|
||||||
await config.init()
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
app = await config.api.application.create({ name: utils.newid() })
|
||||||
|
const deployment = await config.api.application.publish(app.appId)
|
||||||
|
expect(deployment.status).toBe("SUCCESS")
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
it("creates empty app", async () => {
|
it("creates empty app", async () => {
|
||||||
const res = await request
|
const app = await config.api.application.create({ name: utils.newid() })
|
||||||
.post("/api/applications")
|
expect(app._id).toBeDefined()
|
||||||
.field("name", utils.newid())
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body._id).toBeDefined()
|
|
||||||
expect(events.app.created).toBeCalledTimes(1)
|
expect(events.app.created).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates app from template", async () => {
|
it("creates app from template", async () => {
|
||||||
const res = await request
|
const app = await config.api.application.create({
|
||||||
.post("/api/applications")
|
name: utils.newid(),
|
||||||
.field("name", utils.newid())
|
useTemplate: "true",
|
||||||
.field("useTemplate", "true")
|
templateKey: "test",
|
||||||
.field("templateKey", "test")
|
templateString: "{}",
|
||||||
.field("templateString", "{}") // override the file download
|
})
|
||||||
.set(config.defaultHeaders())
|
expect(app._id).toBeDefined()
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body._id).toBeDefined()
|
|
||||||
expect(events.app.created).toBeCalledTimes(1)
|
expect(events.app.created).toBeCalledTimes(1)
|
||||||
expect(events.app.templateImported).toBeCalledTimes(1)
|
expect(events.app.templateImported).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates app from file", async () => {
|
it("creates app from file", async () => {
|
||||||
const res = await request
|
const app = await config.api.application.create({
|
||||||
.post("/api/applications")
|
name: utils.newid(),
|
||||||
.field("name", utils.newid())
|
useTemplate: "true",
|
||||||
.field("useTemplate", "true")
|
templateFile: "src/api/routes/tests/data/export.txt",
|
||||||
.set(config.defaultHeaders())
|
})
|
||||||
.attach("templateFile", "src/api/routes/tests/data/export.txt")
|
expect(app._id).toBeDefined()
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body._id).toBeDefined()
|
|
||||||
expect(events.app.created).toBeCalledTimes(1)
|
expect(events.app.created).toBeCalledTimes(1)
|
||||||
expect(events.app.fileImported).toBeCalledTimes(1)
|
expect(events.app.fileImported).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
@ -84,24 +73,21 @@ describe("/applications", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("migrates navigation settings from old apps", async () => {
|
it("migrates navigation settings from old apps", async () => {
|
||||||
const res = await request
|
const app = await config.api.application.create({
|
||||||
.post("/api/applications")
|
name: utils.newid(),
|
||||||
.field("name", "Old App")
|
useTemplate: "true",
|
||||||
.field("useTemplate", "true")
|
templateFile: "src/api/routes/tests/data/old-app.txt",
|
||||||
.set(config.defaultHeaders())
|
})
|
||||||
.attach("templateFile", "src/api/routes/tests/data/old-app.txt")
|
expect(app._id).toBeDefined()
|
||||||
.expect("Content-Type", /json/)
|
expect(app.navigation).toBeDefined()
|
||||||
.expect(200)
|
expect(app.navigation!.hideLogo).toBe(true)
|
||||||
expect(res.body._id).toBeDefined()
|
expect(app.navigation!.title).toBe("Custom Title")
|
||||||
expect(res.body.navigation).toBeDefined()
|
expect(app.navigation!.hideLogo).toBe(true)
|
||||||
expect(res.body.navigation.hideLogo).toBe(true)
|
expect(app.navigation!.navigation).toBe("Left")
|
||||||
expect(res.body.navigation.title).toBe("Custom Title")
|
expect(app.navigation!.navBackground).toBe(
|
||||||
expect(res.body.navigation.hideLogo).toBe(true)
|
|
||||||
expect(res.body.navigation.navigation).toBe("Left")
|
|
||||||
expect(res.body.navigation.navBackground).toBe(
|
|
||||||
"var(--spectrum-global-color-blue-600)"
|
"var(--spectrum-global-color-blue-600)"
|
||||||
)
|
)
|
||||||
expect(res.body.navigation.navTextColor).toBe(
|
expect(app.navigation!.navTextColor).toBe(
|
||||||
"var(--spectrum-global-color-gray-50)"
|
"var(--spectrum-global-color-gray-50)"
|
||||||
)
|
)
|
||||||
expect(events.app.created).toBeCalledTimes(1)
|
expect(events.app.created).toBeCalledTimes(1)
|
||||||
|
@ -110,164 +96,106 @@ describe("/applications", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
beforeEach(async () => {
|
|
||||||
// Clean all apps but the onde from config
|
|
||||||
await clearAllApps(config.getTenantId(), [config.getAppId()!])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("lists all applications", async () => {
|
it("lists all applications", async () => {
|
||||||
await config.createApp("app1")
|
const apps = await config.api.application.fetch({ status: AppStatus.DEV })
|
||||||
await config.createApp("app2")
|
expect(apps.length).toBeGreaterThan(0)
|
||||||
|
|
||||||
const res = await request
|
|
||||||
.get(`/api/applications?status=${AppStatus.DEV}`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
// two created apps + the inited app
|
|
||||||
expect(res.body.length).toBe(3)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("fetchAppDefinition", () => {
|
describe("fetchAppDefinition", () => {
|
||||||
it("should be able to get an apps definition", async () => {
|
it("should be able to get an apps definition", async () => {
|
||||||
const res = await request
|
const res = await config.api.application.getDefinition(app.appId)
|
||||||
.get(`/api/applications/${config.getAppId()}/definition`)
|
expect(res.libraries.length).toEqual(1)
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body.libraries.length).toEqual(1)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("fetchAppPackage", () => {
|
describe("fetchAppPackage", () => {
|
||||||
it("should be able to fetch the app package", async () => {
|
it("should be able to fetch the app package", async () => {
|
||||||
const res = await request
|
const res = await config.api.application.getAppPackage(app.appId)
|
||||||
.get(`/api/applications/${config.getAppId()}/appPackage`)
|
expect(res.application).toBeDefined()
|
||||||
.set(config.defaultHeaders())
|
expect(res.application.appId).toEqual(config.getAppId())
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body.application).toBeDefined()
|
|
||||||
expect(res.body.application.appId).toEqual(config.getAppId())
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
it("should be able to update the app package", async () => {
|
it("should be able to update the app package", async () => {
|
||||||
const res = await request
|
const updatedApp = await config.api.application.update(app.appId, {
|
||||||
.put(`/api/applications/${config.getAppId()}`)
|
name: "TEST_APP",
|
||||||
.send({
|
})
|
||||||
name: "TEST_APP",
|
expect(updatedApp._rev).toBeDefined()
|
||||||
})
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body._rev).toBeDefined()
|
|
||||||
expect(events.app.updated).toBeCalledTimes(1)
|
expect(events.app.updated).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("publish", () => {
|
describe("publish", () => {
|
||||||
it("should publish app with dev app ID", async () => {
|
it("should publish app with dev app ID", async () => {
|
||||||
const appId = config.getAppId()
|
await config.api.application.publish(app.appId)
|
||||||
await request
|
|
||||||
.post(`/api/applications/${appId}/publish`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(events.app.published).toBeCalledTimes(1)
|
expect(events.app.published).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should publish app with prod app ID", async () => {
|
it("should publish app with prod app ID", async () => {
|
||||||
const appId = config.getProdAppId()
|
await config.api.application.publish(app.appId.replace("_dev", ""))
|
||||||
await request
|
|
||||||
.post(`/api/applications/${appId}/publish`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(events.app.published).toBeCalledTimes(1)
|
expect(events.app.published).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("manage client library version", () => {
|
describe("manage client library version", () => {
|
||||||
it("should be able to update the app client library version", async () => {
|
it("should be able to update the app client library version", async () => {
|
||||||
await request
|
await config.api.application.updateClient(app.appId)
|
||||||
.post(`/api/applications/${config.getAppId()}/client/update`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(events.app.versionUpdated).toBeCalledTimes(1)
|
expect(events.app.versionUpdated).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to revert the app client library version", async () => {
|
it("should be able to revert the app client library version", async () => {
|
||||||
// We need to first update the version so that we can then revert
|
await config.api.application.updateClient(app.appId)
|
||||||
await request
|
await config.api.application.revertClient(app.appId)
|
||||||
.post(`/api/applications/${config.getAppId()}/client/update`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
await request
|
|
||||||
.post(`/api/applications/${config.getAppId()}/client/revert`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(events.app.versionReverted).toBeCalledTimes(1)
|
expect(events.app.versionReverted).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("edited at", () => {
|
describe("edited at", () => {
|
||||||
it("middleware should set edited at", async () => {
|
it("middleware should set updatedAt", async () => {
|
||||||
const headers = config.defaultHeaders()
|
const app = await tk.withFreeze(
|
||||||
headers["referer"] = `/${config.getAppId()}/test`
|
"2021-01-01",
|
||||||
const res = await request
|
async () => await config.api.application.create({ name: utils.newid() })
|
||||||
.put(`/api/applications/${config.getAppId()}`)
|
)
|
||||||
.send({
|
expect(app.updatedAt).toEqual("2021-01-01T00:00:00.000Z")
|
||||||
name: "UPDATED_NAME",
|
|
||||||
})
|
const updatedApp = await tk.withFreeze(
|
||||||
.set(headers)
|
"2021-02-01",
|
||||||
.expect("Content-Type", /json/)
|
async () =>
|
||||||
.expect(200)
|
await config.api.application.update(app.appId, {
|
||||||
expect(res.body._rev).toBeDefined()
|
name: "UPDATED_NAME",
|
||||||
// retrieve the app to check it
|
})
|
||||||
const getRes = await request
|
)
|
||||||
.get(`/api/applications/${config.getAppId()}/appPackage`)
|
expect(updatedApp._rev).toBeDefined()
|
||||||
.set(headers)
|
expect(updatedApp.updatedAt).toEqual("2021-02-01T00:00:00.000Z")
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
const fetchedApp = await config.api.application.get(app.appId)
|
||||||
expect(getRes.body.application.updatedAt).toBeDefined()
|
expect(fetchedApp.updatedAt).toEqual("2021-02-01T00:00:00.000Z")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("sync", () => {
|
describe("sync", () => {
|
||||||
it("app should sync correctly", async () => {
|
it("app should sync correctly", async () => {
|
||||||
const res = await request
|
const { message } = await config.api.application.sync(app.appId)
|
||||||
.post(`/api/applications/${config.getAppId()}/sync`)
|
expect(message).toEqual("App sync completed successfully.")
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body.message).toEqual("App sync completed successfully.")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("app should not sync if production", async () => {
|
it("app should not sync if production", async () => {
|
||||||
const res = await request
|
const { message } = await config.api.application.sync(
|
||||||
.post(`/api/applications/app_123456/sync`)
|
app.appId.replace("_dev", ""),
|
||||||
.set(config.defaultHeaders())
|
{ statusCode: 400 }
|
||||||
.expect("Content-Type", /json/)
|
)
|
||||||
.expect(400)
|
|
||||||
expect(res.body.message).toEqual(
|
expect(message).toEqual(
|
||||||
"This action cannot be performed for production apps"
|
"This action cannot be performed for production apps"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("app should not sync if sync is disabled", async () => {
|
it("app should not sync if sync is disabled", async () => {
|
||||||
env._set("DISABLE_AUTO_PROD_APP_SYNC", true)
|
env._set("DISABLE_AUTO_PROD_APP_SYNC", true)
|
||||||
const res = await request
|
const { message } = await config.api.application.sync(app.appId)
|
||||||
.post(`/api/applications/${config.getAppId()}/sync`)
|
expect(message).toEqual(
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body.message).toEqual(
|
|
||||||
"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable."
|
"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable."
|
||||||
)
|
)
|
||||||
env._set("DISABLE_AUTO_PROD_APP_SYNC", false)
|
env._set("DISABLE_AUTO_PROD_APP_SYNC", false)
|
||||||
|
@ -275,51 +203,26 @@ describe("/applications", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("unpublish", () => {
|
describe("unpublish", () => {
|
||||||
beforeEach(async () => {
|
|
||||||
// We want to republish as the unpublish will delete the prod app
|
|
||||||
await config.publish()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should unpublish app with dev app ID", async () => {
|
it("should unpublish app with dev app ID", async () => {
|
||||||
const appId = config.getAppId()
|
await config.api.application.unpublish(app.appId)
|
||||||
await request
|
|
||||||
.post(`/api/applications/${appId}/unpublish`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect(204)
|
|
||||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should unpublish app with prod app ID", async () => {
|
it("should unpublish app with prod app ID", async () => {
|
||||||
const appId = config.getProdAppId()
|
await config.api.application.unpublish(app.appId.replace("_dev", ""))
|
||||||
await request
|
|
||||||
.post(`/api/applications/${appId}/unpublish`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect(204)
|
|
||||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("delete", () => {
|
describe("delete", () => {
|
||||||
it("should delete published app and dev apps with dev app ID", async () => {
|
it("should delete published app and dev apps with dev app ID", async () => {
|
||||||
await config.createApp("to-delete")
|
await config.api.application.delete(app.appId)
|
||||||
const appId = config.getAppId()
|
|
||||||
await request
|
|
||||||
.delete(`/api/applications/${appId}`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(events.app.deleted).toBeCalledTimes(1)
|
expect(events.app.deleted).toBeCalledTimes(1)
|
||||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should delete published app and dev app with prod app ID", async () => {
|
it("should delete published app and dev app with prod app ID", async () => {
|
||||||
await config.createApp("to-delete")
|
await config.api.application.delete(app.appId.replace("_dev", ""))
|
||||||
const appId = config.getProdAppId()
|
|
||||||
await request
|
|
||||||
.delete(`/api/applications/${appId}`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(events.app.deleted).toBeCalledTimes(1)
|
expect(events.app.deleted).toBeCalledTimes(1)
|
||||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
@ -327,28 +230,18 @@ describe("/applications", () => {
|
||||||
|
|
||||||
describe("POST /api/applications/:appId/sync", () => {
|
describe("POST /api/applications/:appId/sync", () => {
|
||||||
it("should not sync automation logs", async () => {
|
it("should not sync automation logs", async () => {
|
||||||
// setup the apps
|
|
||||||
await config.createApp("testing-auto-logs")
|
|
||||||
const automation = await config.createAutomation()
|
const automation = await config.createAutomation()
|
||||||
await config.publish()
|
await context.doInAppContext(app.appId, () =>
|
||||||
await context.doInAppContext(config.getProdAppId(), () => {
|
config.createAutomationLog(automation)
|
||||||
return config.createAutomationLog(automation)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
// do the sync
|
await config.api.application.sync(app.appId)
|
||||||
const appId = config.getAppId()
|
|
||||||
await request
|
|
||||||
.post(`/api/applications/${appId}/sync`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
// does exist in prod
|
// does exist in prod
|
||||||
const prodLogs = await config.getAutomationLogs()
|
const prodLogs = await config.getAutomationLogs()
|
||||||
expect(prodLogs.data.length).toBe(1)
|
expect(prodLogs.data.length).toBe(1)
|
||||||
|
|
||||||
// delete prod app so we revert to dev log search
|
await config.api.application.unpublish(app.appId)
|
||||||
await config.unpublish()
|
|
||||||
|
|
||||||
// doesn't exist in dev
|
// doesn't exist in dev
|
||||||
const devLogs = await config.getAutomationLogs()
|
const devLogs = await config.getAutomationLogs()
|
||||||
|
|
|
@ -76,13 +76,16 @@ const environment = {
|
||||||
DEFAULTS.AUTOMATION_THREAD_TIMEOUT > QUERY_THREAD_TIMEOUT
|
DEFAULTS.AUTOMATION_THREAD_TIMEOUT > QUERY_THREAD_TIMEOUT
|
||||||
? DEFAULTS.AUTOMATION_THREAD_TIMEOUT
|
? DEFAULTS.AUTOMATION_THREAD_TIMEOUT
|
||||||
: QUERY_THREAD_TIMEOUT,
|
: QUERY_THREAD_TIMEOUT,
|
||||||
SQL_MAX_ROWS: process.env.SQL_MAX_ROWS,
|
|
||||||
BB_ADMIN_USER_EMAIL: process.env.BB_ADMIN_USER_EMAIL,
|
BB_ADMIN_USER_EMAIL: process.env.BB_ADMIN_USER_EMAIL,
|
||||||
BB_ADMIN_USER_PASSWORD: process.env.BB_ADMIN_USER_PASSWORD,
|
BB_ADMIN_USER_PASSWORD: process.env.BB_ADMIN_USER_PASSWORD,
|
||||||
PLUGINS_DIR: process.env.PLUGINS_DIR || DEFAULTS.PLUGINS_DIR,
|
PLUGINS_DIR: process.env.PLUGINS_DIR || DEFAULTS.PLUGINS_DIR,
|
||||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
||||||
MAX_IMPORT_SIZE_MB: process.env.MAX_IMPORT_SIZE_MB,
|
MAX_IMPORT_SIZE_MB: process.env.MAX_IMPORT_SIZE_MB,
|
||||||
SESSION_EXPIRY_SECONDS: process.env.SESSION_EXPIRY_SECONDS,
|
SESSION_EXPIRY_SECONDS: process.env.SESSION_EXPIRY_SECONDS,
|
||||||
|
// SQL
|
||||||
|
SQL_MAX_ROWS: process.env.SQL_MAX_ROWS,
|
||||||
|
SQL_LOGGING_ENABLE: process.env.SQL_LOGGING_ENABLE,
|
||||||
|
SQL_ALIASING_DISABLE: process.env.SQL_ALIASING_DISABLE,
|
||||||
// flags
|
// flags
|
||||||
ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS,
|
ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS,
|
||||||
DISABLE_THREADING: process.env.DISABLE_THREADING,
|
DISABLE_THREADING: process.env.DISABLE_THREADING,
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { QueryJson, Datasource } from "@budibase/types"
|
import {
|
||||||
|
QueryJson,
|
||||||
|
Datasource,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
import { getIntegration } from "../index"
|
import { getIntegration } from "../index"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
|
||||||
export async function makeExternalQuery(
|
export async function makeExternalQuery(
|
||||||
datasource: Datasource,
|
datasource: Datasource,
|
||||||
json: QueryJson
|
json: QueryJson
|
||||||
) {
|
): Promise<DatasourcePlusQueryResponse> {
|
||||||
datasource = await sdk.datasources.enrich(datasource)
|
datasource = await sdk.datasources.enrich(datasource)
|
||||||
const Integration = await getIntegration(datasource.source)
|
const Integration = await getIntegration(datasource.source)
|
||||||
// query is the opinionated function
|
// query is the opinionated function
|
||||||
|
|
|
@ -17,7 +17,6 @@ const envLimit = environment.SQL_MAX_ROWS
|
||||||
: null
|
: null
|
||||||
const BASE_LIMIT = envLimit || 5000
|
const BASE_LIMIT = envLimit || 5000
|
||||||
|
|
||||||
type KnexQuery = Knex.QueryBuilder | Knex
|
|
||||||
// these are invalid dates sent by the client, need to convert them to a real max date
|
// these are invalid dates sent by the client, need to convert them to a real max date
|
||||||
const MIN_ISO_DATE = "0000-00-00T00:00:00.000Z"
|
const MIN_ISO_DATE = "0000-00-00T00:00:00.000Z"
|
||||||
const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z"
|
const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z"
|
||||||
|
@ -130,10 +129,15 @@ class InternalBuilder {
|
||||||
|
|
||||||
// right now we only do filters on the specific table being queried
|
// right now we only do filters on the specific table being queried
|
||||||
addFilters(
|
addFilters(
|
||||||
query: KnexQuery,
|
query: Knex.QueryBuilder,
|
||||||
filters: SearchFilters | undefined,
|
filters: SearchFilters | undefined,
|
||||||
opts: { relationship?: boolean; tableName?: string }
|
tableName: string,
|
||||||
): KnexQuery {
|
opts: { aliases?: Record<string, string>; relationship?: boolean }
|
||||||
|
): Knex.QueryBuilder {
|
||||||
|
function getTableName(name: string) {
|
||||||
|
const alias = opts.aliases?.[name]
|
||||||
|
return alias || name
|
||||||
|
}
|
||||||
function iterate(
|
function iterate(
|
||||||
structure: { [key: string]: any },
|
structure: { [key: string]: any },
|
||||||
fn: (key: string, value: any) => void
|
fn: (key: string, value: any) => void
|
||||||
|
@ -142,10 +146,11 @@ class InternalBuilder {
|
||||||
const updatedKey = dbCore.removeKeyNumbering(key)
|
const updatedKey = dbCore.removeKeyNumbering(key)
|
||||||
const isRelationshipField = updatedKey.includes(".")
|
const isRelationshipField = updatedKey.includes(".")
|
||||||
if (!opts.relationship && !isRelationshipField) {
|
if (!opts.relationship && !isRelationshipField) {
|
||||||
fn(`${opts.tableName}.${updatedKey}`, value)
|
fn(`${getTableName(tableName)}.${updatedKey}`, value)
|
||||||
}
|
}
|
||||||
if (opts.relationship && isRelationshipField) {
|
if (opts.relationship && isRelationshipField) {
|
||||||
fn(updatedKey, value)
|
const [filterTableName, property] = updatedKey.split(".")
|
||||||
|
fn(`${getTableName(filterTableName)}.${property}`, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,7 +322,7 @@ class InternalBuilder {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
addSorting(query: KnexQuery, json: QueryJson): KnexQuery {
|
addSorting(query: Knex.QueryBuilder, json: QueryJson): Knex.QueryBuilder {
|
||||||
let { sort, paginate } = json
|
let { sort, paginate } = json
|
||||||
const table = json.meta?.table
|
const table = json.meta?.table
|
||||||
if (sort && Object.keys(sort || {}).length > 0) {
|
if (sort && Object.keys(sort || {}).length > 0) {
|
||||||
|
@ -333,16 +338,28 @@ class InternalBuilder {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tableNameWithSchema(
|
||||||
|
tableName: string,
|
||||||
|
opts?: { alias?: string; schema?: string }
|
||||||
|
) {
|
||||||
|
let withSchema = opts?.schema ? `${opts.schema}.${tableName}` : tableName
|
||||||
|
if (opts?.alias) {
|
||||||
|
withSchema += ` as ${opts.alias}`
|
||||||
|
}
|
||||||
|
return withSchema
|
||||||
|
}
|
||||||
|
|
||||||
addRelationships(
|
addRelationships(
|
||||||
query: KnexQuery,
|
query: Knex.QueryBuilder,
|
||||||
fromTable: string,
|
fromTable: string,
|
||||||
relationships: RelationshipsJson[] | undefined,
|
relationships: RelationshipsJson[] | undefined,
|
||||||
schema: string | undefined
|
schema: string | undefined,
|
||||||
): KnexQuery {
|
aliases?: Record<string, string>
|
||||||
|
): Knex.QueryBuilder {
|
||||||
if (!relationships) {
|
if (!relationships) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
const tableSets: Record<string, [any]> = {}
|
const tableSets: Record<string, [RelationshipsJson]> = {}
|
||||||
// aggregate into table sets (all the same to tables)
|
// aggregate into table sets (all the same to tables)
|
||||||
for (let relationship of relationships) {
|
for (let relationship of relationships) {
|
||||||
const keyObj: { toTable: string; throughTable: string | undefined } = {
|
const keyObj: { toTable: string; throughTable: string | undefined } = {
|
||||||
|
@ -361,10 +378,17 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
for (let [key, relationships] of Object.entries(tableSets)) {
|
for (let [key, relationships] of Object.entries(tableSets)) {
|
||||||
const { toTable, throughTable } = JSON.parse(key)
|
const { toTable, throughTable } = JSON.parse(key)
|
||||||
const toTableWithSchema = schema ? `${schema}.${toTable}` : toTable
|
const toAlias = aliases?.[toTable] || toTable,
|
||||||
const throughTableWithSchema = schema
|
throughAlias = aliases?.[throughTable] || throughTable,
|
||||||
? `${schema}.${throughTable}`
|
fromAlias = aliases?.[fromTable] || fromTable
|
||||||
: throughTable
|
let toTableWithSchema = this.tableNameWithSchema(toTable, {
|
||||||
|
alias: toAlias,
|
||||||
|
schema,
|
||||||
|
})
|
||||||
|
let throughTableWithSchema = this.tableNameWithSchema(throughTable, {
|
||||||
|
alias: throughAlias,
|
||||||
|
schema,
|
||||||
|
})
|
||||||
if (!throughTable) {
|
if (!throughTable) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
query = query.leftJoin(toTableWithSchema, function () {
|
query = query.leftJoin(toTableWithSchema, function () {
|
||||||
|
@ -372,7 +396,7 @@ class InternalBuilder {
|
||||||
const from = relationship.from,
|
const from = relationship.from,
|
||||||
to = relationship.to
|
to = relationship.to
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.orOn(`${fromTable}.${from}`, "=", `${toTable}.${to}`)
|
this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -384,9 +408,9 @@ class InternalBuilder {
|
||||||
const from = relationship.from
|
const from = relationship.from
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.orOn(
|
this.orOn(
|
||||||
`${fromTable}.${fromPrimary}`,
|
`${fromAlias}.${fromPrimary}`,
|
||||||
"=",
|
"=",
|
||||||
`${throughTable}.${from}`
|
`${throughAlias}.${from}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -395,7 +419,7 @@ class InternalBuilder {
|
||||||
const toPrimary = relationship.toPrimary
|
const toPrimary = relationship.toPrimary
|
||||||
const to = relationship.to
|
const to = relationship.to
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.orOn(`${toTable}.${toPrimary}`, `${throughTable}.${to}`)
|
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -403,12 +427,25 @@ class InternalBuilder {
|
||||||
return query.limit(BASE_LIMIT)
|
return query.limit(BASE_LIMIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
create(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
|
knexWithAlias(
|
||||||
const { endpoint, body } = json
|
knex: Knex,
|
||||||
let query: KnexQuery = knex(endpoint.entityId)
|
endpoint: QueryJson["endpoint"],
|
||||||
|
aliases?: QueryJson["tableAliases"]
|
||||||
|
): Knex.QueryBuilder {
|
||||||
|
const tableName = endpoint.entityId
|
||||||
|
const tableAliased = aliases?.[tableName]
|
||||||
|
? `${tableName} as ${aliases?.[tableName]}`
|
||||||
|
: tableName
|
||||||
|
let query = knex(tableAliased)
|
||||||
if (endpoint.schema) {
|
if (endpoint.schema) {
|
||||||
query = query.withSchema(endpoint.schema)
|
query = query.withSchema(endpoint.schema)
|
||||||
}
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
create(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
||||||
|
const { endpoint, body } = json
|
||||||
|
let query = this.knexWithAlias(knex, endpoint)
|
||||||
const parsedBody = parseBody(body)
|
const parsedBody = parseBody(body)
|
||||||
// make sure no null values in body for creation
|
// make sure no null values in body for creation
|
||||||
for (let [key, value] of Object.entries(parsedBody)) {
|
for (let [key, value] of Object.entries(parsedBody)) {
|
||||||
|
@ -425,12 +462,9 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bulkCreate(knex: Knex, json: QueryJson): KnexQuery {
|
bulkCreate(knex: Knex, json: QueryJson): Knex.QueryBuilder {
|
||||||
const { endpoint, body } = json
|
const { endpoint, body } = json
|
||||||
let query: KnexQuery = knex(endpoint.entityId)
|
let query = this.knexWithAlias(knex, endpoint)
|
||||||
if (endpoint.schema) {
|
|
||||||
query = query.withSchema(endpoint.schema)
|
|
||||||
}
|
|
||||||
if (!Array.isArray(body)) {
|
if (!Array.isArray(body)) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
@ -438,8 +472,10 @@ class InternalBuilder {
|
||||||
return query.insert(parsedBody)
|
return query.insert(parsedBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
read(knex: Knex, json: QueryJson, limit: number): KnexQuery {
|
read(knex: Knex, json: QueryJson, limit: number): Knex.QueryBuilder {
|
||||||
let { endpoint, resource, filters, paginate, relationships } = json
|
let { endpoint, resource, filters, paginate, relationships, tableAliases } =
|
||||||
|
json
|
||||||
|
|
||||||
const tableName = endpoint.entityId
|
const tableName = endpoint.entityId
|
||||||
// select all if not specified
|
// select all if not specified
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
|
@ -465,21 +501,20 @@ class InternalBuilder {
|
||||||
foundLimit = paginate.limit
|
foundLimit = paginate.limit
|
||||||
}
|
}
|
||||||
// start building the query
|
// start building the query
|
||||||
let query: KnexQuery = knex(tableName).limit(foundLimit)
|
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
||||||
if (endpoint.schema) {
|
query = query.limit(foundLimit)
|
||||||
query = query.withSchema(endpoint.schema)
|
|
||||||
}
|
|
||||||
if (foundOffset) {
|
if (foundOffset) {
|
||||||
query = query.offset(foundOffset)
|
query = query.offset(foundOffset)
|
||||||
}
|
}
|
||||||
query = this.addFilters(query, filters, { tableName })
|
query = this.addFilters(query, filters, tableName, {
|
||||||
|
aliases: tableAliases,
|
||||||
|
})
|
||||||
// add sorting to pre-query
|
// add sorting to pre-query
|
||||||
query = this.addSorting(query, json)
|
query = this.addSorting(query, json)
|
||||||
// @ts-ignore
|
const alias = tableAliases?.[tableName] || tableName
|
||||||
let preQuery: KnexQuery = knex({
|
let preQuery = knex({
|
||||||
// @ts-ignore
|
[alias]: query,
|
||||||
[tableName]: query,
|
} as any).select(selectStatement) as any
|
||||||
}).select(selectStatement)
|
|
||||||
// have to add after as well (this breaks MS-SQL)
|
// have to add after as well (this breaks MS-SQL)
|
||||||
if (this.client !== SqlClient.MS_SQL) {
|
if (this.client !== SqlClient.MS_SQL) {
|
||||||
preQuery = this.addSorting(preQuery, json)
|
preQuery = this.addSorting(preQuery, json)
|
||||||
|
@ -489,19 +524,22 @@ class InternalBuilder {
|
||||||
preQuery,
|
preQuery,
|
||||||
tableName,
|
tableName,
|
||||||
relationships,
|
relationships,
|
||||||
endpoint.schema
|
endpoint.schema,
|
||||||
|
tableAliases
|
||||||
)
|
)
|
||||||
return this.addFilters(query, filters, { relationship: true })
|
return this.addFilters(query, filters, tableName, {
|
||||||
|
relationship: true,
|
||||||
|
aliases: tableAliases,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
update(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
|
update(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
||||||
const { endpoint, body, filters } = json
|
const { endpoint, body, filters, tableAliases } = json
|
||||||
let query: KnexQuery = knex(endpoint.entityId)
|
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
||||||
if (endpoint.schema) {
|
|
||||||
query = query.withSchema(endpoint.schema)
|
|
||||||
}
|
|
||||||
const parsedBody = parseBody(body)
|
const parsedBody = parseBody(body)
|
||||||
query = this.addFilters(query, filters, { tableName: endpoint.entityId })
|
query = this.addFilters(query, filters, endpoint.entityId, {
|
||||||
|
aliases: tableAliases,
|
||||||
|
})
|
||||||
// mysql can't use returning
|
// mysql can't use returning
|
||||||
if (opts.disableReturning) {
|
if (opts.disableReturning) {
|
||||||
return query.update(parsedBody)
|
return query.update(parsedBody)
|
||||||
|
@ -510,13 +548,12 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
|
delete(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
||||||
const { endpoint, filters } = json
|
const { endpoint, filters, tableAliases } = json
|
||||||
let query: KnexQuery = knex(endpoint.entityId)
|
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
||||||
if (endpoint.schema) {
|
query = this.addFilters(query, filters, endpoint.entityId, {
|
||||||
query = query.withSchema(endpoint.schema)
|
aliases: tableAliases,
|
||||||
}
|
})
|
||||||
query = this.addFilters(query, filters, { tableName: endpoint.entityId })
|
|
||||||
// mysql can't use returning
|
// mysql can't use returning
|
||||||
if (opts.disableReturning) {
|
if (opts.disableReturning) {
|
||||||
return query.delete()
|
return query.delete()
|
||||||
|
@ -540,7 +577,10 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
* which for the sake of mySQL stops adding the returning statement to inserts, updates and deletes.
|
* which for the sake of mySQL stops adding the returning statement to inserts, updates and deletes.
|
||||||
* @return the query ready to be passed to the driver.
|
* @return the query ready to be passed to the driver.
|
||||||
*/
|
*/
|
||||||
_query(json: QueryJson, opts: QueryOptions = {}) {
|
_query(
|
||||||
|
json: QueryJson,
|
||||||
|
opts: QueryOptions = {}
|
||||||
|
): Knex.SqlNative | Knex.Sql | string {
|
||||||
const sqlClient = this.getSqlClient()
|
const sqlClient = this.getSqlClient()
|
||||||
const config: { client: string; useNullAsDefault?: boolean } = {
|
const config: { client: string; useNullAsDefault?: boolean } = {
|
||||||
client: sqlClient,
|
client: sqlClient,
|
||||||
|
@ -549,7 +589,7 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
config.useNullAsDefault = true
|
config.useNullAsDefault = true
|
||||||
}
|
}
|
||||||
const client = knex(config)
|
const client = knex(config)
|
||||||
let query
|
let query: Knex.QueryBuilder
|
||||||
const builder = new InternalBuilder(sqlClient)
|
const builder = new InternalBuilder(sqlClient)
|
||||||
switch (this._operation(json)) {
|
switch (this._operation(json)) {
|
||||||
case Operation.CREATE:
|
case Operation.CREATE:
|
||||||
|
@ -578,7 +618,6 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
if (opts?.disablePreparedStatements) {
|
if (opts?.disablePreparedStatements) {
|
||||||
return query.toString()
|
return query.toString()
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
|
||||||
return query.toSQL().toNative()
|
return query.toSQL().toNative()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -661,6 +700,18 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
}
|
}
|
||||||
return results.length ? results : [{ [operation.toLowerCase()]: true }]
|
return results.length ? results : [{ [operation.toLowerCase()]: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log(query: string, values?: any[]) {
|
||||||
|
if (!environment.SQL_LOGGING_ENABLE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const sqlClient = this.getSqlClient()
|
||||||
|
let string = `[SQL] [${sqlClient.toUpperCase()}] query="${query}"`
|
||||||
|
if (values) {
|
||||||
|
string += ` values="${values.join(", ")}"`
|
||||||
|
}
|
||||||
|
console.log(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SqlQueryBuilder
|
export default SqlQueryBuilder
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
FieldType,
|
FieldType,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { breakExternalTableId } from "../utils"
|
import { breakExternalTableId, SqlClient } from "../utils"
|
||||||
import SchemaBuilder = Knex.SchemaBuilder
|
import SchemaBuilder = Knex.SchemaBuilder
|
||||||
import CreateTableBuilder = Knex.CreateTableBuilder
|
import CreateTableBuilder = Knex.CreateTableBuilder
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
|
@ -135,7 +135,8 @@ function generateSchema(
|
||||||
// need to check if any columns have been deleted
|
// need to check if any columns have been deleted
|
||||||
if (oldTable) {
|
if (oldTable) {
|
||||||
const deletedColumns = Object.entries(oldTable.schema).filter(
|
const deletedColumns = Object.entries(oldTable.schema).filter(
|
||||||
([key, column]) => isIgnoredType(column.type) && table.schema[key] == null
|
([key, column]) =>
|
||||||
|
!isIgnoredType(column.type) && table.schema[key] == null
|
||||||
)
|
)
|
||||||
deletedColumns.forEach(([key, column]) => {
|
deletedColumns.forEach(([key, column]) => {
|
||||||
if (renamed?.old === key || isIgnoredType(column.type)) {
|
if (renamed?.old === key || isIgnoredType(column.type)) {
|
||||||
|
@ -197,13 +198,14 @@ class SqlTableQueryBuilder {
|
||||||
return json.endpoint.operation
|
return json.endpoint.operation
|
||||||
}
|
}
|
||||||
|
|
||||||
_tableQuery(json: QueryJson): any {
|
_tableQuery(json: QueryJson): Knex.Sql | Knex.SqlNative {
|
||||||
let client = knex({ client: this.sqlClient }).schema
|
let client = knex({ client: this.sqlClient }).schema
|
||||||
if (json?.endpoint?.schema) {
|
let schemaName = json?.endpoint?.schema
|
||||||
client = client.withSchema(json.endpoint.schema)
|
if (schemaName) {
|
||||||
|
client = client.withSchema(schemaName)
|
||||||
}
|
}
|
||||||
|
|
||||||
let query
|
let query: Knex.SchemaBuilder
|
||||||
if (!json.table || !json.meta || !json.meta.tables) {
|
if (!json.table || !json.meta || !json.meta.tables) {
|
||||||
throw "Cannot execute without table being specified"
|
throw "Cannot execute without table being specified"
|
||||||
}
|
}
|
||||||
|
@ -215,6 +217,18 @@ class SqlTableQueryBuilder {
|
||||||
if (!json.meta || !json.meta.table) {
|
if (!json.meta || !json.meta.table) {
|
||||||
throw "Must specify old table for update"
|
throw "Must specify old table for update"
|
||||||
}
|
}
|
||||||
|
// renameColumn does not work for MySQL, so return a raw query
|
||||||
|
if (this.sqlClient === SqlClient.MY_SQL && json.meta.renamed) {
|
||||||
|
const updatedColumn = json.meta.renamed.updated
|
||||||
|
const tableName = schemaName
|
||||||
|
? `\`${schemaName}\`.\`${json.table.name}\``
|
||||||
|
: `\`${json.table.name}\``
|
||||||
|
const externalType = json.table.schema[updatedColumn].externalType!
|
||||||
|
return {
|
||||||
|
sql: `alter table ${tableName} change column \`${json.meta.renamed.old}\` \`${updatedColumn}\` ${externalType};`,
|
||||||
|
bindings: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
query = buildUpdateTable(
|
query = buildUpdateTable(
|
||||||
client,
|
client,
|
||||||
json.table,
|
json.table,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
TableRequest,
|
TableRequest,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { OAuth2Client } from "google-auth-library"
|
import { OAuth2Client } from "google-auth-library"
|
||||||
import {
|
import {
|
||||||
|
@ -334,7 +335,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
return { tables: externalTables, errors }
|
return { tables: externalTables, errors }
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(json: QueryJson) {
|
async query(json: QueryJson): Promise<DatasourcePlusQueryResponse> {
|
||||||
const sheet = json.endpoint.entityId
|
const sheet = json.endpoint.entityId
|
||||||
switch (json.endpoint.operation) {
|
switch (json.endpoint.operation) {
|
||||||
case Operation.CREATE:
|
case Operation.CREATE:
|
||||||
|
@ -384,7 +385,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
return await this.client.addSheet({ title: name, headerValues: [name] })
|
await this.client.addSheet({ title: name, headerValues: [name] })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error creating new table in google sheets", err)
|
console.error("Error creating new table in google sheets", err)
|
||||||
throw err
|
throw err
|
||||||
|
@ -450,7 +451,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
try {
|
try {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheetToDelete = this.client.sheetsByTitle[sheet]
|
const sheetToDelete = this.client.sheetsByTitle[sheet]
|
||||||
return await sheetToDelete.delete()
|
await sheetToDelete.delete()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error deleting table in google sheets", err)
|
console.error("Error deleting table in google sheets", err)
|
||||||
throw err
|
throw err
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
SourceName,
|
SourceName,
|
||||||
Schema,
|
Schema,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getSqlQuery,
|
getSqlQuery,
|
||||||
|
@ -329,6 +330,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
operation === Operation.CREATE
|
operation === Operation.CREATE
|
||||||
? `${query.sql}; SELECT SCOPE_IDENTITY() AS id;`
|
? `${query.sql}; SELECT SCOPE_IDENTITY() AS id;`
|
||||||
: query.sql
|
: query.sql
|
||||||
|
this.log(sql, query.bindings)
|
||||||
return await request.query(sql)
|
return await request.query(sql)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
let readableMessage = getReadableErrorMessage(
|
let readableMessage = getReadableErrorMessage(
|
||||||
|
@ -492,7 +494,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
return response.recordset || [{ deleted: true }]
|
return response.recordset || [{ deleted: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(json: QueryJson) {
|
async query(json: QueryJson): Promise<DatasourcePlusQueryResponse> {
|
||||||
const schema = this.config.schema
|
const schema = this.config.schema
|
||||||
await this.connect()
|
await this.connect()
|
||||||
if (schema && schema !== DEFAULT_SCHEMA && json?.endpoint) {
|
if (schema && schema !== DEFAULT_SCHEMA && json?.endpoint) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
SourceName,
|
SourceName,
|
||||||
Schema,
|
Schema,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
FieldType,
|
DatasourcePlusQueryResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getSqlQuery,
|
getSqlQuery,
|
||||||
|
@ -261,6 +261,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
||||||
const bindings = opts?.disableCoercion
|
const bindings = opts?.disableCoercion
|
||||||
? baseBindings
|
? baseBindings
|
||||||
: bindingTypeCoerce(baseBindings)
|
: bindingTypeCoerce(baseBindings)
|
||||||
|
this.log(query.sql, bindings)
|
||||||
// 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]
|
||||||
|
@ -380,7 +381,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
||||||
return results.length ? results : [{ deleted: true }]
|
return results.length ? results : [{ deleted: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(json: QueryJson) {
|
async query(json: QueryJson): Promise<DatasourcePlusQueryResponse> {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
try {
|
try {
|
||||||
const queryFn = (query: any) =>
|
const queryFn = (query: any) =>
|
||||||
|
|
|
@ -12,6 +12,8 @@ import {
|
||||||
ConnectionInfo,
|
ConnectionInfo,
|
||||||
Schema,
|
Schema,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
Row,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
buildExternalTableId,
|
buildExternalTableId,
|
||||||
|
@ -368,6 +370,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
const options: ExecuteOptions = { autoCommit: true }
|
const options: ExecuteOptions = { autoCommit: true }
|
||||||
const bindings: BindParameters = query.bindings || []
|
const bindings: BindParameters = query.bindings || []
|
||||||
|
|
||||||
|
this.log(query.sql, bindings)
|
||||||
return await connection.execute<T>(query.sql, bindings, options)
|
return await connection.execute<T>(query.sql, bindings, options)
|
||||||
} finally {
|
} finally {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
|
@ -419,9 +422,9 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
: [{ deleted: true }]
|
: [{ deleted: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(json: QueryJson) {
|
async query(json: QueryJson): Promise<DatasourcePlusQueryResponse> {
|
||||||
const operation = this._operation(json)
|
const operation = this._operation(json)
|
||||||
const input = this._query(json, { disableReturning: true })
|
const input = this._query(json, { disableReturning: true }) as SqlQuery
|
||||||
if (Array.isArray(input)) {
|
if (Array.isArray(input)) {
|
||||||
const responses = []
|
const responses = []
|
||||||
for (let query of input) {
|
for (let query of input) {
|
||||||
|
@ -443,7 +446,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
if (deletedRows?.rows?.length) {
|
if (deletedRows?.rows?.length) {
|
||||||
return deletedRows.rows
|
return deletedRows.rows
|
||||||
} else if (response.rows?.length) {
|
} else if (response.rows?.length) {
|
||||||
return response.rows
|
return response.rows as Row[]
|
||||||
} else {
|
} else {
|
||||||
// get the last row that was updated
|
// get the last row that was updated
|
||||||
if (
|
if (
|
||||||
|
@ -454,7 +457,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
const lastRow = await this.internalQuery({
|
const lastRow = await this.internalQuery({
|
||||||
sql: `SELECT * FROM \"${json.endpoint.entityId}\" WHERE ROWID = '${response.lastRowid}'`,
|
sql: `SELECT * FROM \"${json.endpoint.entityId}\" WHERE ROWID = '${response.lastRowid}'`,
|
||||||
})
|
})
|
||||||
return lastRow.rows
|
return lastRow.rows as Row[]
|
||||||
} else {
|
} else {
|
||||||
return [{ [operation.toLowerCase()]: true }]
|
return [{ [operation.toLowerCase()]: true }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
SourceName,
|
SourceName,
|
||||||
Schema,
|
Schema,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getSqlQuery,
|
getSqlQuery,
|
||||||
|
@ -268,7 +269,9 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await client.query(query.sql, query.bindings || [])
|
const bindings = query.bindings || []
|
||||||
|
this.log(query.sql, bindings)
|
||||||
|
return await client.query(query.sql, bindings)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
await this.closeConnection()
|
await this.closeConnection()
|
||||||
let readableMessage = getReadableErrorMessage(
|
let readableMessage = getReadableErrorMessage(
|
||||||
|
@ -417,9 +420,9 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||||
return response.rows.length ? response.rows : [{ deleted: true }]
|
return response.rows.length ? response.rows : [{ deleted: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(json: QueryJson) {
|
async query(json: QueryJson): Promise<DatasourcePlusQueryResponse> {
|
||||||
const operation = this._operation(json).toLowerCase()
|
const operation = this._operation(json).toLowerCase()
|
||||||
const input = this._query(json)
|
const input = this._query(json) as SqlQuery
|
||||||
if (Array.isArray(input)) {
|
if (Array.isArray(input)) {
|
||||||
const responses = []
|
const responses = []
|
||||||
for (let query of input) {
|
for (let query of input) {
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
const Sql = require("../base/sql").default
|
import { SqlClient } from "../utils"
|
||||||
const { SqlClient } = require("../utils")
|
import Sql from "../base/sql"
|
||||||
|
import {
|
||||||
|
Operation,
|
||||||
|
QueryJson,
|
||||||
|
TableSourceType,
|
||||||
|
Table,
|
||||||
|
FieldType,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
const TABLE_NAME = "test"
|
const TABLE_NAME = "test"
|
||||||
|
|
||||||
|
@ -17,7 +24,7 @@ function generateReadJson({
|
||||||
filters,
|
filters,
|
||||||
sort,
|
sort,
|
||||||
paginate,
|
paginate,
|
||||||
}: any = {}) {
|
}: any = {}): QueryJson {
|
||||||
return {
|
return {
|
||||||
endpoint: endpoint(table || TABLE_NAME, "READ"),
|
endpoint: endpoint(table || TABLE_NAME, "READ"),
|
||||||
resource: {
|
resource: {
|
||||||
|
@ -28,41 +35,51 @@ function generateReadJson({
|
||||||
paginate: paginate || {},
|
paginate: paginate || {},
|
||||||
meta: {
|
meta: {
|
||||||
table: {
|
table: {
|
||||||
|
type: "table",
|
||||||
|
sourceType: TableSourceType.EXTERNAL,
|
||||||
|
sourceId: "SOURCE_ID",
|
||||||
|
schema: {},
|
||||||
name: table || TABLE_NAME,
|
name: table || TABLE_NAME,
|
||||||
primary: ["id"],
|
primary: ["id"],
|
||||||
},
|
} as any,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateCreateJson(table = TABLE_NAME, body = {}) {
|
function generateCreateJson(table = TABLE_NAME, body = {}): QueryJson {
|
||||||
return {
|
return {
|
||||||
endpoint: endpoint(table, "CREATE"),
|
endpoint: endpoint(table, "CREATE"),
|
||||||
body,
|
body,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateUpdateJson(table = TABLE_NAME, body = {}, filters = {}) {
|
function generateUpdateJson({
|
||||||
|
table = TABLE_NAME,
|
||||||
|
body = {},
|
||||||
|
filters = {},
|
||||||
|
meta = {},
|
||||||
|
}): QueryJson {
|
||||||
return {
|
return {
|
||||||
endpoint: endpoint(table, "UPDATE"),
|
endpoint: endpoint(table, "UPDATE"),
|
||||||
filters,
|
filters,
|
||||||
body,
|
body,
|
||||||
|
meta,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateDeleteJson(table = TABLE_NAME, filters = {}) {
|
function generateDeleteJson(table = TABLE_NAME, filters = {}): QueryJson {
|
||||||
return {
|
return {
|
||||||
endpoint: endpoint(table, "DELETE"),
|
endpoint: endpoint(table, "DELETE"),
|
||||||
filters,
|
filters,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateRelationshipJson(config: { schema?: string } = {}) {
|
function generateRelationshipJson(config: { schema?: string } = {}): QueryJson {
|
||||||
return {
|
return {
|
||||||
endpoint: {
|
endpoint: {
|
||||||
datasourceId: "Postgres",
|
datasourceId: "Postgres",
|
||||||
entityId: "brands",
|
entityId: "brands",
|
||||||
operation: "READ",
|
operation: Operation.READ,
|
||||||
schema: config.schema,
|
schema: config.schema,
|
||||||
},
|
},
|
||||||
resource: {
|
resource: {
|
||||||
|
@ -76,7 +93,6 @@ function generateRelationshipJson(config: { schema?: string } = {}) {
|
||||||
},
|
},
|
||||||
filters: {},
|
filters: {},
|
||||||
sort: {},
|
sort: {},
|
||||||
paginate: {},
|
|
||||||
relationships: [
|
relationships: [
|
||||||
{
|
{
|
||||||
from: "brand_id",
|
from: "brand_id",
|
||||||
|
@ -240,17 +256,17 @@ describe("SQL query builder", () => {
|
||||||
|
|
||||||
it("should test an update statement", () => {
|
it("should test an update statement", () => {
|
||||||
const query = sql._query(
|
const query = sql._query(
|
||||||
generateUpdateJson(
|
generateUpdateJson({
|
||||||
TABLE_NAME,
|
table: TABLE_NAME,
|
||||||
{
|
body: {
|
||||||
name: "John",
|
name: "John",
|
||||||
},
|
},
|
||||||
{
|
filters: {
|
||||||
equal: {
|
equal: {
|
||||||
id: 1001,
|
id: 1001,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
})
|
||||||
)
|
)
|
||||||
expect(query).toEqual({
|
expect(query).toEqual({
|
||||||
bindings: ["John", 1001],
|
bindings: ["John", 1001],
|
||||||
|
@ -502,7 +518,7 @@ describe("SQL query builder", () => {
|
||||||
const query = sql._query(generateRelationshipJson({ schema: "production" }))
|
const query = sql._query(generateRelationshipJson({ schema: "production" }))
|
||||||
expect(query).toEqual({
|
expect(query).toEqual({
|
||||||
bindings: [500, 5000],
|
bindings: [500, 5000],
|
||||||
sql: `select "brands"."brand_id" as "brands.brand_id", "brands"."brand_name" as "brands.brand_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name", "products"."brand_id" as "products.brand_id" from (select * from "production"."brands" limit $1) as "brands" left join "production"."products" on "brands"."brand_id" = "products"."brand_id" limit $2`,
|
sql: `select "brands"."brand_id" as "brands.brand_id", "brands"."brand_name" as "brands.brand_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name", "products"."brand_id" as "products.brand_id" from (select * from "production"."brands" limit $1) as "brands" left join "production"."products" as "products" on "brands"."brand_id" = "products"."brand_id" limit $2`,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -510,7 +526,7 @@ describe("SQL query builder", () => {
|
||||||
const query = sql._query(generateRelationshipJson())
|
const query = sql._query(generateRelationshipJson())
|
||||||
expect(query).toEqual({
|
expect(query).toEqual({
|
||||||
bindings: [500, 5000],
|
bindings: [500, 5000],
|
||||||
sql: `select "brands"."brand_id" as "brands.brand_id", "brands"."brand_name" as "brands.brand_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name", "products"."brand_id" as "products.brand_id" from (select * from "brands" limit $1) as "brands" left join "products" on "brands"."brand_id" = "products"."brand_id" limit $2`,
|
sql: `select "brands"."brand_id" as "brands.brand_id", "brands"."brand_name" as "brands.brand_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name", "products"."brand_id" as "products.brand_id" from (select * from "brands" limit $1) as "brands" left join "products" as "products" on "brands"."brand_id" = "products"."brand_id" limit $2`,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -520,7 +536,7 @@ describe("SQL query builder", () => {
|
||||||
)
|
)
|
||||||
expect(query).toEqual({
|
expect(query).toEqual({
|
||||||
bindings: [500, 5000],
|
bindings: [500, 5000],
|
||||||
sql: `select "stores"."store_id" as "stores.store_id", "stores"."store_name" as "stores.store_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name" from (select * from "production"."stores" limit $1) as "stores" left join "production"."stocks" on "stores"."store_id" = "stocks"."store_id" left join "production"."products" on "products"."product_id" = "stocks"."product_id" limit $2`,
|
sql: `select "stores"."store_id" as "stores.store_id", "stores"."store_name" as "stores.store_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name" from (select * from "production"."stores" limit $1) as "stores" left join "production"."stocks" as "stocks" on "stores"."store_id" = "stocks"."store_id" left join "production"."products" as "products" on "products"."product_id" = "stocks"."product_id" limit $2`,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -682,4 +698,99 @@ describe("SQL query builder", () => {
|
||||||
sql: `insert into \"test\" (\"name\") values ($1) returning *`,
|
sql: `insert into \"test\" (\"name\") values ($1) returning *`,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should be able to rename column for MySQL", () => {
|
||||||
|
const table: Table = {
|
||||||
|
type: "table",
|
||||||
|
sourceType: TableSourceType.EXTERNAL,
|
||||||
|
name: TABLE_NAME,
|
||||||
|
schema: {
|
||||||
|
first_name: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "first_name",
|
||||||
|
externalType: "varchar(45)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourceId: "SOURCE_ID",
|
||||||
|
}
|
||||||
|
const oldTable: Table = {
|
||||||
|
...table,
|
||||||
|
schema: {
|
||||||
|
name: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "name",
|
||||||
|
externalType: "varchar(45)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const query = new Sql(SqlClient.MY_SQL, limit)._query({
|
||||||
|
table,
|
||||||
|
endpoint: {
|
||||||
|
datasourceId: "MySQL",
|
||||||
|
operation: Operation.UPDATE_TABLE,
|
||||||
|
entityId: TABLE_NAME,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
table: oldTable,
|
||||||
|
tables: { [oldTable.name]: oldTable },
|
||||||
|
renamed: {
|
||||||
|
old: "name",
|
||||||
|
updated: "first_name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: [],
|
||||||
|
sql: `alter table \`${TABLE_NAME}\` change column \`name\` \`first_name\` varchar(45);`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to delete a column", () => {
|
||||||
|
const table: Table = {
|
||||||
|
type: "table",
|
||||||
|
sourceType: TableSourceType.EXTERNAL,
|
||||||
|
name: TABLE_NAME,
|
||||||
|
schema: {
|
||||||
|
first_name: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "first_name",
|
||||||
|
externalType: "varchar(45)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourceId: "SOURCE_ID",
|
||||||
|
}
|
||||||
|
const oldTable: Table = {
|
||||||
|
...table,
|
||||||
|
schema: {
|
||||||
|
first_name: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "first_name",
|
||||||
|
externalType: "varchar(45)",
|
||||||
|
},
|
||||||
|
last_name: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "last_name",
|
||||||
|
externalType: "varchar(45)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const query = sql._query({
|
||||||
|
table,
|
||||||
|
endpoint: {
|
||||||
|
datasourceId: "Postgres",
|
||||||
|
operation: Operation.UPDATE_TABLE,
|
||||||
|
entityId: TABLE_NAME,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
table: oldTable,
|
||||||
|
tables: [oldTable],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(query).toEqual([
|
||||||
|
{
|
||||||
|
bindings: [],
|
||||||
|
sql: `alter table "${TABLE_NAME}" drop column "last_name"`,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
import { QueryJson } from "@budibase/types"
|
||||||
|
import { join } from "path"
|
||||||
|
import Sql from "../base/sql"
|
||||||
|
import { SqlClient } from "../utils"
|
||||||
|
import AliasTables from "../../api/controllers/row/alias"
|
||||||
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
|
function multiline(sql: string) {
|
||||||
|
return sql.replace(/\n/g, "").replace(/ +/g, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Captures of real examples", () => {
|
||||||
|
const limit = 5000
|
||||||
|
const relationshipLimit = 100
|
||||||
|
|
||||||
|
function getJson(name: string): QueryJson {
|
||||||
|
return require(join(__dirname, "sqlQueryJson", name)) as QueryJson
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
it("should create a row with relationships", () => {
|
||||||
|
const queryJson = getJson("createWithRelationships.json")
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: ["A Street", 34, "London", "A", "B", "designer", 1990],
|
||||||
|
sql: multiline(`insert into "persons" ("address", "age", "city", "firstname", "lastname", "type", "year")
|
||||||
|
values ($1, $2, $3, $4, $5, $6, $7) returning *`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("read", () => {
|
||||||
|
it("should handle basic retrieval with relationships", () => {
|
||||||
|
const queryJson = getJson("basicFetchWithRelationships.json")
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: [relationshipLimit, limit],
|
||||||
|
sql: multiline(`select "a"."year" as "a.year", "a"."firstname" as "a.firstname", "a"."personid" as "a.personid",
|
||||||
|
"a"."address" as "a.address", "a"."age" as "a.age", "a"."type" as "a.type", "a"."city" as "a.city",
|
||||||
|
"a"."lastname" as "a.lastname", "b"."executorid" as "b.executorid", "b"."taskname" as "b.taskname",
|
||||||
|
"b"."taskid" as "b.taskid", "b"."completed" as "b.completed", "b"."qaid" as "b.qaid",
|
||||||
|
"b"."executorid" as "b.executorid", "b"."taskname" as "b.taskname", "b"."taskid" as "b.taskid",
|
||||||
|
"b"."completed" as "b.completed", "b"."qaid" as "b.qaid"
|
||||||
|
from (select * from "persons" as "a" order by "a"."firstname" asc limit $1) as "a"
|
||||||
|
left join "tasks" as "b" on "a"."personid" = "b"."qaid" or "a"."personid" = "b"."executorid"
|
||||||
|
order by "a"."firstname" asc limit $2`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle filtering by relationship", () => {
|
||||||
|
const queryJson = getJson("filterByRelationship.json")
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: [relationshipLimit, "assembling", limit],
|
||||||
|
sql: multiline(`select "a"."productname" as "a.productname", "a"."productid" as "a.productid",
|
||||||
|
"b"."executorid" as "b.executorid", "b"."taskname" as "b.taskname", "b"."taskid" as "b.taskid",
|
||||||
|
"b"."completed" as "b.completed", "b"."qaid" as "b.qaid"
|
||||||
|
from (select * from "products" as "a" order by "a"."productname" asc limit $1) as "a"
|
||||||
|
left join "products_tasks" as "c" on "a"."productid" = "c"."productid"
|
||||||
|
left join "tasks" as "b" on "b"."taskid" = "c"."taskid" where "b"."taskname" = $2
|
||||||
|
order by "a"."productname" asc limit $3`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle fetching many to many relationships", () => {
|
||||||
|
const queryJson = getJson("fetchManyToMany.json")
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: [relationshipLimit, limit],
|
||||||
|
sql: multiline(`select "a"."productname" as "a.productname", "a"."productid" as "a.productid",
|
||||||
|
"b"."executorid" as "b.executorid", "b"."taskname" as "b.taskname", "b"."taskid" as "b.taskid",
|
||||||
|
"b"."completed" as "b.completed", "b"."qaid" as "b.qaid"
|
||||||
|
from (select * from "products" as "a" order by "a"."productname" asc limit $1) as "a"
|
||||||
|
left join "products_tasks" as "c" on "a"."productid" = "c"."productid"
|
||||||
|
left join "tasks" as "b" on "b"."taskid" = "c"."taskid"
|
||||||
|
order by "a"."productname" asc limit $2`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle enrichment of rows", () => {
|
||||||
|
const queryJson = getJson("enrichRelationship.json")
|
||||||
|
const filters = queryJson.filters?.oneOf?.taskid as number[]
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: [...filters, limit, limit],
|
||||||
|
sql: multiline(`select "a"."executorid" as "a.executorid", "a"."taskname" as "a.taskname",
|
||||||
|
"a"."taskid" as "a.taskid", "a"."completed" as "a.completed", "a"."qaid" as "a.qaid",
|
||||||
|
"b"."productname" as "b.productname", "b"."productid" as "b.productid"
|
||||||
|
from (select * from "tasks" as "a" where "a"."taskid" in ($1, $2) limit $3) as "a"
|
||||||
|
left join "products_tasks" as "c" on "a"."taskid" = "c"."taskid"
|
||||||
|
left join "products" as "b" on "b"."productid" = "c"."productid" limit $4`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should manage query with many relationship filters", () => {
|
||||||
|
const queryJson = getJson("manyRelationshipFilters.json")
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
const filters = queryJson.filters
|
||||||
|
const notEqualsValue = Object.values(filters?.notEqual!)[0]
|
||||||
|
const rangeValue = Object.values(filters?.range!)[0]
|
||||||
|
const equalValue = Object.values(filters?.equal!)[0]
|
||||||
|
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: [
|
||||||
|
notEqualsValue,
|
||||||
|
relationshipLimit,
|
||||||
|
rangeValue.low,
|
||||||
|
rangeValue.high,
|
||||||
|
equalValue,
|
||||||
|
limit,
|
||||||
|
],
|
||||||
|
sql: multiline(`select "a"."executorid" as "a.executorid", "a"."taskname" as "a.taskname", "a"."taskid" as "a.taskid",
|
||||||
|
"a"."completed" as "a.completed", "a"."qaid" as "a.qaid", "b"."productname" as "b.productname",
|
||||||
|
"b"."productid" as "b.productid", "c"."year" as "c.year", "c"."firstname" as "c.firstname",
|
||||||
|
"c"."personid" as "c.personid", "c"."address" as "c.address", "c"."age" as "c.age", "c"."type" as "c.type",
|
||||||
|
"c"."city" as "c.city", "c"."lastname" as "c.lastname", "c"."year" as "c.year", "c"."firstname" as "c.firstname",
|
||||||
|
"c"."personid" as "c.personid", "c"."address" as "c.address", "c"."age" as "c.age", "c"."type" as "c.type",
|
||||||
|
"c"."city" as "c.city", "c"."lastname" as "c.lastname"
|
||||||
|
from (select * from "tasks" as "a" where not "a"."completed" = $1
|
||||||
|
order by "a"."taskname" asc limit $2) as "a"
|
||||||
|
left join "products_tasks" as "d" on "a"."taskid" = "d"."taskid"
|
||||||
|
left join "products" as "b" on "b"."productid" = "d"."productid"
|
||||||
|
left join "persons" as "c" on "a"."executorid" = "c"."personid" or "a"."qaid" = "c"."personid"
|
||||||
|
where "c"."year" between $3 and $4 and "b"."productname" = $5 order by "a"."taskname" asc limit $6`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
it("should handle performing a simple update", () => {
|
||||||
|
const queryJson = getJson("updateSimple.json")
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: [1990, "C", "A Street", 34, "designer", "London", "B", 5],
|
||||||
|
sql: multiline(`update "persons" as "a" set "year" = $1, "firstname" = $2, "address" = $3, "age" = $4,
|
||||||
|
"type" = $5, "city" = $6, "lastname" = $7 where "a"."personid" = $8 returning *`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle performing an update of relationships", () => {
|
||||||
|
const queryJson = getJson("updateRelationship.json")
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: [1990, "C", "A Street", 34, "designer", "London", "B", 5],
|
||||||
|
sql: multiline(`update "persons" as "a" set "year" = $1, "firstname" = $2, "address" = $3, "age" = $4,
|
||||||
|
"type" = $5, "city" = $6, "lastname" = $7 where "a"."personid" = $8 returning *`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("delete", () => {
|
||||||
|
it("should handle deleting with relationships", () => {
|
||||||
|
const queryJson = getJson("deleteSimple.json")
|
||||||
|
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
|
||||||
|
expect(query).toEqual({
|
||||||
|
bindings: ["ddd", ""],
|
||||||
|
sql: multiline(`delete from "compositetable" as "a" where "a"."keypartone" = $1 and "a"."keyparttwo" = $2
|
||||||
|
returning "a"."keyparttwo" as "a.keyparttwo", "a"."keypartone" as "a.keypartone", "a"."name" as "a.name"`),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("check max character aliasing", () => {
|
||||||
|
it("should handle over 'z' max character alias", () => {
|
||||||
|
const tableNames = []
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
tableNames.push(generator.guid())
|
||||||
|
}
|
||||||
|
const aliasing = new AliasTables(tableNames)
|
||||||
|
let alias: string = ""
|
||||||
|
for (let table of tableNames) {
|
||||||
|
alias = aliasing.getAlias(table)
|
||||||
|
}
|
||||||
|
expect(alias).toEqual("cv")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("check some edge cases", () => {
|
||||||
|
const tableNames = ["hello", "world"]
|
||||||
|
|
||||||
|
it("should handle quoted table names", () => {
|
||||||
|
const aliasing = new AliasTables(tableNames)
|
||||||
|
const aliased = aliasing.aliasField(`"hello"."field"`)
|
||||||
|
expect(aliased).toEqual(`"a"."field"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle quoted table names with graves", () => {
|
||||||
|
const aliasing = new AliasTables(tableNames)
|
||||||
|
const aliased = aliasing.aliasField("`hello`.`world`")
|
||||||
|
expect(aliased).toEqual("`a`.`world`")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle table names in table names correctly", () => {
|
||||||
|
const tableNames = ["he", "hell", "hello"]
|
||||||
|
const aliasing = new AliasTables(tableNames)
|
||||||
|
const aliased1 = aliasing.aliasField("`he`.`world`")
|
||||||
|
const aliased2 = aliasing.aliasField("`hell`.`world`")
|
||||||
|
const aliased3 = aliasing.aliasField("`hello`.`world`")
|
||||||
|
expect(aliased1).toEqual("`a`.`world`")
|
||||||
|
expect(aliased2).toEqual("`b`.`world`")
|
||||||
|
expect(aliased3).toEqual("`c`.`world`")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,183 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"entityId": "persons",
|
||||||
|
"operation": "READ"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.year",
|
||||||
|
"a.firstname",
|
||||||
|
"a.personid",
|
||||||
|
"a.address",
|
||||||
|
"a.age",
|
||||||
|
"a.type",
|
||||||
|
"a.city",
|
||||||
|
"a.lastname",
|
||||||
|
"b.executorid",
|
||||||
|
"b.taskname",
|
||||||
|
"b.taskid",
|
||||||
|
"b.completed",
|
||||||
|
"b.qaid",
|
||||||
|
"b.executorid",
|
||||||
|
"b.taskname",
|
||||||
|
"b.taskid",
|
||||||
|
"b.completed",
|
||||||
|
"b.qaid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {},
|
||||||
|
"sort": {
|
||||||
|
"firstname": {
|
||||||
|
"direction": "ASCENDING"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paginate": {
|
||||||
|
"limit": 100,
|
||||||
|
"page": 1
|
||||||
|
},
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "QA",
|
||||||
|
"from": "personid",
|
||||||
|
"to": "qaid",
|
||||||
|
"aliases": {
|
||||||
|
"tasks": "b",
|
||||||
|
"persons": "a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "executor",
|
||||||
|
"from": "personid",
|
||||||
|
"to": "executorid",
|
||||||
|
"aliases": {
|
||||||
|
"tasks": "b",
|
||||||
|
"persons": "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra": {
|
||||||
|
"idFilter": {}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__persons",
|
||||||
|
"primary": [
|
||||||
|
"personid"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"year": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "year",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "firstname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": true,
|
||||||
|
"name": "personid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "address",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "age",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "options",
|
||||||
|
"externalType": "USER-DEFINED",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "type",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false,
|
||||||
|
"inclusion": [
|
||||||
|
"support",
|
||||||
|
"designer",
|
||||||
|
"programmer",
|
||||||
|
"qa"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "city",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "lastname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"QA": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "QA",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"fieldName": "qaid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "ccb68481c80c34217a4540a2c6c27fe46",
|
||||||
|
"foreignKey": "personid"
|
||||||
|
},
|
||||||
|
"executor": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "executor",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"fieldName": "executorid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "c89530b9770d94bec851e062b5cff3001",
|
||||||
|
"foreignKey": "personid",
|
||||||
|
"tableName": "persons"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "firstname",
|
||||||
|
"views": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tableAliases": {
|
||||||
|
"persons": "a",
|
||||||
|
"tasks": "b"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"entityId": "persons",
|
||||||
|
"operation": "CREATE"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.year",
|
||||||
|
"a.firstname",
|
||||||
|
"a.personid",
|
||||||
|
"a.address",
|
||||||
|
"a.age",
|
||||||
|
"a.type",
|
||||||
|
"a.city",
|
||||||
|
"a.lastname"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {},
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "QA",
|
||||||
|
"from": "personid",
|
||||||
|
"to": "qaid",
|
||||||
|
"aliases": {
|
||||||
|
"tasks": "b",
|
||||||
|
"persons": "a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "executor",
|
||||||
|
"from": "personid",
|
||||||
|
"to": "executorid",
|
||||||
|
"aliases": {
|
||||||
|
"tasks": "b",
|
||||||
|
"persons": "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"year": 1990,
|
||||||
|
"firstname": "A",
|
||||||
|
"address": "A Street",
|
||||||
|
"age": 34,
|
||||||
|
"type": "designer",
|
||||||
|
"city": "London",
|
||||||
|
"lastname": "B"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"idFilter": {}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__persons",
|
||||||
|
"primary": [
|
||||||
|
"personid"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"year": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "year",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "firstname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": true,
|
||||||
|
"name": "personid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "address",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "age",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "options",
|
||||||
|
"externalType": "USER-DEFINED",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "type",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false,
|
||||||
|
"inclusion": [
|
||||||
|
"support",
|
||||||
|
"designer",
|
||||||
|
"programmer",
|
||||||
|
"qa"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "city",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "lastname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"QA": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "QA",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"fieldName": "qaid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "ccb68481c80c34217a4540a2c6c27fe46",
|
||||||
|
"foreignKey": "personid"
|
||||||
|
},
|
||||||
|
"executor": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "executor",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"fieldName": "executorid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "c89530b9770d94bec851e062b5cff3001",
|
||||||
|
"foreignKey": "personid",
|
||||||
|
"tableName": "persons"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "firstname",
|
||||||
|
"views": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tableAliases": {
|
||||||
|
"persons": "a",
|
||||||
|
"tasks": "b"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"entityId": "compositetable",
|
||||||
|
"operation": "DELETE"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.keyparttwo",
|
||||||
|
"a.keypartone",
|
||||||
|
"a.name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"equal": {
|
||||||
|
"keypartone": "ddd",
|
||||||
|
"keyparttwo": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relationships": [],
|
||||||
|
"extra": {
|
||||||
|
"idFilter": {
|
||||||
|
"equal": {
|
||||||
|
"keypartone": "ddd",
|
||||||
|
"keyparttwo": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__compositetable",
|
||||||
|
"primary": [
|
||||||
|
"keypartone",
|
||||||
|
"keyparttwo"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"keyparttwo": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "keyparttwo",
|
||||||
|
"constraints": {
|
||||||
|
"presence": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keypartone": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "keypartone",
|
||||||
|
"constraints": {
|
||||||
|
"presence": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "name",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "keypartone"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tableAliases": {
|
||||||
|
"compositetable": "a"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81",
|
||||||
|
"entityId": "tasks",
|
||||||
|
"operation": "READ"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.executorid",
|
||||||
|
"a.taskname",
|
||||||
|
"a.taskid",
|
||||||
|
"a.completed",
|
||||||
|
"a.qaid",
|
||||||
|
"b.productname",
|
||||||
|
"b.productid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"oneOf": {
|
||||||
|
"taskid": [
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"tableName": "products",
|
||||||
|
"column": "products",
|
||||||
|
"through": "products_tasks",
|
||||||
|
"from": "taskid",
|
||||||
|
"to": "productid",
|
||||||
|
"fromPrimary": "taskid",
|
||||||
|
"toPrimary": "productid",
|
||||||
|
"aliases": {
|
||||||
|
"products_tasks": "c",
|
||||||
|
"products": "b",
|
||||||
|
"tasks": "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra": {
|
||||||
|
"idFilter": {}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__tasks",
|
||||||
|
"primary": [
|
||||||
|
"taskid"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"executorid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "executorid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taskname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "taskname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taskid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": true,
|
||||||
|
"name": "taskid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"completed": {
|
||||||
|
"type": "boolean",
|
||||||
|
"externalType": "boolean",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "completed",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"qaid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "qaid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"tableId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__products",
|
||||||
|
"name": "products",
|
||||||
|
"relationshipType": "many-to-many",
|
||||||
|
"through": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__products_tasks",
|
||||||
|
"type": "link",
|
||||||
|
"_id": "c3b91d00cd36c4cc1a347794725b9adbd",
|
||||||
|
"fieldName": "productid",
|
||||||
|
"throughFrom": "productid",
|
||||||
|
"throughTo": "taskid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "taskname",
|
||||||
|
"sql": true,
|
||||||
|
"views": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tableAliases": {
|
||||||
|
"tasks": "a",
|
||||||
|
"products": "b",
|
||||||
|
"products_tasks": "c"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81",
|
||||||
|
"entityId": "products",
|
||||||
|
"operation": "READ"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.productname",
|
||||||
|
"a.productid",
|
||||||
|
"b.executorid",
|
||||||
|
"b.taskname",
|
||||||
|
"b.taskid",
|
||||||
|
"b.completed",
|
||||||
|
"b.qaid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"string": {},
|
||||||
|
"fuzzy": {},
|
||||||
|
"range": {},
|
||||||
|
"equal": {},
|
||||||
|
"notEqual": {},
|
||||||
|
"empty": {},
|
||||||
|
"notEmpty": {},
|
||||||
|
"contains": {},
|
||||||
|
"notContains": {},
|
||||||
|
"oneOf": {},
|
||||||
|
"containsAny": {}
|
||||||
|
},
|
||||||
|
"sort": {
|
||||||
|
"productname": {
|
||||||
|
"direction": "ASCENDING"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paginate": {
|
||||||
|
"limit": 100,
|
||||||
|
"page": 1
|
||||||
|
},
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "tasks",
|
||||||
|
"through": "products_tasks",
|
||||||
|
"from": "productid",
|
||||||
|
"to": "taskid",
|
||||||
|
"fromPrimary": "productid",
|
||||||
|
"toPrimary": "taskid",
|
||||||
|
"aliases": {
|
||||||
|
"products_tasks": "c",
|
||||||
|
"tasks": "b",
|
||||||
|
"products": "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra": {
|
||||||
|
"idFilter": {}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__products",
|
||||||
|
"primary": [
|
||||||
|
"productid"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"productname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "productname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"productid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": true,
|
||||||
|
"name": "productid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"tableId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__tasks",
|
||||||
|
"name": "tasks",
|
||||||
|
"relationshipType": "many-to-many",
|
||||||
|
"fieldName": "taskid",
|
||||||
|
"through": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__products_tasks",
|
||||||
|
"throughFrom": "taskid",
|
||||||
|
"throughTo": "productid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "c3b91d00cd36c4cc1a347794725b9adbd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "productname"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tableAliases": {
|
||||||
|
"products": "a",
|
||||||
|
"tasks": "b",
|
||||||
|
"products_tasks": "c"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"entityId": "products",
|
||||||
|
"operation": "READ"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.productname",
|
||||||
|
"a.productid",
|
||||||
|
"b.executorid",
|
||||||
|
"b.taskname",
|
||||||
|
"b.taskid",
|
||||||
|
"b.completed",
|
||||||
|
"b.qaid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"equal": {
|
||||||
|
"1:tasks.taskname": "assembling"
|
||||||
|
},
|
||||||
|
"onEmptyFilter": "all"
|
||||||
|
},
|
||||||
|
"sort": {
|
||||||
|
"productname": {
|
||||||
|
"direction": "ASCENDING"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paginate": {
|
||||||
|
"limit": 100,
|
||||||
|
"page": 1
|
||||||
|
},
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "tasks",
|
||||||
|
"through": "products_tasks",
|
||||||
|
"from": "productid",
|
||||||
|
"to": "taskid",
|
||||||
|
"fromPrimary": "productid",
|
||||||
|
"toPrimary": "taskid"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tableAliases": {
|
||||||
|
"products_tasks": "c",
|
||||||
|
"tasks": "b",
|
||||||
|
"products": "a"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__products",
|
||||||
|
"primary": [
|
||||||
|
"productid"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"productname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "productname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"productid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": true,
|
||||||
|
"name": "productid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "tasks",
|
||||||
|
"relationshipType": "many-to-many",
|
||||||
|
"fieldName": "taskid",
|
||||||
|
"through": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__products_tasks",
|
||||||
|
"throughFrom": "taskid",
|
||||||
|
"throughTo": "productid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "ca6862d9ba09146dd8a68e3b5b7055a09"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "productname"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81",
|
||||||
|
"entityId": "tasks",
|
||||||
|
"operation": "READ"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.executorid",
|
||||||
|
"a.taskname",
|
||||||
|
"a.taskid",
|
||||||
|
"a.completed",
|
||||||
|
"a.qaid",
|
||||||
|
"b.productname",
|
||||||
|
"b.productid",
|
||||||
|
"c.year",
|
||||||
|
"c.firstname",
|
||||||
|
"c.personid",
|
||||||
|
"c.address",
|
||||||
|
"c.age",
|
||||||
|
"c.type",
|
||||||
|
"c.city",
|
||||||
|
"c.lastname",
|
||||||
|
"c.year",
|
||||||
|
"c.firstname",
|
||||||
|
"c.personid",
|
||||||
|
"c.address",
|
||||||
|
"c.age",
|
||||||
|
"c.type",
|
||||||
|
"c.city",
|
||||||
|
"c.lastname"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"string": {},
|
||||||
|
"fuzzy": {},
|
||||||
|
"range": {
|
||||||
|
"1:persons.year": {
|
||||||
|
"low": 1990,
|
||||||
|
"high": 2147483647
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"equal": {
|
||||||
|
"2:products.productname": "Computers"
|
||||||
|
},
|
||||||
|
"notEqual": {
|
||||||
|
"3:completed": true
|
||||||
|
},
|
||||||
|
"empty": {},
|
||||||
|
"notEmpty": {},
|
||||||
|
"contains": {},
|
||||||
|
"notContains": {},
|
||||||
|
"oneOf": {},
|
||||||
|
"containsAny": {},
|
||||||
|
"onEmptyFilter": "all"
|
||||||
|
},
|
||||||
|
"sort": {
|
||||||
|
"taskname": {
|
||||||
|
"direction": "ASCENDING"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paginate": {
|
||||||
|
"limit": 100,
|
||||||
|
"page": 1
|
||||||
|
},
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"tableName": "products",
|
||||||
|
"column": "products",
|
||||||
|
"through": "products_tasks",
|
||||||
|
"from": "taskid",
|
||||||
|
"to": "productid",
|
||||||
|
"fromPrimary": "taskid",
|
||||||
|
"toPrimary": "productid",
|
||||||
|
"aliases": {
|
||||||
|
"products_tasks": "d",
|
||||||
|
"products": "b",
|
||||||
|
"tasks": "a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "persons",
|
||||||
|
"column": "tasksToExecute",
|
||||||
|
"from": "executorid",
|
||||||
|
"to": "personid",
|
||||||
|
"aliases": {
|
||||||
|
"persons": "c",
|
||||||
|
"tasks": "a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "persons",
|
||||||
|
"column": "tasksToQA",
|
||||||
|
"from": "qaid",
|
||||||
|
"to": "personid",
|
||||||
|
"aliases": {
|
||||||
|
"persons": "c",
|
||||||
|
"tasks": "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra": {
|
||||||
|
"idFilter": {}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__tasks",
|
||||||
|
"primary": [
|
||||||
|
"taskid"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"executorid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"name": "executorid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
},
|
||||||
|
"autocolumn": true,
|
||||||
|
"autoReason": "foreign_key"
|
||||||
|
},
|
||||||
|
"taskname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "taskname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taskid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": true,
|
||||||
|
"name": "taskid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"completed": {
|
||||||
|
"type": "boolean",
|
||||||
|
"externalType": "boolean",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "completed",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"qaid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"name": "qaid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"tableId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__products",
|
||||||
|
"name": "products",
|
||||||
|
"relationshipType": "many-to-many",
|
||||||
|
"through": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__products_tasks",
|
||||||
|
"type": "link",
|
||||||
|
"_id": "c3b91d00cd36c4cc1a347794725b9adbd",
|
||||||
|
"fieldName": "productid",
|
||||||
|
"throughFrom": "productid",
|
||||||
|
"throughTo": "taskid"
|
||||||
|
},
|
||||||
|
"tasksToExecute": {
|
||||||
|
"tableId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__persons",
|
||||||
|
"name": "tasksToExecute",
|
||||||
|
"relationshipType": "one-to-many",
|
||||||
|
"type": "link",
|
||||||
|
"_id": "c0f440590bda04f28846242156c1dd60b",
|
||||||
|
"foreignKey": "executorid",
|
||||||
|
"fieldName": "personid"
|
||||||
|
},
|
||||||
|
"tasksToQA": {
|
||||||
|
"tableId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81__persons",
|
||||||
|
"name": "tasksToQA",
|
||||||
|
"relationshipType": "one-to-many",
|
||||||
|
"type": "link",
|
||||||
|
"_id": "c5fdf453a0ba743d58e29491d174c974b",
|
||||||
|
"foreignKey": "qaid",
|
||||||
|
"fieldName": "personid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_44a967caf37a435f84fe01cd6dfe8f81",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "taskname",
|
||||||
|
"sql": true,
|
||||||
|
"views": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tableAliases": {
|
||||||
|
"tasks": "a",
|
||||||
|
"products": "b",
|
||||||
|
"persons": "c",
|
||||||
|
"products_tasks": "d"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"entityId": "persons",
|
||||||
|
"operation": "UPDATE"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.year",
|
||||||
|
"a.firstname",
|
||||||
|
"a.personid",
|
||||||
|
"a.address",
|
||||||
|
"a.age",
|
||||||
|
"a.type",
|
||||||
|
"a.city",
|
||||||
|
"a.lastname"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"equal": {
|
||||||
|
"personid": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "QA",
|
||||||
|
"from": "personid",
|
||||||
|
"to": "qaid",
|
||||||
|
"aliases": {
|
||||||
|
"tasks": "b",
|
||||||
|
"persons": "a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "executor",
|
||||||
|
"from": "personid",
|
||||||
|
"to": "executorid",
|
||||||
|
"aliases": {
|
||||||
|
"tasks": "b",
|
||||||
|
"persons": "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"year": 1990,
|
||||||
|
"firstname": "C",
|
||||||
|
"address": "A Street",
|
||||||
|
"age": 34,
|
||||||
|
"type": "designer",
|
||||||
|
"city": "London",
|
||||||
|
"lastname": "B"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"idFilter": {
|
||||||
|
"equal": {
|
||||||
|
"personid": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__persons",
|
||||||
|
"primary": [
|
||||||
|
"personid"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"year": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "year",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "firstname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": true,
|
||||||
|
"name": "personid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "address",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "age",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "options",
|
||||||
|
"externalType": "USER-DEFINED",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "type",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false,
|
||||||
|
"inclusion": [
|
||||||
|
"support",
|
||||||
|
"designer",
|
||||||
|
"programmer",
|
||||||
|
"qa"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "city",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "lastname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"QA": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "QA",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"fieldName": "qaid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "ccb68481c80c34217a4540a2c6c27fe46",
|
||||||
|
"foreignKey": "personid"
|
||||||
|
},
|
||||||
|
"executor": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "executor",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"fieldName": "executorid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "c89530b9770d94bec851e062b5cff3001",
|
||||||
|
"foreignKey": "personid",
|
||||||
|
"tableName": "persons"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "firstname",
|
||||||
|
"views": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tableAliases": {
|
||||||
|
"persons": "a",
|
||||||
|
"tasks": "b"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
{
|
||||||
|
"endpoint": {
|
||||||
|
"datasourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"entityId": "persons",
|
||||||
|
"operation": "UPDATE"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"fields": [
|
||||||
|
"a.year",
|
||||||
|
"a.firstname",
|
||||||
|
"a.personid",
|
||||||
|
"a.address",
|
||||||
|
"a.age",
|
||||||
|
"a.type",
|
||||||
|
"a.city",
|
||||||
|
"a.lastname"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"equal": {
|
||||||
|
"personid": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "QA",
|
||||||
|
"from": "personid",
|
||||||
|
"to": "qaid",
|
||||||
|
"aliases": {
|
||||||
|
"tasks": "b",
|
||||||
|
"persons": "a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"column": "executor",
|
||||||
|
"from": "personid",
|
||||||
|
"to": "executorid",
|
||||||
|
"aliases": {
|
||||||
|
"tasks": "b",
|
||||||
|
"persons": "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"year": 1990,
|
||||||
|
"firstname": "C",
|
||||||
|
"address": "A Street",
|
||||||
|
"age": 34,
|
||||||
|
"type": "designer",
|
||||||
|
"city": "London",
|
||||||
|
"lastname": "B"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"idFilter": {
|
||||||
|
"equal": {
|
||||||
|
"personid": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"table": {
|
||||||
|
"type": "table",
|
||||||
|
"_id": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__persons",
|
||||||
|
"primary": [
|
||||||
|
"personid"
|
||||||
|
],
|
||||||
|
"name": "a",
|
||||||
|
"schema": {
|
||||||
|
"year": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "year",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "firstname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personid": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": true,
|
||||||
|
"name": "personid",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "address",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"type": "number",
|
||||||
|
"externalType": "integer",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "age",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "options",
|
||||||
|
"externalType": "USER-DEFINED",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "type",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false,
|
||||||
|
"inclusion": [
|
||||||
|
"support",
|
||||||
|
"designer",
|
||||||
|
"programmer",
|
||||||
|
"qa"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "city",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"type": "string",
|
||||||
|
"externalType": "character varying",
|
||||||
|
"autocolumn": false,
|
||||||
|
"name": "lastname",
|
||||||
|
"constraints": {
|
||||||
|
"presence": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"QA": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "QA",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"fieldName": "qaid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "ccb68481c80c34217a4540a2c6c27fe46",
|
||||||
|
"foreignKey": "personid"
|
||||||
|
},
|
||||||
|
"executor": {
|
||||||
|
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
|
||||||
|
"name": "executor",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"fieldName": "executorid",
|
||||||
|
"type": "link",
|
||||||
|
"main": true,
|
||||||
|
"_id": "c89530b9770d94bec851e062b5cff3001",
|
||||||
|
"foreignKey": "personid",
|
||||||
|
"tableName": "persons"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
|
||||||
|
"sourceType": "external",
|
||||||
|
"primaryDisplay": "firstname",
|
||||||
|
"views": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tableAliases": {
|
||||||
|
"persons": "a",
|
||||||
|
"tasks": "b"
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,33 @@ import {
|
||||||
DatasourcePlus,
|
DatasourcePlus,
|
||||||
IntegrationBase,
|
IntegrationBase,
|
||||||
Schema,
|
Schema,
|
||||||
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import * as datasources from "./datasources"
|
import * as datasources from "./datasources"
|
||||||
import tableSdk from "../tables"
|
import tableSdk from "../tables"
|
||||||
import { getIntegration } from "../../../integrations"
|
import { getIntegration } from "../../../integrations"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
function checkForSchemaErrors(schema: Record<string, Table>) {
|
||||||
|
const errors: Record<string, string> = {}
|
||||||
|
for (let [tableName, table] of Object.entries(schema)) {
|
||||||
|
if (tableName.includes(".")) {
|
||||||
|
errors[tableName] = "Table names containing dots are not supported."
|
||||||
|
} else {
|
||||||
|
const columnNames = Object.keys(table.schema)
|
||||||
|
const invalidColumnName = columnNames.find(columnName =>
|
||||||
|
columnName.includes(".")
|
||||||
|
)
|
||||||
|
if (invalidColumnName) {
|
||||||
|
errors[
|
||||||
|
tableName
|
||||||
|
] = `Column '${invalidColumnName}' is not supported as it contains a dot.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
export async function buildFilteredSchema(
|
export async function buildFilteredSchema(
|
||||||
datasource: Datasource,
|
datasource: Datasource,
|
||||||
filter?: string[]
|
filter?: string[]
|
||||||
|
@ -30,16 +51,19 @@ export async function buildFilteredSchema(
|
||||||
filteredSchema.errors[key] = schema.errors[key]
|
filteredSchema.errors[key] = schema.errors[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filteredSchema
|
|
||||||
|
return {
|
||||||
|
...filteredSchema,
|
||||||
|
errors: {
|
||||||
|
...filteredSchema.errors,
|
||||||
|
...checkForSchemaErrors(filteredSchema.tables),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildSchemaHelper(datasource: Datasource): Promise<Schema> {
|
async function buildSchemaHelper(datasource: Datasource): Promise<Schema> {
|
||||||
const connector = (await getConnector(datasource)) as DatasourcePlus
|
const connector = (await getConnector(datasource)) as DatasourcePlus
|
||||||
const externalSchema = await connector.buildSchema(
|
return await connector.buildSchema(datasource._id!, datasource.entities!)
|
||||||
datasource._id!,
|
|
||||||
datasource.entities!
|
|
||||||
)
|
|
||||||
return externalSchema
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getConnector(
|
export async function getConnector(
|
||||||
|
|
|
@ -66,11 +66,13 @@ export async function search(options: SearchParams): Promise<{
|
||||||
export interface ExportRowsParams {
|
export interface ExportRowsParams {
|
||||||
tableId: string
|
tableId: string
|
||||||
format: Format
|
format: Format
|
||||||
|
delimiter?: string
|
||||||
rowIds?: string[]
|
rowIds?: string[]
|
||||||
columns?: string[]
|
columns?: string[]
|
||||||
query?: SearchFilters
|
query?: SearchFilters
|
||||||
sort?: string
|
sort?: string
|
||||||
sortOrder?: SortOrder
|
sortOrder?: SortOrder
|
||||||
|
customHeaders?: { [key: string]: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExportRowsResult {
|
export interface ExportRowsResult {
|
||||||
|
|
|
@ -9,10 +9,9 @@ import {
|
||||||
SearchParams,
|
SearchParams,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import * as exporters from "../../../../api/controllers/view/exporters"
|
import * as exporters from "../../../../api/controllers/view/exporters"
|
||||||
import sdk from "../../../../sdk"
|
|
||||||
import { handleRequest } from "../../../../api/controllers/row/external"
|
import { handleRequest } from "../../../../api/controllers/row/external"
|
||||||
import { breakExternalTableId } from "../../../../integrations/utils"
|
import { breakExternalTableId } from "../../../../integrations/utils"
|
||||||
import { cleanExportRows } from "../utils"
|
import sdk from "../../../../sdk"
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
import { ExportRowsParams, ExportRowsResult } from "../search"
|
import { ExportRowsParams, ExportRowsResult } from "../search"
|
||||||
import { HTTPError, db } from "@budibase/backend-core"
|
import { HTTPError, db } from "@budibase/backend-core"
|
||||||
|
@ -101,7 +100,17 @@ export async function search(options: SearchParams) {
|
||||||
export async function exportRows(
|
export async function exportRows(
|
||||||
options: ExportRowsParams
|
options: ExportRowsParams
|
||||||
): Promise<ExportRowsResult> {
|
): Promise<ExportRowsResult> {
|
||||||
const { tableId, format, columns, rowIds, query, sort, sortOrder } = options
|
const {
|
||||||
|
tableId,
|
||||||
|
format,
|
||||||
|
columns,
|
||||||
|
rowIds,
|
||||||
|
query,
|
||||||
|
sort,
|
||||||
|
sortOrder,
|
||||||
|
delimiter,
|
||||||
|
customHeaders,
|
||||||
|
} = options
|
||||||
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
|
|
||||||
let requestQuery: SearchFilters = {}
|
let requestQuery: SearchFilters = {}
|
||||||
|
@ -153,12 +162,23 @@ export async function exportRows(
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = datasource.entities[tableName].schema
|
const schema = datasource.entities[tableName].schema
|
||||||
let exportRows = cleanExportRows(rows, schema, format, columns)
|
let exportRows = sdk.rows.utils.cleanExportRows(
|
||||||
|
rows,
|
||||||
|
schema,
|
||||||
|
format,
|
||||||
|
columns,
|
||||||
|
customHeaders
|
||||||
|
)
|
||||||
|
|
||||||
let content: string
|
let content: string
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case exporters.Format.CSV:
|
case exporters.Format.CSV:
|
||||||
content = exporters.csv(headers ?? Object.keys(schema), exportRows)
|
content = exporters.csv(
|
||||||
|
headers ?? Object.keys(schema),
|
||||||
|
exportRows,
|
||||||
|
delimiter,
|
||||||
|
customHeaders
|
||||||
|
)
|
||||||
break
|
break
|
||||||
case exporters.Format.JSON:
|
case exporters.Format.JSON:
|
||||||
content = exporters.json(exportRows)
|
content = exporters.json(exportRows)
|
||||||
|
|
|
@ -85,7 +85,17 @@ export async function search(options: SearchParams) {
|
||||||
export async function exportRows(
|
export async function exportRows(
|
||||||
options: ExportRowsParams
|
options: ExportRowsParams
|
||||||
): Promise<ExportRowsResult> {
|
): Promise<ExportRowsResult> {
|
||||||
const { tableId, format, rowIds, columns, query, sort, sortOrder } = options
|
const {
|
||||||
|
tableId,
|
||||||
|
format,
|
||||||
|
rowIds,
|
||||||
|
columns,
|
||||||
|
query,
|
||||||
|
sort,
|
||||||
|
sortOrder,
|
||||||
|
delimiter,
|
||||||
|
customHeaders,
|
||||||
|
} = options
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
|
|
||||||
|
@ -125,11 +135,16 @@ export async function exportRows(
|
||||||
rows = result
|
rows = result
|
||||||
}
|
}
|
||||||
|
|
||||||
let exportRows = sdk.rows.utils.cleanExportRows(rows, schema, format, columns)
|
let exportRows = cleanExportRows(rows, schema, format, columns, customHeaders)
|
||||||
if (format === Format.CSV) {
|
if (format === Format.CSV) {
|
||||||
return {
|
return {
|
||||||
fileName: "export.csv",
|
fileName: "export.csv",
|
||||||
content: csv(headers ?? Object.keys(rows[0]), exportRows),
|
content: csv(
|
||||||
|
headers ?? Object.keys(rows[0]),
|
||||||
|
exportRows,
|
||||||
|
delimiter,
|
||||||
|
customHeaders
|
||||||
|
),
|
||||||
}
|
}
|
||||||
} else if (format === Format.JSON) {
|
} else if (format === Format.JSON) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
import cloneDeep from "lodash/cloneDeep"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
import validateJs from "validate.js"
|
import validateJs from "validate.js"
|
||||||
import { FieldType, Row, Table, TableSchema } from "@budibase/types"
|
import {
|
||||||
|
FieldType,
|
||||||
|
QueryJson,
|
||||||
|
Row,
|
||||||
|
Table,
|
||||||
|
TableSchema,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
import { makeExternalQuery } from "../../../integrations/base/query"
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
||||||
import { Format } from "../../../api/controllers/view/exporters"
|
import { Format } from "../../../api/controllers/view/exporters"
|
||||||
import sdk from "../.."
|
import sdk from "../.."
|
||||||
import { isRelationshipColumn } from "../../../db/utils"
|
import { isRelationshipColumn } from "../../../db/utils"
|
||||||
|
|
||||||
export async function getDatasourceAndQuery(json: any) {
|
export async function getDatasourceAndQuery(
|
||||||
|
json: QueryJson
|
||||||
|
): Promise<DatasourcePlusQueryResponse> {
|
||||||
const datasourceId = json.endpoint.datasourceId
|
const datasourceId = json.endpoint.datasourceId
|
||||||
const datasource = await sdk.datasources.get(datasourceId)
|
const datasource = await sdk.datasources.get(datasourceId)
|
||||||
return makeExternalQuery(datasource, json)
|
return makeExternalQuery(datasource, json)
|
||||||
|
@ -16,7 +25,8 @@ export function cleanExportRows(
|
||||||
rows: any[],
|
rows: any[],
|
||||||
schema: TableSchema,
|
schema: TableSchema,
|
||||||
format: string,
|
format: string,
|
||||||
columns?: string[]
|
columns?: string[],
|
||||||
|
customHeaders: { [key: string]: string } = {}
|
||||||
) {
|
) {
|
||||||
let cleanRows = [...rows]
|
let cleanRows = [...rows]
|
||||||
|
|
||||||
|
@ -44,11 +54,27 @@ export function cleanExportRows(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (format === Format.JSON) {
|
||||||
|
// Replace row keys with custom headers
|
||||||
|
for (let row of cleanRows) {
|
||||||
|
renameKeys(customHeaders, row)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleanRows
|
return cleanRows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renameKeys(keysMap: { [key: string]: any }, row: any) {
|
||||||
|
for (const key in keysMap) {
|
||||||
|
Object.defineProperty(
|
||||||
|
row,
|
||||||
|
keysMap[key],
|
||||||
|
Object.getOwnPropertyDescriptor(row, key) || {}
|
||||||
|
)
|
||||||
|
delete row[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isForeignKey(key: string, table: Table) {
|
function isForeignKey(key: string, table: Table) {
|
||||||
const relationships = Object.values(table.schema).filter(isRelationshipColumn)
|
const relationships = Object.values(table.schema).filter(isRelationshipColumn)
|
||||||
return relationships.some(
|
return relationships.some(
|
||||||
|
|
|
@ -1,17 +1,96 @@
|
||||||
import { Response } from "supertest"
|
import { Response } from "supertest"
|
||||||
import { App } from "@budibase/types"
|
import {
|
||||||
|
App,
|
||||||
|
type CreateAppRequest,
|
||||||
|
type FetchAppDefinitionResponse,
|
||||||
|
type FetchAppPackageResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
import TestConfiguration from "../TestConfiguration"
|
import TestConfiguration from "../TestConfiguration"
|
||||||
import { TestAPI } from "./base"
|
import { TestAPI } from "./base"
|
||||||
|
import { AppStatus } from "../../../db/utils"
|
||||||
|
import { constants } from "@budibase/backend-core"
|
||||||
|
|
||||||
export class ApplicationAPI extends TestAPI {
|
export class ApplicationAPI extends TestAPI {
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
super(config)
|
super(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create = async (app: CreateAppRequest): Promise<App> => {
|
||||||
|
const request = this.request
|
||||||
|
.post("/api/applications")
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
|
||||||
|
for (const key of Object.keys(app)) {
|
||||||
|
request.field(key, (app as any)[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.templateFile) {
|
||||||
|
request.attach("templateFile", app.templateFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await request
|
||||||
|
|
||||||
|
if (result.statusCode !== 200) {
|
||||||
|
throw new Error(JSON.stringify(result.body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.body as App
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = async (appId: string): Promise<void> => {
|
||||||
|
await this.request
|
||||||
|
.delete(`/api/applications/${appId}`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
publish = async (
|
||||||
|
appId: string
|
||||||
|
): Promise<{ _id: string; status: string; appUrl: string }> => {
|
||||||
|
// While the publish endpoint does take an :appId parameter, it doesn't
|
||||||
|
// use it. It uses the appId from the context.
|
||||||
|
let headers = {
|
||||||
|
...this.config.defaultHeaders(),
|
||||||
|
[constants.Header.APP_ID]: appId,
|
||||||
|
}
|
||||||
|
const result = await this.request
|
||||||
|
.post(`/api/applications/${appId}/publish`)
|
||||||
|
.set(headers)
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
return result.body as { _id: string; status: string; appUrl: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
unpublish = async (appId: string): Promise<void> => {
|
||||||
|
await this.request
|
||||||
|
.post(`/api/applications/${appId}/unpublish`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect(204)
|
||||||
|
}
|
||||||
|
|
||||||
|
sync = async (
|
||||||
|
appId: string,
|
||||||
|
{ statusCode }: { statusCode: number } = { statusCode: 200 }
|
||||||
|
): Promise<{ message: string }> => {
|
||||||
|
const result = await this.request
|
||||||
|
.post(`/api/applications/${appId}/sync`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(statusCode)
|
||||||
|
return result.body
|
||||||
|
}
|
||||||
|
|
||||||
getRaw = async (appId: string): Promise<Response> => {
|
getRaw = async (appId: string): Promise<Response> => {
|
||||||
|
// While the appPackage endpoint does take an :appId parameter, it doesn't
|
||||||
|
// use it. It uses the appId from the context.
|
||||||
|
let headers = {
|
||||||
|
...this.config.defaultHeaders(),
|
||||||
|
[constants.Header.APP_ID]: appId,
|
||||||
|
}
|
||||||
const result = await this.request
|
const result = await this.request
|
||||||
.get(`/api/applications/${appId}/appPackage`)
|
.get(`/api/applications/${appId}/appPackage`)
|
||||||
.set(this.config.defaultHeaders())
|
.set(headers)
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
return result
|
return result
|
||||||
|
@ -21,4 +100,94 @@ export class ApplicationAPI extends TestAPI {
|
||||||
const result = await this.getRaw(appId)
|
const result = await this.getRaw(appId)
|
||||||
return result.body.application as App
|
return result.body.application as App
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDefinition = async (
|
||||||
|
appId: string
|
||||||
|
): Promise<FetchAppDefinitionResponse> => {
|
||||||
|
const result = await this.request
|
||||||
|
.get(`/api/applications/${appId}/definition`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
return result.body as FetchAppDefinitionResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
getAppPackage = async (appId: string): Promise<FetchAppPackageResponse> => {
|
||||||
|
const result = await this.request
|
||||||
|
.get(`/api/applications/${appId}/appPackage`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
return result.body
|
||||||
|
}
|
||||||
|
|
||||||
|
update = async (
|
||||||
|
appId: string,
|
||||||
|
app: { name?: string; url?: string }
|
||||||
|
): Promise<App> => {
|
||||||
|
const request = this.request
|
||||||
|
.put(`/api/applications/${appId}`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
|
||||||
|
for (const key of Object.keys(app)) {
|
||||||
|
request.field(key, (app as any)[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await request
|
||||||
|
|
||||||
|
if (result.statusCode !== 200) {
|
||||||
|
throw new Error(JSON.stringify(result.body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.body as App
|
||||||
|
}
|
||||||
|
|
||||||
|
updateClient = async (appId: string): Promise<void> => {
|
||||||
|
// While the updateClient endpoint does take an :appId parameter, it doesn't
|
||||||
|
// use it. It uses the appId from the context.
|
||||||
|
let headers = {
|
||||||
|
...this.config.defaultHeaders(),
|
||||||
|
[constants.Header.APP_ID]: appId,
|
||||||
|
}
|
||||||
|
const response = await this.request
|
||||||
|
.post(`/api/applications/${appId}/client/update`)
|
||||||
|
.set(headers)
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
throw new Error(JSON.stringify(response.body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revertClient = async (appId: string): Promise<void> => {
|
||||||
|
// While the revertClient endpoint does take an :appId parameter, it doesn't
|
||||||
|
// use it. It uses the appId from the context.
|
||||||
|
let headers = {
|
||||||
|
...this.config.defaultHeaders(),
|
||||||
|
[constants.Header.APP_ID]: appId,
|
||||||
|
}
|
||||||
|
const response = await this.request
|
||||||
|
.post(`/api/applications/${appId}/client/revert`)
|
||||||
|
.set(headers)
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
throw new Error(JSON.stringify(response.body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = async ({ status }: { status?: AppStatus } = {}): Promise<App[]> => {
|
||||||
|
let query = []
|
||||||
|
if (status) {
|
||||||
|
query.push(`status=${status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.request
|
||||||
|
.get(`/api/applications${query.length ? `?${query.join("&")}` : ""}`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
return result.body as App[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@ export interface ExportRowsRequest {
|
||||||
query?: SearchFilters
|
query?: SearchFilters
|
||||||
sort?: string
|
sort?: string
|
||||||
sortOrder?: SortOrder
|
sortOrder?: SortOrder
|
||||||
|
delimiter?: string
|
||||||
|
customHeaders?: { [key: string]: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExportRowsResponse = ReadStream
|
export type ExportRowsResponse = ReadStream
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { PlanType } from "../../sdk"
|
||||||
|
import type { Layout, App, Screen } from "../../documents"
|
||||||
|
|
||||||
|
export interface CreateAppRequest {
|
||||||
|
name: string
|
||||||
|
url?: string
|
||||||
|
useTemplate?: string
|
||||||
|
templateName?: string
|
||||||
|
templateKey?: string
|
||||||
|
templateFile?: string
|
||||||
|
includeSampleData?: boolean
|
||||||
|
encryptionPassword?: string
|
||||||
|
templateString?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchAppDefinitionResponse {
|
||||||
|
layouts: Layout[]
|
||||||
|
screens: Screen[]
|
||||||
|
libraries: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchAppPackageResponse {
|
||||||
|
application: App
|
||||||
|
licenseType: PlanType
|
||||||
|
screens: Screen[]
|
||||||
|
layouts: Layout[]
|
||||||
|
clientLibPath: string
|
||||||
|
hasLock: boolean
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from "./application"
|
||||||
export * from "./analytics"
|
export * from "./analytics"
|
||||||
export * from "./auth"
|
export * from "./auth"
|
||||||
export * from "./user"
|
export * from "./user"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { User, Document } from "../"
|
import { User, Document, Plugin } from "../"
|
||||||
import { SocketSession } from "../../sdk"
|
import { SocketSession } from "../../sdk"
|
||||||
|
|
||||||
export type AppMetadataErrors = { [key: string]: string[] }
|
export type AppMetadataErrors = { [key: string]: string[] }
|
||||||
|
@ -24,6 +24,8 @@ export interface App extends Document {
|
||||||
icon?: AppIcon
|
icon?: AppIcon
|
||||||
features?: AppFeatures
|
features?: AppFeatures
|
||||||
automations?: AutomationSettings
|
automations?: AutomationSettings
|
||||||
|
usedPlugins?: Plugin[]
|
||||||
|
upgradableVersion?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppInstance {
|
export interface AppInstance {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Table } from "../documents"
|
import { Table, Row } from "../documents"
|
||||||
|
import { QueryJson } from "./search"
|
||||||
|
|
||||||
export const PASSWORD_REPLACEMENT = "--secret-value--"
|
export const PASSWORD_REPLACEMENT = "--secret-value--"
|
||||||
|
|
||||||
|
@ -180,11 +181,25 @@ export interface Schema {
|
||||||
errors: Record<string, string>
|
errors: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return these when an operation occurred but we got no response
|
||||||
|
enum DSPlusOperation {
|
||||||
|
CREATE = "create",
|
||||||
|
READ = "read",
|
||||||
|
UPDATE = "update",
|
||||||
|
DELETE = "delete",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DatasourcePlusQueryResponse =
|
||||||
|
| Row[]
|
||||||
|
| Record<DSPlusOperation, boolean>[]
|
||||||
|
| void
|
||||||
|
|
||||||
export interface DatasourcePlus extends IntegrationBase {
|
export interface DatasourcePlus extends IntegrationBase {
|
||||||
// if the datasource supports the use of bindings directly (to protect against SQL injection)
|
// if the datasource supports the use of bindings directly (to protect against SQL injection)
|
||||||
// this returns the format of the identifier
|
// this returns the format of the identifier
|
||||||
getBindingIdentifier(): string
|
getBindingIdentifier(): string
|
||||||
getStringConcat(parts: string[]): string
|
getStringConcat(parts: string[]): string
|
||||||
|
query(json: QueryJson): Promise<DatasourcePlusQueryResponse>
|
||||||
buildSchema(
|
buildSchema(
|
||||||
datasourceId: string,
|
datasourceId: string,
|
||||||
entities: Record<string, Table>
|
entities: Record<string, Table>
|
||||||
|
|
|
@ -94,6 +94,7 @@ export interface QueryJson {
|
||||||
idFilter?: SearchFilters
|
idFilter?: SearchFilters
|
||||||
}
|
}
|
||||||
relationships?: RelationshipsJson[]
|
relationships?: RelationshipsJson[]
|
||||||
|
tableAliases?: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SqlQuery {
|
export interface SqlQuery {
|
||||||
|
|
|
@ -4,10 +4,10 @@ set -e
|
||||||
if [[ -n $CI ]]
|
if [[ -n $CI ]]
|
||||||
then
|
then
|
||||||
# Running in ci, where resources are limited
|
# Running in ci, where resources are limited
|
||||||
echo "jest --coverage --maxWorkers=2 --forceExit --bail"
|
echo "jest --coverage --maxWorkers=2 --forceExit --bail $@"
|
||||||
jest --coverage --maxWorkers=2 --forceExit --bail
|
jest --coverage --maxWorkers=2 --forceExit --bail $@
|
||||||
else
|
else
|
||||||
# --maxWorkers performs better in development
|
# --maxWorkers performs better in development
|
||||||
echo "jest --coverage --maxWorkers=2 --forceExit"
|
echo "jest --coverage --maxWorkers=2 --forceExit $@"
|
||||||
jest --coverage --maxWorkers=2 --forceExit
|
jest --coverage --maxWorkers=2 --forceExit $@
|
||||||
fi
|
fi
|
|
@ -1,11 +1,10 @@
|
||||||
import { App } from "@budibase/types"
|
import { App, CreateAppRequest } from "@budibase/types"
|
||||||
import { Response } from "node-fetch"
|
import { Response } from "node-fetch"
|
||||||
import {
|
import {
|
||||||
RouteConfig,
|
RouteConfig,
|
||||||
AppPackageResponse,
|
AppPackageResponse,
|
||||||
DeployConfig,
|
DeployConfig,
|
||||||
MessageResponse,
|
MessageResponse,
|
||||||
CreateAppRequest,
|
|
||||||
} from "../../../types"
|
} from "../../../types"
|
||||||
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
|
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
|
||||||
import BaseAPI from "./BaseAPI"
|
import BaseAPI from "./BaseAPI"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { generator } from "../../shared"
|
import { generator } from "../../shared"
|
||||||
import { CreateAppRequest } from "../../types"
|
import { CreateAppRequest } from "@budibase/types"
|
||||||
|
|
||||||
function uniqueWord() {
|
function uniqueWord() {
|
||||||
return generator.word() + generator.hash()
|
return generator.word() + generator.hash()
|
||||||
|
|
|
@ -13,17 +13,6 @@ describe("Internal API - Table Operations", () => {
|
||||||
await config.afterAll()
|
await config.afterAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function createAppFromTemplate() {
|
|
||||||
return config.api.apps.create({
|
|
||||||
name: generator.word(),
|
|
||||||
url: `/${generator.word()}`,
|
|
||||||
useTemplate: "true",
|
|
||||||
templateName: "Near Miss Register",
|
|
||||||
templateKey: "app/near-miss-register",
|
|
||||||
templateFile: undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
it("Create and delete table, columns and rows", async () => {
|
it("Create and delete table, columns and rows", async () => {
|
||||||
// create the app
|
// create the app
|
||||||
await config.createApp(fixtures.apps.appFromTemplate())
|
await config.createApp(fixtures.apps.appFromTemplate())
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { BudibaseInternalAPI } from "../internal-api"
|
import { BudibaseInternalAPI } from "../internal-api"
|
||||||
import { AccountInternalAPI } from "../account-api"
|
import { AccountInternalAPI } from "../account-api"
|
||||||
import { APIRequestOpts, CreateAppRequest, State } from "../types"
|
import { APIRequestOpts, State } from "../types"
|
||||||
import * as fixtures from "../internal-api/fixtures"
|
import * as fixtures from "../internal-api/fixtures"
|
||||||
import { CreateAccountRequest } from "@budibase/types"
|
import { CreateAccountRequest, CreateAppRequest } from "@budibase/types"
|
||||||
|
|
||||||
export default class BudibaseTestConfiguration {
|
export default class BudibaseTestConfiguration {
|
||||||
// apis
|
// apis
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
// TODO: Integrate with budibase
|
|
||||||
export interface CreateAppRequest {
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
useTemplate?: string
|
|
||||||
templateName?: string
|
|
||||||
templateKey?: string
|
|
||||||
templateFile?: string
|
|
||||||
includeSampleData?: boolean
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
export * from "./api"
|
export * from "./api"
|
||||||
export * from "./apiKeyResponse"
|
export * from "./apiKeyResponse"
|
||||||
export * from "./app"
|
|
||||||
export * from "./appPackage"
|
export * from "./appPackage"
|
||||||
export * from "./deploy"
|
export * from "./deploy"
|
||||||
export * from "./newAccount"
|
export * from "./newAccount"
|
||||||
|
|
|
@ -17,12 +17,6 @@ const { nodeExternalsPlugin } = require("esbuild-node-externals")
|
||||||
const svelteCompilePlugin = {
|
const svelteCompilePlugin = {
|
||||||
name: 'svelteCompile',
|
name: 'svelteCompile',
|
||||||
setup(build) {
|
setup(build) {
|
||||||
// This resolve handler is necessary to bundle the Svelte runtime into the the final output,
|
|
||||||
// otherwise the bundled script will attempt to resolve it at runtime
|
|
||||||
build.onResolve({ filter: /svelte\/internal/ }, async () => {
|
|
||||||
return { path: `${process.cwd()}/../../node_modules/svelte/src/runtime/internal/ssr.js` }
|
|
||||||
})
|
|
||||||
|
|
||||||
// Compiles `.svelte` files into JS classes so that they can be directly imported into our
|
// Compiles `.svelte` files into JS classes so that they can be directly imported into our
|
||||||
// Typescript packages
|
// Typescript packages
|
||||||
build.onLoad({ filter: /\.svelte$/ }, async (args) => {
|
build.onLoad({ filter: /\.svelte$/ }, async (args) => {
|
||||||
|
@ -37,7 +31,7 @@ const svelteCompilePlugin = {
|
||||||
contents: js.code,
|
contents: js.code,
|
||||||
// The loader this is passed to, basically how the above provided content is "treated",
|
// The loader this is passed to, basically how the above provided content is "treated",
|
||||||
// the contents provided above will be transpiled and bundled like any other JS file.
|
// the contents provided above will be transpiled and bundled like any other JS file.
|
||||||
loader: 'js',
|
loader: 'js',
|
||||||
// Where to resolve any imports present in the loaded file
|
// Where to resolve any imports present in the loaded file
|
||||||
resolveDir: dir
|
resolveDir: dir
|
||||||
}
|
}
|
||||||
|
@ -80,11 +74,11 @@ async function runBuild(entry, outfile) {
|
||||||
plugins: [
|
plugins: [
|
||||||
svelteCompilePlugin,
|
svelteCompilePlugin,
|
||||||
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
||||||
nodeExternalsPlugin(),
|
nodeExternalsPlugin({
|
||||||
|
allowList: ["@budibase/frontend-core", "svelte"]
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
preserveSymlinks: true,
|
preserveSymlinks: true,
|
||||||
loader: {
|
|
||||||
},
|
|
||||||
metafile: true,
|
metafile: true,
|
||||||
external: [
|
external: [
|
||||||
"deasync",
|
"deasync",
|
||||||
|
|
384
yarn.lock
384
yarn.lock
|
@ -1097,7 +1097,7 @@
|
||||||
"@babel/highlight@^7.23.4":
|
"@babel/highlight@^7.23.4":
|
||||||
version "7.23.4"
|
version "7.23.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
|
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
|
||||||
integrity "sha1-7arfTYIy4alhQy23hQkSB+rQYhs= sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A=="
|
integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-validator-identifier" "^7.22.20"
|
"@babel/helper-validator-identifier" "^7.22.20"
|
||||||
chalk "^2.4.2"
|
chalk "^2.4.2"
|
||||||
|
@ -1988,14 +1988,14 @@
|
||||||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||||
|
|
||||||
"@babel/runtime@^7.10.5":
|
"@babel/runtime@^7.10.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.21.0":
|
||||||
version "7.23.9"
|
version "7.23.9"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
||||||
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.14.0"
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
"@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||||
version "7.23.8"
|
version "7.23.8"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
||||||
integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==
|
integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==
|
||||||
|
@ -3434,9 +3434,9 @@
|
||||||
tar "^6.1.11"
|
tar "^6.1.11"
|
||||||
|
|
||||||
"@mongodb-js/saslprep@^1.1.0":
|
"@mongodb-js/saslprep@^1.1.0":
|
||||||
version "1.1.1"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz#9a6c2516bc9188672c4d953ec99760ba49970da7"
|
resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz#24ec1c4915a65f5c506bb88c081731450d91bb1c"
|
||||||
integrity sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==
|
integrity sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==
|
||||||
dependencies:
|
dependencies:
|
||||||
sparse-bitfield "^3.0.3"
|
sparse-bitfield "^3.0.3"
|
||||||
|
|
||||||
|
@ -4043,70 +4043,70 @@
|
||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
picomatch "^2.3.1"
|
picomatch "^2.3.1"
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi@4.10.0":
|
"@rollup/rollup-android-arm-eabi@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz#786eaf6372be2fc209cc957c14aa9d3ff8fefe6a"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz#38c3abd1955a3c21d492af6b1a1dca4bb1d894d6"
|
||||||
integrity sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==
|
integrity sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64@4.10.0":
|
"@rollup/rollup-android-arm64@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz#0114a042fd6396f4f3233e6171fd5b61a36ed539"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz#3822e929f415627609e53b11cec9a4be806de0e2"
|
||||||
integrity sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==
|
integrity sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64@4.10.0":
|
"@rollup/rollup-darwin-arm64@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz#944d007c1dc71a8c9174d11671c0c34bd74a2c81"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz#6c082de71f481f57df6cfa3701ab2a7afde96f69"
|
||||||
integrity sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==
|
integrity sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64@4.10.0":
|
"@rollup/rollup-darwin-x64@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz#1d08cb4521a058d7736ab1c7fe988daf034a2598"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz#c34ca0d31f3c46a22c9afa0e944403eea0edcfd8"
|
||||||
integrity sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==
|
integrity sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf@4.10.0":
|
"@rollup/rollup-linux-arm-gnueabihf@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz#4763eec1591bf0e99a54ad3d1ef39cb268ed7b19"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz#48e899c1e438629c072889b824a98787a7c2362d"
|
||||||
integrity sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==
|
integrity sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu@4.10.0":
|
"@rollup/rollup-linux-arm64-gnu@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz#e6dae70c53ace836973526c41803b877cffc6f7b"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz#788c2698a119dc229062d40da6ada8a090a73a68"
|
||||||
integrity sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==
|
integrity sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl@4.10.0":
|
"@rollup/rollup-linux-arm64-musl@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz#5692e1a0feba0cc4a933864961afc3211177d242"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz#3882a4e3a564af9e55804beeb67076857b035ab7"
|
||||||
integrity sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==
|
integrity sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu@4.10.0":
|
"@rollup/rollup-linux-riscv64-gnu@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz#fbe3d80f7a7ac54a8847f5bddd1bc6f7b9ccb65f"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz#0c6ad792e1195c12bfae634425a3d2aa0fe93ab7"
|
||||||
integrity sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==
|
integrity sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu@4.10.0":
|
"@rollup/rollup-linux-x64-gnu@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz#3f06b55ccf173446d390d0306643dff62ec99807"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz#9d62485ea0f18d8674033b57aa14fb758f6ec6e3"
|
||||||
integrity sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==
|
integrity sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl@4.10.0":
|
"@rollup/rollup-linux-x64-musl@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz#e4ac9b27041c83d7faab6205f62763103eb317ba"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz#50e8167e28b33c977c1f813def2b2074d1435e05"
|
||||||
integrity sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==
|
integrity sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc@4.10.0":
|
"@rollup/rollup-win32-arm64-msvc@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz#6ad0d4fb0066f240778ee3f61eecf7aa0357f883"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz#68d233272a2004429124494121a42c4aebdc5b8e"
|
||||||
integrity sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==
|
integrity sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc@4.10.0":
|
"@rollup/rollup-win32-ia32-msvc@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz#29d50292381311cc8d3623e73b427b7e2e40a653"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz#366ca62221d1689e3b55a03f4ae12ae9ba595d40"
|
||||||
integrity sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==
|
integrity sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc@4.10.0":
|
"@rollup/rollup-win32-x64-msvc@4.12.0":
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz#4eedd01af3a82c1acb0fe6d837ebf339c4cbf839"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz#9ffdf9ed133a7464f4ae187eb9e1294413fab235"
|
||||||
integrity sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==
|
integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==
|
||||||
|
|
||||||
"@roxi/routify@2.18.0":
|
"@roxi/routify@2.18.0":
|
||||||
version "2.18.0"
|
version "2.18.0"
|
||||||
|
@ -5250,16 +5250,16 @@
|
||||||
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
|
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
|
||||||
|
|
||||||
"@types/chai-subset@^1.3.3":
|
"@types/chai-subset@^1.3.3":
|
||||||
version "1.3.5"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.5.tgz#3fc044451f26985f45625230a7f22284808b0a9a"
|
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
|
||||||
integrity sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==
|
integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/chai" "*"
|
"@types/chai" "*"
|
||||||
|
|
||||||
"@types/chai@*", "@types/chai@^4.3.4":
|
"@types/chai@*", "@types/chai@^4.3.4":
|
||||||
version "4.3.11"
|
version "4.3.9"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.11.tgz#e95050bf79a932cb7305dd130254ccdf9bde671c"
|
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.9.tgz#144d762491967db8c6dea38e03d2206c2623feec"
|
||||||
integrity sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==
|
integrity sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==
|
||||||
|
|
||||||
"@types/chance@1.1.3":
|
"@types/chance@1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
|
@ -5654,10 +5654,10 @@
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
form-data "^3.0.0"
|
form-data "^3.0.0"
|
||||||
|
|
||||||
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.13.4", "@types/node@>=13.7.0", "@types/node@>=8.1.0":
|
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.13.4", "@types/node@>=13.7.0":
|
||||||
version "20.11.2"
|
version "20.10.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.2.tgz#39cea3fe02fbbc2f80ed283e94e1d24f2d3856fb"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.7.tgz#40fe8faf25418a75de9fe68a8775546732a3a901"
|
||||||
integrity sha512-cZShBaVa+UO1LjWWBPmWRR4+/eY/JR/UIEcDlVsw3okjWEu+rB7/mH6X3B/L+qJVHDLjk9QW/y2upp9wp1yDXA==
|
integrity sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
|
@ -5683,10 +5683,17 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.37.tgz#0bfcd173e8e1e328337473a8317e37b3b14fd30d"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.37.tgz#0bfcd173e8e1e328337473a8317e37b3b14fd30d"
|
||||||
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
|
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
|
||||||
|
|
||||||
|
"@types/node@>=8.1.0":
|
||||||
|
version "20.11.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.10.tgz#6c3de8974d65c362f82ee29db6b5adf4205462f9"
|
||||||
|
integrity sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==
|
||||||
|
dependencies:
|
||||||
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
"@types/node@^18.11.18":
|
"@types/node@^18.11.18":
|
||||||
version "18.19.13"
|
version "18.19.10"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.13.tgz#c3e989ca967b862a1f6c8c4148fe31865eedaf1a"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.10.tgz#4de314ab66faf6bc8ba691021a091ddcdf13a158"
|
||||||
integrity sha512-kgnbRDj8ioDyGxoiaXsiu1Ybm/K14ajCgMOkwiqpHrnF7d7QiYRoRqHIpglMMs3DwXinlK4qJ8TZGlj4hfleJg==
|
integrity sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
|
@ -6106,9 +6113,9 @@
|
||||||
integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==
|
integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==
|
||||||
|
|
||||||
"@types/whatwg-url@^11.0.2":
|
"@types/whatwg-url@^11.0.2":
|
||||||
version "11.0.3"
|
version "11.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-11.0.3.tgz#9f584c9a9421f0971029ee504dd62a831cb8f3aa"
|
resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-11.0.4.tgz#ffed0dc8d89d91f62e3f368fcbda222a487c4f63"
|
||||||
integrity sha512-z1ELvMijRL1QmU7QuzDkeYXSF2+dXI0ITKoQsIoVKcNBOiK5RMmWy+pYYxJTHFt8vkpZe7UsvRErQwcxZkjoUw==
|
integrity sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/webidl-conversions" "*"
|
"@types/webidl-conversions" "*"
|
||||||
|
|
||||||
|
@ -6558,16 +6565,11 @@ acorn-walk@^7.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
|
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
|
||||||
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
|
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
|
||||||
|
|
||||||
acorn-walk@^8.0.2, acorn-walk@^8.1.1:
|
acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0:
|
||||||
version "8.2.0"
|
version "8.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
|
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
|
||||||
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
||||||
|
|
||||||
acorn-walk@^8.2.0:
|
|
||||||
version "8.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa"
|
|
||||||
integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==
|
|
||||||
|
|
||||||
acorn@^5.2.1, acorn@^5.7.3:
|
acorn@^5.2.1, acorn@^5.7.3:
|
||||||
version "5.7.4"
|
version "5.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
|
||||||
|
@ -6578,10 +6580,10 @@ acorn@^7.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||||
|
|
||||||
acorn@^8.1.0, acorn@^8.10.0, acorn@^8.11.3, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0:
|
acorn@^8.1.0, acorn@^8.10.0, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0:
|
||||||
version "8.11.3"
|
version "8.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b"
|
||||||
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
|
integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
|
||||||
|
|
||||||
add-stream@^1.0.0:
|
add-stream@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
@ -7030,7 +7032,7 @@ asn1.js@^5.0.0, asn1.js@^5.2.0, asn1.js@^5.4.1:
|
||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
safer-buffer "^2.1.0"
|
safer-buffer "^2.1.0"
|
||||||
|
|
||||||
asn1@^0.2.6, asn1@~0.2.3:
|
asn1@^0.2.4, 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==
|
||||||
|
@ -7081,7 +7083,12 @@ async@^2.6.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.14"
|
lodash "^4.17.14"
|
||||||
|
|
||||||
async@^3.2.1, async@^3.2.3, async@^3.2.4:
|
async@^3.2.1, async@^3.2.3:
|
||||||
|
version "3.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
|
||||||
|
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
|
||||||
|
|
||||||
|
async@^3.2.4:
|
||||||
version "3.2.5"
|
version "3.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
|
||||||
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
|
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
|
||||||
|
@ -7684,6 +7691,11 @@ bufferutil@^4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-gyp-build "^4.3.0"
|
node-gyp-build "^4.3.0"
|
||||||
|
|
||||||
|
buildcheck@0.0.3:
|
||||||
|
version "0.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.3.tgz#70451897a95d80f7807e68fc412eb2e7e35ff4d5"
|
||||||
|
integrity sha512-pziaA+p/wdVImfcbsZLNF32EiWyujlQLwolMqUQE8xpKNOH7KmZQaY8sXN7DGOEzPAElo9QTaeNRfGnf3iOJbA==
|
||||||
|
|
||||||
buildcheck@~0.0.6:
|
buildcheck@~0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
|
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
|
||||||
|
@ -7972,9 +7984,9 @@ catharsis@^0.9.0:
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
|
|
||||||
chai@^4.3.7:
|
chai@^4.3.7:
|
||||||
version "4.4.1"
|
version "4.3.10"
|
||||||
resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1"
|
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384"
|
||||||
integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==
|
integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==
|
||||||
dependencies:
|
dependencies:
|
||||||
assertion-error "^1.1.0"
|
assertion-error "^1.1.0"
|
||||||
check-error "^1.0.3"
|
check-error "^1.0.3"
|
||||||
|
@ -8716,6 +8728,14 @@ cosmiconfig@^8.2.0:
|
||||||
parse-json "^5.0.0"
|
parse-json "^5.0.0"
|
||||||
path-type "^4.0.0"
|
path-type "^4.0.0"
|
||||||
|
|
||||||
|
cpu-features@~0.0.4:
|
||||||
|
version "0.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.4.tgz#0023475bb4f4c525869c162e4108099e35bf19d8"
|
||||||
|
integrity sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==
|
||||||
|
dependencies:
|
||||||
|
buildcheck "0.0.3"
|
||||||
|
nan "^2.15.0"
|
||||||
|
|
||||||
cpu-features@~0.0.9:
|
cpu-features@~0.0.9:
|
||||||
version "0.0.9"
|
version "0.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.9.tgz#5226b92f0f1c63122b0a3eb84cb8335a4de499fc"
|
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.9.tgz#5226b92f0f1c63122b0a3eb84cb8335a4de499fc"
|
||||||
|
@ -9615,9 +9635,9 @@ diff@^4.0.1:
|
||||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||||
|
|
||||||
diff@^5.1.0:
|
diff@^5.1.0:
|
||||||
version "5.2.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
|
||||||
integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==
|
integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
|
||||||
|
|
||||||
diffie-hellman@^5.0.0:
|
diffie-hellman@^5.0.0:
|
||||||
version "5.0.3"
|
version "5.0.3"
|
||||||
|
@ -9673,7 +9693,16 @@ docker-modem@^3.0.0:
|
||||||
split-ca "^1.0.1"
|
split-ca "^1.0.1"
|
||||||
ssh2 "^1.11.0"
|
ssh2 "^1.11.0"
|
||||||
|
|
||||||
dockerode@^3.2.1, dockerode@^3.3.5:
|
dockerode@^3.2.1:
|
||||||
|
version "3.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.4.tgz#875de614a1be797279caa9fe27e5637cf0e40548"
|
||||||
|
integrity sha512-3EUwuXnCU+RUlQEheDjmBE0B7q66PV9Rw5NiH1sXwINq0M9c5ERP9fxgkw36ZHOtzf4AGEEYySnkx/sACC9EgQ==
|
||||||
|
dependencies:
|
||||||
|
"@balena/dockerignore" "^1.0.2"
|
||||||
|
docker-modem "^3.0.0"
|
||||||
|
tar-fs "~2.0.1"
|
||||||
|
|
||||||
|
dockerode@^3.3.5:
|
||||||
version "3.3.5"
|
version "3.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629"
|
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629"
|
||||||
integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==
|
integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==
|
||||||
|
@ -9803,9 +9832,9 @@ dotenv@8.6.0, dotenv@^8.2.0:
|
||||||
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
||||||
|
|
||||||
dotenv@^16.3.1:
|
dotenv@^16.3.1:
|
||||||
version "16.3.1"
|
version "16.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.1.tgz#1d9931f1d3e5d2959350d1250efab299561f7f11"
|
||||||
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
integrity sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==
|
||||||
|
|
||||||
dotenv@~10.0.0:
|
dotenv@~10.0.0:
|
||||||
version "10.0.0"
|
version "10.0.0"
|
||||||
|
@ -10853,13 +10882,20 @@ fast-xml-parser@4.2.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
strnum "^1.0.5"
|
strnum "^1.0.5"
|
||||||
|
|
||||||
fast-xml-parser@^4.1.3, fast-xml-parser@^4.2.2, fast-xml-parser@^4.2.5:
|
fast-xml-parser@^4.1.3:
|
||||||
version "4.3.3"
|
version "4.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz#aeaf5778392329f17168c40c51bcbfec8ff965be"
|
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz#aeaf5778392329f17168c40c51bcbfec8ff965be"
|
||||||
integrity sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==
|
integrity sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==
|
||||||
dependencies:
|
dependencies:
|
||||||
strnum "^1.0.5"
|
strnum "^1.0.5"
|
||||||
|
|
||||||
|
fast-xml-parser@^4.2.2, fast-xml-parser@^4.2.5:
|
||||||
|
version "4.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz#761e641260706d6e13251c4ef8e3f5694d4b0d79"
|
||||||
|
integrity "sha1-dh5kEmBwbW4TJRxO+OP1aU1LDXk= sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg=="
|
||||||
|
dependencies:
|
||||||
|
strnum "^1.0.5"
|
||||||
|
|
||||||
fastest-levenshtein@^1.0.12:
|
fastest-levenshtein@^1.0.12:
|
||||||
version "1.0.16"
|
version "1.0.16"
|
||||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
|
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
|
||||||
|
@ -10919,7 +10955,7 @@ fetch-cookie@0.11.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tough-cookie "^2.3.3 || ^3.0.1 || ^4.0.0"
|
tough-cookie "^2.3.3 || ^3.0.1 || ^4.0.0"
|
||||||
|
|
||||||
fflate@^0.4.1:
|
fflate@^0.4.1, fflate@^0.4.8:
|
||||||
version "0.4.8"
|
version "0.4.8"
|
||||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
|
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
|
||||||
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
|
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
|
||||||
|
@ -15701,17 +15737,7 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
|
|
||||||
mlly@^1.1.0:
|
mlly@^1.1.0, mlly@^1.2.0:
|
||||||
version "1.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.0.tgz#0ecfbddc706857f5e170ccd28c6b0b9c81d3f548"
|
|
||||||
integrity sha512-YOvg9hfYQmnaB56Yb+KrJE2u0Yzz5zR+sLejEvF4fzwzV1Al6hkf2vyHTwqCRyv0hCi9rVCqVoXpyYevQIRwLQ==
|
|
||||||
dependencies:
|
|
||||||
acorn "^8.11.3"
|
|
||||||
pathe "^1.1.2"
|
|
||||||
pkg-types "^1.0.3"
|
|
||||||
ufo "^1.3.2"
|
|
||||||
|
|
||||||
mlly@^1.2.0:
|
|
||||||
version "1.4.2"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e"
|
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e"
|
||||||
integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==
|
integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==
|
||||||
|
@ -15904,6 +15930,11 @@ named-placeholders@^1.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^7.14.1"
|
lru-cache "^7.14.1"
|
||||||
|
|
||||||
|
nan@^2.15.0, nan@^2.16.0:
|
||||||
|
version "2.17.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
|
||||||
|
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
|
||||||
|
|
||||||
nan@^2.17.0, nan@^2.18.0:
|
nan@^2.17.0, nan@^2.18.0:
|
||||||
version "2.18.0"
|
version "2.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554"
|
||||||
|
@ -17287,11 +17318,6 @@ pathe@^1.1.0, pathe@^1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a"
|
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a"
|
||||||
integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==
|
integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==
|
||||||
|
|
||||||
pathe@^1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
|
|
||||||
integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
|
|
||||||
|
|
||||||
pathval@^1.1.1:
|
pathval@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
|
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
|
||||||
|
@ -17916,10 +17942,18 @@ postgres-interval@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
xtend "^4.0.0"
|
xtend "^4.0.0"
|
||||||
|
|
||||||
posthog-js@^1.13.4, posthog-js@^1.36.0:
|
posthog-js@^1.13.4:
|
||||||
version "1.100.0"
|
version "1.103.1"
|
||||||
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.100.0.tgz#687b9a6e4ed226aa6572f4040b418ea0c8b3d353"
|
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.103.1.tgz#f846c413c28aca204dc1527f49d39f651348f3c4"
|
||||||
integrity sha512-r2XZEiHQ9mBK7D1G9k57I8uYZ2kZTAJ0OCX6K/OOdCWN8jKPhw3h5F9No5weilP6eVAn+hrsy7NvPV7SCX7gMg==
|
integrity sha512-cFXFU4Z4kl/+RUUV4ju1DlfM7dwCGi6H9xWsfhljIhGcBbT8UfS4JGgZGXl9ABQDdgDPb9xciqnysFSsUQshTA==
|
||||||
|
dependencies:
|
||||||
|
fflate "^0.4.8"
|
||||||
|
preact "^10.19.3"
|
||||||
|
|
||||||
|
posthog-js@^1.36.0:
|
||||||
|
version "1.96.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.96.1.tgz#4f9719a24e4e14037b0e72d430194d7cdb576447"
|
||||||
|
integrity sha512-kv1vQqYMt2BV3YHS+wxsbGuP+tz+M3y1AzNhz8TfkpY1HT8W/ONT0i0eQpeRr9Y+d4x/fZ6M4cXG5GMvi9lRCA==
|
||||||
dependencies:
|
dependencies:
|
||||||
fflate "^0.4.1"
|
fflate "^0.4.1"
|
||||||
|
|
||||||
|
@ -18160,6 +18194,11 @@ pprof-format@^2.0.7:
|
||||||
resolved "https://registry.yarnpkg.com/pprof-format/-/pprof-format-2.0.7.tgz#526e4361f8b37d16b2ec4bb0696b5292de5046a4"
|
resolved "https://registry.yarnpkg.com/pprof-format/-/pprof-format-2.0.7.tgz#526e4361f8b37d16b2ec4bb0696b5292de5046a4"
|
||||||
integrity sha512-1qWaGAzwMpaXJP9opRa23nPnt2Egi7RMNoNBptEE/XwHbcn4fC2b/4U4bKc5arkGkIh2ZabpF2bEb+c5GNHEKA==
|
integrity sha512-1qWaGAzwMpaXJP9opRa23nPnt2Egi7RMNoNBptEE/XwHbcn4fC2b/4U4bKc5arkGkIh2ZabpF2bEb+c5GNHEKA==
|
||||||
|
|
||||||
|
preact@^10.19.3:
|
||||||
|
version "10.19.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899"
|
||||||
|
integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==
|
||||||
|
|
||||||
prebuild-install@^7.1.1:
|
prebuild-install@^7.1.1:
|
||||||
version "7.1.1"
|
version "7.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
|
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
|
||||||
|
@ -19375,25 +19414,25 @@ rollup@^3.27.1:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
rollup@^4.9.6:
|
rollup@^4.9.6:
|
||||||
version "4.10.0"
|
version "4.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.10.0.tgz#244c2cb54a8de004a949fe6036a0801be9060456"
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.12.0.tgz#0b6d1e5f3d46bbcf244deec41a7421dc54cc45b5"
|
||||||
integrity sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==
|
integrity sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/estree" "1.0.5"
|
"@types/estree" "1.0.5"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@rollup/rollup-android-arm-eabi" "4.10.0"
|
"@rollup/rollup-android-arm-eabi" "4.12.0"
|
||||||
"@rollup/rollup-android-arm64" "4.10.0"
|
"@rollup/rollup-android-arm64" "4.12.0"
|
||||||
"@rollup/rollup-darwin-arm64" "4.10.0"
|
"@rollup/rollup-darwin-arm64" "4.12.0"
|
||||||
"@rollup/rollup-darwin-x64" "4.10.0"
|
"@rollup/rollup-darwin-x64" "4.12.0"
|
||||||
"@rollup/rollup-linux-arm-gnueabihf" "4.10.0"
|
"@rollup/rollup-linux-arm-gnueabihf" "4.12.0"
|
||||||
"@rollup/rollup-linux-arm64-gnu" "4.10.0"
|
"@rollup/rollup-linux-arm64-gnu" "4.12.0"
|
||||||
"@rollup/rollup-linux-arm64-musl" "4.10.0"
|
"@rollup/rollup-linux-arm64-musl" "4.12.0"
|
||||||
"@rollup/rollup-linux-riscv64-gnu" "4.10.0"
|
"@rollup/rollup-linux-riscv64-gnu" "4.12.0"
|
||||||
"@rollup/rollup-linux-x64-gnu" "4.10.0"
|
"@rollup/rollup-linux-x64-gnu" "4.12.0"
|
||||||
"@rollup/rollup-linux-x64-musl" "4.10.0"
|
"@rollup/rollup-linux-x64-musl" "4.12.0"
|
||||||
"@rollup/rollup-win32-arm64-msvc" "4.10.0"
|
"@rollup/rollup-win32-arm64-msvc" "4.12.0"
|
||||||
"@rollup/rollup-win32-ia32-msvc" "4.10.0"
|
"@rollup/rollup-win32-ia32-msvc" "4.12.0"
|
||||||
"@rollup/rollup-win32-x64-msvc" "4.10.0"
|
"@rollup/rollup-win32-x64-msvc" "4.12.0"
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
rotating-file-stream@3.1.0:
|
rotating-file-stream@3.1.0:
|
||||||
|
@ -19425,7 +19464,14 @@ rxjs@^6.6.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
rxjs@^7.5.5, rxjs@^7.8.1:
|
rxjs@^7.5.5:
|
||||||
|
version "7.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
||||||
|
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.1.0"
|
||||||
|
|
||||||
|
rxjs@^7.8.1:
|
||||||
version "7.8.1"
|
version "7.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
|
||||||
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
|
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
|
||||||
|
@ -20139,7 +20185,18 @@ ssh-remote-port-forward@^1.0.4:
|
||||||
"@types/ssh2" "^0.5.48"
|
"@types/ssh2" "^0.5.48"
|
||||||
ssh2 "^1.4.0"
|
ssh2 "^1.4.0"
|
||||||
|
|
||||||
ssh2@^1.11.0, ssh2@^1.4.0:
|
ssh2@^1.11.0:
|
||||||
|
version "1.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.11.0.tgz#ce60186216971e12f6deb553dcf82322498fe2e4"
|
||||||
|
integrity sha512-nfg0wZWGSsfUe/IBJkXVll3PEZ//YH2guww+mP88gTpuSU4FtZN7zu9JoeTGOyCNx2dTDtT9fOpWwlzyj4uOOw==
|
||||||
|
dependencies:
|
||||||
|
asn1 "^0.2.4"
|
||||||
|
bcrypt-pbkdf "^1.0.2"
|
||||||
|
optionalDependencies:
|
||||||
|
cpu-features "~0.0.4"
|
||||||
|
nan "^2.16.0"
|
||||||
|
|
||||||
|
ssh2@^1.4.0:
|
||||||
version "1.15.0"
|
version "1.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.15.0.tgz#2f998455036a7f89e0df5847efb5421748d9871b"
|
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.15.0.tgz#2f998455036a7f89e0df5847efb5421748d9871b"
|
||||||
integrity sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==
|
integrity sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==
|
||||||
|
@ -20224,9 +20281,9 @@ statuses@2.0.1, statuses@^2.0.0:
|
||||||
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
|
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
|
||||||
|
|
||||||
std-env@^3.3.1:
|
std-env@^3.3.1:
|
||||||
version "3.7.0"
|
version "3.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2"
|
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910"
|
||||||
integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==
|
integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==
|
||||||
|
|
||||||
step@0.0.x:
|
step@0.0.x:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
|
@ -20689,9 +20746,9 @@ svelte-spa-router@^4.0.1:
|
||||||
regexparam "2.0.2"
|
regexparam "2.0.2"
|
||||||
|
|
||||||
svelte@^4.2.10:
|
svelte@^4.2.10:
|
||||||
version "4.2.10"
|
version "4.2.12"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-4.2.10.tgz#3bef8d79ca75eb53cc4d03f9fac1546e60393f77"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-4.2.12.tgz#13d98d2274d24d3ad216c8fdc801511171c70bb1"
|
||||||
integrity sha512-Ep06yCaCdgG1Mafb/Rx8sJ1QS3RW2I2BxGp2Ui9LBHSZ2/tO/aGLc5WqPjgiAP6KAnLJGaIr/zzwQlOo1b8MxA==
|
integrity sha512-d8+wsh5TfPwqVzbm4/HCXC783/KPHV60NvwitJnyTA5lWn1elhXMNWhXGCJ7PwPa8qFUnyJNIyuIRt2mT0WMug==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ampproject/remapping" "^2.2.1"
|
"@ampproject/remapping" "^2.2.1"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||||
|
@ -21120,9 +21177,9 @@ tiny-queue@^0.2.0:
|
||||||
integrity sha512-EijGsv7kzd9I9g0ByCl6h42BWNGUZrlCSejfrb3AKeHC33SGbASu1VDf5O3rRiiUOhAC9CHdZxFPbZu0HmR70A==
|
integrity sha512-EijGsv7kzd9I9g0ByCl6h42BWNGUZrlCSejfrb3AKeHC33SGbASu1VDf5O3rRiiUOhAC9CHdZxFPbZu0HmR70A==
|
||||||
|
|
||||||
tinybench@^2.3.1:
|
tinybench@^2.3.1:
|
||||||
version "2.6.0"
|
version "2.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.6.0.tgz#1423284ee22de07c91b3752c048d2764714b341b"
|
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.1.tgz#3408f6552125e53a5a48adee31261686fd71587e"
|
||||||
integrity sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==
|
integrity sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==
|
||||||
|
|
||||||
tinycolor2@^1.6.0:
|
tinycolor2@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
|
@ -21562,11 +21619,6 @@ ufo@^1.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.1.tgz#e085842f4627c41d4c1b60ebea1f75cdab4ce86b"
|
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.1.tgz#e085842f4627c41d4c1b60ebea1f75cdab4ce86b"
|
||||||
integrity sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==
|
integrity sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==
|
||||||
|
|
||||||
ufo@^1.3.2:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.4.0.tgz#39845b31be81b4f319ab1d99fd20c56cac528d32"
|
|
||||||
integrity sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==
|
|
||||||
|
|
||||||
uglify-js@^3.1.4, uglify-js@^3.7.7:
|
uglify-js@^3.1.4, uglify-js@^3.7.7:
|
||||||
version "3.17.4"
|
version "3.17.4"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
|
||||||
|
@ -21611,9 +21663,9 @@ underscore@~1.13.2:
|
||||||
integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==
|
integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==
|
||||||
|
|
||||||
undici-types@^6.0.1:
|
undici-types@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.0.1.tgz#62e2af9fcd3ce359634175658de39df8d0f37197"
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.6.2.tgz#48c65d30bfcae492c3c89b1d147fed9d43a16b79"
|
||||||
integrity sha512-i9dNdkCziyqGpFxhatR9LITcInbFWh+ExlWkrZQpZHje8FfCcJKgps0IbmMd7D1o8c8syG4pIOV+aKIoC9JEyA==
|
integrity sha512-acoBcoBobgsg3YUEO/Oht8JJCuFYpzWLFKbqEbcEZcXdkQrTzkF/yWj9JoLaFDa6ArI31dFEmNZkCjQZ7mlf7w==
|
||||||
|
|
||||||
undici-types@~5.26.4:
|
undici-types@~5.26.4:
|
||||||
version "5.26.5"
|
version "5.26.5"
|
||||||
|
@ -21626,9 +21678,9 @@ undici@^4.14.1:
|
||||||
integrity sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw==
|
integrity sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw==
|
||||||
|
|
||||||
undici@^6.0.1:
|
undici@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/undici/-/undici-6.0.1.tgz#385572addca36d1c2b280629cb694b726170027e"
|
resolved "https://registry.yarnpkg.com/undici/-/undici-6.6.2.tgz#8dce5ae54e8a3bc7140c2b2a0972b5fde9a88efb"
|
||||||
integrity sha512-eZFYQLeS9BiXpsU0cuFhCwfeda2MnC48EVmmOz/eCjsTgmyTdaHdVsPSC/kwC2GtW2e0uH0HIPbadf3/bRWSxw==
|
integrity sha512-vSqvUE5skSxQJ5sztTZ/CdeJb1Wq0Hf44hlYMciqHghvz+K88U0l7D6u1VsndoFgskDcnU+nG3gYmMzJVzd9Qg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@fastify/busboy" "^2.0.0"
|
"@fastify/busboy" "^2.0.0"
|
||||||
|
|
||||||
|
@ -21966,18 +22018,7 @@ vite-plugin-static-copy@^0.17.0:
|
||||||
fs-extra "^11.1.0"
|
fs-extra "^11.1.0"
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
|
|
||||||
"vite@^3.0.0 || ^4.0.0":
|
"vite@^3.0.0 || ^4.0.0", vite@^4.5.0:
|
||||||
version "4.5.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.2.tgz#d6ea8610e099851dad8c7371599969e0f8b97e82"
|
|
||||||
integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==
|
|
||||||
dependencies:
|
|
||||||
esbuild "^0.18.10"
|
|
||||||
postcss "^8.4.27"
|
|
||||||
rollup "^3.27.1"
|
|
||||||
optionalDependencies:
|
|
||||||
fsevents "~2.3.2"
|
|
||||||
|
|
||||||
vite@^4.5.0:
|
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26"
|
||||||
integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==
|
integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==
|
||||||
|
@ -22611,7 +22652,12 @@ yaml@^1.10.2:
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||||
|
|
||||||
yaml@^2.1.1, yaml@^2.2.2:
|
yaml@^2.1.1:
|
||||||
|
version "2.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144"
|
||||||
|
integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==
|
||||||
|
|
||||||
|
yaml@^2.2.2:
|
||||||
version "2.3.4"
|
version "2.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"
|
||||||
integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==
|
integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==
|
||||||
|
|
Loading…
Reference in New Issue