/** * Build the storage operations that keep the workflow visibility indexes in * sync with workflow state writes. Called from every state-mutation chokepoint * (start, status transition, attribute/tag updates, termination cleanup). * * The runtime path mirrors the backfill path: read the per-workflow manifest * to learn which index keys are currently occupied, delete them, write the * new index keys derived from `nextState`, and persist the new manifest. When * `nextState` is `null` the workflow is being removed and we simply drop the * manifest and every key it lists. * * @module core/engine/workflow-indexes */ import { type BatchOperation, type Storage } from '../../storage/interface.ts'; import type { WorkflowState } from '../types.ts'; import { WeftError } from '../weft-error.ts'; import type { EngineInternals } from './internals.ts'; /** * Bumped whenever the index layout or population rules change. The engine * compares this to the watermark stored at `wf-idx-meta:version` to decide * whether the indexes are trustworthy for query-time use. */ export declare const WORKFLOW_VISIBILITY_INDEX_VERSION = 1; /** * Hard cap on the number of candidate workflow ids the engine will * materialize for a single `list` or `aggregate` query. Exceeding the cap * raises a {@link WorkflowListScanCapExceededError} — the operator should * narrow the filter or run the visibility-index backfill so a narrower * scan applies. */ export declare const MAX_LIST_SCAN_ROWS = 1000000; /** * Thrown when `list`/`aggregate` would materialize more candidates than * {@link MAX_LIST_SCAN_ROWS} allows. Transport layers map this to an * `Unprocessable` fault. */ export declare class WorkflowListScanCapExceededError extends WeftError<'WorkflowListScanCapExceededError'> { readonly cap: number; constructor(cap: number); } /** * Per-workflow manifest payload — the exact set of visibility-index keys * this workflow occupies right now. Decoded from the * `wf-idx-manifest:{id}` storage entry. */ export type WorkflowVisibilityManifest = { /** Schema version that wrote this manifest. */ version: number; /** Sorted list of index keys this workflow currently owns. */ keys: string[]; }; /** * Compute the visibility-index keys a workflow should occupy given its * current state. Returns a deterministic, sorted list so manifests compare * stably across runs. */ export declare function deriveWorkflowVisibilityIndexKeys(state: WorkflowState): string[]; /** * Decode a manifest payload read from storage. Returns `null` for missing or * malformed values so callers fall back to the "treat as empty" path — the * derived diff still produces the right set of inserts. */ export declare function decodeWorkflowVisibilityManifest(bytes: Uint8Array | null): WorkflowVisibilityManifest | null; /** * Result of {@link buildWorkflowVisibilityIndexOperations}. `nextManifestKeys` * is `null` when the workflow is being removed (delete manifest + all keys). */ export type WorkflowVisibilityIndexUpdate = { batchOps: BatchOperation[]; nextManifestKeys: string[] | null; }; /** * Build the batch operations required to transition a workflow's visibility * indexes from `currentManifest` (whatever the storage currently records) to * the keys derived from `nextState`. Pass `nextState = null` to drop the * workflow's index footprint entirely. * * The caller is responsible for appending the returned `batchOps` to its own * write batch so storage commits the index update atomically with the state * write it accompanies. */ export declare function buildWorkflowVisibilityIndexOperations(workflowId: string, currentManifest: WorkflowVisibilityManifest | null, nextState: WorkflowState | null): WorkflowVisibilityIndexUpdate; /** * Variant of {@link buildWorkflowVisibilityIndexOperations} that derives the * previous index keys directly from `previousState` instead of from a stored * manifest. Use this on every runtime state-transition write — it avoids an * extra storage round-trip per write and is always correct because the index * is a deterministic function of state. * * The manifest-based variant remains the right choice for backfill, which * may need to delete rows produced by a different schema version. */ export declare function buildWorkflowVisibilityIndexTransition(workflowId: string, previousState: WorkflowState | null, nextState: WorkflowState | null): WorkflowVisibilityIndexUpdate; /** * Watermark recorded by the backfill once every workflow has a manifest at * the current schema version. `engine.list()` and `engine.aggregate()` only * consult the new `wf-idx-*` rows when the watermark is `current`. */ export type WorkflowVisibilityWatermark = 'current' | 'stale'; export declare const WORKFLOW_VISIBILITY_WATERMARK_CACHE_TTL_MS = 1000; /** * Read the visibility-index watermark. Returns `'current'` when the * persisted version is at or above {@link WORKFLOW_VISIBILITY_INDEX_VERSION}. */ export declare function getWorkflowVisibilityWatermark(storage: Storage): Promise; /** * Read the visibility-index watermark through the engine-local query cache. * The short freshness window avoids repeated metadata reads during bursts of * list/aggregate queries without pinning an external backfill or drop decision * until process restart. */ export declare function getCachedWorkflowVisibilityWatermark(internals: EngineInternals): Promise;