@theokit/sdk 2.26.0 → 2.27.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.
package/dist/index.d.cts CHANGED
@@ -1998,6 +1998,19 @@ interface StepContext {
1998
1998
  };
1999
1999
  /** Pause the workflow; resume via `Workflow.resume({...})`. */
2000
2000
  readonly suspend: (payload?: unknown) => Promise<never>;
2001
+ /**
2002
+ * SE29 — the workflow's shared state (from `WorkflowOptions.initialState`,
2003
+ * mutated by {@link setState}), visible to every subsequent step in the run.
2004
+ * `undefined` when no `initialState`/`setState` has run. Persisted across
2005
+ * suspend/resume.
2006
+ */
2007
+ readonly state: unknown;
2008
+ /**
2009
+ * SE29 — update the shared state for subsequent steps. Validated against
2010
+ * `WorkflowOptions.stateSchema` when set (a mismatch throws
2011
+ * {@link WorkflowStateError}, which fails the step/run — Rule 8).
2012
+ */
2013
+ readonly setState: (next: unknown) => void;
2001
2014
  }
2002
2015
  interface StepResult {
2003
2016
  readonly stepId: string;
package/dist/index.d.ts CHANGED
@@ -1998,6 +1998,19 @@ interface StepContext {
1998
1998
  };
1999
1999
  /** Pause the workflow; resume via `Workflow.resume({...})`. */
2000
2000
  readonly suspend: (payload?: unknown) => Promise<never>;
2001
+ /**
2002
+ * SE29 — the workflow's shared state (from `WorkflowOptions.initialState`,
2003
+ * mutated by {@link setState}), visible to every subsequent step in the run.
2004
+ * `undefined` when no `initialState`/`setState` has run. Persisted across
2005
+ * suspend/resume.
2006
+ */
2007
+ readonly state: unknown;
2008
+ /**
2009
+ * SE29 — update the shared state for subsequent steps. Validated against
2010
+ * `WorkflowOptions.stateSchema` when set (a mismatch throws
2011
+ * {@link WorkflowStateError}, which fails the step/run — Rule 8).
2012
+ */
2013
+ readonly setState: (next: unknown) => void;
2001
2014
  }
