import { type WorkflowConnectionDeclarations, type WorkflowConnectionRequirement, type WorkflowConnections, type WorkflowTriggerConnections } from "./connections.js"; import { type WorkflowAccess, type WorkflowAccessDeclarations, type WorkflowAccessRequirements } from "./workflow-access.js"; import type { CapabilityContext } from "./context.js"; import { type RunMetadata } from "./runtime-metadata.js"; import type { Infer, SchemaBuilder } from "./schema-builder.js"; import { type WorkflowState } from "./workflow-state.js"; import type { ManualWorkflowTrigger, WebhookTrigger, WorkflowEventMap, WorkflowTrigger, WorkflowEventCreators, WorkflowTriggerForConnections } from "./events.generated.js"; import { type WorkflowSyncClient } from "./workflow-sync.js"; type HandlerOptions = { /** Return the concrete result instead of writing the output envelope. */ concreteOutput?: true; }; type WorkflowWaitDurationValues = { milliseconds?: number; seconds?: number; minutes?: number; hours?: number; days?: number; weeks?: number; }; export type WorkflowWaitDuration = { [Unit in keyof WorkflowWaitDurationValues]-?: Required> & WorkflowWaitDurationValues; }[keyof WorkflowWaitDurationValues]; type WorkflowWaitKey = string | string[]; type WorkflowInputFieldOptions = { optional?: boolean; }; declare const workflowInputValueType: unique symbol; export type WorkflowInputField = { type: "text" | "number" | "checkbox" | "select" | "multi_select" | "date" | "person" | "files"; optional?: boolean; options?: readonly string[]; readonly [workflowInputValueType]?: T; }; type WorkflowInputValue = TOptions extends { optional: true; } ? T | undefined : T; /** Date-only or local date-time endpoints, with the selected IANA time zone. */ export type WorkflowInputDate = { start: string; end?: string; timeZone?: string; }; export declare const input: { text(options?: TOptions): WorkflowInputField>; number(options?: TOptions): WorkflowInputField>; checkbox(options?: TOptions): WorkflowInputField>; select(values: TOptions, options?: TFieldOptions): WorkflowInputField>; date(options?: TOptions): WorkflowInputField>; multiSelect(values: TOptions, options?: TFieldOptions): WorkflowInputField>; /** Returns the user ID of every selected person. */ person(options?: TOptions): WorkflowInputField>; /** Returns each attachment's URL or Notion attachment block ID. */ files(options?: TOptions): WorkflowInputField>; }; export type WorkflowInputSchema = Record>; export type WorkflowInputValues = { [Key in keyof TSchema]: TSchema[Key] extends WorkflowInputField ? Value : never; }; export type WorkflowInputResponse = { value: WorkflowInputValues; respondedBy: string; respondedAt: string; }; export type WorkflowWaitForInputOptions = { input: TSchema; key?: WorkflowWaitKey; }; type WorkflowEventWaitNotionPropertyFilters = Record; type WorkflowEventWaitUserFilter = { /** Only events caused by these Notion users resolve the wait. */ onlyTriggerForUsers?: { notionIds: string[]; }; }; type WorkflowEventWaitMailFilter = { field: "body" | "domain" | "from" | "subject" | "to"; operator: "is_empty" | "is_not_empty" | "string_contains" | "string_does_not_contain" | "string_ends_with" | "string_is" | "string_is_not" | "string_starts_with"; value: { type: "exact"; value?: string; }; }; type WorkflowEventWaitCalendarScope = { calendars: Array<{ accountId: string; calendarId: string; }>; filter?: Record; }; /** * Where each waitable event comes from. A wait has its own scope: a workflow * cannot both trigger on and wait for the same event. */ export type WorkflowEventWaitScopeMap = { "notion.page.created": { dataSourceId: string; viewId?: string; propertyFilters?: WorkflowEventWaitNotionPropertyFilters; }; "notion.page.updated": { dataSourceId: string; /** Property IDs or names; changes to other properties are ignored. */ properties?: string[]; propertyFilters?: WorkflowEventWaitNotionPropertyFilters; shouldIgnorePageContentUpdates?: boolean; }; "notion.page.deleted": { dataSourceId: string; viewId?: string; propertyFilters?: WorkflowEventWaitNotionPropertyFilters; }; "notion.meetingNote.summary.completed": { dataSourceId: string; viewId?: string; propertyFilters?: WorkflowEventWaitNotionPropertyFilters; }; "notion.page.discussion.comment.added": { dataSourceId: string; }; "notion.agent.mentioned": WorkflowEventWaitUserFilter; "slack.message": WorkflowEventWaitUserFilter & { channelIds: string[]; filters?: { operator: "all" | "any"; filters: Array<{ type: "message.text.contains"; operator: "all" | "any"; values: string[]; }>; }; teamId?: string; }; "slack.reaction.added": WorkflowEventWaitUserFilter & { channelIds?: string[]; allChannels?: boolean; /** Emoji names without colons. */ reactions: string[]; teamId?: string; }; "slack.app.mention": WorkflowEventWaitUserFilter & { channelIds: string[]; teamId?: string; }; "discord.interaction": { channelIds: string[]; }; "mail.email.received": { emailAccountId: string; filters?: WorkflowEventWaitMailFilter[]; inboxOnly?: boolean; }; "mail.email.sent": { emailAccountId: string; filters?: WorkflowEventWaitMailFilter[]; }; "mail.label.applied": { emailAccountId: string; labelNames: string[]; }; "calendar.event.created": WorkflowEventWaitCalendarScope; "calendar.event.updated": WorkflowEventWaitCalendarScope; "calendar.event.canceled": WorkflowEventWaitCalendarScope; "googleDriveOauth.filesChanged": { /** Direct children of a folder, or a shared drive. Polled hourly. */ scope: { type: "folder"; folderId: string; } | { type: "drive"; driveId: string; }; }; }; /** Triggers a workflow can wait on. */ export type WorkflowWaitEventTrigger = Extract; type WorkflowEventWaitScope = WorkflowEventWaitScopeMap[TTrigger["type"]]; type WorkflowWaitEventTriggerForConnections = Extract, WorkflowWaitEventTrigger>; export type WorkflowWaitForEventOptions = { /** The event to wait for, created with the same creators as workflow triggers. */ event: TTrigger; /** How long to wait before resolving with `null`. Must be shorter than 7 days. */ timeout: WorkflowWaitDuration; key?: WorkflowWaitKey; } & ({} extends WorkflowEventWaitScope ? { scope?: WorkflowEventWaitScope; } : { scope: WorkflowEventWaitScope; }); export type WorkflowWaitUntilOptions = { at: Date; key?: WorkflowWaitKey; } | { after: WorkflowWaitDuration; key?: WorkflowWaitKey; }; export type WorkflowWaitUntilResult = { status: "waiting"; wait: { type: "until"; name: string; resumeAtMs: number; }; }; export type WorkflowWaitForInputResult = { status: "waiting"; wait: { type: "input"; name: string; stepKey: string; input: WorkflowInputSchema; }; }; export type WorkflowWaitForEventResult = { status: "waiting"; wait: { type: "event"; name: string; stepKey: string; event: WorkflowWaitEventTrigger & { scope?: Record; }; timeoutAtMs: number; }; }; type WorkflowWaitResult = WorkflowWaitUntilResult | WorkflowWaitForInputResult | WorkflowWaitForEventResult; type WorkflowHandlerResult = { status: "success"; output?: Record; } | WorkflowWaitResult; /** An object schema describing the value a workflow returns. */ export type WorkflowOutputSchema = SchemaBuilder, "object">; /** * What a workflow handler returns: the value described by `outputSchema`, or * nothing when the workflow declares no output schema. */ export type WorkflowHandlerReturn = TOutputSchema extends WorkflowOutputSchema ? Promise> | Infer : Promise | void; export { FatalError, RetryableError } from "./error.js"; export type { WorkflowAccess, WorkflowAccessAgentLevel, WorkflowAccessBindings, WorkflowAccessDeclaration, WorkflowAccessDeclarations, WorkflowAccessLevel, WorkflowAccessLevelFor, WorkflowAccessResource, ResourceAccessLevel, WorkflowAccessResourceType, WorkflowAccessRequirement, WorkflowAccessRequirements, WorkflowAccessValue, } from "./workflow-access.js"; export { access } from "./workflow-access.js"; export type WorkflowEvent = WorkflowEventMap[keyof WorkflowEventMap]; export type WorkflowEventForTrigger = T extends ManualWorkflowTrigger ? { type: "workflow.manual"; input: TInput; } : T extends { type: keyof WorkflowEventMap; } ? WorkflowEventMap[T["type"]] : never; export type WorkflowEventForTriggers = WorkflowEventForTrigger; export { connections, type WorkflowConnection, type WorkflowConnectionDeclarations, type WorkflowConnectionRequirement, type WorkflowConnections, type WorkflowTriggerConnections, type OAuthConfiguration, type OAuthConnection, } from "./connections.js"; export type WorkflowDeadline = { /** Maximum wall-clock lifetime of one logical workflow run. */ afterMs: number; }; /** * An error class, or a predicate, that marks an error as retryable. * Classes match with `instanceof`; any other function is called with the error. */ export type WorkflowRetryableErrorMatcher = (abstract new (...args: never[]) => Error) | ((error: unknown) => boolean); export type WorkflowRetryPolicy = { /** Total attempts, including the initial attempt. At most 4. */ maxAttempts: number; /** Delay before the first retry. Defaults to 1 second. */ initialDelayMs?: number; /** Maximum delay between attempts. Defaults to 5 minutes. */ maxDelayMs?: number; /** Exponential backoff multiplier. Defaults to 2. */ backoffMultiplier?: number; /** * Errors that may be retried, as one matcher or an array of matchers. When * set, only matching errors and `RetryableError` are retried; every other * error fails immediately. When omitted, every error except `FatalError` is * retried. */ retryOn?: WorkflowRetryableErrorMatcher | readonly WorkflowRetryableErrorMatcher[]; }; /** * The inbound HTTP request passed to a webhook verify handler. */ export type WebhookVerifyRequest = { /** Uppercase HTTP method. Deliveries are POSTs; GET/HEAD cover challenge probes. */ method: "GET" | "HEAD" | "POST"; /** The full webhook URL as received. */ url: string; /** Query string parameters. */ query: Record; /** Request headers with lowercased names. */ headers: Record; /** Raw request body. Empty string for bodyless methods like GET and HEAD. */ rawBody: string; }; /** * The HTTP response a verify handler produces. `deliver`, when present, * explicitly controls whether the request is enqueued for the workflow * handler. Otherwise delivery is derived from the status: 2xx enqueues and 4xx * does not. */ export type WebhookVerifyResponse = { /** Response status code. Only 2xx and 4xx statuses are allowed. */ status: number; /** Optional response body, at most 8KB. */ body?: string; /** Response content type. Defaults to "text/plain". */ contentType?: "application/json" | "text/plain"; /** * Whether to enqueue the request for the workflow handler. Defaults to true * for 2xx responses and false for 4xx responses. */ deliver?: boolean; }; /** * A webhook verify handler. Runs synchronously at Notion's webhook ingress, * before the HTTP response is sent, in the app's own sandbox — the same * runtime as the workflow handler, with the full Node.js standard library. * Read signing secrets from `process.env` and use `node:crypto` for * signature checks. * * Keep it fast: the run must fit a small wall-clock budget (a few seconds, * including sandbox startup), so avoid network calls — including * `context.notion` API calls, which typically won't fit the budget. */ export type WebhookVerifyHandler = (request: WebhookVerifyRequest, context: CapabilityContext) => WebhookVerifyResponse | Promise; /** * A verify invocation as dispatched by the platform: a single `request` * object rather than the event a workflow invocation carries. */ export type WebhookVerifyInvocation = { request: WebhookVerifyRequest; }; /** * A trigger as the manifest records it. A declared `verify` handler is flagged * on the webhook trigger itself, which is where the platform reads it to decide * whether to run `verify` at webhook ingress. */ export type WorkflowManifestTrigger = WorkflowTrigger | (WebhookTrigger & { hasVerify: true; }); /** * Configuration passed to {@link workflow}. */ export type WorkflowConfiguration = { /** * A human-readable name for the workflow, shown in the UI when viewing workflows */ name: string; /** * A human-readable description of what the workflow does, shown in the UI when viewing workflows */ description: string; /** * An array of triggers that can invoke this workflow. * * Each trigger defines a specific event or condition that causes the workflow to run. */ triggers: TTriggers; /** * Notion-as-Code resources whose access is configured during cloud deployment. * Deployment reconciles grants through the existing Notion-module permissions * and supplies named runtime bindings on `context.access`. */ access?: TAccess; /** * Connections that must be set up before this workflow can run. */ connections?: TConnections; /** Maximum wall-clock lifetime of one logical workflow run. */ deadline?: WorkflowDeadline; /** * Default retry policy for every step, and the retry policy for failures * that happen outside a step. A step's own `retry` replaces it. */ retry?: WorkflowRetryPolicy; /** * Optional synchronous verification handler, run at Notion's webhook * ingress before the HTTP response is sent. Use it for providers that * require a synchronous verification response: signature checks with * specific status codes, challenge echoes, or handshake requests. * * Only workflows with an `events.webhook()` trigger may declare one. * * The handler controls the HTTP response. Return `deliver: true` to enqueue * the request for the workflow handler, or `deliver: false` to answer the * provider without enqueueing it. When omitted, 2xx enqueues and 4xx does * not. `verify` runs in the app's own sandbox with full Node.js — read * secrets from `process.env` and use `node:crypto` for signature checks. It * receives the capability context as its second argument, without the run * metadata and `step` a workflow run carries. * * Without `verify`, the webhook keeps the current behavior: every request * is accepted with `202` and processed asynchronously. * * @example * ```ts * verify: (request) => { * const signature = createHmac("sha256", process.env.WEBHOOK_SECRET ?? "") * .update(request.rawBody) * .digest("hex"); * const expected = Buffer.from(`sha256=${signature}`); * const actual = Buffer.from(request.headers["x-hub-signature-256"] ?? ""); * const verified = expected.length === actual.length && timingSafeEqual(expected, actual); * return verified ? { status: 200 } : { status: 401, body: "Invalid signature" }; * }, * ``` */ verify?: WebhookVerifyHandler; /** * Optional schema for the object the handler returns, built with * `@notionhq/apps/schema-builder`. The handler's return type is inferred * from it, and the returned value is validated before the run succeeds. * * Without `outputSchema`, the workflow returns nothing. * * @example * ```ts * outputSchema: j.object({ releaseUrl: j.string() }), * handler: async () => ({ releaseUrl: "https://example.com/releases/1" }), * ``` */ outputSchema?: TOutputSchema; handler: (event: WorkflowEventForTriggers, context: WorkflowContext) => WorkflowHandlerReturn>; }; /** Configuration that checks trigger keys against declared connections. */ export type WorkflowTriggerConfiguration = Omit, "triggers"> & { triggers: (context: { events: WorkflowEventCreators>; connections: WorkflowTriggerConnections>; }) => TTriggers; }; /** * A workflow capability as returned by {@link workflow}. * * Note that a workflow carries no key of its own — the build tool derives * the capability key from the file the workflow is default-exported from. */ export type Workflow = { _tag: "workflow"; config: { name: string; description: string; triggers: WorkflowManifestTrigger[]; connections?: readonly WorkflowConnectionRequirement[]; access?: WorkflowAccessRequirements; deadline?: WorkflowDeadline; retry?: WorkflowRetryPolicy; /** JSON Schema for the handler's return value. Absent when the workflow returns nothing. */ outputJsonSchema?: Record; }; handler: (input: WorkflowEventForTriggers | WebhookVerifyInvocation, options?: HandlerOptions) => Promise; }; /** * Create a workflow capability. * * Default-export the result from a file directly under `src/workflows/` — * the file's basename becomes the workflow's key. * * @example * ```ts * // src/workflows/onPageCreated.ts * import { events } from "@notionhq/apps/events"; * import { workflow } from "@notionhq/apps/workflow"; * * export default workflow({ * name: "Send Welcome Email", * description: "Sends a welcome email when a new page is added", * triggers: [events.notionPageCreated()], * handler: async (event, context) => { * console.log(event.page); * await context.step("Process page", async ({ id }) => { * console.log(`Running step ${id}`); * return processPage(event.page); * }); * }, * }); * ``` */ export declare function workflow>, ...WorkflowTriggerForConnections>[] ], const TConnections extends WorkflowConnectionDeclarations = Record, const TAccess extends WorkflowAccessDeclarations = Record, const TOutputSchema extends WorkflowOutputSchema | undefined = undefined>(configuration: WorkflowTriggerConfiguration): Workflow; export declare function workflow, const TAccess extends WorkflowAccessDeclarations = Record, const TOutputSchema extends WorkflowOutputSchema | undefined = undefined>(configuration: WorkflowConfiguration): Workflow; /** Context passed to a workflow step. */ export type StepContext = { /** * An identifier that is stable for this step across attempts of the workflow run. * Use this to make side effects idempotent when the workflow is retried. */ id: string; /** * Durable key-value state shared by every run of this workflow. * * State is exposed on the step context because every operation participates * in the active step's durability and replay boundary. */ state: WorkflowState; /** * Aborts when the workflow run is asked to stop, for example because it was * cancelled or reached its time limit, or when this attempt exceeds the * step's `timeoutMs`. Pass it to `fetch` and other abortable APIs, or call * `signal.throwIfAborted()` between units of work. Each attempt gets a new * signal. * * Cancellation is cooperative: the step keeps running until the callback * observes the signal. A step that throws because of the signal fails and * is not saved, so it runs again if the workflow is retried. A timed-out * attempt that ignores the signal can keep running alongside the retry. */ signal: AbortSignal; }; type WorkflowStepResult = T extends void ? null : T; /** Options for a workflow step. */ export type WorkflowStepOptions = { /** * A stable key used to identify this step across attempts of a workflow run. * Pass a string for a single key segment or an array of strings for a composite * key. Defaults to the step name. Changing the key creates a new logical step. */ key?: string | string[]; /** * Maximum duration of one invocation of the step callback. When it elapses, * the attempt fails and its `signal` is aborted. */ timeoutMs?: number; /** Retry policy for failures from this step callback. */ retry?: WorkflowRetryPolicy; }; type WorkflowStep = { (name: string, fn: (context: StepContext) => T | Promise): Promise>; (name: string, options: WorkflowStepOptions, fn: (context: StepContext) => T | Promise): Promise>; }; type WorkflowWait = { /** * Suspend the workflow until a relative duration has elapsed or an absolute * date has been reached. */ until(name: string, options: WorkflowWaitUntilOptions): Promise; /** * Suspend the workflow until a person submits values matching the input * schema. The returned value is restored from the durable workflow * checkpoint when the request is fulfilled. */ forInput(name: string, options: WorkflowWaitForInputOptions): Promise>; /** * Suspend the workflow until an event of the specified type occurs after the * wait starts. Resolves with the event. If the timeout elapses first, it * throws a `FatalError` and the run fails without retrying. */ forEvent>(name: string, options: WorkflowWaitForEventOptions): Promise>; }; /** Options for invoking another workflow from a workflow. */ export type WorkflowInvokeOptions = { /** * A stable key for this invocation within the calling run. Defaults to the * target workflow's key. Pass one when invoking the same workflow more than * once, such as in a loop. Changing the key starts a new run. */ key?: string | string[]; }; /** The input a workflow accepts through its manual trigger. */ export type WorkflowInvokeInput = TWorkflow extends Workflow ? ManualWorkflowInput> : never; type ManualWorkflowInput = [TTrigger] extends [never] ? never : TTrigger extends ManualWorkflowTrigger ? TInput : never; /** A handle to a workflow run started with `start()`. */ export type WorkflowRunHandle = { /** The ID of the started run. */ runId: string; }; /** Invoke other workflows in the same app. The target must have a manual trigger. */ export type WorkflowInvocations = { /** * Start another workflow and continue without waiting for it. Resolves once * the run is created. The started run's success or failure does not affect * this workflow. * * Starting is durable: a retry or replay of this workflow returns the same * run instead of starting another. Called directly from the handler, the * start is its own step. Called inside `context.step()`, it is part of that * step, and retries of that step return the same run. */ start(workflow: TWorkflow, input: WorkflowInvokeInput, options?: WorkflowInvokeOptions): Promise; }; /** Context passed to a workflow handler. */ export type WorkflowContext = CapabilityContext & { /** Resolved NaC bindings for this invocation; UI-granted resources are not added here. */ readonly access: WorkflowAccess; connections: WorkflowConnections; } & RunMetadata & { /** * Run an observable unit of work within the workflow. * * The callback may be synchronous or asynchronous, but `step` always returns * a promise. Its return value must be JSON-serializable so it can be included * in the success event written to stdout. A callback that returns `undefined` * is normalized to `null`, which keeps first runs and saved replays consistent. * * The step name is used to derive its stable replay key by default. Step key * inputs must be unique within a workflow invocation. Pass an explicit string * or array of strings when display names are duplicated or may change. * * @example * ```ts * const page = await context.step("Fetch page", async ({ id }) => { * console.log(`Running step ${id}`); * return context.notion.pages.retrieve({ page_id: pageId }); * }); * ``` */ step: WorkflowStep; /** Suspend this workflow without consuming compute resources. */ wait: WorkflowWait; /** * Aborts when the workflow run is asked to stop, for example because it was * cancelled or reached its time limit. Pass it to `fetch` and other * abortable APIs, or call `signal.throwIfAborted()` between units of work, * inside or outside a step. * * Cancellation is cooperative: code keeps running until it observes the * signal. A step that throws because of the signal fails and is not saved, * so it runs again if the workflow is retried. New steps do not start once * the signal has aborted. */ readonly signal: AbortSignal; /** Trigger declared syncs or apply event-driven sync changes. */ sync: WorkflowSyncClient; /** Invoke other workflows in the same app. */ workflows: WorkflowInvocations; }; //# sourceMappingURL=workflow.d.ts.map