@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
JavaScript
// 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