Merge branch 'develop' of github.com:Budibase/budibase into feature/sql-relationship-filtering
This commit is contained in:
commit
1eb87eddbb
|
@ -133,5 +133,6 @@
|
||||||
.iconText {
|
.iconText {
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
font-size: var(--spectrum-global-dimension-font-size-50);
|
font-size: var(--spectrum-global-dimension-font-size-50);
|
||||||
|
flex: 0 0 34px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -31,7 +31,10 @@
|
||||||
export let menuItems
|
export let menuItems
|
||||||
export let showMenu = false
|
export let showMenu = false
|
||||||
|
|
||||||
let fields = Object.entries(object).map(([name, value]) => ({ name, value }))
|
let fields = Object.entries(object || {}).map(([name, value]) => ({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
}))
|
||||||
let fieldActivity = buildFieldActivity(activity)
|
let fieldActivity = buildFieldActivity(activity)
|
||||||
|
|
||||||
$: object = fields.reduce(
|
$: object = fields.reduce(
|
||||||
|
|
|
@ -219,3 +219,13 @@ export const RestBodyTypes = [
|
||||||
{ name: "raw (XML)", value: "xml" },
|
{ name: "raw (XML)", value: "xml" },
|
||||||
{ name: "raw (Text)", value: "text" },
|
{ name: "raw (Text)", value: "text" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const PaginationTypes = [
|
||||||
|
{ label: "Page number based", value: "page" },
|
||||||
|
{ label: "Cursor based", value: "cursor" },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const PaginationLocations = [
|
||||||
|
{ label: "Query parameters", value: "query" },
|
||||||
|
{ label: "Request body", value: "body" },
|
||||||
|
]
|
||||||
|
|
|
@ -84,7 +84,7 @@ export function customQueryIconText(datasource, query) {
|
||||||
case "read":
|
case "read":
|
||||||
return "GET"
|
return "GET"
|
||||||
case "delete":
|
case "delete":
|
||||||
return "DELETE"
|
return "DEL"
|
||||||
case "patch":
|
case "patch":
|
||||||
return "PATCH"
|
return "PATCH"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// Do not use any aliased imports in common files, as these will be bundled
|
// Do not use any aliased imports in common files, as these will be bundled
|
||||||
// by multiple bundlers which may not be able to resolve them
|
// by multiple bundlers which may not be able to resolve them.
|
||||||
|
// This will eventually be replaced by the new client implementation when we
|
||||||
|
// add a core package.
|
||||||
import { writable, derived, get } from "svelte/store"
|
import { writable, derived, get } from "svelte/store"
|
||||||
import * as API from "../builderStore/api"
|
import * as API from "../builderStore/api"
|
||||||
import { buildLuceneQuery } from "./lucene"
|
import { buildLuceneQuery } from "./lucene"
|
||||||
|
|
|
@ -122,12 +122,16 @@ export const luceneQuery = (docs, query) => {
|
||||||
|
|
||||||
// Process a string match (fails if the value does not start with the string)
|
// Process a string match (fails if the value does not start with the string)
|
||||||
const stringMatch = match("string", (docValue, testValue) => {
|
const stringMatch = match("string", (docValue, testValue) => {
|
||||||
return !docValue || !docValue.startsWith(testValue)
|
return (
|
||||||
|
!docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase())
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Process a fuzzy match (treat the same as starts with when running locally)
|
// Process a fuzzy match (treat the same as starts with when running locally)
|
||||||
const fuzzyMatch = match("fuzzy", (docValue, testValue) => {
|
const fuzzyMatch = match("fuzzy", (docValue, testValue) => {
|
||||||
return !docValue || !docValue.startsWith(testValue)
|
return (
|
||||||
|
!docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase())
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Process a range match
|
// Process a range match
|
||||||
|
|
|
@ -30,6 +30,8 @@
|
||||||
import {
|
import {
|
||||||
RestBodyTypes as bodyTypes,
|
RestBodyTypes as bodyTypes,
|
||||||
SchemaTypeOptions,
|
SchemaTypeOptions,
|
||||||
|
PaginationLocations,
|
||||||
|
PaginationTypes,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import JSONPreview from "components/integration/JSONPreview.svelte"
|
import JSONPreview from "components/integration/JSONPreview.svelte"
|
||||||
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
|
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
|
||||||
|
@ -269,6 +271,9 @@
|
||||||
query.fields.bodyType = RawRestBodyTypes.NONE
|
query.fields.bodyType = RawRestBodyTypes.NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (query && !query.fields.pagination) {
|
||||||
|
query.fields.pagination = {}
|
||||||
|
}
|
||||||
dynamicVariables = getDynamicVariables(datasource, query._id)
|
dynamicVariables = getDynamicVariables(datasource, query._id)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -343,6 +348,42 @@
|
||||||
/>
|
/>
|
||||||
<RestBodyInput bind:bodyType={query.fields.bodyType} bind:query />
|
<RestBodyInput bind:bodyType={query.fields.bodyType} bind:query />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab title="Pagination">
|
||||||
|
<div class="pagination">
|
||||||
|
<Select
|
||||||
|
label="Pagination type"
|
||||||
|
bind:value={query.fields.pagination.type}
|
||||||
|
options={PaginationTypes}
|
||||||
|
placeholder="None"
|
||||||
|
/>
|
||||||
|
{#if query.fields.pagination.type}
|
||||||
|
<Select
|
||||||
|
label="Pagination parameters location"
|
||||||
|
bind:value={query.fields.pagination.location}
|
||||||
|
options={PaginationLocations}
|
||||||
|
placeholer="Choose where to send pagination parameters"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label={query.fields.pagination.type === "page"
|
||||||
|
? "Page number parameter name "
|
||||||
|
: "Request cursor parameter name"}
|
||||||
|
bind:value={query.fields.pagination.pageParam}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label={query.fields.pagination.type === "page"
|
||||||
|
? "Page size parameter name"
|
||||||
|
: "Request limit parameter name"}
|
||||||
|
bind:value={query.fields.pagination.sizeParam}
|
||||||
|
/>
|
||||||
|
{#if query.fields.pagination.type === "cursor"}
|
||||||
|
<Input
|
||||||
|
label="Response body parameter name for cursor"
|
||||||
|
bind:value={query.fields.pagination.responseParam}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
<Tab title="Transformer">
|
<Tab title="Transformer">
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
{#if !$flags.queryTransformerBanner}
|
{#if !$flags.queryTransformerBanner}
|
||||||
|
@ -564,4 +605,9 @@
|
||||||
.auth-select {
|
.auth-select {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
.pagination {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
|
||||||
import { fetchTableData, fetchTableDefinition } from "./tables"
|
|
||||||
import { fetchViewData } from "./views"
|
|
||||||
import { fetchRelationshipData } from "./relationships"
|
|
||||||
import { FieldTypes } from "../constants"
|
|
||||||
import { executeQuery, fetchQueryDefinition } from "./queries"
|
|
||||||
import {
|
|
||||||
convertJSONSchemaToTableSchema,
|
|
||||||
getJSONArrayDatasourceSchema,
|
|
||||||
} from "builder/src/builderStore/jsonUtils"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches all rows for a particular Budibase data source.
|
|
||||||
*/
|
|
||||||
export const fetchDatasource = async dataSource => {
|
|
||||||
if (!dataSource || !dataSource.type) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all rows in data source
|
|
||||||
const { type, tableId, fieldName } = dataSource
|
|
||||||
let rows = [],
|
|
||||||
info = {}
|
|
||||||
if (type === "table") {
|
|
||||||
rows = await fetchTableData(tableId)
|
|
||||||
} else if (type === "view") {
|
|
||||||
rows = await fetchViewData(dataSource)
|
|
||||||
} else if (type === "query") {
|
|
||||||
// Set the default query params
|
|
||||||
let parameters = cloneDeep(dataSource.queryParams || {})
|
|
||||||
for (let param of dataSource.parameters) {
|
|
||||||
if (!parameters[param.name]) {
|
|
||||||
parameters[param.name] = param.default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { data, ...rest } = await executeQuery({
|
|
||||||
queryId: dataSource._id,
|
|
||||||
parameters,
|
|
||||||
})
|
|
||||||
info = rest
|
|
||||||
rows = data
|
|
||||||
} else if (type === FieldTypes.LINK) {
|
|
||||||
rows = await fetchRelationshipData({
|
|
||||||
rowId: dataSource.rowId,
|
|
||||||
tableId: dataSource.rowTableId,
|
|
||||||
fieldName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enrich the result is always an array
|
|
||||||
return { rows: Array.isArray(rows) ? rows : [], info }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the schema of any kind of datasource.
|
|
||||||
*/
|
|
||||||
export const fetchDatasourceSchema = async dataSource => {
|
|
||||||
if (!dataSource) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const { type } = dataSource
|
|
||||||
let schema
|
|
||||||
|
|
||||||
// Nested providers should already have exposed their own schema
|
|
||||||
if (type === "provider") {
|
|
||||||
schema = dataSource.value?.schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field sources have their schema statically defined
|
|
||||||
if (type === "field") {
|
|
||||||
if (dataSource.fieldType === "attachment") {
|
|
||||||
schema = {
|
|
||||||
url: {
|
|
||||||
type: "string",
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: "string",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else if (dataSource.fieldType === "array") {
|
|
||||||
schema = {
|
|
||||||
value: {
|
|
||||||
type: "string",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON arrays need their table definitions fetched.
|
|
||||||
// We can then extract their schema as a subset of the table schema.
|
|
||||||
if (type === "jsonarray") {
|
|
||||||
const table = await fetchTableDefinition(dataSource.tableId)
|
|
||||||
schema = getJSONArrayDatasourceSchema(table?.schema, dataSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tables, views and links can be fetched by table ID
|
|
||||||
if (
|
|
||||||
(type === "table" || type === "view" || type === "link") &&
|
|
||||||
dataSource.tableId
|
|
||||||
) {
|
|
||||||
const table = await fetchTableDefinition(dataSource.tableId)
|
|
||||||
schema = table?.schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queries can be fetched by query ID
|
|
||||||
if (type === "query" && dataSource._id) {
|
|
||||||
const definition = await fetchQueryDefinition(dataSource._id)
|
|
||||||
schema = definition?.schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check
|
|
||||||
if (!schema) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any JSON fields so we can add any top level properties
|
|
||||||
let jsonAdditions = {}
|
|
||||||
Object.keys(schema).forEach(fieldKey => {
|
|
||||||
const fieldSchema = schema[fieldKey]
|
|
||||||
if (fieldSchema?.type === "json") {
|
|
||||||
const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, {
|
|
||||||
squashObjects: true,
|
|
||||||
})
|
|
||||||
Object.keys(jsonSchema).forEach(jsonKey => {
|
|
||||||
jsonAdditions[`${fieldKey}.${jsonKey}`] = {
|
|
||||||
type: jsonSchema[jsonKey].type,
|
|
||||||
nestedJSON: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return { ...schema, ...jsonAdditions }
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
export * from "./rows"
|
export * from "./rows"
|
||||||
export * from "./auth"
|
export * from "./auth"
|
||||||
export * from "./datasources"
|
|
||||||
export * from "./tables"
|
export * from "./tables"
|
||||||
export * from "./attachments"
|
export * from "./attachments"
|
||||||
export * from "./views"
|
export * from "./views"
|
||||||
|
|
|
@ -4,7 +4,7 @@ import API from "./api"
|
||||||
/**
|
/**
|
||||||
* Executes a query against an external data connector.
|
* Executes a query against an external data connector.
|
||||||
*/
|
*/
|
||||||
export const executeQuery = async ({ queryId, parameters }) => {
|
export const executeQuery = async ({ queryId, pagination, parameters }) => {
|
||||||
const query = await fetchQueryDefinition(queryId)
|
const query = await fetchQueryDefinition(queryId)
|
||||||
if (query?.datasourceId == null) {
|
if (query?.datasourceId == null) {
|
||||||
notificationStore.actions.error("That query couldn't be found")
|
notificationStore.actions.error("That query couldn't be found")
|
||||||
|
@ -14,6 +14,7 @@ export const executeQuery = async ({ queryId, parameters }) => {
|
||||||
url: `/api/v2/queries/${queryId}`,
|
url: `/api/v2/queries/${queryId}`,
|
||||||
body: {
|
body: {
|
||||||
parameters,
|
parameters,
|
||||||
|
pagination,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
|
|
|
@ -19,6 +19,16 @@
|
||||||
export let isScreen = false
|
export let isScreen = false
|
||||||
export let isBlock = false
|
export let isBlock = false
|
||||||
|
|
||||||
|
// Ref to the svelte component
|
||||||
|
let ref
|
||||||
|
|
||||||
|
// Initial settings are passed in on first render of the component.
|
||||||
|
// When the first instance of cachedSettings are set, this object is set to
|
||||||
|
// reference cachedSettings, so that mutations to cachedSettings also affect
|
||||||
|
// initialSettings, but it does not get caught by svelte invalidation - which
|
||||||
|
// would happen if we spread cachedSettings directly to the component.
|
||||||
|
let initialSettings
|
||||||
|
|
||||||
// Component settings are the un-enriched settings for this component that
|
// Component settings are the un-enriched settings for this component that
|
||||||
// need to be enriched at this level.
|
// need to be enriched at this level.
|
||||||
// Nested settings are the un-enriched block settings that are to be passed on
|
// Nested settings are the un-enriched block settings that are to be passed on
|
||||||
|
@ -267,16 +277,26 @@
|
||||||
const cacheSettings = (enriched, nested, conditional) => {
|
const cacheSettings = (enriched, nested, conditional) => {
|
||||||
const allSettings = { ...enriched, ...nested, ...conditional }
|
const allSettings = { ...enriched, ...nested, ...conditional }
|
||||||
if (!cachedSettings) {
|
if (!cachedSettings) {
|
||||||
cachedSettings = allSettings
|
cachedSettings = { ...allSettings }
|
||||||
|
initialSettings = cachedSettings
|
||||||
} else {
|
} else {
|
||||||
Object.keys(allSettings).forEach(key => {
|
Object.keys(allSettings).forEach(key => {
|
||||||
if (!propsAreSame(allSettings[key], cachedSettings[key])) {
|
const same = propsAreSame(allSettings[key], cachedSettings[key])
|
||||||
|
if (!same) {
|
||||||
cachedSettings[key] = allSettings[key]
|
cachedSettings[key] = allSettings[key]
|
||||||
|
assignSetting(key, allSettings[key])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assigns a certain setting to this component.
|
||||||
|
// We manually use the svelte $set function to avoid triggering additional
|
||||||
|
// reactive statements.
|
||||||
|
const assignSetting = (key, value) => {
|
||||||
|
ref?.$$set?.({ [key]: value })
|
||||||
|
}
|
||||||
|
|
||||||
// Generates a key used to determine when components need to fully remount.
|
// Generates a key used to determine when components need to fully remount.
|
||||||
// Currently only toggling editing requires remounting.
|
// Currently only toggling editing requires remounting.
|
||||||
const getRenderKey = (id, editing) => {
|
const getRenderKey = (id, editing) => {
|
||||||
|
@ -299,7 +319,7 @@
|
||||||
data-id={id}
|
data-id={id}
|
||||||
data-name={name}
|
data-name={name}
|
||||||
>
|
>
|
||||||
<svelte:component this={constructor} {...cachedSettings}>
|
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
||||||
{#if children.length}
|
{#if children.length}
|
||||||
{#each children as child (child._id)}
|
{#each children as child (child._id)}
|
||||||
<svelte:self instance={child} />
|
<svelte:self instance={child} />
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { ProgressCircle, Pagination } from "@budibase/bbui"
|
import { ProgressCircle, Pagination } from "@budibase/bbui"
|
||||||
import {
|
|
||||||
buildLuceneQuery,
|
|
||||||
luceneQuery,
|
|
||||||
luceneSort,
|
|
||||||
luceneLimit,
|
|
||||||
} from "builder/src/helpers/lucene"
|
|
||||||
import Placeholder from "./Placeholder.svelte"
|
import Placeholder from "./Placeholder.svelte"
|
||||||
|
import { fetchData } from "utils/fetch/fetchData.js"
|
||||||
|
import { buildLuceneQuery } from "builder/src/helpers/lucene"
|
||||||
|
|
||||||
export let dataSource
|
export let dataSource
|
||||||
export let filter
|
export let filter
|
||||||
|
@ -16,85 +12,30 @@
|
||||||
export let limit
|
export let limit
|
||||||
export let paginate
|
export let paginate
|
||||||
|
|
||||||
const { API, styleable, Provider, ActionTypes } = getContext("sdk")
|
const { styleable, Provider, ActionTypes } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
// Loading flag every time data is being fetched
|
// We need to manage our lucene query manually as we want to allow components
|
||||||
let loading = false
|
// to extend it
|
||||||
|
|
||||||
// Loading flag for the initial load
|
|
||||||
// Mark as loaded if we have no datasource so we don't stall forever
|
|
||||||
let loaded = !dataSource
|
|
||||||
let schemaLoaded = false
|
|
||||||
|
|
||||||
// Provider state
|
|
||||||
let rows = []
|
|
||||||
let allRows = []
|
|
||||||
let info = {}
|
|
||||||
let schema = {}
|
|
||||||
let bookmarks = [null]
|
|
||||||
let pageNumber = 0
|
|
||||||
let query = null
|
|
||||||
let queryExtensions = {}
|
let queryExtensions = {}
|
||||||
|
|
||||||
// Sorting can be overridden at run time, so we can't use the prop directly
|
|
||||||
let currentSortColumn = sortColumn
|
|
||||||
let currentSortOrder = sortOrder
|
|
||||||
|
|
||||||
// Reset the current sort state to props if props change
|
|
||||||
$: currentSortColumn = sortColumn
|
|
||||||
$: currentSortOrder = sortOrder
|
|
||||||
|
|
||||||
$: defaultQuery = buildLuceneQuery(filter)
|
$: defaultQuery = buildLuceneQuery(filter)
|
||||||
$: extendQuery(defaultQuery, queryExtensions)
|
$: query = extendQuery(defaultQuery, queryExtensions)
|
||||||
$: internalTable = dataSource?.type === "table"
|
|
||||||
$: nestedProvider = dataSource?.type === "provider"
|
|
||||||
$: hasNextPage = bookmarks[pageNumber + 1] != null
|
|
||||||
$: hasPrevPage = pageNumber > 0
|
|
||||||
$: getSchema(dataSource)
|
|
||||||
$: sortType = getSortType(schema, currentSortColumn)
|
|
||||||
|
|
||||||
// Wait until schema loads before loading data, so that we can determine
|
// Keep our data fetch instance up to date
|
||||||
// the correct sort type first time
|
$: fetch = createFetch(dataSource)
|
||||||
$: {
|
$: fetch.update({
|
||||||
if (schemaLoaded) {
|
|
||||||
fetchData(
|
|
||||||
dataSource,
|
|
||||||
schema,
|
|
||||||
query,
|
query,
|
||||||
|
sortColumn,
|
||||||
|
sortOrder,
|
||||||
limit,
|
limit,
|
||||||
currentSortColumn,
|
paginate,
|
||||||
currentSortOrder,
|
})
|
||||||
sortType,
|
|
||||||
paginate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reactively filter and sort rows if required
|
|
||||||
$: {
|
|
||||||
if (internalTable) {
|
|
||||||
// Internal tables are already processed server-side
|
|
||||||
rows = allRows
|
|
||||||
} else {
|
|
||||||
// For anything else we use client-side implementations to filter, sort
|
|
||||||
// and limit
|
|
||||||
const filtered = luceneQuery(allRows, query)
|
|
||||||
const sorted = luceneSort(
|
|
||||||
filtered,
|
|
||||||
currentSortColumn,
|
|
||||||
currentSortOrder,
|
|
||||||
sortType
|
|
||||||
)
|
|
||||||
rows = luceneLimit(sorted, limit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build our action context
|
// Build our action context
|
||||||
$: actions = [
|
$: actions = [
|
||||||
{
|
{
|
||||||
type: ActionTypes.RefreshDatasource,
|
type: ActionTypes.RefreshDatasource,
|
||||||
callback: () => refresh(),
|
callback: () => fetch.refresh(),
|
||||||
metadata: { dataSource },
|
metadata: { dataSource },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -108,11 +49,15 @@
|
||||||
{
|
{
|
||||||
type: ActionTypes.SetDataProviderSorting,
|
type: ActionTypes.SetDataProviderSorting,
|
||||||
callback: ({ column, order }) => {
|
callback: ({ column, order }) => {
|
||||||
|
let newOptions = {}
|
||||||
if (column) {
|
if (column) {
|
||||||
currentSortColumn = column
|
newOptions.sortColumn = column
|
||||||
}
|
}
|
||||||
if (order) {
|
if (order) {
|
||||||
currentSortOrder = order
|
newOptions.sortOrder = order
|
||||||
|
}
|
||||||
|
if (Object.keys(newOptions)?.length) {
|
||||||
|
fetch.update(newOptions)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -120,166 +65,30 @@
|
||||||
|
|
||||||
// Build our data context
|
// Build our data context
|
||||||
$: dataContext = {
|
$: dataContext = {
|
||||||
rows,
|
rows: $fetch.rows,
|
||||||
info,
|
info: $fetch.info,
|
||||||
schema,
|
schema: $fetch.schema,
|
||||||
rowsLength: rows?.length,
|
rowsLength: $fetch.rows.length,
|
||||||
|
|
||||||
// Undocumented properties. These aren't supposed to be used in builder
|
// Undocumented properties. These aren't supposed to be used in builder
|
||||||
// bindings, but are used internally by other components
|
// bindings, but are used internally by other components
|
||||||
id: $component?.id,
|
id: $component?.id,
|
||||||
state: {
|
state: {
|
||||||
query,
|
query: $fetch.query,
|
||||||
sortColumn: currentSortColumn,
|
sortColumn: $fetch.sortColumn,
|
||||||
sortOrder: currentSortOrder,
|
sortOrder: $fetch.sortOrder,
|
||||||
},
|
},
|
||||||
loaded,
|
loaded: $fetch.loaded,
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSortType = (schema, sortColumn) => {
|
const createFetch = datasource => {
|
||||||
if (!schema || !sortColumn || !schema[sortColumn]) {
|
return fetchData(datasource, {
|
||||||
return "string"
|
|
||||||
}
|
|
||||||
const type = schema?.[sortColumn]?.type
|
|
||||||
return type === "number" ? "number" : "string"
|
|
||||||
}
|
|
||||||
|
|
||||||
const refresh = async () => {
|
|
||||||
if (schemaLoaded && !nestedProvider) {
|
|
||||||
fetchData(
|
|
||||||
dataSource,
|
|
||||||
schema,
|
|
||||||
query,
|
query,
|
||||||
limit,
|
|
||||||
currentSortColumn,
|
|
||||||
currentSortOrder,
|
|
||||||
sortType,
|
|
||||||
paginate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchData = async (
|
|
||||||
dataSource,
|
|
||||||
schema,
|
|
||||||
query,
|
|
||||||
limit,
|
|
||||||
sortColumn,
|
sortColumn,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
sortType,
|
|
||||||
paginate
|
|
||||||
) => {
|
|
||||||
loading = true
|
|
||||||
if (dataSource?.type === "table") {
|
|
||||||
// Sanity check sort column, as using a non-existant column will prevent
|
|
||||||
// results coming back at all
|
|
||||||
const sort = schema?.[sortColumn] ? sortColumn : undefined
|
|
||||||
|
|
||||||
// For internal tables we use server-side processing
|
|
||||||
const res = await API.searchTable({
|
|
||||||
tableId: dataSource.tableId,
|
|
||||||
query,
|
|
||||||
limit,
|
limit,
|
||||||
sort,
|
|
||||||
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
|
||||||
sortType,
|
|
||||||
paginate,
|
paginate,
|
||||||
})
|
})
|
||||||
pageNumber = 0
|
|
||||||
allRows = res.rows
|
|
||||||
if (res.hasNextPage) {
|
|
||||||
bookmarks = [null, res.bookmark]
|
|
||||||
} else {
|
|
||||||
bookmarks = [null]
|
|
||||||
}
|
|
||||||
} else if (dataSource?.type === "provider") {
|
|
||||||
// For providers referencing another provider, just use the rows it
|
|
||||||
// provides
|
|
||||||
allRows = dataSource?.value?.rows || []
|
|
||||||
} else if (
|
|
||||||
dataSource?.type === "field" ||
|
|
||||||
dataSource?.type === "jsonarray"
|
|
||||||
) {
|
|
||||||
// These sources will be available directly from context.
|
|
||||||
// Enrich non object elements into objects to ensure a valid schema.
|
|
||||||
const data = dataSource?.value || []
|
|
||||||
if (Array.isArray(data) && data[0] && typeof data[0] !== "object") {
|
|
||||||
allRows = data.map(value => ({ value }))
|
|
||||||
} else {
|
|
||||||
allRows = data
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For other data sources like queries or views, fetch all rows from the
|
|
||||||
// server
|
|
||||||
const data = await API.fetchDatasource(dataSource)
|
|
||||||
allRows = data.rows
|
|
||||||
info = data.info
|
|
||||||
}
|
|
||||||
loading = false
|
|
||||||
loaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSchema = async dataSource => {
|
|
||||||
let newSchema = (await API.fetchDatasourceSchema(dataSource)) || {}
|
|
||||||
|
|
||||||
// Ensure there are "name" properties for all fields and that field schema
|
|
||||||
// are objects
|
|
||||||
Object.entries(newSchema).forEach(([fieldName, fieldSchema]) => {
|
|
||||||
if (typeof fieldSchema === "string") {
|
|
||||||
newSchema[fieldName] = {
|
|
||||||
type: fieldSchema,
|
|
||||||
name: fieldName,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newSchema[fieldName] = {
|
|
||||||
...fieldSchema,
|
|
||||||
name: fieldName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
schema = newSchema
|
|
||||||
schemaLoaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextPage = async () => {
|
|
||||||
if (!hasNextPage || !internalTable) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const sort = schema?.[currentSortColumn] ? currentSortColumn : undefined
|
|
||||||
const res = await API.searchTable({
|
|
||||||
tableId: dataSource?.tableId,
|
|
||||||
query,
|
|
||||||
bookmark: bookmarks[pageNumber + 1],
|
|
||||||
limit,
|
|
||||||
sort,
|
|
||||||
sortOrder: currentSortOrder?.toLowerCase() ?? "ascending",
|
|
||||||
sortType,
|
|
||||||
paginate: true,
|
|
||||||
})
|
|
||||||
pageNumber++
|
|
||||||
allRows = res.rows
|
|
||||||
if (res.hasNextPage) {
|
|
||||||
bookmarks[pageNumber + 1] = res.bookmark
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevPage = async () => {
|
|
||||||
if (!hasPrevPage || !internalTable) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const sort = schema?.[currentSortColumn] ? currentSortColumn : undefined
|
|
||||||
const res = await API.searchTable({
|
|
||||||
tableId: dataSource?.tableId,
|
|
||||||
query,
|
|
||||||
bookmark: bookmarks[pageNumber - 1],
|
|
||||||
limit,
|
|
||||||
sort,
|
|
||||||
sortOrder: currentSortOrder?.toLowerCase() ?? "ascending",
|
|
||||||
sortType,
|
|
||||||
paginate: true,
|
|
||||||
})
|
|
||||||
pageNumber--
|
|
||||||
allRows = res.rows
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const addQueryExtension = (key, extension) => {
|
const addQueryExtension = (key, extension) => {
|
||||||
|
@ -309,16 +118,13 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
return extendedQuery
|
||||||
if (JSON.stringify(query) !== JSON.stringify(extendedQuery)) {
|
|
||||||
query = extendedQuery
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={$component.styles} class="container">
|
<div use:styleable={$component.styles} class="container">
|
||||||
<Provider {actions} data={dataContext}>
|
<Provider {actions} data={dataContext}>
|
||||||
{#if !loaded}
|
{#if !$fetch.loaded}
|
||||||
<div class="loading">
|
<div class="loading">
|
||||||
<ProgressCircle />
|
<ProgressCircle />
|
||||||
</div>
|
</div>
|
||||||
|
@ -328,14 +134,14 @@
|
||||||
{:else}
|
{:else}
|
||||||
<slot />
|
<slot />
|
||||||
{/if}
|
{/if}
|
||||||
{#if paginate && internalTable}
|
{#if paginate && $fetch.supportsPagination}
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<Pagination
|
<Pagination
|
||||||
page={pageNumber + 1}
|
page={$fetch.pageNumber + 1}
|
||||||
{hasPrevPage}
|
hasPrevPage={$fetch.hasPrevPage}
|
||||||
{hasNextPage}
|
hasNextPage={$fetch.hasNextPage}
|
||||||
goToPrevPage={prevPage}
|
goToPrevPage={fetch.prevPage}
|
||||||
goToNextPage={nextPage}
|
goToNextPage={fetch.nextPage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
export let cardButtonOnClick
|
export let cardButtonOnClick
|
||||||
export let linkColumn
|
export let linkColumn
|
||||||
|
|
||||||
const { API, styleable } = getContext("sdk")
|
const { fetchDatasourceSchema, styleable } = getContext("sdk")
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const schemaComponentMap = {
|
const schemaComponentMap = {
|
||||||
|
@ -45,6 +45,7 @@
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
let repeaterId
|
let repeaterId
|
||||||
let schema
|
let schema
|
||||||
|
let schemaLoaded = false
|
||||||
|
|
||||||
$: fetchSchema(dataSource)
|
$: fetchSchema(dataSource)
|
||||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||||
|
@ -111,11 +112,13 @@
|
||||||
// Load the datasource schema so we can determine column types
|
// Load the datasource schema so we can determine column types
|
||||||
const fetchSchema = async dataSource => {
|
const fetchSchema = async dataSource => {
|
||||||
if (dataSource) {
|
if (dataSource) {
|
||||||
schema = await API.fetchDatasourceSchema(dataSource)
|
schema = await fetchDatasourceSchema(dataSource)
|
||||||
}
|
}
|
||||||
|
schemaLoaded = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if schemaLoaded}
|
||||||
<Block>
|
<Block>
|
||||||
<div class="card-list" use:styleable={$component.styles}>
|
<div class="card-list" use:styleable={$component.styles}>
|
||||||
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
||||||
|
@ -208,6 +211,7 @@
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
</div>
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.header {
|
.header {
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
export let titleButtonURL
|
export let titleButtonURL
|
||||||
export let titleButtonPeek
|
export let titleButtonPeek
|
||||||
|
|
||||||
const { API, styleable } = getContext("sdk")
|
const { fetchDatasourceSchema, styleable } = getContext("sdk")
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const schemaComponentMap = {
|
const schemaComponentMap = {
|
||||||
|
@ -40,6 +40,7 @@
|
||||||
let formId
|
let formId
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
let schema
|
let schema
|
||||||
|
let schemaLoaded = false
|
||||||
|
|
||||||
$: fetchSchema(dataSource)
|
$: fetchSchema(dataSource)
|
||||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||||
|
@ -89,11 +90,13 @@
|
||||||
// Load the datasource schema so we can determine column types
|
// Load the datasource schema so we can determine column types
|
||||||
const fetchSchema = async dataSource => {
|
const fetchSchema = async dataSource => {
|
||||||
if (dataSource) {
|
if (dataSource) {
|
||||||
schema = await API.fetchDatasourceSchema(dataSource)
|
schema = await fetchDatasourceSchema(dataSource)
|
||||||
}
|
}
|
||||||
|
schemaLoaded = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if schemaLoaded}
|
||||||
<Block>
|
<Block>
|
||||||
<div class={size} use:styleable={$component.styles}>
|
<div class={size} use:styleable={$component.styles}>
|
||||||
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
||||||
|
@ -165,6 +168,7 @@
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
</div>
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.header {
|
.header {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
export let actionType = "Create"
|
export let actionType = "Create"
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const { API } = getContext("sdk")
|
const { API, fetchDatasourceSchema } = getContext("sdk")
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let schema
|
let schema
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
// For all other cases, just grab the normal schema
|
// For all other cases, just grab the normal schema
|
||||||
else {
|
else {
|
||||||
const dataSourceSchema = await API.fetchDatasourceSchema(dataSource)
|
const dataSourceSchema = await fetchDatasourceSchema(dataSource)
|
||||||
schema = dataSourceSchema || {}
|
schema = dataSourceSchema || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
import { styleable } from "utils/styleable"
|
import { styleable } from "utils/styleable"
|
||||||
import { linkable } from "utils/linkable"
|
import { linkable } from "utils/linkable"
|
||||||
import { getAction } from "utils/getAction"
|
import { getAction } from "utils/getAction"
|
||||||
|
import { fetchDatasourceSchema } from "utils/schema.js"
|
||||||
import Provider from "components/context/Provider.svelte"
|
import Provider from "components/context/Provider.svelte"
|
||||||
import { ActionTypes } from "constants"
|
import { ActionTypes } from "constants"
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ export default {
|
||||||
styleable,
|
styleable,
|
||||||
linkable,
|
linkable,
|
||||||
getAction,
|
getAction,
|
||||||
|
fetchDatasourceSchema,
|
||||||
Provider,
|
Provider,
|
||||||
ActionTypes,
|
ActionTypes,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,407 @@
|
||||||
|
import { writable, derived, get } from "svelte/store"
|
||||||
|
import {
|
||||||
|
buildLuceneQuery,
|
||||||
|
luceneLimit,
|
||||||
|
luceneQuery,
|
||||||
|
luceneSort,
|
||||||
|
} from "builder/src/helpers/lucene"
|
||||||
|
import { fetchTableDefinition } from "api"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent class which handles the implementation of fetching data from an
|
||||||
|
* internal table or datasource plus.
|
||||||
|
* For other types of datasource, this class is overridden and extended.
|
||||||
|
*/
|
||||||
|
export default class DataFetch {
|
||||||
|
// Feature flags
|
||||||
|
featureStore = writable({
|
||||||
|
supportsSearch: false,
|
||||||
|
supportsSort: false,
|
||||||
|
supportsPagination: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Config
|
||||||
|
options = {
|
||||||
|
datasource: null,
|
||||||
|
limit: 10,
|
||||||
|
|
||||||
|
// Search config
|
||||||
|
filter: null,
|
||||||
|
query: null,
|
||||||
|
|
||||||
|
// Sorting config
|
||||||
|
sortColumn: null,
|
||||||
|
sortOrder: "ascending",
|
||||||
|
sortType: null,
|
||||||
|
|
||||||
|
// Pagination config
|
||||||
|
paginate: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// State of the fetch
|
||||||
|
store = writable({
|
||||||
|
rows: [],
|
||||||
|
info: null,
|
||||||
|
schema: null,
|
||||||
|
loading: false,
|
||||||
|
loaded: false,
|
||||||
|
query: null,
|
||||||
|
pageNumber: 0,
|
||||||
|
cursor: null,
|
||||||
|
cursors: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new DataFetch instance.
|
||||||
|
* @param opts the fetch options
|
||||||
|
*/
|
||||||
|
constructor(opts) {
|
||||||
|
// Merge options with their default values
|
||||||
|
this.options = {
|
||||||
|
...this.options,
|
||||||
|
...opts,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind all functions to properly scope "this"
|
||||||
|
this.getData = this.getData.bind(this)
|
||||||
|
this.getPage = this.getPage.bind(this)
|
||||||
|
this.getInitialData = this.getInitialData.bind(this)
|
||||||
|
this.determineFeatureFlags = this.determineFeatureFlags.bind(this)
|
||||||
|
this.enrichSchema = this.enrichSchema.bind(this)
|
||||||
|
this.refresh = this.refresh.bind(this)
|
||||||
|
this.update = this.update.bind(this)
|
||||||
|
this.hasNextPage = this.hasNextPage.bind(this)
|
||||||
|
this.hasPrevPage = this.hasPrevPage.bind(this)
|
||||||
|
this.nextPage = this.nextPage.bind(this)
|
||||||
|
this.prevPage = this.prevPage.bind(this)
|
||||||
|
|
||||||
|
// Derive certain properties to return
|
||||||
|
this.derivedStore = derived(
|
||||||
|
[this.store, this.featureStore],
|
||||||
|
([$store, $featureStore]) => {
|
||||||
|
return {
|
||||||
|
...$store,
|
||||||
|
...$featureStore,
|
||||||
|
hasNextPage: this.hasNextPage($store),
|
||||||
|
hasPrevPage: this.hasPrevPage($store),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mark as loaded if we have no datasource
|
||||||
|
if (!this.options.datasource) {
|
||||||
|
this.store.update($store => ({ ...$store, loaded: true }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially fetch data but don't bother waiting for the result
|
||||||
|
this.getInitialData()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the svelte store subscribe method to that instances of this class
|
||||||
|
* can be treated like stores
|
||||||
|
*/
|
||||||
|
get subscribe() {
|
||||||
|
return this.derivedStore.subscribe
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a fresh set of data from the server, resetting pagination
|
||||||
|
*/
|
||||||
|
async getInitialData() {
|
||||||
|
const { datasource, filter, sortColumn, paginate } = this.options
|
||||||
|
const tableId = datasource?.tableId
|
||||||
|
|
||||||
|
// Ensure table ID exists
|
||||||
|
if (!tableId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch datasource definition and determine feature flags
|
||||||
|
const definition = await this.constructor.getDefinition(datasource)
|
||||||
|
const features = this.determineFeatureFlags(definition)
|
||||||
|
this.featureStore.set({
|
||||||
|
supportsSearch: !!features?.supportsSearch,
|
||||||
|
supportsSort: !!features?.supportsSort,
|
||||||
|
supportsPagination: paginate && !!features?.supportsPagination,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fetch and enrich schema
|
||||||
|
let schema = this.constructor.getSchema(datasource, definition)
|
||||||
|
schema = this.enrichSchema(schema)
|
||||||
|
if (!schema) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine what sort type to use
|
||||||
|
if (!this.options.sortType) {
|
||||||
|
let sortType = "string"
|
||||||
|
if (sortColumn) {
|
||||||
|
const type = schema?.[sortColumn]?.type
|
||||||
|
sortType = type === "number" ? "number" : "string"
|
||||||
|
}
|
||||||
|
this.options.sortType = sortType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the lucene query
|
||||||
|
let query = this.options.query
|
||||||
|
if (!query) {
|
||||||
|
query = buildLuceneQuery(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update store
|
||||||
|
this.store.update($store => ({
|
||||||
|
...$store,
|
||||||
|
definition,
|
||||||
|
schema,
|
||||||
|
query,
|
||||||
|
loading: true,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Actually fetch data
|
||||||
|
const page = await this.getPage()
|
||||||
|
this.store.update($store => ({
|
||||||
|
...$store,
|
||||||
|
loading: false,
|
||||||
|
loaded: true,
|
||||||
|
pageNumber: 0,
|
||||||
|
rows: page.rows,
|
||||||
|
info: page.info,
|
||||||
|
cursors: paginate && page.hasNextPage ? [null, page.cursor] : [null],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches some filtered, sorted and paginated data
|
||||||
|
*/
|
||||||
|
async getPage() {
|
||||||
|
const { sortColumn, sortOrder, sortType, limit } = this.options
|
||||||
|
const { query } = get(this.store)
|
||||||
|
const features = get(this.featureStore)
|
||||||
|
|
||||||
|
// Get the actual data
|
||||||
|
let { rows, info, hasNextPage, cursor } = await this.getData()
|
||||||
|
|
||||||
|
// If we don't support searching, do a client search
|
||||||
|
if (!features.supportsSearch) {
|
||||||
|
rows = luceneQuery(rows, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't support sorting, do a client-side sort
|
||||||
|
if (!features.supportsSort) {
|
||||||
|
rows = luceneSort(rows, sortColumn, sortOrder, sortType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't support pagination, do a client-side limit
|
||||||
|
if (!features.supportsPagination) {
|
||||||
|
rows = luceneLimit(rows, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
info,
|
||||||
|
hasNextPage,
|
||||||
|
cursor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a single page of data from the remote resource.
|
||||||
|
* Must be overridden by a datasource specific child class.
|
||||||
|
*/
|
||||||
|
async getData() {
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
info: null,
|
||||||
|
hasNextPage: false,
|
||||||
|
cursor: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the definition for this datasource.
|
||||||
|
* Defaults to fetching a table definition.
|
||||||
|
* @param datasource
|
||||||
|
* @return {object} the definition
|
||||||
|
*/
|
||||||
|
static async getDefinition(datasource) {
|
||||||
|
if (!datasource?.tableId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return await fetchTableDefinition(datasource.tableId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the schema definition for a datasource.
|
||||||
|
* Defaults to getting the "schema" property of the definition.
|
||||||
|
* @param datasource the datasource
|
||||||
|
* @param definition the datasource definition
|
||||||
|
* @return {object} the schema
|
||||||
|
*/
|
||||||
|
static getSchema(datasource, definition) {
|
||||||
|
return definition?.schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enriches the schema and ensures that entries are objects with names
|
||||||
|
* @param schema the datasource schema
|
||||||
|
* @return {object} the enriched datasource schema
|
||||||
|
*/
|
||||||
|
enrichSchema(schema) {
|
||||||
|
if (schema == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let enrichedSchema = {}
|
||||||
|
Object.entries(schema).forEach(([fieldName, fieldSchema]) => {
|
||||||
|
if (typeof fieldSchema === "string") {
|
||||||
|
enrichedSchema[fieldName] = {
|
||||||
|
type: fieldSchema,
|
||||||
|
name: fieldName,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enrichedSchema[fieldName] = {
|
||||||
|
...fieldSchema,
|
||||||
|
name: fieldName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return enrichedSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the feature flag for this datasource definition
|
||||||
|
* @param definition
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
determineFeatureFlags(definition) {
|
||||||
|
return {
|
||||||
|
supportsSearch: false,
|
||||||
|
supportsSort: false,
|
||||||
|
supportsPagination: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the data set and updates options
|
||||||
|
* @param newOptions any new options
|
||||||
|
*/
|
||||||
|
async update(newOptions) {
|
||||||
|
// Check if any settings have actually changed
|
||||||
|
let refresh = false
|
||||||
|
const entries = Object.entries(newOptions || {})
|
||||||
|
for (let [key, value] of entries) {
|
||||||
|
if (JSON.stringify(value) !== JSON.stringify(this.options[key])) {
|
||||||
|
refresh = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!refresh) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign new options and reload data
|
||||||
|
this.options = {
|
||||||
|
...this.options,
|
||||||
|
...newOptions,
|
||||||
|
}
|
||||||
|
await this.getInitialData()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the same page again
|
||||||
|
*/
|
||||||
|
async refresh() {
|
||||||
|
if (get(this.store).loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.store.update($store => ({ ...$store, loading: true }))
|
||||||
|
const { rows, info } = await this.getPage()
|
||||||
|
this.store.update($store => ({ ...$store, rows, info, loading: false }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether there is a next page of data based on the state of the
|
||||||
|
* store
|
||||||
|
* @param state the current store state
|
||||||
|
* @return {boolean} whether there is a next page of data or not
|
||||||
|
*/
|
||||||
|
hasNextPage(state) {
|
||||||
|
return state.cursors[state.pageNumber + 1] != null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether there is a previous page of data based on the state of
|
||||||
|
* the store
|
||||||
|
* @param state the current store state
|
||||||
|
* @return {boolean} whether there is a previous page of data or not
|
||||||
|
*/
|
||||||
|
hasPrevPage(state) {
|
||||||
|
return state.pageNumber > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the next page of data
|
||||||
|
*/
|
||||||
|
async nextPage() {
|
||||||
|
const state = get(this.derivedStore)
|
||||||
|
if (state.loading || !this.options.paginate || !state.hasNextPage) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch next page
|
||||||
|
const nextCursor = state.cursors[state.pageNumber + 1]
|
||||||
|
this.store.update($store => ({
|
||||||
|
...$store,
|
||||||
|
loading: true,
|
||||||
|
cursor: nextCursor,
|
||||||
|
pageNumber: $store.pageNumber + 1,
|
||||||
|
}))
|
||||||
|
const { rows, info, hasNextPage, cursor } = await this.getPage()
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
this.store.update($store => {
|
||||||
|
let { cursors, pageNumber } = $store
|
||||||
|
if (hasNextPage) {
|
||||||
|
cursors[pageNumber + 1] = cursor
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...$store,
|
||||||
|
rows,
|
||||||
|
info,
|
||||||
|
cursors,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the previous page of data
|
||||||
|
*/
|
||||||
|
async prevPage() {
|
||||||
|
const state = get(this.derivedStore)
|
||||||
|
if (state.loading || !this.options.paginate || !state.hasPrevPage) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch previous page
|
||||||
|
const prevCursor = state.cursors[state.pageNumber - 1]
|
||||||
|
this.store.update($store => ({
|
||||||
|
...$store,
|
||||||
|
loading: true,
|
||||||
|
cursor: prevCursor,
|
||||||
|
pageNumber: $store.pageNumber - 1,
|
||||||
|
}))
|
||||||
|
const { rows, info } = await this.getPage()
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
this.store.update($store => {
|
||||||
|
return {
|
||||||
|
...$store,
|
||||||
|
rows,
|
||||||
|
info,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import DataFetch from "./DataFetch.js"
|
||||||
|
|
||||||
|
export default class FieldFetch extends DataFetch {
|
||||||
|
static async getDefinition(datasource) {
|
||||||
|
// Field sources have their schema statically defined
|
||||||
|
let schema
|
||||||
|
if (datasource.fieldType === "attachment") {
|
||||||
|
schema = {
|
||||||
|
url: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if (datasource.fieldType === "array") {
|
||||||
|
schema = {
|
||||||
|
value: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { schema }
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
const { datasource } = this.options
|
||||||
|
|
||||||
|
// These sources will be available directly from context
|
||||||
|
const data = datasource?.value || []
|
||||||
|
let rows = []
|
||||||
|
if (Array.isArray(data) && data[0] && typeof data[0] !== "object") {
|
||||||
|
rows = data.map(value => ({ value }))
|
||||||
|
} else {
|
||||||
|
rows = data
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: rows || [],
|
||||||
|
hasNextPage: false,
|
||||||
|
cursor: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import FieldFetch from "./FieldFetch.js"
|
||||||
|
import { fetchTableDefinition } from "api"
|
||||||
|
import { getJSONArrayDatasourceSchema } from "builder/src/builderStore/jsonUtils"
|
||||||
|
|
||||||
|
export default class JSONArrayFetch extends FieldFetch {
|
||||||
|
static async getDefinition(datasource) {
|
||||||
|
// JSON arrays need their table definitions fetched.
|
||||||
|
// We can then extract their schema as a subset of the table schema.
|
||||||
|
const table = await fetchTableDefinition(datasource.tableId)
|
||||||
|
const schema = getJSONArrayDatasourceSchema(table?.schema, datasource)
|
||||||
|
return { schema }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import DataFetch from "./DataFetch.js"
|
||||||
|
|
||||||
|
export default class NestedProviderFetch extends DataFetch {
|
||||||
|
static async getDefinition(datasource) {
|
||||||
|
// Nested providers should already have exposed their own schema
|
||||||
|
return {
|
||||||
|
schema: datasource?.value?.schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
const { datasource } = this.options
|
||||||
|
// Pull the rows from the existing data provider
|
||||||
|
return {
|
||||||
|
rows: datasource?.value?.rows || [],
|
||||||
|
hasNextPage: false,
|
||||||
|
cursor: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import DataFetch from "./DataFetch.js"
|
||||||
|
import { executeQuery, fetchQueryDefinition } from "api"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
|
export default class QueryFetch extends DataFetch {
|
||||||
|
determineFeatureFlags(definition) {
|
||||||
|
const supportsPagination =
|
||||||
|
!!definition?.fields?.pagination?.type &&
|
||||||
|
!!definition?.fields?.pagination?.location &&
|
||||||
|
!!definition?.fields?.pagination?.pageParam
|
||||||
|
return { supportsPagination }
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getDefinition(datasource) {
|
||||||
|
if (!datasource?._id) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return await fetchQueryDefinition(datasource._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
const { datasource, limit, paginate } = this.options
|
||||||
|
const { supportsPagination } = get(this.featureStore)
|
||||||
|
const { cursor, definition } = get(this.store)
|
||||||
|
const type = definition?.fields?.pagination?.type
|
||||||
|
|
||||||
|
// Set the default query params
|
||||||
|
let parameters = cloneDeep(datasource?.queryParams || {})
|
||||||
|
for (let param of datasource?.parameters || {}) {
|
||||||
|
if (!parameters[param.name]) {
|
||||||
|
parameters[param.name] = param.default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add pagination to query if supported
|
||||||
|
let queryPayload = { queryId: datasource?._id, parameters }
|
||||||
|
if (paginate && supportsPagination) {
|
||||||
|
const requestCursor = type === "page" ? parseInt(cursor || 1) : cursor
|
||||||
|
queryPayload.pagination = { page: requestCursor, limit }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute query
|
||||||
|
const { data, pagination, ...rest } = await executeQuery(queryPayload)
|
||||||
|
|
||||||
|
// Derive pagination info from response
|
||||||
|
let nextCursor = null
|
||||||
|
let hasNextPage = false
|
||||||
|
if (paginate && supportsPagination) {
|
||||||
|
if (type === "page") {
|
||||||
|
// For "page number" pagination, increment the existing page number
|
||||||
|
nextCursor = queryPayload.pagination.page + 1
|
||||||
|
hasNextPage = data?.length === limit && limit > 0
|
||||||
|
} else {
|
||||||
|
// For "cursor" pagination, the cursor should be in the response
|
||||||
|
nextCursor = pagination?.cursor
|
||||||
|
hasNextPage = nextCursor != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: data || [],
|
||||||
|
info: rest,
|
||||||
|
cursor: nextCursor,
|
||||||
|
hasNextPage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import DataFetch from "./DataFetch.js"
|
||||||
|
import { fetchRelationshipData } from "api"
|
||||||
|
|
||||||
|
export default class RelationshipFetch extends DataFetch {
|
||||||
|
async getData() {
|
||||||
|
const { datasource } = this.options
|
||||||
|
const res = await fetchRelationshipData({
|
||||||
|
rowId: datasource?.rowId,
|
||||||
|
tableId: datasource?.rowTableId,
|
||||||
|
fieldName: datasource?.fieldName,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
rows: res || [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import DataFetch from "./DataFetch.js"
|
||||||
|
import { searchTable } from "api"
|
||||||
|
|
||||||
|
export default class TableFetch extends DataFetch {
|
||||||
|
determineFeatureFlags() {
|
||||||
|
return {
|
||||||
|
supportsSearch: true,
|
||||||
|
supportsSort: true,
|
||||||
|
supportsPagination: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
const { datasource, limit, sortColumn, sortOrder, sortType, paginate } =
|
||||||
|
this.options
|
||||||
|
const { tableId } = datasource
|
||||||
|
const { cursor, query } = get(this.store)
|
||||||
|
|
||||||
|
// Search table
|
||||||
|
const res = await searchTable({
|
||||||
|
tableId,
|
||||||
|
query,
|
||||||
|
limit,
|
||||||
|
sort: sortColumn,
|
||||||
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
||||||
|
sortType,
|
||||||
|
paginate,
|
||||||
|
bookmark: cursor,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
rows: res?.rows || [],
|
||||||
|
hasNextPage: res?.hasNextPage || false,
|
||||||
|
cursor: res?.bookmark || null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import DataFetch from "./DataFetch.js"
|
||||||
|
import { fetchViewData } from "api"
|
||||||
|
|
||||||
|
export default class ViewFetch extends DataFetch {
|
||||||
|
static getSchema(datasource, definition) {
|
||||||
|
return definition?.views?.[datasource.name]?.schema
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
const { datasource } = this.options
|
||||||
|
const res = await fetchViewData(datasource)
|
||||||
|
return {
|
||||||
|
rows: res || [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import TableFetch from "./TableFetch.js"
|
||||||
|
import ViewFetch from "./ViewFetch.js"
|
||||||
|
import QueryFetch from "./QueryFetch.js"
|
||||||
|
import RelationshipFetch from "./RelationshipFetch.js"
|
||||||
|
import NestedProviderFetch from "./NestedProviderFetch.js"
|
||||||
|
import FieldFetch from "./FieldFetch.js"
|
||||||
|
import JSONArrayFetch from "./JSONArrayFetch.js"
|
||||||
|
|
||||||
|
const DataFetchMap = {
|
||||||
|
table: TableFetch,
|
||||||
|
view: ViewFetch,
|
||||||
|
query: QueryFetch,
|
||||||
|
link: RelationshipFetch,
|
||||||
|
provider: NestedProviderFetch,
|
||||||
|
field: FieldFetch,
|
||||||
|
jsonarray: JSONArrayFetch,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchData = (datasource, options) => {
|
||||||
|
const Fetch = DataFetchMap[datasource?.type] || TableFetch
|
||||||
|
return new Fetch({ datasource, ...options })
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { convertJSONSchemaToTableSchema } from "builder/src/builderStore/jsonUtils"
|
||||||
|
import TableFetch from "./fetch/TableFetch.js"
|
||||||
|
import ViewFetch from "./fetch/ViewFetch.js"
|
||||||
|
import QueryFetch from "./fetch/QueryFetch.js"
|
||||||
|
import RelationshipFetch from "./fetch/RelationshipFetch.js"
|
||||||
|
import NestedProviderFetch from "./fetch/NestedProviderFetch.js"
|
||||||
|
import FieldFetch from "./fetch/FieldFetch.js"
|
||||||
|
import JSONArrayFetch from "./fetch/JSONArrayFetch.js"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the schema of any kind of datasource.
|
||||||
|
* All datasource fetch classes implement their own functionality to get the
|
||||||
|
* schema of a datasource of their respective types.
|
||||||
|
*/
|
||||||
|
export const fetchDatasourceSchema = async datasource => {
|
||||||
|
const handler = {
|
||||||
|
table: TableFetch,
|
||||||
|
view: ViewFetch,
|
||||||
|
query: QueryFetch,
|
||||||
|
link: RelationshipFetch,
|
||||||
|
provider: NestedProviderFetch,
|
||||||
|
field: FieldFetch,
|
||||||
|
jsonarray: JSONArrayFetch,
|
||||||
|
}[datasource?.type]
|
||||||
|
if (!handler) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the datasource definition and then schema
|
||||||
|
const definition = await handler.getDefinition(datasource)
|
||||||
|
const schema = handler.getSchema(datasource, definition)
|
||||||
|
if (!schema) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any JSON fields so we can add any top level properties
|
||||||
|
let jsonAdditions = {}
|
||||||
|
Object.keys(schema).forEach(fieldKey => {
|
||||||
|
const fieldSchema = schema[fieldKey]
|
||||||
|
if (fieldSchema?.type === "json") {
|
||||||
|
const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, {
|
||||||
|
squashObjects: true,
|
||||||
|
})
|
||||||
|
Object.keys(jsonSchema).forEach(jsonKey => {
|
||||||
|
jsonAdditions[`${fieldKey}.${jsonKey}`] = {
|
||||||
|
type: jsonSchema[jsonKey].type,
|
||||||
|
nestedJSON: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return { ...schema, ...jsonAdditions }
|
||||||
|
}
|
|
@ -140,11 +140,12 @@ async function execute(ctx, opts = { rowsOnly: false }) {
|
||||||
|
|
||||||
// call the relevant CRUD method on the integration class
|
// call the relevant CRUD method on the integration class
|
||||||
try {
|
try {
|
||||||
const { rows, extra } = await Runner.run({
|
const { rows, pagination, extra } = await Runner.run({
|
||||||
appId: ctx.appId,
|
appId: ctx.appId,
|
||||||
datasource,
|
datasource,
|
||||||
queryVerb: query.queryVerb,
|
queryVerb: query.queryVerb,
|
||||||
fields: query.fields,
|
fields: query.fields,
|
||||||
|
pagination: ctx.request.body.pagination,
|
||||||
parameters: ctx.request.body.parameters,
|
parameters: ctx.request.body.parameters,
|
||||||
transformer: query.transformer,
|
transformer: query.transformer,
|
||||||
queryId: ctx.params.queryId,
|
queryId: ctx.params.queryId,
|
||||||
|
@ -152,7 +153,7 @@ async function execute(ctx, opts = { rowsOnly: false }) {
|
||||||
if (opts && opts.rowsOnly) {
|
if (opts && opts.rowsOnly) {
|
||||||
ctx.body = rows
|
ctx.body = rows
|
||||||
} else {
|
} else {
|
||||||
ctx.body = { data: rows, ...extra }
|
ctx.body = { data: rows, pagination, ...extra }
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
|
|
|
@ -232,6 +232,8 @@ export interface RestQueryFields {
|
||||||
json: object
|
json: object
|
||||||
method: string
|
method: string
|
||||||
authConfigId: string
|
authConfigId: string
|
||||||
|
pagination: PaginationConfig | null
|
||||||
|
paginationValues: PaginationValues | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RestConfig {
|
export interface RestConfig {
|
||||||
|
@ -252,6 +254,19 @@ export interface RestConfig {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PaginationConfig {
|
||||||
|
type: string
|
||||||
|
location: string
|
||||||
|
pageParam: string
|
||||||
|
sizeParam: string | null
|
||||||
|
responseParam: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginationValues {
|
||||||
|
page: string | number | null
|
||||||
|
limit: number | null
|
||||||
|
}
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
_id?: string
|
_id?: string
|
||||||
datasourceId: string
|
datasourceId: string
|
||||||
|
|
|
@ -4,9 +4,11 @@ import {
|
||||||
QueryTypes,
|
QueryTypes,
|
||||||
RestConfig,
|
RestConfig,
|
||||||
RestQueryFields as RestQuery,
|
RestQueryFields as RestQuery,
|
||||||
|
PaginationConfig,
|
||||||
AuthType,
|
AuthType,
|
||||||
BasicAuthConfig,
|
BasicAuthConfig,
|
||||||
BearerAuthConfig,
|
BearerAuthConfig,
|
||||||
|
PaginationValues,
|
||||||
} from "../definitions/datasource"
|
} from "../definitions/datasource"
|
||||||
import { IntegrationBase } from "./base/IntegrationBase"
|
import { IntegrationBase } from "./base/IntegrationBase"
|
||||||
|
|
||||||
|
@ -40,6 +42,9 @@ const coreFields = {
|
||||||
type: DatasourceFieldTypes.STRING,
|
type: DatasourceFieldTypes.STRING,
|
||||||
enum: Object.values(BodyTypes),
|
enum: Object.values(BodyTypes),
|
||||||
},
|
},
|
||||||
|
pagination: {
|
||||||
|
type: DatasourceFieldTypes.OBJECT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module RestModule {
|
module RestModule {
|
||||||
|
@ -115,7 +120,7 @@ module RestModule {
|
||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseResponse(response: any) {
|
async parseResponse(response: any, pagination: PaginationConfig | null) {
|
||||||
let data, raw, headers
|
let data, raw, headers
|
||||||
const contentType = response.headers.get("content-type") || ""
|
const contentType = response.headers.get("content-type") || ""
|
||||||
try {
|
try {
|
||||||
|
@ -154,6 +159,13 @@ module RestModule {
|
||||||
for (let [key, value] of Object.entries(headers)) {
|
for (let [key, value] of Object.entries(headers)) {
|
||||||
headers[key] = Array.isArray(value) ? value[0] : value
|
headers[key] = Array.isArray(value) ? value[0] : value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a pagination cursor exists in the response
|
||||||
|
let nextCursor = null
|
||||||
|
if (pagination?.responseParam) {
|
||||||
|
nextCursor = data?.[pagination.responseParam]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
info: {
|
info: {
|
||||||
|
@ -165,10 +177,35 @@ module RestModule {
|
||||||
raw,
|
raw,
|
||||||
headers,
|
headers,
|
||||||
},
|
},
|
||||||
|
pagination: {
|
||||||
|
cursor: nextCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getUrl(path: string, queryString: string, pagination: PaginationConfig | null, paginationValues: PaginationValues | null): string {
|
||||||
|
// Add pagination params to query string if required
|
||||||
|
if (pagination?.location === "query" && paginationValues) {
|
||||||
|
const { pageParam, sizeParam } = pagination
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
|
// Append page number or cursor param if configured
|
||||||
|
if (pageParam && paginationValues.page != null) {
|
||||||
|
params.append(pageParam, paginationValues.page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append page size param if configured
|
||||||
|
if (sizeParam && paginationValues.limit != null) {
|
||||||
|
params.append(sizeParam, paginationValues.limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend query string with pagination params
|
||||||
|
let paginationString = params.toString()
|
||||||
|
if (paginationString) {
|
||||||
|
queryString = `${paginationString}&${queryString}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUrl(path: string, queryString: string): string {
|
|
||||||
const main = `${path}?${queryString}`
|
const main = `${path}?${queryString}`
|
||||||
let complete = main
|
let complete = main
|
||||||
if (this.config.url && !main.startsWith("http")) {
|
if (this.config.url && !main.startsWith("http")) {
|
||||||
|
@ -180,20 +217,36 @@ module RestModule {
|
||||||
return complete
|
return complete
|
||||||
}
|
}
|
||||||
|
|
||||||
addBody(bodyType: string, body: string | any, input: any) {
|
addBody(bodyType: string, body: string | any, input: any, pagination: PaginationConfig | null, paginationValues: PaginationValues | null) {
|
||||||
let error, object, string
|
|
||||||
try {
|
|
||||||
string = typeof body !== "string" ? JSON.stringify(body) : body
|
|
||||||
object = typeof body === "object" ? body : JSON.parse(body)
|
|
||||||
} catch (err) {
|
|
||||||
error = err
|
|
||||||
}
|
|
||||||
if (!input.headers) {
|
if (!input.headers) {
|
||||||
input.headers = {}
|
input.headers = {}
|
||||||
}
|
}
|
||||||
|
if (bodyType === BodyTypes.NONE) {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
let error, object: any = {}, string = ""
|
||||||
|
try {
|
||||||
|
if (body) {
|
||||||
|
string = typeof body !== "string" ? JSON.stringify(body) : body
|
||||||
|
object = typeof body === "object" ? body : JSON.parse(body)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
error = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Util to add pagination values to a certain body type
|
||||||
|
const addPaginationToBody = (insertFn: Function) => {
|
||||||
|
if (pagination?.location === "body") {
|
||||||
|
if (pagination?.pageParam && paginationValues?.page != null) {
|
||||||
|
insertFn(pagination.pageParam, paginationValues.page)
|
||||||
|
}
|
||||||
|
if (pagination?.sizeParam && paginationValues?.limit != null) {
|
||||||
|
insertFn(pagination.sizeParam, paginationValues.limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (bodyType) {
|
switch (bodyType) {
|
||||||
case BodyTypes.NONE:
|
|
||||||
break
|
|
||||||
case BodyTypes.TEXT:
|
case BodyTypes.TEXT:
|
||||||
// content type defaults to plaintext
|
// content type defaults to plaintext
|
||||||
input.body = string
|
input.body = string
|
||||||
|
@ -203,6 +256,9 @@ module RestModule {
|
||||||
for (let [key, value] of Object.entries(object)) {
|
for (let [key, value] of Object.entries(object)) {
|
||||||
params.append(key, value)
|
params.append(key, value)
|
||||||
}
|
}
|
||||||
|
addPaginationToBody((key: string, value: any) => {
|
||||||
|
params.append(key, value)
|
||||||
|
})
|
||||||
input.body = params
|
input.body = params
|
||||||
break
|
break
|
||||||
case BodyTypes.FORM_DATA:
|
case BodyTypes.FORM_DATA:
|
||||||
|
@ -210,6 +266,9 @@ module RestModule {
|
||||||
for (let [key, value] of Object.entries(object)) {
|
for (let [key, value] of Object.entries(object)) {
|
||||||
form.append(key, value)
|
form.append(key, value)
|
||||||
}
|
}
|
||||||
|
addPaginationToBody((key: string, value: any) => {
|
||||||
|
form.append(key, value)
|
||||||
|
})
|
||||||
input.body = form
|
input.body = form
|
||||||
break
|
break
|
||||||
case BodyTypes.XML:
|
case BodyTypes.XML:
|
||||||
|
@ -219,13 +278,15 @@ module RestModule {
|
||||||
input.body = string
|
input.body = string
|
||||||
input.headers["Content-Type"] = "application/xml"
|
input.headers["Content-Type"] = "application/xml"
|
||||||
break
|
break
|
||||||
default:
|
|
||||||
case BodyTypes.JSON:
|
case BodyTypes.JSON:
|
||||||
// if JSON error, throw it
|
// if JSON error, throw it
|
||||||
if (error) {
|
if (error) {
|
||||||
throw "Invalid JSON for request body"
|
throw "Invalid JSON for request body"
|
||||||
}
|
}
|
||||||
input.body = string
|
addPaginationToBody((key: string, value: any) => {
|
||||||
|
object[key] = value
|
||||||
|
})
|
||||||
|
input.body = JSON.stringify(object)
|
||||||
input.headers["Content-Type"] = "application/json"
|
input.headers["Content-Type"] = "application/json"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -271,6 +332,8 @@ module RestModule {
|
||||||
bodyType,
|
bodyType,
|
||||||
requestBody,
|
requestBody,
|
||||||
authConfigId,
|
authConfigId,
|
||||||
|
pagination,
|
||||||
|
paginationValues
|
||||||
} = query
|
} = query
|
||||||
const authHeaders = this.getAuthHeaders(authConfigId)
|
const authHeaders = this.getAuthHeaders(authConfigId)
|
||||||
|
|
||||||
|
@ -289,14 +352,12 @@ module RestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
let input: any = { method, headers: this.headers }
|
let input: any = { method, headers: this.headers }
|
||||||
if (requestBody) {
|
input = this.addBody(bodyType, requestBody, input, pagination, paginationValues)
|
||||||
input = this.addBody(bodyType, requestBody, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.startTimeMs = performance.now()
|
this.startTimeMs = performance.now()
|
||||||
const url = this.getUrl(path, queryString)
|
const url = this.getUrl(path, queryString, pagination, paginationValues)
|
||||||
const response = await fetch(url, input)
|
const response = await fetch(url, input)
|
||||||
return await this.parseResponse(response)
|
return await this.parseResponse(response, pagination)
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(opts: RestQuery) {
|
async create(opts: RestQuery) {
|
||||||
|
|
|
@ -4,19 +4,23 @@ jest.mock("node-fetch", () =>
|
||||||
raw: () => {
|
raw: () => {
|
||||||
return { "content-type": ["application/json"] }
|
return { "content-type": ["application/json"] }
|
||||||
},
|
},
|
||||||
get: () => ["application/json"]
|
get: () => ["application/json"],
|
||||||
},
|
},
|
||||||
json: jest.fn(),
|
json: jest.fn(() => ({
|
||||||
text: jest.fn()
|
my_next_cursor: 123,
|
||||||
|
})),
|
||||||
|
text: jest.fn(),
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const RestIntegration = require("../rest")
|
const RestIntegration = require("../rest")
|
||||||
const { AuthType } = require("../rest")
|
const { AuthType } = require("../rest")
|
||||||
|
const FormData = require("form-data")
|
||||||
|
const { URLSearchParams } = require("url")
|
||||||
|
|
||||||
const HEADERS = {
|
const HEADERS = {
|
||||||
"Accept": "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestConfiguration {
|
class TestConfiguration {
|
||||||
|
@ -165,7 +169,10 @@ describe("REST Integration", () => {
|
||||||
status: 200,
|
status: 200,
|
||||||
json: json ? async () => json : undefined,
|
json: json ? async () => json : undefined,
|
||||||
text: text ? async () => text : undefined,
|
text: text ? async () => text : undefined,
|
||||||
headers: { get: key => key === "content-length" ? 100 : header, raw: () => ({ "content-type": header }) }
|
headers: {
|
||||||
|
get: key => (key === "content-length" ? 100 : header),
|
||||||
|
raw: () => ({ "content-type": header }),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,8 +212,8 @@ describe("REST Integration", () => {
|
||||||
type: AuthType.BASIC,
|
type: AuthType.BASIC,
|
||||||
config: {
|
config: {
|
||||||
username: "user",
|
username: "user",
|
||||||
password: "password"
|
password: "password",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const bearerAuth = {
|
const bearerAuth = {
|
||||||
|
@ -214,41 +221,297 @@ describe("REST Integration", () => {
|
||||||
name: "bearer-1",
|
name: "bearer-1",
|
||||||
type: AuthType.BEARER,
|
type: AuthType.BEARER,
|
||||||
config: {
|
config: {
|
||||||
"token": "mytoken"
|
token: "mytoken",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config = new TestConfiguration({
|
config = new TestConfiguration({
|
||||||
url: BASE_URL,
|
url: BASE_URL,
|
||||||
authConfigs : [basicAuth, bearerAuth]
|
authConfigs: [basicAuth, bearerAuth],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("adds basic auth", async () => {
|
it("adds basic auth", async () => {
|
||||||
const query = {
|
const query = {
|
||||||
authConfigId: "c59c14bd1898a43baa08da68959b24686"
|
authConfigId: "c59c14bd1898a43baa08da68959b24686",
|
||||||
}
|
}
|
||||||
await config.integration.read(query)
|
await config.integration.read(query)
|
||||||
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/?`, {
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/?`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Basic dXNlcjpwYXNzd29yZA=="
|
Authorization: "Basic dXNlcjpwYXNzd29yZA==",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("adds bearer auth", async () => {
|
it("adds bearer auth", async () => {
|
||||||
const query = {
|
const query = {
|
||||||
authConfigId: "0d91d732f34e4befabeff50b392a8ff3"
|
authConfigId: "0d91d732f34e4befabeff50b392a8ff3",
|
||||||
}
|
}
|
||||||
await config.integration.read(query)
|
await config.integration.read(query)
|
||||||
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/?`, {
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/?`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Bearer mytoken"
|
Authorization: "Bearer mytoken",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("page based pagination", () => {
|
||||||
|
it("can paginate using query params", async () => {
|
||||||
|
const pageParam = "my_page_param"
|
||||||
|
const sizeParam = "my_size_param"
|
||||||
|
const pageValue = 3
|
||||||
|
const sizeValue = 10
|
||||||
|
const query = {
|
||||||
|
path: "api",
|
||||||
|
pagination: {
|
||||||
|
type: "page",
|
||||||
|
location: "query",
|
||||||
|
pageParam,
|
||||||
|
sizeParam,
|
||||||
|
},
|
||||||
|
paginationValues: {
|
||||||
|
page: pageValue,
|
||||||
|
limit: sizeValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await config.integration.read(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(
|
||||||
|
`${BASE_URL}/api?${pageParam}=${pageValue}&${sizeParam}=${sizeValue}&`,
|
||||||
|
{
|
||||||
|
headers: {},
|
||||||
|
method: "GET",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can paginate using JSON request body", async () => {
|
||||||
|
const pageParam = "my_page_param"
|
||||||
|
const sizeParam = "my_size_param"
|
||||||
|
const pageValue = 3
|
||||||
|
const sizeValue = 10
|
||||||
|
const query = {
|
||||||
|
bodyType: "json",
|
||||||
|
path: "api",
|
||||||
|
pagination: {
|
||||||
|
type: "page",
|
||||||
|
location: "body",
|
||||||
|
pageParam,
|
||||||
|
sizeParam,
|
||||||
|
},
|
||||||
|
paginationValues: {
|
||||||
|
page: pageValue,
|
||||||
|
limit: sizeValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await config.integration.create(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
[pageParam]: pageValue,
|
||||||
|
[sizeParam]: sizeValue,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can paginate using form-data request body", async () => {
|
||||||
|
const pageParam = "my_page_param"
|
||||||
|
const sizeParam = "my_size_param"
|
||||||
|
const pageValue = 3
|
||||||
|
const sizeValue = 10
|
||||||
|
const query = {
|
||||||
|
bodyType: "form",
|
||||||
|
path: "api",
|
||||||
|
pagination: {
|
||||||
|
type: "page",
|
||||||
|
location: "body",
|
||||||
|
pageParam,
|
||||||
|
sizeParam,
|
||||||
|
},
|
||||||
|
paginationValues: {
|
||||||
|
page: pageValue,
|
||||||
|
limit: sizeValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await config.integration.create(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?`, {
|
||||||
|
body: expect.any(FormData),
|
||||||
|
headers: {},
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
const sentData = JSON.stringify(fetch.mock.calls[0][1].body)
|
||||||
|
expect(sentData).toContain(pageParam)
|
||||||
|
expect(sentData).toContain(sizeParam)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can paginate using form-encoded request body", async () => {
|
||||||
|
const pageParam = "my_page_param"
|
||||||
|
const sizeParam = "my_size_param"
|
||||||
|
const pageValue = 3
|
||||||
|
const sizeValue = 10
|
||||||
|
const query = {
|
||||||
|
bodyType: "encoded",
|
||||||
|
path: "api",
|
||||||
|
pagination: {
|
||||||
|
type: "page",
|
||||||
|
location: "body",
|
||||||
|
pageParam,
|
||||||
|
sizeParam,
|
||||||
|
},
|
||||||
|
paginationValues: {
|
||||||
|
page: pageValue,
|
||||||
|
limit: sizeValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await config.integration.create(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?`, {
|
||||||
|
body: expect.any(URLSearchParams),
|
||||||
|
headers: {},
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
const sentData = fetch.mock.calls[0][1].body
|
||||||
|
expect(sentData.has(pageParam))
|
||||||
|
expect(sentData.get(pageParam)).toEqual(pageValue.toString())
|
||||||
|
expect(sentData.has(sizeParam))
|
||||||
|
expect(sentData.get(sizeParam)).toEqual(sizeValue.toString())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("cursor based pagination", () => {
|
||||||
|
it("can paginate using query params", async () => {
|
||||||
|
const pageParam = "my_page_param"
|
||||||
|
const sizeParam = "my_size_param"
|
||||||
|
const pageValue = 3
|
||||||
|
const sizeValue = 10
|
||||||
|
const query = {
|
||||||
|
path: "api",
|
||||||
|
pagination: {
|
||||||
|
type: "cursor",
|
||||||
|
location: "query",
|
||||||
|
pageParam,
|
||||||
|
sizeParam,
|
||||||
|
responseParam: "my_next_cursor",
|
||||||
|
},
|
||||||
|
paginationValues: {
|
||||||
|
page: pageValue,
|
||||||
|
limit: sizeValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const res = await config.integration.read(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(
|
||||||
|
`${BASE_URL}/api?${pageParam}=${pageValue}&${sizeParam}=${sizeValue}&`,
|
||||||
|
{
|
||||||
|
headers: {},
|
||||||
|
method: "GET",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(res.pagination.cursor).toEqual(123)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can paginate using JSON request body", async () => {
|
||||||
|
const pageParam = "my_page_param"
|
||||||
|
const sizeParam = "my_size_param"
|
||||||
|
const pageValue = 3
|
||||||
|
const sizeValue = 10
|
||||||
|
const query = {
|
||||||
|
bodyType: "json",
|
||||||
|
path: "api",
|
||||||
|
pagination: {
|
||||||
|
type: "page",
|
||||||
|
location: "body",
|
||||||
|
pageParam,
|
||||||
|
sizeParam,
|
||||||
|
responseParam: "my_next_cursor",
|
||||||
|
},
|
||||||
|
paginationValues: {
|
||||||
|
page: pageValue,
|
||||||
|
limit: sizeValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const res = await config.integration.create(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
[pageParam]: pageValue,
|
||||||
|
[sizeParam]: sizeValue,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
expect(res.pagination.cursor).toEqual(123)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can paginate using form-data request body", async () => {
|
||||||
|
const pageParam = "my_page_param"
|
||||||
|
const sizeParam = "my_size_param"
|
||||||
|
const pageValue = 3
|
||||||
|
const sizeValue = 10
|
||||||
|
const query = {
|
||||||
|
bodyType: "form",
|
||||||
|
path: "api",
|
||||||
|
pagination: {
|
||||||
|
type: "page",
|
||||||
|
location: "body",
|
||||||
|
pageParam,
|
||||||
|
sizeParam,
|
||||||
|
responseParam: "my_next_cursor",
|
||||||
|
},
|
||||||
|
paginationValues: {
|
||||||
|
page: pageValue,
|
||||||
|
limit: sizeValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const res = await config.integration.create(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?`, {
|
||||||
|
body: expect.any(FormData),
|
||||||
|
headers: {},
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
const sentData = JSON.stringify(fetch.mock.calls[0][1].body)
|
||||||
|
expect(sentData).toContain(pageParam)
|
||||||
|
expect(sentData).toContain(sizeParam)
|
||||||
|
expect(res.pagination.cursor).toEqual(123)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can paginate using form-encoded request body", async () => {
|
||||||
|
const pageParam = "my_page_param"
|
||||||
|
const sizeParam = "my_size_param"
|
||||||
|
const pageValue = 3
|
||||||
|
const sizeValue = 10
|
||||||
|
const query = {
|
||||||
|
bodyType: "encoded",
|
||||||
|
path: "api",
|
||||||
|
pagination: {
|
||||||
|
type: "page",
|
||||||
|
location: "body",
|
||||||
|
pageParam,
|
||||||
|
sizeParam,
|
||||||
|
responseParam: "my_next_cursor",
|
||||||
|
},
|
||||||
|
paginationValues: {
|
||||||
|
page: pageValue,
|
||||||
|
limit: sizeValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const res = await config.integration.create(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?`, {
|
||||||
|
body: expect.any(URLSearchParams),
|
||||||
|
headers: {},
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
const sentData = fetch.mock.calls[0][1].body
|
||||||
|
expect(sentData.has(pageParam))
|
||||||
|
expect(sentData.get(pageParam)).toEqual(pageValue.toString())
|
||||||
|
expect(sentData.has(sizeParam))
|
||||||
|
expect(sentData.get(sizeParam)).toEqual(sizeValue.toString())
|
||||||
|
expect(res.pagination.cursor).toEqual(123)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,6 +5,9 @@ const { integrations } = require("../integrations")
|
||||||
const { processStringSync } = require("@budibase/string-templates")
|
const { processStringSync } = require("@budibase/string-templates")
|
||||||
const CouchDB = require("../db")
|
const CouchDB = require("../db")
|
||||||
|
|
||||||
|
const IS_TRIPLE_BRACE = new RegExp(/^{{3}.*}{3}$/)
|
||||||
|
const IS_HANDLEBARS = new RegExp(/^{{2}.*}{2}$/)
|
||||||
|
|
||||||
class QueryRunner {
|
class QueryRunner {
|
||||||
constructor(input, flags = { noRecursiveQuery: false }) {
|
constructor(input, flags = { noRecursiveQuery: false }) {
|
||||||
this.appId = input.appId
|
this.appId = input.appId
|
||||||
|
@ -12,6 +15,7 @@ class QueryRunner {
|
||||||
this.queryVerb = input.queryVerb
|
this.queryVerb = input.queryVerb
|
||||||
this.fields = input.fields
|
this.fields = input.fields
|
||||||
this.parameters = input.parameters
|
this.parameters = input.parameters
|
||||||
|
this.pagination = input.pagination
|
||||||
this.transformer = input.transformer
|
this.transformer = input.transformer
|
||||||
this.queryId = input.queryId
|
this.queryId = input.queryId
|
||||||
this.noRecursiveQuery = flags.noRecursiveQuery
|
this.noRecursiveQuery = flags.noRecursiveQuery
|
||||||
|
@ -27,7 +31,13 @@ class QueryRunner {
|
||||||
let { datasource, fields, queryVerb, transformer } = this
|
let { datasource, fields, queryVerb, transformer } = this
|
||||||
// pre-query, make sure datasource variables are added to parameters
|
// pre-query, make sure datasource variables are added to parameters
|
||||||
const parameters = await this.addDatasourceVariables()
|
const parameters = await this.addDatasourceVariables()
|
||||||
const query = threadUtils.enrichQueryFields(fields, parameters)
|
let query = this.enrichQueryFields(fields, parameters)
|
||||||
|
|
||||||
|
// Add pagination values for REST queries
|
||||||
|
if (this.pagination) {
|
||||||
|
query.paginationValues = this.pagination
|
||||||
|
}
|
||||||
|
|
||||||
const Integration = integrations[datasource.source]
|
const Integration = integrations[datasource.source]
|
||||||
if (!Integration) {
|
if (!Integration) {
|
||||||
throw "Integration type does not exist."
|
throw "Integration type does not exist."
|
||||||
|
@ -37,11 +47,13 @@ class QueryRunner {
|
||||||
let output = threadUtils.formatResponse(await integration[queryVerb](query))
|
let output = threadUtils.formatResponse(await integration[queryVerb](query))
|
||||||
let rows = output,
|
let rows = output,
|
||||||
info = undefined,
|
info = undefined,
|
||||||
extra = undefined
|
extra = undefined,
|
||||||
|
pagination = undefined
|
||||||
if (threadUtils.hasExtraData(output)) {
|
if (threadUtils.hasExtraData(output)) {
|
||||||
rows = output.data
|
rows = output.data
|
||||||
info = output.info
|
info = output.info
|
||||||
extra = output.extra
|
extra = output.extra
|
||||||
|
pagination = output.pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
// transform as required
|
// transform as required
|
||||||
|
@ -83,7 +95,7 @@ class QueryRunner {
|
||||||
integration.end()
|
integration.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
return { rows, keys, info, extra }
|
return { rows, keys, info, extra, pagination }
|
||||||
}
|
}
|
||||||
|
|
||||||
async runAnotherQuery(queryId, parameters) {
|
async runAnotherQuery(queryId, parameters) {
|
||||||
|
@ -159,6 +171,50 @@ class QueryRunner {
|
||||||
}
|
}
|
||||||
return parameters
|
return parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enrichQueryFields(fields, parameters = {}) {
|
||||||
|
const enrichedQuery = {}
|
||||||
|
|
||||||
|
// enrich the fields with dynamic parameters
|
||||||
|
for (let key of Object.keys(fields)) {
|
||||||
|
if (fields[key] == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (typeof fields[key] === "object") {
|
||||||
|
// enrich nested fields object
|
||||||
|
enrichedQuery[key] = this.enrichQueryFields(fields[key], parameters)
|
||||||
|
} else if (typeof fields[key] === "string") {
|
||||||
|
// enrich string value as normal
|
||||||
|
let value = fields[key]
|
||||||
|
// add triple brace to avoid escaping e.g. '=' in cookie header
|
||||||
|
if (IS_HANDLEBARS.test(value) && !IS_TRIPLE_BRACE.test(value)) {
|
||||||
|
value = `{${value}}`
|
||||||
|
}
|
||||||
|
enrichedQuery[key] = processStringSync(value, parameters, {
|
||||||
|
noHelpers: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
enrichedQuery[key] = fields[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
enrichedQuery.json ||
|
||||||
|
enrichedQuery.customData ||
|
||||||
|
enrichedQuery.requestBody
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
enrichedQuery.json = JSON.parse(
|
||||||
|
enrichedQuery.json ||
|
||||||
|
enrichedQuery.customData ||
|
||||||
|
enrichedQuery.requestBody
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
// no json found, ignore
|
||||||
|
}
|
||||||
|
delete enrichedQuery.customData
|
||||||
|
}
|
||||||
|
return enrichedQuery
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = (input, callback) => {
|
module.exports = (input, callback) => {
|
||||||
|
|
|
@ -3,14 +3,10 @@ const CouchDB = require("../db")
|
||||||
const { init } = require("@budibase/backend-core")
|
const { init } = require("@budibase/backend-core")
|
||||||
const redis = require("@budibase/backend-core/redis")
|
const redis = require("@budibase/backend-core/redis")
|
||||||
const { SEPARATOR } = require("@budibase/backend-core/db")
|
const { SEPARATOR } = require("@budibase/backend-core/db")
|
||||||
const { processStringSync } = require("@budibase/string-templates")
|
|
||||||
|
|
||||||
const VARIABLE_TTL_SECONDS = 3600
|
const VARIABLE_TTL_SECONDS = 3600
|
||||||
let client
|
let client
|
||||||
|
|
||||||
const IS_TRIPLE_BRACE = new RegExp(/^{{3}.*}{3}$/)
|
|
||||||
const IS_HANDLEBARS = new RegExp(/^{{2}.*}{2}$/)
|
|
||||||
|
|
||||||
async function getClient() {
|
async function getClient() {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
client = await new redis.Client(redis.utils.Databases.QUERY_VARS).init()
|
client = await new redis.Client(redis.utils.Databases.QUERY_VARS).init()
|
||||||
|
@ -80,49 +76,3 @@ exports.hasExtraData = response => {
|
||||||
response.info != null
|
response.info != null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.enrichQueryFields = (fields, parameters = {}) => {
|
|
||||||
const enrichedQuery = {}
|
|
||||||
|
|
||||||
// enrich the fields with dynamic parameters
|
|
||||||
for (let key of Object.keys(fields)) {
|
|
||||||
if (fields[key] == null) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (typeof fields[key] === "object") {
|
|
||||||
// enrich nested fields object
|
|
||||||
enrichedQuery[key] = this.enrichQueryFields(fields[key], parameters)
|
|
||||||
} else if (typeof fields[key] === "string") {
|
|
||||||
// enrich string value as normal
|
|
||||||
let value = fields[key]
|
|
||||||
// add triple brace to avoid escaping e.g. '=' in cookie header
|
|
||||||
if (IS_HANDLEBARS.test(value) && !IS_TRIPLE_BRACE.test(value)) {
|
|
||||||
value = `{${value}}`
|
|
||||||
}
|
|
||||||
enrichedQuery[key] = processStringSync(value, parameters, {
|
|
||||||
noHelpers: true,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
enrichedQuery[key] = fields[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
enrichedQuery.json ||
|
|
||||||
enrichedQuery.customData ||
|
|
||||||
enrichedQuery.requestBody
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
enrichedQuery.json = JSON.parse(
|
|
||||||
enrichedQuery.json ||
|
|
||||||
enrichedQuery.customData ||
|
|
||||||
enrichedQuery.requestBody
|
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
// no json found, ignore
|
|
||||||
}
|
|
||||||
delete enrichedQuery.customData
|
|
||||||
}
|
|
||||||
|
|
||||||
return enrichedQuery
|
|
||||||
}
|
|
||||||
|
|
|
@ -983,10 +983,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@^1.0.27-alpha.0":
|
"@budibase/backend-core@^1.0.27-alpha.13":
|
||||||
version "1.0.27-alpha.0"
|
version "1.0.27-alpha.13"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/auth-1.0.27-alpha.0.tgz#8020c205d20d722983906426cb5a1aaf5cc6aba4"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.27-alpha.13.tgz#89f46e081eb7b342f483fd0eccd72c42b2b2fa6c"
|
||||||
integrity sha512-sfXJjQJsFWfgElsHGHn7beERcsrUA5cotN2p9XEp15SrMeEmy4s9a6K58b779QB/d28GXKXtSJwmM/DrptJetQ==
|
integrity sha512-NiasBvZ5wTpvANG9AjuO34DHMTqWQWSpabLcgwBY0tNG4ekh+wvSCPjCcUvN/bBpOzrVMQ8C4hmS4pvv342BhQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@techpass/passport-openidconnect" "^0.3.0"
|
"@techpass/passport-openidconnect" "^0.3.0"
|
||||||
aws-sdk "^2.901.0"
|
aws-sdk "^2.901.0"
|
||||||
|
@ -1056,26 +1056,64 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/bbui@^1.0.27-alpha.0":
|
"@budibase/bbui@^1.0.35":
|
||||||
version "1.58.13"
|
version "1.0.35"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.58.13.tgz#59df9c73def2d81c75dcbd2266c52c19db88dbd7"
|
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.0.35.tgz#a51886886772257d31e2c6346dbec46fe0c9fd85"
|
||||||
integrity sha512-Zk6CKXdBfKsTVzA1Xs5++shdSSZLfphVpZuKVbjfzkgtuhyH7ruucexuSHEpFsxjW5rEKgKIBoRFzCK5vPvN0w==
|
integrity sha512-8qeAzTujtO7uvhj+dMiyW4BTkQ7dC4xF1CNIwyuTnDwIeFDlXYgNb09VVRs3+nWcX2e2eC53EUs1RnLUoSlTsw==
|
||||||
dependencies:
|
dependencies:
|
||||||
markdown-it "^12.0.2"
|
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
|
||||||
quill "^1.3.7"
|
"@spectrum-css/actionbutton" "^1.0.1"
|
||||||
sirv-cli "^0.4.6"
|
"@spectrum-css/actiongroup" "^1.0.1"
|
||||||
svelte-flatpickr "^2.4.0"
|
"@spectrum-css/avatar" "^3.0.2"
|
||||||
|
"@spectrum-css/button" "^3.0.1"
|
||||||
|
"@spectrum-css/buttongroup" "^3.0.2"
|
||||||
|
"@spectrum-css/checkbox" "^3.0.2"
|
||||||
|
"@spectrum-css/dialog" "^3.0.1"
|
||||||
|
"@spectrum-css/divider" "^1.0.3"
|
||||||
|
"@spectrum-css/dropzone" "^3.0.2"
|
||||||
|
"@spectrum-css/fieldgroup" "^3.0.2"
|
||||||
|
"@spectrum-css/fieldlabel" "^3.0.1"
|
||||||
|
"@spectrum-css/icon" "^3.0.1"
|
||||||
|
"@spectrum-css/illustratedmessage" "^3.0.2"
|
||||||
|
"@spectrum-css/inlinealert" "^2.0.1"
|
||||||
|
"@spectrum-css/inputgroup" "^3.0.2"
|
||||||
|
"@spectrum-css/label" "^2.0.10"
|
||||||
|
"@spectrum-css/link" "^3.1.1"
|
||||||
|
"@spectrum-css/menu" "^3.0.1"
|
||||||
|
"@spectrum-css/modal" "^3.0.1"
|
||||||
|
"@spectrum-css/pagination" "^3.0.3"
|
||||||
|
"@spectrum-css/picker" "^1.0.1"
|
||||||
|
"@spectrum-css/popover" "^3.0.1"
|
||||||
|
"@spectrum-css/progressbar" "^1.0.2"
|
||||||
|
"@spectrum-css/progresscircle" "^1.0.2"
|
||||||
|
"@spectrum-css/radio" "^3.0.2"
|
||||||
|
"@spectrum-css/search" "^3.0.2"
|
||||||
|
"@spectrum-css/sidenav" "^3.0.2"
|
||||||
|
"@spectrum-css/statuslight" "^3.0.2"
|
||||||
|
"@spectrum-css/stepper" "^3.0.3"
|
||||||
|
"@spectrum-css/switch" "^1.0.2"
|
||||||
|
"@spectrum-css/table" "^3.0.1"
|
||||||
|
"@spectrum-css/tabs" "^3.0.1"
|
||||||
|
"@spectrum-css/tags" "^3.0.2"
|
||||||
|
"@spectrum-css/textfield" "^3.0.1"
|
||||||
|
"@spectrum-css/toast" "^3.0.1"
|
||||||
|
"@spectrum-css/tooltip" "^3.0.3"
|
||||||
|
"@spectrum-css/treeview" "^3.0.2"
|
||||||
|
"@spectrum-css/typography" "^3.0.1"
|
||||||
|
"@spectrum-css/underlay" "^2.0.9"
|
||||||
|
"@spectrum-css/vars" "^3.0.1"
|
||||||
|
dayjs "^1.10.4"
|
||||||
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
turndown "^7.0.0"
|
|
||||||
|
|
||||||
"@budibase/client@^1.0.27-alpha.0":
|
"@budibase/client@^1.0.27-alpha.13":
|
||||||
version "1.0.27-alpha.0"
|
version "1.0.35"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-1.0.27-alpha.0.tgz#5393d51f4fd08307aad01dd62fcd717acaa38d68"
|
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-1.0.35.tgz#b832e7e7e35032fb35fe5492fbb721db1da15394"
|
||||||
integrity sha512-wAGiPjZ4n8j69Y0em1nkkUlabcTx7aw7F9MgUusX1oMPihQ0lnBn1Z3rnHON2tRk3rTcdlnitPfGFqsVFFWsCg==
|
integrity sha512-maL3V29PQb9VjgnPZq44GSDZCuamAGp01bheUeJxEeskjQqZUdf8QC7Frf1mT+ZjgKJf3gU6qtFOxmWRbVzVbw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/bbui" "^1.0.27-alpha.0"
|
"@budibase/bbui" "^1.0.35"
|
||||||
"@budibase/standard-components" "^0.9.139"
|
"@budibase/standard-components" "^0.9.139"
|
||||||
"@budibase/string-templates" "^1.0.27-alpha.0"
|
"@budibase/string-templates" "^1.0.35"
|
||||||
regexparam "^1.3.0"
|
regexparam "^1.3.0"
|
||||||
shortid "^2.2.15"
|
shortid "^2.2.15"
|
||||||
svelte-spa-router "^3.0.5"
|
svelte-spa-router "^3.0.5"
|
||||||
|
@ -1125,10 +1163,10 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/string-templates@^1.0.27-alpha.0":
|
"@budibase/string-templates@^1.0.27-alpha.13", "@budibase/string-templates@^1.0.35":
|
||||||
version "1.0.27-alpha.0"
|
version "1.0.35"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.27-alpha.0.tgz#89f72e0599e94f95540c9e4fb7948bec5d645526"
|
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.35.tgz#a888f1e9327bb36416336a91a95a43cb34e6a42d"
|
||||||
integrity sha512-MQXyw+/oIJg2Ezs3GK/HJ2p01ANpl1IjUP/HxDZhTiGUXPDwHXGDKE+t32tiwsYY2l+cn8wHy2DOQbLsRoZhVg==
|
integrity sha512-8HxSv0ru+cgSmphqtOm1pmBM8rc0TRC/6RQGzQefmFFQFfm/SBLAVLLWRmZxAOYTxt4mittGWeL4y05FqEuocg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/handlebars-helpers" "^0.11.7"
|
"@budibase/handlebars-helpers" "^0.11.7"
|
||||||
dayjs "^1.10.4"
|
dayjs "^1.10.4"
|
||||||
|
@ -1873,11 +1911,6 @@
|
||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
"@polka/url@^0.5.0":
|
|
||||||
version "0.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31"
|
|
||||||
integrity sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==
|
|
||||||
|
|
||||||
"@sendgrid/client@^7.1.1":
|
"@sendgrid/client@^7.1.1":
|
||||||
version "7.6.0"
|
version "7.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.6.0.tgz#f90cb8759c96e1d90224f29ad98f8fdc2be287f3"
|
resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.6.0.tgz#f90cb8759c96e1d90224f29ad98f8fdc2be287f3"
|
||||||
|
@ -2065,6 +2098,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.8.tgz#69ef0c935bcc5027f233a78de5aeb0064bf033cb"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.8.tgz#69ef0c935bcc5027f233a78de5aeb0064bf033cb"
|
||||||
integrity sha512-HvC4dywDi11GdrXQDCvKQ0vFlrXLTyJuc9UKf7meQLCGoJbGYDBwe+tHXNK1c6gPMD9BoL6pPMP1K/vRzR4EBQ==
|
integrity sha512-HvC4dywDi11GdrXQDCvKQ0vFlrXLTyJuc9UKf7meQLCGoJbGYDBwe+tHXNK1c6gPMD9BoL6pPMP1K/vRzR4EBQ==
|
||||||
|
|
||||||
|
"@spectrum-css/inlinealert@^2.0.1":
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@spectrum-css/inlinealert/-/inlinealert-2.0.6.tgz#4c5e923a1f56a96cc1adb30ef1f06ae04f2c6376"
|
||||||
|
integrity sha512-OpvvoWP02wWyCnF4IgG8SOPkXymovkC9cGtgMS1FdDubnG3tJZB/JeKTsRR9C9Vt3WBaOmISRdSKlZ4lC9CFzA==
|
||||||
|
|
||||||
"@spectrum-css/inputgroup@^3.0.2":
|
"@spectrum-css/inputgroup@^3.0.2":
|
||||||
version "3.0.8"
|
version "3.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.8.tgz#fc23afc8a73c24d17249c9d2337e8b42085b298b"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.8.tgz#fc23afc8a73c24d17249c9d2337e8b42085b298b"
|
||||||
|
@ -3979,11 +4017,6 @@ clone-response@1.0.2, clone-response@^1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-response "^1.0.0"
|
mimic-response "^1.0.0"
|
||||||
|
|
||||||
clone@^2.1.1:
|
|
||||||
version "2.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
|
||||||
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
|
|
||||||
|
|
||||||
cls-hooked@^4.2.2:
|
cls-hooked@^4.2.2:
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908"
|
resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908"
|
||||||
|
@ -4184,11 +4217,6 @@ configstore@^5.0.1:
|
||||||
write-file-atomic "^3.0.0"
|
write-file-atomic "^3.0.0"
|
||||||
xdg-basedir "^4.0.0"
|
xdg-basedir "^4.0.0"
|
||||||
|
|
||||||
console-clear@^1.1.0:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/console-clear/-/console-clear-1.1.1.tgz#995e20cbfbf14dd792b672cde387bd128d674bf7"
|
|
||||||
integrity sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==
|
|
||||||
|
|
||||||
consolidate@^0.16.0:
|
consolidate@^0.16.0:
|
||||||
version "0.16.0"
|
version "0.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.16.0.tgz#a11864768930f2f19431660a65906668f5fbdc16"
|
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.16.0.tgz#a11864768930f2f19431660a65906668f5fbdc16"
|
||||||
|
@ -4536,18 +4564,6 @@ dedent@^0.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
|
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
|
||||||
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
|
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
|
||||||
|
|
||||||
deep-equal@^1.0.1:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
|
||||||
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
|
|
||||||
dependencies:
|
|
||||||
is-arguments "^1.0.4"
|
|
||||||
is-date-object "^1.0.1"
|
|
||||||
is-regex "^1.0.4"
|
|
||||||
object-is "^1.0.1"
|
|
||||||
object-keys "^1.1.1"
|
|
||||||
regexp.prototype.flags "^1.2.0"
|
|
||||||
|
|
||||||
deep-equal@~1.0.1:
|
deep-equal@~1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||||
|
@ -4758,11 +4774,6 @@ domexception@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
webidl-conversions "^5.0.0"
|
webidl-conversions "^5.0.0"
|
||||||
|
|
||||||
domino@^2.1.6:
|
|
||||||
version "2.1.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe"
|
|
||||||
integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==
|
|
||||||
|
|
||||||
dot-prop@^5.2.0:
|
dot-prop@^5.2.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
|
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
|
||||||
|
@ -5341,11 +5352,6 @@ event-target-shim@^5.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
||||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
||||||
|
|
||||||
eventemitter3@^2.0.3:
|
|
||||||
version "2.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
|
|
||||||
integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
|
|
||||||
|
|
||||||
events@1.1.1:
|
events@1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
||||||
|
@ -5484,7 +5490,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
|
||||||
assign-symbols "^1.0.0"
|
assign-symbols "^1.0.0"
|
||||||
is-extendable "^1.0.1"
|
is-extendable "^1.0.1"
|
||||||
|
|
||||||
extend@^3.0.0, extend@^3.0.2, extend@~3.0.2:
|
extend@^3.0.0, extend@~3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||||
|
@ -5532,11 +5538,6 @@ fast-deep-equal@^3.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
|
||||||
fast-diff@1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
|
|
||||||
integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
|
|
||||||
|
|
||||||
fast-glob@^3.1.1:
|
fast-glob@^3.1.1:
|
||||||
version "3.2.7"
|
version "3.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
|
||||||
|
@ -5964,11 +5965,6 @@ get-paths@0.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
pify "^4.0.1"
|
pify "^4.0.1"
|
||||||
|
|
||||||
get-port@^3.2.0:
|
|
||||||
version "3.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc"
|
|
||||||
integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=
|
|
||||||
|
|
||||||
get-port@^5.1.1:
|
get-port@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
|
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
|
||||||
|
@ -6709,14 +6705,6 @@ is-accessor-descriptor@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
kind-of "^6.0.0"
|
kind-of "^6.0.0"
|
||||||
|
|
||||||
is-arguments@^1.0.4:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
|
|
||||||
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
|
|
||||||
dependencies:
|
|
||||||
call-bind "^1.0.2"
|
|
||||||
has-tostringtag "^1.0.0"
|
|
||||||
|
|
||||||
is-arrayish@^0.2.1:
|
is-arrayish@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||||
|
@ -6981,7 +6969,7 @@ is-property@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
|
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
|
||||||
integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=
|
integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=
|
||||||
|
|
||||||
is-regex@^1.0.4, is-regex@^1.1.4:
|
is-regex@^1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
||||||
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
|
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
|
||||||
|
@ -8267,7 +8255,7 @@ klaw-sync@^6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs "^4.1.11"
|
graceful-fs "^4.1.11"
|
||||||
|
|
||||||
kleur@^3.0.0, kleur@^3.0.3:
|
kleur@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||||
|
@ -8691,11 +8679,6 @@ loader-utils@^2.0.0:
|
||||||
emojis-list "^3.0.0"
|
emojis-list "^3.0.0"
|
||||||
json5 "^2.1.2"
|
json5 "^2.1.2"
|
||||||
|
|
||||||
local-access@^1.0.1:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.1.0.tgz#e007c76ba2ca83d5877ba1a125fc8dfe23ba4798"
|
|
||||||
integrity sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw==
|
|
||||||
|
|
||||||
locate-path@^3.0.0:
|
locate-path@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
||||||
|
@ -8962,17 +8945,6 @@ map-visit@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
object-visit "^1.0.0"
|
object-visit "^1.0.0"
|
||||||
|
|
||||||
markdown-it@^12.0.2:
|
|
||||||
version "12.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.0.tgz#11490c61b412b8f41530319c005ecdcd4367171f"
|
|
||||||
integrity sha512-T345UZZ6ejQWTjG6PSEHplzNy5m4kF6zvUpHVDv8Snl/pEU0OxIK0jGg8YLVNwJvT8E0YJC7/2UvssJDk/wQCQ==
|
|
||||||
dependencies:
|
|
||||||
argparse "^2.0.1"
|
|
||||||
entities "~2.1.0"
|
|
||||||
linkify-it "^3.0.1"
|
|
||||||
mdurl "^1.0.1"
|
|
||||||
uc.micro "^1.0.5"
|
|
||||||
|
|
||||||
markdown-it@^12.2.0:
|
markdown-it@^12.2.0:
|
||||||
version "12.2.0"
|
version "12.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db"
|
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db"
|
||||||
|
@ -9113,11 +9085,6 @@ mime@^1.3.4, mime@^1.4.1:
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
mime@^2.3.1:
|
|
||||||
version "2.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
|
||||||
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
|
||||||
|
|
||||||
mimic-fn@^2.0.0, mimic-fn@^2.1.0:
|
mimic-fn@^2.0.0, mimic-fn@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
|
@ -9202,11 +9169,6 @@ mri@1.1.4:
|
||||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a"
|
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a"
|
||||||
integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==
|
integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==
|
||||||
|
|
||||||
mri@^1.1.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
|
||||||
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
|
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
@ -9524,14 +9486,6 @@ object-inspect@^1.11.0, object-inspect@^1.9.0:
|
||||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
|
||||||
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
|
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
|
||||||
|
|
||||||
object-is@^1.0.1:
|
|
||||||
version "1.1.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
|
|
||||||
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
|
|
||||||
dependencies:
|
|
||||||
call-bind "^1.0.2"
|
|
||||||
define-properties "^1.1.3"
|
|
||||||
|
|
||||||
object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1:
|
object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||||
|
@ -9760,11 +9714,6 @@ pako@^1.0.5:
|
||||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||||
|
|
||||||
parchment@^1.1.4:
|
|
||||||
version "1.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5"
|
|
||||||
integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==
|
|
||||||
|
|
||||||
parent-module@^1.0.0:
|
parent-module@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||||
|
@ -10609,27 +10558,6 @@ quick-format-unescaped@^4.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7"
|
resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7"
|
||||||
integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==
|
integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==
|
||||||
|
|
||||||
quill-delta@^3.6.2:
|
|
||||||
version "3.6.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
|
|
||||||
integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==
|
|
||||||
dependencies:
|
|
||||||
deep-equal "^1.0.1"
|
|
||||||
extend "^3.0.2"
|
|
||||||
fast-diff "1.1.2"
|
|
||||||
|
|
||||||
quill@^1.3.7:
|
|
||||||
version "1.3.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8"
|
|
||||||
integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==
|
|
||||||
dependencies:
|
|
||||||
clone "^2.1.1"
|
|
||||||
deep-equal "^1.0.1"
|
|
||||||
eventemitter3 "^2.0.3"
|
|
||||||
extend "^3.0.2"
|
|
||||||
parchment "^1.1.4"
|
|
||||||
quill-delta "^3.6.2"
|
|
||||||
|
|
||||||
randombytes@^2.1.0:
|
randombytes@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||||
|
@ -10845,14 +10773,6 @@ regex-not@^1.0.0, regex-not@^1.0.2:
|
||||||
extend-shallow "^3.0.2"
|
extend-shallow "^3.0.2"
|
||||||
safe-regex "^1.1.0"
|
safe-regex "^1.1.0"
|
||||||
|
|
||||||
regexp.prototype.flags@^1.2.0:
|
|
||||||
version "1.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26"
|
|
||||||
integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==
|
|
||||||
dependencies:
|
|
||||||
call-bind "^1.0.2"
|
|
||||||
define-properties "^1.1.3"
|
|
||||||
|
|
||||||
regexparam@2.0.0:
|
regexparam@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-2.0.0.tgz#059476767d5f5f87f735fc7922d133fd1a118c8c"
|
resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-2.0.0.tgz#059476767d5f5f87f735fc7922d133fd1a118c8c"
|
||||||
|
@ -11122,13 +11042,6 @@ rxjs@^6.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
sade@^1.4.0:
|
|
||||||
version "1.7.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691"
|
|
||||||
integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==
|
|
||||||
dependencies:
|
|
||||||
mri "^1.1.0"
|
|
||||||
|
|
||||||
safe-buffer@*, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
safe-buffer@*, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
|
@ -11387,27 +11300,6 @@ simple-swizzle@^0.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish "^0.3.1"
|
is-arrayish "^0.3.1"
|
||||||
|
|
||||||
sirv-cli@^0.4.6:
|
|
||||||
version "0.4.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/sirv-cli/-/sirv-cli-0.4.6.tgz#c28ab20deb3b34637f5a60863dc350f055abca04"
|
|
||||||
integrity sha512-/Vj85/kBvPL+n9ibgX6FicLE8VjidC1BhlX67PYPBfbBAphzR6i0k0HtU5c2arejfU3uzq8l3SYPCwl1x7z6Ww==
|
|
||||||
dependencies:
|
|
||||||
console-clear "^1.1.0"
|
|
||||||
get-port "^3.2.0"
|
|
||||||
kleur "^3.0.0"
|
|
||||||
local-access "^1.0.1"
|
|
||||||
sade "^1.4.0"
|
|
||||||
sirv "^0.4.6"
|
|
||||||
tinydate "^1.0.0"
|
|
||||||
|
|
||||||
sirv@^0.4.6:
|
|
||||||
version "0.4.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/sirv/-/sirv-0.4.6.tgz#185e44eb93d24009dd183b7494285c5180b81f22"
|
|
||||||
integrity sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ==
|
|
||||||
dependencies:
|
|
||||||
"@polka/url" "^0.5.0"
|
|
||||||
mime "^2.3.1"
|
|
||||||
|
|
||||||
sisteransi@^1.0.5:
|
sisteransi@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||||
|
@ -11956,13 +11848,6 @@ svelte-apexcharts@^1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
apexcharts "^3.19.2"
|
apexcharts "^3.19.2"
|
||||||
|
|
||||||
svelte-flatpickr@^2.4.0:
|
|
||||||
version "2.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-2.4.0.tgz#190871fc3305956c8c8fd3601cd036b8ac71ef49"
|
|
||||||
integrity sha512-UUC5Te+b0qi4POg7VDwfGh0m5W3Hf64OwkfOTj6FEe/dYZN4cBzpQ82EuuQl0CTbbBAsMkcjJcixV1d2V6EHCQ==
|
|
||||||
dependencies:
|
|
||||||
flatpickr "^4.5.2"
|
|
||||||
|
|
||||||
svelte-flatpickr@^3.1.0, svelte-flatpickr@^3.2.3:
|
svelte-flatpickr@^3.1.0, svelte-flatpickr@^3.2.3:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.2.4.tgz#1824e26a5dc151d14906cfc7dfd100aefd1b072d"
|
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.2.4.tgz#1824e26a5dc151d14906cfc7dfd100aefd1b072d"
|
||||||
|
@ -12272,11 +12157,6 @@ tinycolor2@^1.4.1:
|
||||||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
|
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
|
||||||
integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
|
integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
|
||||||
|
|
||||||
tinydate@^1.0.0:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/tinydate/-/tinydate-1.3.0.tgz#e6ca8e5a22b51bb4ea1c3a2a4fd1352dbd4c57fb"
|
|
||||||
integrity sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==
|
|
||||||
|
|
||||||
tmp@^0.0.33:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||||
|
@ -12488,13 +12368,6 @@ tunnel@0.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||||
|
|
||||||
turndown@^7.0.0:
|
|
||||||
version "7.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/turndown/-/turndown-7.1.1.tgz#96992f2d9b40a1a03d3ea61ad31b5a5c751ef77f"
|
|
||||||
integrity sha512-BEkXaWH7Wh7e9bd2QumhfAXk5g34+6QUmmWx+0q6ThaVOLuLUqsnkq35HQ5SBHSaxjSfSM7US5o4lhJNH7B9MA==
|
|
||||||
dependencies:
|
|
||||||
domino "^2.1.6"
|
|
||||||
|
|
||||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||||
version "0.14.5"
|
version "0.14.5"
|
||||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||||
|
|
Loading…
Reference in New Issue