@lovable.dev/sdk 1.4.0 → 1.6.1
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 +21 -2
- package/dist/index.d.ts +2919 -1232
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +590 -178
- package/dist/schemas.js +293 -90
- package/dist/schemas.js.map +1 -1
- package/dist/workflows.d.ts +233 -0
- package/dist/workflows.js +1250 -0
- package/dist/workflows.js.map +1 -0
- package/package.json +5 -1
- package/src/client.ts +6 -4
- package/src/generated/paths.ts +2915 -1234
- package/src/generated/zod/zod.gen.ts +322 -95
- package/src/types.ts +9 -3
|
@@ -0,0 +1,1250 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
//#region ../workflow-sdk/src/index.ts
|
|
3
|
+
function defineTask(config) {
|
|
4
|
+
assertEntryName(config.name);
|
|
5
|
+
if (config.environment !== "cloudflare") throw new Error("workflow-sdk: only Cloudflare tasks are supported");
|
|
6
|
+
if (config.maxConcurrency !== void 0 && (!Number.isInteger(config.maxConcurrency) || config.maxConcurrency < 1 || config.maxConcurrency > 256)) throw new Error("workflow-sdk: maxConcurrency must be an integer between 1 and 256");
|
|
7
|
+
return config;
|
|
8
|
+
}
|
|
9
|
+
/** A durable promise was rejected (explicitly or by its timeout sweep). Catchable to branch on rejection. */
|
|
10
|
+
var PromiseRejectedError = class extends Error {
|
|
11
|
+
promiseName;
|
|
12
|
+
reason;
|
|
13
|
+
constructor(promiseName, reason) {
|
|
14
|
+
super(`workflow-sdk: promise "${promiseName}" rejected: ${reason}`);
|
|
15
|
+
this.promiseName = promiseName;
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function defineWorkflow(name, handler) {
|
|
20
|
+
return {
|
|
21
|
+
name,
|
|
22
|
+
handler
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function hasOwn(value, key) {
|
|
26
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
27
|
+
}
|
|
28
|
+
function taskDispatchPromise(start) {
|
|
29
|
+
const pending = new Promise(() => void 0);
|
|
30
|
+
let started = false;
|
|
31
|
+
return interceptPromiseMethods(pending, (_method, args, invoke) => {
|
|
32
|
+
if (!started) {
|
|
33
|
+
started = true;
|
|
34
|
+
start();
|
|
35
|
+
}
|
|
36
|
+
return invoke(args);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function interceptPromiseMethods(promise, intercept) {
|
|
40
|
+
return new Proxy(promise, { get(target, property) {
|
|
41
|
+
if (property !== "then" && property !== "catch" && property !== "finally") return Reflect.get(target, property, target);
|
|
42
|
+
const method = Reflect.get(target, property, target);
|
|
43
|
+
return (...args) => intercept(property, args, (interceptedArgs) => Reflect.apply(method, target, interceptedArgs));
|
|
44
|
+
} });
|
|
45
|
+
}
|
|
46
|
+
function hasDurableTaskDispatch(lineage) {
|
|
47
|
+
let current = lineage;
|
|
48
|
+
while (current !== void 0) {
|
|
49
|
+
if (current.durableTaskDispatched) return true;
|
|
50
|
+
current = current.parent;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
var Execution = class {
|
|
55
|
+
input;
|
|
56
|
+
journal;
|
|
57
|
+
offload;
|
|
58
|
+
completed = Object.create(null);
|
|
59
|
+
dispatches = [];
|
|
60
|
+
seenStepNames = /* @__PURE__ */ new Set();
|
|
61
|
+
seenPromiseNames = /* @__PURE__ */ new Set();
|
|
62
|
+
seenAutomaticEntries = /* @__PURE__ */ new Set();
|
|
63
|
+
startedSteps = [];
|
|
64
|
+
unconsumedInlineSteps = /* @__PURE__ */ new Map();
|
|
65
|
+
unconsumedTasks = /* @__PURE__ */ new Map();
|
|
66
|
+
inlineStep = new AsyncLocalStorage();
|
|
67
|
+
inlinePromiseLineage = new AsyncLocalStorage();
|
|
68
|
+
dispatchReady;
|
|
69
|
+
resolveDispatch;
|
|
70
|
+
logger;
|
|
71
|
+
journalRemaining;
|
|
72
|
+
constructor(input, journal, offload, emit) {
|
|
73
|
+
this.input = input;
|
|
74
|
+
this.journal = journal;
|
|
75
|
+
this.offload = offload;
|
|
76
|
+
this.dispatchReady = new Promise((resolve) => {
|
|
77
|
+
this.resolveDispatch = resolve;
|
|
78
|
+
});
|
|
79
|
+
this.journalRemaining = Object.keys(journal).length;
|
|
80
|
+
this.logger = createWorkflowLogger(emit, () => this.journalRemaining > 0);
|
|
81
|
+
}
|
|
82
|
+
consumeJournalEntry() {
|
|
83
|
+
if (this.journalRemaining > 0) this.journalRemaining -= 1;
|
|
84
|
+
}
|
|
85
|
+
async readOrRecord(key, createValue) {
|
|
86
|
+
if (hasOwn(this.journal, key)) {
|
|
87
|
+
this.consumeJournalEntry();
|
|
88
|
+
return readJournalResult(this.journal[key]);
|
|
89
|
+
}
|
|
90
|
+
const createdValue = await createValue();
|
|
91
|
+
if (createdValue instanceof PayloadFile) {
|
|
92
|
+
this.completed[key] = {
|
|
93
|
+
resultType: "ref",
|
|
94
|
+
ref: structuredClone(createdValue.ref)
|
|
95
|
+
};
|
|
96
|
+
return createdValue;
|
|
97
|
+
}
|
|
98
|
+
if (createdValue === void 0) {
|
|
99
|
+
this.completed[key] = {
|
|
100
|
+
result: null,
|
|
101
|
+
resultType: "undefined"
|
|
102
|
+
};
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const encoded = encodeJSONValue(createdValue, "step results");
|
|
106
|
+
if (this.offload !== void 0) {
|
|
107
|
+
const serialized = utf8PayloadBytes(encoded);
|
|
108
|
+
if (serialized.byteLength > this.offload.threshold) {
|
|
109
|
+
const ref = await uploadPayload(this.offload, serialized, "application/json");
|
|
110
|
+
this.completed[key] = {
|
|
111
|
+
resultType: "ref",
|
|
112
|
+
ref,
|
|
113
|
+
transparent: true
|
|
114
|
+
};
|
|
115
|
+
return JSON.parse(encoded);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.completed[key] = { result: JSON.parse(encoded) };
|
|
119
|
+
return JSON.parse(encoded);
|
|
120
|
+
}
|
|
121
|
+
automaticEntry(kind, name, createValue) {
|
|
122
|
+
assertEntryName(name);
|
|
123
|
+
const key = `@${kind}/${name}`;
|
|
124
|
+
if (this.seenAutomaticEntries.has(key)) throw new Error(`workflow-sdk: duplicate ${kind} name "${name}"`);
|
|
125
|
+
this.seenAutomaticEntries.add(key);
|
|
126
|
+
const started = this.readOrRecord(key, createValue);
|
|
127
|
+
this.startedSteps.push(started);
|
|
128
|
+
return started;
|
|
129
|
+
}
|
|
130
|
+
runStep(nameOrTask, effectOrInput, options) {
|
|
131
|
+
if (typeof nameOrTask === "string") {
|
|
132
|
+
const name = nameOrTask;
|
|
133
|
+
if (typeof effectOrInput !== "function") throw new Error("workflow-sdk: inline step effect must be a function");
|
|
134
|
+
assertEntryName(name);
|
|
135
|
+
if (this.seenStepNames.has(name)) throw new Error(`workflow-sdk: duplicate step name "${name}"`);
|
|
136
|
+
this.seenStepNames.add(name);
|
|
137
|
+
const started = this.inlineStep.run(name, () => this.readOrRecord(name, effectOrInput)).catch((error) => {
|
|
138
|
+
throw error instanceof StepFailure ? error : new StepFailure(name, error);
|
|
139
|
+
});
|
|
140
|
+
this.startedSteps.push(started);
|
|
141
|
+
return this.trackInlineStepPromise(name, started);
|
|
142
|
+
}
|
|
143
|
+
const inlineStep = this.inlineStep.getStore();
|
|
144
|
+
if (inlineStep !== void 0) throw new Error(`workflow-sdk: engine-dispatched tasks cannot run inside inline step "${inlineStep}"`);
|
|
145
|
+
const id = options?.id ?? nameOrTask.name;
|
|
146
|
+
assertEntryName(id);
|
|
147
|
+
if (this.seenStepNames.has(id)) throw new Error(`workflow-sdk: duplicate step name "${id}"`);
|
|
148
|
+
this.seenStepNames.add(id);
|
|
149
|
+
if (hasOwn(this.journal, id)) {
|
|
150
|
+
this.consumeJournalEntry();
|
|
151
|
+
return Promise.resolve().then(() => readJournalResult(this.journal[id])).catch((error) => {
|
|
152
|
+
throw error instanceof StepFailure ? error : new StepFailure(id, error);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
let input;
|
|
156
|
+
try {
|
|
157
|
+
input = cloneJSONValue(effectOrInput, "step input");
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throw new StepFailure(id, error);
|
|
160
|
+
}
|
|
161
|
+
const unconsumed = new StepFailure(id, /* @__PURE__ */ new Error(`workflow-sdk: task step "${id}" must be awaited or returned`));
|
|
162
|
+
this.unconsumedTasks.set(id, unconsumed);
|
|
163
|
+
const lineage = this.inlinePromiseLineage.getStore();
|
|
164
|
+
return taskDispatchPromise(() => {
|
|
165
|
+
if (lineage !== void 0) lineage.durableTaskDispatched = true;
|
|
166
|
+
this.unconsumedTasks.delete(id);
|
|
167
|
+
this.dispatches.push({
|
|
168
|
+
id,
|
|
169
|
+
task: nameOrTask.name,
|
|
170
|
+
environment: nameOrTask.environment,
|
|
171
|
+
input,
|
|
172
|
+
...nameOrTask.maxConcurrency === void 0 ? {} : { maxConcurrency: nameOrTask.maxConcurrency }
|
|
173
|
+
});
|
|
174
|
+
this.resolveDispatch();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
step = { run: ((nameOrTask, effectOrInput, options) => this.runStep(nameOrTask, effectOrInput, options)) };
|
|
178
|
+
files = { create: (data, options) => {
|
|
179
|
+
if (this.offload === void 0) return Promise.reject(/* @__PURE__ */ new Error("workflow-sdk: payload offload is not enabled for this run"));
|
|
180
|
+
return createWorkflowFile(this.offload, data, options);
|
|
181
|
+
} };
|
|
182
|
+
now = (name) => this.automaticEntry("now", name, () => Date.now());
|
|
183
|
+
uuid = (name) => this.automaticEntry("uuid", name, () => crypto.randomUUID());
|
|
184
|
+
promise = (name, options) => {
|
|
185
|
+
assertEntryName(name);
|
|
186
|
+
const key = `@promise/${name}`;
|
|
187
|
+
if (this.seenPromiseNames.has(name)) throw new Error(`workflow-sdk: duplicate promise name "${name}"`);
|
|
188
|
+
this.seenPromiseNames.add(name);
|
|
189
|
+
const timeoutSeconds = options?.timeoutSeconds;
|
|
190
|
+
if (timeoutSeconds !== void 0 && (!Number.isInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > 86400)) throw new Error("workflow-sdk: promise timeoutSeconds must be an integer between 1 and 86400");
|
|
191
|
+
if (hasOwn(this.journal, key)) {
|
|
192
|
+
this.consumeJournalEntry();
|
|
193
|
+
const entry = readJournalResult(this.journal[key]);
|
|
194
|
+
if (entry.status === "resolved") return Promise.resolve(structuredClone(entry.value));
|
|
195
|
+
return Promise.reject(new PromiseRejectedError(name, entry.reason ?? "promise rejected"));
|
|
196
|
+
}
|
|
197
|
+
this.unconsumedTasks.set(key, new StepFailure(key, /* @__PURE__ */ new Error(`workflow-sdk: promise "${name}" must be awaited or returned`)));
|
|
198
|
+
return taskDispatchPromise(() => {
|
|
199
|
+
this.unconsumedTasks.delete(key);
|
|
200
|
+
this.dispatches.push({
|
|
201
|
+
id: key,
|
|
202
|
+
task: "@promise",
|
|
203
|
+
environment: "cloudflare",
|
|
204
|
+
input: null,
|
|
205
|
+
promise: {
|
|
206
|
+
name,
|
|
207
|
+
...timeoutSeconds === void 0 ? {} : { timeoutSeconds }
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
this.resolveDispatch();
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
async settleStartedSteps() {
|
|
214
|
+
await Promise.allSettled(this.startedSteps);
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
216
|
+
}
|
|
217
|
+
unconsumedInlineFailure() {
|
|
218
|
+
for (const branch of this.unconsumedInlineSteps.values()) {
|
|
219
|
+
if (branch.state.status === "rejected") {
|
|
220
|
+
if (branch.inheritsRootRejection && branch.root.failureObserved && branch.root.state.status === "rejected" && branch.state.reason === branch.root.state.reason) continue;
|
|
221
|
+
return branch.state.reason instanceof StepFailure ? branch.state.reason : new StepFailure(branch.root.name, branch.state.reason);
|
|
222
|
+
}
|
|
223
|
+
if (branch.state.status === "fulfilled" || hasDurableTaskDispatch(branch.lineage)) continue;
|
|
224
|
+
if (branch.root.state.status === "rejected" && !branch.root.failureObserved) return branch.root.state.reason instanceof StepFailure ? branch.root.state.reason : new StepFailure(branch.root.name, branch.root.state.reason);
|
|
225
|
+
return new StepFailure(branch.root.name, /* @__PURE__ */ new Error(`workflow-sdk: inline step branch "${branch.root.name}" must be awaited or returned`));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
unconsumedTaskFailure() {
|
|
229
|
+
return this.unconsumedTasks.values().next().value;
|
|
230
|
+
}
|
|
231
|
+
waitForDispatch() {
|
|
232
|
+
return this.dispatchReady;
|
|
233
|
+
}
|
|
234
|
+
hasDispatches() {
|
|
235
|
+
return this.dispatches.length > 0;
|
|
236
|
+
}
|
|
237
|
+
context() {
|
|
238
|
+
return {
|
|
239
|
+
input: this.input,
|
|
240
|
+
step: this.step,
|
|
241
|
+
files: this.files,
|
|
242
|
+
log: this.logger,
|
|
243
|
+
now: this.now,
|
|
244
|
+
uuid: this.uuid,
|
|
245
|
+
promise: this.promise
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
trackInlineStepPromise(name, promise, root, inheritsRootRejection = true, lineage) {
|
|
249
|
+
const state = { status: "pending" };
|
|
250
|
+
const inlineRoot = root ?? {
|
|
251
|
+
name,
|
|
252
|
+
failureObserved: false,
|
|
253
|
+
state
|
|
254
|
+
};
|
|
255
|
+
const inlineLineage = lineage ?? { durableTaskDispatched: false };
|
|
256
|
+
const branch = {
|
|
257
|
+
root: inlineRoot,
|
|
258
|
+
lineage: inlineLineage,
|
|
259
|
+
inheritsRootRejection,
|
|
260
|
+
state
|
|
261
|
+
};
|
|
262
|
+
this.unconsumedInlineSteps.set(promise, branch);
|
|
263
|
+
promise.then(() => {
|
|
264
|
+
state.status = "fulfilled";
|
|
265
|
+
}, (reason) => {
|
|
266
|
+
state.status = "rejected";
|
|
267
|
+
state.reason = reason;
|
|
268
|
+
});
|
|
269
|
+
let consumed = false;
|
|
270
|
+
return interceptPromiseMethods(promise, (method, args, invoke) => {
|
|
271
|
+
if (!consumed) {
|
|
272
|
+
consumed = true;
|
|
273
|
+
this.unconsumedInlineSteps.delete(promise);
|
|
274
|
+
}
|
|
275
|
+
const handlesRejection = method === "then" && typeof args[1] === "function" || method === "catch" && typeof args[0] === "function";
|
|
276
|
+
if (branch.inheritsRootRejection && handlesRejection) inlineRoot.failureObserved = true;
|
|
277
|
+
const childLineage = {
|
|
278
|
+
parent: inlineLineage,
|
|
279
|
+
durableTaskDispatched: false
|
|
280
|
+
};
|
|
281
|
+
const derived = invoke(args.map((arg) => typeof arg === "function" ? (...callbackArgs) => this.inlinePromiseLineage.run(childLineage, () => Reflect.apply(arg, void 0, callbackArgs)) : arg));
|
|
282
|
+
return this.trackInlineStepPromise(name, derived, inlineRoot, branch.inheritsRootRejection && !handlesRejection, childLineage);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
var StepFailure = class extends Error {
|
|
287
|
+
stepName;
|
|
288
|
+
reason;
|
|
289
|
+
constructor(stepName, reason) {
|
|
290
|
+
super(errorMessage(reason));
|
|
291
|
+
this.stepName = stepName;
|
|
292
|
+
this.reason = reason;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
function assertEntryName(name) {
|
|
296
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(name)) throw new Error(`workflow-sdk: invalid journal entry name "${name}"`);
|
|
297
|
+
}
|
|
298
|
+
function encodeJSONValue(value, kind) {
|
|
299
|
+
try {
|
|
300
|
+
const encoded = JSON.stringify(value);
|
|
301
|
+
if (encoded === void 0) throw new Error("unsupported value");
|
|
302
|
+
return encoded;
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if (error instanceof Error && error.message.startsWith("workflow-sdk:")) throw error;
|
|
305
|
+
throw new Error(`workflow-sdk: ${kind} must be JSON-serializable`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function cloneJSONValue(value, kind) {
|
|
309
|
+
return JSON.parse(encodeJSONValue(value, kind));
|
|
310
|
+
}
|
|
311
|
+
function readJournalResult(entry) {
|
|
312
|
+
if (entry.resultType === "undefined") return void 0;
|
|
313
|
+
if (entry.resultType === "ref") return resolveJournalRef(entry);
|
|
314
|
+
return structuredClone(entry.result);
|
|
315
|
+
}
|
|
316
|
+
function errorMessage(err) {
|
|
317
|
+
return err instanceof Error ? err.message : String(err);
|
|
318
|
+
}
|
|
319
|
+
function utf8PayloadBytes(text) {
|
|
320
|
+
return new TextEncoder().encode(text).slice();
|
|
321
|
+
}
|
|
322
|
+
const PAYLOAD_SCHEME = "CHUNKED_AES_256_GCM_V1";
|
|
323
|
+
const PAYLOAD_SEGMENT_SIZE = 1048576;
|
|
324
|
+
const PAYLOAD_NONCE_BYTES = 12;
|
|
325
|
+
const PAYLOAD_TAG_BYTES = 16;
|
|
326
|
+
const PAYLOAD_AAD_PREFIX = new TextEncoder().encode("lovable-workflow-payload/v1");
|
|
327
|
+
const PAYLOAD_MAX_FRAME_BYTES = 1048604;
|
|
328
|
+
const payloadOffloadPath = "/runtime/v1/payloads";
|
|
329
|
+
function isPayloadRefValue(value) {
|
|
330
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && hasOwn(value, "bucket") && typeof value.bucket === "string" && value.bucket.length > 0 && value.bucket.length <= 128 && hasOwn(value, "key") && typeof value.key === "string" && value.key.length > 0 && value.key.length <= 1024 && hasOwn(value, "sha256") && typeof value.sha256 === "string" && /^[0-9a-f]{64}$/.test(value.sha256) && hasOwn(value, "sizeBytes") && typeof value.sizeBytes === "number" && Number.isSafeInteger(value.sizeBytes) && value.sizeBytes >= 0 && hasOwn(value, "contentType") && typeof value.contentType === "string" && value.contentType.length > 0 && value.contentType.length <= 256 && hasOwn(value, "wrappedDek") && typeof value.wrappedDek === "string" && hasOwn(value, "kekId") && typeof value.kekId === "string" && value.kekId.length > 0 && value.kekId.length <= 128 && hasOwn(value, "scheme") && value.scheme === PAYLOAD_SCHEME;
|
|
331
|
+
}
|
|
332
|
+
function isPayloadGrantValue(value) {
|
|
333
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && hasOwn(value, "getUrl") && typeof value.getUrl === "string" && value.getUrl.length > 0 && hasOwn(value, "dek") && typeof value.dek === "string" && value.dek.length > 0;
|
|
334
|
+
}
|
|
335
|
+
function isPayloadOffloadValue(value) {
|
|
336
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && hasOwn(value, "endpoint") && typeof value.endpoint === "string" && value.endpoint.length > 0 && value.endpoint.length <= 2048 && hasOwn(value, "threshold") && typeof value.threshold === "number" && Number.isSafeInteger(value.threshold) && value.threshold >= 0 && hasOwn(value, "maxBytes") && typeof value.maxBytes === "number" && Number.isSafeInteger(value.maxBytes) && value.maxBytes >= 1;
|
|
337
|
+
}
|
|
338
|
+
function normalizedPayloadOffload(value) {
|
|
339
|
+
if (value === void 0) return void 0;
|
|
340
|
+
if (!isPayloadOffloadValue(value)) throw new Error("workflow-sdk: invalid payload offload configuration");
|
|
341
|
+
return {
|
|
342
|
+
endpoint: value.endpoint,
|
|
343
|
+
threshold: value.threshold,
|
|
344
|
+
maxBytes: value.maxBytes
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function isJournalEntryValue(entry) {
|
|
348
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return false;
|
|
349
|
+
if (hasOwn(entry, "resultType") && entry.resultType === "ref") return !hasOwn(entry, "result") && hasOwn(entry, "ref") && isPayloadRefValue(entry.ref) && (!hasOwn(entry, "transparent") || typeof entry.transparent === "boolean") && (!hasOwn(entry, "grant") || isPayloadGrantValue(entry.grant));
|
|
350
|
+
return hasOwn(entry, "result") && (!hasOwn(entry, "resultType") || entry.resultType === "undefined");
|
|
351
|
+
}
|
|
352
|
+
const SHA256_K = new Uint32Array([
|
|
353
|
+
1116352408,
|
|
354
|
+
1899447441,
|
|
355
|
+
3049323471,
|
|
356
|
+
3921009573,
|
|
357
|
+
961987163,
|
|
358
|
+
1508970993,
|
|
359
|
+
2453635748,
|
|
360
|
+
2870763221,
|
|
361
|
+
3624381080,
|
|
362
|
+
310598401,
|
|
363
|
+
607225278,
|
|
364
|
+
1426881987,
|
|
365
|
+
1925078388,
|
|
366
|
+
2162078206,
|
|
367
|
+
2614888103,
|
|
368
|
+
3248222580,
|
|
369
|
+
3835390401,
|
|
370
|
+
4022224774,
|
|
371
|
+
264347078,
|
|
372
|
+
604807628,
|
|
373
|
+
770255983,
|
|
374
|
+
1249150122,
|
|
375
|
+
1555081692,
|
|
376
|
+
1996064986,
|
|
377
|
+
2554220882,
|
|
378
|
+
2821834349,
|
|
379
|
+
2952996808,
|
|
380
|
+
3210313671,
|
|
381
|
+
3336571891,
|
|
382
|
+
3584528711,
|
|
383
|
+
113926993,
|
|
384
|
+
338241895,
|
|
385
|
+
666307205,
|
|
386
|
+
773529912,
|
|
387
|
+
1294757372,
|
|
388
|
+
1396182291,
|
|
389
|
+
1695183700,
|
|
390
|
+
1986661051,
|
|
391
|
+
2177026350,
|
|
392
|
+
2456956037,
|
|
393
|
+
2730485921,
|
|
394
|
+
2820302411,
|
|
395
|
+
3259730800,
|
|
396
|
+
3345764771,
|
|
397
|
+
3516065817,
|
|
398
|
+
3600352804,
|
|
399
|
+
4094571909,
|
|
400
|
+
275423344,
|
|
401
|
+
430227734,
|
|
402
|
+
506948616,
|
|
403
|
+
659060556,
|
|
404
|
+
883997877,
|
|
405
|
+
958139571,
|
|
406
|
+
1322822218,
|
|
407
|
+
1537002063,
|
|
408
|
+
1747873779,
|
|
409
|
+
1955562222,
|
|
410
|
+
2024104815,
|
|
411
|
+
2227730452,
|
|
412
|
+
2361852424,
|
|
413
|
+
2428436474,
|
|
414
|
+
2756734187,
|
|
415
|
+
3204031479,
|
|
416
|
+
3329325298
|
|
417
|
+
]);
|
|
418
|
+
function rotr32(value, count) {
|
|
419
|
+
return value >>> count | value << 32 - count;
|
|
420
|
+
}
|
|
421
|
+
var Sha256 = class {
|
|
422
|
+
state = new Uint32Array([
|
|
423
|
+
1779033703,
|
|
424
|
+
3144134277,
|
|
425
|
+
1013904242,
|
|
426
|
+
2773480762,
|
|
427
|
+
1359893119,
|
|
428
|
+
2600822924,
|
|
429
|
+
528734635,
|
|
430
|
+
1541459225
|
|
431
|
+
]);
|
|
432
|
+
block = /* @__PURE__ */ new Uint8Array(64);
|
|
433
|
+
schedule = /* @__PURE__ */ new Uint32Array(64);
|
|
434
|
+
blockBytes = 0;
|
|
435
|
+
totalBytes = 0;
|
|
436
|
+
update(data) {
|
|
437
|
+
this.totalBytes += data.length;
|
|
438
|
+
let offset = 0;
|
|
439
|
+
while (offset < data.length) {
|
|
440
|
+
const take = Math.min(64 - this.blockBytes, data.length - offset);
|
|
441
|
+
this.block.set(data.subarray(offset, offset + take), this.blockBytes);
|
|
442
|
+
this.blockBytes += take;
|
|
443
|
+
offset += take;
|
|
444
|
+
if (this.blockBytes === 64) {
|
|
445
|
+
this.compress();
|
|
446
|
+
this.blockBytes = 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/** Finalizes the digest; the instance must not be updated afterwards. */
|
|
451
|
+
hex() {
|
|
452
|
+
const bitLength = BigInt(this.totalBytes) * 8n;
|
|
453
|
+
this.update(new Uint8Array([128]));
|
|
454
|
+
while (this.blockBytes !== 56) this.update(/* @__PURE__ */ new Uint8Array(1));
|
|
455
|
+
const length = /* @__PURE__ */ new Uint8Array(8);
|
|
456
|
+
new DataView(length.buffer).setBigUint64(0, bitLength, false);
|
|
457
|
+
this.update(length);
|
|
458
|
+
let hex = "";
|
|
459
|
+
for (const word of this.state) hex += word.toString(16).padStart(8, "0");
|
|
460
|
+
return hex;
|
|
461
|
+
}
|
|
462
|
+
compress() {
|
|
463
|
+
const words = this.schedule;
|
|
464
|
+
const view = new DataView(this.block.buffer);
|
|
465
|
+
for (let index = 0; index < 16; index++) words[index] = view.getUint32(index * 4, false);
|
|
466
|
+
for (let index = 16; index < 64; index++) {
|
|
467
|
+
const s0 = rotr32(words[index - 15], 7) ^ rotr32(words[index - 15], 18) ^ words[index - 15] >>> 3;
|
|
468
|
+
const s1 = rotr32(words[index - 2], 17) ^ rotr32(words[index - 2], 19) ^ words[index - 2] >>> 10;
|
|
469
|
+
words[index] = words[index - 16] + s0 + words[index - 7] + s1;
|
|
470
|
+
}
|
|
471
|
+
let [a, b, c, d, e, f, g, h] = this.state;
|
|
472
|
+
for (let index = 0; index < 64; index++) {
|
|
473
|
+
const s1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25);
|
|
474
|
+
const ch = e & f ^ ~e & g;
|
|
475
|
+
const t1 = h + s1 + ch + SHA256_K[index] + words[index] | 0;
|
|
476
|
+
const t2 = (rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)) + (a & b ^ a & c ^ b & c) | 0;
|
|
477
|
+
h = g;
|
|
478
|
+
g = f;
|
|
479
|
+
f = e;
|
|
480
|
+
e = d + t1 | 0;
|
|
481
|
+
d = c;
|
|
482
|
+
c = b;
|
|
483
|
+
b = a;
|
|
484
|
+
a = t1 + t2 | 0;
|
|
485
|
+
}
|
|
486
|
+
this.state[0] += a;
|
|
487
|
+
this.state[1] += b;
|
|
488
|
+
this.state[2] += c;
|
|
489
|
+
this.state[3] += d;
|
|
490
|
+
this.state[4] += e;
|
|
491
|
+
this.state[5] += f;
|
|
492
|
+
this.state[6] += g;
|
|
493
|
+
this.state[7] += h;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
var ByteQueue = class {
|
|
497
|
+
chunks = [];
|
|
498
|
+
length = 0;
|
|
499
|
+
get size() {
|
|
500
|
+
return this.length;
|
|
501
|
+
}
|
|
502
|
+
push(chunk) {
|
|
503
|
+
if (chunk.length === 0) return;
|
|
504
|
+
this.chunks.push(chunk);
|
|
505
|
+
this.length += chunk.length;
|
|
506
|
+
}
|
|
507
|
+
peek(count) {
|
|
508
|
+
if (this.length < count) return void 0;
|
|
509
|
+
const out = new Uint8Array(count);
|
|
510
|
+
let offset = 0;
|
|
511
|
+
for (const chunk of this.chunks) {
|
|
512
|
+
const slice = chunk.subarray(0, Math.min(chunk.length, count - offset));
|
|
513
|
+
out.set(slice, offset);
|
|
514
|
+
offset += slice.length;
|
|
515
|
+
if (offset === count) break;
|
|
516
|
+
}
|
|
517
|
+
return out;
|
|
518
|
+
}
|
|
519
|
+
take(count) {
|
|
520
|
+
const out = new Uint8Array(count);
|
|
521
|
+
let offset = 0;
|
|
522
|
+
while (offset < count) {
|
|
523
|
+
const head = this.chunks[0];
|
|
524
|
+
const needed = count - offset;
|
|
525
|
+
if (head.length <= needed) {
|
|
526
|
+
out.set(head, offset);
|
|
527
|
+
offset += head.length;
|
|
528
|
+
this.chunks.shift();
|
|
529
|
+
} else {
|
|
530
|
+
out.set(head.subarray(0, needed), offset);
|
|
531
|
+
this.chunks[0] = head.subarray(needed);
|
|
532
|
+
offset = count;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
this.length -= count;
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
function payloadSegmentAad(index, final) {
|
|
540
|
+
const aad = new Uint8Array(PAYLOAD_AAD_PREFIX.length + 9);
|
|
541
|
+
aad.set(PAYLOAD_AAD_PREFIX, 0);
|
|
542
|
+
new DataView(aad.buffer).setBigUint64(PAYLOAD_AAD_PREFIX.length, BigInt(index), false);
|
|
543
|
+
aad[aad.length - 1] = final ? 1 : 0;
|
|
544
|
+
return aad;
|
|
545
|
+
}
|
|
546
|
+
async function sealPayloadSegment(key, index, final, plaintext) {
|
|
547
|
+
const nonce = crypto.getRandomValues(new Uint8Array(PAYLOAD_NONCE_BYTES));
|
|
548
|
+
const sealed = new Uint8Array(await crypto.subtle.encrypt({
|
|
549
|
+
name: "AES-GCM",
|
|
550
|
+
iv: nonce,
|
|
551
|
+
additionalData: payloadSegmentAad(index, final)
|
|
552
|
+
}, key, plaintext));
|
|
553
|
+
const frame = new Uint8Array(16 + sealed.length);
|
|
554
|
+
new DataView(frame.buffer).setUint32(0, PAYLOAD_NONCE_BYTES + sealed.length, false);
|
|
555
|
+
frame.set(nonce, 4);
|
|
556
|
+
frame.set(sealed, 16);
|
|
557
|
+
return frame;
|
|
558
|
+
}
|
|
559
|
+
async function openPayloadSegment(key, nonce, sealed, index, final) {
|
|
560
|
+
try {
|
|
561
|
+
return new Uint8Array(await crypto.subtle.decrypt({
|
|
562
|
+
name: "AES-GCM",
|
|
563
|
+
iv: nonce,
|
|
564
|
+
additionalData: payloadSegmentAad(index, final)
|
|
565
|
+
}, key, sealed));
|
|
566
|
+
} catch {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/** Chunked AES-256-GCM encryptor emitting [payloadLen u32 BE][nonce 12][ciphertext||tag] frames. */
|
|
571
|
+
function encryptPayloadStream(key) {
|
|
572
|
+
const pending = new ByteQueue();
|
|
573
|
+
let index = 0;
|
|
574
|
+
return new TransformStream({
|
|
575
|
+
async transform(chunk, controller) {
|
|
576
|
+
if (!(chunk instanceof Uint8Array)) throw new Error("workflow-sdk: payload streams must produce Uint8Array chunks");
|
|
577
|
+
pending.push(chunk.slice());
|
|
578
|
+
while (pending.size > PAYLOAD_SEGMENT_SIZE) {
|
|
579
|
+
controller.enqueue(await sealPayloadSegment(key, index, false, pending.take(PAYLOAD_SEGMENT_SIZE)));
|
|
580
|
+
index += 1;
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
async flush(controller) {
|
|
584
|
+
controller.enqueue(await sealPayloadSegment(key, index, true, pending.take(pending.size)));
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/** Decrypts CHUNKED_AES_256_GCM_V1; rejects truncation, reordering, splices, and digest or size mismatches. */
|
|
589
|
+
function decryptPayloadStream(key, expected) {
|
|
590
|
+
const pending = new ByteQueue();
|
|
591
|
+
const digest = new Sha256();
|
|
592
|
+
let index = 0;
|
|
593
|
+
let finalSeen = false;
|
|
594
|
+
let plaintextBytes = 0;
|
|
595
|
+
const drain = async (controller) => {
|
|
596
|
+
for (;;) {
|
|
597
|
+
const header = pending.peek(4);
|
|
598
|
+
if (header === void 0) return;
|
|
599
|
+
const payloadLength = new DataView(header.buffer).getUint32(0, false);
|
|
600
|
+
if (payloadLength < 28 || payloadLength > PAYLOAD_MAX_FRAME_BYTES) throw new Error("workflow-sdk: offloaded payload frame has an invalid length");
|
|
601
|
+
if (pending.size < 4 + payloadLength) return;
|
|
602
|
+
if (finalSeen) throw new Error("workflow-sdk: offloaded payload has data after the final segment");
|
|
603
|
+
pending.take(4);
|
|
604
|
+
const frame = pending.take(payloadLength);
|
|
605
|
+
const nonce = frame.subarray(0, PAYLOAD_NONCE_BYTES);
|
|
606
|
+
const sealed = frame.subarray(PAYLOAD_NONCE_BYTES);
|
|
607
|
+
let plaintext = sealed.length - PAYLOAD_TAG_BYTES === PAYLOAD_SEGMENT_SIZE ? await openPayloadSegment(key, nonce, sealed, index, false) : void 0;
|
|
608
|
+
if (plaintext === void 0) {
|
|
609
|
+
plaintext = await openPayloadSegment(key, nonce, sealed, index, true);
|
|
610
|
+
if (plaintext === void 0) throw new Error("workflow-sdk: offloaded payload segment failed authentication");
|
|
611
|
+
finalSeen = true;
|
|
612
|
+
}
|
|
613
|
+
index += 1;
|
|
614
|
+
plaintextBytes += plaintext.length;
|
|
615
|
+
digest.update(plaintext);
|
|
616
|
+
if (plaintext.length > 0) controller.enqueue(plaintext);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
return new TransformStream({
|
|
620
|
+
async transform(chunk, controller) {
|
|
621
|
+
if (!(chunk instanceof Uint8Array)) throw new Error("workflow-sdk: payload streams must produce Uint8Array chunks");
|
|
622
|
+
pending.push(chunk.slice());
|
|
623
|
+
await drain(controller);
|
|
624
|
+
},
|
|
625
|
+
async flush(controller) {
|
|
626
|
+
await drain(controller);
|
|
627
|
+
if (!finalSeen) throw new Error("workflow-sdk: offloaded payload is truncated before its final segment");
|
|
628
|
+
if (pending.size > 0) throw new Error("workflow-sdk: offloaded payload has trailing bytes after the final segment");
|
|
629
|
+
if (plaintextBytes !== expected.sizeBytes) throw new Error("workflow-sdk: offloaded payload size does not match its journal pointer");
|
|
630
|
+
if (digest.hex() !== expected.sha256) throw new Error("workflow-sdk: offloaded payload digest does not match its journal pointer");
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
function decodeBase64(value, kind) {
|
|
635
|
+
try {
|
|
636
|
+
const decoded = atob(value);
|
|
637
|
+
const bytes = new Uint8Array(decoded.length);
|
|
638
|
+
for (let index = 0; index < decoded.length; index++) bytes[index] = decoded.charCodeAt(index);
|
|
639
|
+
return bytes;
|
|
640
|
+
} catch {
|
|
641
|
+
throw new Error(`workflow-sdk: ${kind} is not valid base64`);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function hexDigest(bytes) {
|
|
645
|
+
let hex = "";
|
|
646
|
+
for (const byte of new Uint8Array(bytes)) hex += byte.toString(16).padStart(2, "0");
|
|
647
|
+
return hex;
|
|
648
|
+
}
|
|
649
|
+
async function importPayloadDek(dek) {
|
|
650
|
+
const raw = decodeBase64(dek, "payload DEK");
|
|
651
|
+
if (raw.length !== 32) throw new Error("workflow-sdk: payload DEK must be 32 bytes");
|
|
652
|
+
return crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
653
|
+
}
|
|
654
|
+
function httpsUrl(value, kind) {
|
|
655
|
+
let url;
|
|
656
|
+
try {
|
|
657
|
+
url = new URL(value);
|
|
658
|
+
} catch {
|
|
659
|
+
throw new Error(`workflow-sdk: ${kind} is not a valid URL`);
|
|
660
|
+
}
|
|
661
|
+
if (url.protocol !== "https:") throw new Error(`workflow-sdk: ${kind} must use https`);
|
|
662
|
+
return value;
|
|
663
|
+
}
|
|
664
|
+
async function collectStream(stream) {
|
|
665
|
+
const reader = stream.getReader();
|
|
666
|
+
const pending = new ByteQueue();
|
|
667
|
+
for (;;) {
|
|
668
|
+
const { done, value } = await reader.read();
|
|
669
|
+
if (done) break;
|
|
670
|
+
if (!(value instanceof Uint8Array)) {
|
|
671
|
+
reader.cancel().catch(() => void 0);
|
|
672
|
+
throw new Error("workflow-sdk: payload streams must produce Uint8Array chunks");
|
|
673
|
+
}
|
|
674
|
+
pending.push(value.slice());
|
|
675
|
+
}
|
|
676
|
+
return pending.take(pending.size);
|
|
677
|
+
}
|
|
678
|
+
async function encryptPayloadBytes(key, plaintext) {
|
|
679
|
+
return collectStream(new ReadableStream({ start(controller) {
|
|
680
|
+
if (plaintext.length > 0) controller.enqueue(plaintext);
|
|
681
|
+
controller.close();
|
|
682
|
+
} }).pipeThrough(encryptPayloadStream(key)));
|
|
683
|
+
}
|
|
684
|
+
function payloadOffloadEndpoint(endpoint) {
|
|
685
|
+
for (const origin of workflowApiOrigins) if (endpoint === `${origin}${payloadOffloadPath}`) return endpoint;
|
|
686
|
+
throw new Error("workflow-sdk: payload offload endpoint is not an allowed workflows origin");
|
|
687
|
+
}
|
|
688
|
+
async function requestPayloadUpload(offload, sha256, sizeBytes, contentType) {
|
|
689
|
+
const endpoint = payloadOffloadEndpoint(offload.endpoint);
|
|
690
|
+
let response;
|
|
691
|
+
try {
|
|
692
|
+
response = await fetch(endpoint, {
|
|
693
|
+
method: "POST",
|
|
694
|
+
headers: { "content-type": "application/json" },
|
|
695
|
+
body: JSON.stringify({
|
|
696
|
+
sha256,
|
|
697
|
+
sizeBytes,
|
|
698
|
+
contentType
|
|
699
|
+
}),
|
|
700
|
+
redirect: "error"
|
|
701
|
+
});
|
|
702
|
+
} catch {
|
|
703
|
+
throw new Error("workflow-sdk: payload offload request failed");
|
|
704
|
+
}
|
|
705
|
+
if (!response.ok) throw new Error("workflow-sdk: payload offload request failed");
|
|
706
|
+
let body;
|
|
707
|
+
try {
|
|
708
|
+
body = await response.json();
|
|
709
|
+
} catch {
|
|
710
|
+
throw new Error("workflow-sdk: invalid payload offload response");
|
|
711
|
+
}
|
|
712
|
+
if (typeof body !== "object" || body === null || Array.isArray(body) || !hasOwn(body, "putUrl") || typeof body.putUrl !== "string" || !hasOwn(body, "dek") || typeof body.dek !== "string" || !hasOwn(body, "ref") || !isPayloadRefValue(body.ref)) throw new Error("workflow-sdk: invalid payload offload response");
|
|
713
|
+
const ref = structuredClone(body.ref);
|
|
714
|
+
if (ref.sha256 !== sha256 || ref.sizeBytes !== sizeBytes || ref.contentType !== contentType) throw new Error("workflow-sdk: payload offload response does not match the requested payload");
|
|
715
|
+
return {
|
|
716
|
+
putUrl: httpsUrl(body.putUrl, "payload upload URL"),
|
|
717
|
+
key: await importPayloadDek(body.dek),
|
|
718
|
+
ref
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
async function uploadPayload(offload, plaintext, contentType) {
|
|
722
|
+
if (plaintext.byteLength > offload.maxBytes) throw new Error(`workflow-sdk: payload of ${plaintext.byteLength} bytes exceeds the offload limit of ${offload.maxBytes} bytes`);
|
|
723
|
+
const ticket = await requestPayloadUpload(offload, hexDigest(await crypto.subtle.digest("SHA-256", plaintext)), plaintext.byteLength, contentType);
|
|
724
|
+
const ciphertext = await encryptPayloadBytes(ticket.key, plaintext);
|
|
725
|
+
let response;
|
|
726
|
+
try {
|
|
727
|
+
response = await fetch(ticket.putUrl, {
|
|
728
|
+
method: "PUT",
|
|
729
|
+
headers: { "content-type": "application/octet-stream" },
|
|
730
|
+
body: ciphertext,
|
|
731
|
+
redirect: "error"
|
|
732
|
+
});
|
|
733
|
+
} catch {
|
|
734
|
+
throw new Error("workflow-sdk: payload upload failed");
|
|
735
|
+
}
|
|
736
|
+
if (!response.ok) throw new Error("workflow-sdk: payload upload failed");
|
|
737
|
+
return ticket.ref;
|
|
738
|
+
}
|
|
739
|
+
function openPayloadReadStream(ref, grant) {
|
|
740
|
+
const relay = new TransformStream();
|
|
741
|
+
(async () => {
|
|
742
|
+
const getUrl = httpsUrl(grant.getUrl, "payload read URL");
|
|
743
|
+
const key = await importPayloadDek(grant.dek);
|
|
744
|
+
let response;
|
|
745
|
+
try {
|
|
746
|
+
response = await fetch(getUrl, {
|
|
747
|
+
method: "GET",
|
|
748
|
+
redirect: "error"
|
|
749
|
+
});
|
|
750
|
+
} catch {
|
|
751
|
+
throw new Error("workflow-sdk: offloaded payload download failed");
|
|
752
|
+
}
|
|
753
|
+
if (!response.ok || response.body === null) throw new Error("workflow-sdk: offloaded payload download failed");
|
|
754
|
+
await response.body.pipeThrough(decryptPayloadStream(key, {
|
|
755
|
+
sha256: ref.sha256,
|
|
756
|
+
sizeBytes: ref.sizeBytes
|
|
757
|
+
})).pipeTo(relay.writable);
|
|
758
|
+
})().catch((error) => {
|
|
759
|
+
relay.writable.abort(error).catch(() => void 0);
|
|
760
|
+
});
|
|
761
|
+
return relay.readable;
|
|
762
|
+
}
|
|
763
|
+
var PayloadFile = class {
|
|
764
|
+
ref;
|
|
765
|
+
source;
|
|
766
|
+
constructor(ref, source) {
|
|
767
|
+
this.ref = ref;
|
|
768
|
+
this.source = source;
|
|
769
|
+
}
|
|
770
|
+
get sha256() {
|
|
771
|
+
return this.ref.sha256;
|
|
772
|
+
}
|
|
773
|
+
get sizeBytes() {
|
|
774
|
+
return this.ref.sizeBytes;
|
|
775
|
+
}
|
|
776
|
+
get contentType() {
|
|
777
|
+
return this.ref.contentType;
|
|
778
|
+
}
|
|
779
|
+
stream() {
|
|
780
|
+
if (this.source.kind === "local") {
|
|
781
|
+
const bytes = this.source.bytes.slice();
|
|
782
|
+
return new ReadableStream({ start(controller) {
|
|
783
|
+
if (bytes.length > 0) controller.enqueue(bytes);
|
|
784
|
+
controller.close();
|
|
785
|
+
} });
|
|
786
|
+
}
|
|
787
|
+
return openPayloadReadStream(this.ref, this.source.grant);
|
|
788
|
+
}
|
|
789
|
+
async arrayBuffer() {
|
|
790
|
+
const bytes = this.source.kind === "local" ? this.source.bytes : await collectStream(this.stream());
|
|
791
|
+
const copy = new ArrayBuffer(bytes.byteLength);
|
|
792
|
+
new Uint8Array(copy).set(bytes);
|
|
793
|
+
return copy;
|
|
794
|
+
}
|
|
795
|
+
async text() {
|
|
796
|
+
return new TextDecoder().decode(await this.arrayBuffer());
|
|
797
|
+
}
|
|
798
|
+
async json() {
|
|
799
|
+
return JSON.parse(await this.text());
|
|
800
|
+
}
|
|
801
|
+
/** Handles journal as their pointer only as a step's direct result; nesting fails loudly. */
|
|
802
|
+
toJSON() {
|
|
803
|
+
throw new Error("workflow-sdk: a WorkflowFile must be a step's direct result, not nested inside a JSON value");
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
function resolveJournalRef(entry) {
|
|
807
|
+
const ref = entry.ref;
|
|
808
|
+
if (ref === void 0 || !isPayloadRefValue(ref)) throw new Error("workflow-sdk: offloaded journal entry carries a malformed payload pointer");
|
|
809
|
+
if (entry.grant === void 0) throw new Error("workflow-sdk: journal entry points at an offloaded payload but carries no read grant; the engine must hydrate grants before invocation (permanent)");
|
|
810
|
+
if (!isPayloadGrantValue(entry.grant)) throw new Error("workflow-sdk: offloaded journal entry carries a malformed read grant");
|
|
811
|
+
const file = new PayloadFile(structuredClone(ref), {
|
|
812
|
+
kind: "remote",
|
|
813
|
+
grant: structuredClone(entry.grant)
|
|
814
|
+
});
|
|
815
|
+
return entry.transparent === true ? file.json() : file;
|
|
816
|
+
}
|
|
817
|
+
async function payloadCreateBytes(data) {
|
|
818
|
+
if (typeof data === "string") return utf8PayloadBytes(data);
|
|
819
|
+
if (data instanceof Uint8Array) return data.slice();
|
|
820
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data.slice(0));
|
|
821
|
+
if (data instanceof ReadableStream) return collectStream(data);
|
|
822
|
+
throw new Error("workflow-sdk: files.create accepts a ReadableStream, ArrayBuffer, Uint8Array, or string");
|
|
823
|
+
}
|
|
824
|
+
async function createWorkflowFile(offload, data, options) {
|
|
825
|
+
if (typeof options !== "object" || options === null || typeof options.contentType !== "string") throw new Error("workflow-sdk: files.create requires a contentType");
|
|
826
|
+
if (options.contentType.length === 0 || options.contentType.length > 256) throw new Error("workflow-sdk: files.create contentType must be 1-256 characters");
|
|
827
|
+
const plaintext = await payloadCreateBytes(data);
|
|
828
|
+
return new PayloadFile(await uploadPayload(offload, plaintext, options.contentType), {
|
|
829
|
+
kind: "local",
|
|
830
|
+
bytes: plaintext
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
const WORKFLOW_LOG_ENVELOPE_KEY = "__lovable_workflow_log";
|
|
834
|
+
const MAX_LOG_MESSAGE_CHARS = 8192;
|
|
835
|
+
const MAX_LOGS_PER_INVOCATION = 512;
|
|
836
|
+
const MAX_LOG_FIELDS = 64;
|
|
837
|
+
const MAX_LOG_FIELD_CHARS = 2048;
|
|
838
|
+
const CREDENTIAL_NAME_ALTERNATION = [
|
|
839
|
+
"password",
|
|
840
|
+
"passwd",
|
|
841
|
+
"secret",
|
|
842
|
+
"token",
|
|
843
|
+
"authorization",
|
|
844
|
+
"auth",
|
|
845
|
+
"signature",
|
|
846
|
+
"sig",
|
|
847
|
+
"credential",
|
|
848
|
+
"cookie",
|
|
849
|
+
"session",
|
|
850
|
+
"key"
|
|
851
|
+
].join("|");
|
|
852
|
+
const LOG_SCRUB_RULES = [
|
|
853
|
+
{
|
|
854
|
+
pattern: /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]+?-----END[A-Z ]*PRIVATE KEY-----/g,
|
|
855
|
+
replacement: "[REDACTED:private-key]"
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}(?:\.[A-Za-z0-9_-]+)?/g,
|
|
859
|
+
replacement: "[REDACTED:jwt]"
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
pattern: /\b(bearer|basic)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
|
|
863
|
+
replacement: "$1 [REDACTED]"
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
pattern: /\bsk[-_][A-Za-z0-9_-]{10,}/g,
|
|
867
|
+
replacement: "[REDACTED:api-key]"
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
pattern: /\brk_(?:live|test)_[A-Za-z0-9]{10,}/g,
|
|
871
|
+
replacement: "[REDACTED:api-key]"
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
pattern: /\bgh[pousr]_[A-Za-z0-9]{20,}/g,
|
|
875
|
+
replacement: "[REDACTED:github-token]"
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}/g,
|
|
879
|
+
replacement: "[REDACTED:github-token]"
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
pattern: /\bxox[abeprst]-[A-Za-z0-9-]{10,}/g,
|
|
883
|
+
replacement: "[REDACTED:slack-token]"
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
|
|
887
|
+
replacement: "[REDACTED:aws-key]"
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
pattern: /\bAIza[0-9A-Za-z_-]{30,}/g,
|
|
891
|
+
replacement: "[REDACTED:google-key]"
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
pattern: /\bya29\.[0-9A-Za-z_-]{20,}/g,
|
|
895
|
+
replacement: "[REDACTED:google-token]"
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
pattern: /\bglpat-[A-Za-z0-9_-]{20,}/g,
|
|
899
|
+
replacement: "[REDACTED:gitlab-token]"
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
pattern: /\bnpm_[A-Za-z0-9]{30,}/g,
|
|
903
|
+
replacement: "[REDACTED:npm-token]"
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
pattern: new RegExp(`((?:${CREDENTIAL_NAME_ALTERNATION})s?["']?\\s*[:=]\\s*["']?)[^\\s"'\`,;&]{6,}`, "gi"),
|
|
907
|
+
replacement: "$1[REDACTED]"
|
|
908
|
+
}
|
|
909
|
+
];
|
|
910
|
+
const SENSITIVE_FIELD_KEY = new RegExp(`(?:${CREDENTIAL_NAME_ALTERNATION})`, "i");
|
|
911
|
+
function truncateLogText(text, max) {
|
|
912
|
+
if (text.length <= max) return text;
|
|
913
|
+
let end = max;
|
|
914
|
+
const tail = text.charCodeAt(end - 1);
|
|
915
|
+
if (tail >= 55296 && tail <= 56319) end -= 1;
|
|
916
|
+
return `${text.slice(0, end)}…[truncated]`;
|
|
917
|
+
}
|
|
918
|
+
function scrubLogText(text) {
|
|
919
|
+
let scrubbed = text;
|
|
920
|
+
for (const rule of LOG_SCRUB_RULES) scrubbed = scrubbed.replace(rule.pattern, rule.replacement);
|
|
921
|
+
return scrubbed;
|
|
922
|
+
}
|
|
923
|
+
function sanitizeLogFields(fields) {
|
|
924
|
+
const sanitized = {};
|
|
925
|
+
let count = 0;
|
|
926
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
927
|
+
if (key.length === 0 || key.length > 128) continue;
|
|
928
|
+
if (count >= MAX_LOG_FIELDS) {
|
|
929
|
+
sanitized.__fields_truncated = true;
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
count += 1;
|
|
933
|
+
if (SENSITIVE_FIELD_KEY.test(key)) {
|
|
934
|
+
sanitized[key] = "[REDACTED]";
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
if (typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
938
|
+
sanitized[key] = value;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
let text;
|
|
942
|
+
if (typeof value === "string") text = value;
|
|
943
|
+
else try {
|
|
944
|
+
text = JSON.stringify(value) ?? String(value);
|
|
945
|
+
} catch {
|
|
946
|
+
text = "[unserializable]";
|
|
947
|
+
}
|
|
948
|
+
sanitized[key] = truncateLogText(scrubLogText(text), MAX_LOG_FIELD_CHARS);
|
|
949
|
+
}
|
|
950
|
+
return count > 0 || hasOwn(sanitized, "__fields_truncated") ? sanitized : void 0;
|
|
951
|
+
}
|
|
952
|
+
function safeEmit(emit, entry) {
|
|
953
|
+
try {
|
|
954
|
+
emit(entry);
|
|
955
|
+
} catch {}
|
|
956
|
+
}
|
|
957
|
+
function createWorkflowLogger(emit, isReplaying) {
|
|
958
|
+
let emitted = 0;
|
|
959
|
+
let limitReported = false;
|
|
960
|
+
const record = (level, message, fields) => {
|
|
961
|
+
if (isReplaying?.() === true) return;
|
|
962
|
+
if (emitted >= MAX_LOGS_PER_INVOCATION) {
|
|
963
|
+
if (!limitReported) {
|
|
964
|
+
limitReported = true;
|
|
965
|
+
safeEmit(emit, {
|
|
966
|
+
level: "warn",
|
|
967
|
+
message: `workflow-sdk: log limit of ${MAX_LOGS_PER_INVOCATION} entries reached; further logs dropped`
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
emitted += 1;
|
|
973
|
+
const text = truncateLogText(scrubLogText(typeof message === "string" ? message : String(message)), MAX_LOG_MESSAGE_CHARS);
|
|
974
|
+
const sanitizedFields = fields === null || typeof fields !== "object" || Array.isArray(fields) ? void 0 : sanitizeLogFields(fields);
|
|
975
|
+
safeEmit(emit, {
|
|
976
|
+
level,
|
|
977
|
+
message: text,
|
|
978
|
+
...sanitizedFields === void 0 ? {} : { fields: sanitizedFields }
|
|
979
|
+
});
|
|
980
|
+
};
|
|
981
|
+
return {
|
|
982
|
+
debug: (message, fields) => record("debug", message, fields),
|
|
983
|
+
info: (message, fields) => record("info", message, fields),
|
|
984
|
+
warn: (message, fields) => record("warn", message, fields),
|
|
985
|
+
error: (message, fields) => record("error", message, fields)
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
const envelopeLogEmitter = (entry) => {
|
|
989
|
+
const line = JSON.stringify({
|
|
990
|
+
[WORKFLOW_LOG_ENVELOPE_KEY]: 1,
|
|
991
|
+
level: entry.level,
|
|
992
|
+
message: entry.message,
|
|
993
|
+
...entry.fields === void 0 ? {} : { fields: entry.fields }
|
|
994
|
+
});
|
|
995
|
+
if (entry.level === "debug") console.debug(line);
|
|
996
|
+
else if (entry.level === "warn") console.warn(line);
|
|
997
|
+
else if (entry.level === "error") console.error(line);
|
|
998
|
+
else console.info(line);
|
|
999
|
+
};
|
|
1000
|
+
async function invoke(workflow, request, options = {}) {
|
|
1001
|
+
const execution = new Execution(request.input, request.journal ?? {}, normalizedPayloadOffload(request.payloadOffload), options.onLog ?? envelopeLogEmitter);
|
|
1002
|
+
let settledHandlerOutcome;
|
|
1003
|
+
const settleHandler = (outcome) => {
|
|
1004
|
+
settledHandlerOutcome = outcome;
|
|
1005
|
+
return outcome;
|
|
1006
|
+
};
|
|
1007
|
+
const dispatchOutcome = execution.waitForDispatch().then(() => ({ kind: "dispatch" }));
|
|
1008
|
+
const handlerOutcome = Promise.resolve().then(() => workflow.handler(execution.context())).then((output) => settleHandler({
|
|
1009
|
+
kind: "done",
|
|
1010
|
+
output
|
|
1011
|
+
}), (error) => settleHandler({
|
|
1012
|
+
kind: "failed",
|
|
1013
|
+
error
|
|
1014
|
+
}));
|
|
1015
|
+
const outcome = await Promise.race([dispatchOutcome, handlerOutcome]);
|
|
1016
|
+
await execution.settleStartedSteps();
|
|
1017
|
+
if (outcome.kind === "dispatch" || execution.hasDispatches()) {
|
|
1018
|
+
if (settledHandlerOutcome?.kind === "failed") return failedWorkerResponse(settledHandlerOutcome.error, execution.completed);
|
|
1019
|
+
const unconsumedInlineFailure = execution.unconsumedInlineFailure();
|
|
1020
|
+
if (unconsumedInlineFailure !== void 0) return failedWorkerResponse(unconsumedInlineFailure, execution.completed);
|
|
1021
|
+
const unconsumedTask = execution.unconsumedTaskFailure();
|
|
1022
|
+
if (unconsumedTask !== void 0) return failedWorkerResponse(unconsumedTask, execution.completed);
|
|
1023
|
+
return {
|
|
1024
|
+
status: "dispatch",
|
|
1025
|
+
steps: execution.dispatches,
|
|
1026
|
+
completed: execution.completed
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
if (outcome.kind === "done") {
|
|
1030
|
+
const unconsumedInlineFailure = execution.unconsumedInlineFailure();
|
|
1031
|
+
if (unconsumedInlineFailure !== void 0) return failedWorkerResponse(unconsumedInlineFailure, execution.completed);
|
|
1032
|
+
const unconsumedTask = execution.unconsumedTaskFailure();
|
|
1033
|
+
if (unconsumedTask !== void 0) return failedWorkerResponse(unconsumedTask, execution.completed);
|
|
1034
|
+
if (outcome.output === void 0) return {
|
|
1035
|
+
status: "done",
|
|
1036
|
+
output: null,
|
|
1037
|
+
outputType: "undefined",
|
|
1038
|
+
completed: execution.completed
|
|
1039
|
+
};
|
|
1040
|
+
return {
|
|
1041
|
+
status: "done",
|
|
1042
|
+
output: outcome.output,
|
|
1043
|
+
completed: execution.completed
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return failedWorkerResponse(outcome.error, execution.completed);
|
|
1047
|
+
}
|
|
1048
|
+
function failedWorkerResponse(error, completed) {
|
|
1049
|
+
const stepFailure = error instanceof StepFailure ? error : void 0;
|
|
1050
|
+
const failedStep = stepFailure?.stepName ?? (error instanceof PromiseRejectedError ? `@promise/${error.promiseName}` : void 0);
|
|
1051
|
+
return {
|
|
1052
|
+
status: "failed",
|
|
1053
|
+
error: errorMessage(stepFailure?.reason ?? error),
|
|
1054
|
+
...failedStep === void 0 ? {} : { failedStep },
|
|
1055
|
+
completed
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function jsonResponse(body, status) {
|
|
1059
|
+
return new Response(JSON.stringify(body), {
|
|
1060
|
+
status,
|
|
1061
|
+
headers: { "content-type": "application/json" }
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
function isWorkerRequest(value) {
|
|
1065
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1066
|
+
if (!hasOwn(value, "input") || !hasOwn(value, "journal")) return false;
|
|
1067
|
+
if (hasOwn(value, "payloadOffload") && value.payloadOffload !== void 0 && !isPayloadOffloadValue(value.payloadOffload)) return false;
|
|
1068
|
+
const journal = value.journal;
|
|
1069
|
+
if (typeof journal !== "object" || journal === null || Array.isArray(journal)) return false;
|
|
1070
|
+
return Object.entries(journal).every(([name, entry]) => (/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(name) || /^@(now|uuid|promise)\/[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(name)) && isJournalEntryValue(entry));
|
|
1071
|
+
}
|
|
1072
|
+
function isWorkerTaskRequest(value) {
|
|
1073
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1074
|
+
if (!hasOwn(value, "operation") || !hasOwn(value, "task") || !hasOwn(value, "input") || !hasOwn(value, "runName") || !hasOwn(value, "stepId") || !hasOwn(value, "attempt") || !hasOwn(value, "workflowTenant") || !hasOwn(value, "workflowApiUrl")) return false;
|
|
1075
|
+
return value.operation === "task" && typeof value.task === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(value.task) && typeof value.runName === "string" && value.runName.length > 0 && value.runName.length <= 2048 && typeof value.stepId === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(value.stepId) && typeof value.attempt === "number" && Number.isInteger(value.attempt) && value.attempt > 0 && value.attempt <= 2147483647 && typeof value.workflowTenant === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value.workflowTenant) && typeof value.workflowApiUrl === "string";
|
|
1076
|
+
}
|
|
1077
|
+
const workflowApiOrigins = /* @__PURE__ */ new Set(["https://workflows.lovable.dev", "https://workflows.d.l5e.io"]);
|
|
1078
|
+
function workflowApiOrigin(value) {
|
|
1079
|
+
if (typeof value !== "string") return void 0;
|
|
1080
|
+
const normalized = value.endsWith("/") ? value.slice(0, -1) : value;
|
|
1081
|
+
return workflowApiOrigins.has(normalized) ? normalized : void 0;
|
|
1082
|
+
}
|
|
1083
|
+
function workflowIdentifier(value, kind) {
|
|
1084
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(value)) throw new Error(`workflow-sdk: invalid ${kind}`);
|
|
1085
|
+
return value;
|
|
1086
|
+
}
|
|
1087
|
+
function workflowRunResponse(value) {
|
|
1088
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("workflow-sdk: invalid workflow invocation response");
|
|
1089
|
+
const id = hasOwn(value, "id") ? value.id : void 0;
|
|
1090
|
+
const state = hasOwn(value, "state") ? value.state : void 0;
|
|
1091
|
+
const codeAttempt = hasOwn(value, "code_attempt") ? value.code_attempt : void 0;
|
|
1092
|
+
if (typeof id !== "string" || typeof state !== "string" || codeAttempt !== void 0 && (typeof codeAttempt !== "number" || !Number.isInteger(codeAttempt))) throw new Error("workflow-sdk: invalid workflow invocation response");
|
|
1093
|
+
return {
|
|
1094
|
+
id,
|
|
1095
|
+
state,
|
|
1096
|
+
...hasOwn(value, "error") && typeof value.error === "string" ? { error: value.error } : {},
|
|
1097
|
+
...hasOwn(value, "inputs") ? { inputs: value.inputs } : {},
|
|
1098
|
+
...hasOwn(value, "outputs") ? { outputs: value.outputs } : {},
|
|
1099
|
+
...codeAttempt === void 0 ? {} : { codeAttempt },
|
|
1100
|
+
...hasOwn(value, "create_time") && typeof value.create_time === "string" ? { createTime: value.create_time } : {},
|
|
1101
|
+
...hasOwn(value, "start_time") && typeof value.start_time === "string" ? { startTime: value.start_time } : {},
|
|
1102
|
+
...hasOwn(value, "end_time") && typeof value.end_time === "string" ? { endTime: value.end_time } : {}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function workflowsApi(origin, tenantId) {
|
|
1106
|
+
const invocations = /* @__PURE__ */ new Set();
|
|
1107
|
+
return { async run(request) {
|
|
1108
|
+
if (origin === void 0) throw new Error("workflow-sdk: workflow API is not configured");
|
|
1109
|
+
if (typeof request !== "object" || request === null || Array.isArray(request)) throw new Error("workflow-sdk: invalid workflow invocation");
|
|
1110
|
+
const workspaceId = workflowIdentifier(request.workspaceId, "workflow workspace ID");
|
|
1111
|
+
const projectId = workflowIdentifier(request.projectId, "workflow project ID");
|
|
1112
|
+
const workflowId = workflowIdentifier(request.workflowId, "workflow ID");
|
|
1113
|
+
if (request.invocationKey !== void 0 && (typeof request.invocationKey !== "string" || request.invocationKey.length > 128)) throw new Error("workflow-sdk: invalid workflow invocation key");
|
|
1114
|
+
const parent = `tenants/${tenantId}/workspaces/${workspaceId}/workflows/${workflowId}`;
|
|
1115
|
+
const invocation = `${parent}\0${request.invocationKey ?? ""}`;
|
|
1116
|
+
if (invocations.has(invocation)) throw new Error("workflow-sdk: duplicate workflow invocation key");
|
|
1117
|
+
invocations.add(invocation);
|
|
1118
|
+
const inputs = request.inputs === void 0 ? void 0 : cloneJSONValue(request.inputs, "workflow inputs");
|
|
1119
|
+
let response;
|
|
1120
|
+
try {
|
|
1121
|
+
response = await fetch(`${origin}/runtime/v1/runs`, {
|
|
1122
|
+
method: "POST",
|
|
1123
|
+
headers: { "content-type": "application/json" },
|
|
1124
|
+
body: JSON.stringify({
|
|
1125
|
+
parent,
|
|
1126
|
+
project_id: projectId,
|
|
1127
|
+
run: inputs === void 0 ? {} : { inputs },
|
|
1128
|
+
...request.invocationKey === void 0 ? {} : { invocation_key: request.invocationKey }
|
|
1129
|
+
}),
|
|
1130
|
+
redirect: "error"
|
|
1131
|
+
});
|
|
1132
|
+
} catch {
|
|
1133
|
+
throw new Error("workflow-sdk: workflow invocation failed");
|
|
1134
|
+
}
|
|
1135
|
+
if (!response.ok) throw new Error("workflow-sdk: workflow invocation failed");
|
|
1136
|
+
try {
|
|
1137
|
+
return workflowRunResponse(await response.json());
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
if (error instanceof Error && error.message === "workflow-sdk: invalid workflow invocation response") throw error;
|
|
1140
|
+
throw new Error("workflow-sdk: invalid workflow invocation response");
|
|
1141
|
+
}
|
|
1142
|
+
} };
|
|
1143
|
+
}
|
|
1144
|
+
function validatedWorkflowDefinition(value) {
|
|
1145
|
+
if (typeof value !== "object" || value === null || !hasOwn(value, "name") || !hasOwn(value, "handler")) throw new Error("workflow-sdk: invalid workflow definition");
|
|
1146
|
+
if (typeof value.name !== "string" || typeof value.handler !== "function") throw new Error("workflow-sdk: invalid workflow definition");
|
|
1147
|
+
assertEntryName(value.name);
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1150
|
+
function validatedTaskDefinition(value) {
|
|
1151
|
+
if (typeof value !== "object" || value === null || !hasOwn(value, "name") || !hasOwn(value, "environment") || !hasOwn(value, "run") || typeof value.name !== "string" || value.environment !== "cloudflare" || typeof value.run !== "function") throw new Error("workflow-sdk: invalid task definition");
|
|
1152
|
+
return defineTask(value);
|
|
1153
|
+
}
|
|
1154
|
+
function validatedTaskDefinitions(value) {
|
|
1155
|
+
if (value === void 0) return [];
|
|
1156
|
+
if (!Array.isArray(value)) throw new Error("workflow-sdk: tasks must be an array of task definitions");
|
|
1157
|
+
return value.map((task) => validatedTaskDefinition(task));
|
|
1158
|
+
}
|
|
1159
|
+
function toWorker(workflowValue, options = {}) {
|
|
1160
|
+
const workflow = validatedWorkflowDefinition(workflowValue);
|
|
1161
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
1162
|
+
for (const task of validatedTaskDefinitions(options.tasks)) {
|
|
1163
|
+
if (tasks.has(task.name)) throw new Error(`workflow-sdk: duplicate registered task name "${task.name}"`);
|
|
1164
|
+
tasks.set(task.name, task);
|
|
1165
|
+
}
|
|
1166
|
+
return { async fetch(request) {
|
|
1167
|
+
let body;
|
|
1168
|
+
try {
|
|
1169
|
+
body = await request.json();
|
|
1170
|
+
} catch {
|
|
1171
|
+
return jsonResponse({
|
|
1172
|
+
status: "failed",
|
|
1173
|
+
error: "invalid request body",
|
|
1174
|
+
completed: {}
|
|
1175
|
+
}, 400);
|
|
1176
|
+
}
|
|
1177
|
+
if (isWorkerTaskRequest(body)) {
|
|
1178
|
+
const workflowApiUrl = workflowApiOrigin(body.workflowApiUrl);
|
|
1179
|
+
if (body.workflowApiUrl !== "" && workflowApiUrl === void 0) return jsonResponse({
|
|
1180
|
+
status: "failed",
|
|
1181
|
+
error: "workflow API is not configured",
|
|
1182
|
+
completed: {}
|
|
1183
|
+
}, 503);
|
|
1184
|
+
const task = tasks.get(body.task);
|
|
1185
|
+
if (task?.environment !== "cloudflare") return jsonResponse({
|
|
1186
|
+
status: "failed",
|
|
1187
|
+
error: "task is not registered",
|
|
1188
|
+
completed: {}
|
|
1189
|
+
}, 400);
|
|
1190
|
+
try {
|
|
1191
|
+
const result = await task.run(body.input, {
|
|
1192
|
+
runName: body.runName,
|
|
1193
|
+
stepId: body.stepId,
|
|
1194
|
+
attempt: body.attempt,
|
|
1195
|
+
workflows: workflowsApi(workflowApiUrl, body.workflowTenant),
|
|
1196
|
+
log: createWorkflowLogger(envelopeLogEmitter)
|
|
1197
|
+
});
|
|
1198
|
+
return jsonResponse({
|
|
1199
|
+
status: "done",
|
|
1200
|
+
output: result === void 0 ? null : cloneJSONValue(result, "task output"),
|
|
1201
|
+
...result === void 0 ? { outputType: "undefined" } : {},
|
|
1202
|
+
completed: {}
|
|
1203
|
+
}, 200);
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
return jsonResponse({
|
|
1206
|
+
status: "failed",
|
|
1207
|
+
error: errorMessage(err),
|
|
1208
|
+
failedStep: body.task,
|
|
1209
|
+
completed: {}
|
|
1210
|
+
}, 200);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
if (!isWorkerRequest(body)) return jsonResponse({
|
|
1214
|
+
status: "failed",
|
|
1215
|
+
error: "invalid request body",
|
|
1216
|
+
completed: {}
|
|
1217
|
+
}, 400);
|
|
1218
|
+
const response = await invoke(workflow, body);
|
|
1219
|
+
try {
|
|
1220
|
+
return jsonResponse(response, 200);
|
|
1221
|
+
} catch {
|
|
1222
|
+
return jsonResponse({
|
|
1223
|
+
status: "failed",
|
|
1224
|
+
error: "workflow output is not JSON-serializable",
|
|
1225
|
+
completed: response.completed
|
|
1226
|
+
}, 200);
|
|
1227
|
+
}
|
|
1228
|
+
} };
|
|
1229
|
+
}
|
|
1230
|
+
async function driveToCompletion(workflow, input, options = {}) {
|
|
1231
|
+
const journal = Object.create(null);
|
|
1232
|
+
const maxAttempts = Math.max(1, options.maxAttempts ?? 1);
|
|
1233
|
+
for (let attempt = 1;; attempt++) {
|
|
1234
|
+
const response = await invoke(workflow, {
|
|
1235
|
+
input,
|
|
1236
|
+
journal
|
|
1237
|
+
}, { onLog: options.onLog });
|
|
1238
|
+
Object.assign(journal, response.completed);
|
|
1239
|
+
if (response.status === "done") {
|
|
1240
|
+
if (response.outputType === "undefined") return;
|
|
1241
|
+
return response.output;
|
|
1242
|
+
}
|
|
1243
|
+
if (response.status === "dispatch") throw new Error("workflow-sdk: driveToCompletion cannot execute engine-dispatched steps");
|
|
1244
|
+
if (attempt >= maxAttempts) throw new Error(response.error);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
//#endregion
|
|
1248
|
+
export { PromiseRejectedError, decryptPayloadStream, defineTask, defineWorkflow, driveToCompletion, encryptPayloadStream, invoke, toWorker };
|
|
1249
|
+
|
|
1250
|
+
//# sourceMappingURL=workflows.js.map
|