@lostgradient/weft 0.2.0 → 0.3.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 (207) hide show
  1. package/README.md +47 -22
  2. package/dist/cli/generated/operation-client.generated.d.ts +28 -1
  3. package/dist/cli/generated/operation-client.generated.js +2 -0
  4. package/dist/cli-main.js +79 -79
  5. package/dist/client/handle-delegation.d.ts +4 -0
  6. package/dist/client/handle-delegation.js +6 -0
  7. package/dist/client/http-client-requests.d.ts +2 -0
  8. package/dist/client/http-client-requests.js +3 -0
  9. package/dist/client/http-client.d.ts +4 -1
  10. package/dist/client/http-client.js +9 -1
  11. package/dist/client/interface.d.ts +57 -2
  12. package/dist/client/local.d.ts +4 -1
  13. package/dist/client/local.js +7 -0
  14. package/dist/client/start-body.d.ts +7 -1
  15. package/dist/client/start-body.js +13 -4
  16. package/dist/core/codec/extension-codec.js +4 -2
  17. package/dist/core/codec/index.d.ts +1 -0
  18. package/dist/core/codec/index.js +1 -0
  19. package/dist/core/codec/serializer-registry.d.ts +122 -0
  20. package/dist/core/codec/serializer-registry.js +51 -0
  21. package/dist/core/context/index.d.ts +10 -0
  22. package/dist/core/context/index.js +3 -0
  23. package/dist/core/context/internals.d.ts +10 -0
  24. package/dist/core/context/internals.js +5 -1
  25. package/dist/core/context/run-operation.d.ts +16 -3
  26. package/dist/core/context/run-operation.js +16 -7
  27. package/dist/core/context/speculative-child.js +2 -0
  28. package/dist/core/context/types.d.ts +6 -0
  29. package/dist/core/engine/bulk-operations-purge.js +1 -0
  30. package/dist/core/engine/bulk-operations.js +1 -1
  31. package/dist/core/engine/callback-creators-bundles.js +2 -1
  32. package/dist/core/engine/callback-creators-core.js +2 -1
  33. package/dist/core/engine/construction.d.ts +1 -1
  34. package/dist/core/engine/construction.js +15 -3
  35. package/dist/core/engine/disposal.js +12 -0
  36. package/dist/core/engine/engine-create-types.d.ts +0 -14
  37. package/dist/core/engine/engine-internal-types.d.ts +17 -0
  38. package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
  39. package/dist/core/engine/engine-leak-warnings.js +4 -0
  40. package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
  41. package/dist/core/engine/engine-runtime-helpers.js +26 -5
  42. package/dist/core/engine/errors.d.ts +74 -0
  43. package/dist/core/engine/errors.js +25 -1
  44. package/dist/core/engine/handle-result.js +1 -1
  45. package/dist/core/engine/handles.d.ts +89 -40
  46. package/dist/core/engine/handles.js +25 -27
  47. package/dist/core/engine/index.d.ts +122 -4
  48. package/dist/core/engine/index.js +82 -5
  49. package/dist/core/engine/inline-launch-queue.d.ts +14 -0
  50. package/dist/core/engine/inline-launch-queue.js +32 -7
  51. package/dist/core/engine/internals.d.ts +26 -10
  52. package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
  53. package/dist/core/engine/lifecycle/persist.js +5 -20
  54. package/dist/core/engine/lifecycle/recovered-services.d.ts +45 -0
  55. package/dist/core/engine/lifecycle/recovered-services.js +34 -0
  56. package/dist/core/engine/lifecycle/resume.js +33 -5
  57. package/dist/core/engine/lifecycle/shared.d.ts +8 -0
  58. package/dist/core/engine/lifecycle/start-batch.js +23 -12
  59. package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
  60. package/dist/core/engine/lifecycle/start-commit.js +27 -0
  61. package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
  62. package/dist/core/engine/lifecycle/start-exec.js +38 -0
  63. package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
  64. package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
  65. package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
  66. package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
  67. package/dist/core/engine/lifecycle/start.d.ts +3 -3
  68. package/dist/core/engine/lifecycle/start.js +42 -37
  69. package/dist/core/engine/lifecycle.d.ts +3 -2
  70. package/dist/core/engine/lifecycle.js +9 -2
  71. package/dist/core/engine/listing.js +1 -1
  72. package/dist/core/engine/operations-data.d.ts +16 -0
  73. package/dist/core/engine/operations-data.js +6 -0
  74. package/dist/core/engine/operations-time.d.ts +3 -2
  75. package/dist/core/engine/operations-time.js +6 -1
  76. package/dist/core/engine/persisted-data-version.d.ts +5 -9
  77. package/dist/core/engine/persisted-data-version.js +4 -5
  78. package/dist/core/engine/schedule-handle.d.ts +45 -0
  79. package/dist/core/engine/schedule-handle.js +26 -0
  80. package/dist/core/engine/schedules.d.ts +1 -1
  81. package/dist/core/engine/schedules.js +7 -3
  82. package/dist/core/engine/second-instance-detector.d.ts +96 -0
  83. package/dist/core/engine/second-instance-detector.js +108 -0
  84. package/dist/core/engine/signals.d.ts +22 -0
  85. package/dist/core/engine/signals.js +15 -0
  86. package/dist/core/engine/termination/cleanup.d.ts +25 -0
  87. package/dist/core/engine/termination/cleanup.js +21 -1
  88. package/dist/core/engine/termination/complete.js +4 -3
  89. package/dist/core/engine/termination/suspend.d.ts +68 -0
  90. package/dist/core/engine/termination/suspend.js +41 -0
  91. package/dist/core/engine/termination.d.ts +4 -2
  92. package/dist/core/engine/termination.js +2 -0
  93. package/dist/core/engine/validation.js +25 -1
  94. package/dist/core/engine/workflow-feed.d.ts +5 -3
  95. package/dist/core/events/event-map.d.ts +2 -1
  96. package/dist/core/events/workflow-events.d.ts +23 -0
  97. package/dist/core/events/workflow-events.js +9 -0
  98. package/dist/core/inline-execution-strategy.d.ts +5 -0
  99. package/dist/core/inline-execution-strategy.js +2 -1
  100. package/dist/core/list-filter-validation.js +2 -1
  101. package/dist/core/start-workflow-validation.d.ts +22 -0
  102. package/dist/core/start-workflow-validation.js +11 -1
  103. package/dist/core/step-context.d.ts +10 -6
  104. package/dist/core/step-context.js +7 -15
  105. package/dist/core/types/activity.d.ts +6 -3
  106. package/dist/core/types/identity.d.ts +8 -1
  107. package/dist/core/types/launch-metadata.d.ts +33 -0
  108. package/dist/core/types/launch-metadata.js +0 -0
  109. package/dist/core/types/message-handles.d.ts +25 -0
  110. package/dist/core/types/options.d.ts +90 -7
  111. package/dist/core/types/reviews.d.ts +2 -1
  112. package/dist/core/types/services-resolution.d.ts +47 -0
  113. package/dist/core/types/services-resolution.js +0 -0
  114. package/dist/core/types/state.d.ts +11 -11
  115. package/dist/core/types/workflow-builder.d.ts +5 -4
  116. package/dist/core/types/workflow-context.d.ts +25 -0
  117. package/dist/core/types/workflow-function.d.ts +17 -0
  118. package/dist/core/types/workflow-snapshot.d.ts +29 -0
  119. package/dist/core/types/workflow-snapshot.js +0 -0
  120. package/dist/core/types.d.ts +3 -0
  121. package/dist/core/types.js +3 -0
  122. package/dist/core/weft-error.d.ts +46 -14
  123. package/dist/core/weft-error.js +12 -1
  124. package/dist/diagnostics/doctor.js +6 -3
  125. package/dist/diagnostics/format.js +2 -2
  126. package/dist/diagnostics/types.d.ts +1 -0
  127. package/dist/diagnostics/version-check.js +6 -4
  128. package/dist/index.d.ts +10 -5
  129. package/dist/index.js +11 -2
  130. package/dist/json-schema.js +3 -3
  131. package/dist/mcp/cli.js +35 -35
  132. package/dist/mcp/list-filter.js +2 -1
  133. package/dist/mcp/session.js +1 -0
  134. package/dist/observability/index.js +2 -2
  135. package/dist/server/handler.js +30 -30
  136. package/dist/server/index.js +33 -33
  137. package/dist/server/interactive-operations.js +1 -0
  138. package/dist/server/operations/resume-workflow.js +2 -2
  139. package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
  140. package/dist/server/operations/start-or-signal-workflow.js +140 -0
  141. package/dist/server/operations/start-workflow-options.d.ts +32 -0
  142. package/dist/server/operations/start-workflow-options.js +63 -0
  143. package/dist/server/operations/start-workflow.js +7 -69
  144. package/dist/server/operations/suspend-workflow.d.ts +13 -0
  145. package/dist/server/operations/suspend-workflow.js +36 -0
  146. package/dist/server/rest-binding.d.ts +18 -7
  147. package/dist/server/rest-bindings.js +12 -0
  148. package/dist/server/runtime/task-dispatch.js +5 -3
  149. package/dist/server/runtime/task-polling.d.ts +16 -2
  150. package/dist/server/runtime/task-polling.js +20 -5
  151. package/dist/server/runtime/websocket-worker.js +8 -0
  152. package/dist/server/serve-internals.d.ts +8 -0
  153. package/dist/server/serve-internals.js +4 -2
  154. package/dist/server/task-state.d.ts +8 -0
  155. package/dist/service-worker/index.js +28 -28
  156. package/dist/storage/capabilities.d.ts +10 -2
  157. package/dist/storage/capabilities.js +2 -2
  158. package/dist/storage/http.js +2 -2
  159. package/dist/storage/index.d.ts +7 -1
  160. package/dist/storage/indexeddb.js +1 -1
  161. package/dist/storage/interface.d.ts +40 -0
  162. package/dist/storage/interface.js +1 -1
  163. package/dist/storage/key-prefixes.d.ts +1 -1
  164. package/dist/storage/key-prefixes.js +3 -0
  165. package/dist/storage/lmdb.js +1 -1
  166. package/dist/storage/memory.js +1 -1
  167. package/dist/storage/neon-value-mapping.d.ts +47 -0
  168. package/dist/storage/neon-value-mapping.js +11 -0
  169. package/dist/storage/neon.d.ts +108 -0
  170. package/dist/storage/neon.js +10 -0
  171. package/dist/storage/node-sqlite-loader.d.ts +71 -0
  172. package/dist/storage/node-sqlite-loader.js +41 -0
  173. package/dist/storage/node-sqlite.d.ts +1 -19
  174. package/dist/storage/node-sqlite.js +38 -32
  175. package/dist/storage/postgres-key-value-queries.d.ts +79 -0
  176. package/dist/storage/postgres-key-value-queries.js +63 -0
  177. package/dist/storage/resolve.d.ts +2 -165
  178. package/dist/storage/resolve.js +1 -1
  179. package/dist/storage/scoped-storage.js +1 -1
  180. package/dist/storage/storage-configuration.d.ts +209 -0
  181. package/dist/storage/storage-configuration.js +0 -0
  182. package/dist/storage/text-value-store.d.ts +13 -10
  183. package/dist/storage/turso.js +2 -2
  184. package/dist/storage/typed-storage.js +1 -1
  185. package/dist/storage/web-extension.js +1 -1
  186. package/dist/testing/event-loop.d.ts +36 -2
  187. package/dist/testing/index.d.ts +31 -1
  188. package/dist/testing/index.js +33 -33
  189. package/dist/version.d.ts +1 -1
  190. package/dist/version.js +1 -1
  191. package/dist/worker/index.js +9 -5
  192. package/dist/worker/long-poll.js +4 -0
  193. package/dist/worker/protocol-messages.d.ts +20 -0
  194. package/dist/worker/protocol-schemas.d.ts +32 -0
  195. package/dist/worker/protocol-schemas.js +8 -4
  196. package/dist/worker/protocol-task-result.d.ts +28 -0
  197. package/dist/worker/protocol-task-result.js +76 -0
  198. package/dist/worker/protocol.d.ts +4 -15
  199. package/dist/worker/protocol.js +1 -1
  200. package/dist/worker/registry/fair-share.d.ts +29 -0
  201. package/dist/worker/registry/fair-share.js +30 -0
  202. package/dist/worker/registry/routing.d.ts +18 -0
  203. package/dist/worker/registry/routing.js +14 -0
  204. package/dist/worker/registry/types.d.ts +7 -0
  205. package/dist/worker/registry.d.ts +16 -1
  206. package/dist/worker/registry.js +24 -36
  207. package/package.json +17 -4
