generateCloudflareWorkerWrapper function
Generates the JavaScript wrapper that exports Cloudflare bindings.
compiledPath is the path to the Dart-compiled Worker JavaScript. The
generated wrapper imports its basename, exposes each class in
durableObjectClasses as a Durable Object class, and exposes container
classes from containerPorts using their configured TCP port. The result
is intended to be written as the Wrangler main file alongside the
compiled JavaScript.
When ssrEntry is supplied, it must refer to a bundled ES module whose
default export exposes a Cloudflare Fetch-compatible fetch method. The
wrapper dispatches /__ssr requests to that module and leaves all other
requests to the Routed Worker.
final wrapper = generateCloudflareWorkerWrapper(
'.dart_tool/routed/deploy/cloudflare/worker.dart.js',
const ['Counter'],
containerPorts: const {'ApiContainer': 8080},
);
Implementation
String generateCloudflareWorkerWrapper(
String compiledPath,
Iterable<String> durableObjectClasses, {
Map<String, int> containerPorts = const <String, int>{},
String? ssrEntry,
}) {
final relative = p.basename(compiledPath);
final exports = durableObjectClasses
.map((className) {
// Generated JavaScript is intentionally emitted without a leading
// newline so the wrapper remains stable for deployment tooling.
// ignore: leading_newlines_in_multiline_strings
return '''export class $className {
constructor(state, env) {
const factory = __routedDurableObjects['$className'];
if (typeof factory !== 'function') {
throw new Error('No Routed Durable Object factory registered for $className.');
}
this.delegate = factory(state, env);
}
fetch(request) {
return this.delegate.fetch(request);
}
alarm() {
return this.delegate.alarm();
}
webSocketMessage(webSocket, message) {
return this.delegate.webSocketMessage(webSocket, message);
}
webSocketClose(webSocket, code, reason, wasClean) {
return this.delegate.webSocketClose(webSocket, code, reason, wasClean);
}
webSocketError(webSocket, error) {
return this.delegate.webSocketError(webSocket, error);
}
}''';
})
.join('\n\n');
final containerExports = containerPorts.entries
.map((entry) {
// Generated JavaScript is intentionally emitted without a leading
// newline so the wrapper remains stable for deployment tooling.
// ignore: leading_newlines_in_multiline_strings
return '''export class ${entry.key} {
constructor(state, env) {
if (!state.container) {
throw new Error('Cloudflare Container state is unavailable for ${entry.key}.');
}
this.container = state.container;
this.container.start();
}
fetch(request) {
return this.container.getTcpPort(${entry.value}).fetch(request);
}
}''';
})
.join('\n\n');
final sections = [
if (exports.isNotEmpty) exports,
if (containerExports.isNotEmpty) containerExports,
].join('\n\n');
final ssrImport = ssrEntry == null
? ''
: "import frontendSsr from './$ssrEntry';";
final ssrDispatch = ssrEntry == null
? ''
: '''
const pathname = new URL(request.url).pathname;
if (pathname === '/__ssr') {
return await frontendSsr.fetch(request, env, ctx);
}
if (pathname !== '/' && pathname.lastIndexOf('.') > pathname.lastIndexOf('/')) {
const asset = await env.ASSETS.fetch(request);
if (asset.status !== 404) return asset;
}
''';
return '''
import './$relative';
$ssrImport
const __routedDurableObjects =
globalThis.__routed_durable_objects__ ?? {};
$sections
export default {
async fetch(request, env, ctx) {
$ssrDispatch
return await globalThis.__routed_fetch__(request, ctx, env);
},
};
''';
}