@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
|
@@ -11,8 +11,8 @@ const resumeWorkflowInput = z.object({
|
|
|
11
11
|
});
|
|
12
12
|
export const resumeWorkflowOperation = createSingleWorkflowControlOperation({
|
|
13
13
|
name: "weft.workflows.resume",
|
|
14
|
-
summary: "Resume a suspended workflow",
|
|
15
|
-
description: "
|
|
14
|
+
summary: "Resume a suspended or recovered workflow",
|
|
15
|
+
description: "Re-drive a workflow from its persisted checkpoint by `workflowId`, returning the workflow `id`. Accepts a workflow that was explicitly suspended (`weft.workflows.suspend`) or one left running by a prior process; a suspended workflow is durably flipped back to running as part of resuming. Faults with NotFound when the workflow is not visible, or Conflict when it is in a status that cannot be resumed (terminal or pending).",
|
|
16
16
|
destructive: !1,
|
|
17
17
|
tags: ["Workflows"],
|
|
18
18
|
inputSchema: resumeWorkflowInput,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { UnknownRestBinding } from '../rest-bindings.ts';
|
|
3
|
+
declare const startOrSignalWorkflowInput: z.ZodObject<{
|
|
4
|
+
type: z.ZodUnknown;
|
|
5
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
6
|
+
signalName: z.ZodString;
|
|
7
|
+
signalPayload: z.ZodOptional<z.ZodUnknown>;
|
|
8
|
+
signalId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
id: z.ZodOptional<z.ZodUnknown>;
|
|
10
|
+
executionTimeout: z.ZodOptional<z.ZodUnknown>;
|
|
11
|
+
startAt: z.ZodOptional<z.ZodUnknown>;
|
|
12
|
+
startAfter: z.ZodOptional<z.ZodUnknown>;
|
|
13
|
+
tags: z.ZodOptional<z.ZodUnknown>;
|
|
14
|
+
idempotencyKey: z.ZodOptional<z.ZodUnknown>;
|
|
15
|
+
searchAttributes: z.ZodOptional<z.ZodUnknown>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
declare const startOrSignalWorkflowOutput: z.ZodObject<{
|
|
18
|
+
id: z.ZodString;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
export type StartOrSignalWorkflowInput = z.infer<typeof startOrSignalWorkflowInput>;
|
|
21
|
+
export type StartOrSignalWorkflowOutput = z.infer<typeof startOrSignalWorkflowOutput>;
|
|
22
|
+
export declare const startOrSignalWorkflowOperation: import("../operation-catalog.ts").OperationDefinition<{
|
|
23
|
+
type: unknown;
|
|
24
|
+
signalName: string;
|
|
25
|
+
input?: unknown;
|
|
26
|
+
signalPayload?: unknown;
|
|
27
|
+
signalId?: string | undefined;
|
|
28
|
+
id?: unknown;
|
|
29
|
+
executionTimeout?: unknown;
|
|
30
|
+
startAt?: unknown;
|
|
31
|
+
startAfter?: unknown;
|
|
32
|
+
tags?: unknown;
|
|
33
|
+
idempotencyKey?: unknown;
|
|
34
|
+
searchAttributes?: unknown;
|
|
35
|
+
}, {
|
|
36
|
+
id: string;
|
|
37
|
+
}>;
|
|
38
|
+
export declare const startOrSignalWorkflowRestBinding: UnknownRestBinding;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
IdempotencyKeyPurgedError,
|
|
4
|
+
StartOrSignalConflictError,
|
|
5
|
+
WorkflowNotRegisteredError
|
|
6
|
+
} from "../../core/engine/errors.js";
|
|
7
|
+
import { runtimeWorkflowEngine } from "../../core/runtime-workflow-engine.js";
|
|
8
|
+
import { isSignalIdWithinByteLimit } from "../../core/signal-id.js";
|
|
9
|
+
import { StartWorkflowValidationError } from "../../core/start-workflow-validation.js";
|
|
10
|
+
import { defineOperation } from "../operation-registry.js";
|
|
11
|
+
import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
|
|
12
|
+
import { buildSharedStartWorkflowOptions } from "./start-workflow-options.js";
|
|
13
|
+
const startOrSignalWorkflowInput = z.object({
|
|
14
|
+
type: z.unknown().describe("Workflow type name. Runtime validation requires a non-empty string."),
|
|
15
|
+
input: z.unknown().optional(),
|
|
16
|
+
signalName: z.string().min(1).describe("Signal name. Must be a non-empty string."),
|
|
17
|
+
signalPayload: z.unknown().optional(),
|
|
18
|
+
signalId: z.string().min(1).refine(isSignalIdWithinByteLimit, "signalId must be at most 128 bytes").optional(),
|
|
19
|
+
id: z.unknown().optional(),
|
|
20
|
+
executionTimeout: z.unknown().optional(),
|
|
21
|
+
startAt: z.unknown().optional(),
|
|
22
|
+
startAfter: z.unknown().optional(),
|
|
23
|
+
tags: z.unknown().optional(),
|
|
24
|
+
idempotencyKey: z.unknown().optional(),
|
|
25
|
+
searchAttributes: z.unknown().optional()
|
|
26
|
+
}), startOrSignalWorkflowOutput = z.object({
|
|
27
|
+
id: z.string()
|
|
28
|
+
});
|
|
29
|
+
function validateStartOrSignalWorkflowInput(input, lookupSearchAttributeSchema) {
|
|
30
|
+
if (typeof input.type !== "string" || input.type.length === 0)
|
|
31
|
+
throw invalidParamsFault("Missing required field: type");
|
|
32
|
+
let options;
|
|
33
|
+
try {
|
|
34
|
+
options = buildSharedStartWorkflowOptions(input, lookupSearchAttributeSchema(input.type));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
throw invalidParamsFault(message);
|
|
38
|
+
}
|
|
39
|
+
assertConvergenceTokenProvided(input.signalId, options.idempotencyKey);
|
|
40
|
+
const signal = {
|
|
41
|
+
name: input.signalName,
|
|
42
|
+
payload: input.signalPayload,
|
|
43
|
+
...input.signalId === void 0 ? {} : { signalId: input.signalId }
|
|
44
|
+
};
|
|
45
|
+
return { type: input.type, signal, options };
|
|
46
|
+
}
|
|
47
|
+
function assertConvergenceTokenProvided(signalId, idempotencyKey) {
|
|
48
|
+
if (signalId === void 0 && idempotencyKey === void 0)
|
|
49
|
+
throw invalidParamsFault("startOrSignal requires either signalId or idempotencyKey to identify the signal to deliver. (Concurrent callers converge on one workflow and one signal only with a shared idempotencyKey, or a shared id plus signalId; a bare signalId starts a fresh run per caller.)");
|
|
50
|
+
if (signalId !== void 0 && idempotencyKey !== void 0)
|
|
51
|
+
throw invalidParamsFault("startOrSignal does not accept both signalId and idempotencyKey: the signal id derives from the idempotency key for convergence. Provide exactly one.");
|
|
52
|
+
}
|
|
53
|
+
function resolveStartOrSignalWorkflowFault(error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
if (error instanceof WorkflowNotRegisteredError)
|
|
56
|
+
throw invalidParamsFault(message);
|
|
57
|
+
if (error instanceof StartOrSignalConflictError || error instanceof IdempotencyKeyPurgedError)
|
|
58
|
+
throw {
|
|
59
|
+
code: "Conflict",
|
|
60
|
+
message,
|
|
61
|
+
data: { reason: message }
|
|
62
|
+
};
|
|
63
|
+
if (error instanceof StartWorkflowValidationError)
|
|
64
|
+
throw invalidParamsFault(message);
|
|
65
|
+
throw {
|
|
66
|
+
code: "EngineFailure",
|
|
67
|
+
message,
|
|
68
|
+
data: {}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export const startOrSignalWorkflowOperation = defineOperation({
|
|
72
|
+
name: "weft.workflows.startorsignal",
|
|
73
|
+
mcpExposable: !1,
|
|
74
|
+
summary: "Atomically start a workflow or signal it if it already exists",
|
|
75
|
+
description: 'Start a workflow or deliver a signal to it if it already exists (signal-with-start). An absent target is created and delivered the signal in one batch; a non-terminal target (running, pending, or suspended) is signalled; a terminal target faults with Conflict, as does a spent `idempotencyKey` whose run was purged or swept by retention. Requires `type`, `signalName`, and exactly one of `signalId` or `idempotencyKey` (not both). Accepts `input`, `signalPayload`, and the non-idempotency start options from weft.workflows.start (`id`, `executionTimeout`, `startAt`/`startAfter`, `tags`, `searchAttributes`); `idempotencyKey` is governed solely by the "exactly one of" rule above. Returns the workflow `id`. Concurrent callers converge on one workflow and one signal only with a shared `idempotencyKey`, or a shared `id` plus `signalId`; a bare `signalId` (no `id`/`idempotencyKey`) starts a fresh run per caller and does not converge.',
|
|
76
|
+
destructive: !1,
|
|
77
|
+
tags: ["Workflows", "Signals"],
|
|
78
|
+
inputSchema: startOrSignalWorkflowInput,
|
|
79
|
+
outputSchema: startOrSignalWorkflowOutput,
|
|
80
|
+
access: { kind: "public" },
|
|
81
|
+
producibleFaults: ["Conflict"],
|
|
82
|
+
transports: { http: !0, jsonRpcHttp: !0, jsonRpcWebSocket: !0, jsonRpcStdio: !0 },
|
|
83
|
+
unknownKeyPolicy: { http: "strip", jsonRpc: "reject" },
|
|
84
|
+
invoke: async ({ input, engine }) => {
|
|
85
|
+
const typedEngine = runtimeWorkflowEngine(engine), { type, signal, options } = validateStartOrSignalWorkflowInput(input, (validType) => {
|
|
86
|
+
return typedEngine.getWorkflowDefinition(validType)?.searchAttributes;
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
return { id: (await typedEngine.startOrSignal(type, input.input, signal, options)).id };
|
|
90
|
+
} catch (error) {
|
|
91
|
+
resolveStartOrSignalWorkflowFault(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}), startOrSignalWorkflowRestBinding = {
|
|
95
|
+
method: "POST",
|
|
96
|
+
path: "/v1/workflows/start-or-signal",
|
|
97
|
+
pathParamNames: [],
|
|
98
|
+
operationName: "weft.workflows.startorsignal",
|
|
99
|
+
inputSources: {
|
|
100
|
+
type: { kind: "body-field", bodyField: "type" },
|
|
101
|
+
input: { kind: "body-field", bodyField: "input" },
|
|
102
|
+
signalName: { kind: "body-field", bodyField: "signalName" },
|
|
103
|
+
signalPayload: { kind: "body-field", bodyField: "signalPayload" },
|
|
104
|
+
signalId: { kind: "body-field", bodyField: "signalId" },
|
|
105
|
+
id: { kind: "body-field", bodyField: "id" },
|
|
106
|
+
executionTimeout: { kind: "body-field", bodyField: "executionTimeout" },
|
|
107
|
+
startAt: { kind: "body-field", bodyField: "startAt" },
|
|
108
|
+
startAfter: { kind: "body-field", bodyField: "startAfter" },
|
|
109
|
+
tags: { kind: "body-field", bodyField: "tags" },
|
|
110
|
+
idempotencyKey: { kind: "body-field", bodyField: "idempotencyKey" },
|
|
111
|
+
searchAttributes: { kind: "body-field", bodyField: "searchAttributes" }
|
|
112
|
+
},
|
|
113
|
+
extractInput: async (request) => {
|
|
114
|
+
let body;
|
|
115
|
+
try {
|
|
116
|
+
body = await request.json();
|
|
117
|
+
} catch {
|
|
118
|
+
throw invalidParamsFault("Invalid JSON body");
|
|
119
|
+
}
|
|
120
|
+
if (typeof body !== "object" || body === null)
|
|
121
|
+
throw invalidParamsFault("Request body must be a JSON object");
|
|
122
|
+
const record = body;
|
|
123
|
+
return {
|
|
124
|
+
type: record.type,
|
|
125
|
+
input: record.input,
|
|
126
|
+
signalName: record.signalName,
|
|
127
|
+
signalPayload: record.signalPayload,
|
|
128
|
+
signalId: record.signalId,
|
|
129
|
+
id: record.id,
|
|
130
|
+
executionTimeout: record.executionTimeout,
|
|
131
|
+
startAt: record.startAt,
|
|
132
|
+
startAfter: record.startAfter,
|
|
133
|
+
tags: record.tags,
|
|
134
|
+
idempotencyKey: record.idempotencyKey,
|
|
135
|
+
searchAttributes: record.searchAttributes
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
success: { kind: "json", status: 201 },
|
|
139
|
+
shapeFault: shapeRestFault
|
|
140
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SearchAttributeSchema, SearchAttributeValue, StartOptions } from '../../core/types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The raw, transport-supplied start-option fields shared by `weft.workflows.start`
|
|
4
|
+
* and `weft.workflows.startorsignal`. Each is `unknown` because validation happens
|
|
5
|
+
* in {@link buildSharedStartWorkflowOptions}, not at the schema boundary, so both
|
|
6
|
+
* surfaces hit one cross-transport error path.
|
|
7
|
+
*/
|
|
8
|
+
export type SharedStartWorkflowOptionInput = {
|
|
9
|
+
id?: unknown;
|
|
10
|
+
executionTimeout?: unknown;
|
|
11
|
+
startAt?: unknown;
|
|
12
|
+
startAfter?: unknown;
|
|
13
|
+
tags?: unknown;
|
|
14
|
+
idempotencyKey?: unknown;
|
|
15
|
+
searchAttributes?: unknown;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Coerce the shared start-option fields into a validated {@link StartOptions}.
|
|
19
|
+
* Both start operations call this so they cannot drift in how they validate `id`,
|
|
20
|
+
* `executionTimeout`, `startAt`/`startAfter`, `tags`, `idempotencyKey`, and
|
|
21
|
+
* `searchAttributes` (a new field added here covers both surfaces at once). Throws
|
|
22
|
+
* {@link StartWorkflowValidationError} on any malformed field.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildSharedStartWorkflowOptions(input: SharedStartWorkflowOptionInput, searchAttributeSchema: SearchAttributeSchema | undefined): StartOptions;
|
|
25
|
+
/**
|
|
26
|
+
* Coerce a transport-supplied `searchAttributes` object into validated
|
|
27
|
+
* {@link SearchAttributeValue}s. Shared by `weft.workflows.start` and
|
|
28
|
+
* `weft.workflows.startorsignal` so both apply the same null-prototype guard,
|
|
29
|
+
* date-time normalization, schema-presence check, and type validation —
|
|
30
|
+
* preventing the two start surfaces from drifting in how they accept attributes.
|
|
31
|
+
*/
|
|
32
|
+
export declare function coerceStartWorkflowSearchAttributes(value: unknown, fieldName: string, schema: SearchAttributeSchema | undefined): Record<string, SearchAttributeValue>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { validateAttributeType } from "../../core/search-attributes.js";
|
|
2
|
+
import {
|
|
3
|
+
assertExclusiveStartWorkflowOptions,
|
|
4
|
+
coerceStartWorkflowDuration,
|
|
5
|
+
coerceStartWorkflowId,
|
|
6
|
+
coerceStartWorkflowIdempotencyKey,
|
|
7
|
+
coerceStartWorkflowTags,
|
|
8
|
+
coerceStartWorkflowTimestamp,
|
|
9
|
+
StartWorkflowValidationError
|
|
10
|
+
} from "../../core/start-workflow-validation.js";
|
|
11
|
+
export function buildSharedStartWorkflowOptions(input, searchAttributeSchema) {
|
|
12
|
+
const options = {};
|
|
13
|
+
if (input.id !== void 0)
|
|
14
|
+
options.id = coerceStartWorkflowId(input.id, 'Field "id"');
|
|
15
|
+
if (input.executionTimeout !== void 0)
|
|
16
|
+
options.executionTimeout = coerceStartWorkflowDuration(input.executionTimeout, 'Field "executionTimeout"');
|
|
17
|
+
if (input.startAt !== void 0)
|
|
18
|
+
options.startAt = coerceStartWorkflowTimestamp(input.startAt, 'Field "startAt"');
|
|
19
|
+
if (input.startAfter !== void 0)
|
|
20
|
+
options.startAfter = coerceStartWorkflowDuration(input.startAfter, 'Field "startAfter"');
|
|
21
|
+
if (input.tags !== void 0)
|
|
22
|
+
options.tags = coerceStartWorkflowTags(input.tags, 'Field "tags"');
|
|
23
|
+
if (input.idempotencyKey !== void 0)
|
|
24
|
+
options.idempotencyKey = coerceStartWorkflowIdempotencyKey(input.idempotencyKey, 'Field "idempotencyKey"');
|
|
25
|
+
if (input.searchAttributes !== void 0)
|
|
26
|
+
options.searchAttributes = coerceStartWorkflowSearchAttributes(input.searchAttributes, 'Field "searchAttributes"', searchAttributeSchema);
|
|
27
|
+
assertExclusiveStartWorkflowOptions(options.startAt, options.startAfter);
|
|
28
|
+
return options;
|
|
29
|
+
}
|
|
30
|
+
export function coerceStartWorkflowSearchAttributes(value, fieldName, schema) {
|
|
31
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
32
|
+
throw new StartWorkflowValidationError(`${fieldName} must be an object`);
|
|
33
|
+
const attributes = Object.create(null);
|
|
34
|
+
for (const [key, attributeValue] of Object.entries(value))
|
|
35
|
+
attributes[key] = coerceStartWorkflowSearchAttributeValue(key, attributeValue, fieldName, schema);
|
|
36
|
+
return attributes;
|
|
37
|
+
}
|
|
38
|
+
function coerceStartWorkflowSearchAttributeValue(key, value, fieldName, schema) {
|
|
39
|
+
if (!isSearchAttributeValue(value))
|
|
40
|
+
throw new StartWorkflowValidationError(`${fieldName}.${key} must be a string, number, boolean, Date, or string array`);
|
|
41
|
+
if (schema === void 0)
|
|
42
|
+
return value;
|
|
43
|
+
const definition = schema[key];
|
|
44
|
+
if (definition === void 0)
|
|
45
|
+
throw new StartWorkflowValidationError(`Unknown search attribute "${key}". Registered attributes: ${Object.keys(schema).join(", ")}`);
|
|
46
|
+
const normalizedValue = definition.type === "string" && definition.format === "date-time" && typeof value === "string" ? coerceDateTimeSearchAttribute(key, value, fieldName) : value;
|
|
47
|
+
try {
|
|
48
|
+
validateAttributeType(key, normalizedValue, definition);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
throw new StartWorkflowValidationError(message);
|
|
52
|
+
}
|
|
53
|
+
return normalizedValue;
|
|
54
|
+
}
|
|
55
|
+
function coerceDateTimeSearchAttribute(key, value, fieldName) {
|
|
56
|
+
const date = new Date(value);
|
|
57
|
+
if (Number.isNaN(date.getTime()))
|
|
58
|
+
throw new StartWorkflowValidationError(`${fieldName}.${key} must be a valid date-time string`);
|
|
59
|
+
return date;
|
|
60
|
+
}
|
|
61
|
+
function isSearchAttributeValue(value) {
|
|
62
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
63
|
+
}
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import {
|
|
3
|
+
IdempotencyKeyPurgedError,
|
|
3
4
|
WorkflowAlreadyExistsError,
|
|
4
5
|
WorkflowNotRegisteredError
|
|
5
6
|
} from "../../core/engine/errors.js";
|
|
6
7
|
import { runtimeWorkflowEngine } from "../../core/runtime-workflow-engine.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
assertExclusiveStartWorkflowOptions,
|
|
10
|
-
coerceStartWorkflowDuration,
|
|
11
|
-
coerceStartWorkflowId,
|
|
12
|
-
coerceStartWorkflowTags,
|
|
13
|
-
coerceStartWorkflowTimestamp,
|
|
14
|
-
StartWorkflowValidationError
|
|
15
|
-
} from "../../core/start-workflow-validation.js";
|
|
8
|
+
import { StartWorkflowValidationError } from "../../core/start-workflow-validation.js";
|
|
16
9
|
import { defineOperation } from "../operation-registry.js";
|
|
17
10
|
import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
|
|
11
|
+
import { buildSharedStartWorkflowOptions } from "./start-workflow-options.js";
|
|
18
12
|
const startWorkflowInput = z.object({
|
|
19
13
|
type: z.unknown().describe("Workflow type name. Runtime validation requires a non-empty string."),
|
|
20
14
|
input: z.unknown().optional(),
|
|
@@ -34,7 +28,7 @@ function validateStartWorkflowInput(input, lookupSearchAttributeSchema) {
|
|
|
34
28
|
const type = input.type;
|
|
35
29
|
let options;
|
|
36
30
|
try {
|
|
37
|
-
options =
|
|
31
|
+
options = buildSharedStartWorkflowOptions(input, lookupSearchAttributeSchema(type));
|
|
38
32
|
} catch (error) {
|
|
39
33
|
const message = error instanceof Error ? error.message : String(error);
|
|
40
34
|
throw invalidParamsFault(message);
|
|
@@ -45,7 +39,7 @@ function resolveStartWorkflowAccess(error) {
|
|
|
45
39
|
const message = error instanceof Error ? error.message : String(error);
|
|
46
40
|
if (error instanceof WorkflowNotRegisteredError)
|
|
47
41
|
throw invalidParamsFault(message);
|
|
48
|
-
if (error instanceof WorkflowAlreadyExistsError)
|
|
42
|
+
if (error instanceof WorkflowAlreadyExistsError || error instanceof IdempotencyKeyPurgedError)
|
|
49
43
|
throw {
|
|
50
44
|
code: "Conflict",
|
|
51
45
|
message,
|
|
@@ -63,7 +57,7 @@ export const startWorkflowOperation = defineOperation({
|
|
|
63
57
|
name: "weft.workflows.start",
|
|
64
58
|
mcpExposable: !1,
|
|
65
59
|
summary: "Start a new workflow",
|
|
66
|
-
description: "Start a new workflow execution of a registered type. Requires `type` (the registered workflow type name) and accepts an optional `input` payload plus start options: `id` (client-supplied workflow id), `executionTimeout`, `startAt`/`startAfter` (mutually exclusive scheduling), `tags`, and `searchAttributes`. Returns the workflow `id`. Faults with InvalidParams for an unregistered type or malformed options, and Conflict when a workflow with the same id already exists.",
|
|
60
|
+
description: "Start a new workflow execution of a registered type. Requires `type` (the registered workflow type name) and accepts an optional `input` payload plus start options: `id` (client-supplied workflow id), `executionTimeout`, `startAt`/`startAfter` (mutually exclusive scheduling), `tags`, `idempotencyKey` (at-most-once dedup: a repeated key returns the existing run instead of starting a second), and `searchAttributes`. Returns the workflow `id`. Faults with InvalidParams for an unregistered type or malformed options, and Conflict when a workflow with the same id already exists or the supplied `idempotencyKey` has been spent (its run was purged or swept by retention).",
|
|
67
61
|
destructive: !1,
|
|
68
62
|
tags: ["Workflows"],
|
|
69
63
|
inputSchema: startWorkflowInput,
|
|
@@ -82,63 +76,7 @@ export const startWorkflowOperation = defineOperation({
|
|
|
82
76
|
resolveStartWorkflowAccess(error);
|
|
83
77
|
}
|
|
84
78
|
}
|
|
85
|
-
})
|
|
86
|
-
function buildStartWorkflowOptions(input, searchAttributeSchema) {
|
|
87
|
-
const options = {};
|
|
88
|
-
if (input.id !== void 0)
|
|
89
|
-
options.id = coerceStartWorkflowId(input.id, 'Field "id"');
|
|
90
|
-
if (input.executionTimeout !== void 0)
|
|
91
|
-
options.executionTimeout = coerceStartWorkflowDuration(input.executionTimeout, 'Field "executionTimeout"');
|
|
92
|
-
if (input.startAt !== void 0)
|
|
93
|
-
options.startAt = coerceStartWorkflowTimestamp(input.startAt, 'Field "startAt"');
|
|
94
|
-
if (input.startAfter !== void 0)
|
|
95
|
-
options.startAfter = coerceStartWorkflowDuration(input.startAfter, 'Field "startAfter"');
|
|
96
|
-
if (input.tags !== void 0)
|
|
97
|
-
options.tags = coerceStartWorkflowTags(input.tags, 'Field "tags"');
|
|
98
|
-
if (input.idempotencyKey !== void 0)
|
|
99
|
-
throw new StartWorkflowValidationError("idempotencyKey is not supported over HttpClient because the start workflow HTTP protocol does not implement start idempotency");
|
|
100
|
-
if (input.searchAttributes !== void 0)
|
|
101
|
-
options.searchAttributes = coerceStartWorkflowSearchAttributes(input.searchAttributes, 'Field "searchAttributes"', searchAttributeSchema);
|
|
102
|
-
assertExclusiveStartWorkflowOptions(options.startAt, options.startAfter);
|
|
103
|
-
return options;
|
|
104
|
-
}
|
|
105
|
-
function coerceStartWorkflowSearchAttributes(value, fieldName, schema) {
|
|
106
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
107
|
-
throw new StartWorkflowValidationError(`${fieldName} must be an object`);
|
|
108
|
-
const attributes = Object.create(null);
|
|
109
|
-
for (const [key, attributeValue] of Object.entries(value)) {
|
|
110
|
-
const coercedValue = coerceStartWorkflowSearchAttributeValue(key, attributeValue, fieldName, schema);
|
|
111
|
-
attributes[key] = coercedValue;
|
|
112
|
-
}
|
|
113
|
-
return attributes;
|
|
114
|
-
}
|
|
115
|
-
function coerceStartWorkflowSearchAttributeValue(key, value, fieldName, schema) {
|
|
116
|
-
if (!isSearchAttributeValue(value))
|
|
117
|
-
throw new StartWorkflowValidationError(`${fieldName}.${key} must be a string, number, boolean, Date, or string array`);
|
|
118
|
-
if (schema === void 0)
|
|
119
|
-
return value;
|
|
120
|
-
const definition = schema[key];
|
|
121
|
-
if (definition === void 0)
|
|
122
|
-
throw new StartWorkflowValidationError(`Unknown search attribute "${key}". Registered attributes: ${Object.keys(schema).join(", ")}`);
|
|
123
|
-
const normalizedValue = definition.type === "string" && definition.format === "date-time" && typeof value === "string" ? coerceDateTimeSearchAttribute(key, value, fieldName) : value;
|
|
124
|
-
try {
|
|
125
|
-
validateAttributeType(key, normalizedValue, definition);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
128
|
-
throw new StartWorkflowValidationError(message);
|
|
129
|
-
}
|
|
130
|
-
return normalizedValue;
|
|
131
|
-
}
|
|
132
|
-
function coerceDateTimeSearchAttribute(key, value, fieldName) {
|
|
133
|
-
const date = new Date(value);
|
|
134
|
-
if (Number.isNaN(date.getTime()))
|
|
135
|
-
throw new StartWorkflowValidationError(`${fieldName}.${key} must be a valid date-time string`);
|
|
136
|
-
return date;
|
|
137
|
-
}
|
|
138
|
-
function isSearchAttributeValue(value) {
|
|
139
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
140
|
-
}
|
|
141
|
-
export const startWorkflowRestBinding = {
|
|
79
|
+
}), startWorkflowRestBinding = {
|
|
142
80
|
method: "POST",
|
|
143
81
|
path: "/v1/workflows",
|
|
144
82
|
pathParamNames: [],
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { UnknownRestBinding } from '../rest-bindings.ts';
|
|
3
|
+
declare const suspendWorkflowInput: z.ZodObject<{
|
|
4
|
+
workflowId: z.ZodString;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
declare const suspendWorkflowOutput: z.ZodUndefined;
|
|
7
|
+
export type SuspendWorkflowInput = z.infer<typeof suspendWorkflowInput>;
|
|
8
|
+
export type SuspendWorkflowOutput = z.infer<typeof suspendWorkflowOutput>;
|
|
9
|
+
export declare const suspendWorkflowOperation: import("../operation-catalog.ts").OperationDefinition<{
|
|
10
|
+
workflowId: string;
|
|
11
|
+
}, undefined>;
|
|
12
|
+
export declare const suspendWorkflowRestBinding: UnknownRestBinding;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { WorkflowSuspendNotSupportedError } from "../../core/engine/errors.js";
|
|
3
|
+
import { shapeRestFault } from "./operation-helpers.js";
|
|
4
|
+
import {
|
|
5
|
+
createSingleWorkflowControlOperation,
|
|
6
|
+
extractWorkflowIdFromPath
|
|
7
|
+
} from "./single-workflow-control-operation.js";
|
|
8
|
+
const suspendWorkflowInput = z.object({
|
|
9
|
+
workflowId: z.string().min(1)
|
|
10
|
+
}), suspendWorkflowOutput = z.undefined();
|
|
11
|
+
export const suspendWorkflowOperation = createSingleWorkflowControlOperation({
|
|
12
|
+
name: "weft.workflows.suspend",
|
|
13
|
+
summary: "Suspend a running workflow",
|
|
14
|
+
description: "Request suspension of a running workflow by `id` without terminating it. The workflow transitions to the non-terminal `suspended` status, keeps its durable checkpoint, and is later resumable via `resume`. Unlike cancellation, suspension does not run cancel handlers and does not settle the result. Suspending a workflow that is not running is a no-op. Faults with Unprocessable when the engine runs in worker execution mode (which cannot pause a run without cancelling it).",
|
|
15
|
+
destructive: !1,
|
|
16
|
+
tags: ["Workflows"],
|
|
17
|
+
inputSchema: suspendWorkflowInput,
|
|
18
|
+
outputSchema: suspendWorkflowOutput,
|
|
19
|
+
producibleFaults: ["Unprocessable"],
|
|
20
|
+
invoke: async ({ input, engine }) => {
|
|
21
|
+
await engine.suspend(input.workflowId);
|
|
22
|
+
return;
|
|
23
|
+
},
|
|
24
|
+
mapErrorToFault: ({ error, message }) => error instanceof WorkflowSuspendNotSupportedError ? { code: "Unprocessable", message, data: { reason: message } } : void 0
|
|
25
|
+
}), suspendWorkflowRestBinding = {
|
|
26
|
+
method: "POST",
|
|
27
|
+
path: "/v1/workflows/:id/suspend",
|
|
28
|
+
pathParamNames: ["id"],
|
|
29
|
+
operationName: "weft.workflows.suspend",
|
|
30
|
+
inputSources: {
|
|
31
|
+
workflowId: { kind: "path", pathParam: "id" }
|
|
32
|
+
},
|
|
33
|
+
extractInput: async (_request, pathParams) => extractWorkflowIdFromPath(pathParams),
|
|
34
|
+
success: { kind: "empty", status: 204 },
|
|
35
|
+
shapeFault: shapeRestFault
|
|
36
|
+
};
|
|
@@ -111,14 +111,25 @@ export type RestBinding<Input, Output> = {
|
|
|
111
111
|
readonly shapeSuccess?: (output: Output, request: Request) => Response;
|
|
112
112
|
/**
|
|
113
113
|
* Optional override for fault → HTTP response mapping. When absent,
|
|
114
|
-
* the transport adapter falls back to `faultToHttpResponse
|
|
115
|
-
*
|
|
114
|
+
* the transport adapter falls back to `faultToHttpResponse`, which emits
|
|
115
|
+
* the REST fault body `{ error: { code, message, data? } }`. This is
|
|
116
|
+
* distinct from the JSON-RPC fault object (`faultToJsonRpcError`): JSON-RPC
|
|
117
|
+
* uses a flat `{ code, message, data }` with a numeric `code` and the
|
|
118
|
+
* symbolic name relocated to `data.weftCode`. REST and JSON-RPC deliberately
|
|
119
|
+
* differ in fault shape, so each transport owns its own projection.
|
|
116
120
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
121
|
+
* REST operations provide this to shape faults the way a REST client
|
|
122
|
+
* expects: most use `shapeRestFault`, which masks an `EngineFailure` to a
|
|
123
|
+
* flat `{ error: "Internal server error" }` with status `500` (never
|
|
124
|
+
* leaking internal detail over REST) and maps the remaining fault codes to
|
|
125
|
+
* their HTTP statuses. A few operations supply a bespoke shaper to special-case
|
|
126
|
+
* a particular fault — typically to override its message or to handle one code
|
|
127
|
+
* explicitly — while delegating the rest. The status often matches what the
|
|
128
|
+
* shared map would already produce; the shaper exists for the operation-specific
|
|
129
|
+
* detail (for example, `get-workflow-result` returns the custom message
|
|
130
|
+
* `"Timeout waiting for workflow result"` on a `Timeout`, and `get-stream-chunks`
|
|
131
|
+
* handles `InvalidParams` inline). This per-operation hook is the current
|
|
132
|
+
* contract, not a transitional shim.
|
|
122
133
|
*/
|
|
123
134
|
readonly shapeFault?: (fault: OperationFault) => Response;
|
|
124
135
|
};
|
|
@@ -139,6 +139,10 @@ import {
|
|
|
139
139
|
signalWorkflowOperation,
|
|
140
140
|
signalWorkflowRestBinding
|
|
141
141
|
} from "./operations/signal-workflow.js";
|
|
142
|
+
import {
|
|
143
|
+
startOrSignalWorkflowOperation,
|
|
144
|
+
startOrSignalWorkflowRestBinding
|
|
145
|
+
} from "./operations/start-or-signal-workflow.js";
|
|
142
146
|
import { startWorkflowOperation, startWorkflowRestBinding } from "./operations/start-workflow.js";
|
|
143
147
|
import {
|
|
144
148
|
storageBatchOperation,
|
|
@@ -162,6 +166,10 @@ import {
|
|
|
162
166
|
submitReviewDecisionOperation,
|
|
163
167
|
submitReviewDecisionRestBinding
|
|
164
168
|
} from "./operations/submit-review-decision.js";
|
|
169
|
+
import {
|
|
170
|
+
suspendWorkflowOperation,
|
|
171
|
+
suspendWorkflowRestBinding
|
|
172
|
+
} from "./operations/suspend-workflow.js";
|
|
165
173
|
import {
|
|
166
174
|
timeoutWorkflowOperation,
|
|
167
175
|
timeoutWorkflowRestBinding
|
|
@@ -191,6 +199,7 @@ import {
|
|
|
191
199
|
import { workflowEventsSubscriptionOperation } from "./operations/workflow-events-subscription.js";
|
|
192
200
|
export const REST_BINDINGS = [
|
|
193
201
|
startWorkflowRestBinding,
|
|
202
|
+
startOrSignalWorkflowRestBinding,
|
|
194
203
|
recoverAllRestBinding,
|
|
195
204
|
listWorkflowsRestBinding,
|
|
196
205
|
aggregateWorkflowsRestBinding,
|
|
@@ -211,6 +220,7 @@ export const REST_BINDINGS = [
|
|
|
211
220
|
queryWorkflowRestBinding,
|
|
212
221
|
queryWorkflowWithInputRestBinding,
|
|
213
222
|
resumeWorkflowRestBinding,
|
|
223
|
+
suspendWorkflowRestBinding,
|
|
214
224
|
forkWorkflowRestBinding,
|
|
215
225
|
timeoutWorkflowRestBinding,
|
|
216
226
|
updateWorkflowRestBinding,
|
|
@@ -314,6 +324,7 @@ export function createLiveOperationRegistry(options) {
|
|
|
314
324
|
const resolved = options ?? {};
|
|
315
325
|
return createOperationRegistry([
|
|
316
326
|
startWorkflowOperation,
|
|
327
|
+
startOrSignalWorkflowOperation,
|
|
317
328
|
recoverAllOperation,
|
|
318
329
|
listWorkflowsOperation,
|
|
319
330
|
aggregateWorkflowsOperation,
|
|
@@ -333,6 +344,7 @@ export function createLiveOperationRegistry(options) {
|
|
|
333
344
|
failAsyncActivityOperation,
|
|
334
345
|
queryWorkflowOperation,
|
|
335
346
|
resumeWorkflowOperation,
|
|
347
|
+
suspendWorkflowOperation,
|
|
336
348
|
forkWorkflowOperation,
|
|
337
349
|
timeoutWorkflowOperation,
|
|
338
350
|
updateWorkflowOperation,
|
|
@@ -59,8 +59,8 @@ async function selectAndReserveWorker(context, options, task, queue, visibilityT
|
|
|
59
59
|
const ws = context.workerSockets.get(worker.id);
|
|
60
60
|
if (!ws)
|
|
61
61
|
return !1;
|
|
62
|
-
const now = Date.now(), existingQueuedRecord = await readQueuedRecord(options.engine.storage, task.operationId);
|
|
63
|
-
context.registry.assignTask(worker.id, task.operationId, visibilityTimeout, task.fairShareKey);
|
|
62
|
+
const now = Date.now(), existingQueuedRecord = await readQueuedRecord(options.engine.storage, task.operationId), attemptToken = crypto.randomUUID();
|
|
63
|
+
context.registry.assignTask(worker.id, task.operationId, visibilityTimeout, task.fairShareKey, attemptToken);
|
|
64
64
|
const deadline = now + visibilityTimeout;
|
|
65
65
|
context.deadlineTracker.add({ operationId: task.operationId, deadline });
|
|
66
66
|
const inflightRecord = {
|
|
@@ -73,7 +73,8 @@ async function selectAndReserveWorker(context, options, task, queue, visibilityT
|
|
|
73
73
|
attempt: task.attempt ?? 1,
|
|
74
74
|
visibilityTimeout,
|
|
75
75
|
retryPolicy: task.retryPolicy,
|
|
76
|
-
workflowId: task.workflowId
|
|
76
|
+
workflowId: task.workflowId,
|
|
77
|
+
attemptToken
|
|
77
78
|
}, normalizedInflightRecord = await transitionQueuedToInflight(options.engine.storage, task.operationId, inflightRecord, {
|
|
78
79
|
queuedRecord: existingQueuedRecord,
|
|
79
80
|
now
|
|
@@ -84,6 +85,7 @@ async function selectAndReserveWorker(context, options, task, queue, visibilityT
|
|
|
84
85
|
activityName: task.activityName,
|
|
85
86
|
input: task.input === void 0 ? null : task.input,
|
|
86
87
|
attempt: task.attempt ?? 1,
|
|
88
|
+
attemptToken,
|
|
87
89
|
...task.headers ? { headers: task.headers } : {}
|
|
88
90
|
}));
|
|
89
91
|
recordTaskQueueLatencyMetric(context.metricsCollector, normalizedInflightRecord);
|
|
@@ -3,7 +3,21 @@ import { type Principal } from '../principal.ts';
|
|
|
3
3
|
import type { PendingTask } from '../task-queue-types.ts';
|
|
4
4
|
import type { InflightRecord } from '../task-state.ts';
|
|
5
5
|
import type { ServerContext } from './context.ts';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* A freshly created long-poll inflight record always carries an `attemptToken`.
|
|
8
|
+
* The intersection makes that invariant a compile-time fact so the claim can read
|
|
9
|
+
* the token directly without an unreachable empty-string fallback.
|
|
10
|
+
*/
|
|
11
|
+
type TokenedInflightRecord = InflightRecord & {
|
|
12
|
+
attemptToken: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function createLongPollInflightRecord(queue: string, task: PendingTask): TokenedInflightRecord;
|
|
15
|
+
/** The worker-facing identity of a long-poll claim: the synthetic worker id and its per-claim token. */
|
|
16
|
+
export interface LongPollClaim {
|
|
17
|
+
workerId: string;
|
|
18
|
+
attemptToken: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function markTaskClaimedByLongPollWorker(context: ServerContext, options: ServeOptions, queue: string, task: PendingTask): Promise<LongPollClaim>;
|
|
8
21
|
export declare function handleTaskPollRequest(context: ServerContext, options: ServeOptions, request: Request, url: URL, principal?: Principal): Promise<Response | null>;
|
|
9
22
|
export declare function handleTaskResultRequest(context: ServerContext, options: ServeOptions, request: Request, url: URL, principal?: Principal): Promise<Response | null>;
|
|
23
|
+
export {};
|