diff --git a/api_types.ts b/api_types.ts index 8d052673a6..9903c711a3 100644 --- a/api_types.ts +++ b/api_types.ts @@ -781,6 +781,29 @@ export interface TemporaryBlobStorage { ): Promise; } + +/** + * A service for retrieving the sync state in Coda Brain. + * @hidden + * + * TODO(ebo): unhide this + */ +export interface SyncStateService { + /** + * Retrieve the latest version of rows with the given row ids and returns a mapping of row ids to + * their latest version, e.g. {"Id-123": "1.0.0"}. + * + * If the row id is not found, it will not be included in the response. + * + * If the row version is not defined, it will be set to an empty string. + * + * @hidden + * + * TODO(ebo): unhide this + */ + getLatestRowVersions(rowIds: string[]): Promise<{[rowId: string]: string}>; +} + /** * TODO(patrick): Unhide this * @hidden @@ -1062,6 +1085,21 @@ export interface SyncExecutionContext< * Information about state of the current sync. */ readonly sync: Sync; + + /** + * A service for retrieving the sync state in Coda Brain. + * @hidden + * + * TODO(ebo): unhide this + */ + readonly syncStateService: SyncStateService; +} + +/** + * A function to check if a given {@link ExecutionContext} is a {@link SyncExecutionContext}. + */ +export function isSyncExecutionContext(context: ExecutionContext): context is SyncExecutionContext { + return context.hasOwnProperty('sync') && context.hasOwnProperty('syncStateService'); } /** diff --git a/dist/api_types.d.ts b/dist/api_types.d.ts index 4586de3859..620eabc258 100644 --- a/dist/api_types.d.ts +++ b/dist/api_types.d.ts @@ -647,6 +647,29 @@ export interface TemporaryBlobStorage { downloadFilename?: string; }): Promise; } +/** + * A service for retrieving the sync state in Coda Brain. + * @hidden + * + * TODO(ebo): unhide this + */ +export interface SyncStateService { + /** + * Retrieve the latest version of rows with the given row ids and returns a mapping of row ids to + * their latest version, e.g. {"Id-123": "1.0.0"}. + * + * If the row id is not found, it will not be included in the response. + * + * If the row version is not defined, it will be set to an empty string. + * + * @hidden + * + * TODO(ebo): unhide this + */ + getLatestRowVersions(rowIds: string[]): Promise<{ + [rowId: string]: string; + }>; +} /** * TODO(patrick): Unhide this * @hidden @@ -883,7 +906,18 @@ export interface SyncExecutionContext; + /** + * A service for retrieving the sync state in Coda Brain. + * @hidden + * + * TODO(ebo): unhide this + */ + readonly syncStateService: SyncStateService; } +/** + * A function to check if a given {@link ExecutionContext} is a {@link SyncExecutionContext}. + */ +export declare function isSyncExecutionContext(context: ExecutionContext): context is SyncExecutionContext; /** * Sub-class of {@link SyncExecutionContext} that is passed to the `options` function of * mutable sync tables for properties with `options` enabled. diff --git a/dist/api_types.js b/dist/api_types.js index dd0e7bdd8b..b69d2716d8 100644 --- a/dist/api_types.js +++ b/dist/api_types.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.TableRole = exports.OptionsType = exports.FutureLiveDates = exports.PastLiveDates = exports.AllPrecannedDates = exports.PrecannedDate = exports.FromNowDateRanges = exports.PastLiveDateRanges = exports.UntilNowDateRanges = exports.PrecannedDateRange = exports.InvocationSource = exports.InvocationErrorType = exports.PermissionSyncMode = exports.ValidFetchMethods = exports.NetworkConnection = exports.ConnectionRequirement = exports.ParameterTypeInputMap = exports.ParameterType = exports.fileArray = exports.imageArray = exports.htmlArray = exports.dateArray = exports.booleanArray = exports.numberArray = exports.stringArray = exports.isArrayType = exports.Type = void 0; +exports.TableRole = exports.OptionsType = exports.FutureLiveDates = exports.PastLiveDates = exports.AllPrecannedDates = exports.PrecannedDate = exports.FromNowDateRanges = exports.PastLiveDateRanges = exports.UntilNowDateRanges = exports.PrecannedDateRange = exports.isSyncExecutionContext = exports.InvocationSource = exports.InvocationErrorType = exports.PermissionSyncMode = exports.ValidFetchMethods = exports.NetworkConnection = exports.ConnectionRequirement = exports.ParameterTypeInputMap = exports.ParameterType = exports.fileArray = exports.imageArray = exports.htmlArray = exports.dateArray = exports.booleanArray = exports.numberArray = exports.stringArray = exports.isArrayType = exports.Type = void 0; /** * Markers used internally to represent data types for parameters and return values. * It should not be necessary to ever use these values directly. @@ -257,6 +257,13 @@ var InvocationSource; InvocationSource["Doc"] = "Doc"; InvocationSource["NativeIntegration"] = "NativeIntegration"; })(InvocationSource || (exports.InvocationSource = InvocationSource = {})); +/** + * A function to check if a given {@link ExecutionContext} is a {@link SyncExecutionContext}. + */ +function isSyncExecutionContext(context) { + return context.hasOwnProperty('sync') && context.hasOwnProperty('syncStateService'); +} +exports.isSyncExecutionContext = isSyncExecutionContext; // A mapping exists in coda that allows these to show up in the UI. // If adding new values here, add them to that mapping and vice versa. /** diff --git a/dist/bundle.d.ts b/dist/bundle.d.ts index 52a4bb66c1..808dee81d5 100644 --- a/dist/bundle.d.ts +++ b/dist/bundle.d.ts @@ -632,6 +632,29 @@ export interface TemporaryBlobStorage { downloadFilename?: string; }): Promise; } +/** + * A service for retrieving the sync state in Coda Brain. + * @hidden + * + * TODO(ebo): unhide this + */ +export interface SyncStateService { + /** + * Retrieve the latest version of rows with the given row ids and returns a mapping of row ids to + * their latest version, e.g. {"Id-123": "1.0.0"}. + * + * If the row id is not found, it will not be included in the response. + * + * If the row version is not defined, it will be set to an empty string. + * + * @hidden + * + * TODO(ebo): unhide this + */ + getLatestRowVersions(rowIds: string[]): Promise<{ + [rowId: string]: string; + }>; +} declare enum PermissionSyncMode { Personal = "Personal", PermissionAware = "PermissionAware" @@ -845,6 +868,13 @@ export interface SyncExecutionContext; + /** + * A service for retrieving the sync state in Coda Brain. + * @hidden + * + * TODO(ebo): unhide this + */ + readonly syncStateService: SyncStateService; } /** * Sub-class of {@link SyncExecutionContext} that is passed to the `options` function of diff --git a/dist/index.d.ts b/dist/index.d.ts index e657e93d50..7bf9e5e93a 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -112,6 +112,7 @@ export type { SyncExecutionContext } from './api_types'; export type { SyncFormulaResult } from './api'; export type { SyncTableDef } from './api'; export type { SyncTableOptions } from './api'; +export type { SyncStateService } from './api_types'; export type { TemporaryBlobStorage } from './api_types'; export { Type } from './api_types'; export type { TypedPackFormula } from './api'; diff --git a/dist/runtime/bootstrap/index.d.ts b/dist/runtime/bootstrap/index.d.ts index 8f63e465e3..6b92ee5830 100644 --- a/dist/runtime/bootstrap/index.d.ts +++ b/dist/runtime/bootstrap/index.d.ts @@ -11,6 +11,7 @@ import type { Logger } from '../../api_types'; import type { PackFunctionResponse } from '../types'; import type { ParamDefs } from '../../api_types'; import type { ParamValues } from '../../api_types'; +import type { SyncStateService } from '../../api_types'; import type { SyncUpdate } from '../../api'; import type { TemporaryBlobStorage } from '../../api_types'; export type { Context } from 'isolated-vm'; @@ -47,10 +48,11 @@ export type ExecutionContextPrimitives = Omit; export declare function registerBundle(isolate: Isolate, context: Context, path: string, stubName: string, requiresManualClosure?: boolean): Promise; diff --git a/dist/runtime/bootstrap/index.js b/dist/runtime/bootstrap/index.js index 727ed3f720..03f528af01 100644 --- a/dist/runtime/bootstrap/index.js +++ b/dist/runtime/bootstrap/index.js @@ -150,12 +150,13 @@ exports.injectSerializer = injectSerializer; /** * Injects the ExecutionContext object, including stubs for network calls, into the isolate. */ -async function injectExecutionContext({ context, fetcher, temporaryBlobStorage, logger, endpoint, invocationLocation, timezone, invocationToken, sync, authenticationName, executionId, previousAttemptError, ...rest }) { +async function injectExecutionContext({ context, fetcher, temporaryBlobStorage, syncStateService, logger, endpoint, invocationLocation, timezone, invocationToken, sync, authenticationName, executionId, previousAttemptError, ...rest }) { (0, ensure_1.ensureNever)(); // Inject all of the primitives into a global we'll access when we execute the pack function. const executionContextPrimitives = { fetcher: {}, temporaryBlobStorage: {}, + syncStateService: {}, logger: {}, endpoint, invocationLocation, @@ -180,6 +181,9 @@ async function injectExecutionContext({ context, fetcher, temporaryBlobStorage, await injectLogFunction(context, 'console.log', logger.info.bind(logger)); await injectAsyncFunction(context, 'executionContext.temporaryBlobStorage.storeBlob', temporaryBlobStorage.storeBlob.bind(temporaryBlobStorage)); await injectAsyncFunction(context, 'executionContext.temporaryBlobStorage.storeUrl', temporaryBlobStorage.storeUrl.bind(temporaryBlobStorage)); + if (syncStateService) { + await injectAsyncFunction(context, 'executionContext.syncStateService.getLatestRowVersions', syncStateService.getLatestRowVersions.bind(syncStateService)); + } } exports.injectExecutionContext = injectExecutionContext; async function registerBundle(isolate, context, path, stubName, requiresManualClosure = true) { diff --git a/dist/testing/execution.js b/dist/testing/execution.js index ad9e322aef..196af84734 100644 --- a/dist/testing/execution.js +++ b/dist/testing/execution.js @@ -555,8 +555,7 @@ function newRealFetcherExecutionContext(packDef, manifestPath) { } exports.newRealFetcherExecutionContext = newRealFetcherExecutionContext; function newRealFetcherSyncExecutionContext(packDef, manifestPath) { - const context = newRealFetcherExecutionContext(packDef, manifestPath); - return { ...context, sync: {} }; + return (0, fetcher_2.newFetcherSyncExecutionContext)(buildUpdateCredentialsCallback(manifestPath), (0, helpers_3.getPackAuth)(packDef), packDef.networkDomains, getCredentials(manifestPath)); } exports.newRealFetcherSyncExecutionContext = newRealFetcherSyncExecutionContext; const SyncUpdateSchema = z.object({ diff --git a/dist/testing/fetcher.js b/dist/testing/fetcher.js index 9520d50a4e..73dd5605ea 100644 --- a/dist/testing/fetcher.js +++ b/dist/testing/fetcher.js @@ -547,6 +547,14 @@ class AuthenticatingBlobStorage { return `https://not-a-real-url.s3.amazonaws.com/tempBlob/${(0, uuid_1.v4)()}`; } } +class FakeSyncStateService { + async getLatestRowVersions(rowIds) { + return rowIds.reduce((acc, rowId) => { + acc[rowId] = '1.0.0'; + return acc; + }, {}); + } +} function newFetcherExecutionContext(updateCredentialsCallback, authDef, networkDomains, credentials) { const invocationToken = (0, uuid_1.v4)(); const fetcher = new AuthenticatingFetcher(updateCredentialsCallback, authDef, networkDomains, credentials, invocationToken); @@ -564,7 +572,7 @@ function newFetcherExecutionContext(updateCredentialsCallback, authDef, networkD exports.newFetcherExecutionContext = newFetcherExecutionContext; function newFetcherSyncExecutionContext(updateCredentialsCallback, authDef, networkDomains, credentials) { const context = newFetcherExecutionContext(updateCredentialsCallback, authDef, networkDomains, credentials); - return { ...context, sync: {} }; + return { ...context, sync: {}, syncStateService: new FakeSyncStateService() }; } exports.newFetcherSyncExecutionContext = newFetcherSyncExecutionContext; function addQueryParam(url, param, value) { diff --git a/dist/testing/ivm_helper.d.ts b/dist/testing/ivm_helper.d.ts index 70510b313f..a95c79d0d4 100644 --- a/dist/testing/ivm_helper.d.ts +++ b/dist/testing/ivm_helper.d.ts @@ -1,3 +1,3 @@ -import type { ExecutionContext } from '../api'; +import type { ExecutionContext } from '../api_types'; import type { Context as IVMContext } from 'isolated-vm'; export declare function setupIvmContext(bundlePath: string, executionContext: ExecutionContext): Promise; diff --git a/dist/testing/ivm_helper.js b/dist/testing/ivm_helper.js index faab382b9e..40de4cadcd 100644 --- a/dist/testing/ivm_helper.js +++ b/dist/testing/ivm_helper.js @@ -11,6 +11,7 @@ const ivm_wrapper_1 = require("./ivm_wrapper"); const bootstrap_2 = require("../runtime/bootstrap"); const bootstrap_3 = require("../runtime/bootstrap"); const bootstrap_4 = require("../runtime/bootstrap"); +const api_types_1 = require("../api_types"); const path_1 = __importDefault(require("path")); const bootstrap_5 = require("../runtime/bootstrap"); const IsolateMemoryLimit = 128; @@ -56,6 +57,7 @@ async function setupIvmContext(bundlePath, executionContext) { authenticationName: executionContext.authenticationName, executionId: executionContext.executionId, previousAttemptError: executionContext.previousAttemptError, + syncStateService: (0, api_types_1.isSyncExecutionContext)(executionContext) ? executionContext.syncStateService : undefined, }); return ivmContext; } diff --git a/dist/testing/mocks.d.ts b/dist/testing/mocks.d.ts index 12f2b83b45..79648cf0cb 100644 --- a/dist/testing/mocks.d.ts +++ b/dist/testing/mocks.d.ts @@ -4,6 +4,7 @@ import type { FetchRequest } from '../api_types'; import type { FetchResponse } from '../api_types'; import type { Sync } from '../api_types'; import type { SyncExecutionContext } from '../api_types'; +import type { SyncStateService } from '../api_types'; import type { TemporaryBlobStorage } from '../api_types'; import sinon from 'sinon'; export type SinonFunctionSpy any> = T extends (...args: infer ArgsT) => infer RetT ? sinon.SinonSpy : never; @@ -19,6 +20,9 @@ export interface MockExecutionContext extends ExecutionContext { } export interface MockSyncExecutionContext extends MockExecutionContext { sync: Sync; + syncStateService: { + getLatestRowVersions: SinonFunctionStub; + }; } /** Mock type of the specified `SyncExecutionContext`. */ export type SyncExecutionContextAsMock = T extends SyncExecutionContext ? MockSyncExecutionContext : never; diff --git a/dist/testing/mocks.js b/dist/testing/mocks.js index f3f6ed6e02..669c57903d 100644 --- a/dist/testing/mocks.js +++ b/dist/testing/mocks.js @@ -7,7 +7,12 @@ exports.newJsonFetchResponse = exports.newMockExecutionContext = exports.newMock const sinon_1 = __importDefault(require("sinon")); const uuid_1 = require("uuid"); function newMockSyncExecutionContext(overrides) { - return { ...newMockExecutionContext(), sync: {}, ...overrides }; + return { + ...newMockExecutionContext(), + sync: {}, + syncStateService: { getLatestRowVersions: sinon_1.default.stub() }, + ...overrides, + }; } exports.newMockSyncExecutionContext = newMockSyncExecutionContext; function newMockExecutionContext(overrides) { diff --git a/docs/reference/sdk/interfaces/testing.MockSyncExecutionContext.md b/docs/reference/sdk/interfaces/testing.MockSyncExecutionContext.md index 5ec8d0aa68..3170c5bcc5 100644 --- a/docs/reference/sdk/interfaces/testing.MockSyncExecutionContext.md +++ b/docs/reference/sdk/interfaces/testing.MockSyncExecutionContext.md @@ -103,6 +103,18 @@ Information about state of the current sync. Only populated if this is a sync ta ___ +### syncStateService + +• **syncStateService**: `Object` + +#### Type declaration + +| Name | Type | +| :------ | :------ | +| `getLatestRowVersions` | `SinonStub`<[rowIds: string[]], `Promise`<{ `[rowId: string]`: `string`; }\>\> | + +___ + ### temporaryBlobStorage • **temporaryBlobStorage**: `Object` diff --git a/index.ts b/index.ts index 184ecbc72c..758b0bdcc5 100644 --- a/index.ts +++ b/index.ts @@ -122,6 +122,7 @@ export type {SyncExecutionContext} from './api_types'; export type {SyncFormulaResult} from './api'; export type {SyncTableDef} from './api'; export type {SyncTableOptions} from './api'; +export type {SyncStateService} from './api_types'; export type {TemporaryBlobStorage} from './api_types'; export {Type} from './api_types'; export type {TypedPackFormula} from './api'; diff --git a/package.json b/package.json index ba4b9b01b1..161aaaf31b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codahq/packs-sdk", - "version": "1.8.6-prerelease.2", + "version": "1.8.6-prerelease.3", "license": "MIT", "workspaces": [ "dev/eslint" diff --git a/runtime/bootstrap/index.ts b/runtime/bootstrap/index.ts index 1ff9fcb460..342df651d5 100644 --- a/runtime/bootstrap/index.ts +++ b/runtime/bootstrap/index.ts @@ -11,6 +11,7 @@ import type {Logger} from '../../api_types'; import type {PackFunctionResponse} from '../types'; import type {ParamDefs} from '../../api_types'; import type {ParamValues} from '../../api_types'; +import type {SyncStateService} from '../../api_types'; import type {SyncUpdate} from '../../api'; import type {TemporaryBlobStorage} from '../../api_types'; import {ensureNever} from '../../helpers/ensure'; @@ -228,6 +229,7 @@ export async function injectExecutionContext({ context, fetcher, temporaryBlobStorage, + syncStateService, logger, endpoint, invocationLocation, @@ -242,6 +244,7 @@ export async function injectExecutionContext({ context: Context; fetcher: Fetcher; temporaryBlobStorage: TemporaryBlobStorage; + syncStateService: SyncStateService | undefined; logger: Logger; } & ExecutionContextPrimitives): Promise { ensureNever(); @@ -249,6 +252,7 @@ export async function injectExecutionContext({ const executionContextPrimitives = { fetcher: {}, temporaryBlobStorage: {}, + syncStateService: {}, logger: {}, endpoint, invocationLocation, @@ -286,6 +290,14 @@ export async function injectExecutionContext({ 'executionContext.temporaryBlobStorage.storeUrl', temporaryBlobStorage.storeUrl.bind(temporaryBlobStorage), ); + + if (syncStateService) { + await injectAsyncFunction( + context, + 'executionContext.syncStateService.getLatestRowVersions', + syncStateService.getLatestRowVersions.bind(syncStateService), + ); + } } export async function registerBundle( diff --git a/testing/execution.ts b/testing/execution.ts index 533fb1703c..002e8b361d 100644 --- a/testing/execution.ts +++ b/testing/execution.ts @@ -858,8 +858,12 @@ export function newRealFetcherSyncExecutionContext( packDef: BasicPackDefinition, manifestPath: string, ): SyncExecutionContext { - const context = newRealFetcherExecutionContext(packDef, manifestPath); - return {...context, sync: {}}; + return newFetcherSyncExecutionContext( + buildUpdateCredentialsCallback(manifestPath), + getPackAuth(packDef), + packDef.networkDomains, + getCredentials(manifestPath), + ); } const SyncUpdateSchema = z.object({ diff --git a/testing/fetcher.ts b/testing/fetcher.ts index f79ba72024..ee62d1f94f 100644 --- a/testing/fetcher.ts +++ b/testing/fetcher.ts @@ -25,6 +25,7 @@ import {STSClient} from '@aws-sdk/client-sts'; import {Sha256} from '@aws-crypto/sha256-js'; import {SignatureV4} from '@aws-sdk/signature-v4'; import type {SyncExecutionContext} from '../api_types'; +import type {SyncStateService} from '../api_types'; import type {TemporaryBlobStorage} from '../api_types'; import type {TokenCredentials} from './auth_types'; import {URL} from 'url'; @@ -703,6 +704,15 @@ class AuthenticatingBlobStorage implements TemporaryBlobStorage { } } +class FakeSyncStateService implements SyncStateService { + async getLatestRowVersions(rowIds: string[]): Promise<{[rowId: string]: string}> { + return rowIds.reduce((acc, rowId) => { + acc[rowId] = '1.0.0'; + return acc; + }, {} as {[rowId: string]: string}); + } +} + export function newFetcherExecutionContext( updateCredentialsCallback: (newCreds: Credentials) => void | undefined, authDef: Authentication | undefined, @@ -736,7 +746,7 @@ export function newFetcherSyncExecutionContext( credentials?: Credentials, ): SyncExecutionContext { const context = newFetcherExecutionContext(updateCredentialsCallback, authDef, networkDomains, credentials); - return {...context, sync: {}}; + return {...context, sync: {}, syncStateService: new FakeSyncStateService()}; } function addQueryParam(url: string, param: string, value: string): string { diff --git a/testing/ivm_helper.ts b/testing/ivm_helper.ts index 22120d0be2..f2a84b14cf 100644 --- a/testing/ivm_helper.ts +++ b/testing/ivm_helper.ts @@ -1,4 +1,4 @@ -import type {ExecutionContext} from '../api'; +import type {ExecutionContext} from '../api_types'; import type {Context as IVMContext} from 'isolated-vm'; import {build as buildBundle} from '../cli/build'; import {createIsolateContext} from '../runtime/bootstrap'; @@ -7,6 +7,7 @@ import {getIvm} from './ivm_wrapper'; import {getThunkPath} from '../runtime/bootstrap'; import {injectExecutionContext} from '../runtime/bootstrap'; import {injectSerializer} from '../runtime/bootstrap'; +import { isSyncExecutionContext} from '../api_types'; import path from 'path'; import {registerBundles} from '../runtime/bootstrap'; @@ -58,6 +59,7 @@ export async function setupIvmContext(bundlePath: string, executionContext: Exec authenticationName: executionContext.authenticationName, executionId: executionContext.executionId, previousAttemptError: executionContext.previousAttemptError, + syncStateService: isSyncExecutionContext(executionContext) ? executionContext.syncStateService : undefined, }); return ivmContext; diff --git a/testing/mocks.ts b/testing/mocks.ts index a839000887..87016417c6 100644 --- a/testing/mocks.ts +++ b/testing/mocks.ts @@ -4,6 +4,7 @@ import type {FetchRequest} from '../api_types'; import type {FetchResponse} from '../api_types'; import type {Sync} from '../api_types'; import type {SyncExecutionContext} from '../api_types'; +import type {SyncStateService} from '../api_types'; import type {TemporaryBlobStorage} from '../api_types'; import sinon from 'sinon'; import {v4} from 'uuid'; @@ -32,6 +33,9 @@ export interface MockSyncExecutionContext< IncrementalSyncContinuationT = ContinuationT, > extends MockExecutionContext { sync: Sync; + syncStateService: { + getLatestRowVersions: SinonFunctionStub; + }; } /** Mock type of the specified `SyncExecutionContext`. */ @@ -46,7 +50,12 @@ export type SyncExecutionContextAsMock = T exten export function newMockSyncExecutionContext>( overrides?: Partial, ): SyncExecutionContextAsMock { - return {...newMockExecutionContext(), sync: {}, ...overrides} as SyncExecutionContextAsMock; + return { + ...newMockExecutionContext(), + sync: {}, + syncStateService: {getLatestRowVersions: sinon.stub()}, + ...overrides, + } as SyncExecutionContextAsMock; } export function newMockExecutionContext(overrides?: Partial): MockExecutionContext {