diff --git a/.changeset/deep-lizards-move.md b/.changeset/deep-lizards-move.md new file mode 100644 index 000000000000..e4ecbb02dbd2 --- /dev/null +++ b/.changeset/deep-lizards-move.md @@ -0,0 +1,35 @@ +--- +"wrangler": patch +--- + +fix `wrangler dev` logs being logged on the incorrect level in some cases + +currently the way `wrangler dev` prints logs is faulty, for example the following code + +```js +console.error("this is an error"); +console.warn("this is a warning"); +console.debug("this is a debug"); +``` + +inside a worker would cause the following logs: + +```text +✘ [ERROR] this is an error + +✘ [ERROR] this is a warning + +this is a debug +``` + +(note that the warning is printed as an error and the debug log is printed even if by default it should not) + +the changes here make sure that the logs are instead logged to their correct level, so for the code about the following will be logged instead: + +```text +✘ [ERROR] this is an error + +▲ [WARNING] this is a warning +``` + +(running `wrangler dev` with the `--log-level=debug` flag will also cause the debug log to be included as well) diff --git a/.changeset/full-areas-lick.md b/.changeset/full-areas-lick.md new file mode 100644 index 000000000000..66091e99e4e3 --- /dev/null +++ b/.changeset/full-areas-lick.md @@ -0,0 +1,7 @@ +--- +"miniflare": minor +--- + +add `structuredWorkerdLogs` option + +add a new top-level option named `structuredWorkerdLogs` that makes workerd print to stdout structured logs (stringified jsons of the following shape: `{ timestamp: number, level: string, message: string }`) instead of printing logs to stdout and stderr diff --git a/fixtures/shared/src/run-wrangler-long-lived.ts b/fixtures/shared/src/run-wrangler-long-lived.ts index b8d2aaa78f27..7ad701a39241 100644 --- a/fixtures/shared/src/run-wrangler-long-lived.ts +++ b/fixtures/shared/src/run-wrangler-long-lived.ts @@ -87,7 +87,9 @@ async function runLongLivedWrangler( chunks.push(chunk); }); wranglerProcess.stderr?.on("data", (chunk) => { - console.log(`[${command}]`, chunk.toString()); + if (process.env.WRANGLER_LOG === "debug") { + console.log(`[${command}]`, chunk.toString()); + } chunks.push(chunk); }); wranglerProcess.once("exit", (exitCode) => { diff --git a/fixtures/worker-logs/package.json b/fixtures/worker-logs/package.json new file mode 100644 index 000000000000..9e5d46ce66fb --- /dev/null +++ b/fixtures/worker-logs/package.json @@ -0,0 +1,22 @@ +{ + "name": "@fixture/worker-logs", + "private": true, + "scripts": { + "check:type": "tsc", + "dev": "pnpm dev.module", + "dev.module": "wrangler dev -c wrangler.module.jsonc", + "dev.service": "wrangler dev -c wrangler.service.jsonc", + "start": "pnpm dev.module", + "test:ci": "vitest run", + "test:watch": "vitest" + }, + "devDependencies": { + "@cloudflare/workers-tsconfig": "workspace:^", + "typescript": "catalog:default", + "vitest": "catalog:default", + "wrangler": "workspace:*" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/fixtures/worker-logs/src/module.js b/fixtures/worker-logs/src/module.js new file mode 100644 index 000000000000..c825ace82cc2 --- /dev/null +++ b/fixtures/worker-logs/src/module.js @@ -0,0 +1,25 @@ +export default { + async fetch(request, env) { + const response = new Response("Hello"); + + const customMessage = request.headers.get("x-custom-message"); + if (customMessage) { + if (customMessage === "%__VERY_VERY_LONG_MESSAGE_%") { + // We can't simply pass a huge long message as a header thus + // why a placeholder is used here + console.log("z".repeat(2 ** 20)); + } else { + console.log(customMessage); + } + return response; + } + + console.log("<<<<>>>>"); + console.warn("<<<<>>>>"); + console.error("<<<<>>>>"); + console.debug("<<<<>>>>"); + console.info("<<<<>>>>"); + + return response; + }, +}; diff --git a/fixtures/worker-logs/src/service.js b/fixtures/worker-logs/src/service.js new file mode 100644 index 000000000000..cd66a074da2a --- /dev/null +++ b/fixtures/worker-logs/src/service.js @@ -0,0 +1,12 @@ +async function handler(request) { + console.log("<<<<>>>>"); + console.warn("<<<<>>>>"); + console.error("<<<<>>>>"); + console.debug("<<<<>>>>"); + console.info("<<<<>>>>"); + return new Response("Hello"); +} + +addEventListener("fetch", (event) => { + event.respondWith(handler(event.request)); +}); diff --git a/fixtures/worker-logs/tests/index.test.ts b/fixtures/worker-logs/tests/index.test.ts new file mode 100644 index 000000000000..5779a918482b --- /dev/null +++ b/fixtures/worker-logs/tests/index.test.ts @@ -0,0 +1,298 @@ +import { setTimeout } from "node:timers/promises"; +import { resolve } from "path"; +import { describe, test } from "vitest"; +import { runWranglerDev } from "../../shared/src/run-wrangler-long-lived"; + +async function getWranglerDevOutput( + type: "module" | "service", + extraArgs: string[] = [], + customMessage?: string, + requests = 1, + env = {} +) { + const { ip, port, stop, getOutput } = await runWranglerDev( + resolve(__dirname, ".."), + [ + `-c=wrangler.${type}.jsonc`, + "--port=0", + "--inspector-port=0", + ...extraArgs, + ], + env + ); + + const request = new Request(`http://${ip}:${port}`); + if (customMessage) { + request.headers.set("x-custom-message", customMessage); + } + + for (let i = 0; i < requests; i++) { + const response = await fetch(request); + await response.text(); + + // We wait for a bit for the output stream to be completely ready + // (this is a bit slow but it's generic to be used by all tests + // in this file, it also seems to make the tests very stable) + await setTimeout(500); + } + + await stop(); + + let output = getOutput(); + + output = output + // Windows gets a different marker for ✘, so let's normalize it here + // so that these tests can be platform independent + .replaceAll("✘", "X") + // Let's also normalize Windows newlines + .replaceAll("\r\n", "\n"); + + // Let's filter out lines we're not interested in + output = output + .split("\n") + .filter((line) => + logLineToIgnoreRegexps.every((regex) => !regex.test(line)) + ) + // let's also sort the logs for more stability of the tests, ideally + // we would want to test the log's ordering as well but that seems + // to cause flakes in the CI runs + .sort() + .join("\n"); + + return output; +} + +describe("'wrangler dev' correctly displays logs", () => { + describe("module workers", () => { + test("default behavior", async ({ expect }) => { + const output = await getWranglerDevOutput("module"); + expect(output).toMatchInlineSnapshot(` + "X [ERROR] <<<<>>>> + ▲ [WARNING] <<<<>>>> + <<<<>>>> + <<<<>>>>" + `); + }); + + test("with --log-level=log", async ({ expect }) => { + const output = await getWranglerDevOutput("module", ["--log-level=log"]); + expect(output).toMatchInlineSnapshot(` + "X [ERROR] <<<<>>>> + ▲ [WARNING] <<<<>>>> + <<<<>>>> + <<<<>>>>" + `); + }); + + test("with --log-level=info", async ({ expect }) => { + const output = await getWranglerDevOutput("module", ["--log-level=info"]); + expect(output).toMatchInlineSnapshot(` + "X [ERROR] <<<<>>>> + ▲ [WARNING] <<<<>>>> + <<<<>>>>" + `); + }); + + test("with --log-level=warn", async ({ expect }) => { + const output = await getWranglerDevOutput("module", ["--log-level=warn"]); + expect(output).toMatchInlineSnapshot(` + "X [ERROR] <<<<>>>> + ▲ [WARNING] <<<<>>>>" + `); + }); + + test("with --log-level=error", async ({ expect }) => { + const output = await getWranglerDevOutput("module", [ + "--log-level=error", + ]); + expect(output).toMatchInlineSnapshot( + `"X [ERROR] <<<<>>>>"` + ); + }); + + test("with --log-level=debug", async ({ expect }) => { + const output = await getWranglerDevOutput( + "module", + ["--log-level=debug"], + undefined, + // For some reason in debug mode two requests are + // needed to trigger the log here... + 2 + ); + expect(output).toContain("<<<<>>>>"); + }); + + test('with WRANGLER_LOG="debug"', async ({ expect }) => { + const output = await getWranglerDevOutput( + "module", + [], + undefined, + // For some reason in debug mode two requests are + // needed to trigger the log here... + 2, + { WRANGLER_LOG: "debug" } + ); + expect(output).toContain("<<<<>>>>"); + }); + + test("with --log-level=none", async ({ expect }) => { + const output = await getWranglerDevOutput("module", ["--log-level=none"]); + expect(output).toMatchInlineSnapshot(`""`); + }); + + // the workerd structured logs follow this structure: + // {"timestamp":,"level":"","message":""} + // the following tests check for edge case scenario where the following + // structure could not get detected correctly + describe("edge case scenarios", () => { + test("base case", async ({ expect }) => { + const output = await getWranglerDevOutput("module", [], "hello"); + expect(output).toMatchInlineSnapshot(`"hello"`); + }); + test("quotes in message", async ({ expect }) => { + const output = await getWranglerDevOutput("module", [], 'hel"lo'); + expect(output).toMatchInlineSnapshot(`"hel"lo"`); + }); + + test("braces in message", async ({ expect }) => { + const output = await getWranglerDevOutput("module", [], "hel{}lo"); + expect(output).toMatchInlineSnapshot(`"hel{}lo"`); + }); + + test("a workerd structured message in the message", async ({ + expect, + }) => { + const output = await getWranglerDevOutput( + "module", + [], + 'This is an example of a Workerd structured log: {"timestamp":1234567890,"level":"log","message":"Hello World!"}' + ); + expect(output).toMatchInlineSnapshot( + `"This is an example of a Workerd structured log: {"timestamp":1234567890,"level":"log","message":"Hello World!"}"` + ); + }); + + test("a very very very long message (that gets split in multiple chunks)", async ({ + expect, + }) => { + const output = await getWranglerDevOutput( + "module", + [], + "%__VERY_VERY_LONG_MESSAGE_%" + ); + expect(output).toMatch(new RegExp(`^z{${2 ** 20}}$`)); + }); + }); + }); + + // Note: service workers logs are handled differently from standard logs (and are built on top of + // inspector Runtime.consoleAPICalled events), they don't work as well as logs for module + // workers. Service workers are also deprecated so it's not a huge deal, the following + // tests are only here in place to make sure that the basic logging functionality of + // service workers does work + describe("service workers", () => { + test("default behavior", async ({ expect }) => { + const output = await getWranglerDevOutput("service"); + expect(output).toMatchInlineSnapshot(` + "<<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>>" + `); + }); + + test("with --log-level=log", async ({ expect }) => { + const output = await getWranglerDevOutput("service", ["--log-level=log"]); + expect(output).toMatchInlineSnapshot(` + "<<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>>" + `); + }); + + test("with --log-level=info", async ({ expect }) => { + const output = await getWranglerDevOutput("service", [ + "--log-level=info", + ]); + expect(output).toMatchInlineSnapshot(` + "<<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>>" + `); + }); + + test("with --log-level=warn", async ({ expect }) => { + const output = await getWranglerDevOutput("service", [ + "--log-level=warn", + ]); + expect(output).toMatchInlineSnapshot(` + "<<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>>" + `); + }); + + test("with --log-level=error", async ({ expect }) => { + const output = await getWranglerDevOutput("service", [ + "--log-level=error", + ]); + expect(output).toMatchInlineSnapshot(` + "<<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>>" + `); + }); + + test("with --log-level=debug", async ({ expect }) => { + const output = await getWranglerDevOutput("service", [ + "--log-level=debug", + ]); + expect(output).toContain("<<<<>>>>"); + }); + + test("with --log-level=none", async ({ expect }) => { + const output = await getWranglerDevOutput("service", [ + "--log-level=none", + ]); + expect(output).toMatchInlineSnapshot(` + "<<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>> + <<<<>>>>" + `); + }); + }); +}); + +const logLineToIgnoreRegexps = [ + // let's skip empty lines + /^\s*$/, + // part of the wrangler banner + /⛅️ wrangler/, + // divisor after the wrangler banner + /^-+$/, + // wrangler logs such as ` ⎔ Starting local server...` + /^\s*⎔/, + // wrangler's ready on log + /^\[wrangler:inf\] Ready on http:\/\/[^:]+:\d+$/, + // positive response to get request + /^\[wrangler:inf\] GET \/ 200 OK \(\d+ms\)$/, + // let's skip the telemetry messages + /^Cloudflare collects anonymous telemetry about your usage of Wrangler\. Learn more at https:\/\/.*$/, + // The following are V3 specific log lines that we want to ignore + /The version of Wrangler you are using is now out-of-date/, + /After installation, run Wrangler with `npx wrangler`/, + /Please update to the latest version to prevent critical errors/, + /Run `npm install --save-dev wrangler@4` to update to the latest version/, + /No bindings found/, +]; diff --git a/fixtures/worker-logs/tsconfig.json b/fixtures/worker-logs/tsconfig.json new file mode 100644 index 000000000000..df8f82d26782 --- /dev/null +++ b/fixtures/worker-logs/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2021", + "esModuleInterop": true, + "module": "CommonJS", + "lib": ["ES2021"], + "types": ["node"], + "skipLibCheck": true, + "moduleResolution": "node", + "noEmit": true + }, + "include": ["tests", "../../node-types.d.ts"] +} diff --git a/fixtures/worker-logs/wrangler.module.jsonc b/fixtures/worker-logs/wrangler.module.jsonc new file mode 100644 index 000000000000..4994cfa5d384 --- /dev/null +++ b/fixtures/worker-logs/wrangler.module.jsonc @@ -0,0 +1,5 @@ +{ + "name": "worker-logs", + "compatibility_date": "2022-03-31", + "main": "src/module.js", +} diff --git a/fixtures/worker-logs/wrangler.service.jsonc b/fixtures/worker-logs/wrangler.service.jsonc new file mode 100644 index 000000000000..950793198e49 --- /dev/null +++ b/fixtures/worker-logs/wrangler.service.jsonc @@ -0,0 +1,5 @@ +{ + "name": "worker-logs", + "compatibility_date": "2022-03-31", + "main": "src/service.js", +} diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index ef8d5476f35d..06cea78c2582 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -720,6 +720,8 @@ export class Miniflare { #runtimeDispatcher?: Dispatcher; #proxyClient?: ProxyClient; + #structuredWorkerdLogs: boolean; + #cfObject?: Record = {}; // Path to temporary directory for use as scratch space/"in-memory" Durable @@ -759,6 +761,8 @@ export class Miniflare { } this.#log = this.#sharedOpts.core.log ?? new NoOpLog(); + this.#structuredWorkerdLogs = + this.#sharedOpts.core.structuredWorkerdLogs ?? false; this.#liveReloadServer = new WebSocketServer({ noServer: true }); this.#webSocketServer = new WebSocketServer({ @@ -1375,7 +1379,13 @@ export class Miniflare { "Ensure wrapped bindings don't have bindings to themselves." ); } - return { services: servicesArray, sockets, extensions }; + + return { + services: servicesArray, + sockets, + extensions, + structuredLogging: this.#structuredWorkerdLogs, + }; } async #assembleAndUpdateConfig() { @@ -1626,6 +1636,9 @@ export class Miniflare { this.#sharedOpts = sharedOpts; this.#workerOpts = workerOpts; this.#log = this.#sharedOpts.core.log ?? this.#log; + this.#structuredWorkerdLogs = + this.#sharedOpts.core.structuredWorkerdLogs ?? + this.#structuredWorkerdLogs; // Send to runtime and wait for updates to process await this.#assembleAndUpdateConfig(); diff --git a/packages/miniflare/src/plugins/core/index.ts b/packages/miniflare/src/plugins/core/index.ts index 07a1c6b65a03..39db293fddce 100644 --- a/packages/miniflare/src/plugins/core/index.ts +++ b/packages/miniflare/src/plugins/core/index.ts @@ -222,6 +222,10 @@ export const CoreSharedOptionsSchema = z.object({ unsafeStickyBlobs: z.boolean().optional(), unsafeEnableAssetsRpc: z.boolean().optional(), + + // Whether to get structured logs from workerd or not (default to `false`) + // This option is useful in combination with a custom handleRuntimeStdio. + structuredWorkerdLogs: z.boolean().default(false), }); export const CORE_PLUGIN_NAME = "core"; diff --git a/packages/miniflare/src/runtime/config/generated.ts b/packages/miniflare/src/runtime/config/generated.ts index a4c02953b42d..db0578d12fb6 100644 --- a/packages/miniflare/src/runtime/config/generated.ts +++ b/packages/miniflare/src/runtime/config/generated.ts @@ -4,12 +4,14 @@ import * as $ from "capnp-es"; export const _capnpFileId = BigInt("0xe6afd26682091c01"); /** * Top-level configuration for a workerd instance. - * */ + * + */ export class Config extends $.Struct { static readonly _capnp = { displayName: "Config", id: "8794486c76aaa7d6", - size: new $.ObjectSize(0, 5), + size: new $.ObjectSize(8, 5), + defaultStructuredLogging: $.getBitMask(false, 0), }; static _Services: $.ListCtor; static _Sockets: $.ListCtor; @@ -38,7 +40,8 @@ export class Config extends $.Struct { * * The "internet" service backs the global `fetch()` function in a Worker, unless that Worker's * configuration specifies some other service using the `globalOutbound` setting. - * */ + * + */ get services(): $.List { return $.utils.getList(0, Config._Services, this); } @@ -60,7 +63,8 @@ export class Config extends $.Struct { /** * List of sockets on which this server will listen, and the services that will be exposed * through them. - * */ + * + */ get sockets(): $.List { return $.utils.getList(1, Config._Sockets, this); } @@ -87,7 +91,8 @@ export class Config extends $.Struct { * WARNING: Use at your own risk. V8 flags can have all sorts of wild effects including completely * breaking everything. V8 flags also generally do not come with any guarantee of stability * between V8 versions. Most users should not set any V8 flags. - * */ + * + */ get v8Flags(): $.List { return $.utils.getList(2, $.TextList, this); } @@ -109,7 +114,8 @@ export class Config extends $.Struct { /** * Extensions provide capabilities to all workers. Extensions are usually prepared separately * and are late-linked with the app using this config field. - * */ + * + */ get extensions(): $.List { return $.utils.getList(3, Config._Extensions, this); } @@ -132,7 +138,8 @@ export class Config extends $.Struct { * A list of gates which are enabled. * These are used to gate features/changes in workerd and in our internal repo. See the equivalent * config definition in our internal repo for more details. - * */ + * + */ get autogates(): $.List { return $.utils.getList(4, $.TextList, this); } @@ -145,6 +152,19 @@ export class Config extends $.Struct { set autogates(value: $.List) { $.utils.copyFrom(value, $.utils.getPointer(4, this)); } + /** + * If true, logs will be emitted as JSON for structured logging. + * When false, logs use the traditional human-readable format. + * This affects the format of logs from KJ_LOG and exception reporting as well as js logs. + * This won't work for logs coming from service worker syntax workers with the old module registry. + * + */ + get structuredLogging(): boolean { + return $.utils.getBit(0, this, Config._capnp.defaultStructuredLogging); + } + set structuredLogging(value: boolean) { + $.utils.setBit(0, value, this, Config._capnp.defaultStructuredLogging); + } toString(): string { return "Config_" + super.toString(); } @@ -196,7 +216,31 @@ export class Socket_Https extends $.Struct { } } export const Socket_Which = { + /** + * Each socket has a unique name which can be used on the command line to override the socket's + * address with `--socket-addr =` or `--socket-fd =`. + * + */ HTTP: 0, + /** + * Address/port on which this socket will listen. Optional; if not specified, then you will be + * required to specify the socket on the command line with with `--socket-addr =` or + * `--socket-fd =`. + * + * Examples: + * - "*:80": Listen on port 80 on all local IPv4 and IPv6 interfaces. + * - "1.2.3.4": Listen on the specific IPv4 address on the default port for the protocol. + * - "1.2.3.4:80": Listen on the specific IPv4 address and port. + * - "1234:5678::abcd": Listen on the specific IPv6 address on the default port for the protocol. + * - "[1234:5678::abcd]:80": Listen on the specific IPv6 address and port. + * - "unix:/path/to/socket": Listen on a Unix socket. + * - "unix-abstract:name": On Linux, listen on the given "abstract" Unix socket name. + * - "example.com:80": Perform a DNS lookup to determine the address, and then listen on it. If + * this resolves to multiple addresses, listen on all of them. + * + * (These are the formats supported by KJ's parseAddress().) + * + */ HTTPS: 1, } as const; export type Socket_Which = (typeof Socket_Which)[keyof typeof Socket_Which]; @@ -211,7 +255,8 @@ export class Socket extends $.Struct { /** * Each socket has a unique name which can be used on the command line to override the socket's * address with `--socket-addr =` or `--socket-fd =`. - * */ + * + */ get name(): string { return $.utils.getText(0, this); } @@ -235,7 +280,8 @@ export class Socket extends $.Struct { * this resolves to multiple addresses, listen on all of them. * * (These are the formats supported by KJ's parseAddress().) - * */ + * + */ get address(): string { return $.utils.getText(1, this); } @@ -289,7 +335,8 @@ export class Socket extends $.Struct { } /** * Service name which should handle requests on this socket. - * */ + * + */ get service(): ServiceDesignator { return $.utils.getStruct(4, ServiceDesignator, this); } @@ -310,17 +357,42 @@ export class Socket extends $.Struct { } } export const Service_Which = { + /** + * Name of the service. Used only to refer to the service from elsewhere in the config file. + * Services are not accessible unless you explicitly configure them to be, such as through a + * `Socket` or through a binding from another Worker. + * + */ UNSPECIFIED: 0, + /** + * (This catches when someone forgets to specify one of the union members. Do not set this.) + * + */ WORKER: 1, + /** + * A Worker! + * + */ NETWORK: 2, + /** + * A service that implements access to a network. fetch() requests are routed according to + * the URL hostname. + * + */ EXTERNAL: 3, + /** + * A service that forwards all requests to a specific remote server. Typically used to + * connect to a back-end server on your internal network. + * + */ DISK: 4, } as const; export type Service_Which = (typeof Service_Which)[keyof typeof Service_Which]; /** * Defines a named service. Each server has a list of named services. The names are private, * used to refer to the services within this same config file. - * */ + * + */ export class Service extends $.Struct { static readonly UNSPECIFIED = Service_Which.UNSPECIFIED; static readonly WORKER = Service_Which.WORKER; @@ -336,7 +408,8 @@ export class Service extends $.Struct { * Name of the service. Used only to refer to the service from elsewhere in the config file. * Services are not accessible unless you explicitly configure them to be, such as through a * `Socket` or through a binding from another Worker. - * */ + * + */ get name(): string { return $.utils.getText(0, this); } @@ -358,7 +431,8 @@ export class Service extends $.Struct { } /** * A Worker! - * */ + * + */ get worker(): Worker { $.utils.testWhich("worker", $.utils.getUint16(0, this), 1, this); return $.utils.getStruct(1, Worker, this); @@ -387,7 +461,8 @@ export class Service extends $.Struct { /** * A service that implements access to a network. fetch() requests are routed according to * the URL hostname. - * */ + * + */ get network(): Network { $.utils.testWhich("network", $.utils.getUint16(0, this), 2, this); return $.utils.getStruct(1, Network, this); @@ -416,7 +491,8 @@ export class Service extends $.Struct { /** * A service that forwards all requests to a specific remote server. Typically used to * connect to a back-end server on your internal network. - * */ + * + */ get external(): ExternalServer { $.utils.testWhich("external", $.utils.getUint16(0, this), 3, this); return $.utils.getStruct(1, ExternalServer, this); @@ -446,7 +522,8 @@ export class Service extends $.Struct { * An HTTP service backed by a directory on disk, supporting a basic HTTP GET/PUT. Generally * not intended to be exposed directly to the internet; typically you want to bind this into * a Worker that adds logic for setting Content-Type and the like. - * */ + * + */ get disk(): DiskDirectory { $.utils.testWhich("disk", $.utils.getUint16(0, this), 4, this); return $.utils.getStruct(1, DiskDirectory, this); @@ -473,14 +550,23 @@ export class Service extends $.Struct { } } export const ServiceDesignator_Props_Which = { + /** + * Empty object. (This is the default.) + * + */ EMPTY: 0, + /** + * A JSON-encoded value. + * + */ JSON: 1, } as const; export type ServiceDesignator_Props_Which = (typeof ServiceDesignator_Props_Which)[keyof typeof ServiceDesignator_Props_Which]; /** * Value to provide in `ctx.props` in the target worker. - * */ + * + */ export class ServiceDesignator_Props extends $.Struct { static readonly EMPTY = ServiceDesignator_Props_Which.EMPTY; static readonly JSON = ServiceDesignator_Props_Which.JSON; @@ -497,7 +583,8 @@ export class ServiceDesignator_Props extends $.Struct { } /** * A JSON-encoded value. - * */ + * + */ get json(): string { $.utils.testWhich("json", $.utils.getUint16(0, this), 1, this); return $.utils.getText(2, this); @@ -530,7 +617,8 @@ export class ServiceDesignator_Props extends $.Struct { * You can write this instead, which is equivalent: * * bindings = [(service = "foo")] - * */ + * + */ export class ServiceDesignator extends $.Struct { static readonly _capnp = { displayName: "ServiceDesignator", @@ -539,7 +627,8 @@ export class ServiceDesignator extends $.Struct { }; /** * Name of the service in the Config.services list. - * */ + * + */ get name(): string { return $.utils.getText(0, this); } @@ -551,7 +640,8 @@ export class ServiceDesignator extends $.Struct { * the default entrypoint, whereas `export let foo = {` defines an entrypoint named `foo`. If * `entrypoint` is specified here, it names an alternate entrypoint to use on the target worker, * otherwise the default is used. - * */ + * + */ get entrypoint(): string { return $.utils.getText(1, this); } @@ -560,7 +650,8 @@ export class ServiceDesignator extends $.Struct { } /** * Value to provide in `ctx.props` in the target worker. - * */ + * + */ get props(): ServiceDesignator_Props { return $.utils.getAs(ServiceDesignator_Props, this); } @@ -572,14 +663,55 @@ export class ServiceDesignator extends $.Struct { } } export const Worker_Module_Which = { + /** + * Name (or path) used to import the module. + * + */ ES_MODULE: 0, + /** + * An ES module file with imports and exports. + * + * As with `serviceWorkerScript`, above, the value is the raw source code. + * + */ COMMON_JS_MODULE: 1, + /** + * A common JS module, using require(). + * + */ TEXT: 2, + /** + * A raw text blob. Importing this will produce a string with the value. + * + */ DATA: 3, + /** + * A raw data blob. Importing this will produce an ArrayBuffer with the value. + * + */ WASM: 4, + /** + * A Wasm module. The value is a compiled binary Wasm module file. Importing this will produce + * a `WebAssembly.Module` object, which you can then instantiate. + * + */ JSON: 5, - NODE_JS_COMPAT_MODULE: 6, + /** + * Importing this will produce the result of parsing the given text as JSON. + * + */ + OBSOLETE: 6, + /** + * This position used to be the nodeJsCompatModule type that has now been + * obsoleted. + * + */ PYTHON_MODULE: 7, + /** + * A Python module. All bundles containing this value type are converted into a JS/WASM Worker + * Bundle prior to execution. + * + */ PYTHON_REQUIREMENT: 8, } as const; export type Worker_Module_Which = @@ -591,8 +723,7 @@ export class Worker_Module extends $.Struct { static readonly DATA = Worker_Module_Which.DATA; static readonly WASM = Worker_Module_Which.WASM; static readonly JSON = Worker_Module_Which.JSON; - static readonly NODE_JS_COMPAT_MODULE = - Worker_Module_Which.NODE_JS_COMPAT_MODULE; + static readonly OBSOLETE = Worker_Module_Which.OBSOLETE; static readonly PYTHON_MODULE = Worker_Module_Which.PYTHON_MODULE; static readonly PYTHON_REQUIREMENT = Worker_Module_Which.PYTHON_REQUIREMENT; static readonly _capnp = { @@ -602,7 +733,8 @@ export class Worker_Module extends $.Struct { }; /** * Name (or path) used to import the module. - * */ + * + */ get name(): string { return $.utils.getText(0, this); } @@ -613,7 +745,8 @@ export class Worker_Module extends $.Struct { * An ES module file with imports and exports. * * As with `serviceWorkerScript`, above, the value is the raw source code. - * */ + * + */ get esModule(): string { $.utils.testWhich("esModule", $.utils.getUint16(0, this), 0, this); return $.utils.getText(1, this); @@ -627,7 +760,8 @@ export class Worker_Module extends $.Struct { } /** * A common JS module, using require(). - * */ + * + */ get commonJsModule(): string { $.utils.testWhich("commonJsModule", $.utils.getUint16(0, this), 1, this); return $.utils.getText(1, this); @@ -641,7 +775,8 @@ export class Worker_Module extends $.Struct { } /** * A raw text blob. Importing this will produce a string with the value. - * */ + * + */ get text(): string { $.utils.testWhich("text", $.utils.getUint16(0, this), 2, this); return $.utils.getText(1, this); @@ -662,7 +797,8 @@ export class Worker_Module extends $.Struct { } /** * A raw data blob. Importing this will produce an ArrayBuffer with the value. - * */ + * + */ get data(): $.Data { $.utils.testWhich("data", $.utils.getUint16(0, this), 3, this); return $.utils.getData(1, this); @@ -691,7 +827,8 @@ export class Worker_Module extends $.Struct { /** * A Wasm module. The value is a compiled binary Wasm module file. Importing this will produce * a `WebAssembly.Module` object, which you can then instantiate. - * */ + * + */ get wasm(): $.Data { $.utils.testWhich("wasm", $.utils.getUint16(0, this), 4, this); return $.utils.getData(1, this); @@ -712,7 +849,8 @@ export class Worker_Module extends $.Struct { } /** * Importing this will produce the result of parsing the given text as JSON. - * */ + * + */ get json(): string { $.utils.testWhich("json", $.utils.getUint16(0, this), 5, this); return $.utils.getText(1, this); @@ -725,31 +863,26 @@ export class Worker_Module extends $.Struct { $.utils.setText(1, value, this); } /** - * A Node.js module is a specialization of a commonJsModule that: - * (a) allows for importing Node.js-compat built-ins without the node: specifier-prefix - * (b) exposes the subset of common Node.js globals such as process, Buffer, etc that - * we implement in the workerd runtime. - * */ - get nodeJsCompatModule(): string { - $.utils.testWhich( - "nodeJsCompatModule", - $.utils.getUint16(0, this), - 6, - this - ); + * This position used to be the nodeJsCompatModule type that has now been + * obsoleted. + * + */ + get obsolete(): string { + $.utils.testWhich("obsolete", $.utils.getUint16(0, this), 6, this); return $.utils.getText(1, this); } - get _isNodeJsCompatModule(): boolean { + get _isObsolete(): boolean { return $.utils.getUint16(0, this) === 6; } - set nodeJsCompatModule(value: string) { + set obsolete(value: string) { $.utils.setUint16(0, 6, this); $.utils.setText(1, value, this); } /** * A Python module. All bundles containing this value type are converted into a JS/WASM Worker * Bundle prior to execution. - * */ + * + */ get pythonModule(): string { $.utils.testWhich("pythonModule", $.utils.getUint16(0, this), 7, this); return $.utils.getText(1, this); @@ -765,7 +898,11 @@ export class Worker_Module extends $.Struct { * A Python package that is required by this bundle. The package must be supported by * Pyodide (https://pyodide.org/en/stable/usage/packages-in-pyodide.html). All packages listed * will be installed prior to the execution of the worker. - * */ + * + * The value of this field is ignored and should always be an empty string. Only the module + * name matters. The field should have been declared `Void`, but it's difficult to change now. + * + */ get pythonRequirement(): string { $.utils.testWhich("pythonRequirement", $.utils.getUint16(0, this), 8, this); return $.utils.getText(1, this); @@ -784,9 +921,13 @@ export class Worker_Module extends $.Struct { return $.utils.disown(this.namedExports); } /** - * For commonJsModule and nodeJsCompatModule, this is a list of named exports that the + * For commonJsModule modules, this is a list of named exports that the * module expects to be exported once the evaluation is complete. - * */ + * + * (`commonJsModule` should have been a group containing the body and `namedExports`, but it's + * too late to change now.) + * + */ get namedExports(): $.List { return $.utils.getList(2, $.TextList, this); } @@ -807,6 +948,10 @@ export class Worker_Module extends $.Struct { } } export const Worker_Binding_Type_Which = { + /** + * (This catches when someone forgets to specify one of the union members. Do not set this.) + * + */ UNSPECIFIED: 0, TEXT: 1, DATA: 2, @@ -821,12 +966,14 @@ export const Worker_Binding_Type_Which = { QUEUE: 11, ANALYTICS_ENGINE: 12, HYPERDRIVE: 13, + DURABLE_OBJECT_CLASS: 14, } as const; export type Worker_Binding_Type_Which = (typeof Worker_Binding_Type_Which)[keyof typeof Worker_Binding_Type_Which]; /** * Specifies the type of a parameter binding. - * */ + * + */ export class Worker_Binding_Type extends $.Struct { static readonly UNSPECIFIED = Worker_Binding_Type_Which.UNSPECIFIED; static readonly TEXT = Worker_Binding_Type_Which.TEXT; @@ -843,6 +990,8 @@ export class Worker_Binding_Type extends $.Struct { static readonly QUEUE = Worker_Binding_Type_Which.QUEUE; static readonly ANALYTICS_ENGINE = Worker_Binding_Type_Which.ANALYTICS_ENGINE; static readonly HYPERDRIVE = Worker_Binding_Type_Which.HYPERDRIVE; + static readonly DURABLE_OBJECT_CLASS = + Worker_Binding_Type_Which.DURABLE_OBJECT_CLASS; static readonly _capnp = { displayName: "Type", id: "8906a1296519bf8a", @@ -962,6 +1111,12 @@ export class Worker_Binding_Type extends $.Struct { set hyperdrive(_: true) { $.utils.setUint16(0, 13, this); } + get _isDurableObjectClass(): boolean { + return $.utils.getUint16(0, this) === 14; + } + set durableObjectClass(_: true) { + $.utils.setUint16(0, 14, this); + } toString(): string { return "Worker_Binding_Type_" + super.toString(); } @@ -971,7 +1126,8 @@ export class Worker_Binding_Type extends $.Struct { } /** * The type of a Durable Object namespace binding. - * */ + * + */ export class Worker_Binding_DurableObjectNamespaceDesignator extends $.Struct { static readonly _capnp = { displayName: "DurableObjectNamespaceDesignator", @@ -980,7 +1136,8 @@ export class Worker_Binding_DurableObjectNamespaceDesignator extends $.Struct { }; /** * Exported class name that implements the Durable Object. - * */ + * + */ get className(): string { return $.utils.getText(0, this); } @@ -997,7 +1154,8 @@ export class Worker_Binding_DurableObjectNamespaceDesignator extends $.Struct { * * (This is intentionally not a ServiceDesignator because you cannot choose an alternate * entrypoint here; the class name IS the entrypoint.) - * */ + * + */ get serviceName(): string { return $.utils.getText(1, this); } @@ -1023,14 +1181,23 @@ export const Worker_Binding_CryptoKey_Usage = { export type Worker_Binding_CryptoKey_Usage = (typeof Worker_Binding_CryptoKey_Usage)[keyof typeof Worker_Binding_CryptoKey_Usage]; export const Worker_Binding_CryptoKey_Algorithm_Which = { + /** + * Just a name, like `AES-GCM`. + * + */ NAME: 0, + /** + * An object, encoded here as JSON. + * + */ JSON: 1, } as const; export type Worker_Binding_CryptoKey_Algorithm_Which = (typeof Worker_Binding_CryptoKey_Algorithm_Which)[keyof typeof Worker_Binding_CryptoKey_Algorithm_Which]; /** * Value for the `algorithm` parameter. - * */ + * + */ export class Worker_Binding_CryptoKey_Algorithm extends $.Struct { static readonly NAME = Worker_Binding_CryptoKey_Algorithm_Which.NAME; static readonly JSON = Worker_Binding_CryptoKey_Algorithm_Which.JSON; @@ -1041,7 +1208,8 @@ export class Worker_Binding_CryptoKey_Algorithm extends $.Struct { }; /** * Just a name, like `AES-GCM`. - * */ + * + */ get name(): string { $.utils.testWhich("name", $.utils.getUint16(2, this), 0, this); return $.utils.getText(1, this); @@ -1055,7 +1223,8 @@ export class Worker_Binding_CryptoKey_Algorithm extends $.Struct { } /** * An object, encoded here as JSON. - * */ + * + */ get json(): string { $.utils.testWhich("json", $.utils.getUint16(2, this), 1, this); return $.utils.getText(1, this); @@ -1080,16 +1249,37 @@ export class Worker_Binding_CryptoKey_Algorithm extends $.Struct { export const Worker_Binding_CryptoKey_Which = { RAW: 0, HEX: 1, + /** + * Raw key material, possibly hex or base64-encoded. Use this for symmetric keys. + * + * Hint: `raw` would typically be used with Cap'n Proto's `embed` syntax to embed an + * external binary key file. `hex` or `base64` could do that too but can also be specified + * inline. + * + */ BASE64: 2, + /** + * Private key in PEM-encoded PKCS#8 format. + * + */ PKCS8: 3, + /** + * Public key in PEM-encoded SPKI format. + * + */ SPKI: 4, + /** + * Key in JSON format. + * + */ JWK: 5, } as const; export type Worker_Binding_CryptoKey_Which = (typeof Worker_Binding_CryptoKey_Which)[keyof typeof Worker_Binding_CryptoKey_Which]; /** * Parameters to crypto.subtle.importKey(). - * */ + * + */ export class Worker_Binding_CryptoKey extends $.Struct { static readonly RAW = Worker_Binding_CryptoKey_Which.RAW; static readonly HEX = Worker_Binding_CryptoKey_Which.HEX; @@ -1146,7 +1336,8 @@ export class Worker_Binding_CryptoKey extends $.Struct { * Hint: `raw` would typically be used with Cap'n Proto's `embed` syntax to embed an * external binary key file. `hex` or `base64` could do that too but can also be specified * inline. - * */ + * + */ get base64(): string { $.utils.testWhich("base64", $.utils.getUint16(0, this), 2, this); return $.utils.getText(0, this); @@ -1160,7 +1351,8 @@ export class Worker_Binding_CryptoKey extends $.Struct { } /** * Private key in PEM-encoded PKCS#8 format. - * */ + * + */ get pkcs8(): string { $.utils.testWhich("pkcs8", $.utils.getUint16(0, this), 3, this); return $.utils.getText(0, this); @@ -1174,7 +1366,8 @@ export class Worker_Binding_CryptoKey extends $.Struct { } /** * Public key in PEM-encoded SPKI format. - * */ + * + */ get spki(): string { $.utils.testWhich("spki", $.utils.getUint16(0, this), 4, this); return $.utils.getText(0, this); @@ -1188,7 +1381,8 @@ export class Worker_Binding_CryptoKey extends $.Struct { } /** * Key in JSON format. - * */ + * + */ get jwk(): string { $.utils.testWhich("jwk", $.utils.getUint16(0, this), 5, this); return $.utils.getText(0, this); @@ -1202,7 +1396,8 @@ export class Worker_Binding_CryptoKey extends $.Struct { } /** * Value for the `algorithm` parameter. - * */ + * + */ get algorithm(): Worker_Binding_CryptoKey_Algorithm { return $.utils.getAs(Worker_Binding_CryptoKey_Algorithm, this); } @@ -1213,7 +1408,8 @@ export class Worker_Binding_CryptoKey extends $.Struct { * Is the Worker allowed to export this key to obtain the underlying key material? Setting * this false ensures that the key cannot be leaked by errant JavaScript code; the key can * only be used in WebCrypto operations. - * */ + * + */ get extractable(): boolean { return $.utils.getBit( 32, @@ -1237,7 +1433,8 @@ export class Worker_Binding_CryptoKey extends $.Struct { } /** * What operations is this key permitted to be used for? - * */ + * + */ get usages(): $.List { return $.utils.getList( 2, @@ -1296,7 +1493,8 @@ export class Worker_Binding_MemoryCacheLimits extends $.Struct { } /** * A binding that wraps a group of (lower-level) bindings in a common API. - * */ + * + */ export class Worker_Binding_WrappedBinding extends $.Struct { static readonly _capnp = { displayName: "WrappedBinding", @@ -1309,7 +1507,8 @@ export class Worker_Binding_WrappedBinding extends $.Struct { * Wrapper module name. * The module must be an internal one (provided by extension or registered in the c++ code). * Module will be instantitated during binding initialization phase. - * */ + * + */ get moduleName(): string { return $.utils.getText(0, this); } @@ -1321,7 +1520,8 @@ export class Worker_Binding_WrappedBinding extends $.Struct { * The function needs to accept a single `env` argument - a dictionary with inner bindings. * Function will be invoked during initialization phase and its return value will be used as * resulting binding value. - * */ + * + */ get entrypoint(): string { return $.utils.getText( 1, @@ -1342,7 +1542,8 @@ export class Worker_Binding_WrappedBinding extends $.Struct { * Inner bindings that will be created and passed in the env dictionary. * These bindings shall be used to implement end-user api, and are not available to the * binding consumers unless "re-exported" in wrapBindings function. - * */ + * + */ get innerBindings(): $.List { return $.utils.getList( 2, @@ -1371,7 +1572,8 @@ export class Worker_Binding_WrappedBinding extends $.Struct { /** * Indicates that the Worker requires a binding of the given type, but it won't be specified * here. Another Worker can inherit this Worker and fill in this binding. - * */ + * + */ export class Worker_Binding_Parameter extends $.Struct { static readonly _capnp = { displayName: "parameter", @@ -1386,7 +1588,8 @@ export class Worker_Binding_Parameter extends $.Struct { } /** * Expected type of this parameter. - * */ + * + */ get type(): Worker_Binding_Type { return $.utils.getStruct(1, Worker_Binding_Type, this); } @@ -1405,7 +1608,8 @@ export class Worker_Binding_Parameter extends $.Struct { * * When a Worker has any non-optional parameters that haven't been filled in, then it can * only be used for inheritance; it cannot be invoked directly. - * */ + * + */ get optional(): boolean { return $.utils.getBit(16, this); } @@ -1419,7 +1623,8 @@ export class Worker_Binding_Parameter extends $.Struct { /** * A binding for Hyperdrive. Allows workers to use Hyperdrive caching & pooling for Postgres * databases. - * */ + * + */ export class Worker_Binding_Hyperdrive extends $.Struct { static readonly _capnp = { displayName: "hyperdrive", @@ -1474,7 +1679,8 @@ export class Worker_Binding_Hyperdrive extends $.Struct { } /** * A binding representing access to an in-memory cache. - * */ + * + */ export class Worker_Binding_MemoryCache extends $.Struct { static readonly _capnp = { displayName: "memoryCache", @@ -1485,7 +1691,8 @@ export class Worker_Binding_MemoryCache extends $.Struct { * The identifier associated with this cache. Any number of isolates * can access the same in-memory cache (within the same process), and * each worker may use any number of in-memory caches. - * */ + * + */ get id(): string { return $.utils.getText(1, this); } @@ -1514,26 +1721,162 @@ export class Worker_Binding_MemoryCache extends $.Struct { return "Worker_Binding_MemoryCache_" + super.toString(); } } +/** + * A binding representing the ability to dynamically load Workers from code presented at + * runtime. + * + * A Worker loader is not just a function that loads a Worker, but also serves as a + * cache of Workers, automatically unloading Workers that are not in use. To that end, each + * Worker must have a name, and if a Worker with that name already exists, it'll be reused. + * + */ +export class Worker_Binding_WorkerLoader extends $.Struct { + static readonly _capnp = { + displayName: "workerLoader", + id: "a3de996091635c4d", + size: new $.ObjectSize(8, 6), + }; + /** + * Optional: The identifier associated with this Worker loader. Multiple Workers can bind to + * the same ID in order to access the same loader, so that if they request the same name + * from it, they'll end up sharing the same loaded Worker. + * + * (If omitted, the binding will not share a cache with any other binding.) + * + */ + get id(): string { + return $.utils.getText(1, this); + } + set id(value: string) { + $.utils.setText(1, value, this); + } + toString(): string { + return "Worker_Binding_WorkerLoader_" + super.toString(); + } +} export const Worker_Binding_Which = { UNSPECIFIED: 0, + /** + * (This catches when someone forgets to specify one of the union members. Do not set this.) + * + */ PARAMETER: 1, + /** + * Indicates that the Worker requires a binding of the given type, but it won't be specified + * here. Another Worker can inherit this Worker and fill in this binding. + * + */ TEXT: 2, + /** + * A string. + * + */ DATA: 3, + /** + * An ArrayBuffer. + * + */ JSON: 4, + /** + * A value parsed from JSON. + * + */ WASM_MODULE: 5, + /** + * A WebAssembly module. The binding will be an instance of `WebAssembly.Module`. Only + * supported when using Service Workers syntax. + * + * DEPRECATED: Please switch to ES modules syntax instead, and embed Wasm modules as modules. + * + */ CRYPTO_KEY: 6, + /** + * A CryptoKey instance, for use with the WebCrypto API. + * + * Note that by setting `extractable = false`, you can prevent the Worker code from accessing + * or leaking the raw key material; it will only be able to use the key to perform WebCrypto + * operations. + * + */ SERVICE: 7, + /** + * A binding representing access to an in-memory cache. + * + */ + DURABLE_OBJECT_CLASS: 19, + /** + * Binding to a named service (possibly, a worker). + * + */ DURABLE_OBJECT_NAMESPACE: 8, + /** + * Binding to the durable object namespace implemented by the given class. + * + * In the common case that this refers to a class in the same Worker, you can specify just + * a string, like: + * + * durableObjectNamespace = "MyClass" + * + */ KV_NAMESPACE: 9, + /** + * A KV namespace, implemented by the named service. The Worker sees a KvNamespace-typed + * binding. Requests to the namespace will be converted into HTTP requests targeting the + * given service name. + * + */ R2BUCKET: 10, R2ADMIN: 11, + /** + * R2 bucket and admin API bindings. Similar to KV namespaces, these turn operations into + * HTTP requests aimed at the named service. + * + */ WRAPPED: 12, + /** + * Wraps a collection of inner bindings in a common api functionality. + * + */ QUEUE: 13, + /** + * A Queue binding, implemented by the named service. Requests to the + * namespace will be converted into HTTP requests targeting the given + * service name. + * + */ FROM_ENVIRONMENT: 14, + /** + * Takes the value of an environment variable from the system. The value specified here is + * the name of a system environment variable. The value of the binding is obtained by invoking + * `getenv()` with that name. If the environment variable isn't set, the binding value is + * `null`. + * + */ ANALYTICS_ENGINE: 15, - HYPERDRIVE: 16, + /** + * A binding for Analytics Engine. Allows workers to store information through Analytics Engine Events. + * workerd will forward AnalyticsEngineEvents to designated service in the body of HTTP requests + * This binding is subject to change and requires the `--experimental` flag + * + */ + HYPERDRIVE: 16, + /** + * A binding for Hyperdrive. Allows workers to use Hyperdrive caching & pooling for Postgres + * databases. + * + */ UNSAFE_EVAL: 17, + /** + * A simple binding that enables access to the UnsafeEval API. + * + */ MEMORY_CACHE: 18, + /** + * A Durable Object class binding, without an actual storage namespace. This can be used to + * implement a facet. + * + */ + WORKER_LOADER: 20, } as const; export type Worker_Binding_Which = (typeof Worker_Binding_Which)[keyof typeof Worker_Binding_Which]; @@ -1546,6 +1889,8 @@ export class Worker_Binding extends $.Struct { static readonly WASM_MODULE = Worker_Binding_Which.WASM_MODULE; static readonly CRYPTO_KEY = Worker_Binding_Which.CRYPTO_KEY; static readonly SERVICE = Worker_Binding_Which.SERVICE; + static readonly DURABLE_OBJECT_CLASS = + Worker_Binding_Which.DURABLE_OBJECT_CLASS; static readonly DURABLE_OBJECT_NAMESPACE = Worker_Binding_Which.DURABLE_OBJECT_NAMESPACE; static readonly KV_NAMESPACE = Worker_Binding_Which.KV_NAMESPACE; @@ -1558,6 +1903,7 @@ export class Worker_Binding extends $.Struct { static readonly HYPERDRIVE = Worker_Binding_Which.HYPERDRIVE; static readonly UNSAFE_EVAL = Worker_Binding_Which.UNSAFE_EVAL; static readonly MEMORY_CACHE = Worker_Binding_Which.MEMORY_CACHE; + static readonly WORKER_LOADER = Worker_Binding_Which.WORKER_LOADER; static readonly Type = Worker_Binding_Type; static readonly DurableObjectNamespaceDesignator = Worker_Binding_DurableObjectNamespaceDesignator; @@ -1584,7 +1930,8 @@ export class Worker_Binding extends $.Struct { /** * Indicates that the Worker requires a binding of the given type, but it won't be specified * here. Another Worker can inherit this Worker and fill in this binding. - * */ + * + */ get parameter(): Worker_Binding_Parameter { $.utils.testWhich("parameter", $.utils.getUint16(0, this), 1, this); return $.utils.getAs(Worker_Binding_Parameter, this); @@ -1601,7 +1948,8 @@ export class Worker_Binding extends $.Struct { } /** * A string. - * */ + * + */ get text(): string { $.utils.testWhich("text", $.utils.getUint16(0, this), 2, this); return $.utils.getText(1, this); @@ -1622,7 +1970,8 @@ export class Worker_Binding extends $.Struct { } /** * An ArrayBuffer. - * */ + * + */ get data(): $.Data { $.utils.testWhich("data", $.utils.getUint16(0, this), 3, this); return $.utils.getData(1, this); @@ -1643,7 +1992,8 @@ export class Worker_Binding extends $.Struct { } /** * A value parsed from JSON. - * */ + * + */ get json(): string { $.utils.testWhich("json", $.utils.getUint16(0, this), 4, this); return $.utils.getText(1, this); @@ -1667,7 +2017,8 @@ export class Worker_Binding extends $.Struct { * supported when using Service Workers syntax. * * DEPRECATED: Please switch to ES modules syntax instead, and embed Wasm modules as modules. - * */ + * + */ get wasmModule(): $.Data { $.utils.testWhich("wasmModule", $.utils.getUint16(0, this), 5, this); return $.utils.getData(1, this); @@ -1699,7 +2050,8 @@ export class Worker_Binding extends $.Struct { * Note that by setting `extractable = false`, you can prevent the Worker code from accessing * or leaking the raw key material; it will only be able to use the key to perform WebCrypto * operations. - * */ + * + */ get cryptoKey(): Worker_Binding_CryptoKey { $.utils.testWhich("cryptoKey", $.utils.getUint16(0, this), 6, this); return $.utils.getStruct(1, Worker_Binding_CryptoKey, this); @@ -1727,7 +2079,8 @@ export class Worker_Binding extends $.Struct { } /** * Binding to a named service (possibly, a worker). - * */ + * + */ get service(): ServiceDesignator { $.utils.testWhich("service", $.utils.getUint16(0, this), 7, this); return $.utils.getStruct(1, ServiceDesignator, this); @@ -1746,6 +2099,41 @@ export class Worker_Binding extends $.Struct { $.utils.setUint16(0, 7, this); $.utils.copyFrom(value, $.utils.getPointer(1, this)); } + _adoptDurableObjectClass(value: $.Orphan): void { + $.utils.setUint16(0, 19, this); + $.utils.adopt(value, $.utils.getPointer(1, this)); + } + _disownDurableObjectClass(): $.Orphan { + return $.utils.disown(this.durableObjectClass); + } + /** + * A Durable Object class binding, without an actual storage namespace. This can be used to + * implement a facet. + * + */ + get durableObjectClass(): ServiceDesignator { + $.utils.testWhich( + "durableObjectClass", + $.utils.getUint16(0, this), + 19, + this + ); + return $.utils.getStruct(1, ServiceDesignator, this); + } + _hasDurableObjectClass(): boolean { + return !$.utils.isNull($.utils.getPointer(1, this)); + } + _initDurableObjectClass(): ServiceDesignator { + $.utils.setUint16(0, 19, this); + return $.utils.initStructAt(1, ServiceDesignator, this); + } + get _isDurableObjectClass(): boolean { + return $.utils.getUint16(0, this) === 19; + } + set durableObjectClass(value: ServiceDesignator) { + $.utils.setUint16(0, 19, this); + $.utils.copyFrom(value, $.utils.getPointer(1, this)); + } _adoptDurableObjectNamespace( value: $.Orphan ): void { @@ -1762,7 +2150,8 @@ export class Worker_Binding extends $.Struct { * a string, like: * * durableObjectNamespace = "MyClass" - * */ + * + */ get durableObjectNamespace(): Worker_Binding_DurableObjectNamespaceDesignator { $.utils.testWhich( "durableObjectNamespace", @@ -1807,7 +2196,8 @@ export class Worker_Binding extends $.Struct { * A KV namespace, implemented by the named service. The Worker sees a KvNamespace-typed * binding. Requests to the namespace will be converted into HTTP requests targeting the * given service name. - * */ + * + */ get kvNamespace(): ServiceDesignator { $.utils.testWhich("kvNamespace", $.utils.getUint16(0, this), 9, this); return $.utils.getStruct(1, ServiceDesignator, this); @@ -1861,7 +2251,8 @@ export class Worker_Binding extends $.Struct { /** * R2 bucket and admin API bindings. Similar to KV namespaces, these turn operations into * HTTP requests aimed at the named service. - * */ + * + */ get r2Admin(): ServiceDesignator { $.utils.testWhich("r2Admin", $.utils.getUint16(0, this), 11, this); return $.utils.getStruct(1, ServiceDesignator, this); @@ -1889,7 +2280,8 @@ export class Worker_Binding extends $.Struct { } /** * Wraps a collection of inner bindings in a common api functionality. - * */ + * + */ get wrapped(): Worker_Binding_WrappedBinding { $.utils.testWhich("wrapped", $.utils.getUint16(0, this), 12, this); return $.utils.getStruct(1, Worker_Binding_WrappedBinding, this); @@ -1919,7 +2311,8 @@ export class Worker_Binding extends $.Struct { * A Queue binding, implemented by the named service. Requests to the * namespace will be converted into HTTP requests targeting the given * service name. - * */ + * + */ get queue(): ServiceDesignator { $.utils.testWhich("queue", $.utils.getUint16(0, this), 13, this); return $.utils.getStruct(1, ServiceDesignator, this); @@ -1943,7 +2336,8 @@ export class Worker_Binding extends $.Struct { * the name of a system environment variable. The value of the binding is obtained by invoking * `getenv()` with that name. If the environment variable isn't set, the binding value is * `null`. - * */ + * + */ get fromEnvironment(): string { $.utils.testWhich("fromEnvironment", $.utils.getUint16(0, this), 14, this); return $.utils.getText(1, this); @@ -1966,7 +2360,8 @@ export class Worker_Binding extends $.Struct { * A binding for Analytics Engine. Allows workers to store information through Analytics Engine Events. * workerd will forward AnalyticsEngineEvents to designated service in the body of HTTP requests * This binding is subject to change and requires the `--experimental` flag - * */ + * + */ get analyticsEngine(): ServiceDesignator { $.utils.testWhich("analyticsEngine", $.utils.getUint16(0, this), 15, this); return $.utils.getStruct(1, ServiceDesignator, this); @@ -1988,7 +2383,8 @@ export class Worker_Binding extends $.Struct { /** * A binding for Hyperdrive. Allows workers to use Hyperdrive caching & pooling for Postgres * databases. - * */ + * + */ get hyperdrive(): Worker_Binding_Hyperdrive { $.utils.testWhich("hyperdrive", $.utils.getUint16(0, this), 16, this); return $.utils.getAs(Worker_Binding_Hyperdrive, this); @@ -2011,7 +2407,8 @@ export class Worker_Binding extends $.Struct { } /** * A binding representing access to an in-memory cache. - * */ + * + */ get memoryCache(): Worker_Binding_MemoryCache { $.utils.testWhich("memoryCache", $.utils.getUint16(0, this), 18, this); return $.utils.getAs(Worker_Binding_MemoryCache, this); @@ -2026,6 +2423,29 @@ export class Worker_Binding extends $.Struct { set memoryCache(_: true) { $.utils.setUint16(0, 18, this); } + /** + * A binding representing the ability to dynamically load Workers from code presented at + * runtime. + * + * A Worker loader is not just a function that loads a Worker, but also serves as a + * cache of Workers, automatically unloading Workers that are not in use. To that end, each + * Worker must have a name, and if a Worker with that name already exists, it'll be reused. + * + */ + get workerLoader(): Worker_Binding_WorkerLoader { + $.utils.testWhich("workerLoader", $.utils.getUint16(0, this), 20, this); + return $.utils.getAs(Worker_Binding_WorkerLoader, this); + } + _initWorkerLoader(): Worker_Binding_WorkerLoader { + $.utils.setUint16(0, 20, this); + return $.utils.getAs(Worker_Binding_WorkerLoader, this); + } + get _isWorkerLoader(): boolean { + return $.utils.getUint16(0, this) === 20; + } + set workerLoader(_: true) { + $.utils.setUint16(0, 20, this); + } toString(): string { return "Worker_Binding_" + super.toString(); } @@ -2033,8 +2453,50 @@ export class Worker_Binding extends $.Struct { return $.utils.getUint16(0, this) as Worker_Binding_Which; } } +export class Worker_DurableObjectNamespace_ContainerOptions extends $.Struct { + static readonly _capnp = { + displayName: "ContainerOptions", + id: "a609621a4d236cd7", + size: new $.ObjectSize(0, 1), + }; + /** + * Image name to be used to create the container using supported provider. + * By default, we pull the "latest" tag of this image. + * + */ + get imageName(): string { + return $.utils.getText(0, this); + } + set imageName(value: string) { + $.utils.setText(0, value, this); + } + toString(): string { + return "Worker_DurableObjectNamespace_ContainerOptions_" + super.toString(); + } +} export const Worker_DurableObjectNamespace_Which = { + /** + * Exported class name that implements the Durable Object. + * + * Changing the class name will not break compatibility with existing storage, so long as + * `uniqueKey` stays the same. + * + */ UNIQUE_KEY: 0, + /** + * A unique, stable ID associated with this namespace. This could be a GUID, or any other + * string which does not appear anywhere else in the world. + * + * This string is used to ensure that objects of this class have unique identifiers distinct + * from objects of any other class. Object IDs are cryptographically derived from `uniqueKey` + * and validated against it. It is impossible to guess or forge a valid object ID without + * knowing the `uniqueKey`. Hence, if you keep the key secret, you can prevent anyone from + * forging IDs. However, if you don't care if users can forge valid IDs, then it's not a big + * deal if the key leaks. + * + * DO NOT LOSE this key, otherwise it may be difficult or impossible to recover stored data. + * + */ EPHEMERAL_LOCAL: 1, } as const; export type Worker_DurableObjectNamespace_Which = @@ -2043,17 +2505,20 @@ export class Worker_DurableObjectNamespace extends $.Struct { static readonly UNIQUE_KEY = Worker_DurableObjectNamespace_Which.UNIQUE_KEY; static readonly EPHEMERAL_LOCAL = Worker_DurableObjectNamespace_Which.EPHEMERAL_LOCAL; + static readonly ContainerOptions = + Worker_DurableObjectNamespace_ContainerOptions; static readonly _capnp = { displayName: "DurableObjectNamespace", id: "b429dd547d15747d", - size: new $.ObjectSize(8, 2), + size: new $.ObjectSize(8, 3), }; /** * Exported class name that implements the Durable Object. * * Changing the class name will not break compatibility with existing storage, so long as * `uniqueKey` stays the same. - * */ + * + */ get className(): string { return $.utils.getText(0, this); } @@ -2072,7 +2537,8 @@ export class Worker_DurableObjectNamespace extends $.Struct { * deal if the key leaks. * * DO NOT LOSE this key, otherwise it may be difficult or impossible to recover stored data. - * */ + * + */ get uniqueKey(): string { $.utils.testWhich("uniqueKey", $.utils.getUint16(0, this), 0, this); return $.utils.getText(1, this); @@ -2096,7 +2562,8 @@ export class Worker_DurableObjectNamespace extends $.Struct { * pinned to memory forever, so we provide this flag to change the default behavior. * * Note that this is only supported in Workerd; production Durable Objects cannot toggle eviction. - * */ + * + */ get preventEviction(): boolean { return $.utils.getBit(16, this); } @@ -2110,13 +2577,49 @@ export class Worker_DurableObjectNamespace extends $.Struct { * workerd uses SQLite to back all Durable Objects, but the SQL API is hidden by default to * emulate behavior of traditional DO namespaces on Cloudflare that aren't SQLite-backed. This * flag should be enabled when testing code that will run on a SQLite-backed namespace. - * */ + * + */ get enableSql(): boolean { return $.utils.getBit(17, this); } set enableSql(value: boolean) { $.utils.setBit(17, value, this); } + _adoptContainer( + value: $.Orphan + ): void { + $.utils.adopt(value, $.utils.getPointer(2, this)); + } + _disownContainer(): $.Orphan { + return $.utils.disown(this.container); + } + /** + * If present, Durable Objects in this namespace have attached containers. + * workerd will talk to the configured container engine to start containers for each + * Durable Object based on the given image. The Durable Object can access the container via the + * ctx.container API. TODO(CloudChamber): add link to docs. + * + */ + get container(): Worker_DurableObjectNamespace_ContainerOptions { + return $.utils.getStruct( + 2, + Worker_DurableObjectNamespace_ContainerOptions, + this + ); + } + _hasContainer(): boolean { + return !$.utils.isNull($.utils.getPointer(2, this)); + } + _initContainer(): Worker_DurableObjectNamespace_ContainerOptions { + return $.utils.initStructAt( + 2, + Worker_DurableObjectNamespace_ContainerOptions, + this + ); + } + set container(value: Worker_DurableObjectNamespace_ContainerOptions) { + $.utils.copyFrom(value, $.utils.getPointer(2, this)); + } toString(): string { return "Worker_DurableObjectNamespace_" + super.toString(); } @@ -2124,16 +2627,65 @@ export class Worker_DurableObjectNamespace extends $.Struct { return $.utils.getUint16(0, this) as Worker_DurableObjectNamespace_Which; } } +export class Worker_DockerConfiguration extends $.Struct { + static readonly _capnp = { + displayName: "DockerConfiguration", + id: "e62f96c20d9fb872", + size: new $.ObjectSize(0, 1), + }; + /** + * Path to the Docker socket. + * + */ + get socketPath(): string { + return $.utils.getText(0, this); + } + set socketPath(value: string) { + $.utils.setText(0, value, this); + } + toString(): string { + return "Worker_DockerConfiguration_" + super.toString(); + } +} export const Worker_DurableObjectStorage_Which = { + /** + * Default. The worker has no Durable Objects. `durableObjectNamespaces` must be empty, or + * define all namespaces as `ephemeralLocal`, or this must be an abstract worker (meant to be + * inherited by other workers, who will specify `durableObjectStorage`). + * + */ NONE: 0, + /** + * The `state.storage` API stores in-memory only. All stored data will persist for the + * lifetime of the process, but will be lost upon process exit. + * + * Individual objects will still shut down when idle as normal -- only data stored with the + * `state.storage` interface is persistent for the lifetime of the process. + * + * This mode is intended for local testing purposes. + * + */ IN_MEMORY: 1, + /** + * ** EXPERIMENTAL; SUBJECT TO BACKWARDS-INCOMPATIBLE CHANGE ** + * + * Durable Object data will be stored in a directory on local disk. This field is the name of + * a service, which must be a DiskDirectory service. For each Durable Object class, a + * subdirectory will be created using `uniqueKey` as the name. Within the directory, one or + * more files are created for each object, with names `.`, where `.` may be any of + * a number of different extensions depending on the storage mode. (Currently, the main storage + * is a file with the extension `.sqlite`, and in certain situations extra files with the + * extensions `.sqlite-wal`, and `.sqlite-shm` may also be present.) + * + */ LOCAL_DISK: 2, } as const; export type Worker_DurableObjectStorage_Which = (typeof Worker_DurableObjectStorage_Which)[keyof typeof Worker_DurableObjectStorage_Which]; /** * Specifies where this worker's Durable Objects are stored. - * */ + * + */ export class Worker_DurableObjectStorage extends $.Struct { static readonly NONE = Worker_DurableObjectStorage_Which.NONE; static readonly IN_MEMORY = Worker_DurableObjectStorage_Which.IN_MEMORY; @@ -2141,7 +2693,7 @@ export class Worker_DurableObjectStorage extends $.Struct { static readonly _capnp = { displayName: "durableObjectStorage", id: "cc72b3faa57827d4", - size: new $.ObjectSize(8, 11), + size: new $.ObjectSize(8, 13), }; get _isNone(): boolean { return $.utils.getUint16(2, this) === 0; @@ -2165,7 +2717,8 @@ export class Worker_DurableObjectStorage extends $.Struct { * a number of different extensions depending on the storage mode. (Currently, the main storage * is a file with the extension `.sqlite`, and in certain situations extra files with the * extensions `.sqlite-wal`, and `.sqlite-shm` may also be present.) - * */ + * + */ get localDisk(): string { $.utils.testWhich("localDisk", $.utils.getUint16(2, this), 2, this); return $.utils.getText(8, this); @@ -2184,9 +2737,110 @@ export class Worker_DurableObjectStorage extends $.Struct { return $.utils.getUint16(2, this) as Worker_DurableObjectStorage_Which; } } +export const Worker_ContainerEngine_Which = { + /** + * No container engine configured. Container operations will not be available. + * + */ + NONE: 0, + /** + * Use local Docker daemon for container operations. + * Only used for local development and testing purposes. + * + */ + LOCAL_DOCKER: 1, +} as const; +export type Worker_ContainerEngine_Which = + (typeof Worker_ContainerEngine_Which)[keyof typeof Worker_ContainerEngine_Which]; +export class Worker_ContainerEngine extends $.Struct { + static readonly NONE = Worker_ContainerEngine_Which.NONE; + static readonly LOCAL_DOCKER = Worker_ContainerEngine_Which.LOCAL_DOCKER; + static readonly _capnp = { + displayName: "containerEngine", + id: "82de68f58dc2eb24", + size: new $.ObjectSize(8, 13), + }; + get _isNone(): boolean { + return $.utils.getUint16(4, this) === 0; + } + set none(_: true) { + $.utils.setUint16(4, 0, this); + } + _adoptLocalDocker(value: $.Orphan): void { + $.utils.setUint16(4, 1, this); + $.utils.adopt(value, $.utils.getPointer(12, this)); + } + _disownLocalDocker(): $.Orphan { + return $.utils.disown(this.localDocker); + } + /** + * Use local Docker daemon for container operations. + * Only used for local development and testing purposes. + * + */ + get localDocker(): Worker_DockerConfiguration { + $.utils.testWhich("localDocker", $.utils.getUint16(4, this), 1, this); + return $.utils.getStruct(12, Worker_DockerConfiguration, this); + } + _hasLocalDocker(): boolean { + return !$.utils.isNull($.utils.getPointer(12, this)); + } + _initLocalDocker(): Worker_DockerConfiguration { + $.utils.setUint16(4, 1, this); + return $.utils.initStructAt(12, Worker_DockerConfiguration, this); + } + get _isLocalDocker(): boolean { + return $.utils.getUint16(4, this) === 1; + } + set localDocker(value: Worker_DockerConfiguration) { + $.utils.setUint16(4, 1, this); + $.utils.copyFrom(value, $.utils.getPointer(12, this)); + } + toString(): string { + return "Worker_ContainerEngine_" + super.toString(); + } + which(): Worker_ContainerEngine_Which { + return $.utils.getUint16(4, this) as Worker_ContainerEngine_Which; + } +} export const Worker_Which = { + /** + * The Worker is composed of ES modules that may import each other. The first module in the list + * is the main module, which exports event handlers. + * + */ MODULES: 0, + /** + * The Worker is composed of one big script that uses global `addEventListener()` to register + * event handlers. + * + * The value of this field is the raw source code. When using Cap'n Proto text format, use the + * `embed` directive to read the code from an external file: + * + * serviceWorkerScript = embed "worker.js" + * + */ SERVICE_WORKER_SCRIPT: 1, + /** + * Inherit the configuration of some other Worker by its service name. This Worker is a clone + * of the other worker, but various settings can be modified: + * * `bindings`, if specified, overrides specific named bindings. (Each binding listed in the + * derived worker must match the name and type of some binding in the inherited worker.) + * * `globalOutbound`, if non-null, overrides the one specified in the inherited worker. + * * `compatibilityDate` and `compatibilityFlags` CANNOT be modified; they must be null. + * * If the inherited worker defines durable object namespaces, then the derived worker must + * specify `durableObjectStorage` to specify where its instances should be stored. Each + * devived worker receives its own namespace of objects. `durableObjectUniqueKeyModifier` + * must also be specified by derived workers. + * + * This can be useful when you want to run the same Worker in multiple configurations or hooked + * up to different back-ends. Note that all derived workers run in the same isolate as the + * base worker; they differ in the content of the `env` object passed to them, which contains + * the bindings. (When using service workers syntax, the global scope contains the bindings; + * in this case each derived worker runs in its own global scope, though still in the same + * isolate.) + * + */ INHERIT: 2, } as const; export type Worker_Which = (typeof Worker_Which)[keyof typeof Worker_Which]; @@ -2197,14 +2851,15 @@ export class Worker extends $.Struct { static readonly Module = Worker_Module; static readonly Binding = Worker_Binding; static readonly DurableObjectNamespace = Worker_DurableObjectNamespace; + static readonly DockerConfiguration = Worker_DockerConfiguration; static readonly _capnp = { displayName: "Worker", id: "acfa77e88fd97d1c", - size: new $.ObjectSize(8, 11), + size: new $.ObjectSize(8, 13), defaultGlobalOutbound: $.readRawPointer( new Uint8Array([ - 0x10, 0x07, 0x50, 0x01, 0x03, 0x00, 0x00, 0x11, 0x09, 0x4a, 0x00, 0x01, - 0xff, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, + 16, 7, 80, 1, 3, 0, 0, 17, 9, 74, 0, 1, 255, 105, 110, 116, 101, 114, + 110, 101, 116, 0, 0, 0, ]).buffer ), }; @@ -2212,6 +2867,7 @@ export class Worker extends $.Struct { static _Bindings: $.ListCtor; static _DurableObjectNamespaces: $.ListCtor; static _Tails: $.ListCtor; + static _StreamingTails: $.ListCtor; _adoptModules(value: $.Orphan<$.List>): void { $.utils.setUint16(0, 0, this); $.utils.adopt(value, $.utils.getPointer(0, this)); @@ -2222,7 +2878,8 @@ export class Worker extends $.Struct { /** * The Worker is composed of ES modules that may import each other. The first module in the list * is the main module, which exports event handlers. - * */ + * + */ get modules(): $.List { $.utils.testWhich("modules", $.utils.getUint16(0, this), 0, this); return $.utils.getList(0, Worker._Modules, this); @@ -2249,7 +2906,8 @@ export class Worker extends $.Struct { * `embed` directive to read the code from an external file: * * serviceWorkerScript = embed "worker.js" - * */ + * + */ get serviceWorkerScript(): string { $.utils.testWhich( "serviceWorkerScript", @@ -2284,7 +2942,8 @@ export class Worker extends $.Struct { * the bindings. (When using service workers syntax, the global scope contains the bindings; * in this case each derived worker runs in its own global scope, though still in the same * isolate.) - * */ + * + */ get inherit(): string { $.utils.testWhich("inherit", $.utils.getUint16(0, this), 2, this); return $.utils.getText(0, this); @@ -2314,7 +2973,8 @@ export class Worker extends $.Struct { * `compatibilityDate` must be specified, unless the Worker inhits from another worker, in which * case it must not be specified. `compatibilityFlags` can optionally be specified when * `compatibilityDate` is specified. - * */ + * + */ get compatibilityFlags(): $.List { return $.utils.getList(2, $.TextList, this); } @@ -2339,7 +2999,8 @@ export class Worker extends $.Struct { * * For Workers using ES modules syntax, the bindings are delivered via the `env` object. For * service workers syntax, each binding shows up as a global variable. - * */ + * + */ get bindings(): $.List { return $.utils.getList(3, Worker._Bindings, this); } @@ -2361,7 +3022,8 @@ export class Worker extends $.Struct { /** * Where should the global "fetch" go to? The default is the service called "internet", which * should usually be configured to talk to the public internet. - * */ + * + */ get globalOutbound(): ServiceDesignator { return $.utils.getStruct( 4, @@ -2386,8 +3048,9 @@ export class Worker extends $.Struct { return $.utils.disown(this.cacheApiOutbound); } /** - * List of durable object namespaces in this Worker. - * */ + * Where should cache API (i.e. caches.default and caches.open(...)) requests go? + * + */ get cacheApiOutbound(): ServiceDesignator { return $.utils.getStruct(7, ServiceDesignator, this); } @@ -2411,12 +3074,9 @@ export class Worker extends $.Struct { return $.utils.disown(this.durableObjectNamespaces); } /** - * Additional text which is hashed together with `DurableObjectNamespace.uniqueKey`. When using - * worker inheritance, each derived worker must specify a unique modifier to ensure that its - * Durable Object instances have unique IDs from all other workers inheriting the same parent. + * List of durable object namespaces in this Worker. * - * DO NOT LOSE this value, otherwise it may be difficult or impossible to recover stored data. - * */ + */ get durableObjectNamespaces(): $.List { return $.utils.getList(5, Worker._DurableObjectNamespaces, this); } @@ -2432,8 +3092,13 @@ export class Worker extends $.Struct { $.utils.copyFrom(value, $.utils.getPointer(5, this)); } /** - * Specifies where this worker's Durable Objects are stored. - * */ + * Additional text which is hashed together with `DurableObjectNamespace.uniqueKey`. When using + * worker inheritance, each derived worker must specify a unique modifier to ensure that its + * Durable Object instances have unique IDs from all other workers inheriting the same parent. + * + * DO NOT LOSE this value, otherwise it may be difficult or impossible to recover stored data. + * + */ get durableObjectUniqueKeyModifier(): string { return $.utils.getText(6, this); } @@ -2441,8 +3106,9 @@ export class Worker extends $.Struct { $.utils.setText(6, value, this); } /** - * Where should cache API (i.e. caches.default and caches.open(...)) requests go? - * */ + * Specifies where this worker's Durable Objects are stored. + * + */ get durableObjectStorage(): Worker_DurableObjectStorage { return $.utils.getAs(Worker_DurableObjectStorage, this); } @@ -2464,7 +3130,8 @@ export class Worker extends $.Struct { /** * List of tail worker services that should receive tail events for this worker. * See: https://developers.cloudflare.com/workers/observability/logs/tail-workers/ - * */ + * + */ get tails(): $.List { return $.utils.getList(10, Worker._Tails, this); } @@ -2477,6 +3144,35 @@ export class Worker extends $.Struct { set tails(value: $.List) { $.utils.copyFrom(value, $.utils.getPointer(10, this)); } + _adoptStreamingTails(value: $.Orphan<$.List>): void { + $.utils.adopt(value, $.utils.getPointer(11, this)); + } + _disownStreamingTails(): $.Orphan<$.List> { + return $.utils.disown(this.streamingTails); + } + /** + * List of streaming tail worker services that should receive tail events for this worker. + * NOTE: This will be deleted in a future refactor, do not depend on this. + * + */ + get streamingTails(): $.List { + return $.utils.getList(11, Worker._StreamingTails, this); + } + _hasStreamingTails(): boolean { + return !$.utils.isNull($.utils.getPointer(11, this)); + } + _initStreamingTails(length: number): $.List { + return $.utils.initList(11, Worker._StreamingTails, length, this); + } + set streamingTails(value: $.List) { + $.utils.copyFrom(value, $.utils.getPointer(11, this)); + } + get containerEngine(): Worker_ContainerEngine { + return $.utils.getAs(Worker_ContainerEngine, this); + } + _initContainerEngine(): Worker_ContainerEngine { + return $.utils.getAs(Worker_ContainerEngine, this); + } toString(): string { return "Worker_" + super.toString(); } @@ -2486,7 +3182,8 @@ export class Worker extends $.Struct { } /** * Talk to the server over encrypted HTTPS. - * */ + * + */ export class ExternalServer_Https extends $.Struct { static readonly _capnp = { displayName: "https", @@ -2532,7 +3229,8 @@ export class ExternalServer_Https extends $.Struct { /** * If present, expect the host to present a certificate authenticating it as this hostname. * If `certificateHost` is not provided, then the certificate is checked against `address`. - * */ + * + */ get certificateHost(): string { return $.utils.getText(3, this); } @@ -2546,7 +3244,8 @@ export class ExternalServer_Https extends $.Struct { /** * Connect to the server over raw TCP. Bindings to this service will only support the * `connect()` method; `fetch()` will throw an exception. - * */ + * + */ export class ExternalServer_Tcp extends $.Struct { static readonly _capnp = { displayName: "tcp", @@ -2582,8 +3281,32 @@ export class ExternalServer_Tcp extends $.Struct { } } export const ExternalServer_Which = { + /** + * Address/port of the server. Optional; if not specified, then you will be required to specify + * the address on the command line with with `--external-addr =`. + * + * Examples: + * - "1.2.3.4": Connect to the given IPv4 address on the protocol's default port. + * - "1.2.3.4:80": Connect to the given IPv4 address and port. + * - "1234:5678::abcd": Connect to the given IPv6 address on the protocol's default port. + * - "[1234:5678::abcd]:80": Connect to the given IPv6 address and port. + * - "unix:/path/to/socket": Connect to the given Unix Domain socket by path. + * - "unix-abstract:name": On Linux, connect to the given "abstract" Unix socket name. + * - "example.com:80": Perform a DNS lookup to determine the address, and then connect to it. + * + * (These are the formats supported by KJ's parseAddress().) + * + */ HTTP: 0, + /** + * Talk to the server over unencrypted HTTP. + * + */ HTTPS: 1, + /** + * Talk to the server over encrypted HTTPS. + * + */ TCP: 2, } as const; export type ExternalServer_Which = @@ -2604,7 +3327,8 @@ export type ExternalServer_Which = * the request will be delivered to the target server using the protocol specified below. A * header like `X-Forwarded-Proto` can be used to pass along the original protocol; see * `HttpOptions`. - * */ + * + */ export class ExternalServer extends $.Struct { static readonly HTTP = ExternalServer_Which.HTTP; static readonly HTTPS = ExternalServer_Which.HTTPS; @@ -2628,7 +3352,8 @@ export class ExternalServer extends $.Struct { * - "example.com:80": Perform a DNS lookup to determine the address, and then connect to it. * * (These are the formats supported by KJ's parseAddress().) - * */ + * + */ get address(): string { return $.utils.getText(0, this); } @@ -2644,7 +3369,8 @@ export class ExternalServer extends $.Struct { } /** * Talk to the server over unencrypted HTTP. - * */ + * + */ get http(): HttpOptions { $.utils.testWhich("http", $.utils.getUint16(0, this), 0, this); return $.utils.getStruct(1, HttpOptions, this); @@ -2665,7 +3391,8 @@ export class ExternalServer extends $.Struct { } /** * Talk to the server over encrypted HTTPS. - * */ + * + */ get https(): ExternalServer_Https { $.utils.testWhich("https", $.utils.getUint16(0, this), 1, this); return $.utils.getAs(ExternalServer_Https, this); @@ -2683,7 +3410,8 @@ export class ExternalServer extends $.Struct { /** * Connect to the server over raw TCP. Bindings to this service will only support the * `connect()` method; `fetch()` will throw an exception. - * */ + * + */ get tcp(): ExternalServer_Tcp { $.utils.testWhich("tcp", $.utils.getUint16(0, this), 2, this); return $.utils.getAs(ExternalServer_Tcp, this); @@ -2719,7 +3447,8 @@ export class ExternalServer extends $.Struct { * allow = ["public", "private"], * ) * ) - * */ + * + */ export class Network extends $.Struct { static readonly _capnp = { displayName: "Network", @@ -2727,8 +3456,7 @@ export class Network extends $.Struct { size: new $.ObjectSize(0, 3), defaultAllow: $.readRawPointer( new Uint8Array([ - 0x10, 0x03, 0x11, 0x01, 0x0e, 0x11, 0x01, 0x3a, 0x3f, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, + 16, 3, 17, 1, 14, 17, 1, 58, 63, 112, 117, 98, 108, 105, 99, ]).buffer ), }; @@ -2781,7 +3509,8 @@ export class Network extends $.Struct { * out to be permitted, then the system will behave as if the DNS entry did not exist. * * (The above is exactly the format supported by kj::Network::restrictPeers().) - * */ + * + */ get deny(): $.List { return $.utils.getList(1, $.TextList, this); } @@ -2840,7 +3569,8 @@ export class Network extends $.Struct { * is no acceptable format for these, regardless of what the client says it accepts). * * `HEAD` requests are properly optimized to perform a stat() without actually opening the file. - * */ + * + */ export class DiskDirectory extends $.Struct { static readonly _capnp = { displayName: "DiskDirectory", @@ -2855,7 +3585,8 @@ export class DiskDirectory extends $.Struct { * * Relative paths are interpreted relative to the current directory where the server is executed, * NOT relative to the config file. So, you should usually use absolute paths in the config file. - * */ + * + */ get path(): string { return $.utils.getText(0, this); } @@ -2866,7 +3597,8 @@ export class DiskDirectory extends $.Struct { * Whether to support PUT requests for writing. A PUT will write to a temporary file which * is atomically moved into place upon successful completion of the upload. Parent directories are * created as needed. - * */ + * + */ get writable(): boolean { return $.utils.getBit(0, this, DiskDirectory._capnp.defaultWritable); } @@ -2879,7 +3611,8 @@ export class DiskDirectory extends $.Struct { * e.g. a git repository or an `.htaccess` file. * * Note that the special links "." and ".." will never be accessible regardless of this setting. - * */ + * + */ get allowDotfiles(): boolean { return $.utils.getBit(1, this, DiskDirectory._capnp.defaultAllowDotfiles); } @@ -2891,7 +3624,18 @@ export class DiskDirectory extends $.Struct { } } export const HttpOptions_Style = { + /** + * Normal HTTP. The request line contains only the path, and the separate `Host` header + * specifies the hostname. + * + */ HOST: 0, + /** + * HTTP proxy protocol. The request line contains a full URL instead of a path. No `Host` + * header is required. This is the protocol used by HTTP forward proxies. This allows you to + * implement such a proxy as a Worker. + * + */ PROXY: 1, } as const; export type HttpOptions_Style = @@ -2904,7 +3648,8 @@ export class HttpOptions_Header extends $.Struct { }; /** * Case-insensitive. - * */ + * + */ get name(): string { return $.utils.getText(0, this); } @@ -2913,7 +3658,8 @@ export class HttpOptions_Header extends $.Struct { } /** * If null, the header will be removed. - * */ + * + */ get value(): string { return $.utils.getText(1, this); } @@ -2927,7 +3673,8 @@ export class HttpOptions_Header extends $.Struct { /** * Options for using HTTP (as a client or server). In particular, this specifies behavior that is * important in the presence of proxy servers, whether forward or reverse. - * */ + * + */ export class HttpOptions extends $.Struct { static readonly Style = HttpOptions_Style; static readonly Header = HttpOptions_Header; @@ -2960,7 +3707,8 @@ export class HttpOptions extends $.Struct { * like "X-Forwarded-Proto". * * This setting is ignored when `style` is `proxy`. - * */ + * + */ get forwardedProtoHeader(): string { return $.utils.getText(0, this); } @@ -2970,7 +3718,8 @@ export class HttpOptions extends $.Struct { /** * If set, then the `request.cf` object will be encoded (as JSON) into / parsed from the header * with this name. Otherwise, it will be discarded on send / `undefined` on receipt. - * */ + * + */ get cfBlobHeader(): string { return $.utils.getText(1, this); } @@ -2990,7 +3739,8 @@ export class HttpOptions extends $.Struct { * e.g. to add an authorization token to all requests when using `ExternalServer`. It can also * apply to incoming requests received on a `Socket` to modify the headers that will be delivered * to the app. Any existing header with the same name is removed. - * */ + * + */ get injectRequestHeaders(): $.List { return $.utils.getList(2, HttpOptions._InjectRequestHeaders, this); } @@ -3013,7 +3763,8 @@ export class HttpOptions extends $.Struct { } /** * Same as `injectRequestHeaders` but for responses. - * */ + * + */ get injectResponseHeaders(): $.List { return $.utils.getList(3, HttpOptions._InjectResponseHeaders, this); } @@ -3036,7 +3787,8 @@ export class HttpOptions extends $.Struct { * connection. The server will expose a WorkerdBootstrap as the bootstrap interface, allowing * events to be delivered to the target worker via capnp. Clients will use capnp for non-HTTP * event types (especially JSRPC). - * */ + * + */ get capnpConnectHost(): string { return $.utils.getText(4, this); } @@ -3058,7 +3810,8 @@ export class TlsOptions_Keypair extends $.Struct { * keys. * * Remember that you can use Cap'n Proto's `embed` syntax to reference an external file. - * */ + * + */ get privateKey(): string { return $.utils.getText(0, this); } @@ -3068,7 +3821,8 @@ export class TlsOptions_Keypair extends $.Struct { /** * Certificate chain in PEM format. A chain can be constructed by concatenating multiple * PEM-encoded certificates, starting with the leaf certificate. - * */ + * + */ get certificateChain(): string { return $.utils.getText(1, this); } @@ -3080,6 +3834,10 @@ export class TlsOptions_Keypair extends $.Struct { } } export const TlsOptions_Version = { + /** + * A good default chosen by the code maintainers. May change over time. + * + */ GOOD_DEFAULT: 0, SSL3: 1, TLS1DOT0: 2, @@ -3094,7 +3852,8 @@ export type TlsOptions_Version = * on the context. * * This is based on KJ's TlsContext::Options. - * */ + * + */ export class TlsOptions extends $.Struct { static readonly Keypair = TlsOptions_Keypair; static readonly Version = TlsOptions_Version; @@ -3114,7 +3873,8 @@ export class TlsOptions extends $.Struct { } /** * The default private key and certificate to use. Optional when acting as a client. - * */ + * + */ get keypair(): TlsOptions_Keypair { return $.utils.getStruct(0, TlsOptions_Keypair, this); } @@ -3133,7 +3893,8 @@ export class TlsOptions extends $.Struct { * * Typically, when using this, you'd set `trustBrowserCas = false` and list a specific private CA * in `trustedCertificates`. - * */ + * + */ get requireClientCerts(): boolean { return $.utils.getBit(0, this, TlsOptions._capnp.defaultRequireClientCerts); } @@ -3144,7 +3905,8 @@ export class TlsOptions extends $.Struct { * If true, trust certificates which are signed by one of the CAs that browsers normally trust. * You should typically set this true when talking to the public internet, but you may want to * set it false when talking to servers on your internal network. - * */ + * + */ get trustBrowserCas(): boolean { return $.utils.getBit(1, this, TlsOptions._capnp.defaultTrustBrowserCas); } @@ -3160,7 +3922,8 @@ export class TlsOptions extends $.Struct { /** * Additional CA certificates to trust, in PEM format. Remember that you can use Cap'n Proto's * `embed` syntax to read the certificates from other files. - * */ + * + */ get trustedCertificates(): $.List { return $.utils.getList(1, $.TextList, this); } @@ -3176,7 +3939,8 @@ export class TlsOptions extends $.Struct { /** * Minimum TLS version that will be allowed. Generally you should not override this unless you * have unusual backwards-compatibility needs. - * */ + * + */ get minVersion(): TlsOptions_Version { return $.utils.getUint16( 2, @@ -3197,7 +3961,8 @@ export class TlsOptions extends $.Struct { * - You have extreme backwards-compatibility needs and wish to enable obsolete and/or broken * algorithms. * - You need quickly to disable an algorithm recently discovered to be broken. - * */ + * + */ get cipherList(): string { return $.utils.getText(2, this); } @@ -3210,7 +3975,8 @@ export class TlsOptions extends $.Struct { } /** * A module extending workerd functionality. - * */ + * + */ export class Extension_Module extends $.Struct { static readonly _capnp = { displayName: "Module", @@ -3220,7 +3986,8 @@ export class Extension_Module extends $.Struct { }; /** * Full js module name. - * */ + * + */ get name(): string { return $.utils.getText(0, this); } @@ -3229,7 +3996,8 @@ export class Extension_Module extends $.Struct { } /** * Internal modules can be imported by other extension modules only and not the user code. - * */ + * + */ get internal(): boolean { return $.utils.getBit(0, this, Extension_Module._capnp.defaultInternal); } @@ -3238,7 +4006,8 @@ export class Extension_Module extends $.Struct { } /** * Raw source code of ES module. - * */ + * + */ get esModule(): string { return $.utils.getText(1, this); } @@ -3251,7 +4020,8 @@ export class Extension_Module extends $.Struct { } /** * Additional capabilities for workers. - * */ + * + */ export class Extension extends $.Struct { static readonly Module = Extension_Module; static readonly _capnp = { @@ -3270,7 +4040,8 @@ export class Extension extends $.Struct { * List of javascript modules provided by the extension. * These modules can either be imported directly as user-level api (if not marked internal) * or used to define more complicated workerd constructs such as wrapped bindings and events. - * */ + * + */ get modules(): $.List { return $.utils.getList(0, Extension._Modules, this); } @@ -3287,6 +4058,89 @@ export class Extension extends $.Struct { return "Extension_" + super.toString(); } } +export class FallbackServiceRequest_Attribute extends $.Struct { + static readonly _capnp = { + displayName: "Attribute", + id: "ecfe563249c01f75", + size: new $.ObjectSize(0, 2), + }; + get name(): string { + return $.utils.getText(0, this); + } + set name(value: string) { + $.utils.setText(0, value, this); + } + get value(): string { + return $.utils.getText(1, this); + } + set value(value: string) { + $.utils.setText(1, value, this); + } + toString(): string { + return "FallbackServiceRequest_Attribute_" + super.toString(); + } +} +export class FallbackServiceRequest extends $.Struct { + static readonly Attribute = FallbackServiceRequest_Attribute; + static readonly _capnp = { + displayName: "FallbackServiceRequest", + id: "daf79e36b3f32800", + size: new $.ObjectSize(0, 5), + }; + static _Attributes: $.ListCtor; + get type(): string { + return $.utils.getText(0, this); + } + set type(value: string) { + $.utils.setText(0, value, this); + } + get specifier(): string { + return $.utils.getText(1, this); + } + set specifier(value: string) { + $.utils.setText(1, value, this); + } + get rawSpecifier(): string { + return $.utils.getText(2, this); + } + set rawSpecifier(value: string) { + $.utils.setText(2, value, this); + } + get referrer(): string { + return $.utils.getText(3, this); + } + set referrer(value: string) { + $.utils.setText(3, value, this); + } + _adoptAttributes( + value: $.Orphan<$.List> + ): void { + $.utils.adopt(value, $.utils.getPointer(4, this)); + } + _disownAttributes(): $.Orphan<$.List> { + return $.utils.disown(this.attributes); + } + get attributes(): $.List { + return $.utils.getList(4, FallbackServiceRequest._Attributes, this); + } + _hasAttributes(): boolean { + return !$.utils.isNull($.utils.getPointer(4, this)); + } + _initAttributes(length: number): $.List { + return $.utils.initList( + 4, + FallbackServiceRequest._Attributes, + length, + this + ); + } + set attributes(value: $.List) { + $.utils.copyFrom(value, $.utils.getPointer(4, this)); + } + toString(): string { + return "FallbackServiceRequest_" + super.toString(); + } +} Config._Services = $.CompositeList(Service); Config._Sockets = $.CompositeList(Socket); Config._Extensions = $.CompositeList(Extension); @@ -3297,6 +4151,10 @@ Worker._DurableObjectNamespaces = $.CompositeList( Worker_DurableObjectNamespace ); Worker._Tails = $.CompositeList(ServiceDesignator); +Worker._StreamingTails = $.CompositeList(ServiceDesignator); HttpOptions._InjectRequestHeaders = $.CompositeList(HttpOptions_Header); HttpOptions._InjectResponseHeaders = $.CompositeList(HttpOptions_Header); Extension._Modules = $.CompositeList(Extension_Module); +FallbackServiceRequest._Attributes = $.CompositeList( + FallbackServiceRequest_Attribute +); diff --git a/packages/miniflare/src/runtime/config/workerd.ts b/packages/miniflare/src/runtime/config/workerd.ts index bcd563332c5e..839eddc44e46 100644 --- a/packages/miniflare/src/runtime/config/workerd.ts +++ b/packages/miniflare/src/runtime/config/workerd.ts @@ -21,6 +21,7 @@ export interface Config { v8Flags?: string[]; extensions?: Extension[]; autogates?: string[]; + structuredLogging?: boolean; } export type Socket = { diff --git a/packages/miniflare/test/index.spec.ts b/packages/miniflare/test/index.spec.ts index 5c364e5cd6d8..3d2c88d90d8b 100644 --- a/packages/miniflare/test/index.spec.ts +++ b/packages/miniflare/test/index.spec.ts @@ -2943,3 +2943,96 @@ test.serial( t.is(await res.text(), "one text"); } ); + +test.only("Miniflare: logs are treated as standard stdout/stderr chunks be default", async (t) => { + const collected = { + stdout: "", + stderr: "", + }; + const mf = new Miniflare({ + modules: true, + handleRuntimeStdio(stdout, stderr) { + stdout.forEach((data) => { + collected.stdout += `${data}`; + }); + stderr.forEach((error) => { + collected.stderr += `${error}`; + }); + }, + script: ` + export default { + async fetch(req, env) { + console.log('__LOG__'); + console.warn('__WARN__'); + console.error('__ERROR__'); + console.info('__INFO__'); + console.debug('__DEBUG__'); + return new Response('Hello world!'); + } + }`, + }); + t.teardown(() => mf.dispose()); + + const response = await mf.dispatchFetch("http://localhost"); + await response.text(); + + t.is(collected.stdout, "__LOG__\n__INFO__\n__DEBUG__\n"); + t.is(collected.stderr, "__WARN__\n__ERROR__\n"); +}); + +test("Miniflare: logs are structured and all sent to stdout when structuredWorkerdLogs is true", async (t) => { + const collected = { + stdout: "", + stderr: "", + }; + const mf = new Miniflare({ + modules: true, + structuredWorkerdLogs: true, + handleRuntimeStdio(stdout, stderr) { + stdout.forEach((data) => { + collected.stdout += `${data}`; + }); + stderr.forEach((error) => { + collected.stderr += `${error}`; + }); + }, + script: ` + export default { + async fetch(req, env) { + console.log('__LOG__'); + console.warn('__WARN__'); + console.error('__ERROR__'); + console.info('__INFO__'); + console.debug('__DEBUG__'); + return new Response('Hello world!'); + } + }`, + }); + t.teardown(() => mf.dispose()); + + const response = await mf.dispatchFetch("http://localhost"); + await response.text(); + + t.regex( + collected.stdout, + /{"timestamp":\d+,"level":"log","message":"__LOG__"}/ + ); + t.regex( + collected.stdout, + /{"timestamp":\d+,"level":"warn","message":"__WARN__"}/ + ); + t.regex( + collected.stdout, + /{"timestamp":\d+,"level":"error","message":"__ERROR__"}/ + ); + t.regex( + collected.stdout, + /{"timestamp":\d+,"level":"info","message":"__INFO__"}/ + ); + t.regex( + collected.stdout, + /{"timestamp":\d+,"level":"debug","message":"__DEBUG__"}/ + ); + + t.is(collected.stderr, ""); +}); diff --git a/packages/wrangler/src/api/startDevWorker/ProxyController.ts b/packages/wrangler/src/api/startDevWorker/ProxyController.ts index 52ad0f54f577..96f5558c7b36 100644 --- a/packages/wrangler/src/api/startDevWorker/ProxyController.ts +++ b/packages/wrangler/src/api/startDevWorker/ProxyController.ts @@ -10,11 +10,8 @@ import { logConsoleMessage, maybeHandleNetworkLoadResource, } from "../../dev/inspect"; -import { - castLogLevel, - handleRuntimeStdio, - WranglerLog, -} from "../../dev/miniflare"; +import { castLogLevel, WranglerLog } from "../../dev/miniflare"; +import { handleRuntimeStdioWithStructuredLogs } from "../../dev/miniflare/stdio"; import { getHttpsOptions } from "../../https-options"; import { logger } from "../../logger"; import { getSourceMappedStack } from "../../sourcemap"; @@ -163,7 +160,8 @@ export class ProxyController extends Controller { // if debugging, log requests with specic ProxyWorker prefix logger.loggerLevel === "debug" ? "wrangler-ProxyWorker" : "wrangler", }), - handleRuntimeStdio, + handleRuntimeStdio: handleRuntimeStdioWithStructuredLogs, + structuredWorkerdLogs: true, liveReload: false, }; diff --git a/packages/wrangler/src/dev/miniflare.ts b/packages/wrangler/src/dev/miniflare/index.ts similarity index 84% rename from packages/wrangler/src/dev/miniflare.ts rename to packages/wrangler/src/dev/miniflare/index.ts index 74f6710f6528..79eb6d0d1784 100644 --- a/packages/wrangler/src/dev/miniflare.ts +++ b/packages/wrangler/src/dev/miniflare/index.ts @@ -6,29 +6,29 @@ import { AIFetcher, EXTERNAL_AI_WORKER_NAME, EXTERNAL_AI_WORKER_SCRIPT, -} from "../ai/fetcher"; -import { ModuleTypeToRuleType } from "../deployment-bundle/module-collection"; -import { withSourceURLs } from "../deployment-bundle/source-url"; -import { createFatalError, UserError } from "../errors"; -import { getFlag } from "../experimental-flags"; +} from "../../ai/fetcher"; +import { ModuleTypeToRuleType } from "../../deployment-bundle/module-collection"; +import { withSourceURLs } from "../../deployment-bundle/source-url"; +import { createFatalError, UserError } from "../../errors"; +import { getFlag } from "../../experimental-flags"; import { EXTERNAL_IMAGES_WORKER_NAME, EXTERNAL_IMAGES_WORKER_SCRIPT, imagesLocalFetcher, imagesRemoteFetcher, -} from "../images/fetcher"; -import { logger } from "../logger"; -import { getSourceMappedString } from "../sourcemap"; -import { updateCheck } from "../update-check"; +} from "../../images/fetcher"; +import { logger } from "../../logger"; +import { updateCheck } from "../../update-check"; import { EXTERNAL_VECTORIZE_WORKER_NAME, EXTERNAL_VECTORIZE_WORKER_SCRIPT, MakeVectorizeFetcher, -} from "../vectorize/fetcher"; -import { getClassNamesWhichUseSQLite } from "./class-names-sqlite"; -import type { ServiceFetch } from "../api"; -import type { AssetsOptions } from "../assets"; -import type { Config } from "../config"; +} from "../../vectorize/fetcher"; +import { getClassNamesWhichUseSQLite } from "../class-names-sqlite"; +import { handleRuntimeStdioWithStructuredLogs } from "./stdio"; +import type { ServiceFetch } from "../../api"; +import type { AssetsOptions } from "../../assets"; +import type { Config } from "../../config"; import type { CfD1Database, CfDurableObject, @@ -41,14 +41,13 @@ import type { CfUnsafeBinding, CfWorkerInit, CfWorkflow, -} from "../deployment-bundle/worker"; -import type { WorkerRegistry } from "../dev-registry"; -import type { LoggerLevel } from "../logger"; -import type { LegacyAssetPaths } from "../sites"; -import type { EsbuildBundle } from "./use-esbuild"; +} from "../../deployment-bundle/worker"; +import type { WorkerRegistry } from "../../dev-registry"; +import type { LoggerLevel } from "../../logger"; +import type { LegacyAssetPaths } from "../../sites"; +import type { EsbuildBundle } from "../use-esbuild"; import type { MiniflareOptions, SourceOptions, WorkerOptions } from "miniflare"; import type { UUID } from "node:crypto"; -import type { Readable } from "node:stream"; // This worker proxies all external Durable Objects to the Wrangler session // where they're defined, and receives all requests from other Wrangler sessions @@ -810,137 +809,6 @@ export function buildSitesOptions({ } } -export function handleRuntimeStdio(stdout: Readable, stderr: Readable) { - // ASSUMPTION: each chunk is a whole message from workerd - // This may not hold across OSes/architectures, but it seems to work on macOS M-line - // I'm going with this simple approach to avoid complicating this too early - // We can iterate on this heuristic in the future if it causes issues - const classifiers = { - // Is this chunk a big chonky barf from workerd that we want to hijack to cleanup/ignore? - isBarf(chunk: string) { - const containsLlvmSymbolizerWarning = chunk.includes( - "Not symbolizing stack traces because $LLVM_SYMBOLIZER is not set" - ); - const containsRecursiveIsolateLockWarning = chunk.includes( - "took recursive isolate lock" - ); - // Matches stack traces from workerd - // - on unix: groups of 9 hex digits separated by spaces - // - on windows: groups of 12 hex digits, or a single digit 0, separated by spaces - const containsHexStack = /stack:( (0|[a-f\d]{4,})){3,}/.test(chunk); - - return ( - containsLlvmSymbolizerWarning || - containsRecursiveIsolateLockWarning || - containsHexStack - ); - }, - // Is this chunk an Address In Use error? - isAddressInUse(chunk: string) { - return chunk.includes("Address already in use; toString() = "); - }, - isWarning(chunk: string) { - return /\.c\+\+:\d+: warning:/.test(chunk); - }, - isCodeMovedWarning(chunk: string) { - return /CODE_MOVED for unknown code block/.test(chunk); - }, - isAccessViolation(chunk: string) { - return chunk.includes("access violation;"); - }, - }; - - stdout.on("data", (chunk: Buffer | string) => { - chunk = chunk.toString().trim(); - - if (classifiers.isBarf(chunk)) { - // this is a big chonky barf from workerd that we want to hijack to cleanup/ignore - - // CLEANABLE: - // there are no known cases to cleanup yet - // but, as they are identified, we will do that here - - // IGNORABLE: - // anything else not handled above is considered ignorable - // so send it to the debug logs which are discarded unless - // the user explicitly sets a logLevel indicating they care - logger.debug(chunk); - } - - // known case: warnings are not info, log them as such - else if (classifiers.isWarning(chunk)) { - logger.warn(chunk); - } - - // anything not explicitly handled above should be logged as info (via stdout) - else { - logger.info(getSourceMappedString(chunk)); - } - }); - - stderr.on("data", (chunk: Buffer | string) => { - chunk = chunk.toString().trim(); - - if (classifiers.isBarf(chunk)) { - // this is a big chonky barf from workerd that we want to hijack to cleanup/ignore - - // CLEANABLE: - // known case to cleanup: Address in use errors - if (classifiers.isAddressInUse(chunk)) { - const address = chunk.match( - /Address already in use; toString\(\) = (.+)\n/ - )?.[1]; - - logger.error( - `Address already in use (${address}). Please check that you are not already running a server on this address or specify a different port with --port.` - ); - - // Log the original error to the debug logs. - logger.debug(chunk); - } - // In the past we have seen Access Violation errors on Windows, which may be caused by an outdated - // version of the Windows OS or the Microsoft Visual C++ Redistributable. - // See https://github.com/cloudflare/workers-sdk/issues/6170#issuecomment-2245209918 - else if (classifiers.isAccessViolation(chunk)) { - let error = "There was an access violation in the runtime."; - if (process.platform === "win32") { - error += - "\nOn Windows, this may be caused by an outdated Microsoft Visual C++ Redistributable library.\n" + - "Check that you have the latest version installed.\n" + - "See https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist."; - } - logger.error(error); - - // Log the original error to the debug logs. - logger.debug(chunk); - } - - // IGNORABLE: - // anything else not handled above is considered ignorable - // so send it to the debug logs which are discarded unless - // the user explicitly sets a logLevel indicating they care - else { - logger.debug(chunk); - } - } - - // known case: warnings are not errors, log them as such - else if (classifiers.isWarning(chunk)) { - logger.warn(chunk); - } - - // known case: "error: CODE_MOVED for unknown code block?", warning for workerd devs, not application devs - else if (classifiers.isCodeMovedWarning(chunk)) { - // ignore entirely, don't even send it to the debug log file - } - - // anything not explicitly handled above should be logged as an error (via stderr) - else { - logger.error(getSourceMappedString(chunk)); - } - }); -} - let didWarnMiniflareCronSupport = false; let didWarnMiniflareVectorizeSupport = false; let didWarnAiAccountUsage = false; @@ -1032,7 +900,8 @@ export async function buildMiniflareOptions( log, verbose: logger.loggerLevel === "debug", - handleRuntimeStdio, + handleRuntimeStdio: handleRuntimeStdioWithStructuredLogs, + structuredWorkerdLogs: true, ...persistOptions, workers: [ diff --git a/packages/wrangler/src/dev/miniflare/stdio.ts b/packages/wrangler/src/dev/miniflare/stdio.ts new file mode 100644 index 000000000000..3953d4a8e658 --- /dev/null +++ b/packages/wrangler/src/dev/miniflare/stdio.ts @@ -0,0 +1,232 @@ +import { logger } from "../../logger"; +import { getSourceMappedString } from "../../sourcemap"; +import type { Readable } from "node:stream"; + +/** + * Handler function to pass to Miniflare instances for handling the Worker process stream. + * + * This function can be used when Miniflare's `structuredWorkerdLogs` option is set to `true`. + * + * @param stdout The stdout stream connected to Miniflare's Workerd process + * @param stderr The stderr stream connected to Miniflare's Workerd process + */ +export function handleRuntimeStdioWithStructuredLogs( + stdout: Readable, + stderr: Readable +) { + stdout.on("data", getProcessStreamDataListener("stdout")); + stderr.on("data", getProcessStreamDataListener("stderr")); +} + +/** + * Creates a listener for the "data" event of a process stream associated to Miniflare's Workerd process. + * + * This function can be used when Miniflare's `structuredWorkerdLogs` option is set to `true`. + * + * @param processStream The target process stream + * @returns the listener to set for the stream's "data" event + */ +function getProcessStreamDataListener(processStream: "stdout" | "stderr") { + let streamAccumulator = ""; + + return (chunk: Buffer | string) => { + const fullStreamOutput = `${streamAccumulator}${chunk}`; + + let currentLogsStr = ""; + + // Structured logs are divided by newlines so let's get the + // last one, we know that anything in between will include + // one or more structured logs + const lastNewlineIdx = fullStreamOutput.lastIndexOf("\n"); + + if (lastNewlineIdx > 0) { + // If we've found a newline we will take the structured logs + // up to that point, the rest (which is the beginning of a + // new structured log) will be saved for later + currentLogsStr = fullStreamOutput.slice(0, lastNewlineIdx); + streamAccumulator = fullStreamOutput.slice(lastNewlineIdx + 1); + } else { + // If we didn't find a newline we're dealing with a structured + // log that has been split, so let's save the whole thing for + // later (so that we can process the log once we've seen it + // in full) + streamAccumulator = fullStreamOutput; + } + + const lines = currentLogsStr.split("\n"); + + for (const line of lines) { + const structuredLog = parseStructuredLog(line); + if (structuredLog) { + logStructuredLog(structuredLog, processStream); + } else { + const level = processStream === "stdout" ? "log" : "error"; + // Unexpectedly we've received a line that is not a structured log, so we simply + // log it to the most likely appropriate logger level + logger[level](line); + } + } + }; +} + +type WorkerdStructuredLog = { + timestamp: number; + level: string; + message: string; +}; + +/** + * Parses a string to obtain the potential structured log the string represents. + * + * @param str Target string + * @returns The structured log extracted from the string or null is the string doesn't represent a structured log + */ +function parseStructuredLog(str: string): WorkerdStructuredLog | null { + try { + const maybeStructuredLog = JSON.parse(str) as Record; + + if (typeof maybeStructuredLog !== "object" || maybeStructuredLog === null) { + return null; + } + + const timestamp = parseInt(maybeStructuredLog.timestamp); + + if ( + isNaN(timestamp) || + typeof maybeStructuredLog.level !== "string" || + typeof maybeStructuredLog.message !== "string" + ) { + return null; + } + + return { + timestamp, + level: maybeStructuredLog.level, + message: maybeStructuredLog.message, + }; + } catch { + return null; + } +} + +/** + * Logs a structured log in the most appropriate way on the most appropriate log level. + * + * @param structuredLog The structured log to print + * @param processStream The process stream from where the structured log comes from + */ +function logStructuredLog( + { level, message }: WorkerdStructuredLog, + processStream: "stdout" | "stderr" +): void { + // TODO: the following code analyzes the message without considering its log level, + // ideally, in order to avoid false positives, we should run this logic scoped + // to the relevant log levels (as we do for `isCodeMovedWarning`) + if (messageClassifiers.isBarf(message)) { + // this is a big chonky barf from workerd that we want to hijack to cleanup/ignore + + // CLEANABLE: + // known case to cleanup: Address in use errors + if (messageClassifiers.isAddressInUse(message)) { + const address = message.match( + /Address already in use; toString\(\) = (.+)\n/ + )?.[1]; + + logger.error( + `Address already in use (${address}). Please check that you are not already running a server on this address or specify a different port with --port.` + ); + + // Also log the original error to the debug logs. + return logger.debug(message); + } + + // In the past we have seen Access Violation errors on Windows, which may be caused by an outdated + // version of the Windows OS or the Microsoft Visual C++ Redistributable. + // See https://github.com/cloudflare/workers-sdk/issues/6170#issuecomment-2245209918 + if (messageClassifiers.isAccessViolation(message)) { + let error = "There was an access violation in the runtime."; + if (process.platform === "win32") { + error += + "\nOn Windows, this may be caused by an outdated Microsoft Visual C++ Redistributable library.\n" + + "Check that you have the latest version installed.\n" + + "See https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist."; + } + logger.error(error); + + // Also log the original error to the debug logs. + return logger.debug(message); + } + + // IGNORABLE: + // anything else not handled above is considered ignorable + // so send it to the debug logs which are discarded unless + // the user explicitly sets a logLevel indicating they care + return logger.debug(message); + } + + if ( + (level === "info" || level === "error") && + messageClassifiers.isCodeMovedWarning(message) + ) { + // known case: "error: CODE_MOVED for unknown code block?", warning for workerd devs, not application devs + // ignore entirely, don't even send it to the debug log file + // workerd references: + // - https://github.com/cloudflare/workerd/blob/d170f4d9b/src/workerd/jsg/setup.c%2B%2B#L566 + // - https://github.com/cloudflare/workerd/blob/d170f4d9b/src/workerd/jsg/setup.c%2B%2B#L572 + return; + } + + if (level === "warn") { + return logger.warn(message); + } + + if (level === "info") { + return logger.info(message); + } + + if (level === "debug") { + return logger.debug(message); + } + + if (level === "error") { + return logger.error(getSourceMappedString(message)); + } + + if (processStream === "stderr") { + return logger.error(getSourceMappedString(message)); + } else { + return logger.log(getSourceMappedString(message)); + } +} + +const messageClassifiers = { + // Is this chunk a big chonky barf from workerd that we want to hijack to cleanup/ignore? + isBarf(chunk: string) { + const containsLlvmSymbolizerWarning = chunk.includes( + "Not symbolizing stack traces because $LLVM_SYMBOLIZER is not set" + ); + const containsRecursiveIsolateLockWarning = chunk.includes( + "took recursive isolate lock" + ); + // Matches stack traces from workerd + // - on unix: groups of 9 hex digits separated by spaces + // - on windows: groups of 12 hex digits, or a single digit 0, separated by spaces + const containsHexStack = /stack:( (0|[a-f\d]{4,})){3,}/.test(chunk); + + return ( + containsLlvmSymbolizerWarning || + containsRecursiveIsolateLockWarning || + containsHexStack + ); + }, + // Is this chunk an Address In Use error? + isAddressInUse(chunk: string) { + return chunk.includes("Address already in use; toString() = "); + }, + isCodeMovedWarning(chunk: string) { + return /CODE_MOVED for unknown code block/.test(chunk); + }, + isAccessViolation(chunk: string) { + return chunk.includes("access violation;"); + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f80aa9f1b6c..30f769b9ed23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -898,6 +898,21 @@ importers: specifier: workspace:* version: link:../../packages/wrangler + fixtures/worker-logs: + devDependencies: + '@cloudflare/workers-tsconfig': + specifier: workspace:^ + version: link:../../packages/workers-tsconfig + typescript: + specifier: catalog:default + version: 5.7.3 + vitest: + specifier: catalog:default + version: 3.0.5(@types/node@18.19.76)(@vitest/ui@3.0.5)(jiti@2.4.2)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + wrangler: + specifier: workspace:* + version: link:../../packages/wrangler + fixtures/worker-ts: devDependencies: '@cloudflare/workers-types': @@ -6351,9 +6366,6 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathe@2.0.1: - resolution: {integrity: sha512-6jpjMpOth5S9ITVu5clZ7NOgHNsv5vRQdheL9ztp2vZmM6fRbLvyua1tiBIL4lk8SAe3ARzeXEly6siXCjDHDw==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -13038,7 +13050,7 @@ snapshots: mlly@1.7.4: dependencies: acorn: 8.14.0 - pathe: 2.0.1 + pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.5.4 @@ -13346,8 +13358,6 @@ snapshots: pathe@1.1.2: {} - pathe@2.0.1: {} - pathe@2.0.3: {} pathval@2.0.0: {}