import type { DatabaseSchema } from "./schema.js"; import { getSyncCapabilityKey, type KeyPropertyName, type Sync, type SyncChangeDelete, type SyncChangeUpsert, } from "./sync.js"; export type WorkflowSyncTriggerResult = { status: "queued"; }; export type WorkflowSyncApplyResult = { upserted: number; deleted: number; skipped: number; }; export type WorkflowSyncClient = { /** * Queue a run of the sync's own handler with its persisted state. Resolves * when the run is queued, not when the sync cycle finishes. */ trigger(syncDefinition: Sync): Promise; /** * Write event-driven changes through the sync without running its handler. * Pass `state` to replace the sync's persisted state (for example, its * cursor); omit it to leave the state unchanged. */ apply, State>( syncDefinition: Sync, args: { changes: Array< | Pick< SyncChangeUpsert, "type" | "key" | "properties" | "upstreamUpdatedAt" > | SyncChangeDelete >; state?: State; }, ): Promise; }; async function callWorkflowSyncApi( endpoint: string, body: Record, ): Promise { const baseUrl = process.env.NOTION_API_BASE_URL; const token = process.env.NOTION_API_TOKEN; if (!baseUrl || !token) { throw new Error( "Workflow sync operations require a deployed workflow with Notion access configured.", ); } const response = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v3/${endpoint}`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify(body), }); const result: unknown = await response.json(); if (!response.ok) { const message = typeof result === "object" && result !== null && "message" in result && typeof result.message === "string" ? result.message : `Workflow sync operation failed with status ${response.status}`; throw new Error(message); } return result; } function parseTriggerResult(result: unknown): WorkflowSyncTriggerResult { if ( typeof result !== "object" || result === null || !("status" in result) || result.status !== "queued" ) { throw new Error("Workflow sync trigger returned an invalid response."); } return { status: "queued" }; } function parseApplyResult(result: unknown): WorkflowSyncApplyResult { if ( typeof result !== "object" || result === null || !("upserted" in result) || typeof result.upserted !== "number" || !("deleted" in result) || typeof result.deleted !== "number" || !("skipped" in result) || typeof result.skipped !== "number" ) { throw new Error("Workflow sync apply returned an invalid response."); } return { upserted: result.upserted, deleted: result.deleted, skipped: result.skipped, }; } export function createWorkflowSyncClient(): WorkflowSyncClient { return { async trigger(syncDefinition) { const result = await callWorkflowSyncApi("workersWorkflowSyncTrigger", { capabilityKey: getSyncCapabilityKey(syncDefinition), }); return parseTriggerResult(result); }, async apply(syncDefinition, args) { const result = await callWorkflowSyncApi("workersWorkflowSyncApply", { capabilityKey: getSyncCapabilityKey(syncDefinition), changes: args.changes, ...(args.state === undefined ? {} : { state: args.state }), }); return parseApplyResult(result); }, }; }