@lostgradient/weft 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/help-text.d.ts +3 -3
  3. package/dist/cli/help-text.js +4 -4
  4. package/dist/cli/operation-catalog-snapshot.d.ts +1 -1
  5. package/dist/cli/schedule.js +1 -3
  6. package/dist/cli/serve-registrations.d.ts +3 -3
  7. package/dist/cli/serve-registrations.js +2 -2
  8. package/dist/cli/shutdown.d.ts +11 -0
  9. package/dist/cli/shutdown.js +25 -0
  10. package/dist/cli/validate.d.ts +1 -1
  11. package/dist/cli/version-check.d.ts +1 -1
  12. package/dist/cli/version-check.js +1 -1
  13. package/dist/cli-main.js +10 -29
  14. package/dist/client/http-client-storage.js +7 -35
  15. package/dist/client/http-request.d.ts +8 -9
  16. package/dist/client/http-request.js +3 -25
  17. package/dist/core/context/parallel-operations.d.ts +0 -2
  18. package/dist/core/context/parallel-operations.js +0 -7
  19. package/dist/core/engine/callback-checkpoint-persistence.d.ts +2 -0
  20. package/dist/core/engine/callback-checkpoint-persistence.js +6 -3
  21. package/dist/core/engine/callback-creators.js +4 -22
  22. package/dist/core/engine/checkpoint-io.d.ts +1 -1
  23. package/dist/core/engine/operations-coordination.js +1 -1
  24. package/dist/core/engine/parallel-dispatch.d.ts +1 -1
  25. package/dist/core/engine/parallel-dispatch.js +1 -1
  26. package/dist/core/engine/review-list-entries.d.ts +1 -1
  27. package/dist/core/engine/review-list-entries.js +6 -30
  28. package/dist/core/engine/timeline-coordinator-detail.d.ts +1 -1
  29. package/dist/core/review/index.d.ts +87 -0
  30. package/dist/core/review/index.js +29 -0
  31. package/dist/diagnostics/validate.d.ts +9 -30
  32. package/dist/diagnostics/validate.js +83 -21
  33. package/dist/diagnostics/version-check.d.ts +2 -2
  34. package/dist/http.js +2 -2
  35. package/dist/server/openapi-error-responses.js +0 -14
  36. package/dist/server/operation-catalog/index.d.ts +1 -1
  37. package/dist/server/operation-catalog/raise-fault.d.ts +2 -1
  38. package/dist/server/operation-catalog/types.d.ts +2 -1
  39. package/dist/server/operation-fault.d.ts +1 -4
  40. package/dist/server/operation-fault.js +1 -1
  41. package/dist/server/operation-registry.d.ts +1 -1
  42. package/dist/server/operations/list-reviews.js +2 -19
  43. package/dist/server/operations/recover-all.js +1 -10
  44. package/dist/server/operations/single-workflow-control-operation.d.ts +2 -1
  45. package/dist/storage/bounded-ndjson-response.d.ts +5 -0
  46. package/dist/storage/bounded-ndjson-response.js +33 -0
  47. package/dist/storage/http.js +5 -37
  48. package/dist/storage/neon.d.ts +3 -16
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Weft
2
2
 
3
- A Bun-native durable execution engine. Current release: `0.13.0`.
3
+ A Bun-native durable execution engine. Current release: `0.14.0`.
4
4
 
5
5
  Install the library from npm as `@lostgradient/weft`:
6
6
 
@@ -1,11 +1,11 @@
1
- export declare const HELP_TEXT = "\nweft - Bun-native durable execution engine\n\nUsage: weft [command] [options]\n\nCommands:\n serve Start the Weft server (default)\n doctor Run diagnostics on the Weft database\n conformance Run RemoteWorker protocol conformance checks\n schedule Manage recurring schedules\n timeline Inspect workflow timeline and replay history\n version:check Check workflow version compatibility\n validate Lint workflow registrations for design-time anti-patterns\n codegen Generate TypeScript declarations from a registry snapshot\n api Inspect and invoke catalog operations on a running server\n server Inspect a running server's health and operation surface\n workflow List, inspect, start, signal, and cancel workflows on a server\n tail Stream a workflow's events as Server-Sent Events\n completions Generate or install shell completion scripts\n version Print the installed Weft version\n\nVersion (leading token only; a subcommand owns its own option line):\n weft version | weft --version | weft -v Print the installed Weft version\n\nServe Options:\n -p, --port <port> Server port (default: 7233)\n -d, --database <path> Database file path (default: ./weft.db)\n -s, --storage <backend> Storage backend: sqlite, lmdb, memory (default: sqlite)\n -w, --workflows <path> Path to workflow module to register on startup\n -h, --help Show this help message\n";
1
+ export declare const HELP_TEXT = "\nweft - Bun-native durable execution engine\n\nUsage: weft [command] [options]\n\nCommands:\n serve Start the Weft server (default)\n doctor Run diagnostics on the Weft database\n conformance Run RemoteWorker protocol conformance checks\n schedule Manage recurring schedules\n timeline Inspect workflow timeline and replay history\n version:check Check workflow version compatibility\n validate Lint workflow definitions for design-time anti-patterns\n codegen Generate TypeScript declarations from a registry snapshot\n api Inspect and invoke catalog operations on a running server\n server Inspect a running server's health and operation surface\n workflow List, inspect, start, signal, and cancel workflows on a server\n tail Stream a workflow's events as Server-Sent Events\n completions Generate or install shell completion scripts\n version Print the installed Weft version\n\nVersion (leading token only; a subcommand owns its own option line):\n weft version | weft --version | weft -v Print the installed Weft version\n\nServe Options:\n -p, --port <port> Server port (default: 7233)\n -d, --database <path> Database file path (default: ./weft.db)\n -s, --storage <backend> Storage backend: sqlite, lmdb, memory (default: sqlite)\n -w, --workflows <path> Path to workflow module to register on startup\n -h, --help Show this help message\n";
2
2
  export declare const API_HELP_TEXT = "\nweft api - Inspect and invoke catalog operations on a running server\n\nUsage:\n weft api --list [options]\n weft api --describe <operation-name> [options]\n weft api <operation-name> [--input <json> | --input-file <path|->] [options]\n\nOptions:\n --server <url> Server URL (default: WEFT_ADDR, profile, run lockfile, or localhost)\n --token <token> Bearer token (default: WEFT_TOKEN)\n --profile <name> Profile from ~/.weft/config (default: WEFT_PROFILE or default_profile)\n --input <json> JSON object input for the operation\n --input-file <path> Read JSON object input from a file, or '-' for stdin\n --list List catalog operations\n --describe <name> Print one operation's schema and metadata\n --yes Confirm destructive operations without prompting\n -j, --json Emit machine-readable JSON output\n -h, --help Show this help message\n\nExit codes:\n 0 Success\n 1 Operation failed or destructive operation was not confirmed\n 2 Connection error\n 3 Usage or input validation error\n";