2002
2015
  interface StepResult {
2003
2016
  readonly stepId: string;
package/dist/index.js CHANGED
@@ -4333,7 +4333,7 @@ var init_batch = __esm({
4333
4333
  });
4334
4334
 
4335
4335
  // src/types/workflow.ts
4336
- var WorkflowDuplicateStepIdError, WorkflowAlreadyRunningError, WorkflowSnapshotNotFoundError, WorkflowMaxIterationsExceededError, WorkflowNotSerializableError, WorkflowResumeStepNotFoundError, WorkflowParallelError, WorkflowCompensateNotImplementedError;
4336
+ var WorkflowDuplicateStepIdError, WorkflowInputError, WorkflowOutputError, WorkflowStateError, WorkflowAlreadyRunningError, WorkflowSnapshotNotFoundError, WorkflowMaxIterationsExceededError, WorkflowNotSerializableError, WorkflowResumeStepNotFoundError, WorkflowParallelError, WorkflowCompensateNotImplementedError;
4337
4337
  var init_workflow = __esm({
4338
4338
  "src/types/workflow.ts"() {
4339
4339
  WorkflowDuplicateStepIdError = class extends Error {
@@ -4344,6 +4344,36 @@ var init_workflow = __esm({
4344
4344
  stepId;
4345
4345
  name = "WorkflowDuplicateStepIdError";
4346
4346
  };
4347
+ WorkflowInputError = class extends Error {
4348
+ constructor(workflowName, detail) {
4349
+ super(`Workflow "${workflowName}" input failed schema validation: ${detail}`);
4350
+ this.workflowName = workflowName;
4351
+ this.detail = detail;
4352
+ }
4353
+ workflowName;
4354
+ detail;
4355
+ name = "WorkflowInputError";
4356
+ };
4357
+ WorkflowOutputError = class extends Error {
4358
+ constructor(workflowName, detail) {
4359
+ super(`Workflow "${workflowName}" output failed schema validation: ${detail}`);
4360
+ this.workflowName = workflowName;
4361
+ this.detail = detail;
4362
+ }
4363
+ workflowName;
4364
+ detail;
4365
+ name = "WorkflowOutputError";
4366
+ };
4367
+ WorkflowStateError = class extends Error {
4368
+ constructor(workflowName, detail) {
4369
+ super(`Workflow "${workflowName}" state failed schema validation: ${detail}`);
4370
+ this.workflowName = workflowName;
4371
+ this.detail = detail;
4372
+ }
4373
+ workflowName;
4374
+ detail;
4375
+ name = "WorkflowStateError";
4376
+ };
4347
4377
  WorkflowAlreadyRunningError = class extends Error {
4348
4378
  constructor(workflowName, runId) {
4349
4379
  super(`Workflow "${workflowName}" run "${runId}" already in-flight.`);
@@ -4536,7 +4566,7 @@ var init_snapshot_store = __esm({
4536
4566
  });
4537
4567
 
4538
4568
  // src/internal/workflow/ctx.ts
4539
- function makeStepContext(runId, signal) {
4569
+ function makeStepContext(runId, signal, state4) {
4540
4570
  return {
4541
4571
  runId,
4542
4572
  signal,
@@ -4547,7 +4577,13 @@ function makeStepContext(runId, signal) {
4547
4577
  },
4548
4578
  suspend: async (payload) => {
4549
4579
  throw new WorkflowSuspendedSentinel(payload);
4550
- }
4580
+ },
4581
+ // SE29 — read reflects the current shared state; write goes through the
4582
+ // controller (which validates against `stateSchema`).
4583
+ get state() {
4584
+ return state4.getState();
4585
+ },
4586
+ setState: (next) => state4.setState(next)
4551
4587
  };
4552
4588
  }
4553
4589
  function emit(level, runId, msg, attrs) {
@@ -4600,6 +4636,57 @@ var init_error_shape = __esm({
4600
4636
  }
4601
4637
  });
4602
4638
 
4639
+ // src/internal/workflow/executor-helpers.ts
4640
+ function assembleRun(params) {
4641
+ return {
4642
+ id: params.runId,
4643
+ name: params.name,
4644
+ status: params.status,
4645
+ startedAt: params.startedAt,
4646
+ endedAt: Date.now(),
4647
+ stepResults: params.stepResults,
4648
+ ...params.output !== void 0 ? { output: params.output } : {},
4649
+ ...params.error !== void 0 ? { error: params.error } : {}
4650
+ };
4651
+ }
4652
+ function abortRun(name, runId, startedAt, stepResults, signal) {
4653
+ return assembleRun({
4654
+ runId,
4655
+ name,
4656
+ status: "cancelled",
4657
+ stepResults,
4658
+ startedAt,
4659
+ error: { name: "AbortError", message: String(signal.reason ?? "Aborted") }
4660
+ });
4661
+ }
4662
+ function validateWorkflowSchema(schema, value) {
4663
+ if (schema === void 0) return void 0;
4664
+ let parsed;
4665
+ try {
4666
+ parsed = schema.safeParse(value);
4667
+ } catch {
4668
+ return "schema uses async refinements, which whole-workflow validation does not support (use a synchronous Zod schema)";
4669
+ }
4670
+ if (parsed.success) return void 0;
4671
+ return parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
4672
+ }
4673
+ function makeStateController(options, initial) {
4674
+ let current = initial;
4675
+ return {
4676
+ getState: () => current,
4677
+ setState: (next) => {
4678
+ const issues = validateWorkflowSchema(options.stateSchema, next);
4679
+ if (issues !== void 0) throw new WorkflowStateError(options.name, issues);
4680
+ current = next;
4681
+ }
4682
+ };
4683
+ }
4684
+ var init_executor_helpers = __esm({
4685
+ "src/internal/workflow/executor-helpers.ts"() {
4686
+ init_workflow();
4687
+ }
4688
+ });
4689
+
4603
4690
  // src/internal/workflow/run-id.ts
4604
4691
  function mintRunId() {
4605
4692
  return `wfr-${globalThis.crypto.randomUUID().replace(/-/g, "").slice(0, 8)}`;
@@ -5266,6 +5353,8 @@ async function handleSuspend(err, ctx) {
5266
5353
  suspendedPayload: err.payload,
5267
5354
  stepResults: ctx.stepResults,
5268
5355
  accumulatedInput: ctx.acc,
5356
+ state: ctx.stepCtx.state,
5357
+ // SE29 — capture the shared state at suspend
5269
5358
  options: ctx.options
5270
5359
  });
5271
5360
  } catch (snapErr) {
@@ -5317,7 +5406,7 @@ async function runOneStep(args) {
5317
5406
  result = await dispatchStep(args.step, args.acc, args.ctx, args.options, args.stepResults);
5318
5407
  } catch (err) {
5319
5408
  if (err instanceof WorkflowSuspendedSentinel) {
5320
- const outcome = await handleSuspend(err, { ...args, stepSpan });
5409
+ const outcome = await handleSuspend(err, { ...args, stepSpan, stepCtx: args.ctx });
5321
5410
  return { kind: "terminal", run: outcome.run };
5322
5411
  }
5323
5412
  result = {
@@ -5334,22 +5423,42 @@ async function runOneStep(args) {
5334
5423
  stepSpan.end();
5335
5424
  return { kind: "ok", result };
5336
5425
  }
5337
- function abortRun(name, runId, startedAt, stepResults, signal) {
5338
- return assembleRun({
5339
- runId,
5340
- name,
5341
- status: "cancelled",
5342
- stepResults,
5343
- startedAt,
5344
- error: { name: "AbortError", message: String(signal.reason ?? "Aborted") }
5345
- });
5426
+ function handleStepOutcome(outcome, step, loop, onStepEvent) {
5427
+ if (outcome.kind === "terminal") {
5428
+ if (outcome.run.status === "suspended") {
5429
+ onStepEvent?.({ type: "workflow_suspended", stepId: step.id });
5430
+ }
5431
+ return { terminal: outcome.run };
5432
+ }
5433
+ loop.stepResults.push(outcome.result);
5434
+ if (outcome.result.status === "failed") {
5435
+ onStepEvent?.({
5436
+ type: "step_failed",
5437
+ stepId: step.id,
5438
+ error: outcome.result.error ?? { name: "WorkflowStepError", message: "step failed" }
5439
+ });
5440
+ return {
5441
+ terminal: assembleRun({
5442
+ runId: loop.runId,
5443
+ name: loop.name,
5444
+ status: "failed",
5445
+ stepResults: loop.stepResults,
5446
+ startedAt: loop.startedAt,
5447
+ error: outcome.result.error
5448
+ })
5449
+ };
5450
+ }
5451
+ onStepEvent?.({ type: "step_completed", stepId: step.id, output: outcome.result.output });
5452
+ return { acc: outcome.result.output };
5346
5453
  }
5347
5454
  async function runStepsLoop(params) {
5348
- const { options, steps, ctx, runId, startedAt, signal } = params;
5455
+ const { options, steps, ctx, runId, startedAt, signal, onStepEvent } = params;
5349
5456
  const stepResults = [...params.initialStepResults ?? []];
5457
+ const loop = { stepResults, runId, name: options.name, startedAt };
5350
5458
  let acc = params.input;
5351
5459
  for (const step of steps) {
5352
5460
  if (signal.aborted) return abortRun(options.name, runId, startedAt, stepResults, signal);
5461
+ onStepEvent?.({ type: "step_started", stepId: step.id });
5353
5462
  const outcome = await runOneStep({
5354
5463
  step,
5355
5464
  acc,
@@ -5360,20 +5469,22 @@ async function runStepsLoop(params) {
5360
5469
  name: options.name,
5361
5470
  startedAt
5362
5471
  });
5363
- if (outcome.kind === "terminal") return outcome.run;
5364
- stepResults.push(outcome.result);
5365
- if (outcome.result.status === "failed") {
5366
- return assembleRun({
5367
- runId,
5368
- name: options.name,
5369
- status: "failed",
5370
- stepResults,
5371
- startedAt,
5372
- error: outcome.result.error
5373
- });
5374
- }
5375
- acc = outcome.result.output;
5472
+ const handled = handleStepOutcome(outcome, step, loop, onStepEvent);
5473
+ if ("terminal" in handled) return handled.terminal;
5474
+ acc = handled.acc;
5475
+ }
5476
+ const outputIssues = validateWorkflowSchema(options.outputSchema, acc);
5477
+ if (outputIssues !== void 0) {
5478
+ return assembleRun({
5479
+ runId,
5480
+ name: options.name,
5481
+ status: "failed",
5482
+ stepResults,
5483
+ startedAt,
5484
+ error: errToShape(new WorkflowOutputError(options.name, outputIssues))
5485
+ });
5376
5486
  }
5487
+ onStepEvent?.({ type: "workflow_completed" });
5377
5488
  return assembleRun({
5378
5489
  runId,
5379
5490
  name: options.name,
@@ -5396,8 +5507,33 @@ async function executeWorkflow(options, steps, input, runOpts) {
5396
5507
  runSpan.end();
5397
5508
  return abortRun(options.name, runId, startedAt, [], signal);
5398
5509
  }
5399
- const ctx = makeStepContext(runId, signal);
5510
+ const internal = runOpts;
5511
+ const seededState = internal?.restoredState !== void 0 ? internal.restoredState : options.initialState;
5512
+ const stateController = makeStateController(options, seededState);
5513
+ const ctx = makeStepContext(runId, signal, stateController);
5400
5514
  try {
5515
+ const inputIssues = validateWorkflowSchema(options.inputSchema, input);
5516
+ if (inputIssues !== void 0) {
5517
+ return assembleRun({
5518
+ runId,
5519
+ name: options.name,
5520
+ status: "failed",
5521
+ stepResults: [],
5522
+ startedAt,
5523
+ error: errToShape(new WorkflowInputError(options.name, inputIssues))
5524
+ });
5525
+ }
5526
+ const stateIssues = seededState !== void 0 ? validateWorkflowSchema(options.stateSchema, seededState) : void 0;
5527
+ if (stateIssues !== void 0) {
5528
+ return assembleRun({
5529
+ runId,
5530
+ name: options.name,
5531
+ status: "failed",
5532
+ stepResults: [],
5533
+ startedAt,
5534
+ error: errToShape(new WorkflowStateError(options.name, stateIssues))
5535
+ });
5536
+ }
5401
5537
  return await runStepsLoop({
5402
5538
  options,
5403
5539
  steps,
@@ -5406,7 +5542,8 @@ async function executeWorkflow(options, steps, input, runOpts) {
5406
5542
  runId,
5407
5543
  startedAt,
5408
5544
  signal,
5409
- ...runOpts?.initialStepResults !== void 0 ? { initialStepResults: runOpts.initialStepResults } : {}
5545
+ ...internal?.initialStepResults !== void 0 ? { initialStepResults: internal.initialStepResults } : {},
5546
+ ...internal?.onStepEvent !== void 0 ? { onStepEvent: internal.onStepEvent } : {}
5410
5547
  });
5411
5548
  } finally {
5412
5549
  runSpan.end();
@@ -5437,28 +5574,17 @@ async function dispatchStep(step, input, ctx, options, prevStepResults) {
5437
5574
  }
5438
5575
  }
5439
5576
  }
5440
- function assembleRun(params) {
5441
- const endedAt = Date.now();
5442
- return {
5443
- id: params.runId,
5444
- name: params.name,
5445
- status: params.status,
5446
- startedAt: params.startedAt,
5447
- endedAt,
5448
- stepResults: params.stepResults,
5449
- ...params.output !== void 0 ? { output: params.output } : {},
5450
- ...params.error !== void 0 ? { error: params.error } : {}
5451
- };
5452
- }
5453
5577
  async function saveSnapshot(p) {
5454
5578
  const snapshot = {
5455
- _schemaVersion: 1,
5579
+ _schemaVersion: 2,
5580
+ // SE29 — carries `state`
5456
5581
  runId: p.runId,
5457
5582
  workflowName: p.workflowName,
5458
5583
  currentStepId: p.currentStepId,
5459
5584
  suspendedPayload: p.suspendedPayload,
5460
5585
  stepResults: p.stepResults,
5461
5586
  accumulatedInput: p.accumulatedInput,
5587
+ ...p.state !== void 0 ? { state: p.state } : {},
5462
5588
  suspendedAt: Date.now()
5463
5589
  };
5464
5590
  const store = getSnapshotStoreFor(p.options);
@@ -5491,7 +5617,10 @@ async function resumeWorkflow(opts) {
5491
5617
  signal: opts.signal,
5492
5618
  runId: opts.runId,
5493
5619
  // M3 #62 — restore prior step outputs so the resumed run is not lossy (internal seam).
5494
- initialStepResults: snapshot.stepResults
5620
+ initialStepResults: snapshot.stepResults,
5621
+ // SE29 — restore shared state (v2 snapshot). A v1 snapshot has no `state` →
5622
+ // executeWorkflow falls back to `options.initialState`.
5623
+ ...snapshot.state !== void 0 ? { restoredState: snapshot.state } : {}
5495
5624
  });
5496
5625
  }
5497
5626
  var init_executor = __esm({
@@ -5499,6 +5628,7 @@ var init_executor = __esm({
5499
5628
  init_workflow();
5500
5629
  init_ctx();
5501
5630
  init_error_shape();
5631
+ init_executor_helpers();
5502
5632
  init_run_id();
5503
5633
  init_single_flight();
5504
5634
  init_snapshot_store();
@@ -21028,6 +21158,45 @@ var PersistenceSchema = z.object({
21028
21158
 
21029
21159
  // src/workflow.ts
21030
21160
  init_path_guard();
21161
+
21162
+ // src/internal/workflow/event-stream.ts
21163
+ function createEventStream() {
21164
+ const buffer = [];
21165
+ const waiters = [];
21166
+ let ended = false;
21167
+ const stream = {
21168
+ push(event) {
21169
+ if (ended) return;
21170
+ const waiter = waiters.shift();
21171
+ if (waiter !== void 0) waiter({ value: event, done: false });
21172
+ else buffer.push(event);
21173
+ },
21174
+ end() {
21175
+ if (ended) return;
21176
+ ended = true;
21177
+ for (const waiter of waiters.splice(0)) waiter({ value: void 0, done: true });
21178
+ },
21179
+ next() {
21180
+ const buffered = buffer.shift();
21181
+ if (buffered !== void 0) return Promise.resolve({ value: buffered, done: false });
21182
+ if (ended) return Promise.resolve({ value: void 0, done: true });
21183
+ return new Promise((resolve3) => waiters.push(resolve3));
21184
+ },
21185
+ // `for await` calls return() on break/throw — close early so events stop
21186
+ // buffering in memory for a consumer that stopped iterating.
21187
+ return() {
21188
+ stream.end();
21189
+ buffer.length = 0;
21190
+ return Promise.resolve({ value: void 0, done: true });
21191
+ },
21192
+ [Symbol.asyncIterator]() {
21193
+ return stream;
21194
+ }
21195
+ };
21196
+ return stream;
21197
+ }
21198
+
21199
+ // src/workflow.ts
21031
21200
  init_workflow();
21032
21201
  var RetryPolicySchema = z.object({
21033
21202
  // EC-3 absorbed: maxAttempts MUST be a finite int in [1, 20].
@@ -21039,7 +21208,12 @@ var RetryPolicySchema = z.object({
21039
21208
  });
21040
21209
  var WorkflowOptionsSchema = z.object({
21041
21210
  name: z.string().min(1).max(128),
21042
- persistence: PersistenceSchema
21211
+ persistence: PersistenceSchema,
21212
+ // SE27 — declared so a future `new WorkflowBuilder(parsed)` refactor cannot
21213
+ // silently drop them (create() passes the ORIGINAL options today, but the
21214
+ // schema is also the documentation of the shape).
21215
+ inputSchema: z.custom().optional(),
21216
+ outputSchema: z.custom().optional()
21043
21217
  });
21044
21218
  var WorkflowBuilder = class {
21045
21219
  /** @internal */
@@ -21216,6 +21390,36 @@ var Workflow = class {
21216
21390
  }
21217
21391
  return result;
21218
21392
  }
21393
+ /**
21394
+ * SE28 — run the workflow and STREAM step-level events as they happen. Returns
21395
+ * an async iterator of {@link WorkflowEvent}s (`step_started` / `step_completed`
21396
+ * / `step_failed` / `workflow_suspended` / `workflow_completed`, top-level
21397
+ * steps) plus a `result` promise resolving to the same terminal
21398
+ * {@link WorkflowRun} `run()` returns. Iterate for progress; await `result` for
21399
+ * the outcome. The stream ends when the run terminates.
21400
+ *
21401
+ * `result` is the AUTHORITATIVE terminal status. Not every terminal state has a
21402
+ * closing event: a step failure emits `step_failed`, but an `outputSchema`
21403
+ * rejection (SE27) or an abort ends the stream WITHOUT `workflow_completed` —
21404
+ * always `await result` to read the final `status`. Consuming order is free:
21405
+ * awaiting `result` without draining, or draining without awaiting `result`,
21406
+ * both work (breaking out of `for await` stops the buffering early).
21407
+ */
21408
+ stream(input, opts) {
21409
+ const queue = createEventStream();
21410
+ const result = (async () => {
21411
+ const { executeWorkflow: executeWorkflow2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
21412
+ try {
21413
+ return await executeWorkflow2(this._options, this._steps, input, {
21414
+ ...opts,
21415
+ onStepEvent: (event) => queue.push(event)
21416
+ });
21417
+ } finally {
21418
+ queue.end();
21419
+ }
21420
+ })();
21421
+ return Object.assign(queue, { result });
21422
+ }
21219
21423
  /**
21220
21424
  * Resume a suspended workflow from its snapshot. Throws
21221
21425
  * `WorkflowSnapshotNotFoundError` if `runId` is unknown.