@cloudflare/vitest-plugin 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1065 @@
1
+ import assert from "node:assert";
2
+ import { DurableObject, WorkerEntrypoint, WorkflowEntrypoint, env, env as env$1, exports } from "cloudflare:workers";
3
+ import workerdUnsafe from "workerd:unsafe";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+
6
+ //#region src/worker/d1.ts
7
+ function isD1Database(v) {
8
+ return typeof v === "object" && v !== null && v.constructor.name === "D1Database" && "prepare" in v && typeof v.prepare === "function" && "batch" in v && typeof v.batch === "function" && "exec" in v && typeof v.exec === "function";
9
+ }
10
+ function isD1Migration(v) {
11
+ return typeof v === "object" && v !== null && "name" in v && typeof v.name === "string" && "queries" in v && Array.isArray(v.queries) && v.queries.every((query) => typeof query === "string");
12
+ }
13
+ function isD1Migrations(v) {
14
+ return Array.isArray(v) && v.every(isD1Migration);
15
+ }
16
+ async function applyD1Migrations(db, migrations, migrationsTableName = "d1_migrations") {
17
+ if (!isD1Database(db)) throw new TypeError("Failed to execute 'applyD1Migrations': parameter 1 is not of type 'D1Database'.");
18
+ if (!isD1Migrations(migrations)) throw new TypeError("Failed to execute 'applyD1Migrations': parameter 2 is not of type 'D1Migration[]'.");
19
+ if (typeof migrationsTableName !== "string") throw new TypeError("Failed to execute 'applyD1Migrations': parameter 3 is not of type 'string'.");
20
+ const escapeId = (id) => `"${id.replace(/"/g, "\"\"")}"`;
21
+ const escapedTableName = escapeId(migrationsTableName);
22
+ const schema = `CREATE TABLE IF NOT EXISTS ${escapedTableName} (
23
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
24
+ name TEXT UNIQUE,
25
+ applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
26
+ );`;
27
+ await db.prepare(schema).run();
28
+ const appliedMigrationNames = (await db.prepare(`SELECT name FROM ${escapedTableName};`).all()).results.map(({ name }) => name);
29
+ const insertMigrationStmt = db.prepare(`INSERT INTO ${escapedTableName} (name) VALUES (?);`);
30
+ for (const migration of migrations) {
31
+ if (appliedMigrationNames.includes(migration.name)) continue;
32
+ const queries = migration.queries.map((query) => db.prepare(query));
33
+ queries.push(insertMigrationStmt.bind(migration.name));
34
+ await db.batch(queries);
35
+ }
36
+ }
37
+
38
+ //#endregion
39
+ //#region src/worker/env.ts
40
+ /**
41
+ * For reasons that aren't clear to me, just `SELF = exports.default` ends up with SELF being
42
+ * undefined in a test. This Proxy solution works.
43
+ */
44
+ const SELF = new Proxy({}, { get(_, p) {
45
+ const target = exports.default;
46
+ const value = target[p];
47
+ return typeof value === "function" ? value.bind(target) : value;
48
+ } });
49
+ function getSerializedOptions() {
50
+ assert(typeof __vitest_worker__ === "object", "Expected global Vitest state");
51
+ const options = __vitest_worker__.providedContext.cloudflarePoolOptions;
52
+ assert(options !== void 0, "Expected serialised options, got keys: " + Object.keys(__vitest_worker__.providedContext).join(", "));
53
+ const parsedOptions = JSON.parse(options);
54
+ return {
55
+ ...parsedOptions,
56
+ durableObjectBindingDesignators: new Map(parsedOptions.durableObjectBindingDesignators)
57
+ };
58
+ }
59
+ function getResolvedMainPath(forBindingType) {
60
+ const options = getSerializedOptions();
61
+ if (options.main === void 0) throw new Error(`Using ${forBindingType} bindings to the current worker requires \`poolOptions.workers.main\` to be set to your worker's entrypoint: ${JSON.stringify(options)}`);
62
+ return options.main;
63
+ }
64
+
65
+ //#endregion
66
+ //#region src/worker/durable-objects.ts
67
+ const DEFAULT_EVICTION_OPTIONS$1 = { webSockets: "hibernate" };
68
+ const CF_KEY_ACTION = "vitestPoolWorkersDurableObjectAction";
69
+ let nextActionId = 0;
70
+ const kUseResponse = Symbol("kUseResponse");
71
+ const actionResults = /* @__PURE__ */ new Map();
72
+ function isDurableObjectNamespace(v) {
73
+ return v instanceof Object && /^(?:Loopback)?DurableObjectNamespace$/.test(v.constructor.name) && "newUniqueId" in v && typeof v.newUniqueId === "function" && "idFromName" in v && typeof v.idFromName === "function" && "idFromString" in v && typeof v.idFromString === "function" && "get" in v && typeof v.get === "function";
74
+ }
75
+ function isDurableObjectStub(v) {
76
+ return typeof v === "object" && v !== null && (v.constructor.name === "DurableObject" || v.constructor.name === "WorkerRpc") && "fetch" in v && typeof v.fetch === "function" && "id" in v && typeof v.id === "object";
77
+ }
78
+ let sameIsolatedNamespaces;
79
+ function getSameIsolateNamespaces() {
80
+ if (sameIsolatedNamespaces !== void 0) return sameIsolatedNamespaces;
81
+ sameIsolatedNamespaces = [];
82
+ const options = getSerializedOptions();
83
+ if (options.durableObjectBindingDesignators === void 0) return sameIsolatedNamespaces;
84
+ for (const [key, designator] of options.durableObjectBindingDesignators) {
85
+ if (designator.scriptName !== void 0) continue;
86
+ const namespace = env$1[key] ?? exports?.[key];
87
+ assert(isDurableObjectNamespace(namespace), `Expected ${key} to be a DurableObjectNamespace binding`);
88
+ sameIsolatedNamespaces.push(namespace);
89
+ }
90
+ return sameIsolatedNamespaces;
91
+ }
92
+ function assertSameIsolate(stub) {
93
+ const idString = stub.id.toString();
94
+ const namespaces = getSameIsolateNamespaces();
95
+ for (const namespace of namespaces) try {
96
+ namespace.idFromString(idString);
97
+ return;
98
+ } catch {}
99
+ throw new Error("Durable Object test helpers can only be used with stubs pointing to objects defined within the same worker.");
100
+ }
101
+ async function runInStub(stub, callback) {
102
+ const id = nextActionId++;
103
+ actionResults.set(id, callback);
104
+ const response = await stub.fetch("http://x", {
105
+ cf: { [CF_KEY_ACTION]: id },
106
+ redirect: "manual"
107
+ });
108
+ assert(actionResults.has(id), `Expected action result for ${id}`);
109
+ const result = actionResults.get(id);
110
+ actionResults.delete(id);
111
+ if (result === kUseResponse) return response;
112
+ else if (response.ok) return result;
113
+ else throw result;
114
+ }
115
+ async function runInDurableObject(stub, callback) {
116
+ if (!isDurableObjectStub(stub)) throw new TypeError("Failed to execute 'runInDurableObject': parameter 1 is not of type 'DurableObjectStub'.");
117
+ if (typeof callback !== "function") throw new TypeError("Failed to execute 'runInDurableObject': parameter 2 is not of type 'function'.");
118
+ assertSameIsolate(stub);
119
+ return runInStub(stub, callback);
120
+ }
121
+ async function runAlarm(instance, state) {
122
+ if (await state.storage.getAlarm() === null) return false;
123
+ await state.storage.deleteAlarm();
124
+ await instance.alarm?.();
125
+ return true;
126
+ }
127
+ async function runDurableObjectAlarm(stub) {
128
+ if (!isDurableObjectStub(stub)) throw new TypeError("Failed to execute 'runDurableObjectAlarm': parameter 1 is not of type 'DurableObjectStub'.");
129
+ return await runInDurableObject(stub, runAlarm);
130
+ }
131
+ async function evictDurableObject(stub, options = DEFAULT_EVICTION_OPTIONS$1) {
132
+ if (!isDurableObjectStub(stub)) throw new TypeError("Failed to execute 'evictDurableObject': parameter 1 is not of type 'DurableObjectStub'.");
133
+ await workerdUnsafe.evict(stub, options);
134
+ }
135
+ /**
136
+ * Internal method for running `callback` inside the I/O context of the
137
+ * Runner Durable Object.
138
+ *
139
+ * Tests run in this context by default. This is required for performing
140
+ * operations that use Vitest's RPC mechanism as the Durable Object
141
+ * owns the RPC WebSocket. For example, importing modules or sending logs.
142
+ * Trying to perform those operations from a different context (e.g. within
143
+ * a `export default { fetch() {} }` handler or user Durable Object's `fetch()`
144
+ * handler) without using this function will result in a `Cannot perform I/O on
145
+ * behalf of a different request` error.
146
+ */
147
+ function runInRunnerObject(callback) {
148
+ return runInStub(env$1["__VITEST_POOL_WORKERS_RUNNER_OBJECT"].get("singleton"), callback);
149
+ }
150
+ async function maybeHandleRunRequest(request, instance, state) {
151
+ const actionId = request.cf?.[CF_KEY_ACTION];
152
+ if (actionId === void 0) return;
153
+ assert(typeof actionId === "number", `Expected numeric ${CF_KEY_ACTION}`);
154
+ try {
155
+ const callback = actionResults.get(actionId);
156
+ assert(typeof callback === "function", `Expected callback for ${actionId}`);
157
+ const result = await callback(instance, state);
158
+ if (result instanceof Response) {
159
+ actionResults.set(actionId, kUseResponse);
160
+ return result;
161
+ } else actionResults.set(actionId, result);
162
+ return new Response(null, { status: 204 });
163
+ } catch (e) {
164
+ actionResults.set(actionId, e);
165
+ return new Response(null, { status: 500 });
166
+ }
167
+ }
168
+ async function listDurableObjectIds(namespace) {
169
+ if (!isDurableObjectNamespace(namespace)) throw new TypeError("Failed to execute 'listDurableObjectIds': parameter 1 is not of type 'DurableObjectNamespace'.");
170
+ const boundName = Object.entries(env$1).find((entry) => namespace === entry[1])?.[0];
171
+ assert(boundName !== void 0, "Expected to find bound name for namespace");
172
+ const options = getSerializedOptions();
173
+ const searchParams = new URLSearchParams({ binding_name: boundName });
174
+ if (options.selfName !== void 0) searchParams.set("worker_name", options.selfName);
175
+ const url = `http://placeholder/durable-objects?${searchParams.toString()}`;
176
+ const res = await env$1.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE.fetch(url);
177
+ assert.strictEqual(res.status, 200);
178
+ const ids = await res.json();
179
+ assert(Array.isArray(ids));
180
+ return ids.map((id) => {
181
+ assert(typeof id === "string");
182
+ return namespace.idFromString(id);
183
+ });
184
+ }
185
+
186
+ //#endregion
187
+ //#region src/worker/wait-until.ts
188
+ /**
189
+ * In production, Workers have a 30-second limit for `waitUntil` promises.
190
+ * We use the same limit here. If promises are still pending after this,
191
+ * they almost certainly indicate a bug (e.g. a `waitUntil` promise that
192
+ * will never resolve). We log a warning and move on so the test suite
193
+ * doesn't hang indefinitely.
194
+ */
195
+ let WAIT_UNTIL_TIMEOUT = 3e4;
196
+ /** @internal — only exposed for tests */
197
+ function setWaitUntilTimeout(ms) {
198
+ WAIT_UNTIL_TIMEOUT = ms;
199
+ }
200
+ const kTimedOut = Symbol("kTimedOut");
201
+ /**
202
+ * Empty array and wait for all promises to resolve until no more added.
203
+ * If a single promise rejects, the rejection will be passed-through.
204
+ * If multiple promises reject, the rejections will be aggregated.
205
+ *
206
+ * If any batch of promises hasn't settled after {@link WAIT_UNTIL_TIMEOUT}ms,
207
+ * a warning is logged and the remaining promises are abandoned.
208
+ */
209
+ async function waitForWaitUntil(waitUntil) {
210
+ const errors = [];
211
+ while (waitUntil.length > 0) {
212
+ const batch = waitUntil.splice(0);
213
+ let timeoutId;
214
+ const result = await Promise.race([Promise.allSettled(batch).then((results) => ({ results })), new Promise((resolve) => timeoutId = setTimeout(() => resolve(kTimedOut), WAIT_UNTIL_TIMEOUT))]);
215
+ clearTimeout(timeoutId);
216
+ if (result === kTimedOut) {
217
+ __console.warn(`[vitest-plugin] ${batch.length} waitUntil promise(s) did not resolve within ${WAIT_UNTIL_TIMEOUT / 1e3}s and will be abandoned. This normally means your Worker's waitUntil handler has a bug that prevents it from settling (e.g. a fetch that never completes or a missing resolve/reject call).`);
218
+ waitUntil.length = 0;
219
+ break;
220
+ }
221
+ for (const settled of result.results) if (settled.status === "rejected") errors.push(settled.reason);
222
+ }
223
+ if (errors.length === 1) throw errors[0];
224
+ else if (errors.length > 1) throw new AggregateError(errors);
225
+ }
226
+ const globalWaitUntil = [];
227
+ function registerGlobalWaitUntil(promise) {
228
+ globalWaitUntil.push(promise);
229
+ }
230
+ function waitForGlobalWaitUntil() {
231
+ return waitForWaitUntil(globalWaitUntil);
232
+ }
233
+ const handlerContextStore = new AsyncLocalStorage();
234
+ function registerHandlerAndGlobalWaitUntil(promise) {
235
+ const handlerContext = handlerContextStore.getStore();
236
+ if (handlerContext === void 0) registerGlobalWaitUntil(promise);
237
+ else handlerContext.waitUntil(promise);
238
+ }
239
+
240
+ //#endregion
241
+ //#region src/worker/patch-ctx.ts
242
+ const patchedHandlerContexts = /* @__PURE__ */ new WeakSet();
243
+ /**
244
+ * Executes the given callback within the provided ExecutionContext,
245
+ * patching the context to ensure that:
246
+ *
247
+ * - waitUntil calls are registered globally
248
+ * - ctx.exports shows a warning if accessing missing exports
249
+ */
250
+ function patchAndRunWithHandlerContext(ctx, callback) {
251
+ if (!patchedHandlerContexts.has(ctx)) {
252
+ patchedHandlerContexts.add(ctx);
253
+ const originalWaitUntil = ctx.waitUntil;
254
+ ctx.waitUntil = (promise) => {
255
+ registerGlobalWaitUntil(promise);
256
+ return originalWaitUntil.call(ctx, promise);
257
+ };
258
+ if (isCtxExportsEnabled(ctx.exports)) Object.defineProperty(ctx, "exports", { value: getCtxExportsProxy(ctx.exports) });
259
+ }
260
+ return handlerContextStore.run(ctx, callback);
261
+ }
262
+ /**
263
+ * Creates a proxy to the `ctx.exports` object that will warn the user if they attempt
264
+ * to access an undefined property. This could be a valid mistake by the user or
265
+ * it could mean that our static analysis of the main Worker's exports missed something.
266
+ */
267
+ function getCtxExportsProxy(exports$1) {
268
+ return new Proxy(exports$1, { get(target, p) {
269
+ if (p in target) return target[p];
270
+ console.warn(`Attempted to access 'ctx.exports.${p}', which was not defined for the main Worker.\nCheck that '${p}' is exported as an entry-point from the Worker.\nThe '@cloudflare/vitest-plugin' integration tries to infer these exports by analyzing the source code of the main Worker.\n`);
271
+ } });
272
+ }
273
+ /**
274
+ * Returns true if `ctx.exports` is enabled via compatibility flags.
275
+ */
276
+ function isCtxExportsEnabled(exports$1) {
277
+ return globalThis.Cloudflare?.compatibilityFlags.enable_ctx_exports && exports$1 !== void 0;
278
+ }
279
+
280
+ //#endregion
281
+ //#region src/worker/entrypoints.ts
282
+ /**
283
+ * Internal method for importing a module using Vite's transformation and
284
+ * execution pipeline. Can be called from any I/O context, and will ensure the
285
+ * request is run from within the `__VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__`.
286
+ */
287
+ async function importModule(specifier) {
288
+ /**
289
+ * We need to run this import inside the Runner Object, or we get errors like:
290
+ * - The Workers runtime canceled this request because it detected that your Worker's code had hung and would never generate a response. Refer to: https://developers.cloudflare.com/workers/observability/errors/
291
+ * - Cannot perform I/O on behalf of a different Durable Object. I/O objects (such as streams, request/response bodies, and others) created in the context of one Durable Object cannot be accessed from a different Durable Object in the same isolate. This is a limitation of Cloudflare Workers which allows us to improve overall performance.
292
+ */
293
+ return runInRunnerObject(() => {
294
+ return __vitest_mocker__.moduleRunner.import(specifier);
295
+ });
296
+ }
297
+ const IGNORED_KEYS = ["self"];
298
+ /**
299
+ * Create a class extending `superClass` with a `Proxy` as a `prototype`.
300
+ * Unknown accesses on the `prototype` will defer to `getUnknownPrototypeKey()`.
301
+ * `workerd` will only look for RPC methods/properties on the prototype, not the
302
+ * instance. This helps avoid accidentally exposing things over RPC, but makes
303
+ * things a little trickier for us...
304
+ */
305
+ function createProxyPrototypeClass(superClass, getUnknownPrototypeKey) {
306
+ function Class(...args) {
307
+ Class.prototype = new Proxy(Class.prototype, { get(target, key, receiver) {
308
+ const value = Reflect.get(target, key, receiver);
309
+ if (value !== void 0) return value;
310
+ if (typeof key === "symbol" || IGNORED_KEYS.includes(key)) return;
311
+ return getUnknownPrototypeKey.call(receiver, key);
312
+ } });
313
+ return Reflect.construct(superClass, args, Class);
314
+ }
315
+ Reflect.setPrototypeOf(Class.prototype, superClass.prototype);
316
+ Reflect.setPrototypeOf(Class, superClass);
317
+ return Class;
318
+ }
319
+ /**
320
+ * Only properties and methods declared on the prototype can be accessed over
321
+ * RPC. This function throws a helpful error message if a property isn't
322
+ * defined. Note we need to distinguish between a property that returns
323
+ * `undefined` and something not being defined at all.
324
+ */
325
+ function assertRPCPropertyAccessible(ctor, instance, key) {
326
+ if (!Reflect.has(ctor.prototype, key)) {
327
+ const quotedKey = JSON.stringify(key);
328
+ const instanceHasKey = Reflect.has(instance, key);
329
+ let message = "";
330
+ if (instanceHasKey) message = [
331
+ `The RPC receiver's prototype does not implement ${quotedKey}, but the receiver instance does.`,
332
+ "Only properties and methods defined on the prototype can be accessed over RPC.",
333
+ `Ensure properties are declared like \`get ${key}() { ... }\` instead of \`${key} = ...\`,`,
334
+ `and methods are declared like \`${key}() { ... }\` instead of \`${key} = () => { ... }\`.`
335
+ ].join("\n");
336
+ else message = `The RPC receiver does not implement ${quotedKey}.`;
337
+ throw new TypeError(message);
338
+ }
339
+ }
340
+ function getRPCProperty(ctor, instance, key) {
341
+ assertRPCPropertyAccessible(ctor, instance, key);
342
+ return Reflect.get(ctor.prototype, key, instance);
343
+ }
344
+ /**
345
+ * When calling RPC methods dynamically, we don't know whether the `property`
346
+ * returned from `getSELFRPCProperty()` or `getDurableObjectRPCProperty()` below
347
+ * is just a property or a method. If we just returned `property`, but the
348
+ * client tried to call it as a method, `workerd` would throw an "x is not a
349
+ * function" error.
350
+ *
351
+ * Instead, we return a *callable, custom thenable*. This behaves like a
352
+ * function and a `Promise`! If `workerd` calls it, we'll wait for the promise
353
+ * to resolve then forward the call. Otherwise, this just appears like a regular
354
+ * async property. Note all client calls are async, so converting sync
355
+ * properties and methods to async is fine here.
356
+ *
357
+ * Unfortunately, wrapping `property` with a `Proxy` and an `apply()` trap gives
358
+ * `TypeError: Method Promise.prototype.then called on incompatible receiver #<Promise>`. :(
359
+ */
360
+ function getRPCPropertyCallableThenable(key, property, queueOwner) {
361
+ const fn = async function(...args) {
362
+ return enqueueInvocation(queueOwner, async (release) => {
363
+ try {
364
+ const maybeFn = await property;
365
+ if (typeof maybeFn === "function") return maybeFn(...args);
366
+ else throw new TypeError(`${JSON.stringify(key)} is not a function.`);
367
+ } finally {
368
+ release();
369
+ }
370
+ });
371
+ };
372
+ fn.then = (onFulfilled, onRejected) => property.then(onFulfilled, onRejected);
373
+ fn.catch = (onRejected) => property.catch(onRejected);
374
+ fn.finally = (onFinally) => property.finally(onFinally);
375
+ return fn;
376
+ }
377
+ const invocationQueues = /* @__PURE__ */ new WeakMap();
378
+ /**
379
+ * Preserve the order in which async wrapper invocations begin executing.
380
+ *
381
+ * Resolving a property like `stub.method`, or ensuring a Durable Object handler
382
+ * instance, may need to import user modules or instantiate wrapper objects. If
383
+ * several calls are fired synchronously, those async steps can otherwise
384
+ * complete out of order before the actual user code is invoked. The queue is
385
+ * released as soon as invocation starts, so async completions can still run
386
+ * concurrently.
387
+ */
388
+ async function enqueueInvocation(owner, callback) {
389
+ const previous = invocationQueues.get(owner) ?? Promise.resolve();
390
+ let releaseStarted;
391
+ const started = new Promise((resolve) => {
392
+ releaseStarted = resolve;
393
+ });
394
+ const release = () => {
395
+ const releaseStartedFn = releaseStarted;
396
+ if (releaseStartedFn !== void 0) releaseStartedFn();
397
+ };
398
+ const result = previous.catch(() => {}).then(() => callback(release));
399
+ invocationQueues.set(owner, started);
400
+ return result;
401
+ }
402
+ function getEntrypointState(instance) {
403
+ return instance;
404
+ }
405
+ const WORKER_ENTRYPOINT_KEYS = [
406
+ "connect",
407
+ "tailStream",
408
+ "fetch",
409
+ "tail",
410
+ "trace",
411
+ "scheduled",
412
+ "queue",
413
+ "test",
414
+ "email"
415
+ ];
416
+ const DURABLE_OBJECT_KEYS = [
417
+ "connect",
418
+ "fetch",
419
+ "alarm",
420
+ "webSocketMessage",
421
+ "webSocketClose",
422
+ "webSocketError"
423
+ ];
424
+ const OPTIONAL_DURABLE_OBJECT_KEYS = new Set([
425
+ "webSocketMessage",
426
+ "webSocketClose",
427
+ "webSocketError"
428
+ ]);
429
+ /**
430
+ * Get the export to use for `entrypoint`. This is used for the `SELF` service
431
+ * binding in `cloudflare:test`, which sets `entrypoint` to "default".
432
+ * This requires importing the `main` module with Vite.
433
+ */
434
+ async function getWorkerEntrypointExport(env$2, entrypoint) {
435
+ const mainPath = getResolvedMainPath("service");
436
+ const mainModule = await importModule(mainPath);
437
+ const entrypointValue = typeof mainModule === "object" && mainModule !== null && entrypoint in mainModule && mainModule[entrypoint];
438
+ if (!entrypointValue) {
439
+ const message = `${mainPath} does not export a ${entrypoint} entrypoint. \`@cloudflare/vitest-plugin\` does not support service workers or named entrypoints for \`SELF\`.\nIf you're using service workers, please migrate to the modules format: https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/`;
440
+ throw new TypeError(message);
441
+ }
442
+ return {
443
+ mainPath,
444
+ entrypointValue
445
+ };
446
+ }
447
+ /**
448
+ * Get a property named `key` from the user's `WorkerEntrypoint`. `wrapper` here
449
+ * is an instance of a `WorkerEntrypoint` wrapper (i.e. the return value of
450
+ * `createWorkerEntrypointWrapper()`). This requires importing the `main` module
451
+ * with Vite, so will always return a `Promise.`
452
+ */
453
+ async function getWorkerEntrypointRPCProperty(wrapper, entrypoint, key) {
454
+ const { ctx } = getEntrypointState(wrapper);
455
+ const { mainPath, entrypointValue } = await getWorkerEntrypointExport(env$1, entrypoint);
456
+ return patchAndRunWithHandlerContext(ctx, () => {
457
+ const env$2 = env$1;
458
+ const expectedWorkerEntrypointMessage = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkerEntrypoint\` for RPC`;
459
+ if (typeof entrypointValue !== "function") throw new TypeError(expectedWorkerEntrypointMessage);
460
+ const ctor = entrypointValue;
461
+ const instance = new ctor(ctx, env$2);
462
+ if (!(instance instanceof WorkerEntrypoint)) throw new TypeError(expectedWorkerEntrypointMessage);
463
+ const value = getRPCProperty(ctor, instance, key);
464
+ if (typeof value === "function") return (...args) => patchAndRunWithHandlerContext(ctx, () => value.apply(instance, args));
465
+ else return value;
466
+ });
467
+ }
468
+ function createWorkerEntrypointWrapper(entrypoint) {
469
+ const Wrapper = createProxyPrototypeClass(WorkerEntrypoint, function(key) {
470
+ if (DURABLE_OBJECT_KEYS.includes(key)) return;
471
+ return getRPCPropertyCallableThenable(key, getWorkerEntrypointRPCProperty(this, entrypoint, key), this);
472
+ });
473
+ for (const key of WORKER_ENTRYPOINT_KEYS) Wrapper.prototype[key] = async function(thing) {
474
+ const { mainPath, entrypointValue } = await getWorkerEntrypointExport(this.env, entrypoint);
475
+ return patchAndRunWithHandlerContext(this.ctx, () => {
476
+ if (typeof entrypointValue === "object" && entrypointValue !== null) {
477
+ const maybeFn = entrypointValue[key];
478
+ if (typeof maybeFn === "function") return maybeFn.call(entrypointValue, thing, env$1, this.ctx);
479
+ else {
480
+ const message = `Expected ${entrypoint} export of ${mainPath} to define a \`${key}()\` function`;
481
+ throw new TypeError(message);
482
+ }
483
+ } else if (typeof entrypointValue === "function") {
484
+ const instance = new entrypointValue(this.ctx, env$1);
485
+ if (!(instance instanceof WorkerEntrypoint)) {
486
+ const message = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkerEntrypoint\``;
487
+ throw new TypeError(message);
488
+ }
489
+ const maybeFn = instance[key];
490
+ if (typeof maybeFn === "function") return maybeFn.call(instance, thing);
491
+ else {
492
+ const message = `Expected ${entrypoint} export of ${mainPath} to define a \`${key}()\` method`;
493
+ throw new TypeError(message);
494
+ }
495
+ } else {
496
+ const message = `Expected ${entrypoint} export of ${mainPath} to be an object or a class, got ${entrypointValue}`;
497
+ throw new TypeError(message);
498
+ }
499
+ });
500
+ };
501
+ return Wrapper;
502
+ }
503
+ const kInstanceConstructor = Symbol("kInstanceConstructor");
504
+ const kInstance = Symbol("kInstance");
505
+ const kEnsureInstance = Symbol("kEnsureInstance");
506
+ async function getDurableObjectRPCProperty(wrapper, className, key) {
507
+ const { mainPath, instanceCtor, instance } = await wrapper[kEnsureInstance]();
508
+ if (!(instance instanceof DurableObject)) {
509
+ const message = `Expected ${className} exported by ${mainPath} be a subclass of \`DurableObject\` for RPC`;
510
+ throw new TypeError(message);
511
+ }
512
+ assertRPCPropertyAccessible(instanceCtor, instance, key);
513
+ if (Object.hasOwn(instance, key)) throw new TypeError(`The RPC receiver does not implement the method ${JSON.stringify(key)}.`);
514
+ const value = Reflect.get(instance, key, instance);
515
+ if (typeof value === "function") return value.bind(instance);
516
+ else return value;
517
+ }
518
+ function createDurableObjectWrapper(className) {
519
+ const Wrapper = createProxyPrototypeClass(DurableObject, function(key) {
520
+ if (WORKER_ENTRYPOINT_KEYS.includes(key)) return;
521
+ return getRPCPropertyCallableThenable(key, getDurableObjectRPCProperty(this, className, key), this);
522
+ });
523
+ Wrapper.prototype[kEnsureInstance] = async function() {
524
+ const { ctx, env: env$2 } = getEntrypointState(this);
525
+ const mainPath = getResolvedMainPath("Durable Object");
526
+ const constructor = (await importModule(mainPath))[className];
527
+ if (typeof constructor !== "function") throw new TypeError(`${mainPath} does not export a ${className} Durable Object`);
528
+ this[kInstanceConstructor] ??= constructor;
529
+ if (this[kInstanceConstructor] !== constructor) {
530
+ await ctx.blockConcurrencyWhile(() => {
531
+ throw new Error(`${mainPath} changed, invalidating this Durable Object. Please retry the \`DurableObjectStub#fetch()\` call.`);
532
+ });
533
+ assert.fail("Unreachable");
534
+ }
535
+ if (this[kInstance] === void 0) {
536
+ this[kInstance] = new this[kInstanceConstructor](ctx, env$2);
537
+ await ctx.blockConcurrencyWhile(async () => {});
538
+ }
539
+ return {
540
+ mainPath,
541
+ instanceCtor: this[kInstanceConstructor],
542
+ instance: this[kInstance]
543
+ };
544
+ };
545
+ Wrapper.prototype.fetch = async function(request) {
546
+ const { ctx } = getEntrypointState(this);
547
+ const { mainPath, instance } = await this[kEnsureInstance]();
548
+ const response = await maybeHandleRunRequest(request, instance, ctx);
549
+ if (response !== void 0) return response;
550
+ if (instance.fetch === void 0) {
551
+ const message = `${className} exported by ${mainPath} does not define a \`fetch()\` method`;
552
+ throw new TypeError(message);
553
+ }
554
+ return instance.fetch(request);
555
+ };
556
+ for (const key of DURABLE_OBJECT_KEYS) {
557
+ if (key === "fetch") continue;
558
+ Wrapper.prototype[key] = async function(...args) {
559
+ return enqueueInvocation(this, async (release) => {
560
+ try {
561
+ const { mainPath, instance } = await this[kEnsureInstance]();
562
+ const maybeFn = instance[key];
563
+ if (typeof maybeFn === "function") return maybeFn.apply(instance, args);
564
+ else if (OPTIONAL_DURABLE_OBJECT_KEYS.has(key)) return;
565
+ else {
566
+ const message = `${className} exported by ${mainPath} does not define a \`${key}()\` method`;
567
+ throw new TypeError(message);
568
+ }
569
+ } finally {
570
+ release();
571
+ }
572
+ });
573
+ };
574
+ }
575
+ return Wrapper;
576
+ }
577
+ function createWorkflowEntrypointWrapper(entrypoint) {
578
+ const Wrapper = createProxyPrototypeClass(WorkflowEntrypoint, function(key) {
579
+ if (!["run"].includes(key)) return;
580
+ return getRPCPropertyCallableThenable(key, getWorkerEntrypointRPCProperty(this, entrypoint, key), this);
581
+ });
582
+ Wrapper.prototype.run = async function(...args) {
583
+ const { mainPath, entrypointValue } = await getWorkerEntrypointExport(env$1, entrypoint);
584
+ if (typeof entrypointValue === "function") {
585
+ const instance = new entrypointValue(this.ctx, env$1);
586
+ if (!(instance instanceof WorkflowEntrypoint)) {
587
+ const message = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkflowEntrypoint\``;
588
+ throw new TypeError(message);
589
+ }
590
+ const maybeFn = instance["run"];
591
+ if (typeof maybeFn === "function") return patchAndRunWithHandlerContext(this.ctx, () => maybeFn.call(instance, ...args));
592
+ else {
593
+ const message = `Expected ${entrypoint} export of ${mainPath} to define a \`run()\` method, but got ${typeof maybeFn}`;
594
+ throw new TypeError(message);
595
+ }
596
+ } else {
597
+ const message = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkflowEntrypoint\`, but got ${entrypointValue}`;
598
+ throw new TypeError(message);
599
+ }
600
+ };
601
+ return Wrapper;
602
+ }
603
+
604
+ //#endregion
605
+ //#region src/worker/events.ts
606
+ const kConstructFlag = Symbol("kConstructFlag");
607
+ const kWaitUntil = Symbol("kWaitUntil");
608
+ var ExecutionContext = class ExecutionContext {
609
+ [kWaitUntil] = [];
610
+ constructor(flag) {
611
+ if (flag !== kConstructFlag) throw new TypeError("Illegal constructor");
612
+ }
613
+ exports = isCtxExportsEnabled(exports) ? getCtxExportsProxy(exports) : void 0;
614
+ waitUntil(promise) {
615
+ if (!(this instanceof ExecutionContext)) throw new TypeError("Illegal invocation");
616
+ this[kWaitUntil].push(promise);
617
+ registerGlobalWaitUntil(promise);
618
+ }
619
+ passThroughOnException() {}
620
+ };
621
+ function createExecutionContext() {
622
+ return new ExecutionContext(kConstructFlag);
623
+ }
624
+ function isExecutionContextLike(v) {
625
+ return typeof v === "object" && v !== null && kWaitUntil in v && Array.isArray(v[kWaitUntil]);
626
+ }
627
+ async function waitOnExecutionContext(ctx) {
628
+ if (!isExecutionContextLike(ctx)) throw new TypeError("Failed to execute 'getWaitUntil': parameter 1 is not of type 'ExecutionContext'.\nYou must call 'createExecutionContext()' or 'createPagesEventContext()' to get an 'ExecutionContext' instance.");
629
+ return waitForWaitUntil(ctx[kWaitUntil]);
630
+ }
631
+ var ScheduledController = class ScheduledController {
632
+ scheduledTime;
633
+ cron;
634
+ constructor(flag, options) {
635
+ if (flag !== kConstructFlag) throw new TypeError("Illegal constructor");
636
+ const scheduledTime = Number(options?.scheduledTime ?? Date.now());
637
+ const cron = String(options?.cron ?? "");
638
+ Object.defineProperties(this, {
639
+ scheduledTime: { get() {
640
+ return scheduledTime;
641
+ } },
642
+ cron: { get() {
643
+ return cron;
644
+ } }
645
+ });
646
+ }
647
+ noRetry() {
648
+ if (!(this instanceof ScheduledController)) throw new TypeError("Illegal invocation");
649
+ }
650
+ };
651
+ function createScheduledController(options) {
652
+ if (options !== void 0 && typeof options !== "object") throw new TypeError("Failed to execute 'createScheduledController': parameter 1 is not of type 'ScheduledOptions'.");
653
+ return new ScheduledController(kConstructFlag, options);
654
+ }
655
+ const kRetry = Symbol("kRetry");
656
+ const kAck = Symbol("kAck");
657
+ const kRetryAll = Symbol("kRetryAll");
658
+ const kAckAll = Symbol("kAckAll");
659
+ var QueueMessage = class QueueMessage {
660
+ #controller;
661
+ id;
662
+ timestamp;
663
+ body;
664
+ attempts;
665
+ [kRetry] = false;
666
+ [kAck] = false;
667
+ constructor(flag, controller, message) {
668
+ if (flag !== kConstructFlag) throw new TypeError("Illegal constructor");
669
+ this.#controller = controller;
670
+ const id = String(message.id);
671
+ let timestamp;
672
+ if (typeof message.timestamp === "number") timestamp = new Date(message.timestamp);
673
+ else if (message.timestamp instanceof Date) timestamp = new Date(message.timestamp.getTime());
674
+ else throw new TypeError("Incorrect type for the 'timestamp' field on 'ServiceBindingQueueMessage': the provided value is not of type 'date'.");
675
+ let attempts;
676
+ if (typeof message.attempts === "number") attempts = message.attempts;
677
+ else throw new TypeError("Incorrect type for the 'attempts' field on 'ServiceBindingQueueMessage': the provided value is not of type 'number'.");
678
+ if ("serializedBody" in message) throw new TypeError("Cannot use `serializedBody` with `createMessageBatch()`");
679
+ const body = structuredClone(message.body);
680
+ Object.defineProperties(this, {
681
+ id: { get() {
682
+ return id;
683
+ } },
684
+ timestamp: { get() {
685
+ return timestamp;
686
+ } },
687
+ body: { get() {
688
+ return body;
689
+ } },
690
+ attempts: { get() {
691
+ return attempts;
692
+ } }
693
+ });
694
+ }
695
+ retry() {
696
+ if (!(this instanceof QueueMessage)) throw new TypeError("Illegal invocation");
697
+ if (this.#controller[kRetryAll]) return;
698
+ if (this.#controller[kAckAll]) {
699
+ console.warn(`Received a call to retry() on message ${this.id} after ackAll() was already called. Calling retry() on a message after calling ackAll() has no effect.`);
700
+ return;
701
+ }
702
+ if (this[kAck]) {
703
+ console.warn(`Received a call to retry() on message ${this.id} after ack() was already called. Calling retry() on a message after calling ack() has no effect.`);
704
+ return;
705
+ }
706
+ this[kRetry] = true;
707
+ }
708
+ ack() {
709
+ if (!(this instanceof QueueMessage)) throw new TypeError("Illegal invocation");
710
+ if (this.#controller[kAckAll]) return;
711
+ if (this.#controller[kRetryAll]) {
712
+ console.warn(`Received a call to ack() on message ${this.id} after retryAll() was already called. Calling ack() on a message after calling retryAll() has no effect.`);
713
+ return;
714
+ }
715
+ if (this[kRetry]) {
716
+ console.warn(`Received a call to ack() on message ${this.id} after retry() was already called. Calling ack() on a message after calling retry() has no effect.`);
717
+ return;
718
+ }
719
+ this[kAck] = true;
720
+ }
721
+ };
722
+ var QueueController = class QueueController {
723
+ queue;
724
+ messages;
725
+ metadata;
726
+ [kRetryAll] = false;
727
+ [kAckAll] = false;
728
+ constructor(flag, queueOption, messagesOption) {
729
+ if (flag !== kConstructFlag) throw new TypeError("Illegal constructor");
730
+ const queue = String(queueOption);
731
+ const messages = messagesOption.map((message) => new QueueMessage(kConstructFlag, this, message));
732
+ const metadata = { metrics: {
733
+ backlogCount: 0,
734
+ backlogBytes: 0,
735
+ oldestMessageTimestamp: /* @__PURE__ */ new Date(0)
736
+ } };
737
+ Object.defineProperties(this, {
738
+ queue: { get() {
739
+ return queue;
740
+ } },
741
+ messages: { get() {
742
+ return messages;
743
+ } },
744
+ metadata: { get() {
745
+ return metadata;
746
+ } }
747
+ });
748
+ }
749
+ retryAll() {
750
+ if (!(this instanceof QueueController)) throw new TypeError("Illegal invocation");
751
+ if (this[kAckAll]) {
752
+ console.warn("Received a call to retryAll() after ackAll() was already called. Calling retryAll() after calling ackAll() has no effect.");
753
+ return;
754
+ }
755
+ this[kRetryAll] = true;
756
+ }
757
+ ackAll() {
758
+ if (!(this instanceof QueueController)) throw new TypeError("Illegal invocation");
759
+ if (this[kRetryAll]) {
760
+ console.warn("Received a call to ackAll() after retryAll() was already called. Calling ackAll() after calling retryAll() has no effect.");
761
+ return;
762
+ }
763
+ this[kAckAll] = true;
764
+ }
765
+ };
766
+ function createMessageBatch(queueName, messages) {
767
+ if (arguments.length === 0) throw new TypeError("Failed to execute 'createMessageBatch': parameter 1 is not of type 'string'.");
768
+ if (!Array.isArray(messages)) throw new TypeError("Failed to execute 'createMessageBatch': parameter 2 is not of type 'Array'.");
769
+ return new QueueController(kConstructFlag, queueName, messages);
770
+ }
771
+ async function getQueueResult(batch, ctx) {
772
+ if (!(batch instanceof QueueController)) throw new TypeError("Failed to execute 'getQueueResult': parameter 1 is not of type 'MessageBatch'.\nYou must call 'createMessageBatch()' to get a 'MessageBatch' instance.");
773
+ if (!(ctx instanceof ExecutionContext)) throw new TypeError("Failed to execute 'getQueueResult': parameter 2 is not of type 'ExecutionContext'.\nYou must call 'createExecutionContext()' to get an 'ExecutionContext' instance.");
774
+ await waitOnExecutionContext(ctx);
775
+ const retryMessages = [];
776
+ const explicitAcks = [];
777
+ for (const message of batch.messages) {
778
+ if (message[kRetry]) retryMessages.push({ msgId: message.id });
779
+ if (message[kAck]) explicitAcks.push(message.id);
780
+ }
781
+ return {
782
+ outcome: "ok",
783
+ retryBatch: { retry: batch[kRetryAll] },
784
+ ackAll: batch[kAckAll],
785
+ retryMessages,
786
+ explicitAcks
787
+ };
788
+ }
789
+ function hasASSETSServiceBinding(value) {
790
+ return "ASSETS" in value && typeof value.ASSETS === "object" && value.ASSETS !== null && "fetch" in value.ASSETS && typeof value.ASSETS.fetch === "function";
791
+ }
792
+ function createPagesEventContext(opts) {
793
+ if (typeof opts !== "object" || opts === null) throw new TypeError("Failed to execute 'createPagesEventContext': parameter 1 is not of type 'EventContextInit'.");
794
+ if (!(opts.request instanceof Request)) throw new TypeError("Incorrect type for the 'request' field on 'EventContextInit': the provided value is not of type 'Request'.");
795
+ if (opts.functionPath !== void 0 && typeof opts.functionPath !== "string") throw new TypeError("Incorrect type for the 'functionPath' field on 'EventContextInit': the provided value is not of type 'string'.");
796
+ if (opts.next !== void 0 && typeof opts.next !== "function") throw new TypeError("Incorrect type for the 'next' field on 'EventContextInit': the provided value is not of type 'function'.");
797
+ if (opts.params !== void 0 && !(typeof opts.params === "object" && opts.params !== null)) throw new TypeError("Incorrect type for the 'params' field on 'EventContextInit': the provided value is not of type 'object'.");
798
+ if (opts.data !== void 0 && !(typeof opts.data === "object" && opts.data !== null)) throw new TypeError("Incorrect type for the 'data' field on 'EventContextInit': the provided value is not of type 'object'.");
799
+ if (!hasASSETSServiceBinding(env)) throw new TypeError("Cannot call `createPagesEventContext()` without defining `ASSETS` service binding");
800
+ const ctx = createExecutionContext();
801
+ return {
802
+ request: opts.next ? opts.request.clone() : opts.request,
803
+ functionPath: opts.functionPath ?? "",
804
+ [kWaitUntil]: ctx[kWaitUntil],
805
+ waitUntil: ctx.waitUntil.bind(ctx),
806
+ passThroughOnException: ctx.passThroughOnException.bind(ctx),
807
+ async next(nextInput, nextInit) {
808
+ if (opts.next === void 0) throw new TypeError("Cannot call `EventContext#next()` without including `next` property in 2nd argument to `createPagesEventContext()`");
809
+ if (nextInput === void 0) return opts.next(opts.request);
810
+ else {
811
+ if (typeof nextInput === "string") nextInput = new URL(nextInput, opts.request.url).toString();
812
+ const nextRequest = new Request(nextInput, nextInit);
813
+ return opts.next(nextRequest);
814
+ }
815
+ },
816
+ env,
817
+ params: opts.params ?? {},
818
+ data: opts.data ?? {}
819
+ };
820
+ }
821
+
822
+ //#endregion
823
+ //#region src/worker/reset.ts
824
+ const DEFAULT_EVICTION_OPTIONS = { webSockets: "hibernate" };
825
+ async function reset() {
826
+ await workerdUnsafe.deleteAllDurableObjects();
827
+ }
828
+ async function abortAllDurableObjects() {
829
+ await workerdUnsafe.abortAllDurableObjects();
830
+ }
831
+ async function evictAllDurableObjects(options = DEFAULT_EVICTION_OPTIONS) {
832
+ await workerdUnsafe.evictAllDurableObjects(options);
833
+ }
834
+
835
+ //#endregion
836
+ //#region src/worker/secrets-store.ts
837
+ const ADMIN_API = "SecretsStoreSecret::admin_api";
838
+ /**
839
+ * Returns the admin API for a secrets store binding, allowing tests to
840
+ * create, update, and delete secrets that would otherwise be read-only.
841
+ *
842
+ * ```ts
843
+ * import { adminSecretsStore } from "cloudflare:test";
844
+ *
845
+ * const admin = adminSecretsStore(env.MY_SECRET);
846
+ * await admin.create("my-secret-value");
847
+ * ```
848
+ */
849
+ function adminSecretsStore(binding) {
850
+ if (typeof binding !== "object" || binding === null || typeof binding[ADMIN_API] !== "function") throw new TypeError("Failed to execute 'adminSecretsStore': parameter 1 is not a secrets store binding.");
851
+ return binding[ADMIN_API]();
852
+ }
853
+
854
+ //#endregion
855
+ //#region ../workflows-shared/src/introspection.ts
856
+ function normalizeStreamMockChunk(value) {
857
+ if (value instanceof Uint8Array) return value;
858
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
859
+ if (ArrayBuffer.isView(value) && !(value instanceof DataView)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
860
+ throw new TypeError("Workflow mockStepResult() ReadableStream chunks must be ArrayBuffer or TypedArray values.");
861
+ }
862
+ async function readStreamMockChunks(stream) {
863
+ if (stream.locked) throw new TypeError("Workflow mockStepResult() received a locked or unreadable ReadableStream.");
864
+ const chunks = [];
865
+ const reader = stream.getReader();
866
+ let fullyRead = false;
867
+ try {
868
+ while (true) {
869
+ const result = await reader.read();
870
+ if (result.done) {
871
+ fullyRead = true;
872
+ return chunks;
873
+ }
874
+ const chunk = normalizeStreamMockChunk(result.value);
875
+ if (chunk.byteLength === 0) continue;
876
+ chunks.push(chunk.slice());
877
+ }
878
+ } finally {
879
+ if (!fullyRead) await reader.cancel("stream mock consumption stopped before completion").catch(() => {});
880
+ try {
881
+ reader.releaseLock();
882
+ } catch {}
883
+ }
884
+ }
885
+ var WorkflowInstanceModificationRecorder = class {
886
+ constructor(operations) {
887
+ this.operations = operations;
888
+ }
889
+ async disableSleeps(steps) {
890
+ this.operations.push({
891
+ type: "disableSleeps",
892
+ steps
893
+ });
894
+ }
895
+ async disableRetryDelays(steps) {
896
+ this.operations.push({
897
+ type: "disableRetryDelays",
898
+ steps
899
+ });
900
+ }
901
+ async mockStepResult(step, stepResult) {
902
+ if (stepResult instanceof ReadableStream) {
903
+ const streamResult = {
904
+ __workflowIntrospectionStreamResult: true,
905
+ chunks: await readStreamMockChunks(stepResult)
906
+ };
907
+ this.operations.push({
908
+ type: "mockStepResult",
909
+ step,
910
+ stepResult: streamResult
911
+ });
912
+ return;
913
+ }
914
+ this.operations.push({
915
+ type: "mockStepResult",
916
+ step,
917
+ stepResult
918
+ });
919
+ }
920
+ async mockStepError(step, error, times) {
921
+ this.operations.push({
922
+ type: "mockStepError",
923
+ step,
924
+ error: {
925
+ name: error.name,
926
+ message: error.message
927
+ },
928
+ times
929
+ });
930
+ }
931
+ async forceStepTimeout(step, times) {
932
+ this.operations.push({
933
+ type: "forceStepTimeout",
934
+ step,
935
+ times
936
+ });
937
+ }
938
+ async mockEvent(event) {
939
+ this.operations.push({
940
+ type: "mockEvent",
941
+ event
942
+ });
943
+ }
944
+ async forceEventTimeout(step) {
945
+ this.operations.push({
946
+ type: "forceEventTimeout",
947
+ step
948
+ });
949
+ }
950
+ };
951
+ var WorkflowIntrospectorHandle = class {
952
+ #disposed = false;
953
+ #sessionId;
954
+ #instanceIntrospectors = /* @__PURE__ */ new Map();
955
+ #operations = [];
956
+ constructor(workflow) {
957
+ this.workflow = workflow;
958
+ }
959
+ async start() {
960
+ this.#sessionId = await this.workflow.unsafeStartIntrospection();
961
+ }
962
+ getSessionId() {
963
+ if (this.#sessionId === void 0) throw new Error("Workflow introspection has not started.");
964
+ return this.#sessionId;
965
+ }
966
+ async modifyAll(fn) {
967
+ const sessionId = this.getSessionId();
968
+ await fn(new WorkflowInstanceModificationRecorder(this.#operations));
969
+ await this.workflow.unsafeSetIntrospectionOperations(sessionId, this.#operations);
970
+ }
971
+ async get() {
972
+ const sessionId = this.getSessionId();
973
+ await this.syncInstanceIntrospectors(sessionId);
974
+ return Array.from(this.#instanceIntrospectors.values());
975
+ }
976
+ async syncInstanceIntrospectors(sessionId) {
977
+ const instanceIds = await this.workflow.unsafeGetIntrospectionInstances(sessionId);
978
+ for (const instanceId of instanceIds) if (!this.#instanceIntrospectors.has(instanceId)) this.#instanceIntrospectors.set(instanceId, new WorkflowInstanceIntrospectorHandle(this.workflow, instanceId));
979
+ }
980
+ async stopIntrospectionSession(sessionId) {
981
+ try {
982
+ await this.syncInstanceIntrospectors(sessionId);
983
+ } finally {
984
+ await this.workflow.unsafeStopIntrospection(sessionId);
985
+ }
986
+ }
987
+ async disposeInstanceIntrospectors() {
988
+ try {
989
+ await Promise.all(Array.from(this.#instanceIntrospectors.values(), (introspector) => introspector.dispose()));
990
+ } finally {
991
+ this.#instanceIntrospectors.clear();
992
+ }
993
+ }
994
+ /** Keep this bound; explicit resource management may call the disposer unbound. */
995
+ dispose = async () => {
996
+ if (this.#disposed) return;
997
+ this.#disposed = true;
998
+ const sessionId = this.#sessionId;
999
+ try {
1000
+ if (sessionId !== void 0) await this.stopIntrospectionSession(sessionId);
1001
+ } finally {
1002
+ await this.disposeInstanceIntrospectors();
1003
+ }
1004
+ };
1005
+ async [Symbol.asyncDispose]() {
1006
+ await this.dispose();
1007
+ }
1008
+ };
1009
+ var WorkflowInstanceIntrospectorHandle = class {
1010
+ #instanceModifier;
1011
+ #instanceModifierPromise;
1012
+ constructor(workflow, instanceId) {
1013
+ this.workflow = workflow;
1014
+ this.instanceId = instanceId;
1015
+ this.#instanceModifierPromise = workflow.unsafeGetInstanceModifier(instanceId).then((modifier) => {
1016
+ this.#instanceModifier = modifier;
1017
+ this.#instanceModifierPromise = void 0;
1018
+ return this.#instanceModifier;
1019
+ });
1020
+ this.#instanceModifierPromise.catch(() => {});
1021
+ }
1022
+ async modify(fn) {
1023
+ if (this.#instanceModifierPromise !== void 0) this.#instanceModifier = await this.#instanceModifierPromise;
1024
+ if (this.#instanceModifier === void 0) throw new Error("could not apply modifications due to internal error. Retrying the test may resolve the issue.");
1025
+ await fn(this.#instanceModifier);
1026
+ return this;
1027
+ }
1028
+ async waitForStepResult(step) {
1029
+ return await this.workflow.unsafeWaitForStepResult(this.instanceId, step.name, step.index);
1030
+ }
1031
+ async waitForStatus(status) {
1032
+ if (status === "queued") return;
1033
+ await this.workflow.unsafeWaitForStatus(this.instanceId, status);
1034
+ }
1035
+ async getOutput() {
1036
+ return await this.workflow.unsafeGetOutputOrError(this.instanceId, true);
1037
+ }
1038
+ async getError() {
1039
+ return await this.workflow.unsafeGetOutputOrError(this.instanceId, false);
1040
+ }
1041
+ /** Keep this bound; explicit resource management may call the disposer unbound. */
1042
+ dispose = async () => {
1043
+ await this.workflow.unsafeAbort(this.instanceId, "Instance dispose");
1044
+ };
1045
+ async [Symbol.asyncDispose]() {
1046
+ await this.dispose();
1047
+ }
1048
+ };
1049
+
1050
+ //#endregion
1051
+ //#region src/worker/workflows.ts
1052
+ async function introspectWorkflowInstance(workflow, instanceId) {
1053
+ if (!workflow || !instanceId) throw new Error("[WorkflowIntrospector] Workflow binding and instance id are required.");
1054
+ return new WorkflowInstanceIntrospectorHandle(workflow, instanceId);
1055
+ }
1056
+ async function introspectWorkflow(workflow) {
1057
+ if (!workflow) throw new Error("[WorkflowIntrospector] Workflow binding is required.");
1058
+ const introspector = new WorkflowIntrospectorHandle(workflow);
1059
+ await introspector.start();
1060
+ return introspector;
1061
+ }
1062
+
1063
+ //#endregion
1064
+ export { SELF, abortAllDurableObjects, adminSecretsStore, applyD1Migrations, createDurableObjectWrapper, createExecutionContext, createMessageBatch, createPagesEventContext, createScheduledController, createWorkerEntrypointWrapper, createWorkflowEntrypointWrapper, env, evictAllDurableObjects, evictDurableObject, getQueueResult, getResolvedMainPath, getSerializedOptions, handlerContextStore, introspectWorkflow, introspectWorkflowInstance, listDurableObjectIds, maybeHandleRunRequest, registerGlobalWaitUntil, registerHandlerAndGlobalWaitUntil, reset, runDurableObjectAlarm, runInDurableObject, runInRunnerObject, setWaitUntilTimeout, waitForGlobalWaitUntil, waitForWaitUntil, waitOnExecutionContext };
1065
+ //# sourceMappingURL=test-internal.mjs.map