3
3
  export declare const CONFORMANCE_HELP_TEXT = "\nweft conformance - Run RemoteWorker protocol conformance checks\n\nUsage: weft conformance [options] -- <worker-command> [args...]\n\nThe worker command receives:\n WEFT_WORKER_URL\n WEFT_WORKER_QUEUE\n WEFT_WORKER_ACTIVITIES\n WEFT_WORKER_PROTOCOL_VERSION\n\nOptions:\n --timeout <ms> Per-check timeout in milliseconds (default: 15000)\n -j, --json Output results as JSON\n -h, --help Show this help message\n";
4
4
  export declare const DOCTOR_HELP_TEXT = "\nweft doctor - Run diagnostics on the Weft database\n\nUsage: weft doctor [options]\n\nOptions:\n -d, --database <path> Database file path (default: ./weft.db)\n -j, --json Output results as JSON\n -h, --help Show this help message\n";
5
5
  export declare const VERSION_CHECK_HELP_TEXT = "\nweft version:check - Check workflow version compatibility\n\nUsage: weft version:check [options]\n\nOptions:\n -d, --database <path> Database file path (default: ./weft.db)\n -w, --workflows <path> Path to workflows module\n -j, --json Output results as JSON\n -h, --help Show this help message\n";
6
6
  export declare const TIMELINE_HELP_TEXT = "\nweft timeline - Inspect workflow timeline and replay history\n\nUsage:\n weft timeline <workflowId> [options]\n weft timeline <workflowId> --diff <fromStep> <toStep> [options]\n\nOptions:\n -d, --database <path> Database file path (default: ./weft.db)\n --step <step> Show replay details for one checkpoint step\n --diff Diff two checkpoint steps (requires two positional step numbers)\n -h, --help Show this help message\n";
7
- export declare const SCHEDULE_HELP_TEXT = "\nweft schedule - Manage recurring schedules\n\nUsage:\n weft schedule list [options]\n weft schedule create <workflowType> <cronExpression> [options]\n weft schedule create <workflowType> --every <duration> [options]\n weft schedule pause <scheduleId> [options]\n weft schedule resume <scheduleId> [options]\n weft schedule cancel <scheduleId> [options]\n\nProvide a cron expression positional OR an --every interval (e.g. \"30s\", \"1h\"),\nbut not both. Interval schedules fire one period after creation, then every\nperiod after that.\n\nOptions:\n -d, --database <path> Database file path (default: ./weft.db)\n -s, --storage <backend> Storage backend: sqlite, lmdb (default: sqlite)\n -w, --workflows <path> Path to workflow registrations module (required for create)\n --every <duration> Interval cadence for create (e.g. 30s, 5m, 1h); mutually exclusive with the cron positional\n --input <json> JSON input payload for create (default: null)\n --id <id> Custom schedule id for create\n --overlap <policy> Overlap policy: skip, queue, cancel-running, allow\n --backfill Run missed ticks on recovery\n --jitter <duration> Deterministic dispatch jitter for create (e.g. 30s, 5m)\n -j, --json Output results as JSON\n -h, --help Show this help message\n";
8
- export declare const VALIDATE_HELP_TEXT = "\nweft validate - Lint workflow registrations for design-time anti-patterns\n\nUsage: weft validate <entry.ts>... [options]\n\nArguments:\n <entry.ts>... One or more TypeScript modules or glob patterns that\n resolve to workflow registrations and/or activity\n definitions.\n\nOptions:\n -j, --json Output results as JSON\n -h, --help Show this help message\n\nExit codes:\n 0 No errors (warnings may be present)\n 1 One or more errors detected\n 2 Entry file could not be loaded (takes precedence over validation errors)\n\nJSON output:\n { entries, valid, hasLoadErrors, hasValidationErrors }\n\nChecks performed:\n unbounded-retry Activity retry.maxAttempts is Infinity\n stateful-without-compensator Non-idempotent activity has no compensate fn\n";
7
+ export declare const SCHEDULE_HELP_TEXT = "\nweft schedule - Manage recurring schedules\n\nUsage:\n weft schedule list [options]\n weft schedule create <workflowType> <cronExpression> [options]\n weft schedule create <workflowType> --every <duration> [options]\n weft schedule pause <scheduleId> [options]\n weft schedule resume <scheduleId> [options]\n weft schedule cancel <scheduleId> [options]\n\nProvide a cron expression positional OR an --every interval (e.g. \"30s\", \"1h\"),\nbut not both. Interval schedules fire one period after creation, then every\nperiod after that.\n\nOptions:\n -d, --database <path> Database file path (default: ./weft.db)\n -s, --storage <backend> Storage backend: sqlite, lmdb (default: sqlite)\n -w, --workflows <path> Path to workflow definitions module (required for create)\n --every <duration> Interval cadence for create (e.g. 30s, 5m, 1h); mutually exclusive with the cron positional\n --input <json> JSON input payload for create (default: null)\n --id <id> Custom schedule id for create\n --overlap <policy> Overlap policy: skip, queue, cancel-running, allow\n --backfill Run missed ticks on recovery\n --jitter <duration> Deterministic dispatch jitter for create (e.g. 30s, 5m)\n -j, --json Output results as JSON\n -h, --help Show this help message\n";
8
+ export declare const VALIDATE_HELP_TEXT = "\nweft validate - Lint workflow definitions for design-time anti-patterns\n\nUsage: weft validate <entry.ts>... [options]\n\nArguments:\n <entry.ts>... One or more TypeScript modules or glob patterns that\n resolve to workflow definitions and/or activity\n definitions.\n\nOptions:\n -j, --json Output results as JSON\n -h, --help Show this help message\n\nExit codes:\n 0 No errors (warnings may be present)\n 1 One or more errors detected\n 2 Entry file could not be loaded (takes precedence over validation errors)\n\nJSON output:\n { entries, valid, hasLoadErrors, hasValidationErrors }\n\nChecks performed:\n unbounded-retry Activity retry.maxAttempts is Infinity\n stateful-without-compensator Non-idempotent activity has no compensate fn\n";
9
9
  export declare const SERVER_HELP_TEXT = "\nweft server - Inspect a running server's health and operation surface\n\nUsage:\n weft server health [options]\n weft server info [options]\n\nOptions:\n --server <url> Server URL (default: WEFT_ADDR, profile, run lockfile, or localhost)\n --token <token> Bearer token (default: WEFT_TOKEN)\n --profile <name> Profile from ~/.weft/config\n --wait (health) Poll until the server is reachable\n --wait-timeout <ms> (health) Maximum time to wait, in milliseconds (default: 30000)\n -j, --json Emit machine-readable JSON output\n -q, --quiet Suppress success/error text (use the exit code)\n -h, --help Show this help message\n\nExit codes:\n 0 Healthy\n 1 Unreachable / unhealthy\n 2 Connection error\n";
