UNPKG

@cloudflare/vitest-pool-workers

Version:

Workers Vitest integration for writing Vitest unit and integration tests that run inside the Workers runtime

1,123 lines (1,114 loc) 38.2 kB
// src/worker/d1.ts function isD1Database(v) { 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"; } function isD1Migration(v) { 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"); } function isD1Migrations(v) { return Array.isArray(v) && v.every(isD1Migration); } async function applyD1Migrations(db, migrations, migrationsTableName = "d1_migrations") { if (!isD1Database(db)) { throw new TypeError( "Failed to execute 'applyD1Migrations': parameter 1 is not of type 'D1Database'." ); } if (!isD1Migrations(migrations)) { throw new TypeError( "Failed to execute 'applyD1Migrations': parameter 2 is not of type 'D1Migration[]'." ); } if (typeof migrationsTableName !== "string") { throw new TypeError( "Failed to execute 'applyD1Migrations': parameter 3 is not of type 'string'." ); } const schema = `CREATE TABLE IF NOT EXISTS ${migrationsTableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL );`; await db.prepare(schema).run(); const appliedMigrationNamesResult = await db.prepare(`SELECT name FROM ${migrationsTableName};`).all(); const appliedMigrationNames = appliedMigrationNamesResult.results.map( ({ name }) => name ); const insertMigrationStmt = db.prepare( `INSERT INTO ${migrationsTableName} (name) VALUES (?);` ); for (const migration of migrations) { if (appliedMigrationNames.includes(migration.name)) { continue; } const queries = migration.queries.map((query) => db.prepare(query)); queries.push(insertMigrationStmt.bind(migration.name)); await db.batch(queries); } } // src/worker/durable-objects.ts import assert2 from "node:assert"; // src/worker/env.ts import assert from "node:assert"; var env; var SELF; function stripInternalEnv(internalEnv2) { const result = { ...internalEnv2 }; delete result.__VITEST_POOL_WORKERS_SELF_NAME; delete result.__VITEST_POOL_WORKERS_SELF_SERVICE; delete result.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE; delete result.__VITEST_POOL_WORKERS_RUNNER_OBJECT; delete result.__VITEST_POOL_WORKERS_UNSAFE_EVAL; return result; } var internalEnv; function setEnv(newEnv) { internalEnv = newEnv; SELF = newEnv.__VITEST_POOL_WORKERS_SELF_SERVICE; env = stripInternalEnv(newEnv); } function getSerializedOptions() { assert(typeof __vitest_worker__ === "object", "Expected global Vitest state"); const options = __vitest_worker__.config?.poolOptions?.workers; assert(options !== void 0, "Expected serialised options"); return options; } function getResolvedMainPath(forBindingType) { const options = getSerializedOptions(); 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` ); } return options.main; } // src/worker/durable-objects.ts var CF_KEY_ACTION = "vitestPoolWorkersDurableObjectAction"; var nextActionId = 0; var kUseResponse = Symbol("kUseResponse"); var actionResults = /* @__PURE__ */ new Map(); function isDurableObjectNamespace(v) { return typeof v === "object" && v !== null && v.constructor.name === "DurableObjectNamespace" && "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"; } function isDurableObjectStub(v) { 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"; } var sameIsolatedNamespaces; function getSameIsolateNamespaces() { if (sameIsolatedNamespaces !== void 0) { return sameIsolatedNamespaces; } sameIsolatedNamespaces = []; const options = getSerializedOptions(); if (options.durableObjectBindingDesignators === void 0) { return sameIsolatedNamespaces; } for (const [key, designator] of options.durableObjectBindingDesignators) { if (designator.scriptName !== void 0) { continue; } const namespace = internalEnv[key]; assert2( isDurableObjectNamespace(namespace), `Expected ${key} to be a DurableObjectNamespace binding` ); sameIsolatedNamespaces.push(namespace); } return sameIsolatedNamespaces; } function assertSameIsolate(stub) { const idString = stub.id.toString(); const namespaces = getSameIsolateNamespaces(); for (const namespace of namespaces) { try { namespace.idFromString(idString); return; } catch { } } throw new Error( "Durable Object test helpers can only be used with stubs pointing to objects defined within the same worker." ); } async function runInStub(stub, callback) { const id = nextActionId++; actionResults.set(id, callback); const response = await stub.fetch("http://x", { cf: { [CF_KEY_ACTION]: id } }); assert2(actionResults.has(id), `Expected action result for ${id}`); const result = actionResults.get(id); actionResults.delete(id); if (result === kUseResponse) { return response; } else if (response.ok) { return result; } else { throw result; } } async function runInDurableObject(stub, callback) { if (!isDurableObjectStub(stub)) { throw new TypeError( "Failed to execute 'runInDurableObject': parameter 1 is not of type 'DurableObjectStub'." ); } if (typeof callback !== "function") { throw new TypeError( "Failed to execute 'runInDurableObject': parameter 2 is not of type 'function'." ); } assertSameIsolate(stub); return runInStub(stub, callback); } async function runAlarm(instance, state) { const alarm = await state.storage.getAlarm(); if (alarm === null) { return false; } await state.storage.deleteAlarm(); await instance.alarm?.(); return true; } async function runDurableObjectAlarm(stub) { if (!isDurableObjectStub(stub)) { throw new TypeError( "Failed to execute 'runDurableObjectAlarm': parameter 1 is not of type 'DurableObjectStub'." ); } return runInDurableObject(stub, runAlarm); } function runInRunnerObject(env2, callback) { const stub = env2.__VITEST_POOL_WORKERS_RUNNER_OBJECT.get("singleton"); return runInStub(stub, callback); } async function maybeHandleRunRequest(request, instance, state) { const actionId = request.cf?.[CF_KEY_ACTION]; if (actionId === void 0) { return; } assert2(typeof actionId === "number", `Expected numeric ${CF_KEY_ACTION}`); try { const callback = actionResults.get(actionId); assert2(typeof callback === "function", `Expected callback for ${actionId}`); const result = await callback(instance, state); if (result instanceof Response) { actionResults.set(actionId, kUseResponse); return result; } else { actionResults.set(actionId, result); } return new Response(null, { status: 204 }); } catch (e) { actionResults.set(actionId, e); return new Response(null, { status: 500 }); } } async function listDurableObjectIds(namespace) { if (!isDurableObjectNamespace(namespace)) { throw new TypeError( "Failed to execute 'listDurableObjectIds': parameter 1 is not of type 'DurableObjectNamespace'." ); } const boundName = Object.entries(internalEnv).find( (entry) => namespace === entry[1] )?.[0]; assert2(boundName !== void 0, "Expected to find bound name for namespace"); const options = getSerializedOptions(); const designator = options.durableObjectBindingDesignators?.get(boundName); assert2(designator !== void 0, "Expected to find designator for namespace"); let uniqueKey = designator.unsafeUniqueKey; if (uniqueKey === void 0) { const scriptName = designator.scriptName ?? internalEnv.__VITEST_POOL_WORKERS_SELF_NAME; const className = designator.className; uniqueKey = `${scriptName}-${className}`; } const url = `http://placeholder/durable-objects?unique_key=${encodeURIComponent( uniqueKey )}`; const res = await internalEnv.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE.fetch(url); assert2.strictEqual(res.status, 200); const ids = await res.json(); assert2(Array.isArray(ids)); return ids.map((id) => { assert2(typeof id === "string"); return namespace.idFromString(id); }); } // src/worker/entrypoints.ts import assert3 from "node:assert"; import { DurableObject as DurableObjectClass, WorkerEntrypoint, WorkflowEntrypoint } from "cloudflare:workers"; // src/worker/wait-until.ts import { AsyncLocalStorage } from "node:async_hooks"; async function waitForWaitUntil(waitUntil) { const errors = []; while (waitUntil.length > 0) { const results = await Promise.allSettled(waitUntil.splice(0)); for (const result of results) { if (result.status === "rejected") { errors.push(result.reason); } } } if (errors.length === 1) { throw errors[0]; } else if (errors.length > 1) { throw new AggregateError(errors); } } var globalWaitUntil = []; function registerGlobalWaitUntil(promise) { globalWaitUntil.push(promise); } function waitForGlobalWaitUntil() { return waitForWaitUntil(globalWaitUntil); } var handlerContextStore = new AsyncLocalStorage(); var patchedHandlerContexts = /* @__PURE__ */ new WeakSet(); function patchAndRunWithHandlerContext(ctx, callback) { if (!patchedHandlerContexts.has(ctx)) { patchedHandlerContexts.add(ctx); const originalWaitUntil = ctx.waitUntil; ctx.waitUntil = (promise) => { registerGlobalWaitUntil(promise); return originalWaitUntil.call(ctx, promise); }; } return handlerContextStore.run(ctx, callback); } function registerHandlerAndGlobalWaitUntil(promise) { const handlerContext = handlerContextStore.getStore(); if (handlerContext === void 0) { registerGlobalWaitUntil(promise); } else { handlerContext.waitUntil(promise); } } // src/worker/entrypoints.ts function importModule(env2, specifier) { return runInRunnerObject(env2, (instance) => { if (instance.executor === void 0) { const message = "Expected Vitest to start running before importing modules.\nThis usually means you have multiple `vitest` versions installed.\nUse your package manager's `why` command to list versions and why each is installed (e.g. `npm why vitest`)."; throw new Error(message); } return instance.executor.executeId(specifier); }); } function createProxyPrototypeClass(superClass, getUnknownPrototypeKey) { function Class(...args) { Class.prototype = new Proxy(Class.prototype, { get(target, key, receiver) { const value = Reflect.get(target, key, receiver); if (value !== void 0) { return value; } if (key === "self" || typeof key === "symbol") { return; } return getUnknownPrototypeKey.call(receiver, key); } }); return Reflect.construct(superClass, args, Class); } Reflect.setPrototypeOf(Class.prototype, superClass.prototype); Reflect.setPrototypeOf(Class, superClass); return Class; } function getRPCProperty(ctor, instance, key) { const prototypeHasKey = Reflect.has(ctor.prototype, key); if (!prototypeHasKey) { const quotedKey = JSON.stringify(key); const instanceHasKey = Reflect.has(instance, key); let message = ""; if (instanceHasKey) { message = [ `The RPC receiver's prototype does not implement ${quotedKey}, but the receiver instance does.`, "Only properties and methods defined on the prototype can be accessed over RPC.", `Ensure properties are declared like \`get ${key}() { ... }\` instead of \`${key} = ...\`,`, `and methods are declared like \`${key}() { ... }\` instead of \`${key} = () => { ... }\`.` ].join("\n"); } else { message = `The RPC receiver does not implement ${quotedKey}.`; } throw new TypeError(message); } return Reflect.get( /* target */ ctor.prototype, key, /* receiver */ instance ); } function getRPCPropertyCallableThenable(key, property) { const fn = async function(...args) { const maybeFn = await property; if (typeof maybeFn === "function") { return maybeFn(...args); } else { throw new TypeError(`${JSON.stringify(key)} is not a function.`); } }; fn.then = (onFulfilled, onRejected) => property.then(onFulfilled, onRejected); fn.catch = (onRejected) => property.catch(onRejected); fn.finally = (onFinally) => property.finally(onFinally); return fn; } function getEntrypointState(instance) { return instance; } var WORKER_ENTRYPOINT_KEYS = [ "fetch", "tail", "trace", "scheduled", "queue", "test" ]; var DURABLE_OBJECT_KEYS = [ "fetch", "alarm", "webSocketMessage", "webSocketClose", "webSocketError" ]; async function getWorkerEntrypointExport(env2, entrypoint) { const mainPath = getResolvedMainPath("service"); const mainModule = await importModule(env2, mainPath); const entrypointValue = typeof mainModule === "object" && mainModule !== null && entrypoint in mainModule && mainModule[entrypoint]; if (!entrypointValue) { const message = `${mainPath} does not export a ${entrypoint} entrypoint. \`@cloudflare/vitest-pool-workers\` does not support service workers or named entrypoints for \`SELF\`. If you're using service workers, please migrate to the modules format: https://developers.cloudflare.com/workers/reference/migrate-to-module-workers.`; throw new TypeError(message); } return { mainPath, entrypointValue }; } async function getWorkerEntrypointRPCProperty(wrapper, entrypoint, key) { const { ctx, env: env2 } = getEntrypointState(wrapper); const { mainPath, entrypointValue } = await getWorkerEntrypointExport( env2, entrypoint ); const userEnv = stripInternalEnv(env2); return patchAndRunWithHandlerContext(ctx, () => { const expectedWorkerEntrypointMessage = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkerEntrypoint\` for RPC`; if (typeof entrypointValue !== "function") { throw new TypeError(expectedWorkerEntrypointMessage); } const ctor = entrypointValue; const instance = new ctor(ctx, userEnv); if (!(instance instanceof WorkerEntrypoint)) { throw new TypeError(expectedWorkerEntrypointMessage); } const value = getRPCProperty(ctor, instance, key); if (typeof value === "function") { return (...args) => patchAndRunWithHandlerContext(ctx, () => value.apply(instance, args)); } else { return value; } }); } function createWorkerEntrypointWrapper(entrypoint) { const Wrapper = createProxyPrototypeClass( WorkerEntrypoint, function(key) { if (DURABLE_OBJECT_KEYS.includes(key)) { return; } const property = getWorkerEntrypointRPCProperty(this, entrypoint, key); return getRPCPropertyCallableThenable(key, property); } ); for (const key of WORKER_ENTRYPOINT_KEYS) { Wrapper.prototype[key] = async function(thing) { const { mainPath, entrypointValue } = await getWorkerEntrypointExport( this.env, entrypoint ); const userEnv = stripInternalEnv(this.env); return patchAndRunWithHandlerContext(this.ctx, () => { if (typeof entrypointValue === "object" && entrypointValue !== null) { const maybeFn = entrypointValue[key]; if (typeof maybeFn === "function") { return maybeFn.call(entrypointValue, thing, userEnv, this.ctx); } else { const message = `Expected ${entrypoint} export of ${mainPath} to define a \`${key}()\` function`; throw new TypeError(message); } } else if (typeof entrypointValue === "function") { const ctor = entrypointValue; const instance = new ctor(this.ctx, userEnv); if (!(instance instanceof WorkerEntrypoint)) { const message = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkerEntrypoint\``; throw new TypeError(message); } const maybeFn = instance[key]; if (typeof maybeFn === "function") { return maybeFn.call(instance, thing); } else { const message = `Expected ${entrypoint} export of ${mainPath} to define a \`${key}()\` method`; throw new TypeError(message); } } else { const message = `Expected ${entrypoint} export of ${mainPath}to be an object or a class, got ${entrypointValue}`; throw new TypeError(message); } }); }; } return Wrapper; } var kInstanceConstructor = Symbol("kInstanceConstructor"); var kInstance = Symbol("kInstance"); var kEnsureInstance = Symbol("kEnsureInstance"); async function getDurableObjectRPCProperty(wrapper, className, key) { const { mainPath, instanceCtor, instance } = await wrapper[kEnsureInstance](); if (!(instance instanceof DurableObjectClass)) { const message = `Expected ${className} exported by ${mainPath} be a subclass of \`DurableObject\` for RPC`; throw new TypeError(message); } const value = getRPCProperty(instanceCtor, instance, key); if (typeof value === "function") { return value.bind(instance); } else { return value; } } function createDurableObjectWrapper(className) { const Wrapper = createProxyPrototypeClass(DurableObjectClass, function(key) { if (WORKER_ENTRYPOINT_KEYS.includes(key)) { return; } const property = getDurableObjectRPCProperty(this, className, key); return getRPCPropertyCallableThenable(key, property); }); Wrapper.prototype[kEnsureInstance] = async function() { const { ctx, env: env2 } = getEntrypointState(this); const mainPath = getResolvedMainPath("Durable Object"); const mainModule = await importModule(env2, mainPath); const constructor = mainModule[className]; if (typeof constructor !== "function") { throw new TypeError( `${mainPath} does not export a ${className} Durable Object` ); } this[kInstanceConstructor] ??= constructor; if (this[kInstanceConstructor] !== constructor) { await ctx.blockConcurrencyWhile(() => { throw new Error( `${mainPath} changed, invalidating this Durable Object. Please retry the \`DurableObjectStub#fetch()\` call.` ); }); assert3.fail("Unreachable"); } if (this[kInstance] === void 0) { const userEnv = stripInternalEnv(env2); this[kInstance] = new this[kInstanceConstructor](ctx, userEnv); await ctx.blockConcurrencyWhile(async () => { }); } return { mainPath, instanceCtor: this[kInstanceConstructor], instance: this[kInstance] }; }; Wrapper.prototype.fetch = async function(request) { const { ctx } = getEntrypointState(this); const { mainPath, instance } = await this[kEnsureInstance](); const response = await maybeHandleRunRequest(request, instance, ctx); if (response !== void 0) { return response; } if (instance.fetch === void 0) { const message = `${className} exported by ${mainPath} does not define a \`fetch()\` method`; throw new TypeError(message); } return instance.fetch(request); }; for (const key of DURABLE_OBJECT_KEYS) { if (key === "fetch") { continue; } Wrapper.prototype[key] = async function(...args) { const { mainPath, instance } = await this[kEnsureInstance](); const maybeFn = instance[key]; if (typeof maybeFn === "function") { return maybeFn.apply(instance, args); } else { const message = `${className} exported by ${mainPath} does not define a \`${key}()\` method`; throw new TypeError(message); } }; } return Wrapper; } function createWorkflowEntrypointWrapper(entrypoint) { const Wrapper = createProxyPrototypeClass( WorkflowEntrypoint, function(key) { if (!["run"].includes(key)) { return; } const property = getWorkerEntrypointRPCProperty( this, entrypoint, key ); return getRPCPropertyCallableThenable(key, property); } ); Wrapper.prototype.run = async function(...args) { const { mainPath, entrypointValue } = await getWorkerEntrypointExport( this.env, entrypoint ); const userEnv = stripInternalEnv(this.env); if (typeof entrypointValue === "function") { const ctor = entrypointValue; const instance = new ctor(this.ctx, userEnv); if (!(instance instanceof WorkflowEntrypoint)) { const message = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkflowEntrypoint\``; throw new TypeError(message); } const maybeFn = instance["run"]; if (typeof maybeFn === "function") { return patchAndRunWithHandlerContext( this.ctx, () => maybeFn.call(instance, ...args) ); } else { const message = `Expected ${entrypoint} export of ${mainPath} to define a \`run()\` method, but got ${typeof maybeFn}`; throw new TypeError(message); } } else { const message = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkflowEntrypoint\`, but got ${entrypointValue}`; throw new TypeError(message); } }; return Wrapper; } // src/worker/events.ts var kConstructFlag = Symbol("kConstructFlag"); var kWaitUntil = Symbol("kWaitUntil"); var ExecutionContext = class _ExecutionContext { // https://github.com/cloudflare/workerd/blob/v1.20231218.0/src/workerd/api/global-scope.h#L168 [kWaitUntil] = []; constructor(flag) { if (flag !== kConstructFlag) { throw new TypeError("Illegal constructor"); } } waitUntil(promise) { if (!(this instanceof _ExecutionContext)) { throw new TypeError("Illegal invocation"); } this[kWaitUntil].push(promise); registerGlobalWaitUntil(promise); } passThroughOnException() { } }; function createExecutionContext() { return new ExecutionContext(kConstructFlag); } function isExecutionContextLike(v) { return typeof v === "object" && v !== null && kWaitUntil in v && Array.isArray(v[kWaitUntil]); } async function waitOnExecutionContext(ctx) { 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." ); } return waitForWaitUntil(ctx[kWaitUntil]); } var ScheduledController = class _ScheduledController { // https://github.com/cloudflare/workerd/blob/v1.20231218.0/src/workerd/api/scheduled.h#L35 scheduledTime; cron; constructor(flag, options) { if (flag !== kConstructFlag) { throw new TypeError("Illegal constructor"); } const scheduledTime = Number(options?.scheduledTime ?? Date.now()); const cron = String(options?.cron ?? ""); Object.defineProperties(this, { scheduledTime: { get() { return scheduledTime; } }, cron: { get() { return cron; } } }); } noRetry() { if (!(this instanceof _ScheduledController)) { throw new TypeError("Illegal invocation"); } } }; function createScheduledController(options) { if (options !== void 0 && typeof options !== "object") { throw new TypeError( "Failed to execute 'createScheduledController': parameter 1 is not of type 'ScheduledOptions'." ); } return new ScheduledController(kConstructFlag, options); } var kRetry = Symbol("kRetry"); var kAck = Symbol("kAck"); var kRetryAll = Symbol("kRetryAll"); var kAckAll = Symbol("kAckAll"); var QueueMessage = class _QueueMessage { // https://github.com/cloudflare/workerd/blob/v1.20231218.0/src/workerd/api/queue.h#L113 #controller; id; timestamp; body; attempts; [kRetry] = false; [kAck] = false; constructor(flag, controller, message) { if (flag !== kConstructFlag) { throw new TypeError("Illegal constructor"); } this.#controller = controller; const id = String(message.id); let timestamp; if (typeof message.timestamp === "number") { timestamp = new Date(message.timestamp); } else if (message.timestamp instanceof Date) { timestamp = new Date(message.timestamp.getTime()); } else { throw new TypeError( "Incorrect type for the 'timestamp' field on 'ServiceBindingQueueMessage': the provided value is not of type 'date'." ); } let attempts; if (typeof message.attempts === "number") { attempts = message.attempts; } else { throw new TypeError( "Incorrect type for the 'attempts' field on 'ServiceBindingQueueMessage': the provided value is not of type 'number'." ); } if ("serializedBody" in message) { throw new TypeError( "Cannot use `serializedBody` with `createMessageBatch()`" ); } const body = structuredClone(message.body); Object.defineProperties(this, { id: { get() { return id; } }, timestamp: { get() { return timestamp; } }, body: { get() { return body; } }, attempts: { get() { return attempts; } } }); } retry() { if (!(this instanceof _QueueMessage)) { throw new TypeError("Illegal invocation"); } if (this.#controller[kRetryAll]) { return; } if (this.#controller[kAckAll]) { 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.` ); return; } if (this[kAck]) { 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.` ); return; } this[kRetry] = true; } ack() { if (!(this instanceof _QueueMessage)) { throw new TypeError("Illegal invocation"); } if (this.#controller[kAckAll]) { return; } if (this.#controller[kRetryAll]) { 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.` ); return; } if (this[kRetry]) { 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.` ); return; } this[kAck] = true; } }; var QueueController = class _QueueController { // https://github.com/cloudflare/workerd/blob/v1.20231218.0/src/workerd/api/queue.h#L198 queue; messages; [kRetryAll] = false; [kAckAll] = false; constructor(flag, queueOption, messagesOption) { if (flag !== kConstructFlag) { throw new TypeError("Illegal constructor"); } const queue = String(queueOption); const messages = messagesOption.map( (message) => new QueueMessage(kConstructFlag, this, message) ); Object.defineProperties(this, { queue: { get() { return queue; } }, messages: { get() { return messages; } } }); } retryAll() { if (!(this instanceof _QueueController)) { throw new TypeError("Illegal invocation"); } if (this[kAckAll]) { console.warn( "Received a call to retryAll() after ackAll() was already called. Calling retryAll() after calling ackAll() has no effect." ); return; } this[kRetryAll] = true; } ackAll() { if (!(this instanceof _QueueController)) { throw new TypeError("Illegal invocation"); } if (this[kRetryAll]) { console.warn( "Received a call to ackAll() after retryAll() was already called. Calling ackAll() after calling retryAll() has no effect." ); return; } this[kAckAll] = true; } }; function createMessageBatch(queueName, messages) { if (arguments.length === 0) { throw new TypeError( "Failed to execute 'createMessageBatch': parameter 1 is not of type 'string'." ); } if (!Array.isArray(messages)) { throw new TypeError( "Failed to execute 'createMessageBatch': parameter 2 is not of type 'Array'." ); } return new QueueController(kConstructFlag, queueName, messages); } async function getQueueResult(batch, ctx) { 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." ); } 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." ); } await waitOnExecutionContext(ctx); const retryMessages = []; const explicitAcks = []; for (const message of batch.messages) { if (message[kRetry]) { retryMessages.push({ msgId: message.id }); } if (message[kAck]) { explicitAcks.push(message.id); } } return { outcome: "ok", retryBatch: { retry: batch[kRetryAll] }, ackAll: batch[kAckAll], retryMessages, explicitAcks }; } function hasASSETSServiceBinding(value) { return "ASSETS" in value && typeof value.ASSETS === "object" && value.ASSETS !== null && "fetch" in value.ASSETS && typeof value.ASSETS.fetch === "function"; } function createPagesEventContext(opts) { if (typeof opts !== "object" || opts === null) { throw new TypeError( "Failed to execute 'createPagesEventContext': parameter 1 is not of type 'EventContextInit'." ); } if (!(opts.request instanceof Request)) { throw new TypeError( "Incorrect type for the 'request' field on 'EventContextInit': the provided value is not of type 'Request'." ); } 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'." ); } 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'." ); } 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'." ); } 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'." ); } if (!hasASSETSServiceBinding(env)) { throw new TypeError( "Cannot call `createPagesEventContext()` without defining `ASSETS` service binding" ); } const ctx = createExecutionContext(); return { // If we might need to re-use this request, clone it request: opts.next ? opts.request.clone() : opts.request, functionPath: opts.functionPath ?? "", [kWaitUntil]: ctx[kWaitUntil], waitUntil: ctx.waitUntil.bind(ctx), passThroughOnException: ctx.passThroughOnException.bind(ctx), async next(nextInput, nextInit) { if (opts.next === void 0) { throw new TypeError( "Cannot call `EventContext#next()` without including `next` property in 2nd argument to `createPagesEventContext()`" ); } if (nextInput === void 0) { return opts.next(opts.request); } else { if (typeof nextInput === "string") { nextInput = new URL(nextInput, opts.request.url).toString(); } const nextRequest = new Request(nextInput, nextInit); return opts.next(nextRequest); } }, env, params: opts.params ?? {}, data: opts.data ?? {} }; } // src/worker/fetch-mock.ts import assert4 from "node:assert"; import { Buffer } from "node:buffer"; import { isMockActive, MockAgent, setDispatcher } from "cloudflare:mock-agent"; var DECODER = new TextDecoder(); function castAsAbortError(err) { err.code = "ABORT_ERR"; err.name = "AbortError"; return err; } var fetchMock = new MockAgent({ connections: 1 }); var requests = /* @__PURE__ */ new WeakMap(); var responses = /* @__PURE__ */ new WeakMap(); var originalFetch = fetch; setDispatcher((opts, handler) => { const request = requests.get(opts); assert4(request !== void 0, "Expected dispatch to come from fetch()"); originalFetch.call(globalThis, request.request, { body: request.body }).then((response) => { responses.set(opts, response); assert4(handler.onComplete !== void 0, "Expected onComplete() handler"); handler.onComplete?.([]); }).catch((error) => { assert4(handler.onError !== void 0, "Expected onError() handler"); handler.onError(error); }); }); globalThis.fetch = async (input, init) => { const isActive = isMockActive(fetchMock); if (!isActive) { return originalFetch.call(globalThis, input, init); } const request = new Request(input, init); const url = new URL(request.url); const abortSignal = init?.signal; let abortSignalAborted = abortSignal?.aborted ?? false; abortSignal?.addEventListener("abort", () => { abortSignalAborted = true; }); if (request.headers.get("Upgrade") !== null) { return originalFetch.call(globalThis, request); } const requestHeaders = {}; for (const entry of request.headers) { const key = entry[0].toLowerCase(); const value = entry[1]; if (key === "set-cookie") { (requestHeaders[key] ??= []).push(value); } else { requestHeaders[key] = value; } } const bodyArray = request.body === null ? null : new Uint8Array(await request.arrayBuffer()); const bodyText = bodyArray === null ? "" : DECODER.decode(bodyArray); const dispatchOptions = { origin: url.origin, path: url.pathname + url.search, method: request.method, body: bodyText, headers: requestHeaders }; requests.set(dispatchOptions, { request, body: bodyArray }); let responseStatusCode; let responseStatusText; let responseHeaders; const responseChunks = []; let responseResolve; let responseReject; const responsePromise = new Promise((resolve, reject) => { responseResolve = resolve; responseReject = reject; }); const dispatchHandlers = { onConnect(abort) { if (abortSignalAborted) { abort(); } }, onError(error) { responseReject(error); }, onUpgrade(_statusCode, _headers, _socket) { assert4.fail("Unreachable: upgrade requests not supported"); }, // `onHeaders` and `onData` will only be called if the response was mocked onHeaders(statusCode, headers, _resume, statusText) { if (abortSignalAborted) { return false; } responseStatusCode = statusCode; responseStatusText = statusText; if (headers === null) { return true; } assert4.strictEqual(headers.length % 2, 0, "Expected key/value array"); responseHeaders = Array.from({ length: headers.length / 2 }).map( (_, i) => [headers[i * 2].toString(), headers[i * 2 + 1].toString()] ); return true; }, onData(chunk) { if (abortSignalAborted) { return false; } responseChunks.push(chunk); return true; }, onComplete(_trailers) { if (abortSignalAborted) { responseReject( castAsAbortError(new Error("The operation was aborted")) ); return; } const maybeResponse = responses.get(dispatchOptions); if (maybeResponse === void 0) { const responseBody = Buffer.concat(responseChunks); const response = new Response(responseBody, { status: responseStatusCode, statusText: responseStatusText, headers: responseHeaders }); const throwImmutableHeadersError = () => { throw new TypeError("Can't modify immutable headers"); }; Object.defineProperty(response, "url", { value: url.href }); Object.defineProperties(response.headers, { set: { value: throwImmutableHeadersError }, append: { value: throwImmutableHeadersError }, delete: { value: throwImmutableHeadersError } }); responseResolve(response); } else { responseResolve(maybeResponse); } }, onBodySent(_chunk) { } // (ignored) }; fetchMock.dispatch(dispatchOptions, dispatchHandlers); return responsePromise; }; export { SELF, applyD1Migrations, castAsAbortError, createDurableObjectWrapper, createExecutionContext, createMessageBatch, createPagesEventContext, createScheduledController, createWorkerEntrypointWrapper, createWorkflowEntrypointWrapper, env, fetchMock, getQueueResult, getResolvedMainPath, getSerializedOptions, internalEnv, listDurableObjectIds, maybeHandleRunRequest, patchAndRunWithHandlerContext, registerGlobalWaitUntil, registerHandlerAndGlobalWaitUntil, runDurableObjectAlarm, runInDurableObject, runInRunnerObject, setEnv, stripInternalEnv, waitForGlobalWaitUntil, waitForWaitUntil, waitOnExecutionContext }; //# sourceMappingURL=test-internal.mjs.map