From dd96bf7434f8b61013b5d03153ac5978b0b7c5c3 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Mon, 31 Aug 2026 19:33:09 -0700 Subject: [PATCH 1/2] fix(selectors): paginate BigQuery resources --- apps/sim/lib/selectors/manifest.ts | 2 + .../server/providers/bigquery.test.ts | 207 ++++++++++++++ .../selectors/server/providers/bigquery.ts | 270 ++++++++++++------ 3 files changed, 391 insertions(+), 88 deletions(-) create mode 100644 apps/sim/lib/selectors/server/providers/bigquery.test.ts diff --git a/apps/sim/lib/selectors/manifest.ts b/apps/sim/lib/selectors/manifest.ts index 34cb8141983..63ba070b6d7 100644 --- a/apps/sim/lib/selectors/manifest.ts +++ b/apps/sim/lib/selectors/manifest.ts @@ -96,10 +96,12 @@ export const selectorManifest = { 'attio.objects': providerSelector([], { detail: true }), 'bigquery.datasets': providerSelector(['projectId', 'impersonateUserEmail'], { readiness: { all: ['oauthCredential', 'projectId'] }, + listMode: 'paginated', detail: true, }), 'bigquery.tables': providerSelector(['projectId', 'datasetId', 'impersonateUserEmail'], { readiness: { all: ['oauthCredential', 'projectId', 'datasetId'] }, + listMode: 'paginated', detail: true, }), 'bitbucket.workspaces': providerSelector([], { listMode: 'paginated', detail: true }), diff --git a/apps/sim/lib/selectors/server/providers/bigquery.test.ts b/apps/sim/lib/selectors/server/providers/bigquery.test.ts new file mode 100644 index 00000000000..9658d8a8ca6 --- /dev/null +++ b/apps/sim/lib/selectors/server/providers/bigquery.test.ts @@ -0,0 +1,207 @@ +/** + * @vitest-environment node + */ +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockFetch, mockResolveSelectorOAuthAccessToken } = vi.hoisted(() => ({ + mockFetch: vi.fn(), + mockResolveSelectorOAuthAccessToken: vi.fn(), +})) + +vi.mock('@/lib/selectors/server/credentials', () => ({ + resolveSelectorOAuthAccessToken: mockResolveSelectorOAuthAccessToken, +})) + +import { createSelectorProtectedValues } from '@/lib/selectors/server/protected-values' +import { bigQuerySelectorAttachments } from '@/lib/selectors/server/providers/bigquery' +import type { ExecuteServerSelectorArgs } from '@/lib/selectors/server/types' + +const PROJECT_ID = 'selector-test' +const DATASET_ID = 'analytics' + +function args( + selectorKey: 'bigquery.datasets' | 'bigquery.tables', + request: ExecuteServerSelectorArgs['request'] +): ExecuteServerSelectorArgs { + return { + selectorKey, + context: { + oauthCredential: 'credential-1', + projectId: PROJECT_ID, + ...(selectorKey === 'bigquery.tables' ? { datasetId: DATASET_ID } : {}), + }, + request, + scope: { kind: 'workspace', workspaceId: 'workspace-1' }, + workspaceId: 'workspace-1', + principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' }, + requesterUserId: 'user-1', + credential: { suppliedId: 'credential-1' }, + references: new Map(), + protectedValues: createSelectorProtectedValues(), + } +} + +function dataset(datasetId: string, friendlyName: string) { + return { + datasetReference: { projectId: PROJECT_ID, datasetId }, + friendlyName, + } +} + +function table(tableId: string, friendlyName: string) { + return { + tableReference: { projectId: PROJECT_ID, datasetId: DATASET_ID, tableId }, + friendlyName, + } +} + +describe('BigQuery server selector adapters', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.stubGlobal('fetch', mockFetch) + mockResolveSelectorOAuthAccessToken.mockResolvedValue('server-only-token') + }) + + afterAll(() => vi.unstubAllGlobals()) + + it('returns one dataset page and forwards its continuation token on demand', async () => { + mockFetch + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + datasets: [dataset('dataset_1', 'Dataset 1')], + nextPageToken: 'dataset-page-2', + }), + { status: 200 } + ) + ) + .mockResolvedValueOnce( + new Response(JSON.stringify({ datasets: [dataset('dataset_2', 'Dataset 2')] }), { + status: 200, + }) + ) + + await expect( + bigQuerySelectorAttachments['bigquery.datasets'].execute( + args('bigquery.datasets', { kind: 'list' }) + ) + ).resolves.toEqual({ + kind: 'list', + items: [{ id: 'dataset_1', label: 'Dataset 1' }], + nextCursor: 'dataset-page-2', + }) + await expect( + bigQuerySelectorAttachments['bigquery.datasets'].execute( + args('bigquery.datasets', { kind: 'list', cursor: 'dataset-page-2' }) + ) + ).resolves.toEqual({ + kind: 'list', + items: [{ id: 'dataset_2', label: 'Dataset 2' }], + }) + + const firstUrl = new URL(String(mockFetch.mock.calls[0]?.[0])) + const secondUrl = new URL(String(mockFetch.mock.calls[1]?.[0])) + expect(firstUrl.searchParams.get('maxResults')).toBe('200') + expect(firstUrl.searchParams.has('pageToken')).toBe(false) + expect(secondUrl.searchParams.get('pageToken')).toBe('dataset-page-2') + expect(mockFetch).toHaveBeenCalledTimes(2) + }) + + it('returns one table page and forwards its continuation token on demand', async () => { + mockFetch + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + tables: [table('table_1', 'Table 1')], + nextPageToken: 'table-page-2', + }), + { status: 200 } + ) + ) + .mockResolvedValueOnce( + new Response(JSON.stringify({ tables: [table('table_2', 'Table 2')] }), { + status: 200, + }) + ) + + await expect( + bigQuerySelectorAttachments['bigquery.tables'].execute( + args('bigquery.tables', { kind: 'list' }) + ) + ).resolves.toEqual({ + kind: 'list', + items: [{ id: 'table_1', label: 'Table 1' }], + nextCursor: 'table-page-2', + }) + await expect( + bigQuerySelectorAttachments['bigquery.tables'].execute( + args('bigquery.tables', { kind: 'list', cursor: 'table-page-2' }) + ) + ).resolves.toEqual({ + kind: 'list', + items: [{ id: 'table_2', label: 'Table 2' }], + }) + + const firstUrl = new URL(String(mockFetch.mock.calls[0]?.[0])) + const secondUrl = new URL(String(mockFetch.mock.calls[1]?.[0])) + expect(firstUrl.searchParams.get('maxResults')).toBe('200') + expect(firstUrl.searchParams.has('pageToken')).toBe(false) + expect(secondUrl.searchParams.get('pageToken')).toBe('table-page-2') + expect(mockFetch).toHaveBeenCalledTimes(2) + }) + + it('hydrates a selected dataset directly by id', async () => { + mockFetch.mockResolvedValueOnce( + new Response(JSON.stringify(dataset('saved_dataset', 'Saved Dataset')), { status: 200 }) + ) + + await expect( + bigQuerySelectorAttachments['bigquery.datasets'].execute( + args('bigquery.datasets', { kind: 'detail', id: 'saved_dataset' }) + ) + ).resolves.toEqual({ + kind: 'detail', + item: { id: 'saved_dataset', label: 'Saved Dataset' }, + }) + + const url = new URL(String(mockFetch.mock.calls[0]?.[0])) + expect(url.pathname).toBe(`/bigquery/v2/projects/${PROJECT_ID}/datasets/saved_dataset`) + expect(url.searchParams.get('datasetView')).toBe('METADATA') + expect(mockFetch).toHaveBeenCalledTimes(1) + }) + + it('hydrates a selected table directly by its encoded id', async () => { + const tableId = 'Sales Table-Δ' + mockFetch.mockResolvedValueOnce( + new Response(JSON.stringify(table(tableId, 'Saved Table')), { status: 200 }) + ) + + await expect( + bigQuerySelectorAttachments['bigquery.tables'].execute( + args('bigquery.tables', { kind: 'detail', id: tableId }) + ) + ).resolves.toEqual({ + kind: 'detail', + item: { id: tableId, label: 'Saved Table' }, + }) + + const url = new URL(String(mockFetch.mock.calls[0]?.[0])) + expect(url.pathname).toBe( + `/bigquery/v2/projects/${PROJECT_ID}/datasets/${DATASET_ID}/tables/Sales%20Table-%CE%94` + ) + expect(url.searchParams.get('view')).toBe('BASIC') + expect(mockFetch).toHaveBeenCalledTimes(1) + }) + + it.each([ + ['bigquery.datasets', { kind: 'detail', id: 'missing_dataset' }], + ['bigquery.tables', { kind: 'detail', id: 'missing_table' }], + ] as const)('returns null when %s detail is missing', async (selectorKey, request) => { + mockFetch.mockResolvedValueOnce(new Response(null, { status: 404 })) + + await expect( + bigQuerySelectorAttachments[selectorKey].execute(args(selectorKey, request)) + ).resolves.toEqual({ kind: 'detail', item: null }) + expect(mockFetch).toHaveBeenCalledTimes(1) + }) +}) diff --git a/apps/sim/lib/selectors/server/providers/bigquery.ts b/apps/sim/lib/selectors/server/providers/bigquery.ts index 661d351d4f6..8ffbd0704af 100644 --- a/apps/sim/lib/selectors/server/providers/bigquery.ts +++ b/apps/sim/lib/selectors/server/providers/bigquery.ts @@ -7,29 +7,53 @@ import { SelectorContextUnavailableError, SelectorOptionsUnavailableError, } from '@/lib/selectors/server/errors' -import { flatSelectorResult } from '@/lib/selectors/server/providers/flat-results' -import { fetchProviderJson } from '@/lib/selectors/server/providers/provider-http' -import type { - ExecuteServerSelectorArgs, - ServerSelectorAttachmentMap, +import { + fetchProviderJson, + fetchProviderJsonWithStatus, +} from '@/lib/selectors/server/providers/provider-http' +import { + detailSelectorResult, + type ExecuteServerSelectorArgs, + listSelectorResult, + requireListRequest, + type ServerSelectorAttachmentMap, } from '@/lib/selectors/server/types' type BigQuerySelectorKey = Extract -const BIGQUERY_MAX_PAGES = 20 const BIGQUERY_PAGE_SIZE = 200 +const BIGQUERY_CURSOR_MAX_LENGTH = 4_096 const BIGQUERY_SCOPES = getScopesForService('google-bigquery') +const PROJECT_ID_PATTERN = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/ +const DATASET_ID_PATTERN = /^[A-Za-z0-9_]{1,1024}$/ +const TABLE_ID_PATTERN = /^[\p{L}\p{M}\p{N}\p{Pc}\p{Pd}\p{Zs}]+$/u + +const projectIdSchema = z.string().regex(PROJECT_ID_PATTERN) +const datasetIdSchema = z.string().regex(DATASET_ID_PATTERN) +const tableIdSchema = z.string().superRefine((value, context) => { + if ( + !value || + new TextEncoder().encode(value).byteLength > 1_024 || + !TABLE_ID_PATTERN.test(value) + ) { + context.addIssue({ code: 'custom', message: 'Invalid BigQuery table ID' }) + } +}) const bigQueryDatasetSchema = z.object({ datasetReference: z.object({ - datasetId: z.string().min(1), - projectId: z.string().min(1), + datasetId: datasetIdSchema, + projectId: projectIdSchema, }), friendlyName: z.string().optional(), }) const bigQueryTableSchema = z.object({ - tableReference: z.object({ tableId: z.string().min(1) }), + tableReference: z.object({ + datasetId: datasetIdSchema, + projectId: projectIdSchema, + tableId: tableIdSchema, + }), friendlyName: z.string().optional(), }) @@ -48,8 +72,29 @@ function requireCredential(args: ExecuteServerSelectorArgs) { return args.credential } -function requireContext(value: string | undefined): string { - if (!value) throw new SelectorContextUnavailableError() +function requireProjectId(value: string | undefined): string { + const parsed = projectIdSchema.safeParse(value?.trim()) + if (!parsed.success) throw new SelectorContextUnavailableError() + return parsed.data +} + +function requireDatasetId(value: string | undefined): string { + const parsed = datasetIdSchema.safeParse(value?.trim()) + if (!parsed.success) throw new SelectorContextUnavailableError() + return parsed.data +} + +function requireTableId(value: string): string { + const parsed = tableIdSchema.safeParse(value) + if (!parsed.success) throw new SelectorContextUnavailableError() + return parsed.data +} + +function requireCursor(value: string | undefined): string | undefined { + if (value === undefined) return undefined + if (!value.trim() || value.length > BIGQUERY_CURSOR_MAX_LENGTH) { + throw new SelectorContextUnavailableError() + } return value } @@ -63,85 +108,148 @@ async function getAccessToken(args: ExecuteServerSelectorArgs): Promise }) } +function requestHeaders(accessToken: string) { + return { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/json', + } +} + async function listDatasets(args: ExecuteServerSelectorArgs) { - const projectId = requireContext(args.context.projectId) + const request = requireListRequest(args.selectorKey, args.request) + const projectId = requireProjectId(args.context.projectId) + const cursor = requireCursor(request.cursor) const accessToken = await getAccessToken(args) - const datasets: z.infer[] = [] - let pageToken: string | undefined - let truncated = false + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets` + ) + url.searchParams.set('maxResults', String(BIGQUERY_PAGE_SIZE)) + if (cursor) url.searchParams.set('pageToken', cursor) - for (let page = 0; page < BIGQUERY_MAX_PAGES; page++) { - const url = new URL( - `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets` - ) - url.searchParams.set('maxResults', String(BIGQUERY_PAGE_SIZE)) - if (pageToken) url.searchParams.set('pageToken', pageToken) - - const body = await fetchProviderJson(url, { - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', - }, - redirect: 'error', - signal: args.signal, - }) - const parsed = datasetsPageSchema.safeParse(body) - if (!parsed.success) throw new SelectorOptionsUnavailableError() - - datasets.push(...(parsed.data.datasets ?? [])) - pageToken = parsed.data.nextPageToken - if (!pageToken) break - if (page === BIGQUERY_MAX_PAGES - 1) truncated = true + const body = await fetchProviderJson(url, { + headers: requestHeaders(accessToken), + redirect: 'error', + signal: args.signal, + }) + const parsed = datasetsPageSchema.safeParse(body) + if (!parsed.success) throw new SelectorOptionsUnavailableError() + if (parsed.data.datasets?.some((dataset) => dataset.datasetReference.projectId !== projectId)) { + throw new SelectorOptionsUnavailableError() } - return { - items: datasets.map((dataset) => ({ + return listSelectorResult( + (parsed.data.datasets ?? []).map((dataset) => ({ id: dataset.datasetReference.datasetId, label: dataset.friendlyName || dataset.datasetReference.datasetId, })), - truncated, - } + parsed.data.nextPageToken + ) } async function listTables(args: ExecuteServerSelectorArgs) { - const projectId = requireContext(args.context.projectId) - const datasetId = requireContext(args.context.datasetId) + const request = requireListRequest(args.selectorKey, args.request) + const projectId = requireProjectId(args.context.projectId) + const datasetId = requireDatasetId(args.context.datasetId) + const cursor = requireCursor(request.cursor) const accessToken = await getAccessToken(args) - const tables: z.infer[] = [] - let pageToken: string | undefined - let truncated = false + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets/${encodeURIComponent(datasetId)}/tables` + ) + url.searchParams.set('maxResults', String(BIGQUERY_PAGE_SIZE)) + if (cursor) url.searchParams.set('pageToken', cursor) - for (let page = 0; page < BIGQUERY_MAX_PAGES; page++) { - const url = new URL( - `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets/${encodeURIComponent(datasetId)}/tables` + const body = await fetchProviderJson(url, { + headers: requestHeaders(accessToken), + redirect: 'error', + signal: args.signal, + }) + const parsed = tablesPageSchema.safeParse(body) + if (!parsed.success) throw new SelectorOptionsUnavailableError() + if ( + parsed.data.tables?.some( + (table) => + table.tableReference.projectId !== projectId || table.tableReference.datasetId !== datasetId ) - url.searchParams.set('maxResults', String(BIGQUERY_PAGE_SIZE)) - if (pageToken) url.searchParams.set('pageToken', pageToken) - - const body = await fetchProviderJson(url, { - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', - }, - redirect: 'error', - signal: args.signal, - }) - const parsed = tablesPageSchema.safeParse(body) - if (!parsed.success) throw new SelectorOptionsUnavailableError() - - tables.push(...(parsed.data.tables ?? [])) - pageToken = parsed.data.nextPageToken - if (!pageToken) break - if (page === BIGQUERY_MAX_PAGES - 1) truncated = true + ) { + throw new SelectorOptionsUnavailableError() } - return { - items: tables.map((table) => ({ + return listSelectorResult( + (parsed.data.tables ?? []).map((table) => ({ id: table.tableReference.tableId, label: table.friendlyName || table.tableReference.tableId, })), - truncated, + parsed.data.nextPageToken + ) +} + +async function getDataset(args: ExecuteServerSelectorArgs) { + if (args.request.kind !== 'detail') throw new SelectorOptionsUnavailableError() + const projectId = requireProjectId(args.context.projectId) + const datasetId = requireDatasetId(args.request.id) + const accessToken = await getAccessToken(args) + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets/${encodeURIComponent(datasetId)}` + ) + url.searchParams.set('datasetView', 'METADATA') + + const result = await fetchProviderJsonWithStatus( + url, + { + headers: requestHeaders(accessToken), + redirect: 'error', + signal: args.signal, + }, + { passthroughStatuses: [404] } + ) + if (!result.ok) return detailSelectorResult(null) + + const parsed = bigQueryDatasetSchema.safeParse(result.data) + if ( + !parsed.success || + parsed.data.datasetReference.projectId !== projectId || + parsed.data.datasetReference.datasetId !== datasetId + ) { + throw new SelectorOptionsUnavailableError() + } + return detailSelectorResult({ + id: datasetId, + label: parsed.data.friendlyName || datasetId, + }) +} + +async function getTable(args: ExecuteServerSelectorArgs) { + if (args.request.kind !== 'detail') throw new SelectorOptionsUnavailableError() + const projectId = requireProjectId(args.context.projectId) + const datasetId = requireDatasetId(args.context.datasetId) + const tableId = requireTableId(args.request.id) + const accessToken = await getAccessToken(args) + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets/${encodeURIComponent(datasetId)}/tables/${encodeURIComponent(tableId)}` + ) + url.searchParams.set('view', 'BASIC') + + const result = await fetchProviderJsonWithStatus( + url, + { + headers: requestHeaders(accessToken), + redirect: 'error', + signal: args.signal, + }, + { passthroughStatuses: [404] } + ) + if (!result.ok) return detailSelectorResult(null) + + const parsed = bigQueryTableSchema.safeParse(result.data) + if ( + !parsed.success || + parsed.data.tableReference.projectId !== projectId || + parsed.data.tableReference.datasetId !== datasetId || + parsed.data.tableReference.tableId !== tableId + ) { + throw new SelectorOptionsUnavailableError() } + return detailSelectorResult({ id: tableId, label: parsed.data.friendlyName || tableId }) } const credential = { @@ -155,30 +263,16 @@ export const bigQuerySelectorAttachments = { credential, destination: 'fixed', async execute(args) { - const result = await listDatasets(args) - return flatSelectorResult( - args.request, - result.items, - true, - result.truncated - ? { truncated: { reason: 'provider-cap', pages: BIGQUERY_MAX_PAGES } } - : undefined - ) + if (args.request.kind === 'detail') return getDataset(args) + return listDatasets(args) }, }, 'bigquery.tables': { credential, destination: 'fixed', async execute(args) { - const result = await listTables(args) - return flatSelectorResult( - args.request, - result.items, - true, - result.truncated - ? { truncated: { reason: 'provider-cap', pages: BIGQUERY_MAX_PAGES } } - : undefined - ) + if (args.request.kind === 'detail') return getTable(args) + return listTables(args) }, }, } satisfies ServerSelectorAttachmentMap From acb90a29f2d9b98fb066ed05e5b759982f81fca9 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Mon, 31 Aug 2026 19:45:27 -0700 Subject: [PATCH 2/2] fix(selectors): support legacy BigQuery project ids --- .../server/providers/bigquery.test.ts | 34 ++++++++++++++----- .../selectors/server/providers/bigquery.ts | 20 +++++++---- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/apps/sim/lib/selectors/server/providers/bigquery.test.ts b/apps/sim/lib/selectors/server/providers/bigquery.test.ts index 9658d8a8ca6..87840705600 100644 --- a/apps/sim/lib/selectors/server/providers/bigquery.test.ts +++ b/apps/sim/lib/selectors/server/providers/bigquery.test.ts @@ -19,6 +19,16 @@ import type { ExecuteServerSelectorArgs } from '@/lib/selectors/server/types' const PROJECT_ID = 'selector-test' const DATASET_ID = 'analytics' +interface DatasetFixture { + datasetReference: { projectId: string; datasetId: string } + friendlyName: string +} + +interface TableFixture { + tableReference: { projectId: string; datasetId: string; tableId: string } + friendlyName: string +} + function args( selectorKey: 'bigquery.datasets' | 'bigquery.tables', request: ExecuteServerSelectorArgs['request'] @@ -41,14 +51,14 @@ function args( } } -function dataset(datasetId: string, friendlyName: string) { +function dataset(datasetId: string, friendlyName: string, projectId = PROJECT_ID): DatasetFixture { return { - datasetReference: { projectId: PROJECT_ID, datasetId }, + datasetReference: { projectId, datasetId }, friendlyName, } } -function table(tableId: string, friendlyName: string) { +function table(tableId: string, friendlyName: string): TableFixture { return { tableReference: { projectId: PROJECT_ID, datasetId: DATASET_ID, tableId }, friendlyName, @@ -150,22 +160,28 @@ describe('BigQuery server selector adapters', () => { expect(mockFetch).toHaveBeenCalledTimes(2) }) - it('hydrates a selected dataset directly by id', async () => { + it('hydrates a selected dataset in a legacy domain-scoped project directly by id', async () => { + const projectId = 'example.com:selector-test' mockFetch.mockResolvedValueOnce( - new Response(JSON.stringify(dataset('saved_dataset', 'Saved Dataset')), { status: 200 }) + new Response(JSON.stringify(dataset('saved_dataset', 'Saved Dataset', projectId)), { + status: 200, + }) ) + const detailArgs = args('bigquery.datasets', { kind: 'detail', id: 'saved_dataset' }) + detailArgs.context.projectId = projectId + await expect( - bigQuerySelectorAttachments['bigquery.datasets'].execute( - args('bigquery.datasets', { kind: 'detail', id: 'saved_dataset' }) - ) + bigQuerySelectorAttachments['bigquery.datasets'].execute(detailArgs) ).resolves.toEqual({ kind: 'detail', item: { id: 'saved_dataset', label: 'Saved Dataset' }, }) const url = new URL(String(mockFetch.mock.calls[0]?.[0])) - expect(url.pathname).toBe(`/bigquery/v2/projects/${PROJECT_ID}/datasets/saved_dataset`) + expect(url.pathname).toBe( + '/bigquery/v2/projects/example.com%3Aselector-test/datasets/saved_dataset' + ) expect(url.searchParams.get('datasetView')).toBe('METADATA') expect(mockFetch).toHaveBeenCalledTimes(1) }) diff --git a/apps/sim/lib/selectors/server/providers/bigquery.ts b/apps/sim/lib/selectors/server/providers/bigquery.ts index 8ffbd0704af..5efa62e389d 100644 --- a/apps/sim/lib/selectors/server/providers/bigquery.ts +++ b/apps/sim/lib/selectors/server/providers/bigquery.ts @@ -17,6 +17,7 @@ import { listSelectorResult, requireListRequest, type ServerSelectorAttachmentMap, + type ServerSelectorExecutionResult, } from '@/lib/selectors/server/types' type BigQuerySelectorKey = Extract @@ -24,7 +25,8 @@ type BigQuerySelectorKey = Extract { if (!args.credential) throw new SelectorConnectionUnavailableError() return args.credential } @@ -108,14 +112,16 @@ async function getAccessToken(args: ExecuteServerSelectorArgs): Promise }) } -function requestHeaders(accessToken: string) { +function requestHeaders(accessToken: string): Record { return { Authorization: `Bearer ${accessToken}`, Accept: 'application/json', } } -async function listDatasets(args: ExecuteServerSelectorArgs) { +async function listDatasets( + args: ExecuteServerSelectorArgs +): Promise { const request = requireListRequest(args.selectorKey, args.request) const projectId = requireProjectId(args.context.projectId) const cursor = requireCursor(request.cursor) @@ -146,7 +152,7 @@ async function listDatasets(args: ExecuteServerSelectorArgs) { ) } -async function listTables(args: ExecuteServerSelectorArgs) { +async function listTables(args: ExecuteServerSelectorArgs): Promise { const request = requireListRequest(args.selectorKey, args.request) const projectId = requireProjectId(args.context.projectId) const datasetId = requireDatasetId(args.context.datasetId) @@ -183,7 +189,7 @@ async function listTables(args: ExecuteServerSelectorArgs) { ) } -async function getDataset(args: ExecuteServerSelectorArgs) { +async function getDataset(args: ExecuteServerSelectorArgs): Promise { if (args.request.kind !== 'detail') throw new SelectorOptionsUnavailableError() const projectId = requireProjectId(args.context.projectId) const datasetId = requireDatasetId(args.request.id) @@ -218,7 +224,7 @@ async function getDataset(args: ExecuteServerSelectorArgs) { }) } -async function getTable(args: ExecuteServerSelectorArgs) { +async function getTable(args: ExecuteServerSelectorArgs): Promise { if (args.request.kind !== 'detail') throw new SelectorOptionsUnavailableError() const projectId = requireProjectId(args.context.projectId) const datasetId = requireDatasetId(args.context.datasetId)