Working on plumbing 'source' all the way through our code.

This commit is contained in:
Sam Rose 2024-09-24 12:30:45 +01:00
parent 6cf7c55fd9
commit 51774b3434
No known key found for this signature in database
23 changed files with 204 additions and 215 deletions

View File

@ -10,7 +10,7 @@ import {
StaticDatabases,
DEFAULT_TENANT_ID,
} from "../constants"
import { Database, IdentityContext, Snippet, App } from "@budibase/types"
import { Database, IdentityContext, Snippet, App, Table } from "@budibase/types"
import { ContextMap } from "./types"
let TEST_APP_ID: string | null = null
@ -394,3 +394,20 @@ export function setFeatureFlags(key: string, value: Record<string, any>) {
context.featureFlagCache ??= {}
context.featureFlagCache[key] = value
}
export function getTableForView(viewId: string): Table | undefined {
const context = getCurrentContext()
if (!context) {
return
}
return context.viewToTableCache?.[viewId]
}
export function setTableForView(viewId: string, table: Table) {
const context = getCurrentContext()
if (!context) {
return
}
context.viewToTableCache ??= {}
context.viewToTableCache[viewId] = table
}

View File

@ -1,4 +1,4 @@
import { IdentityContext, Snippet, VM } from "@budibase/types"
import { IdentityContext, Snippet, Table, VM } from "@budibase/types"
import { OAuth2Client } from "google-auth-library"
import { GoogleSpreadsheet } from "google-spreadsheet"
@ -21,4 +21,5 @@ export type ContextMap = {
featureFlagCache?: {
[key: string]: Record<string, any>
}
viewToTableCache?: Record<string, Table>
}

View File