10
10
  export declare const WORKFLOW_HELP_TEXT = "\nweft workflow - List, inspect, start, signal, and cancel workflows on a server\n\nUsage:\n weft workflow ls [--type <type>] [--status <status>] [--limit <n>] [options]\n weft workflow get <workflow-id> [options]\n weft workflow events <workflow-id> [options]\n weft workflow start <workflow-type> [--input <json> | --input-file <path|->] [--id <id>] [options]\n weft workflow cancel <workflow-id> [--yes] [--dry-run] [options]\n weft workflow signal <workflow-id> <signal-name> [--input <json> | --input-file <path|->] [options]\n\nOptions:\n --server <url> Server URL (default: WEFT_ADDR, profile, run lockfile, or localhost)\n --token <token> Bearer token (default: WEFT_TOKEN)\n --profile <name> Profile from ~/.weft/config\n --type <type> (ls) Filter by workflow type\n --status <status> (ls) Filter by workflow status\n --limit <n> (ls) Maximum number of rows\n --input <json> (start/signal) JSON input payload\n --input-file <path> (start/signal) Read JSON input from a file, or '-' for stdin\n --id <id> (start) Explicit workflow id\n -y, --yes (cancel) Confirm without prompting\n --dry-run (cancel) Print affected count without cancelling\n -j, --json Emit machine-readable output (NDJSON for lists)\n -q, --quiet Print ids only / suppress success text\n -h, --help Show this help message\n\nExit codes:\n 0 Success\n 1 Operation failed or destructive operation was not confirmed\n 2 Connection error\n 3 Usage or input error\n 4 Operation unavailable on this server (version skew)\n";
11
11
  export declare const TAIL_HELP_TEXT = "\nweft tail - Stream a workflow's events as Server-Sent Events\n\nUsage:\n weft tail <workflow-id> [options]\n\nOptions:\n --server <url> Server URL (default: WEFT_ADDR, profile, run lockfile, or localhost)\n --token <token> Bearer token (default: WEFT_TOKEN)\n --profile <name> Profile from ~/.weft/config\n -j, --json Emit one JSON object per line (NDJSON)\n -q, --quiet Do not echo events to stdout\n -h, --help Show this help message\n\nPress Ctrl-C to stop tailing; the stream ends cleanly with exit code 0.\n";
@@ -10,7 +10,7 @@ Commands:
10
10
  schedule Manage recurring schedules
11
11
  timeline Inspect workflow timeline and replay history
12
12
  version:check Check workflow version compatibility
13
- validate Lint workflow registrations for design-time anti-patterns
13
+ validate Lint workflow definitions for design-time anti-patterns
14
14
  codegen Generate TypeScript declarations from a registry snapshot
15
15
  api Inspect and invoke catalog operations on a running server
16
16
  server Inspect a running server's health and operation surface
@@ -117,7 +117,7 @@ period after that.
117
117
  Options:
118
118
  -d, --database <path> Database file path (default: ./weft.db)
119
119
  -s, --storage <backend> Storage backend: sqlite, lmdb (default: sqlite)
120
- -w, --workflows <path> Path to workflow registrations module (required for create)
120
+ -w, --workflows <path> Path to workflow definitions module (required for create)
121
121
  --every <duration> Interval cadence for create (e.g. 30s, 5m, 1h); mutually exclusive with the cron positional
122
122
  --input <json> JSON input payload for create (default: null)
123
123
  --id <id> Custom schedule id for create
@@ -127,13 +127,13 @@ Options:
127
127
  -j, --json Output results as JSON
128
128
  -h, --help Show this help message
129
129
  `, VALIDATE_HELP_TEXT = `
130
- weft validate - Lint workflow registrations for design-time anti-patterns
130
+ weft validate - Lint workflow definitions for design-time anti-patterns
131
131
 
132
132
  Usage: weft validate <entry.ts>... [options]
133
133
 
134
134
  Arguments:
135
135
  <entry.ts>... One or more TypeScript modules or glob patterns that
136
- resolve to workflow registrations and/or activity
136
+ resolve to workflow definitions and/or activity
137
137
  definitions.
138
138
 
