// Copyright (c) 2025 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 import { WorkerEntrypoint, DurableObject } from 'cloudflare:workers'; import assert from 'node:assert'; export class GreeterLoopback extends WorkerEntrypoint { async greet(name) { return `${this.ctx.props.greeting}, ${name}!`; } } export let basics = { async test(ctrl, env, ctx) { let worker = env.loader.get('basics', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` import {WorkerEntrypoint} from "cloudflare:workers"; export default { greet(name) { return "Hello, " + name; } } export let alternate = { greet(name, env, ctx) { return \`\${ctx.props.greeting}, \${name}\`; } } export class FancyPropsEntrypoint extends WorkerEntrypoint { async run() { let greet1 = await this.ctx.props.greeter.greet("Dave"); let greet2 = await this.ctx.props.greeter2.greet("Eve"); return [greet1, greet2].join("\\n"); } } `, }, }; }); { let result = await worker.getEntrypoint().greet('Alice'); assert.strictEqual(result, 'Hello, Alice'); } let greeter = worker.getEntrypoint('alternate', { props: { greeting: 'Welcome' }, }); let greeter2 = worker.getEntrypoint('alternate', { props: { greeting: 'Howdy' }, }); { let result = await greeter.greet('Bob'); assert.strictEqual(result, 'Welcome, Bob'); } { let result = await greeter2.greet('Carol'); assert.strictEqual(result, 'Howdy, Carol'); } let fancyProps = await worker.getEntrypoint('FancyPropsEntrypoint', { props: { greeter: ctx.exports.GreeterLoopback({ props: { greeting: "G'day" } }), greeter2: ctx.exports.GreeterLoopback({ props: { greeting: 'Sup' } }), }, }); { let result = await fancyProps.run(); assert.strictEqual(result, "G'day, Dave!\nSup, Eve!"); } // Note that we CANNOT transfer a dynamic worker entrypoint. assert.rejects( () => worker.getEntrypoint('FancyPropsEntrypoint', { props: { greeter, greeter2 }, }), { name: 'DataCloneError', message: 'Entrypoints to dynamically-loaded workers cannot be transferred to other Workers, ' + 'because the system does not know how to reload this Worker from scratch. Instead, ' + 'have the parent Worker expose an entrypoint which constructs the dynamic worker ' + 'and forwards to it.', } ); // Let's quickly verify that if we use ctx.exports.GreeterLoopback *without* invoking it, then // we get an error that it isn't serializable. (This isn't really testing worker-loader, it's // more testing LoopbackServiceStub and that serializability is not inherited.) assert.rejects( () => worker.getEntrypoint('FancyPropsEntrypoint', { props: { greeter: ctx.exports.GreeterLoopback, greeter2: ctx.exports.GreeterLoopback, }, }), { name: 'DataCloneError', message: 'Could not serialize object of type "LoopbackServiceStub". This type does not support ' + 'serialization.', } ); }, }; export let pythonBasics = { async test(ctrl, env, ctx) { let worker = env.loader.get('pythonBasics', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.py', compatibilityFlags: ['python_workers', 'python_no_global_handlers'], modules: { 'foo.py': ` from workers import WorkerEntrypoint class Default(WorkerEntrypoint): async def greet(self, name): return "Hello, " + name class alternate(WorkerEntrypoint): async def greet(self, name): return "Welcome, " + name `, }, }; }); { let result = await worker.getEntrypoint().greet('Stacey'); assert.strictEqual(result, 'Hello, Stacey'); } { let result = await worker.getEntrypoint('alternate').greet('Anthony'); assert.strictEqual(result, 'Welcome, Anthony'); } }, }; // Test supplying a basic `env` object. export let passEnv = { async test(ctrl, env, ctx) { let worker = env.loader.get('passEnv', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` export default { fetch(req, env, ctx) { return new Response("env.hello = " + env.hello); }, } `, }, env: { hello: 123, }, }; }); let resp = await worker.getEntrypoint().fetch('https://example.com'); assert.strictEqual(await resp.text(), 'env.hello = 123'); }, }; export let passEnvCaps = { async test(ctrl, env, ctx) { let worker = env.loader.get('passEnvCaps', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` export default { async fetch(req, env, ctx) { let greet1 = await env.greeter.greet("Alice"); let greet2 = await env.greeter2.greet("Bob"); return new Response([greet1, greet2].join("\\n")); }, } `, }, env: { greeter: ctx.exports.GreeterLoopback({ props: { greeting: 'Hello' }, }), greeter2: ctx.exports.GreeterLoopback({ props: { greeting: 'Welcome' }, }), }, }; }); let resp = await worker.getEntrypoint().fetch('https://example.com'); assert.strictEqual(await resp.text(), 'Hello, Alice!\nWelcome, Bob!'); }, }; export let testOutbound = { async fetch(req, env, ctx) { return new Response('hello from testOutbound'); }, }; // Test overriding globalOutbound export let overrideGlobalOutbound = { async test(ctrl, env, ctx) { let worker = env.loader.get('overrideGlobalOutbound', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` export default { fetch(req, env, ctx) { return fetch(req); }, } `, }, // Set globalOutbound to redirect to our own `testOutbound` entrypoint. globalOutbound: ctx.exports.testOutbound, }; }); let resp = await worker.getEntrypoint().fetch('https://example.com'); assert.strictEqual(await resp.text(), 'hello from testOutbound'); }, }; // This test's own `globalOutbound` is configured to loop back to this entrypoint. export let defaultOutbound = { async fetch(req, env, ctx) { return new Response('hello from defaultOutbound'); }, }; // Test that globalOutbound is inherited if not specified. export let inheritGlobalOutbound = { async test(ctrl, env, ctx) { let worker = env.loader.get('inheritGlobalOutbound', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` export default { fetch(req, env, ctx) { return fetch(req); }, } `, }, // Don't set `globalOutbound` at all. }; }); let resp = await worker.getEntrypoint().fetch('https://example.com'); assert.strictEqual(await resp.text(), 'hello from defaultOutbound'); }, }; // Test setting `globalOutbound` to null. export let nullGlobalOutbound = { async test(ctrl, env, ctx) { let worker = env.loader.get('nullGlobalOutbound', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` export default { fetch(req, env, ctx) { return fetch(req); }, } `, }, globalOutbound: null, }; }); await assert.rejects(worker.getEntrypoint().fetch('https://example.com'), { name: 'Error', message: 'This worker is not permitted to access the internet via global functions like fetch(). ' + "It must use capabilities (such as bindings in 'env') to talk to the outside world.", }); }, }; // We need to bounce the tail event through an actor to receive it back in the execution context // that set up the dynamic tail. `RendezvousActor` helps with that. export class RendezvousActor extends DurableObject { promiseAndResolvers = Promise.withResolvers(); resolve(value) { this.promiseAndResolvers.resolve(value); } wait() { return this.promiseAndResolvers.promise; } } export class TestTail extends WorkerEntrypoint { async tail(event) { // HACK: Currently, tail events are not serializable over RPC. :( event = JSON.parse(JSON.stringify(event)); await this.ctx.exports.RendezvousActor.getByName('tails').resolve({ props: this.ctx.props, event, }); } } // Test tail worker on dynamically-loaded worker. export let tails = { async test(ctrl, env, ctx) { let worker = env.loader.get('tails', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` export default { fetch(req, env, ctx) { console.log("hello, tail"); return new Response("OK"); }, } `, }, tails: [ctx.exports.TestTail({ props: { foo: 123 } })], }; }); let resp = await worker.getEntrypoint().fetch('https://example.com'); assert.strictEqual(await resp.text(), 'OK'); let { props, event } = await ctx.exports.RendezvousActor.getByName('tails').wait(); assert.deepEqual(props, { foo: 123 }); assert.strictEqual(event[0].logs[0].message[0], 'hello, tail'); }, }; export class GreeterFacet extends DurableObject { async greet(name) { return `${this.ctx.props.greeting}, ${name}?`; } } export class FacetTestActor extends DurableObject { async doTest() { let worker = this.env.loader.get('facets', () => { return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` import {DurableObject} from "cloudflare:workers"; export class MyActor extends DurableObject { i = 0; increment(j = 1) { this.i += j; return this.i; } myProps() { let result = {...this.ctx.props}; delete result.propsGreeter; return result; } async greets() { let greeter1 = this.ctx.facets.get("greeter1", () => { return { class: this.env.envGreeter }; }); let greeter2 = this.ctx.facets.get("greeter2", () => { return { class: this.ctx.props.propsGreeter }; }); let greeter3 = this.ctx.facets.get("greeter3", () => { return { class: this.env.envGreeter2 }; }); let greet1 = await greeter1.greet("Alice"); let greet2 = await greeter2.greet("Bob"); let greet3 = await greeter3.greet("Carol"); return [greet1, greet2, greet3].join("\\n"); } } `, }, // Set globalOutbound to redirect to our own `testOutbound` entrypoint. globalOutbound: this.ctx.exports.testOutbound, env: { envGreeter: this.ctx.exports.GreeterFacet({ props: { greeting: 'Hello' }, }), envGreeter2: this.ctx.exports.GreeterFacet({ props: { greeting: 'Howdy' }, }), // Catch a bug where actor class channel numbers were assigned incorrectly. someSubrequestChannel: this.ctx.exports.testOutbound({}), }, }; }); let cls = worker.getDurableObjectClass('MyActor', { props: { foo: 123, bar: 456, propsGreeter: this.ctx.exports.GreeterFacet({ props: { greeting: 'Welcome' }, }), }, }); let facet = this.ctx.facets.get('bar', () => { return { class: cls }; }); assert.strictEqual(await facet.increment(), 1); assert.strictEqual(await facet.increment(4), 5); assert.deepEqual(await facet.myProps(), { foo: 123, bar: 456 }); assert.strictEqual( await facet.greets(), 'Hello, Alice?\nWelcome, Bob?\nHowdy, Carol?' ); } } export let facets = { async test(ctrl, env, ctx) { let id = ctx.exports.FacetTestActor.idFromName('foo'); let stub = ctx.exports.FacetTestActor.get(id); await stub.doTest(); }, }; // Test isolate uniqueness when loading the same name multiple times. export let isolateUniqueness = { async test(ctrl, env, ctx) { let loadCount = 0; let loadCodeCallback = () => { ++loadCount; return { compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` import {WorkerEntrypoint} from "cloudflare:workers"; let i = 0; export default class extends WorkerEntrypoint { increment() { return i++; } } `, }, }; }; let assertNotCalled = () => { throw new Error( 'Code loader callback called when it should have reused an existing isolate.' ); }; let name = 'isolateUniqueness'; let name2 = 'isolateUniquenessOtherName'; // Load the same name from all four of our loaders: // - Two loaders with the same ID. // - One with a unique ID. // - One with no ID (anonymous). let shared1 = env.sharedLoader1.get(name, loadCodeCallback).getEntrypoint(); let shared2 = env.sharedLoader2.get(name, assertNotCalled).getEntrypoint(); let unique = env.uniqueLoader.get(name, loadCodeCallback).getEntrypoint(); let anonymous = env.loader.get(name, loadCodeCallback).getEntrypoint(); // Also try loading a different name from various loaders. let anonymousOtherName = env.loader .get(name2, loadCodeCallback) .getEntrypoint(); let sharedOtherName = env.sharedLoader1 .get(name2, loadCodeCallback) .getEntrypoint(); // Same name from loaders with the same ID should go to the same isolate. assert.strictEqual(await shared1.increment(), 0); assert.strictEqual(await shared1.increment(), 1); assert.strictEqual(await shared2.increment(), 2); assert.strictEqual(await shared2.increment(), 3); // Different IDs -> different isolates. assert.strictEqual(await anonymous.increment(), 0); assert.strictEqual(await anonymous.increment(), 1); assert.strictEqual(await unique.increment(), 0); assert.strictEqual(await unique.increment(), 1); // Different names -> different isolates. assert.strictEqual(await anonymousOtherName.increment(), 0); assert.strictEqual(await anonymousOtherName.increment(), 1); assert.strictEqual(await sharedOtherName.increment(), 0); assert.strictEqual(await sharedOtherName.increment(), 1); // Load the same name from the same loader -> same isolate. let anonymousAgain = env.loader.get(name, assertNotCalled).getEntrypoint(); assert.strictEqual(await anonymousAgain.increment(), 2); assert.strictEqual(await anonymousAgain.increment(), 3); // Test that null/undefined name parameter creates unique isolates each time // Each call should generate a unique name internally. let noName1 = env.loader.get(null, loadCodeCallback).getEntrypoint(); let noName2 = env.loader.get(undefined, loadCodeCallback).getEntrypoint(); let noName3 = env.loader.get(undefined, loadCodeCallback).getEntrypoint(); // Each should be a different isolate assert.strictEqual(await noName1.increment(), 0); assert.strictEqual(await noName1.increment(), 1); assert.strictEqual(await noName2.increment(), 0); assert.strictEqual(await noName2.increment(), 1); assert.strictEqual(await noName3.increment(), 0); assert.strictEqual(await noName3.increment(), 1); assert.strictEqual(loadCount, 8); }, }; // Test non-string module types export let moduleTypes = { async test(ctrl, env, ctx) { let worker = env.loader.get('moduleTypes', () => { return { compatibilityDate: '2025-01-01', mainModule: 'main.js', modules: { 'main.js': { js: ` import {WorkerEntrypoint} from "cloudflare:workers"; import textModule from './text.txt'; import jsonModule from './data.json'; import cjsModule from './cjs.cjs'; import dataModule from './binary.dat'; export default class extends WorkerEntrypoint { getTextModule() { return textModule; } getJsonModule() { return jsonModule; } getCjsModule() { return cjsModule.greet('Alice'); } getDataModule() { return dataModule; } } `, }, 'text.txt': { text: 'Hello from text module!', }, 'data.json': { json: { message: 'Hello from JSON module!', value: 42 }, }, 'cjs.cjs': { cjs: ` module.exports = { greet: function(name) { return 'Hello from CommonJS, ' + name; } }; `, }, 'binary.dat': { data: new TextEncoder().encode('Hello from binary data!'), }, }, }; }); let entrypoint = worker.getEntrypoint(); assert.strictEqual( await entrypoint.getTextModule(), 'Hello from text module!' ); let jsonData = await entrypoint.getJsonModule(); assert.strictEqual(jsonData.message, 'Hello from JSON module!'); assert.strictEqual(jsonData.value, 42); assert.strictEqual( await entrypoint.getCjsModule(), 'Hello from CommonJS, Alice' ); let dataModule = await entrypoint.getDataModule(); let decoder = new TextDecoder(); assert.strictEqual(decoder.decode(dataModule), 'Hello from binary data!'); }, }; // Test WASM module support export let wasmModules = { async test(ctrl, env, ctx) { // Create a simple WASM module that exports an add function // (module (func (export "add") (param i32 i32) (result i32) (local.get 0) (local.get 1) (i32.add))) const wasmBytes = new Uint8Array([ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b, ]); let worker = env.loader.get('wasmModules', () => { return { compatibilityDate: '2025-01-01', mainModule: 'main.js', modules: { 'main.js': { js: ` import {WorkerEntrypoint} from "cloudflare:workers"; import wasmModule from './math.wasm'; export default class extends WorkerEntrypoint { async getWasmAdd(a, b) { const instance = await WebAssembly.instantiate(wasmModule); return instance.exports.add(a, b); } } `, }, 'math.wasm': { wasm: wasmBytes, }, }, }; }); let entrypoint = worker.getEntrypoint(); let result = await entrypoint.getWasmAdd(5, 7); assert.strictEqual(result, 12); result = await entrypoint.getWasmAdd(100, 42); assert.strictEqual(result, 142); }, }; // Test setting compat date / flags works export let compatDateFlags = { async test(ctrl, env, ctx) { let getCode = (compatibilityDate, compatibilityFlags) => { return { compatibilityDate, compatibilityFlags, mainModule: 'main.js', modules: { 'main.js': ` import {WorkerEntrypoint} from "cloudflare:workers"; export default class extends WorkerEntrypoint { hasNodeCompat() { return typeof process !== 'undefined'; } canUseRequestCache() { try { new Request("https://foo", {cache: "no-store"}); return true; } catch { return false; } } } `, }, }; }; // Test old date (2023), with node compat turned on. { let worker = env.loader.get('compatDateFlags1', () => getCode('2023-01-01', ['nodejs_compat', 'nodejs_compat_v2']) ); let entrypoint = await worker.getEntrypoint(); assert.strictEqual(await entrypoint.hasNodeCompat(), true); assert.strictEqual(await entrypoint.canUseRequestCache(), false); } // Test newer date (2025), with node compat not enabled. { let worker = env.loader.get('compatDateFlags2', () => getCode('2025-01-01', []) ); let entrypoint = await worker.getEntrypoint(); assert.strictEqual(await entrypoint.hasNodeCompat(), false); assert.strictEqual(await entrypoint.canUseRequestCache(), true); } }, }; // Test code loader callback that does something asynchronous export let asyncCodeLoader = { async test(ctrl, env, ctx) { let worker = env.loader.get('asyncCodeLoader', async () => { // Simulate async work await new Promise((resolve) => setTimeout(resolve, 1)); return { compatibilityDate: '2025-01-01', mainModule: 'main.js', modules: { 'main.js': ` import {WorkerEntrypoint} from "cloudflare:workers"; export default class extends WorkerEntrypoint { getMessage() { return 'Hello from async loaded worker!'; } } `, }, }; }); let result = await worker.getEntrypoint().getMessage(); assert.strictEqual(result, 'Hello from async loaded worker!'); }, }; // Test what happens if the code getter callback throws an exception export let codeLoaderException = { async test(ctrl, env, ctx) { let worker = env.loader.get('codeLoaderException', () => { throw new Error('Code loader failed!'); }); let ep = worker.getEntrypoint(); try { await ep.someMethod(); assert.fail('Expected exception to be thrown'); } catch (error) { assert.strictEqual(error.message, 'Code loader failed!'); } }, }; // Test that ctx.exports works in dynamically-loaded workers. export let ctxExports = { async test(ctrl, env, ctx) { let worker = env.loader.get('ctxExports', () => { return { compatibilityDate: '2025-01-01', compatibilityFlags: ['enable_ctx_exports'], mainModule: 'foo.js', modules: { 'foo.js': ` import {WorkerEntrypoint} from "cloudflare:workers"; export default class extends WorkerEntrypoint { async greet(name) { let greeting = await this.ctx.exports.AltEntrypoint.greet(name); return greeting + "!"; } } export class AltEntrypoint extends WorkerEntrypoint { greet(name) { return "Hello, " + name; } } `, }, }; }); let result = await worker.getEntrypoint().greet('Alice'); assert.strictEqual(result, 'Hello, Alice!'); }, }; const mixedModules = { compatibilityDate: '2025-01-01', modules: { 'foo.py': ` from workers import WorkerEntrypoint class Default(WorkerEntrypoint): async def greet(self, name): return "Hello, " + name `, 'foo.js': ` export default { greet(name) { return "Hello, " + name; } } `, }, }; export let noMixedJsPythonModules = { async test(ctrl, env, ctx) { let worker = env.loader.get('noMixedJsPythonModules', () => { return { ...mixedModules, mainModule: 'foo.py', }; }); await assert.rejects(worker.getEntrypoint().greet('Alice'), { name: 'TypeError', message: 'Module "foo.js" is a JS module, but the main module is a Python module.', }); }, }; export let noMixedJsPythonModules2 = { async test(ctrl, env, ctx) { let worker = env.loader.get('noMixedJsPythonModules2', () => { return { ...mixedModules, mainModule: 'foo.js', }; }); await assert.rejects(worker.getEntrypoint().greet('Alice'), { name: 'TypeError', message: 'Module "foo.py" is a Python module, but the main module isn\'t a Python module.', }); }, }; export let suggestWorkerBundlerForTypeScriptModules = { async test(ctrl, env, ctx) { for (let mainModule of ['main.ts', 'main.tsx', 'main.jsx']) { let worker = env.loader.get(mainModule, () => { return { compatibilityDate: '2026-01-01', mainModule, modules: { [mainModule]: ` export default { fetch() { return new Response('ok'); } } `, }, }; }); await assert.rejects( worker.getEntrypoint().fetch('https://example.com'), { name: 'TypeError', message: `Module name must end with '.js' or '.py' (or the content must be an object indicating the type explicitly). Got: ${mainModule}. ` + "If you're trying to load TypeScript, bundle it first with '@cloudflare/worker-bundler' and pass the generated JavaScript modules.", } ); } }, }; // Test that startup exceptions show proper error messages, not internal errors. export let startupException = { async test(ctrl, env, ctx) { let worker = env.loader.get('startupException', () => { return { compatibilityDate: '2025-01-01', mainModule: 'main.js', modules: { 'main.js': ` import {WorkerEntrypoint} from "cloudflare:workers"; // This will throw during startup (global scope) throw new Error('intentional startup error'); export default class extends WorkerEntrypoint { greet(name) { return "Hello, " + name; } } `, }, }; }); let ep = worker.getEntrypoint(); try { await ep.greet('Alice'); assert.fail('Expected exception to be thrown'); } catch (error) { assert( error.message.includes('intentional startup error'), `Expected error message to contain 'intentional startup error', but got: ${error.message}` ); // The error should NOT be a generic internal error assert( !error.message.includes('internal error; reference'), `Error should not be a generic internal error, got: ${error.message}` ); } }, }; export let justLoad = { async test(ctrl, env, ctx) { let worker = env.loader.load({ compatibilityDate: '2025-01-01', mainModule: 'foo.js', modules: { 'foo.js': ` import {WorkerEntrypoint} from "cloudflare:workers"; export default { greet(name) { return "Hello, " + name; } } export let alternate = { greet(name, env, ctx) { return \`\${ctx.props.greeting}, \${name}\`; } } export class FancyPropsEntrypoint extends WorkerEntrypoint { async run() { let greet1 = await this.ctx.props.greeter.greet("Dave"); let greet2 = await this.ctx.props.greeter2.greet("Eve"); return [greet1, greet2].join("\\n"); } } `, }, }); { let result = await worker.getEntrypoint().greet('Alice'); assert.strictEqual(result, 'Hello, Alice'); } let greeter = worker.getEntrypoint('alternate', { props: { greeting: 'Welcome' }, }); let greeter2 = worker.getEntrypoint('alternate', { props: { greeting: 'Howdy' }, }); { let result = await greeter.greet('Bob'); assert.strictEqual(result, 'Welcome, Bob'); } { let result = await greeter2.greet('Carol'); assert.strictEqual(result, 'Howdy, Carol'); } }, }; // Test that abortIsolate() works correctly for dynamic workers. // When abortIsolate() is called on a dynamic worker, it should: // 1. Terminate the isolate (not the entire process). // 2. Cause subsequent lookups by name to reload the isolate from scratch. export let abortIsolateDynamic = { async test(ctrl, env, ctx) { // Create a dynamic worker that calls abortIsolate. ping() increments and returns a // module-scoped counter so we can distinguish fresh isolates from reused ones. let loadCount = 0; const getWorkerEntrypoint = () => env.loader .get('abort-test', () => { loadCount++; return { compatibilityDate: '2025-01-01', compatibilityFlags: ['experimental'], allowExperimental: true, mainModule: 'worker.js', modules: { 'worker.js': ` import {WorkerEntrypoint, abortIsolate} from "cloudflare:workers"; let counter = 0; export class TestEntrypoint extends WorkerEntrypoint { async crash() { abortIsolate('Dynamic worker abort test'); } async ping() { return ++counter; } } `, }, }; }) .getEntrypoint('TestEntrypoint'); let entrypoint = getWorkerEntrypoint(); // First, verify the worker works normally. Each ping increments the counter. assert.strictEqual(await entrypoint.ping(), 1); assert.strictEqual(await entrypoint.ping(), 2); assert.strictEqual(loadCount, 1); entrypoint = getWorkerEntrypoint(); assert.strictEqual(await entrypoint.ping(), 3); assert.strictEqual(loadCount, 1); // Now trigger abortIsolate - this should terminate the isolate but not crash the process. await assert.rejects( () => entrypoint.crash(), (err) => { return err.message.startsWith('internal error; reference ='); } ); entrypoint = getWorkerEntrypoint(); assert.strictEqual(await entrypoint.ping(), 1); assert.strictEqual(await entrypoint.ping(), 2); assert.strictEqual(loadCount, 2); }, }; // Test that abortIsolate() works correctly for anonymous dynamic workers. // Anonymous workers don't have a name and therefore aren't stored in the loader's map. export let abortIsolateDynamicAnonymous = { async test(ctrl, env, ctx) { // Create an anonymous dynamic worker (no name provided to get()) let worker = env.loader.get(null, () => { return { compatibilityDate: '2025-01-01', compatibilityFlags: ['experimental'], allowExperimental: true, mainModule: 'worker.js', modules: { 'worker.js': ` import {WorkerEntrypoint, abortIsolate} from "cloudflare:workers"; let counter = 0; export class TestEntrypoint extends WorkerEntrypoint { async crash() { abortIsolate('Anonymous dynamic worker abort test'); } async ping() { return ++counter; } } `, }, }; }); // First, verify the worker works normally. let entrypoint = worker.getEntrypoint('TestEntrypoint'); assert.strictEqual(await entrypoint.ping(), 1); assert.strictEqual(await entrypoint.ping(), 2); // Now trigger abortIsolate - this should terminate the isolate // but NOT crash the entire process. await assert.rejects( () => entrypoint.crash(), (err) => { return err.message.startsWith('internal error; reference ='); } ); }, };