@temelj/workflow 0.0.0-alpha.1 → 0.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.d.mts +29 -0
- package/dist/bundle.mjs +28 -0
- package/dist/client/create.d.mts +10 -2
- package/dist/client/create.mjs +5 -3
- package/dist/client/facades.mjs +6 -3
- package/dist/client/run-handle.d.mts +1 -0
- package/dist/client/run-handle.mjs +3 -1
- package/dist/cloudflare/cloudflare.d.mts +12 -0
- package/dist/cloudflare/codec.d.mts +16 -0
- package/dist/cloudflare/codec.mjs +25 -0
- package/dist/cloudflare/entrypoint.d.mts +12 -0
- package/dist/cloudflare/entrypoint.mjs +18 -0
- package/dist/cloudflare/execution.d.mts +13 -0
- package/dist/cloudflare/execution.mjs +28 -0
- package/dist/cloudflare/host.d.mts +19 -0
- package/dist/cloudflare/host.mjs +24 -0
- package/dist/cloudflare/mod.d.mts +8 -0
- package/dist/cloudflare/mod.mjs +7 -0
- package/dist/cloudflare/name.d.mts +5 -0
- package/dist/cloudflare/name.mjs +22 -0
- package/dist/cloudflare/registry.d.mts +24 -0
- package/dist/cloudflare/registry.mjs +26 -0
- package/dist/cloudflare/runtime.d.mts +30 -0
- package/dist/cloudflare/runtime.mjs +226 -0
- package/dist/cloudflare/step.d.mts +22 -0
- package/dist/cloudflare/step.mjs +160 -0
- package/dist/cloudflare/types.d.mts +87 -0
- package/dist/cloudflare/types.mjs +1 -0
- package/dist/engine/inspection-api.mjs +2 -1
- package/dist/engine/inspection.mjs +7 -5
- package/dist/engine/message.mjs +16 -13
- package/dist/engine/observation.mjs +1 -3
- package/dist/engine/run-data.mjs +2 -1
- package/dist/engine/run-service.mjs +2 -1
- package/dist/engine/start-api.mjs +2 -1
- package/dist/engine/state.mjs +2 -1
- package/dist/engine/stream.d.mts +1 -0
- package/dist/engine/stream.mjs +1 -0
- package/dist/engine/task.mjs +2 -1
- package/dist/errors/definition.d.mts +1 -0
- package/dist/errors/definition.mjs +3 -0
- package/dist/errors/details.mjs +2 -1
- package/dist/history/replay-projection.mjs +1 -3
- package/dist/mod.d.mts +2 -1
- package/dist/mod.mjs +2 -1
- package/dist/otel.mjs +4 -2
- package/dist/runtime.d.mts +1 -0
- package/dist/runtime.mjs +7 -3
- package/dist/store/cleanup.mjs +3 -26
- package/dist/store/message-index.mjs +2 -1
- package/dist/store/query.mjs +5 -3
- package/dist/store/run.mjs +9 -6
- package/dist/store/schedule.mjs +5 -3
- package/dist/store/step-attempt.mjs +5 -3
- package/dist/store-keys.d.mts +2 -1
- package/dist/store-keys.mjs +24 -1
- package/dist/types/common.d.mts +1 -0
- package/dist/types/common.mjs +1 -0
- package/dist/types/runtime.d.mts +10 -2
- package/dist/worker/error.d.mts +1 -0
- package/dist/worker/error.mjs +1 -0
- package/dist/worker.d.mts +1 -0
- package/dist/worker.mjs +1 -0
- package/package.json +15 -10
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { WorkflowResultTimeoutError, WorkflowRunCanceledError } from "../errors/run.mjs";
|
|
2
|
+
import { durationTotal } from "../utility.mjs";
|
|
3
|
+
import { WorkflowCapabilityError } from "../errors/capability.mjs";
|
|
4
|
+
import { WorkflowDefinitionError } from "../errors/definition.mjs";
|
|
5
|
+
import "../errors/mod.mjs";
|
|
6
|
+
import { getCompiledWorkflowDefinition } from "../compiled-function.mjs";
|
|
7
|
+
import { Registry } from "../registry.mjs";
|
|
8
|
+
import { parseWorkflowInput } from "../definition.mjs";
|
|
9
|
+
import { resolveSendMessageInput } from "../message-options.mjs";
|
|
10
|
+
import { decodeCloudflareWorkflowValue, encodeCloudflareWorkflowValue } from "./codec.mjs";
|
|
11
|
+
import { resolveCloudflareWorkflowImplementation } from "./registry.mjs";
|
|
12
|
+
import { makeCloudflareWorkflowEventType } from "./name.mjs";
|
|
13
|
+
//#region src/cloudflare/runtime.ts
|
|
14
|
+
const DEFAULT_RESULT_POLL_INTERVAL = 100;
|
|
15
|
+
const CLOUDFLARE_INSTANCE_ID_PATTERN = /^[a-zA-Z0-9_][a-zA-Z0-9-_]*$/;
|
|
16
|
+
/** Creates a runtime facade over a generic Cloudflare Workflow binding. */
|
|
17
|
+
function createCloudflareWorkflowRuntime(options) {
|
|
18
|
+
return new CloudflareWorkflowRuntimeImpl(options);
|
|
19
|
+
}
|
|
20
|
+
var CloudflareWorkflowRuntimeImpl = class {
|
|
21
|
+
runs;
|
|
22
|
+
messages;
|
|
23
|
+
schedules = unsupportedApi("Cloudflare workflow schedules");
|
|
24
|
+
locks = unsupportedApi("Cloudflare workflow locks");
|
|
25
|
+
workers = unsupportedApi("Cloudflare workflow workers");
|
|
26
|
+
#binding;
|
|
27
|
+
#registry;
|
|
28
|
+
#registration;
|
|
29
|
+
#resultPollInterval;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#binding = options.binding;
|
|
32
|
+
this.#registry = options.registry ?? new Registry();
|
|
33
|
+
this.#registration = options.registration ?? "dynamic";
|
|
34
|
+
this.#resultPollInterval = options.resultPollInterval === void 0 ? DEFAULT_RESULT_POLL_INTERVAL : durationMilliseconds(options.resultPollInterval, "Cloudflare result poll interval");
|
|
35
|
+
this.runs = {
|
|
36
|
+
getHandle: async () => WorkflowCapabilityError.unsupported("Cloudflare run lookup without a workflow target; use runtime.runs.get(workflow, runId)"),
|
|
37
|
+
list: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow run listing"),
|
|
38
|
+
get: async (workflow, runId) => this.#createHandle(workflow, runId)
|
|
39
|
+
};
|
|
40
|
+
this.messages = { send: async (runId, input, channelOptions) => {
|
|
41
|
+
const message = await resolveSendMessageInput(input, channelOptions);
|
|
42
|
+
await (await this.#binding.get(runId)).sendEvent({
|
|
43
|
+
type: makeCloudflareWorkflowEventType(message.messageId),
|
|
44
|
+
payload: encodeCloudflareWorkflowValue(message.payload)
|
|
45
|
+
});
|
|
46
|
+
} };
|
|
47
|
+
}
|
|
48
|
+
async close() {}
|
|
49
|
+
implementWorkflow(definition, handler) {
|
|
50
|
+
if (this.#registration === "static") {
|
|
51
|
+
const implementation = this.#registry.get(definition);
|
|
52
|
+
if (implementation === void 0 || implementation.handler !== handler) WorkflowDefinitionError.implementationNotBundled(workflowImplementationKey(definition.name, definition.version));
|
|
53
|
+
return implementation;
|
|
54
|
+
}
|
|
55
|
+
return this.#registry.implementWorkflow(definition, handler);
|
|
56
|
+
}
|
|
57
|
+
register(implementation) {
|
|
58
|
+
if (this.#registry.get(implementation.definition) !== implementation) {
|
|
59
|
+
if (this.#registration === "static") WorkflowDefinitionError.implementationNotBundled(workflowImplementationKey(implementation.name, implementation.version));
|
|
60
|
+
this.#registry.register(implementation);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async start(workflow, ...args) {
|
|
64
|
+
if (typeof workflow === "function") {
|
|
65
|
+
const implementation = getCompiledWorkflowDefinition(workflow);
|
|
66
|
+
if (implementation === void 0) WorkflowDefinitionError.compiledMetadataMissing();
|
|
67
|
+
this.register(implementation);
|
|
68
|
+
return await this.#startTarget(implementation, args, void 0);
|
|
69
|
+
}
|
|
70
|
+
const target = workflow;
|
|
71
|
+
if (target.kind === "implementation") this.register(target);
|
|
72
|
+
return await this.#startTarget(target, args[0], args[1]);
|
|
73
|
+
}
|
|
74
|
+
async #startTarget(target, rawInput, startOptions) {
|
|
75
|
+
rejectUnsupportedStartOptions(startOptions);
|
|
76
|
+
const requestedVersion = startOptions?.workflowVersion ?? target.version;
|
|
77
|
+
const implementation = resolveCloudflareWorkflowImplementation(this.#registry, target.name, requestedVersion);
|
|
78
|
+
if (implementation.config.retry !== void 0) WorkflowCapabilityError.unsupported("Cloudflare workflow-level retry configuration");
|
|
79
|
+
const input = await parseWorkflowInput(implementation.definition, rawInput);
|
|
80
|
+
const envelope = {
|
|
81
|
+
kind: "temelj-workflow",
|
|
82
|
+
version: 1,
|
|
83
|
+
workflowName: implementation.name,
|
|
84
|
+
...implementation.version === void 0 ? {} : { workflowVersion: implementation.version },
|
|
85
|
+
input: encodeCloudflareWorkflowValue(input)
|
|
86
|
+
};
|
|
87
|
+
const id = await resolveInstanceId(implementation.name, implementation.version, startOptions);
|
|
88
|
+
const createdAt = Temporal.Now.instant();
|
|
89
|
+
let instance;
|
|
90
|
+
if (startOptions?.idempotencyKey === void 0) instance = await this.#binding.create({
|
|
91
|
+
...id === void 0 ? {} : { id },
|
|
92
|
+
params: envelope
|
|
93
|
+
});
|
|
94
|
+
else {
|
|
95
|
+
if (id === void 0) throw new TypeError("Cloudflare idempotent workflow start requires a resolved instance id");
|
|
96
|
+
instance = (await this.#binding.createBatch([{
|
|
97
|
+
id,
|
|
98
|
+
params: envelope
|
|
99
|
+
}]))[0] ?? await this.#binding.get(id);
|
|
100
|
+
}
|
|
101
|
+
return this.#handle(implementation.definition, instance, createdAt);
|
|
102
|
+
}
|
|
103
|
+
async #createHandle(workflow, runId) {
|
|
104
|
+
const implementation = resolveCloudflareWorkflowImplementation(this.#registry, workflow.name, workflow.version);
|
|
105
|
+
const instance = await this.#binding.get(runId);
|
|
106
|
+
return this.#handle(implementation.definition, instance, Temporal.Now.instant());
|
|
107
|
+
}
|
|
108
|
+
#handle(definition, instance, createdAt) {
|
|
109
|
+
const status = async () => await instance.status();
|
|
110
|
+
const record = async () => workflowRunRecord(definition, instance.id, createdAt, await status());
|
|
111
|
+
return {
|
|
112
|
+
runId: instance.id,
|
|
113
|
+
workflowName: definition.name,
|
|
114
|
+
...definition.version === void 0 ? {} : { workflowVersion: definition.version },
|
|
115
|
+
getRun: record,
|
|
116
|
+
status: async () => cloudflareRunStatus((await status()).status),
|
|
117
|
+
events: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow event history"),
|
|
118
|
+
timeline: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow timeline"),
|
|
119
|
+
result: async (options) => await cloudflareWorkflowResult(instance, options, this.#resultPollInterval),
|
|
120
|
+
getStreamInfo: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow streams"),
|
|
121
|
+
followStream: () => WorkflowCapabilityError.unsupported("Cloudflare workflow streams"),
|
|
122
|
+
waitForStream: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow streams"),
|
|
123
|
+
waitForStreamChunk: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow streams"),
|
|
124
|
+
waitForStreamTerminal: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow streams"),
|
|
125
|
+
readStream: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow streams"),
|
|
126
|
+
readStreamPage: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow streams"),
|
|
127
|
+
reschedule: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow rescheduling"),
|
|
128
|
+
releaseStaleLease: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow worker leases"),
|
|
129
|
+
retryFailed: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow manual retries"),
|
|
130
|
+
markPermanentlyFailed: async () => WorkflowCapabilityError.unsupported("Cloudflare permanent workflow failure"),
|
|
131
|
+
setMetadata: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow metadata"),
|
|
132
|
+
setAttributes: async () => WorkflowCapabilityError.unsupported("Cloudflare workflow attributes"),
|
|
133
|
+
async cancel() {
|
|
134
|
+
if (!isTerminalCloudflareStatus((await instance.status()).status)) await instance.terminate();
|
|
135
|
+
return await record();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function workflowImplementationKey(name, version) {
|
|
141
|
+
return version === void 0 ? name : `${name}@${version}`;
|
|
142
|
+
}
|
|
143
|
+
function isTerminalCloudflareStatus(status) {
|
|
144
|
+
return status === "complete" || status === "errored" || status === "terminated";
|
|
145
|
+
}
|
|
146
|
+
async function cloudflareWorkflowResult(instance, options, defaultPollInterval) {
|
|
147
|
+
const pollInterval = options?.pollInterval === void 0 ? defaultPollInterval : durationMilliseconds(options.pollInterval, "Cloudflare result poll interval");
|
|
148
|
+
const timeout = options?.timeout === void 0 ? void 0 : durationMilliseconds(options.timeout, "Cloudflare result timeout");
|
|
149
|
+
const startedAt = Date.now();
|
|
150
|
+
while (true) {
|
|
151
|
+
const state = await instance.status();
|
|
152
|
+
if (state.status === "complete") return decodeCloudflareWorkflowValue(state.output, `Cloudflare Workflow ${instance.id} output`);
|
|
153
|
+
if (state.status === "errored") {
|
|
154
|
+
const error = new Error(state.error?.message ?? "Cloudflare Workflow failed");
|
|
155
|
+
error.name = state.error?.name ?? "Error";
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
if (state.status === "terminated") WorkflowRunCanceledError.canceled(instance.id);
|
|
159
|
+
if (state.status === "unknown") throw new TypeError("Cloudflare Workflow returned an unknown instance status");
|
|
160
|
+
if (timeout !== void 0 && Date.now() - startedAt >= timeout) WorkflowResultTimeoutError.timedOut(instance.id, Temporal.Duration.from({ milliseconds: timeout }));
|
|
161
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function workflowRunRecord(definition, runId, createdAt, state) {
|
|
165
|
+
const now = Temporal.Now.instant();
|
|
166
|
+
const status = cloudflareRunStatus(state.status);
|
|
167
|
+
return {
|
|
168
|
+
id: runId,
|
|
169
|
+
namespace: "cloudflare",
|
|
170
|
+
workflowName: definition.name,
|
|
171
|
+
...definition.version === void 0 ? {} : { workflowVersion: definition.version },
|
|
172
|
+
status,
|
|
173
|
+
...state.status === "complete" ? { output: decodeCloudflareWorkflowValue(state.output, `Cloudflare Workflow ${runId} output`) } : {},
|
|
174
|
+
...state.error === void 0 ? {} : { error: {
|
|
175
|
+
name: state.error.name,
|
|
176
|
+
message: state.error.message
|
|
177
|
+
} },
|
|
178
|
+
createdAt,
|
|
179
|
+
updatedAt: now,
|
|
180
|
+
lastTransitionAt: now,
|
|
181
|
+
lastTransitionReason: status === "completed" ? "completed" : status === "failed" ? "failed" : status === "canceled" ? "canceled" : status === "waiting" ? "waiting" : status === "running" ? "started" : "created",
|
|
182
|
+
...status === "completed" || status === "failed" || status === "canceled" ? { finishedAt: now } : {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function cloudflareRunStatus(status) {
|
|
186
|
+
switch (status) {
|
|
187
|
+
case "queued": return "pending";
|
|
188
|
+
case "running": return "running";
|
|
189
|
+
case "waiting":
|
|
190
|
+
case "paused":
|
|
191
|
+
case "waitingForPause": return "waiting";
|
|
192
|
+
case "complete": return "completed";
|
|
193
|
+
case "errored": return "failed";
|
|
194
|
+
case "terminated": return "canceled";
|
|
195
|
+
case "unknown": throw new TypeError("Cloudflare Workflow returned an unknown instance status");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function resolveInstanceId(workflowName, workflowVersion, options) {
|
|
199
|
+
if (options?.id !== void 0) {
|
|
200
|
+
validateInstanceId(options.id);
|
|
201
|
+
return options.id;
|
|
202
|
+
}
|
|
203
|
+
if (options?.idempotencyKey === void 0) return;
|
|
204
|
+
const input = new TextEncoder().encode(`${workflowName}\0${workflowVersion ?? ""}\0${options.idempotencyKey}`);
|
|
205
|
+
const id = `temelj-${[...new Uint8Array(await crypto.subtle.digest("SHA-256", input))].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
206
|
+
validateInstanceId(id);
|
|
207
|
+
return id;
|
|
208
|
+
}
|
|
209
|
+
function validateInstanceId(id) {
|
|
210
|
+
if (id.length > 100 || !CLOUDFLARE_INSTANCE_ID_PATTERN.test(id)) throw new TypeError("Cloudflare Workflow instance id must be at most 100 characters and contain only letters, digits, underscores, and hyphens");
|
|
211
|
+
}
|
|
212
|
+
function rejectUnsupportedStartOptions(options) {
|
|
213
|
+
if (options?.availableAt !== void 0 || options?.deadlineAt !== void 0 || options?.context !== void 0 || options?.parent !== void 0 || options?.workflowVersionResolver !== void 0) WorkflowCapabilityError.unsupported("Cloudflare start context, parent, delayed availability, deadline, or version resolver");
|
|
214
|
+
}
|
|
215
|
+
function durationMilliseconds(duration, label) {
|
|
216
|
+
const milliseconds = durationTotal(duration);
|
|
217
|
+
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) throw new RangeError(`${label} must resolve to non-negative whole milliseconds`);
|
|
218
|
+
return milliseconds;
|
|
219
|
+
}
|
|
220
|
+
function unsupportedApi(capability) {
|
|
221
|
+
return new Proxy({}, { get() {
|
|
222
|
+
return () => WorkflowCapabilityError.unsupported(capability);
|
|
223
|
+
} });
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
export { createCloudflareWorkflowRuntime };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { WorkflowContext } from "../types/definition.mjs";
|
|
2
|
+
import { WorkflowStepApi } from "../types/step.mjs";
|
|
3
|
+
import { CloudflareNonRetryableErrorConstructor, CloudflareWorkflowStep } from "./types.mjs";
|
|
4
|
+
import { Logger } from "@temelj/log";
|
|
5
|
+
//#region src/cloudflare/step.d.ts
|
|
6
|
+
interface CreateCloudflareWorkflowStepApiOptions {
|
|
7
|
+
readonly step: CloudflareWorkflowStep;
|
|
8
|
+
readonly logger?: Logger;
|
|
9
|
+
readonly nonRetryableError?: CloudflareNonRetryableErrorConstructor;
|
|
10
|
+
}
|
|
11
|
+
/** Creates the Temelj durable command API backed by one Cloudflare Workflow step object. */
|
|
12
|
+
declare function createCloudflareWorkflowStepApi(options: CreateCloudflareWorkflowStepApiOptions): WorkflowStepApi;
|
|
13
|
+
interface CreateCloudflareWorkflowContextOptions<TInput> extends CreateCloudflareWorkflowStepApiOptions {
|
|
14
|
+
readonly input: TInput;
|
|
15
|
+
readonly runId: string;
|
|
16
|
+
readonly workflowName: string;
|
|
17
|
+
readonly workflowVersion?: string;
|
|
18
|
+
readonly createdAt: Temporal.Instant;
|
|
19
|
+
}
|
|
20
|
+
declare function createCloudflareWorkflowContext<TInput>(options: CreateCloudflareWorkflowContextOptions<TInput>): WorkflowContext<TInput>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { CreateCloudflareWorkflowContextOptions, CreateCloudflareWorkflowStepApiOptions, createCloudflareWorkflowContext, createCloudflareWorkflowStepApi };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { WorkflowNonRetryableError } from "../errors/failures.mjs";
|
|
2
|
+
import { durationTotal } from "../utility.mjs";
|
|
3
|
+
import { WorkflowCapabilityError } from "../errors/capability.mjs";
|
|
4
|
+
import "../errors/mod.mjs";
|
|
5
|
+
import { resolveWaitForMessageInput } from "../message-options.mjs";
|
|
6
|
+
import { resolveWorkflowRetryConfig } from "../engine/retry.mjs";
|
|
7
|
+
import { decodeCloudflareWorkflowValue, encodeCloudflareWorkflowValue } from "./codec.mjs";
|
|
8
|
+
import { makeCloudflareWorkflowEventType, makeCloudflareWorkflowStepName } from "./name.mjs";
|
|
9
|
+
import { validateStandardSchema } from "@temelj/standard-schema";
|
|
10
|
+
import { createNoopLogger } from "@temelj/log";
|
|
11
|
+
//#region src/cloudflare/step.ts
|
|
12
|
+
const DEFAULT_STEP_RETRY = {
|
|
13
|
+
maximumAttempts: 10,
|
|
14
|
+
backoffCoefficient: 2
|
|
15
|
+
};
|
|
16
|
+
const MAXIMUM_CLOUDFLARE_ATTEMPTS = 1e4;
|
|
17
|
+
const MAXIMUM_CLOUDFLARE_DELAY_MILLISECONDS = 31536e6;
|
|
18
|
+
const MAXIMUM_CLOUDFLARE_STEP_TIMEOUT_MILLISECONDS = 18e5;
|
|
19
|
+
const MAXIMUM_EVENT_WAIT_MILLISECONDS = 31536e6;
|
|
20
|
+
/** Creates the Temelj durable command API backed by one Cloudflare Workflow step object. */
|
|
21
|
+
function createCloudflareWorkflowStepApi(options) {
|
|
22
|
+
const logger = options.logger ?? createNoopLogger();
|
|
23
|
+
const runTask = async (config, callback) => {
|
|
24
|
+
const commandName = config.commandId ?? config.name;
|
|
25
|
+
if (commandName === void 0) throw new TypeError("Cloudflare durable task requires a name or commandId");
|
|
26
|
+
const retry = resolveWorkflowRetryConfig(config.retry, DEFAULT_STEP_RETRY, "Cloudflare Workflow step retry");
|
|
27
|
+
const stepName = makeCloudflareWorkflowStepName(commandName);
|
|
28
|
+
const encoded = await options.step.do(stepName, cloudflareStepConfig(config, retry), async (cloudflareContext) => {
|
|
29
|
+
const context = workflowStepContext(commandName, retry, cloudflareContext, logger);
|
|
30
|
+
try {
|
|
31
|
+
return encodeCloudflareWorkflowValue(await callback(context));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error instanceof WorkflowNonRetryableError && options.nonRetryableError !== void 0) throw new options.nonRetryableError(error.message, error.name);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return decodeCloudflareWorkflowValue(encoded, `Cloudflare step ${commandName} result`);
|
|
38
|
+
};
|
|
39
|
+
const deterministic = {
|
|
40
|
+
now: async (commandId = "now") => await runTask({ commandId: `deterministic:${commandId}` }, () => Temporal.Now.instant()),
|
|
41
|
+
random: async (commandId = "random") => await runTask({ commandId: `deterministic:${commandId}` }, () => Math.random()),
|
|
42
|
+
recordedId: async (commandId = "id") => await runTask({ commandId: `deterministic:${commandId}` }, () => crypto.randomUUID()),
|
|
43
|
+
id: async (commandId = "id") => await runTask({ commandId: `deterministic:${commandId}` }, () => crypto.randomUUID()),
|
|
44
|
+
uuid: async (commandId = "uuid") => await runTask({ commandId: `deterministic:${commandId}` }, () => crypto.randomUUID()),
|
|
45
|
+
bytes: async (commandId, length) => await runTask({ commandId: `deterministic:${commandId}` }, () => {
|
|
46
|
+
const bytes = new Uint8Array(length);
|
|
47
|
+
for (let offset = 0; offset < bytes.byteLength; offset += 65536) crypto.getRandomValues(bytes.subarray(offset, offset + 65536));
|
|
48
|
+
return bytes;
|
|
49
|
+
})
|
|
50
|
+
};
|
|
51
|
+
const waitMessage = (async (messageInput, channelOptions) => {
|
|
52
|
+
const wait = resolveWaitForMessageInput(messageInput, channelOptions);
|
|
53
|
+
const timeout = wait.timeout === void 0 ? MAXIMUM_EVENT_WAIT_MILLISECONDS : durationMilliseconds(wait.timeout, "Cloudflare Workflow message timeout");
|
|
54
|
+
if (timeout < 1e3 || timeout > MAXIMUM_EVENT_WAIT_MILLISECONDS) throw new RangeError("Cloudflare Workflow message timeout must be between 1 second and 365 days");
|
|
55
|
+
const event = await options.step.waitForEvent(makeCloudflareWorkflowStepName(wait.commandId ?? wait.name ?? wait.messageId), {
|
|
56
|
+
type: makeCloudflareWorkflowEventType(wait.messageId),
|
|
57
|
+
timeout
|
|
58
|
+
});
|
|
59
|
+
const decoded = decodeCloudflareWorkflowValue(event.payload, `Cloudflare message ${wait.messageId} payload`);
|
|
60
|
+
if (wait.schema === void 0) return decoded;
|
|
61
|
+
return await validateStandardSchema(wait.schema, decoded, "Workflow message validation failed");
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
log: logger,
|
|
65
|
+
task: {
|
|
66
|
+
run: runTask,
|
|
67
|
+
async call(definition, ...args) {
|
|
68
|
+
return await runTask(definition.config, async (context) => {
|
|
69
|
+
return await definition.handler(...args, context);
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
async sleep(commandId, duration) {
|
|
73
|
+
await options.step.sleep(makeCloudflareWorkflowStepName(commandId), durationMilliseconds(duration, "Cloudflare Workflow sleep"));
|
|
74
|
+
},
|
|
75
|
+
async sleepUntil(commandId, timestamp) {
|
|
76
|
+
await options.step.sleepUntil(makeCloudflareWorkflowStepName(commandId), timestamp.epochMilliseconds);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
workflow: unsupportedFacet("child workflows"),
|
|
80
|
+
message: {
|
|
81
|
+
send: async () => WorkflowCapabilityError.unsupported("Cloudflare in-workflow message send"),
|
|
82
|
+
wait: waitMessage
|
|
83
|
+
},
|
|
84
|
+
lock: unsupportedFacet("Cloudflare workflow locks"),
|
|
85
|
+
runData: unsupportedFacet("Cloudflare workflow run data"),
|
|
86
|
+
deterministic,
|
|
87
|
+
stream: unsupportedFacet("Cloudflare workflow streams"),
|
|
88
|
+
hook: unsupportedFacet("Cloudflare workflow hooks")
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function createCloudflareWorkflowContext(options) {
|
|
92
|
+
const logger = options.logger ?? createNoopLogger();
|
|
93
|
+
const step = createCloudflareWorkflowStepApi({
|
|
94
|
+
...options,
|
|
95
|
+
logger
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
input: options.input,
|
|
99
|
+
step,
|
|
100
|
+
deterministic: step.deterministic,
|
|
101
|
+
log: logger,
|
|
102
|
+
run: {
|
|
103
|
+
id: options.runId,
|
|
104
|
+
workflowName: options.workflowName,
|
|
105
|
+
...options.workflowVersion === void 0 ? {} : { workflowVersion: options.workflowVersion },
|
|
106
|
+
createdAt: options.createdAt
|
|
107
|
+
},
|
|
108
|
+
signal: new AbortController().signal,
|
|
109
|
+
version: options.workflowVersion
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function cloudflareStepConfig(config, retry) {
|
|
113
|
+
if (retry.maximumAttempts === 0 || retry.maximumAttempts > MAXIMUM_CLOUDFLARE_ATTEMPTS) throw new RangeError(`Cloudflare Workflow steps support at most ${MAXIMUM_CLOUDFLARE_ATTEMPTS} attempts`);
|
|
114
|
+
const initialDelay = retry.initialInterval === void 0 ? 0 : durationMilliseconds(retry.initialInterval, "Cloudflare Workflow retry interval");
|
|
115
|
+
const maximumDelay = retry.maximumInterval === void 0 ? void 0 : durationMilliseconds(retry.maximumInterval, "Cloudflare Workflow maximum retry interval");
|
|
116
|
+
assertMaximumDuration(initialDelay, MAXIMUM_CLOUDFLARE_DELAY_MILLISECONDS, "Cloudflare Workflow retry interval");
|
|
117
|
+
if (maximumDelay !== void 0) assertMaximumDuration(maximumDelay, MAXIMUM_CLOUDFLARE_DELAY_MILLISECONDS, "Cloudflare Workflow maximum retry interval");
|
|
118
|
+
const timeout = config.timeout === void 0 ? void 0 : durationMilliseconds(config.timeout, "Cloudflare Workflow step timeout");
|
|
119
|
+
if (timeout !== void 0) assertMaximumDuration(timeout, MAXIMUM_CLOUDFLARE_STEP_TIMEOUT_MILLISECONDS, "Cloudflare Workflow step timeout");
|
|
120
|
+
return {
|
|
121
|
+
retries: {
|
|
122
|
+
limit: retry.maximumAttempts,
|
|
123
|
+
delay: ({ ctx }) => retryDelay(initialDelay, retry, ctx.attempt, maximumDelay)
|
|
124
|
+
},
|
|
125
|
+
...timeout === void 0 ? {} : { timeout }
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function retryDelay(initialDelay, retry, attempt, maximumDelay) {
|
|
129
|
+
const delay = initialDelay * retry.backoffCoefficient ** Math.max(0, attempt - 1);
|
|
130
|
+
return Math.min(delay, maximumDelay ?? MAXIMUM_CLOUDFLARE_DELAY_MILLISECONDS);
|
|
131
|
+
}
|
|
132
|
+
function workflowStepContext(commandName, retry, context, logger) {
|
|
133
|
+
return {
|
|
134
|
+
step: {
|
|
135
|
+
id: `${commandName}:${context.step.count}`,
|
|
136
|
+
name: commandName,
|
|
137
|
+
count: context.step.count,
|
|
138
|
+
kind: "run"
|
|
139
|
+
},
|
|
140
|
+
attempt: context.attempt,
|
|
141
|
+
config: retry,
|
|
142
|
+
signal: new AbortController().signal,
|
|
143
|
+
log: logger
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function durationMilliseconds(duration, label) {
|
|
147
|
+
const milliseconds = durationTotal(duration);
|
|
148
|
+
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) throw new RangeError(`${label} must resolve to non-negative whole milliseconds`);
|
|
149
|
+
return milliseconds;
|
|
150
|
+
}
|
|
151
|
+
function assertMaximumDuration(value, maximum, label) {
|
|
152
|
+
if (value > maximum) throw new RangeError(`${label} must not exceed ${maximum} milliseconds`);
|
|
153
|
+
}
|
|
154
|
+
function unsupportedFacet(capability) {
|
|
155
|
+
return new Proxy({}, { get() {
|
|
156
|
+
return () => WorkflowCapabilityError.unsupported(capability);
|
|
157
|
+
} });
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
export { createCloudflareWorkflowContext, createCloudflareWorkflowStepApi };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region src/cloudflare/types.d.ts
|
|
2
|
+
/** Minimal Cloudflare Workflow binding used by the Temelj adapter. */
|
|
3
|
+
interface CloudflareWorkflowBinding {
|
|
4
|
+
create(options?: CloudflareWorkflowInstanceCreateOptions): Promise<CloudflareWorkflowInstance>;
|
|
5
|
+
createBatch(items: readonly CloudflareWorkflowInstanceCreateOptions[]): Promise<readonly CloudflareWorkflowInstance[]>;
|
|
6
|
+
get(id: string): Promise<CloudflareWorkflowInstance>;
|
|
7
|
+
}
|
|
8
|
+
/** Parameters accepted when creating a Cloudflare Workflow instance. */
|
|
9
|
+
interface CloudflareWorkflowInstanceCreateOptions {
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
readonly params?: unknown;
|
|
12
|
+
}
|
|
13
|
+
/** Minimal Cloudflare Workflow instance used by the Temelj adapter. */
|
|
14
|
+
interface CloudflareWorkflowInstance {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
status(): Promise<CloudflareWorkflowInstanceStatus>;
|
|
17
|
+
terminate(options?: {
|
|
18
|
+
readonly rollback?: boolean;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
sendEvent(options: {
|
|
21
|
+
readonly type: string;
|
|
22
|
+
readonly payload?: unknown;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/** Status returned by a Cloudflare Workflow instance binding. */
|
|
26
|
+
interface CloudflareWorkflowInstanceStatus {
|
|
27
|
+
readonly status: "queued" | "running" | "paused" | "errored" | "terminated" | "complete" | "waiting" | "waitingForPause" | "unknown";
|
|
28
|
+
readonly error?: {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly message: string;
|
|
31
|
+
};
|
|
32
|
+
readonly output?: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** Minimal Cloudflare durable step context. */
|
|
35
|
+
interface CloudflareWorkflowStepContext {
|
|
36
|
+
readonly step: {
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly count: number;
|
|
39
|
+
};
|
|
40
|
+
readonly attempt: number;
|
|
41
|
+
readonly config: CloudflareWorkflowStepConfig;
|
|
42
|
+
}
|
|
43
|
+
/** Cloudflare retry and timeout configuration used by a durable step. */
|
|
44
|
+
interface CloudflareWorkflowStepConfig {
|
|
45
|
+
readonly retries?: {
|
|
46
|
+
readonly limit: number;
|
|
47
|
+
readonly delay: number | ((input: {
|
|
48
|
+
readonly ctx: CloudflareWorkflowStepContext;
|
|
49
|
+
readonly error: Error;
|
|
50
|
+
}) => number | Promise<number>);
|
|
51
|
+
readonly backoff?: "constant" | "linear" | "exponential";
|
|
52
|
+
};
|
|
53
|
+
readonly timeout?: number;
|
|
54
|
+
}
|
|
55
|
+
/** Event returned by Cloudflare after a matching `waitForEvent` call. */
|
|
56
|
+
interface CloudflareWorkflowStepEvent<TPayload = unknown> {
|
|
57
|
+
readonly payload: TPayload;
|
|
58
|
+
readonly timestamp: Date;
|
|
59
|
+
readonly type: string;
|
|
60
|
+
}
|
|
61
|
+
/** Minimal Cloudflare durable step API used by the Temelj adapter. */
|
|
62
|
+
interface CloudflareWorkflowStep {
|
|
63
|
+
do<T>(name: string, config: CloudflareWorkflowStepConfig, callback: (context: CloudflareWorkflowStepContext) => T | Promise<T>): Promise<T>;
|
|
64
|
+
sleep(name: string, duration: number): Promise<void>;
|
|
65
|
+
sleepUntil(name: string, timestamp: number): Promise<void>;
|
|
66
|
+
waitForEvent<T>(name: string, options: {
|
|
67
|
+
readonly type: string;
|
|
68
|
+
readonly timeout: string | number;
|
|
69
|
+
}): Promise<CloudflareWorkflowStepEvent<T>>;
|
|
70
|
+
}
|
|
71
|
+
/** Event delivered to the generic Temelj Cloudflare entrypoint. */
|
|
72
|
+
interface CloudflareWorkflowEvent<TPayload = unknown> {
|
|
73
|
+
readonly payload: TPayload;
|
|
74
|
+
readonly timestamp: Date;
|
|
75
|
+
readonly instanceId: string;
|
|
76
|
+
readonly workflowName: string;
|
|
77
|
+
readonly schedule?: {
|
|
78
|
+
readonly cron: string;
|
|
79
|
+
readonly scheduledTime: number;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Constructor used to convert Temelj non-retryable errors for Cloudflare. */
|
|
83
|
+
interface CloudflareNonRetryableErrorConstructor {
|
|
84
|
+
new (message: string, name?: string): Error;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { CloudflareNonRetryableErrorConstructor, CloudflareWorkflowBinding, CloudflareWorkflowEvent, CloudflareWorkflowInstance, CloudflareWorkflowInstanceCreateOptions, CloudflareWorkflowInstanceStatus, CloudflareWorkflowStep, CloudflareWorkflowStepConfig, CloudflareWorkflowStepContext, CloudflareWorkflowStepEvent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -42,7 +42,8 @@ function createWorkflowEngineInspectionApi(store, now) {
|
|
|
42
42
|
});
|
|
43
43
|
},
|
|
44
44
|
async getTimeline(runId) {
|
|
45
|
-
|
|
45
|
+
const run = await getRequiredRun(store, runId);
|
|
46
|
+
return createTimeline(run, await store.getEvents(runId));
|
|
46
47
|
},
|
|
47
48
|
async listStepAttempts(runId, options) {
|
|
48
49
|
await getRequiredRun(store, runId);
|
|
@@ -81,22 +81,24 @@ async function getWorkflowRunSummary(options) {
|
|
|
81
81
|
const { store, now, filters } = options;
|
|
82
82
|
const cleanupCutoff = workflowSummaryCleanupCutoff(filters);
|
|
83
83
|
const runs = await store.listRuns(filters);
|
|
84
|
-
|
|
84
|
+
const summaryInputs = {
|
|
85
85
|
runs,
|
|
86
86
|
now,
|
|
87
87
|
cleanupCutoff,
|
|
88
88
|
...await getWorkflowRunSummaryActivityInputs(store, runs)
|
|
89
|
-
}
|
|
89
|
+
};
|
|
90
|
+
return summarizeWorkflowRuns(summaryInputs);
|
|
90
91
|
}
|
|
91
92
|
async function getWorkflowRecoverySummary(options) {
|
|
92
93
|
const { store, now, filters } = options;
|
|
93
94
|
const runs = await store.listRuns(filters);
|
|
95
|
+
const activeRuns = runs.filter((run) => {
|
|
96
|
+
return workflowRunCanHaveActiveWork(run);
|
|
97
|
+
});
|
|
94
98
|
return summarizeWorkflowRecovery({
|
|
95
99
|
runs,
|
|
96
100
|
now,
|
|
97
|
-
inputs: await getWorkflowRecoverySummaryInputs(store,
|
|
98
|
-
return workflowRunCanHaveActiveWork(run);
|
|
99
|
-
}))
|
|
101
|
+
inputs: await getWorkflowRecoverySummaryInputs(store, activeRuns)
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
async function getWorkflowRecoverySummaryInputs(store, activeRuns) {
|
package/dist/engine/message.mjs
CHANGED
|
@@ -60,18 +60,20 @@ function createWorkflowStepMessageApi(environment) {
|
|
|
60
60
|
payload: toOptionalPersistedValue(options.payload, "message payload", limits),
|
|
61
61
|
idempotencyKey
|
|
62
62
|
};
|
|
63
|
-
if (started === void 0)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
if (started === void 0) {
|
|
64
|
+
const timestamp = now();
|
|
65
|
+
await appendDurableStepStartedEvent(environment, {
|
|
66
|
+
kind: "message_send_started",
|
|
67
|
+
timestamp,
|
|
68
|
+
stepId: identity.id,
|
|
69
|
+
stepName: identity.name,
|
|
70
|
+
count: identity.count,
|
|
71
|
+
targetRunId: nextMessageSend.targetRunId,
|
|
72
|
+
messageId: nextMessageSend.messageId,
|
|
73
|
+
payload: nextMessageSend.payload,
|
|
74
|
+
...nextMessageSend.idempotencyKey === void 0 ? {} : { idempotencyKey: nextMessageSend.idempotencyKey }
|
|
75
|
+
});
|
|
76
|
+
} else requireMessageSendReplayMatches(identity, started, started, nextMessageSend);
|
|
75
77
|
try {
|
|
76
78
|
await sendWorkflowMessage(store, targetRunId, {
|
|
77
79
|
...options,
|
|
@@ -338,7 +340,8 @@ function messageWaitAvailableAt(now, timeoutAt, deadlineAt) {
|
|
|
338
340
|
async function wakeRunForMessageEvent(store, runId, messageEvent) {
|
|
339
341
|
const run = await store.getRun(runId);
|
|
340
342
|
if (run === void 0 || run.status !== "waiting") return;
|
|
341
|
-
const
|
|
343
|
+
const events = await store.getEvents(runId);
|
|
344
|
+
const activeWaits = new WorkflowHistory(events).activeMessageWaitStepIds(messageEvent.messageId);
|
|
342
345
|
if (!(messageEvent.waiterStepIds === void 0 ? activeWaits.length > 0 : activeWaits.some((stepId) => messageEvent.waiterStepIds?.includes(stepId)))) return;
|
|
343
346
|
await store.updateRunIfCurrent(run, {
|
|
344
347
|
...run,
|
|
@@ -181,9 +181,7 @@ function emitRunEvent(emit, run, previousRun) {
|
|
|
181
181
|
case "deadline":
|
|
182
182
|
emit("workflow:run-failed", payload);
|
|
183
183
|
break;
|
|
184
|
-
case "canceled":
|
|
185
|
-
emit("workflow:run-canceled", payload);
|
|
186
|
-
break;
|
|
184
|
+
case "canceled": emit("workflow:run-canceled", payload);
|
|
187
185
|
}
|
|
188
186
|
}
|
|
189
187
|
function makeRunObservationEvent(run, previousRun) {
|
package/dist/engine/run-data.mjs
CHANGED
|
@@ -52,7 +52,8 @@ async function setWorkflowRunAttributes(environment, commandId, attributes) {
|
|
|
52
52
|
const recorded = history.attributesSet(identity.id);
|
|
53
53
|
if (recorded !== void 0) {
|
|
54
54
|
requireAttributesReplayMatches(identity, recorded, normalized);
|
|
55
|
-
const
|
|
55
|
+
const recordedPatch = workflowAttributePatchFromEvent(recorded);
|
|
56
|
+
const updated = await patchCurrentExecutionRunAttributes(store, executionOwner, recordedPatch, recorded.timestamp);
|
|
56
57
|
if (updated === void 0) WorkflowStaleExecutionError.stale(executionOwner.id);
|
|
57
58
|
Object.assign(executionOwner, updated);
|
|
58
59
|
return;
|
|
@@ -20,7 +20,8 @@ async function rerunWorkflowRun(service, startWorkflow, runId, rerunOptions) {
|
|
|
20
20
|
}
|
|
21
21
|
async function resolveRerunWorkflowDefinition(sourceRun, options) {
|
|
22
22
|
const workflowName = options?.workflowName ?? sourceRun.workflowName;
|
|
23
|
-
|
|
23
|
+
const resolvedWorkflowVersion = await resolveWorkflowVersion(workflowName, options !== void 0 && "workflowVersion" in options ? options.workflowVersion : sourceRun.workflowVersion, options?.workflowVersionResolver);
|
|
24
|
+
return defineWorkflowFromName(workflowName, resolvedWorkflowVersion);
|
|
24
25
|
}
|
|
25
26
|
async function rescheduleWorkflowRun(service, lifecycle, runId, rescheduleOptions) {
|
|
26
27
|
const options = rescheduleOptions;
|
|
@@ -28,7 +28,8 @@ function resolveScheduleNextOptions(options, now) {
|
|
|
28
28
|
function createWorkflowEngineStartApi(service) {
|
|
29
29
|
return {
|
|
30
30
|
async startWorkflow(workflow, input, startOptions) {
|
|
31
|
-
const
|
|
31
|
+
const definition = defineWorkflowFromTarget(workflow);
|
|
32
|
+
const resolvedDefinition = await resolveWorkflowStartSpec(definition, startOptions);
|
|
32
33
|
const parsedInput = await parseWorkflowInput(resolvedDefinition, input);
|
|
33
34
|
const idempotencyKey = startOptions?.idempotencyKey;
|
|
34
35
|
const explicitRunId = startOptions?.id;
|
package/dist/engine/state.mjs
CHANGED
|
@@ -376,7 +376,8 @@ async function parentHasActiveChildWorkflowAttemptForRun(store, parentRunId, chi
|
|
|
376
376
|
}
|
|
377
377
|
async function parentHasActiveChildWorkflowEventForAttempt(store, parentRunId, attempt) {
|
|
378
378
|
if (attempt.childRunId === void 0) return false;
|
|
379
|
-
const
|
|
379
|
+
const events = await store.getEvents(parentRunId);
|
|
380
|
+
const lifecycle = new WorkflowHistory(events).childWorkflowLifecycleForAttempt(attempt);
|
|
380
381
|
if (lifecycle.started === void 0) return false;
|
|
381
382
|
if (!isPositiveSafeInteger(lifecycle.started.attempt)) return false;
|
|
382
383
|
return !lifecycle.hasTerminalAfterStarted;
|
package/dist/engine/stream.d.mts
CHANGED
|
@@ -6,6 +6,7 @@ import { WorkflowStepIdentity } from "../types/run.mjs";
|
|
|
6
6
|
import { EventRecord } from "../types/events.mjs";
|
|
7
7
|
import { WorkflowEventHistoryWatcher, WorkflowEventReader, WorkflowRunReaderRepository } from "../types/store.mjs";
|
|
8
8
|
import { WorkflowExecutionEnvironment } from "../types/engine.mjs";
|
|
9
|
+
import "temporal-polyfill/global";
|
|
9
10
|
//#region src/engine/stream.d.ts
|
|
10
11
|
interface WorkflowStreamReader<TChunk> {
|
|
11
12
|
(options?: ReadStreamOptions): Promise<StreamState<TChunk>>;
|