139
139
  Options:
@@ -1,5 +1,5 @@
1
+ import type { FaultCode } from '../core/fault-code.ts';
1
2
  import type { ErasedOperation, OperationKind, TransportAvailability } from '../server/operation-catalog.ts';
2
- import type { FaultCode } from '../server/operation-fault.ts';
3
3
  import type { ParamSource, ResponseShape } from '../server/rest-binding.ts';
4
4
  export type CatalogRestBindingSnapshot = {
5
5
  readonly method: string;
@@ -51,10 +51,8 @@ function parseScheduleInput(input) {
51
51
  }
52
52
  async function registerScheduleWorkflows(engine, workflowsPath, loadRegistrationsFromModule) {
53
53
  const loaded = await loadRegistrationsFromModule(workflowsPath);
54
- for (const [workflowType, registration] of Object.entries(loaded.registrations)) {
55
- const definition = { ...registration, name: workflowType };
54
+ for (const definition of Object.values(loaded.registrations))
56
55
  registerOnRuntimeEngine(runtimeWorkflowEngine(engine), definition);
57
- }
58
56
  }
59
57
  async function executeScheduleCreate(options, engine, loadRegistrationsFromModule) {
60
58
  const validationError = getScheduleCreateValidationError(options);
@@ -1,6 +1,6 @@
1
1
  import type { ActivityDefinition } from '../core/activity.ts';
2
2
  import type { Engine } from '../core/engine.ts';
3
- import type { WorkflowRegistration } from '../diagnostics/validate.ts';
3
+ import type { WorkflowDefinition } from '../core/types.ts';
4
4
  /**
5
5
  * Convert a plain-object `ActivityDefinition` into a callable function that
6
6
  * satisfies the engine's `isActivityDefinition` check (which requires a
@@ -8,8 +8,8 @@ import type { WorkflowRegistration } from '../diagnostics/validate.ts';
8
8
  */
9
9
  export declare function toActivityCallable(definition: ActivityDefinition): (...args: unknown[]) => unknown;
10
10
  /**
11
- * Register a map of workflow registrations and a list of activity definitions
11
+ * Register a map of workflow definitions and a list of activity definitions
12
12
  * on an engine instance. Plain-object activities are normalized to callables
13
13
  * before registration.
14
14
  */
15
- export declare function registerModuleExports(engine: Engine, registrations: Record<string, WorkflowRegistration>, activities: ActivityDefinition[]): void;
15
+ export declare function registerModuleExports(engine: Engine, registrations: Record<string, WorkflowDefinition>, activities: ActivityDefinition[]): void;
@@ -13,8 +13,8 @@ export function toActivityCallable(definition) {
13
13
  }
14
14
  export function registerModuleExports(engine, registrations, activities) {
15
15
  const runtime = runtimeWorkflowEngine(engine);
16
- for (const [workflowType, registration] of Object.entries(registrations))
17
- registerOnRuntimeEngine(runtime, { ...registration, name: workflowType });
16
+ for (const definition of Object.values(registrations))
17
+ registerOnRuntimeEngine(runtime, definition);
18
18
  for (const activity of activities)
19
19
  if (typeof activity === "function")
20
20
  engine.register(activity);
@@ -0,0 +1,11 @@
1
+ export declare const CLI_SHUTDOWN_SIGNALS: readonly ["SIGINT", "SIGTERM"];
2
+ export type CliShutdownSignal = (typeof CLI_SHUTDOWN_SIGNALS)[number];
3
+ export type CliShutdownDependencies = {
4
+ readonly stopServer: () => Promise<void>;
5
+ readonly removeRunLockfile: () => Promise<void>;
6
+ readonly disposeStorage: () => void;
7
+ readonly log: (message: string) => void;
8
+ readonly reportError: (message: string, error: unknown) => void;
9
+ readonly exit: (code: 0 | 1) => void;
10
+ };
11
+ export declare function createCliShutdownHandler(dependencies: CliShutdownDependencies): (signal: CliShutdownSignal) => Promise<void>;
@@ -0,0 +1,25 @@
1
+ export const CLI_SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
2
+ async function runCliShutdown(signal, dependencies) {
3
+ dependencies.log(`
4
+ Received ${signal}; shutting down...`);
5
+ try {
6
+ await dependencies.stopServer();
7
+ try {
8
+ await dependencies.removeRunLockfile();
9
+ } catch (error) {
10
+ dependencies.reportError("[weft] Failed to remove run lockfile:", error);
11
+ }
12
+ dependencies.disposeStorage();
13
+ dependencies.exit(0);
14
+ } catch (error) {
15
+ dependencies.reportError("[weft] Shutdown error:", error);
16
+ dependencies.exit(1);
17
+ }
18
+ }
19
+ export function createCliShutdownHandler(dependencies) {
20
+ let shutdownPromise;
21
+ return (signal) => {
22
+ shutdownPromise ??= runCliShutdown(signal, dependencies);
23
+ return shutdownPromise;
24
+ };
25
+ }
@@ -1,5 +1,5 @@
1
1
  import type { CommandOutput } from './types.ts';
2
- /** Validates workflow registration modules and reports load or design-time errors. */
2
+ /** Validates workflow definition modules and reports load or design-time errors. */
3
3
  export declare function executeValidate(options: {
4
4
  entryPaths: string[];
5
5
  json: boolean;
@@ -1,5 +1,5 @@
1
1
  import type { CommandOutput } from './types.ts';
2
- /** Checks stored workflow history against the current workflow registrations. */
2
+ /** Checks stored workflow history against the current workflow definitions. */
3
3
  export declare function executeVersionCheck(options: {
4
4
  database: string;
5
5
  workflows: string;
@@ -8,7 +8,7 @@ export async function executeVersionCheck(options) {
8
8
  };
9
9
  const { runVersionCheck } = await import("../diagnostics/version-check.js"), { formatVersionCheckReport } = await import("../diagnostics/format.js"), { BunSQLiteStorage } = await import("../storage/bun-sql.js"), storage = new BunSQLiteStorage(options.database);
10
10
  try {
11
- const registrations = await import(resolve(process.cwd(), options.workflows)), report = await runVersionCheck(storage, registrations.default);
11
+ const workflowsPath = resolve(process.cwd(), options.workflows), { loadRegistrationsFromModule } = await import("../diagnostics/validate.js"), { registrations } = await loadRegistrationsFromModule(workflowsPath), report = await runVersionCheck(storage, registrations);
12
12
  return { stdout: options.json ? JSON.stringify(report, null, 2) : formatVersionCheckReport(report), exitCode: 0 };
13
13
  } finally {
14
14
  storage[Symbol.dispose]();
package/dist/cli-main.js CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  WORKFLOW_HELP_TEXT,
33
33
  writeRunLockfile
34
34
  } from "./cli/index.js";
35
+ import { CLI_SHUTDOWN_SIGNALS, createCliShutdownHandler } from "./cli/shutdown.js";
35
36
  import { Engine } from "./core/engine.js";
36
37
  import { serve } from "./server/index.js";
37
38
  const parsedArguments = (() => {
@@ -67,36 +68,16 @@ if (parsedArguments.command === "version") {
67
68
  console.log(`Health check: ${new URL("/v1/health", server.url).href}`);
68
69
  console.log(`Storage: ${parsedArguments.storage}`);
69
70
  console.log(`Database: ${parsedArguments.database}`);
70
- process.on("SIGINT", () => {
71
- console.log(`
72
- Shutting down...`);
73
- server.stop().then(async () => {
74
- try {
75
- await removeRunLockfile(server.url);
76
- } catch (error) {
77
- console.error("[weft] Failed to remove run lockfile:", error);
78
- }
79
- storage[Symbol.dispose]();
80
- process.exit(0);
81
- }).catch((error) => {
82
- console.error("[weft] Shutdown error:", error);
83
- process.exit(1);
84
- });
85
- });
86
- process.on("SIGTERM", () => {
87
- server.stop().then(async () => {
88
- try {
89
- await removeRunLockfile(server.url);
90
- } catch (error) {
91
- console.error("[weft] Failed to remove run lockfile:", error);
92
- }
93
- storage[Symbol.dispose]();
94
- process.exit(0);
95
- }).catch((error) => {
96
- console.error("[weft] Shutdown error:", error);
97
- process.exit(1);
98
- });
71
+ const shutdown = createCliShutdownHandler({
72
+ stopServer: () => server.stop(),
73
+ removeRunLockfile: () => removeRunLockfile(server.url),
74
+ disposeStorage: () => storage[Symbol.dispose](),
75
+ log: (message) => console.log(message),
76
+ reportError: (message, error) => console.error(message, error),
77
+ exit: (code) => process.exit(code)
99
78
  });
79
+ for (const signal of CLI_SHUTDOWN_SIGNALS)
80
+ process.on(signal, () => void shutdown(signal));
100
81
  } else if (parsedArguments.command === "doctor") {
101
82
  if (parsedArguments.help) {
102
83
  console.log(DOCTOR_HELP_TEXT);
@@ -1,3 +1,4 @@
1
+ import { readBoundedNdjsonResponse } from "../storage/bounded-ndjson-response.js";
1
2
  import { decodeBase64ToBytes, encodeBytesToBase64, isRecord } from "../storage/byte-encoding.js";
2
3
  import {
3
4
  assertStorageBatchOperationCount
@@ -19,42 +20,13 @@ function scanEntry(value) {
19
20
  throw Error("HttpClient storage scan response contained an invalid NDJSON entry.");
20
21
  return [value.key, decodeBase64ToBytes(value.value)];
21
22
  }
22
- async function releaseScanReader(reader, reachedEndOfStream) {
23
- if (!reachedEndOfStream)
24
- try {
25
- await reader.cancel();
26
- } catch {}
27
- reader.releaseLock();
28
- }
29
23
  async function* scanResponse(response) {
30
- if (response.body === null)
31
- return;
32
- const reader = response.body.getReader(), decoder = new TextDecoder;
33
- let bufferedText = "", bytesRead = 0, reachedEndOfStream = !1;
34
- try {
35
- while (!0) {
36
- const { done, value } = await reader.read();
37
- if (done) {
38
- reachedEndOfStream = !0;
39
- break;
40
- }
41
- bytesRead += value.byteLength;
42
- if (bytesRead > MAX_SCAN_RESPONSE_BYTES)
43
- throw Error("HttpClient storage scan response exceeded the maximum allowed size.");
44
- bufferedText += decoder.decode(value, { stream: !0 });
45
- const lines = bufferedText.split(`
46
- `);
47
- bufferedText = lines.pop() ?? "";
48
- for (const line of lines)
49
- if (line.trim().length > 0)
50
- yield scanEntry(JSON.parse(line));
51
- }
52
- bufferedText += decoder.decode();
53
- if (bufferedText.trim().length > 0)
54
- yield scanEntry(JSON.parse(bufferedText));
55
- } finally {
56
- await releaseScanReader(reader, reachedEndOfStream);
57
- }
24
+ for await (const line of readBoundedNdjsonResponse(response, {
25
+ maximumBytes: MAX_SCAN_RESPONSE_BYTES,
26
+ sizeLimitError: () => Error("HttpClient storage scan response exceeded the maximum allowed size.")
27
+ }))
28
+ if (line.trim().length > 0)
29
+ yield scanEntry(JSON.parse(line));
58
30
  }
59
31
  export function createHttpClientStorage(baseUrl, headers) {
60
32
  return {
@@ -63,7 +63,7 @@ export declare function resolveHttpClientConnection(options: HttpClientOptions):
63
63
  /**
64
64
  * Error thrown when the server returns a non-2xx response.
65
65
  *
66
- * When the server sends a fault body with a recognized coarse `code`, the wire
66
+ * When a transport response carries a recognized coarse fault code, the wire
67
67
  * value is surfaced as {@link HttpClientError.faultCode} with a derived
68
68
  * {@link HttpClientError.category}, so callers can branch without
69
69
  * string-matching `message`. Both are `undefined` for the production flat REST
@@ -96,8 +96,8 @@ export declare class HttpClientError extends WeftError<'HttpClientError'> {
96
96
  /** HTTP status code of the failed response. */
97
97
  readonly status: number;
98
98
  /**
99
- * The server's stable wire fault code, when the response carried a structured
100
- * fault body. `undefined` for plain-string error bodies or unrecognized codes.
99
+ * The server's stable wire fault code, when the transport response carried a
100
+ * recognized code. `undefined` for flat REST error bodies or unrecognized codes.
101
101
  */
102
102
  readonly faultCode?: FaultCode | undefined;
103
103
  /**
@@ -107,18 +107,17 @@ export declare class HttpClientError extends WeftError<'HttpClientError'> {
107
107
  readonly category?: FailureCategory | undefined;
108
108
  /**
109
109
  * The fine-grained originating public {@link WeftErrorCode} the server
110
- * attached (e.g. `WorkflowNotFoundError`), when the structured fault carried
111
- * one. This is what makes cross-transport branching work: a producer can call
110
+ * attached (e.g. `WorkflowNotFoundError`), when the response carried one.
111
+ * This is what makes cross-transport branching work: a producer can call
112
112
  * `isWeftFault(error, 'WorkflowNotFoundError')` and have it match over HTTP
113
113
  * exactly as it matches the in-process typed error. `undefined` when the fault
114
- * carried only the coarse {@link faultCode} (most faults) or no structured body.
114
+ * carried only the coarse {@link faultCode} (most faults) or no recognized code.
115
115
  */
116
116
  readonly weftCode?: WeftErrorCode | undefined;
117
117
  /**
118
118
  * The fault's wire `data` payload, when the response carried a structured
119
- * body (`{ error, data }` for production REST, nested `{ error: { data } }`
120
- * for structured REST responses, or `error.data` for JSON-RPC). Shape is
121
- * fault-code-dependent — see {@link FaultCode} and the server's
119
+ * body (`{ error, data }` for production REST or `error.data` for JSON-RPC).
120
+ * Shape is fault-code-dependent see {@link FaultCode} and the server's
122
121
  * `OperationFault` per-code `data` union for what each code carries (e.g.
123
122
  * `InvalidParams.data.issues`, `NotFound.data.resource`). `undefined` for
124
123
  * plain-string error bodies, bodies with no `data` field, or a `data` field
@@ -1,5 +1,5 @@
1
1
  import { resolveConnection } from "../connection.js";
2
- import { failureCategoryForFaultCode, isFaultCode } from "../core/fault-code.js";
2
+ import { failureCategoryForFaultCode } from "../core/fault-code.js";
3
3
  import { isWeftErrorCode, WeftError } from "../core/weft-error.js";
4
4
  export function resolveHttpClientConnection(options) {
5
5
  const connection = resolveConnection({
@@ -49,34 +49,12 @@ function parseFlatErrorBody(body) {
49
49
  ...data === void 0 ? {} : { data }
50
50
  };
51
51
  }
52
- function isStructuredErrorBody(value) {
53
- if (value === null || typeof value !== "object")
54
- return !1;
55
- const error = value.error;
56
- if (error === null || typeof error !== "object")
57
- return !1;
58
- return typeof error.message === "string";
59
- }
60
- function weftCodeFromData(data) {
61
- if (data === null || typeof data !== "object")
62
- return;
63
- const candidate = data.weftCode;
64
- return isWeftErrorCode(candidate) ? candidate : void 0;
65
- }
66
52
  function isRecord(value) {
67
53
  return typeof value === "object" && value !== null && !Array.isArray(value);
68
54
  }
69
55
  async function parseErrorBody(response) {
70
56
  try {
71
57
  const body = await response.json();
72
- if (isStructuredErrorBody(body)) {
73
- const { code, message, data } = body.error, weftCode = weftCodeFromData(data), base = { message };
74
- if (weftCode !== void 0)
75
- base.weftCode = weftCode;
76
- if (isRecord(data))
77
- base.data = data;
78
- return isFaultCode(code) ? { ...base, faultCode: code } : base;
79
- }
80
58
  if (isFlatErrorBody(body) && body.error)
81
59
  return parseFlatErrorBody(body);
82
60
  return { message: response.statusText };
@@ -97,8 +75,8 @@ export async function requestResponse(baseUrl, path, baseHeaders, options) {
97
75
  if (response.status === 404 && (!options?.method || options.method === "GET"))
98
76
  return null;
99
77
  if (!response.ok) {
100
- const { message, faultCode, weftCode, data } = await parseErrorBody(response);
101
- throw new HttpClientError(response.status, message, { faultCode, weftCode, data });
78
+ const { message, weftCode, data } = await parseErrorBody(response);
79
+ throw new HttpClientError(response.status, message, { weftCode, data });
102
80
  }
103
81
  return response;
104
82
  }
@@ -1,8 +1,6 @@
1
1
  import type { Context } from './index.ts';
2
2
  import type { ContextInternals } from './internals.ts';
3
3
  import type { ContextOperationRequest } from './operation-request.ts';
4
- import { assertValidParallelOperationCacheEntry, BranchTopologyChangedError, createParallelOperationCacheEntry, isParallelOperationCacheEntry, type ParallelBranchSlot, type ParallelOperationCacheEntry } from './parallel-cache-entry.ts';
5
- export { assertValidParallelOperationCacheEntry, BranchTopologyChangedError, createParallelOperationCacheEntry, isParallelOperationCacheEntry, type ParallelBranchSlot, type ParallelOperationCacheEntry, };
6
4
  export declare function all(context: Context, internals: ContextInternals, operations: Generator<ContextOperationRequest, unknown, unknown>[]): Generator<ContextOperationRequest, unknown[], unknown>;
7
5
  export declare function race(context: Context, internals: ContextInternals, operations: Generator<ContextOperationRequest, unknown, unknown>[], branchNames?: string[]): Generator<ContextOperationRequest, unknown, unknown>;
8
6
  export declare function memo<T>(context: Context, internals: ContextInternals, key: string, fn: () => T | Promise<T>): Generator<ContextOperationRequest, T, unknown>;
@@ -6,13 +6,6 @@ import {
6
6
  isParallelOperationCacheEntry
7
7
  } from "./parallel-cache-entry.js";
8
8
  import { captureCallerStack } from "./validation.js";
9
-
10
- export {
11
- assertValidParallelOperationCacheEntry,
12
- BranchTopologyChangedError,
13
- createParallelOperationCacheEntry,
14
- isParallelOperationCacheEntry
15
- };
16
9
  function reconstructAllResult(entry) {
17
10
  return entry.branches.map((slot) => {
18
11
  if (slot.status !== "fulfilled")
@@ -1,3 +1,5 @@
1
1
  import type { ContextOperationRequest } from '../context.ts';
2
+ import { type PersistCheckpointCallbacks } from './checkpoint-io.ts';
2
3
  import type { Engine } from './index.ts';
4
+ export declare function createCheckpointPersistenceCallbacks<TWorkflows extends object, TActivities extends object>(engine: Engine<TWorkflows, TActivities>): PersistCheckpointCallbacks;
3
5
  export declare function persistCheckpointForDataOperation<TWorkflows extends object, TActivities extends object>(engine: Engine<TWorkflows, TActivities>, workflowId: string, operation: ContextOperationRequest): Promise<void>;
@@ -9,8 +9,8 @@ import {
9
9
  import { getInternals } from "./internals.js";
10
10
  import { swallowPromiseRejection } from "./strategy-helpers.js";
11
11
  import { terminateWorkflow } from "./termination.js";
12
- export function persistCheckpointForDataOperation(engine, workflowId, operation) {
13
- return persistCheckpoint(getInternals(engine), workflowId, operation, void 0, {
12
+ export function createCheckpointPersistenceCallbacks(engine) {
13
+ return {
14
14
  appendTimelineBatchOperations: (id, checkpointOperation, step, timestamp, operations) => appendTimelineBatchOperations(getInternals(engine), id, checkpointOperation, step, timestamp, operations),
15
15
  swallowPromiseRejection: (promise) => {
16
16
  swallowPromiseRejection(promise);
@@ -21,5 +21,8 @@ export function persistCheckpointForDataOperation(engine, workflowId, operation)
21
21
  engine.dispatchEvent(event);
22
22
  },
23
23
  enforceHistoryCircuitBreaker: (id) => terminateWorkflow(getInternals(engine), id, "timed-out", createTerminationCallbacks(engine), HISTORY_CIRCUIT_BREAKER_REASON)
24
- }, { timeline: "preserve-pending" });
24
+ };
25
+ }
26
+ export function persistCheckpointForDataOperation(engine, workflowId, operation) {
27
+ return persistCheckpoint(getInternals(engine), workflowId, operation, void 0, createCheckpointPersistenceCallbacks(engine), { timeline: "preserve-pending" });
25
28
  }
@@ -1,6 +1,5 @@
1
1
  import { KEYS } from "../../storage/interface.js";
2
- import { HISTORY_CIRCUIT_BREAKER_REASON } from "../types.js";
3
- import { validateAttributeValueSizes } from "./attributes-tags.js";
2
+ import { createCheckpointPersistenceCallbacks } from "./callback-checkpoint-persistence.js";
4
3
  import {
5
4
  createConstraintCallbacks,
6
5
  createLifecycleCallbacks,
@@ -8,12 +7,7 @@ import {
8
7
  registerEnsureRetentionSweepInterval
9
8
  } from "./callback-creators-core.js";
10
9
  import { createOperationRouterCallbacks } from "./callback-creators-router.js";
11
- import {
12
- appendTimelineBatchOperations,
13
- persistCheckpoint,
14
- pruneCheckpointHistory,
15
- validateDevelopmentCheckpoint
16
- } from "./checkpoint-io.js";
10
+ import { persistCheckpoint, validateDevelopmentCheckpoint } from "./checkpoint-io.js";
17
11
  import { evaluateConstraints } from "./constraints.js";
18
12
  import {
19
13
  getParkedWorkflowResumeDisposition,
@@ -30,8 +24,7 @@ import {
30
24
  } from "./retention.js";
31
25
  import { hasBufferedSignal } from "./signals.js";
32
26
  import { loadWorkflowState, runSerializedWorkflowStateWrite } from "./storage-io.js";
33
- import { swallowPromiseRejection } from "./strategy-helpers.js";
34
- import { cleanupWaiters, terminateWorkflow } from "./termination.js";
27
+ import { cleanupWaiters } from "./termination.js";
35
28
  export { createUpdateCallbacks } from "./callback-creators-bundles.js";
36
29
  export {
37
30
  createBroadcastCallbacks,
@@ -64,18 +57,7 @@ export function createInlineParkingCallbacks(engine) {
64
57
  };
65
58
  }
66
59
  export function persistCheckpointForEngine(engine, workflowId, operation, workerCheckpointBytes) {
67
- return persistCheckpoint(getInternals(engine), workflowId, operation, workerCheckpointBytes, {
68
- appendTimelineBatchOperations: (id, checkpointOperation, step, timestamp, operations) => appendTimelineBatchOperations(getInternals(engine), id, checkpointOperation, step, timestamp, operations),
69
- swallowPromiseRejection: (promise) => {
70
- swallowPromiseRejection(promise);
71
- },
72
- validateAttributeValueSizes,
73
- pruneCheckpointHistory: (id, step) => pruneCheckpointHistory(getInternals(engine), id, step),
74
- dispatchEvent: (event) => {
75
- engine.dispatchEvent(event);
76
- },
77
- enforceHistoryCircuitBreaker: (id) => terminateWorkflow(getInternals(engine), id, "timed-out", createTerminationCallbacks(engine), HISTORY_CIRCUIT_BREAKER_REASON)
78
- });
60
+ return persistCheckpoint(getInternals(engine), workflowId, operation, workerCheckpointBytes, createCheckpointPersistenceCallbacks(engine));
79
61
  }
80
62
  function ensureRetentionSweepIntervalForEngine(engine) {
81
63
  ensureRetentionSweepInterval(getInternals(engine), {
@@ -9,7 +9,7 @@ type PendingTimelineEntryValue = {
9
9
  export type PersistCheckpointOptions = {
10
10
  timeline?: 'record-operation' | 'preserve-pending';
11
11
  };
12
- type PersistCheckpointCallbacks = {
12
+ export type PersistCheckpointCallbacks = {
13
13
  appendTimelineBatchOperations: (workflowId: string, operation: ContextOperationRequest, step: number, timestamp: number, operations: BatchOperation[]) => PendingTimelineEntryValue;
14
14
  swallowPromiseRejection: (promise: Promise<void>) => void;
15
15
  validateAttributeValueSizes: (attributes: Record<string, SearchAttributeValue>) => void;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isParallelOperationCacheEntry
3
- } from "../context/parallel-operations.js";
3
+ } from "../context/parallel-cache-entry.js";
4
4
  import {
5
5
  executeRunAllBranches,
6
6
  executeRunAllBranchesSettled
@@ -25,7 +25,7 @@
25
25
  * checkpoint-write machinery — see the `parallel-execution.md` guide
26
26
  * for the precise durability contract.
27
27
  */
28
- import type { ParallelBranchSlot, ParallelOperationCacheEntry } from '../context/parallel-operations.ts';
28
+ import type { ParallelBranchSlot, ParallelOperationCacheEntry } from '../context/parallel-cache-entry.ts';
29
29
  export type ParallelDispatchResult = {
30
30
  /** Final slot table — every entry is `fulfilled` or `rejected`. */
31
31
  slots: ParallelBranchSlot[];
@@ -1,4 +1,4 @@
1
- import { createParallelOperationCacheEntry } from "../context/parallel-operations.js";
1
+ import { createParallelOperationCacheEntry } from "../context/parallel-cache-entry.js";
2
2
  export function createFirstRejectionCapture() {
3
3
  return { hasFirstError: !1, firstError: void 0 };
4
4
  }
@@ -1,4 +1,4 @@
1
- import type { HumanReviewResult, ReviewRequest } from '../review/index.ts';
1
+ import { type HumanReviewResult, type ReviewRequest } from '../review/index.ts';
2
2
  import type { CompletedReviewEntry, PendingReviewEntry } from '../types.ts';
3
3
  export declare function toPendingReviewEntry(review: ReviewRequest): PendingReviewEntry;
4
4
  export declare function parseStoredReviewRequest(value: Uint8Array): ReviewRequest | null;
@@ -1,32 +1,8 @@
1
- import { z } from "zod";
2
1
  import { decode } from "../codec.js";
3
- const storedReviewRequestSchema = z.object({
4
- reviewId: z.string(),
5
- workflowId: z.string(),
6
- artifact: z.unknown().nonoptional(),
7
- reviewType: z.string(),
8
- reviewers: z.array(z.string()),
9
- allowPartial: z.boolean(),
10
- timeout: z.number().optional(),
11
- webhookUrl: z.string().optional(),
12
- createdAt: z.number()
13
- }), persistedCompletedReviewEntrySchema = z.object({
14
- status: z.literal("completed"),
15
- reviewId: z.string(),
16
- workflowId: z.string(),
17
- artifact: z.unknown().nonoptional(),
18
- reviewType: z.string(),
19
- reviewers: z.array(z.string()),
20
- allowPartial: z.boolean(),
21
- timeout: z.number().optional(),
22
- webhookUrl: z.string().optional(),
23
- createdAt: z.number(),
24
- decision: z.enum(["approved", "rejected", "needs-changes"]),
25
- reviewer: z.string(),
26
- feedback: z.string().optional(),
27
- sectionDecisions: z.record(z.string(), z.enum(["approved", "rejected"])).optional(),
28
- timestamp: z.number()
29
- });
2
+ import {
3
+ completedReviewEntrySchema,
4
+ reviewRequestSchema
5
+ } from "../review/index.js";
30
6
  function assignWhenDefined(target, key, value) {
31
7
  if (value !== void 0)
32
8
  target[key] = value;
@@ -58,7 +34,7 @@ export function parseStoredReviewRequest(value) {
58
34
  } catch {
59
35
  return null;
60
36
  }
61
- const parsedReview = storedReviewRequestSchema.safeParse(decodedValue);
37
+ const parsedReview = reviewRequestSchema.safeParse(decodedValue);
62
38
  if (!parsedReview.success)
63
39
  return null;
64
40
  return normalizeStoredReviewRequest(parsedReview.data);
@@ -110,7 +86,7 @@ export function parseCompletedReviewEntry(value) {
110
86
  } catch {
111
87
  return null;
112
88
  }
113
- const parsedReview = persistedCompletedReviewEntrySchema.safeParse(decodedValue);
89
+ const parsedReview = completedReviewEntrySchema.safeParse(decodedValue);
114
90
  if (!parsedReview.success)
115
91
  return null;
116
92
  return normalizeCompletedReviewEntry(parsedReview.data);
@@ -1,5 +1,5 @@
1
1
  import type { ContextOperationRequest } from '../context.ts';
2
- import type { ParallelBranchSlot } from '../context/parallel-operations.ts';
2
+ import type { ParallelBranchSlot } from '../context/parallel-cache-entry.ts';
3
3
  import type { WorkflowTimelineOperationDetail } from '../types.ts';
4
4
  import type { EngineInternals } from './internals.ts';
5
5
  export declare function operationTimelineDetail(operation: ContextOperationRequest, index: number, outcome: WorkflowTimelineOperationDetail['outcome'], options?: {