@lostgradient/weft 0.2.1 → 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.
- package/README.md +47 -22
- package/dist/cli/generated/operation-client.generated.d.ts +28 -1
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/cli-main.js +79 -79
- package/dist/client/handle-delegation.d.ts +4 -0
- package/dist/client/handle-delegation.js +6 -0
- package/dist/client/http-client-requests.d.ts +2 -0
- package/dist/client/http-client-requests.js +3 -0
- package/dist/client/http-client.d.ts +4 -1
- package/dist/client/http-client.js +9 -1
- package/dist/client/interface.d.ts +57 -2
- package/dist/client/local.d.ts +4 -1
- package/dist/client/local.js +7 -0
- package/dist/client/start-body.d.ts +7 -1
- package/dist/client/start-body.js +13 -4
- package/dist/core/codec/extension-codec.js +4 -2
- package/dist/core/codec/index.d.ts +1 -0
- package/dist/core/codec/index.js +1 -0
- package/dist/core/codec/serializer-registry.d.ts +122 -0
- package/dist/core/codec/serializer-registry.js +51 -0
- package/dist/core/context/index.d.ts +9 -0
- package/dist/core/context/internals.d.ts +9 -0
- package/dist/core/context/internals.js +3 -0
- package/dist/core/context/run-operation.d.ts +16 -3
- package/dist/core/context/run-operation.js +16 -7
- package/dist/core/engine/bulk-operations.js +1 -1
- package/dist/core/engine/construction.d.ts +0 -1
- package/dist/core/engine/construction.js +10 -1
- package/dist/core/engine/disposal.js +12 -0
- package/dist/core/engine/engine-create-types.d.ts +0 -14
- package/dist/core/engine/engine-internal-types.d.ts +12 -0
- package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
- package/dist/core/engine/engine-leak-warnings.js +4 -0
- package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
- package/dist/core/engine/engine-runtime-helpers.js +26 -5
- package/dist/core/engine/errors.d.ts +74 -0
- package/dist/core/engine/errors.js +25 -1
- package/dist/core/engine/handle-result.js +1 -1
- package/dist/core/engine/handles.d.ts +89 -40
- package/dist/core/engine/handles.js +25 -27
- package/dist/core/engine/index.d.ts +96 -4
- package/dist/core/engine/index.js +75 -4
- package/dist/core/engine/inline-launch-queue.d.ts +14 -0
- package/dist/core/engine/inline-launch-queue.js +32 -7
- package/dist/core/engine/internals.d.ts +18 -10
- package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
- package/dist/core/engine/lifecycle/persist.js +5 -20
- package/dist/core/engine/lifecycle/resume.js +25 -4
- package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
- package/dist/core/engine/lifecycle/start-commit.js +27 -0
- package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
- package/dist/core/engine/lifecycle/start-exec.js +38 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
- package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
- package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
- package/dist/core/engine/lifecycle/start.d.ts +3 -3
- package/dist/core/engine/lifecycle/start.js +31 -37
- package/dist/core/engine/lifecycle.d.ts +3 -2
- package/dist/core/engine/lifecycle.js +9 -2
- package/dist/core/engine/listing.js +1 -1
- package/dist/core/engine/persisted-data-version.d.ts +5 -9
- package/dist/core/engine/persisted-data-version.js +4 -5
- package/dist/core/engine/schedule-handle.d.ts +45 -0
- package/dist/core/engine/schedule-handle.js +26 -0
- package/dist/core/engine/schedules.d.ts +1 -1
- package/dist/core/engine/schedules.js +7 -3
- package/dist/core/engine/second-instance-detector.d.ts +96 -0
- package/dist/core/engine/second-instance-detector.js +108 -0
- package/dist/core/engine/signals.d.ts +22 -0
- package/dist/core/engine/signals.js +15 -0
- package/dist/core/engine/termination/cleanup.d.ts +25 -0
- package/dist/core/engine/termination/cleanup.js +19 -1
- package/dist/core/engine/termination/complete.js +4 -3
- package/dist/core/engine/termination/suspend.d.ts +68 -0
- package/dist/core/engine/termination/suspend.js +41 -0
- package/dist/core/engine/termination.d.ts +4 -2
- package/dist/core/engine/termination.js +2 -0
- package/dist/core/engine/validation.js +25 -1
- package/dist/core/engine/workflow-feed.d.ts +5 -3
- package/dist/core/events/event-map.d.ts +2 -1
- package/dist/core/events/workflow-events.d.ts +23 -0
- package/dist/core/events/workflow-events.js +9 -0
- package/dist/core/list-filter-validation.js +2 -1
- package/dist/core/start-workflow-validation.d.ts +22 -0
- package/dist/core/start-workflow-validation.js +11 -1
- package/dist/core/step-context.d.ts +10 -6
- package/dist/core/step-context.js +7 -15
- package/dist/core/types/activity.d.ts +6 -3
- package/dist/core/types/identity.d.ts +8 -1
- package/dist/core/types/launch-metadata.d.ts +33 -0
- package/dist/core/types/launch-metadata.js +0 -0
- package/dist/core/types/message-handles.d.ts +25 -0
- package/dist/core/types/options.d.ts +48 -54
- package/dist/core/types/reviews.d.ts +2 -1
- package/dist/core/types/services-resolution.d.ts +47 -0
- package/dist/core/types/services-resolution.js +0 -0
- package/dist/core/types/state.d.ts +11 -11
- package/dist/core/types/workflow-builder.d.ts +5 -4
- package/dist/core/types/workflow-function.d.ts +17 -0
- package/dist/core/types/workflow-snapshot.d.ts +29 -0
- package/dist/core/types/workflow-snapshot.js +0 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.js +3 -0
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +3 -1
- package/dist/diagnostics/doctor.js +6 -3
- package/dist/diagnostics/format.js +2 -2
- package/dist/diagnostics/types.d.ts +1 -0
- package/dist/diagnostics/version-check.js +6 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -1
- package/dist/json-schema.js +1 -1
- package/dist/mcp/cli.js +35 -35
- package/dist/mcp/list-filter.js +2 -1
- package/dist/mcp/session.js +1 -0
- package/dist/observability/index.js +2 -2
- package/dist/server/handler.js +30 -30
- package/dist/server/index.js +33 -33
- package/dist/server/interactive-operations.js +1 -0
- package/dist/server/operations/resume-workflow.js +2 -2
- package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
- package/dist/server/operations/start-or-signal-workflow.js +140 -0
- package/dist/server/operations/start-workflow-options.d.ts +32 -0
- package/dist/server/operations/start-workflow-options.js +63 -0
- package/dist/server/operations/start-workflow.js +7 -69
- package/dist/server/operations/suspend-workflow.d.ts +13 -0
- package/dist/server/operations/suspend-workflow.js +36 -0
- package/dist/server/rest-binding.d.ts +18 -7
- package/dist/server/rest-bindings.js +12 -0
- package/dist/server/runtime/task-dispatch.js +5 -3
- package/dist/server/runtime/task-polling.d.ts +16 -2
- package/dist/server/runtime/task-polling.js +20 -5
- package/dist/server/runtime/websocket-worker.js +8 -0
- package/dist/server/serve-internals.d.ts +8 -0
- package/dist/server/serve-internals.js +4 -2
- package/dist/server/task-state.d.ts +8 -0
- package/dist/service-worker/index.js +28 -28
- package/dist/storage/capabilities.d.ts +10 -2
- package/dist/storage/capabilities.js +2 -2
- package/dist/storage/http.js +2 -2
- package/dist/storage/index.d.ts +6 -1
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +26 -0
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +2 -0
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon-value-mapping.d.ts +47 -0
- package/dist/storage/neon-value-mapping.js +11 -0
- package/dist/storage/neon.d.ts +108 -0
- package/dist/storage/neon.js +10 -0
- package/dist/storage/node-sqlite-loader.d.ts +71 -0
- package/dist/storage/node-sqlite-loader.js +41 -0
- package/dist/storage/node-sqlite.d.ts +1 -19
- package/dist/storage/node-sqlite.js +38 -32
- package/dist/storage/postgres-key-value-queries.d.ts +79 -0
- package/dist/storage/postgres-key-value-queries.js +63 -0
- package/dist/storage/resolve.d.ts +2 -165
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/storage-configuration.d.ts +209 -0
- package/dist/storage/storage-configuration.js +0 -0
- package/dist/storage/text-value-store.d.ts +9 -9
- package/dist/storage/turso.js +2 -2
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/index.js +33 -33
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/index.js +9 -5
- package/dist/worker/long-poll.js +4 -0
- package/dist/worker/protocol-messages.d.ts +20 -0
- package/dist/worker/protocol-schemas.d.ts +32 -0
- package/dist/worker/protocol-schemas.js +8 -4
- package/dist/worker/protocol-task-result.d.ts +28 -0
- package/dist/worker/protocol-task-result.js +76 -0
- package/dist/worker/protocol.d.ts +4 -15
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/fair-share.d.ts +29 -0
- package/dist/worker/registry/fair-share.js +30 -0
- package/dist/worker/registry/routing.d.ts +18 -0
- package/dist/worker/registry/routing.js +14 -0
- package/dist/worker/registry/types.d.ts +7 -0
- package/dist/worker/registry.d.ts +16 -1
- package/dist/worker/registry.js +24 -36
- 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
|
-
/**
|
|
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[]>;
|
package/dist/client/local.d.ts
CHANGED
|
@@ -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>;
|
package/dist/client/local.js
CHANGED
|
@@ -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
|
-
|
|
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 = {};
|
package/dist/core/codec/index.js
CHANGED
|
@@ -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
|
+
}
|
|
@@ -55,6 +55,15 @@ export declare class Context implements WorkflowContext {
|
|
|
55
55
|
run<TInput, TResult>(fn: ((input: TInput) => Promise<TResult> | TResult) & {
|
|
56
56
|
execute?: never;
|
|
57
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
|
+
*/
|
|
58
67
|
sleep(duration: Duration): Generator<ContextOperationRequest, void, unknown>;
|
|
59
68
|
suspendUntil<T = unknown>(resumeToken: string): Generator<ContextOperationRequest, T, unknown>;
|
|
60
69
|
waitForSignal<TInput>(definition: SignalDefinition<TInput>): Generator<ContextOperationRequest, TInput, unknown>;
|
|
@@ -29,3 +29,12 @@ export interface ContextInternals {
|
|
|
29
29
|
}
|
|
30
30
|
export declare function initializeInternals(context: Context, options: ContextOptions, initialSessionState: Record<string, unknown> | undefined): void;
|
|
31
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;
|
|
@@ -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 {};
|