@ -6,7 +6,7 @@ import {
ViewName,
} from "../constants"
import { getProdAppID } from "./conversions"
import { DatabaseQueryOpts } from "@budibase/types"
import { DatabaseQueryOpts, VirtualDocumentType } from "@budibase/types"
/**
* If creating DB allDocs/query params with only a single top level ID this can be used, this
@ -66,9 +66,8 @@ export function getQueryIndex(viewName: ViewName) {
/**
* Check if a given ID is that of a table.
* @returns {boolean}
*/
export const isTableId = (id: string) => {
export const isTableId = (id: string): boolean => {
// this includes datasource plus tables
return (
!!id &&
@ -77,13 +76,16 @@ export const isTableId = (id: string) => {
)
}
export function isViewId(id: string): boolean {
return !!id && id.startsWith(`${VirtualDocumentType.VIEW}${SEPARATOR}`)
}
/**
* Check if a given ID is that of a datasource or datasource plus.
* @returns {boolean}
*/
export const isDatasourceId = (id: string) => {
export const isDatasourceId = (id: string): boolean => {
// this covers both datasources and datasource plus
return id && id.startsWith(`${DocumentType.DATASOURCE}${SEPARATOR}`)
return !!id && id.startsWith(`${DocumentType.DATASOURCE}${SEPARATOR}`)
}
/**

View File

@ -1274,9 +1274,6 @@ class InternalBuilder {
if (counting) {
query = this.addDistinctCount(query)
} else if (aggregations.length > 0) {
query = query.select(
this.knex.raw("ROW_NUMBER() OVER (ORDER BY (SELECT 0)) as _id")
)
query = this.addAggregations(query, aggregations)
} else {
query = query.select(this.generateSelectStatement())

View File

@ -19,6 +19,7 @@ import {
SortJson,
SortType,
Table,
ViewV2,
} from "@budibase/types"
import {
breakExternalTableId,
@ -159,19 +160,43 @@ function isEditableColumn(column: FieldSchema) {
export class ExternalRequest<T extends Operation> {
private readonly operation: T
private readonly tableId: string
private datasource?: Datasource
private tables: { [key: string]: Table } = {}
private readonly source: Table | ViewV2
private datasource: Datasource
constructor(operation: T, tableId: string, datasource?: Datasource) {
this.operation = operation
this.tableId = tableId
this.datasource = datasource
if (datasource && datasource.entities) {
this.tables = datasource.entities
public static async for<T extends Operation>(
operation: T,
source: Table | ViewV2,
opts: { datasource?: Datasource } = {}
) {
if (!opts.datasource) {
if (sdk.views.isView(source)) {
const table = await sdk.views.getTable(source.id)
opts.datasource = await sdk.datasources.get(table.sourceId!)
} else {
opts.datasource = await sdk.datasources.get(source.sourceId!)
}
}
return new ExternalRequest(operation, source, opts.datasource)
}
private get tables(): { [key: string]: Table } {
if (!this.datasource.entities) {
throw new Error("Datasource does not have entities")
}
return this.datasource.entities
}
private constructor(
operation: T,
source: Table | ViewV2,
datasource: Datasource
) {
this.operation = operation
this.source = source
this.datasource = datasource
}
private prepareFilters(
id: string | undefined | string[],
filters: SearchFilters,
@ -290,20 +315,6 @@ export class ExternalRequest<T extends Operation> {
return this.tables[tableName]
}
// seeds the object with table and datasource information
async retrieveMetadata(
datasourceId: string
): Promise<{ tables: Record<string, Table>; datasource: Datasource }> {
if (!this.datasource) {
this.datasource = await sdk.datasources.get(datasourceId)
if (!this.datasource || !this.datasource.entities) {
throw "No tables found, fetch tables before query."
}
this.tables = this.datasource.entities
}
return { tables: this.tables, datasource: this.datasource }
}
async getRow(table: Table, rowId: string): Promise<Row> {
const response = await getDatasourceAndQuery({
endpoint: getEndpoint(table._id!, Operation.READ),
@ -619,24 +630,16 @@ export class ExternalRequest<T extends Operation> {
}
async run(config: RunConfig): Promise<ExternalRequestReturnType<T>> {
const { operation, tableId } = this
if (!tableId) {
throw new Error("Unable to run without a table ID")
}
let { datasourceId, tableName } = breakExternalTableId(tableId)
let datasource = this.datasource
if (!datasource) {
const { datasource: ds } = await this.retrieveMetadata(datasourceId)
datasource = ds
}
const tables = this.tables
const table = tables[tableName]
let isSql = isSQL(datasource)
if (!table) {
throw new Error(
`Unable to process query, table "${tableName}" not defined.`
)
const { operation } = this
let table: Table
if (sdk.views.isView(this.source)) {
table = await sdk.views.getTable(this.source.id)
} else {
table = this.source
}
let isSql = isSQL(this.datasource)
// look for specific components of config which may not be considered acceptable
let { id, row, filters, sort, paginate, rows } = cleanupConfig(
config,
@ -687,8 +690,8 @@ export class ExternalRequest<T extends Operation> {
}
let json: QueryJson = {
endpoint: {
datasourceId: datasourceId!,
entityId: tableName,
datasourceId: this.datasource._id!,
entityId: table.name,
operation,
},
resource: {
@ -714,7 +717,7 @@ export class ExternalRequest<T extends Operation> {
},
meta: {
table,
tables: tables,
tables: this.tables,
},
}

View File

@ -17,6 +17,7 @@ import {
Row,
Table,
UserCtx,
ViewV2,
} from "@budibase/types"
import sdk from "../../../sdk"
import * as utils from "./utils"
@ -29,29 +30,29 @@ import { generateIdForRow } from "./utils"
export async function handleRequest<T extends Operation>(
operation: T,
tableId: string,
source: Table | ViewV2,
opts?: RunConfig
): Promise<ExternalRequestReturnType<T>> {
return new ExternalRequest<T>(operation, tableId, opts?.datasource).run(
opts || {}
)
return (
await ExternalRequest.for<T>(operation, source, {
datasource: opts?.datasource,
})
).run(opts || {})
}
export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
const { tableId, viewId } = utils.getSourceId(ctx)
const source = await utils.getSource(ctx)
const { _id, ...rowData } = ctx.request.body
const table = await sdk.tables.getTable(tableId)
const { row: dataToUpdate } = await inputProcessing(
ctx.user?._id,
cloneDeep(table),
cloneDeep(source),
rowData
)
const validateResult = await sdk.rows.utils.validate({
row: dataToUpdate,
tableId,
source,
})
if (!validateResult.valid) {
throw { validation: validateResult.errors }

View File

@ -1,6 +1,6 @@
import * as utils from "../../../../db/utils"
import { context } from "@budibase/backend-core"
import { context, docIds } from "@budibase/backend-core"
import {
Aggregation,
Ctx,
@ -9,6 +9,7 @@ import {
RelationshipsJson,
Row,
Table,
ViewV2,
} from "@budibase/types"
import {
processDates,
@ -78,7 +79,7 @@ export function getSourceId(ctx: Ctx): { tableId: string; viewId?: string } {
// top priority, use the URL first
if (ctx.params?.sourceId) {
const { sourceId } = ctx.params
if (utils.isViewID(sourceId)) {
if (docIds.isViewId(sourceId)) {
return {
tableId: utils.extractViewInfoFromID(sourceId).tableId,
viewId: sourceId,
@ -97,6 +98,14 @@ export function getSourceId(ctx: Ctx): { tableId: string; viewId?: string } {
throw new Error("Unable to find table ID in request")
}
export async function getSource(ctx: Ctx): Promise<Table | ViewV2> {
const { tableId, viewId } = getSourceId(ctx)
if (viewId) {
return sdk.views.get(viewId)
}
return sdk.tables.getTable(tableId)
}
export async function validate(
opts: { row: Row } & ({ tableId: string } | { table: Table })
) {

View File

@ -84,11 +84,8 @@ export async function searchView(
}))
const searchOptions: RequiredKeys<SearchViewRowRequest> &
RequiredKeys<
Pick<RowSearchParams, "tableId" | "viewId" | "query" | "fields">
> = {
tableId: view.tableId,
viewId: view.id,
RequiredKeys<Pick<RowSearchParams, "sourceId" | "query" | "fields">> = {
sourceId: view.id,
query: enrichedQuery,
fields: viewFields,
...getSortOptions(body, view),

View File

@ -1,4 +1,4 @@
import { context, db as dbCore, utils } from "@budibase/backend-core"
import { context, db as dbCore, docIds, utils } from "@budibase/backend-core"
import {
DatabaseQueryOpts,
Datasource,
@ -318,12 +318,8 @@ export function generateViewID(tableId: string) {
}${SEPARATOR}${tableId}${SEPARATOR}${newid()}`
}
export function isViewID(viewId: string) {
return viewId?.split(SEPARATOR)[0] === VirtualDocumentType.VIEW
}
export function extractViewInfoFromID(viewId: string) {
if (!isViewID(viewId)) {
if (!docIds.isViewId(viewId)) {
throw new Error("Unable to extract table ID, is not a view ID")
}
const split = viewId.split(SEPARATOR)

View File

@ -15,7 +15,7 @@ export function triggerRowActionAuthorised(
const rowActionId: string = ctx.params[actionPath]
const isTableId = docIds.isTableId(sourceId)
const isViewId = utils.isViewID(sourceId)
const isViewId = docIds.isViewId(sourceId)
if (!isTableId && !isViewId) {
ctx.throw(400, `'${sourceId}' is not a valid source id`)
}

View File

@ -1,10 +1,10 @@
import { db, roles } from "@budibase/backend-core"
import { db, docIds, roles } from "@budibase/backend-core"
import {
PermissionLevel,
PermissionSource,
VirtualDocumentType,
} from "@budibase/types"
import { extractViewInfoFromID, isViewID } from "../../../db/utils"
import { extractViewInfoFromID } from "../../../db/utils"
import {
CURRENTLY_SUPPORTED_LEVELS,
getBasePermissions,
@ -20,7 +20,7 @@ type ResourcePermissions = Record<
export async function getInheritablePermissions(
resourceId: string
): Promise<ResourcePermissions | undefined> {
if (isViewID(resourceId)) {
if (docIds.isViewId(resourceId)) {
return await getResourcePerms(extractViewInfoFromID(resourceId).tableId)
}
}

View File

@ -1,11 +1,11 @@
import { context, HTTPError, utils } from "@budibase/backend-core"
import { context, docIds, HTTPError, utils } from "@budibase/backend-core"
import {
AutomationTriggerStepId,
SEPARATOR,
TableRowActions,
VirtualDocumentType,
} from "@budibase/types"
import { generateRowActionsID, isViewID } from "../../db/utils"
import { generateRowActionsID } from "../../db/utils"
import automations from "./automations"
import { definitions as TRIGGER_DEFINITIONS } from "../../automations/triggerInfo"
import * as triggers from "../../automations/triggers"
@ -155,7 +155,7 @@ export async function update(
async function guardView(tableId: string, viewId: string) {
let view
if (isViewID(viewId)) {
if (docIds.isViewId(viewId)) {
view = await sdk.views.get(viewId)
}
if (!view || view.tableId !== tableId) {

View File

@ -53,8 +53,8 @@ export const removeInvalidFilters = (
}
export const getQueryableFields = async (
fields: string[],
table: Table
table: Table,
fields?: string[]
): Promise<string[]> => {
const extractTableFields = async (
table: Table,
@ -110,6 +110,9 @@ export const getQueryableFields = async (
"_id", // Querying by _id is always allowed, even if it's never part of the schema
]
if (fields === undefined) {
fields = Object.keys(table.schema)
}
result.push(...(await extractTableFields(table, fields, [table._id!])))
return result

View File

@ -1,9 +1,8 @@
import { db as dbCore, context } from "@budibase/backend-core"
import { db as dbCore, context, docIds } from "@budibase/backend-core"
import { Database, Row } from "@budibase/types"
import {
extractViewInfoFromID,
getRowParams,
isViewID,
} from "../../../db/utils"
import { isExternalTableID } from "../../../integrations/utils"
import * as internal from "./internal"
@ -26,7 +25,7 @@ export async function getAllInternalRows(appId?: string) {
function pickApi(tableOrViewId: string) {
let tableId = tableOrViewId
if (isViewID(tableOrViewId)) {
if (docIds.isViewId(tableOrViewId)) {
tableId = extractViewInfoFromID(tableOrViewId).tableId
}

View File

@ -4,6 +4,8 @@ import {
RowSearchParams,
SearchResponse,
SortOrder,
Table,
ViewV2,
} from "@budibase/types"
import { isExternalTableID } from "../../../integrations/utils"
import * as internal from "./search/internal"
@ -12,7 +14,7 @@ import { ExportRowsParams, ExportRowsResult } from "./search/types"
import { dataFilters } from "@budibase/shared-core"
import sdk from "../../index"
import { searchInputMapping } from "./search/utils"
import { features } from "@budibase/backend-core"
import { features, docIds } from "@budibase/backend-core"
import tracer from "dd-trace"
import { getQueryableFields, removeInvalidFilters } from "./queryUtils"
@ -36,8 +38,7 @@ export async function search(
): Promise<SearchResponse<Row>> {
return await tracer.trace("search", async span => {
span?.addTags({
tableId: options.tableId,
viewId: options.viewId,
sourceId: options.sourceId,
query: options.query,
sort: options.sort,
sortOrder: options.sortOrder,
@ -52,20 +53,18 @@ export async function search(
.join(", "),
})
const isExternalTable = isExternalTableID(options.tableId)
options.query = dataFilters.cleanupQuery(options.query || {})
options.query = dataFilters.fixupFilterArrays(options.query)
span?.addTags({
span.addTags({
cleanedQuery: options.query,
isExternalTable,
})
if (
!dataFilters.hasFilters(options.query) &&
options.query.onEmptyFilter === EmptyFilterOption.RETURN_NONE
) {
span?.addTags({ emptyQuery: true })
span.addTags({ emptyQuery: true })
return {
rows: [],
}
@ -75,34 +74,47 @@ export async function search(
options.sortOrder = options.sortOrder.toLowerCase() as SortOrder
}
const table = await sdk.tables.getTable(options.tableId)
let source: Table | ViewV2
let table: Table
if (docIds.isTableId(options.sourceId)) {
source = await sdk.tables.getTable(options.sourceId)
table = source
options = searchInputMapping(source, options)
} else if (docIds.isViewId(options.sourceId)) {
source = await sdk.views.get(options.sourceId)
table = await sdk.tables.getTable(source.tableId)
options = searchInputMapping(table, options)
if (options.query) {
const tableFields = Object.keys(table.schema).filter(
f => table.schema[f].visible !== false
)
const queriableFields = await getQueryableFields(
options.fields?.filter(f => tableFields.includes(f)) ?? tableFields,
table
)
options.query = removeInvalidFilters(options.query, queriableFields)
span.addTags({
tableId: table._id,
})
} else {
throw new Error(`Invalid source ID: ${options.sourceId}`)
}
if (options.query) {
const visibleFields = (
options.fields || Object.keys(table.schema)
).filter(field => table.schema[field].visible)
const queryableFields = await getQueryableFields(table, visibleFields)
options.query = removeInvalidFilters(options.query, queryableFields)
}
const isExternalTable = isExternalTableID(table._id!)
let result: SearchResponse<Row>
if (isExternalTable) {
span?.addTags({ searchType: "external" })
result = await external.search(options, table)
result = await external.search(options, source)
} else if (await features.flags.isEnabled("SQS")) {
span?.addTags({ searchType: "sqs" })
result = await internal.sqs.search(options, table)
result = await internal.sqs.search(options, source)
} else {
span?.addTags({ searchType: "lucene" })
result = await internal.lucene.search(options, table)
result = await internal.lucene.search(options, source)
}
span?.addTags({
span.addTags({
foundRows: result.rows.length,
totalRows: result.totalRows,
})

View File

@ -9,6 +9,7 @@ import {
SortJson,
SortOrder,
Table,
ViewV2,
} from "@budibase/types"
import * as exporters from "../../../../api/controllers/view/exporters"
import { handleRequest } from "../../../../api/controllers/row/external"
@ -60,9 +61,8 @@ function getPaginationAndLimitParameters(
export async function search(
options: RowSearchParams,
table: Table
source: Table | ViewV2
): Promise<SearchResponse<Row>> {
const { tableId } = options
const { countRows, paginate, query, ...params } = options
const { limit } = params
let bookmark =
@ -112,10 +112,9 @@ export async function search(
: Promise.resolve(undefined),
])
let processed = await outputProcessing(table, rows, {
let processed = await outputProcessing(source, rows, {
preserveLinks: true,
squash: true,
fromViewId: options.viewId,
})
let hasNextPage = false

View File

@ -9,6 +9,7 @@ import {
SearchResponse,
Row,
RowSearchParams,
ViewV2,
} from "@budibase/types"
import { db as dbCore, context } from "@budibase/backend-core"
import { utils } from "@budibase/shared-core"
@ -83,10 +84,7 @@ function userColumnMapping(column: string, options: RowSearchParams) {
// maps through the search parameters to check if any of the inputs are invalid
// based on the table schema, converts them to something that is valid.
export function searchInputMapping(table: Table, options: RowSearchParams) {
if (!table?.schema) {
return options
}
for (let [key, column] of Object.entries(table.schema)) {
for (let [key, column] of Object.entries(table.schema || {})) {
switch (column.type) {
case FieldType.BB_REFERENCE_SINGLE: {
const subtype = column.subtype

View File

@ -203,7 +203,7 @@ describe("query utils", () => {
},
})
const result = await getQueryableFields(Object.keys(table.schema), table)
const result = await getQueryableFields(table)
expect(result).toEqual(["_id", "name", "age"])
})
@ -216,7 +216,7 @@ describe("query utils", () => {
},
})
const result = await getQueryableFields(Object.keys(table.schema), table)
const result = await getQueryableFields(table)
expect(result).toEqual(["_id", "name"])
})
@ -245,7 +245,7 @@ describe("query utils", () => {
})
const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table)
return getQueryableFields(table)
})
expect(result).toEqual([
"_id",
@ -282,7 +282,7 @@ describe("query utils", () => {
})
const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table)
return getQueryableFields(table)
})
expect(result).toEqual(["_id", "name", "aux.name", "auxTable.name"])
})
@ -313,7 +313,7 @@ describe("query utils", () => {
})
const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table)
return getQueryableFields(table)
})
expect(result).toEqual(["_id", "name"])
})
@ -381,7 +381,7 @@ describe("query utils", () => {
it("includes nested relationship fields from main table", async () => {
const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table)
return getQueryableFields(table)
})
expect(result).toEqual([
"_id",
@ -398,7 +398,7 @@ describe("query utils", () => {
it("includes nested relationship fields from aux 1 table", async () => {
const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(aux1.schema), aux1)
return getQueryableFields(aux1)
})
expect(result).toEqual([
"_id",
@ -420,7 +420,7 @@ describe("query utils", () => {
it("includes nested relationship fields from aux 2 table", async () => {
const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(aux2.schema), aux2)
return getQueryableFields(aux2)
})
expect(result).toEqual([
"_id",
@ -474,7 +474,7 @@ describe("query utils", () => {
it("includes nested relationship fields from main table", async () => {
const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table)
return getQueryableFields(table)
})
expect(result).toEqual([
"_id",
@ -488,7 +488,7 @@ describe("query utils", () => {
it("includes nested relationship fields from aux table", async () => {
const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(aux.schema), aux)
return getQueryableFields(aux)
})
expect(result).toEqual([
"_id",

View File

@ -13,16 +13,14 @@ import {
TableSchema,
SqlClient,
ArrayOperator,
ViewV2,
} from "@budibase/types"
import { makeExternalQuery } from "../../../integrations/base/query"
import { Format } from "../../../api/controllers/view/exporters"
import sdk from "../.."
import {
extractViewInfoFromID,
isRelationshipColumn,
isViewID,
} from "../../../db/utils"
import { extractViewInfoFromID, isRelationshipColumn } from "../../../db/utils"
import { isSQL } from "../../../integrations/utils"
import { docIds } from "@budibase/backend-core"
const SQL_CLIENT_SOURCE_MAP: Record<SourceName, SqlClient | undefined> = {
[SourceName.POSTGRES]: SqlClient.POSTGRES,
@ -142,37 +140,32 @@ function isForeignKey(key: string, table: Table) {
}
export async function validate({
tableId,
source,
row,
table,
}: {
tableId?: string
source: Table | ViewV2
row: Row
table?: Table
}): Promise<{
valid: boolean
errors: Record<string, any>
}> {
let fetchedTable: Table | undefined
if (!table && tableId) {
fetchedTable = await sdk.tables.getTable(tableId)
} else if (table) {
fetchedTable = table
}
if (fetchedTable === undefined) {
throw new Error("Unable to fetch table for validation")
let table: Table
if (sdk.views.isView(source)) {
table = await sdk.views.getTable(source.id)
} else {
table = source
}
const errors: Record<string, any> = {}
const disallowArrayTypes = [
FieldType.ATTACHMENT_SINGLE,
FieldType.BB_REFERENCE_SINGLE,
]
for (let fieldName of Object.keys(fetchedTable.schema)) {
const column = fetchedTable.schema[fieldName]
for (let fieldName of Object.keys(table.schema)) {
const column = table.schema[fieldName]
const constraints = cloneDeep(column.constraints)
const type = column.type
// foreign keys are likely to be enriched
if (isForeignKey(fieldName, fetchedTable)) {
if (isForeignKey(fieldName, table)) {
continue
}
// formulas shouldn't validated, data will be deleted anyway
@ -323,7 +316,7 @@ export function isArrayFilter(operator: any): operator is ArrayOperator {
}
export function tryExtractingTableAndViewId(tableOrViewId: string) {
if (isViewID(tableOrViewId)) {
if (docIds.isViewId(tableOrViewId)) {
return {
tableId: extractViewInfoFromID(tableOrViewId).tableId,
viewId: tableOrViewId,

View File

@ -9,3 +9,7 @@ export function isExternal(opts: { table?: Table; tableId?: string }): boolean {
}
return false
}
export function isTable(table: any): table is Table {
return table.type === "table"
}

View File

@ -9,7 +9,7 @@ import {
ViewV2ColumnEnriched,
ViewV2Enriched,
} from "@budibase/types"
import { HTTPError } from "@budibase/backend-core"
import { context, HTTPError } from "@budibase/backend-core"
import {
helpers,
PROTECTED_EXTERNAL_COLUMNS,
@ -40,6 +40,23 @@ export async function getEnriched(viewId: string): Promise<ViewV2Enriched> {
return pickApi(tableId).getEnriched(viewId)
}
export async function getTable(viewId: string): Promise<Table> {
const cached = context.getTableForView(viewId)
if (cached) {
return cached
}
const { tableId } = utils.extractViewInfoFromID(viewId)
const table = await sdk.tables.getTable(tableId)
context.setTableForView(viewId, table)
return table
}
export function isView(view: any): view is ViewV2 {
return (
view.version === 2 && "id" in view && "tableId" in view && "name" in view
)
}
async function guardCalculationViewSchema(
table: Table,
view: Omit<ViewV2, "id" | "version">

View File

@ -10,8 +10,7 @@ export interface Aggregation {
}
export interface SearchParams {
tableId?: string
viewId?: string
sourceId?: string
query?: SearchFilters
paginate?: boolean
bookmark?: string | number
@ -30,7 +29,7 @@ export interface SearchParams {
// when searching for rows we want a more extensive search type that requires certain properties
export interface RowSearchParams
extends WithRequired<SearchParams, "tableId" | "query"> {}
extends WithRequired<SearchParams, "sourceId" | "query"> {}
export interface SearchResponse<T> {
rows: T[]

View File

@ -17796,21 +17796,11 @@ periscopic@^3.1.0:
estree-walker "^3.0.0"
is-reference "^3.0.0"
pg-cloudflare@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98"
integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==
pg-connection-string@2.5.0, pg-connection-string@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
pg-connection-string@^2.6.4:
version "2.6.4"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.4.tgz#f543862adfa49fa4e14bc8a8892d2a84d754246d"
integrity sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==
pg-int8@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
@ -17821,21 +17811,11 @@ pg-pool@^3.6.0:
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e"
integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==
pg-pool@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2"
integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==
pg-protocol@*, pg-protocol@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833"
integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==
pg-protocol@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3"
integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==
pg-types@^2.1.0, pg-types@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
@ -17860,19 +17840,6 @@ pg@8.10.0:
pg-types "^2.1.0"
pgpass "1.x"
pg@^8.12.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.12.0.tgz#9341724db571022490b657908f65aee8db91df79"
integrity sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==
dependencies:
pg-connection-string "^2.6.4"
pg-pool "^3.6.2"
pg-protocol "^1.6.1"
pg-types "^2.1.0"
pgpass "1.x"
optionalDependencies:
pg-cloudflare "^1.1.1"
pgpass@1.x:
version "1.0.5"
resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"
@ -20786,16 +20753,7 @@ string-similarity@^4.0.4:
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b"
integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -20886,7 +20844,7 @@ stringify-object@^3.2.1:
is-obj "^1.0.1"
is-regexp "^1.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -20900,13 +20858,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
@ -22862,7 +22813,7 @@ worker-farm@1.7.0:
dependencies:
errno "~0.1.7"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -22880,15 +22831,6 @@ wrap-ansi@^5.1.0:
string-width "^3.0.0"
strip-ansi "^5.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"