@@ -4,6 +4,8 @@ import type { WorkflowEventTail } from './event-tail.ts';
4
4
  import type { ClientHandle, ClientScheduleHandle } from './interface.ts';
5
5
  export interface WorkflowHandleDelegationClient {
6
6
  cancel(id: string): Promise<void>;
7
+ suspend(id: string): Promise<void>;
8
+ resume(id: string): Promise<ClientHandle>;
7
9
  tail(id: string): WorkflowEventTail;
8
10
  signal(id: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
9
11
  update(id: string, name: string, payload?: unknown, options?: {
@@ -21,6 +23,8 @@ export declare abstract class WorkflowHandleDelegation<TClient extends WorkflowH
21
23
  constructor(id: string, client: TClient);
22
24
  abstract result(): Promise<unknown>;
23
25
  cancel(): Promise<void>;
26
+ suspend(): Promise<void>;
27
+ resume(): Promise<void>;
24
28
  signal(name: SignalDefinition): Promise<void>;
25
29
  signal<TInput>(name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
26
30
  signal(name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
@@ -10,6 +10,12 @@ export class WorkflowHandleDelegation {
10
10
  async cancel() {
11
11
  return this.client.cancel(this.id);
12
12
  }
13
+ async suspend() {
14
+ return this.client.suspend(this.id);
15
+ }
16
+ async resume() {
17
+ await this.client.resume(this.id);
18
+ }
13
19
  async signal(nameOrDefinition, payload, options) {
14
20
  if (options === void 0)
15
21
  return this.client.signal(this.id, messageName(nameOrDefinition), payload);
@@ -50,6 +50,8 @@ export declare function getScheduleRequest(context: HttpClientRequestContext, id
50
50
  export declare function cancelWorkflowRequest(context: HttpClientRequestContext, id: string): Promise<void>;
51
51
  /** Force-timeout a workflow (`POST /v1/workflows/:id/timeout`). */
52
52
  export declare function timeoutWorkflowRequest(context: HttpClientRequestContext, id: string): Promise<void>;
53
+ /** Suspend a running workflow (`POST /v1/workflows/:id/suspend`). */
54
+ export declare function suspendWorkflowRequest(context: HttpClientRequestContext, id: string): Promise<void>;
53
55
  /** Pause a recurring schedule (`POST /v1/schedules/:id/pause`). */
54
56
  export declare function pauseScheduleRequest(context: HttpClientRequestContext, id: string): Promise<void>;
55
57
  /** Resume a paused schedule (`POST /v1/schedules/:id/resume`). */
@@ -155,6 +155,9 @@ export function cancelWorkflowRequest(context, id) {
155
155
  export function timeoutWorkflowRequest(context, id) {
156
156
  return request(context.baseUrl, `/workflows/${encodeURIComponent(id)}/timeout`, context.headers, { method: "POST" });
157
157
  }
158
+ export function suspendWorkflowRequest(context, id) {
159
+ return request(context.baseUrl, `/workflows/${encodeURIComponent(id)}/suspend`, context.headers, { method: "POST" });
160
+ }
158
161
  export function pauseScheduleRequest(context, id) {
159
162
  return request(context.baseUrl, `/schedules/${encodeURIComponent(id)}/pause`, context.headers, { method: "POST" });
160
163
  }
@@ -1,6 +1,6 @@
1
1
  import { type CatalogOperationName, type CatalogOperationTypes, type WeftClient as CatalogOperations } from '../cli/generated/operation-client.generated.ts';
2
2
  import type { StoredStreamChunk } from '../core/context.ts';
3
- import type { AttributeFilterKey, BulkCancelResult, BulkDeleteResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PaginatedResult, PurgeResult, QueryDefinition, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, StartOptions, SubmitReviewOptions, TypedListFilter, UpdateDefinition, WorkflowEvent, WorkflowInput, WorkflowOutput, WorkflowRegistry, WorkflowReplay, WorkflowState, WorkflowSummary, WorkflowTimelineEntry } from '../core/types.ts';
3
+ import type { AttributeFilterKey, BulkCancelResult, BulkDeleteResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PaginatedResult, PurgeResult, QueryDefinition, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, StartOptions, StartOrSignalSignal, SubmitReviewOptions, TypedListFilter, UpdateDefinition, WorkflowEvent, WorkflowInput, WorkflowOutput, WorkflowRegistry, WorkflowReplay, WorkflowState, WorkflowSummary, WorkflowTimelineEntry } from '../core/types.ts';
4
4
  import { type WorkflowEventSubscription } from './event-stream.ts';
5
5
  import type { WorkflowEventTail } from './event-tail.ts';
6
6
  import { type HttpClientOptions } from './http-request.ts';
@@ -61,6 +61,8 @@ export declare class HttpClient implements WeftClient {
61
61
  call<Name extends CatalogOperationName>(name: Name, input: CatalogOperationTypes[Name]['input']): Promise<CatalogOperationTypes[Name]['output']>;
62
62
  start<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, options?: StartOptions): Promise<ClientHandle<WorkflowOutput<WorkflowRegistry, TName>>>;
63
63
  start<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, options?: StartOptions): Promise<ClientHandle>;
64
+ startOrSignal<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, signal: StartOrSignalSignal, options?: StartOptions): Promise<ClientHandle<WorkflowOutput<WorkflowRegistry, TName>>>;
65
+ startOrSignal<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, signal: StartOrSignalSignal, options?: StartOptions): Promise<ClientHandle>;
64
66
  schedule<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise<ClientScheduleHandle>;
65
67
  schedule<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise<ClientScheduleHandle>;
66
68
  get(id: string): Promise<WorkflowState | null>;
@@ -68,6 +70,7 @@ export declare class HttpClient implements WeftClient {
68
70
  list<const TAttributeKeys extends readonly AttributeFilterKey[] = readonly AttributeFilterKey[]>(filter?: TypedListFilter<TAttributeKeys>): Promise<PaginatedResult<WorkflowSummary>>;
69
71
  listSchedules(filter?: ScheduleFilter): Promise<PaginatedResult<ScheduleSummary>>;
70
72
  cancel(id: string): Promise<void>;
73
+ suspend(id: string): Promise<void>;
71
74
  pauseSchedule(id: string): Promise<void>;
72
75
  resumeSchedule(id: string): Promise<void>;
73
76
  cancelSchedule(id: string): Promise<void>;
@@ -31,6 +31,7 @@ import {
31
31
  signalWorkflowRequest,
32
32
  submitCoordinatedUpdateRequest,
33
33
  submitReviewRequest,
34
+ suspendWorkflowRequest,
34
35
  tagAllWorkflowRequests,
35
36
  timeoutWorkflowRequest,
36
37
  untagAllWorkflowRequests,
@@ -43,7 +44,7 @@ import { HttpScheduleHandle } from "./http-schedule-handle.js";
43
44
  import { openClientEventSubscription } from "./open-event-subscription.js";
44
45
  import { buildScheduleListSearchParams } from "./schedule-list-search-params.js";
45
46
  import { buildWorkflowListSearchParams } from "./search-params.js";
46
- import { buildStartBody, scheduleSpecToWireFields } from "./start-body.js";
47
+ import { buildStartBody, buildStartOrSignalBody, scheduleSpecToWireFields } from "./start-body.js";
47
48
 
48
49
  export class HttpClient {
49
50
  baseUrl;
@@ -72,6 +73,10 @@ export class HttpClient {
72
73
  });
73
74
  return new HttpHandle(response.id, this);
74
75
  }
76
+ async startOrSignal(type, input, signal, options) {
77
+ const body = buildStartOrSignalBody(type, input, signal, options), response = await request(this.baseUrl, "/workflows/start-or-signal", this.headers, { method: "POST", body: JSON.stringify(body) });
78
+ return new HttpHandle(response.id, this);
79
+ }
75
80
  async schedule(type, input, spec, options) {
76
81
  const body = {
77
82
  type,
@@ -107,6 +112,9 @@ export class HttpClient {
107
112
  async cancel(id) {
108
113
  return cancelWorkflowRequest(this, id);
109
114
  }
115
+ async suspend(id) {
116
+ return suspendWorkflowRequest(this, id);
117
+ }
110
118
  async pauseSchedule(id) {
111
119
  return pauseScheduleRequest(this, id);
112
120
  }
@@ -8,7 +8,7 @@
8
8
  import type { CatalogOperationName, CatalogOperationTypes, WeftClient as CatalogOperations } from '../cli/generated/operation-client.generated.ts';
9
9
  import type { StoredStreamChunk } from '../core/context.ts';
10
10
  import type { TypedEventTarget, WeftEventMap } from '../core/events.ts';
11
- import type { AttributeFilterKey, BulkCancelResult, BulkDeleteResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PaginatedResult, PurgeResult, QueryDefinition, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, StartOptions, SubmitReviewOptions, TypedListFilter, UpdateDefinition, WorkflowEvent, WorkflowInput, WorkflowOutput, WorkflowRegistry, WorkflowReplay, WorkflowState, WorkflowSummary, WorkflowTimelineEntry } from '../core/types.ts';
11
+ import type { AttributeFilterKey, BulkCancelResult, BulkDeleteResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PaginatedResult, PurgeResult, QueryDefinition, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, StartOptions, StartOrSignalSignal, SubmitReviewOptions, TypedListFilter, UpdateDefinition, WorkflowEvent, WorkflowInput, WorkflowOutput, WorkflowRegistry, WorkflowReplay, WorkflowState, WorkflowSummary, WorkflowTimelineEntry } from '../core/types.ts';
12
12
  import type { WorkflowEventTail } from './event-tail.ts';
13
13
  import type { KnownWorkflowName, UnknownNameWhenRegistryEmpty } from './workflow-name-typing.ts';
14
14
  /**
@@ -56,6 +56,20 @@ export interface ClientHandle<TResult = unknown> extends TypedEventTarget<WeftEv
56
56
  result(): Promise<TResult>;
57
57
  /** Cancel this workflow. */
58
58
  cancel(): Promise<void>;
59
+ /**
60
+ * Suspend this workflow without terminating it: it moves to the non-terminal
61
+ * `suspended` status, keeps its checkpoint, and is later resumable via
62
+ * {@link ClientHandle.resume}. Unlike {@link ClientHandle.cancel}, it does not
63
+ * run cancel handlers and does not settle `result()`. Inline execution mode
64
+ * only (worker-mode servers fault with `Unprocessable`).
65
+ */
66
+ suspend(): Promise<void>;
67
+ /**
68
+ * Resume this workflow from its persisted checkpoint after a
69
+ * {@link ClientHandle.suspend} (or after a process restart left it running).
70
+ * `result()` resolves when the resumed run completes.
71
+ */
72
+ resume(): Promise<void>;
59
73
  /** Send a named signal with an optional payload. */
60
74
  signal(name: SignalDefinition): Promise<void>;
61
75
  signal<TInput>(name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
@@ -189,9 +203,40 @@ export interface WeftClient {
189
203
  * returned handle's `result()` to its output type. Without augmentation the
190
204
  * permissive string-name overload applies, so the client stays usable with
191
205
  * plain string names and no hard dependency on codegen.
206
+ *
207
+ * Pass `options.idempotencyKey` for at-most-once starts: a repeated key returns
208
+ * a handle to the existing run rather than starting a second. Conflicts (a
209
+ * duplicate `id`, or a key whose run was purged) are transport-dependent:
210
+ * `LocalClient` throws the typed error (`WorkflowAlreadyExistsError` /
211
+ * `IdempotencyKeyPurgedError`), while `HttpClient` throws `HttpClientError`
212
+ * with `status === 409` and `faultCode === 'Conflict'`.
192
213
  */
193
214
  start<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, options?: StartOptions): Promise<ClientHandle<WorkflowOutput<WorkflowRegistry, TName>>>;
194
215
  start<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, options?: StartOptions): Promise<ClientHandle>;
216
+ /**
217
+ * Atomically start a workflow or signal it if it already exists
218
+ * (signal-with-start). An absent target is created and delivered the signal in
219
+ * one batch; a non-terminal target (running, pending, or suspended) is
220
+ * signalled; a terminal target is rejected as a conflict.
221
+ *
222
+ * The rejection shape is transport-dependent: `LocalClient` throws the typed
223
+ * `StartOrSignalConflictError` (and `IdempotencyKeyPurgedError` for a spent
224
+ * key), while `HttpClient` throws `HttpClientError` with `status === 409` and
225
+ * `faultCode === 'Conflict'`. Branch on `faultCode`/`status` for code that runs
226
+ * over either transport.
227
+ *
228
+ * Pass `options.idempotencyKey` to dedup independent callers such as retried
229
+ * webhooks: concurrent same-key callers converge on one workflow and one
230
+ * delivered signal, with the signal id derived from the key. Convergence needs a
231
+ * shared workflow identity — `options.idempotencyKey` (id-free) or
232
+ * `options.id` + `signal.signalId`. A bare `signal.signalId` with neither is an
233
+ * atomic start-with-one-signal that does NOT converge concurrent callers (each
234
+ * gets its own run). Supply exactly one of `signal.signalId` or
235
+ * `options.idempotencyKey`; `options.id` and `options.idempotencyKey` are
236
+ * mutually exclusive.
237
+ */
238
+ startOrSignal<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, signal: StartOrSignalSignal, options?: StartOptions): Promise<ClientHandle<WorkflowOutput<WorkflowRegistry, TName>>>;
239
+ startOrSignal<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, signal: StartOrSignalSignal, options?: StartOptions): Promise<ClientHandle>;
195
240
  /**
196
241
  * Register a recurring schedule (cron string or interval spec) and return a
197
242
  * handle to it.
@@ -212,6 +257,12 @@ export interface WeftClient {
212
257
  listSchedules(filter?: ScheduleFilter): Promise<PaginatedResult<ScheduleSummary>>;
213
258
  /** Cancel a running workflow. */
214
259
  cancel(id: string): Promise<void>;
260
+ /**
261
+ * Suspend a running workflow without terminating it. It moves to the
262
+ * non-terminal `suspended` status, keeps its checkpoint, and is later
263
+ * resumable via {@link WeftClient.resume}. Inline execution mode only.
264
+ */
265
+ suspend(id: string): Promise<void>;
215
266
  /** Pause a recurring schedule. */
216
267
  pauseSchedule(id: string): Promise<void>;
217
268
  /** Resume a recurring schedule. */
@@ -240,7 +291,11 @@ export interface WeftClient {
240
291
  }): Promise<unknown>;
241
292
  /** Out-of-band ("async") activity completion by task token. See {@link WeftClientActivity}. */
242
293
  readonly activity: WeftClientActivity;
243
- /** Resume a failed or timed-out workflow. */
294
+ /**
295
+ * Re-drive a workflow from its persisted checkpoint. Accepts a workflow that
296
+ * was explicitly suspended (`suspend(id)`) or one left `'running'` by a prior
297
+ * process; throws for a status that cannot be resumed (terminal or pending).
298
+ */
244
299
  resume(id: string): Promise<ClientHandle>;
245
300
  /** Recover all interrupted workflows. */
246
301
  recoverAll(): Promise<ClientHandle[]>;
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { type CatalogOperationName, type CatalogOperationTypes, type WeftClient as CatalogOperations } from '../cli/generated/operation-client.generated.ts';
11
11
  import type { Engine } from '../core/engine.ts';
12
- import type { AttributeFilterKey, BulkCancelResult, BulkDeleteResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PaginatedResult, PurgeResult, QueryDefinition, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, StartOptions, SubmitReviewOptions, TypedListFilter, UpdateDefinition, WorkflowEvent, WorkflowInput, WorkflowOutput, WorkflowRegistry, WorkflowReplay, WorkflowState, WorkflowSummary, WorkflowTimelineEntry } from '../core/types.ts';
12
+ import type { AttributeFilterKey, BulkCancelResult, BulkDeleteResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PaginatedResult, PurgeResult, QueryDefinition, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, StartOptions, StartOrSignalSignal, SubmitReviewOptions, TypedListFilter, UpdateDefinition, WorkflowEvent, WorkflowInput, WorkflowOutput, WorkflowRegistry, WorkflowReplay, WorkflowState, WorkflowSummary, WorkflowTimelineEntry } from '../core/types.ts';
13
13
  import type { WorkflowEventTail } from './event-tail.ts';
14
14
  import type { ClientHandle, ClientScheduleHandle, UpdateResult, WeftClient, WeftClientActivity } from './interface.ts';
15
15
  import type { KnownWorkflowName, UnknownNameWhenRegistryEmpty } from './workflow-name-typing.ts';
@@ -57,6 +57,8 @@ export declare class LocalClient implements WeftClient {
57
57
  call<Name extends CatalogOperationName>(name: Name, input: CatalogOperationTypes[Name]['input']): Promise<CatalogOperationTypes[Name]['output']>;
58
58
  start<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, options?: StartOptions): Promise<ClientHandle<WorkflowOutput<WorkflowRegistry, TName>>>;
59
59
  start<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, options?: StartOptions): Promise<ClientHandle>;
60
+ startOrSignal<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, signal: StartOrSignalSignal, options?: StartOptions): Promise<ClientHandle<WorkflowOutput<WorkflowRegistry, TName>>>;
61
+ startOrSignal<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, signal: StartOrSignalSignal, options?: StartOptions): Promise<ClientHandle>;
60
62
  schedule<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise<ClientScheduleHandle>;
61
63
  schedule<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise<ClientScheduleHandle>;
62
64
  get(id: string): Promise<WorkflowState | null>;
@@ -64,6 +66,7 @@ export declare class LocalClient implements WeftClient {
64
66
  list<const TAttributeKeys extends readonly AttributeFilterKey[] = readonly AttributeFilterKey[]>(filter?: TypedListFilter<TAttributeKeys>): Promise<PaginatedResult<WorkflowSummary>>;
65
67
  listSchedules(filter?: ScheduleFilter): Promise<PaginatedResult<ScheduleSummary>>;
66
68
  cancel(id: string): Promise<void>;
69
+ suspend(id: string): Promise<void>;
67
70
  pauseSchedule(id: string): Promise<void>;
68
71
  resumeSchedule(id: string): Promise<void>;
69
72
  cancelSchedule(id: string): Promise<void>;
@@ -53,6 +53,10 @@ export class LocalClient {
53
53
  const handle = await this.#engine.start(type, input, options);
54
54
  return new LocalHandle(handle, this);
55
55
  }
56
+ async startOrSignal(type, input, signal, options) {
57
+ const handle = await this.#engine.startOrSignal(type, input, signal, options);
58
+ return new LocalHandle(handle, this);
59
+ }
56
60
  async schedule(type, input, spec, options) {
57
61
  const handle = await this.#engine.schedule(type, input, spec, options);
58
62
  return new LocalScheduleHandle(handle.id, this);
@@ -72,6 +76,9 @@ export class LocalClient {
72
76
  async cancel(id) {
73
77
  return this.#engine.cancel(id);
74
78
  }
79
+ async suspend(id) {
80
+ return this.#engine.suspend(id);
81
+ }
75
82
  async pauseSchedule(id) {
76
83
  return this.#engine.pauseSchedule(id);
77
84
  }
@@ -1,4 +1,4 @@
1
- import type { ScheduleSpec, StartOptions } from '../core/types.ts';
1
+ import type { ScheduleSpec, StartOptions, StartOrSignalSignal } from '../core/types.ts';
2
2
  export declare function setIfDefined(body: Record<string, unknown>, key: string, value: unknown): void;
3
3
  /**
4
4
  * Translate a schedule recurrence specification into the wire body fields the
@@ -7,3 +7,9 @@ export declare function setIfDefined(body: Record<string, unknown>, key: string,
7
7
  */
8
8
  export declare function scheduleSpecToWireFields(spec: string | ScheduleSpec): Record<string, unknown>;
9
9
  export declare function buildStartBody(type: string, input: unknown, options?: StartOptions): Record<string, unknown>;
10
+ /**
11
+ * Build the wire body for `weft.workflows.startorsignal`. Flattens the signal
12
+ * spec into `signalName` / `signalPayload` / `signalId` and carries the same
13
+ * start options as {@link buildStartBody}.
14
+ */
15
+ export declare function buildStartOrSignalBody(type: string, input: unknown, signal: StartOrSignalSignal, options?: StartOptions): Record<string, unknown>;
@@ -9,15 +9,24 @@ export function scheduleSpecToWireFields(spec) {
9
9
  return { every: spec.every };
10
10
  return { cronExpression: spec.cron };
11
11
  }
12
- export function buildStartBody(type, input, options) {
13
- if (options?.idempotencyKey !== void 0)
14
- throw Error("idempotencyKey is not supported over HttpClient because the start workflow HTTP protocol does not implement start idempotency");
15
- const body = { type, input };
12
+ function applyStartOptionsToBody(body, options) {
16
13
  setIfDefined(body, "id", options?.id);
17
14
  setIfDefined(body, "executionTimeout", options?.executionTimeout);
18
15
  setIfDefined(body, "startAt", options?.startAt);
19
16
  setIfDefined(body, "startAfter", options?.startAfter);
20
17
  setIfDefined(body, "tags", options?.tags);
18
+ setIfDefined(body, "idempotencyKey", options?.idempotencyKey);
21
19
  setIfDefined(body, "searchAttributes", options?.searchAttributes);
20
+ }
21
+ export function buildStartBody(type, input, options) {
22
+ const body = { type, input };
23
+ applyStartOptionsToBody(body, options);
24
+ return body;
25
+ }
26
+ export function buildStartOrSignalBody(type, input, signal, options) {
27
+ const body = { type, input, signalName: signal.name };
28
+ setIfDefined(body, "signalPayload", signal.payload);
29
+ setIfDefined(body, "signalId", signal.signalId);
30
+ applyStartOptionsToBody(body, options);
22
31
  return body;
23
32
  }
@@ -5,8 +5,10 @@ import {
5
5
  decodeCodecDate,
6
6
  encodeCodecDate
7
7
  } from "../codec-helpers.js";
8
+ import { bindSerializerRegistryToCodec, hasRegisteredSerializer } from "./serializer-registry.js";
8
9
  const EXTENSION_TYPE_DATE = 1, EXTENSION_TYPE_REGEXP = 2, EXTENSION_TYPE_MAP = 3, EXTENSION_TYPE_SET = 4, EXTENSION_TYPE_UNDEFINED = 5, EXTENSION_TYPE_ERROR = 6;
9
10
  export const extensionCodec = new ExtensionCodec;
11
+ bindSerializerRegistryToCodec(extensionCodec, replaceUndefined);
10
12
  extensionCodec.register({
11
13
  type: EXTENSION_TYPE_DATE,
12
14
  encode: encodeCodecDate,
@@ -75,7 +77,7 @@ extensionCodec.register({
75
77
  extensionCodec.register({
76
78
  type: EXTENSION_TYPE_ERROR,
77
79
  encode(value) {
78
- if (value instanceof Error)
80
+ if (value instanceof Error && !hasRegisteredSerializer(value))
79
81
  return msgpackEncode({
80
82
  name: value.name,
81
83
  message: value.message,
@@ -135,7 +137,7 @@ function replaceSetUndefined(value, visited) {
135
137
  return result;
136
138
  }
137
139
  function isNestedValueFree(value) {
138
- return value instanceof Date || value instanceof RegExp || value instanceof Error || value instanceof Uint8Array || value instanceof ArrayBuffer;
140
+ return value instanceof Date || value instanceof RegExp || value instanceof Error || value instanceof Uint8Array || value instanceof ArrayBuffer || hasRegisteredSerializer(value);
139
141
  }
140
142
  function replaceRecordUndefined(value, visited) {
141
143
  const record = value, result = {};
@@ -1,3 +1,4 @@
1
1
  export { decode, encode } from './api.ts';
2
+ export { registerSerializer, type SerializerHandlers } from './serializer-registry.ts';
2
3
  export { validateCloneable } from './validation.ts';
3
4
  export type { CloneValidationError, CloneValidationResult } from './validation.ts';
@@ -1,2 +1,3 @@
1
1
  export { decode, encode } from "./api.js";
2
+ export { registerSerializer } from "./serializer-registry.js";
2
3
  export { validateCloneable } from "./validation.js";
@@ -0,0 +1,122 @@
1
+ import { ExtensionCodec } from '@msgpack/msgpack';
2
+ /**
3
+ * Encode/decode handlers for a custom structured type that crosses Weft's
4
+ * checkpoint codec. `toJSON` reduces an instance to a plain, msgpack-encodable
5
+ * value; `fromJSON` rebuilds the instance from that value on the far side.
6
+ *
7
+ * The handlers must round-trip deterministically: a value encoded and then
8
+ * decoded must reconstruct an equivalent instance, with field order and content
9
+ * stable across calls (the codec backs replay-deterministic checkpoints).
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { type SerializerHandlers } from '@lostgradient/weft';
14
+ *
15
+ * class ValidationError extends Error {
16
+ * constructor(
17
+ * message: string,
18
+ * readonly issues: string[],
19
+ * ) {
20
+ * super(message);
21
+ * this.name = 'ValidationError';
22
+ * }
23
+ * }
24
+ *
25
+ * const handlers: SerializerHandlers<ValidationError> = {
26
+ * toJSON: (error) => ({ message: error.message, issues: error.issues }),
27
+ * fromJSON: (data) => {
28
+ * const record = data as { message: string; issues: string[] };
29
+ * return new ValidationError(record.message, record.issues);
30
+ * },
31
+ * };
32
+ * void handlers;
33
+ * ```
34
+ */
35
+ export type SerializerHandlers<T> = {
36
+ toJSON(value: T): unknown;
37
+ fromJSON(data: unknown): T;
38
+ };
39
+ /**
40
+ * Constructor of a registrable type. Uses a `never[]`-rest abstract constructor
41
+ * so it accepts any class (a concrete class is assignable to an abstract
42
+ * constructor whose parameters are `never[]`) without an `any`. The handlers,
43
+ * not the constructor signature, own (de)serialization; the constructor is used
44
+ * only for instance-identity matching.
45
+ */
46
+ type RegistrableConstructor<T> = abstract new (...args: never[]) => T;
47
+ /**
48
+ * Register a custom (de)serializer for `constructor` on Weft's checkpoint codec.
49
+ * Once registered, any instance of `constructor` that crosses the codec — an
50
+ * activity result, workflow input, signal payload, or error — round-trips
51
+ * through `handlers` instead of the generic structured-clone fallback (which,
52
+ * for errors, would otherwise drop subclass fields like a `ZodError`'s
53
+ * `.issues`).
54
+ *
55
+ * `options.tag` is a stable, developer-chosen discriminant stored inside each
56
+ * encoded value. Decode resolves the handler by this tag, so registration order
57
+ * and count are irrelevant and a checkpoint stays decodable across deploys.
58
+ * Choose an explicit, durable string — do NOT rely on `constructor.name`, which
59
+ * a minified build mangles, silently breaking cross-build decode.
60
+ *
61
+ * Matching is by exact constructor identity, and the built-in `Error` encoder
62
+ * defers to a registered serializer. The other built-in extension types
63
+ * (`Date`, `RegExp`, `Map`, `Set`) do NOT defer: registering a serializer for a
64
+ * subclass of one of those built-ins has no effect, because the built-in
65
+ * encoder matches the instance first. Register serializers for your own classes
66
+ * (or `Error` subclasses), not for built-in-collection subclasses.
67
+ *
68
+ * Registration is process-global and one-shot per constructor and per tag: call
69
+ * it once at module load, before constructing any engine. Re-registering the
70
+ * same constructor, or reusing a `tag` already taken by another constructor,
71
+ * throws. A checkpoint written with a registered serializer is decodable by any
72
+ * process that registered the same tag → handler.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * import { registerSerializer } from '@lostgradient/weft';
77
+ *
78
+ * class RateLimitError extends Error {
79
+ * constructor(readonly retryAfterMs: number) {
80
+ * super('rate limited');
81
+ * this.name = 'RateLimitError';
82
+ * }
83
+ * }
84
+ *
85
+ * registerSerializer(
86
+ * RateLimitError,
87
+ * {
88
+ * toJSON: (error) => ({ retryAfterMs: error.retryAfterMs }),
89
+ * fromJSON: (data) => new RateLimitError((data as { retryAfterMs: number }).retryAfterMs),
90
+ * },
91
+ * { tag: 'RateLimitError' },
92
+ * );
93
+ * ```
94
+ */
95
+ export declare function registerSerializer<T>(constructor: RegistrableConstructor<T>, handlers: SerializerHandlers<T>, options: {
96
+ tag: string;
97
+ }): void;
98
+ /**
99
+ * Whether `value` is an instance of a registered constructor. The built-in
100
+ * Error extension encoder consults this to defer to a registered serializer
101
+ * (so a registered Error subclass uses its custom handler, not the generic
102
+ * Error encoding), regardless of extension-encoder registration order.
103
+ */
104
+ export declare function hasRegisteredSerializer(value: object): boolean;
105
+ /**
106
+ * Wire the shared extensionCodec so the single custom-serializer extension type
107
+ * is registered on the live codec, along with the codec's `replaceUndefined`
108
+ * preprocessor so custom-serializer output is encoded with the same `undefined`
109
+ * semantics as the public `encode()`. Called once at codec construction.
110
+ * `replaceUndefined` is passed in rather than imported to avoid a static cycle:
111
+ * extension-codec.ts imports this module for `hasRegisteredSerializer`.
112
+ */
113
+ export declare function bindSerializerRegistryToCodec(codec: ExtensionCodec, replaceUndefined: (value: unknown, visited: Set<object>) => unknown): void;
114
+ /**
115
+ * Test-only reset of the global registry. Production code never unregisters —
116
+ * a stale serializer could misread a checkpoint — but tests need isolation
117
+ * between registration cases. Clears both tag and constructor maps; the single
118
+ * extension-type decoder stays bound to the codec and simply finds an empty
119
+ * registry until the next registration.
120
+ */
121
+ export declare function resetSerializerRegistryForTesting(): void;
122
+ export {};
@@ -0,0 +1,51 @@
1
+ import { ExtensionCodec, decode as msgpackDecode, encode as msgpackEncode } from "@msgpack/msgpack";
2
+ const CUSTOM_SERIALIZER_EXTENSION_TYPE = 100, registryByConstructor = new Map, registryByTag = new Map;
3
+ export function registerSerializer(constructor, handlers, options) {
4
+ const key = constructor, { tag } = options;
5
+ if (typeof tag !== "string" || tag.length === 0)
6
+ throw Error("registerSerializer requires a non-empty string options.tag.");
7
+ if (registryByConstructor.has(key))
8
+ throw Error(`A serializer is already registered for ${constructor.name || "an anonymous constructor"}; registerSerializer is one-shot per constructor.`);
9
+ if (registryByTag.has(tag))
10
+ throw Error(`A serializer is already registered with tag "${tag}"; serializer tags must be unique.`);
11
+ const entry = {
12
+ tag,
13
+ constructor: key,
14
+ handlers
15
+ };
16
+ registryByConstructor.set(key, entry);
17
+ registryByTag.set(tag, entry);
18
+ }
19
+ export function hasRegisteredSerializer(value) {
20
+ return registryByConstructor.has(value.constructor);
21
+ }
22
+ export function bindSerializerRegistryToCodec(codec, replaceUndefined) {
23
+ codec.register({
24
+ type: CUSTOM_SERIALIZER_EXTENSION_TYPE,
25
+ encode(value) {
26
+ if (typeof value !== "object" || value === null)
27
+ return null;
28
+ const entry = registryByConstructor.get(value.constructor);
29
+ if (entry === void 0)
30
+ return null;
31
+ const data = replaceUndefined(entry.handlers.toJSON(value), new Set);
32
+ return msgpackEncode({ tag: entry.tag, data }, { extensionCodec: codec });
33
+ },
34
+ decode(bytes) {
35
+ const payload = msgpackDecode(bytes, { extensionCodec: codec });
36
+ if (typeof payload !== "object" || payload === null || !("tag" in payload))
37
+ throw Error("Corrupt custom-serializer payload: missing tag.");
38
+ const { tag, data } = payload;
39
+ if (typeof tag !== "string")
40
+ throw Error("Corrupt custom-serializer payload: tag is not a string.");
41
+ const entry = registryByTag.get(tag);
42
+ if (entry === void 0)
43
+ throw Error(`No serializer registered for tag "${tag}". Register it (with the same tag) before decoding a checkpoint that used it.`);
44
+ return entry.handlers.fromJSON(data);
45
+ }
46
+ });
47
+ }
48
+ export function resetSerializerRegistryForTesting() {
49
+ registryByConstructor.clear();
50
+ registryByTag.clear();
51
+ }
@@ -27,6 +27,7 @@ export declare class Context implements WorkflowContext {
27
27
  readonly signal: AbortSignal;
28
28
  constructor(options: ContextOptions);
29
29
  get executionTimeRemaining(): number;
30
+ get services(): unknown;
30
31
  get stepIndex(): number;
31
32
  get nestingDepth(): number;
32
33
  get accumulatedResults(): Map<number, unknown>;
@@ -54,6 +55,15 @@ export declare class Context implements WorkflowContext {
54
55
  run<TInput, TResult>(fn: ((input: TInput) => Promise<TResult> | TResult) & {
55
56
  execute?: never;
56
57
  }, input: TInput, options?: ActivityCallOptions): Generator<ContextOperationRequest, TResult, unknown>;
58
+ /**
59
+ * Durably sleep for `duration` before continuing. Unlike a wall-clock
60
+ * `setTimeout`, this is a checkpointed operation: the engine persists the timer
61
+ * and resumes the workflow when it fires, so a sleep survives process restarts
62
+ * and spans of days. On replay an already-elapsed sleep is a no-op — a
63
+ * recovered workflow does not wait again. `duration` is milliseconds
64
+ * (`number`) or a duration string (`'30s'`, `'1h'`, `'2d'` — see
65
+ * {@link Duration}); drive it with `yield*` from a workflow body.
66
+ */
57
67
  sleep(duration: Duration): Generator<ContextOperationRequest, void, unknown>;
58
68
  suspendUntil<T = unknown>(resumeToken: string): Generator<ContextOperationRequest, T, unknown>;
59
69
  waitForSignal<TInput>(definition: SignalDefinition<TInput>): Generator<ContextOperationRequest, TInput, unknown>;
@@ -37,6 +37,9 @@ export class Context {
37
37
  return 1 / 0;
38
38
  return Math.max(0, internals.deadline - internals.getNow());
39
39
  }
40
+ get services() {
41
+ return getInternals(this).services;
42
+ }
40
43
  get stepIndex() {
41
44
  return getInternals(this).stepIndex;
42
45
  }
@@ -25,6 +25,16 @@ export interface ContextInternals {
25
25
  executionStateOwnerId: string;
26
26
  resolveWorkflowType: ((target: string | Function) => string) | undefined;
27
27
  registerCancelHandler: ((handler: () => Promise<void> | void) => () => void) | undefined;
28
+ services: unknown;
28
29
  }
29
30
  export declare function initializeInternals(context: Context, options: ContextOptions, initialSessionState: Record<string, unknown> | undefined): void;
30
31
  export declare function getInternals(context: Context): ContextInternals;
32
+ /**
33
+ * Non-throwing probe: is `value` a concrete {@link Context} with initialized
34
+ * internals? Used to distinguish the inline-mode `Context` (which carries the
35
+ * `stepIndex`/`accumulatedResults` replay machinery) from the minimal
36
+ * worker-mode `WorkerWorkflowContext`, which is a different shape. WeakMap
37
+ * `.has` accepts any object key and returns `false` for non-`Context` objects,
38
+ * so this is safe to call on any context-shaped value.
39
+ */
40
+ export declare function hasContextInternals(value: object): value is Context;
@@ -24,7 +24,8 @@ export function initializeInternals(context, options, initialSessionState) {
24
24
  nestingDepth: options.nestingDepth ?? 0,
25
25
  executionStateOwnerId: options.executionStateOwnerId ?? options.workflowId,
26
26
  resolveWorkflowType: options.resolveWorkflowType,
27
- registerCancelHandler: options.registerCancelHandler
27
+ registerCancelHandler: options.registerCancelHandler,
28
+ services: options.services
28
29
  };
29
30
  INTERNALS.set(context, internals);
30
31
  }
@@ -34,3 +35,6 @@ export function getInternals(context) {
34
35
  throw Error("Context internals not initialized");
35
36
  return internals;
36
37
  }
38
+ export function hasContextInternals(value) {
39
+ return INTERNALS.has(value);
40
+ }
@@ -1,6 +1,19 @@
1
- import { type RetryPolicy } from '../types.ts';
1
+ import { type RetryPolicy, type WorkflowContext } from '../types.ts';
2
2
  import type { Context } from './index.ts';
3
3
  import type { ContextOperationRequest } from './operation-request.ts';
4
+ /**
5
+ * Recover the concrete {@link Context} from the public {@link WorkflowContext}
6
+ * the engine passes to a workflow handler. Under the inline execution strategy
7
+ * the engine invokes a handler with the concrete `Context` instance (carrying
8
+ * the `stepIndex`/`accumulatedResults` replay machinery). Worker execution mode
9
+ * instead drives the handler with a minimal `WorkerWorkflowContext`, which has
10
+ * no replay internals — so infrastructure such as `compileStepWorkflow` cannot
11
+ * drive the durable activity machinery there. This probes for the inline
12
+ * internals (via the `hasContextInternals` type guard, which narrows without a
13
+ * cast) and throws an actionable error rather than the cryptic
14
+ * "Context internals not initialized" from a downstream `getInternals` call.
15
+ */
16
+ export declare function asConcreteContext(context: WorkflowContext): Context;
4
17
  type ActivityInput = string | (Function & {
5
18
  retry?: RetryPolicy;
6
19
  });
@@ -16,6 +29,6 @@ interface RunActivityRequest<TResult> {
16
29
  retryPolicy?: RetryPolicy;
17
30
  }
18
31
  export declare function shouldRetryActivityError(error: unknown, policy: RetryPolicy | undefined, attempt: number): policy is RetryPolicy;
19
- export declare function createRunActivityRequest<TResult>(context: Context, activity: ActivityInput, rest: readonly unknown[]): RunActivityRequest<TResult>;
20
- export declare function runActivityWithRetry<TResult>(context: Context, activity: ActivityInput, rest: readonly unknown[]): Generator<ContextOperationRequest, TResult, unknown>;
32
+ export declare function createRunActivityRequest<TResult>(context: Context, activity: ActivityInput, rest: readonly unknown[], explicitName?: string): RunActivityRequest<TResult>;
33
+ export declare function runActivityWithRetry<TResult>(context: Context, activity: ActivityInput, rest: readonly unknown[], explicitName?: string): Generator<ContextOperationRequest, TResult, unknown>;
21
34
  export {};