@takazudo/zfb-adapter-cloudflare 2.22.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,7 +29,6 @@ In `zfb.config.json`:
29
29
 
30
30
  ```json
31
31
  {
32
- "framework": "preact",
33
32
  "adapter": "@takazudo/zfb-adapter-cloudflare"
34
33
  }
35
34
  ```
package/dist/index.d.ts CHANGED
@@ -3,8 +3,10 @@
3
3
  * `ExecutionContext` interface. We do not depend on `@cloudflare/workers-types`
4
4
  * at the type level here because that would force every consumer of this
5
5
  * package to install it; instead we keep a minimal structural shape and
6
- * let users widen it via the generic on [`getCloudflareContext`] when
7
- * they need richer bindings.
6
+ * let consumers describe a compatible richer shape with the second generic
7
+ * on [`getCloudflareContext`] when they need additional execution-context
8
+ * bindings. That type is supplied by the caller; it is not detected or
9
+ * validated at runtime.
8
10
  */
9
11
  export interface CloudflareExecutionContext {
10
12
  /** Extends the lifetime of the request beyond the response. */
@@ -13,14 +15,15 @@ export interface CloudflareExecutionContext {
13
15
  passThroughOnException(): void;
14
16
  }
15
17
  /**
16
- * Per-request Cloudflare context. `Env` defaults to `unknown` so the
17
- * caller can narrow it at the call site (recommended) or leave it open.
18
+ * Per-request Cloudflare context. `Env` defaults to `unknown` and `Ctx` to
19
+ * the minimal execution-context shape. Consumers can supply narrower or
20
+ * richer types at the call site when they know the runtime shape.
18
21
  */
19
- export interface CloudflareContext<Env = unknown> {
22
+ export interface CloudflareContext<Env = unknown, Ctx extends CloudflareExecutionContext = CloudflareExecutionContext> {
20
23
  /** CF env bindings (secrets, KV, D1, …) wired up via wrangler.toml. */
21
24
  readonly env: Env;
22
- /** ExecutionContext for waitUntil / passThroughOnException. */
23
- readonly ctx: CloudflareExecutionContext;
25
+ /** ExecutionContext for waitUntil / passThroughOnException and any declared extensions. */
26
+ readonly ctx: Ctx;
24
27
  /** The original Request, useful when handlers want headers / URL. */
25
28
  readonly request: Request;
26
29
  }
@@ -28,14 +31,16 @@ export interface CloudflareContext<Env = unknown> {
28
31
  * Establish a Cloudflare context for the duration of `fn`. Used by the
29
32
  * `_worker.js` wrapper; not normally called by user code.
30
33
  */
31
- export declare function runWithCloudflareContext<T>(context: CloudflareContext, fn: () => T): T;
34
+ export declare function runWithCloudflareContext<T, C extends CloudflareContext = CloudflareContext>(context: C, fn: () => T): T;
32
35
  /**
33
36
  * Read the current Cloudflare context. Throws if called outside a
34
37
  * Cloudflare request scope (e.g. from a build-time SSG render). Catch
35
38
  * the error and gate on `prerender = false` if you need a route to work
36
39
  * in both modes.
37
40
  *
38
- * The `Env` generic narrows the bindings shape — passing it is
39
- * recommended so TypeScript catches typos like `env.ANTRHOPIC_KEY`.
41
+ * `Env` describes the bindings available to the caller, while `Ctx` can
42
+ * describe a compatible richer execution context. These generic arguments
43
+ * are caller assertions: this function does not inspect runtime bindings or
44
+ * feature-detect optional properties. Guard optional fields before use.
40
45
  */
41
- export declare function getCloudflareContext<Env = unknown>(): CloudflareContext<Env>;
46
+ export declare function getCloudflareContext<Env = unknown, Ctx extends CloudflareExecutionContext = CloudflareExecutionContext>(): CloudflareContext<Env, Ctx>;
package/dist/index.js CHANGED
@@ -68,8 +68,10 @@ export function runWithCloudflareContext(context, fn) {
68
68
  * the error and gate on `prerender = false` if you need a route to work
69
69
  * in both modes.
70
70
  *
71
- * The `Env` generic narrows the bindings shape — passing it is
72
- * recommended so TypeScript catches typos like `env.ANTRHOPIC_KEY`.
71
+ * `Env` describes the bindings available to the caller, while `Ctx` can
72
+ * describe a compatible richer execution context. These generic arguments
73
+ * are caller assertions: this function does not inspect runtime bindings or
74
+ * feature-detect optional properties. Guard optional fields before use.
73
75
  */
74
76
  export function getCloudflareContext() {
75
77
  const c = getStorage().getStore();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,qEAAqE;AACrE,kBAAkB;AAClB,EAAE;AACF,sEAAsE;AACtE,uEAAuE;AACvE,iEAAiE;AACjE,EAAE;AACF,wCAAwC;AACxC,EAAE;AACF,6EAA6E;AAC7E,EAAE;AACF,oCAAoC;AACpC,+CAA+C;AAC/C,kFAAkF;AAClF,yDAAyD;AACzD,MAAM;AACN,EAAE;AACF,wEAAwE;AACxE,qBAAqB;AACrB,EAAE;AACF,yEAAyE;AACzE,EAAE;AACF,oDAAoD;AACpD,EAAE;AACF,uEAAuE;AACvE,yEAAyE;AACzE,yEAAyE;AACzE,4DAA4D;AAC5D,EAAE;AACF,sEAAsE;AACtE,yEAAyE;AACzE,mEAAmE;AACnE,yEAAyE;AACzE,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,6CAA6C;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AA8BrD,uDAAuD;AACvD,MAAM,WAAW,GAAG,wBAAwB,CAAC;AAM7C;;;;;;GAMG;AACH,SAAS,UAAU;IACjB,MAAM,CAAC,GAAG,UAAuC,CAAC;IAClD,IAAI,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;IACzB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,GAAG,IAAI,iBAAiB,EAAqB,CAAC;QACjD,CAAC,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC;IACvB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAI,OAA0B,EAAE,EAAW;IACjF,OAAO,UAAU,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC;IAClC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,6FAA6F;YAC3F,yFAAyF;YACzF,gGAAgG,CACnG,CAAC;IACJ,CAAC;IACD,OAAO,CAA2B,CAAC;AACrC,CAAC","sourcesContent":["// `@takazudo/zfb-adapter-cloudflare` — Cloudflare Workers Static Assets\n// adapter for the zfb framework (also deployable to Cloudflare Pages\n// advanced mode).\n//\n// This entry (`./`) is the **Workers-runtime** surface. It is safe to\n// bundle into a Cloudflare Worker and does not depend on any Node-only\n// built-ins beyond `node:async_hooks` (which workerd polyfills).\n//\n// Usage in a `prerender = false` route:\n//\n// import { getCloudflareContext } from \"@takazudo/zfb-adapter-cloudflare\";\n//\n// export const prerender = false;\n// export default async function ApiRoute() {\n// const { env, ctx } = getCloudflareContext<{ ANTHROPIC_API_KEY: string }>();\n// // env.ANTHROPIC_API_KEY, ctx.waitUntil(...), etc.\n// }\n//\n// For Node-only build helpers (e.g. `emitWorker`), import the `./build`\n// sub-entry instead:\n//\n// import { emitWorker } from \"@takazudo/zfb-adapter-cloudflare/build\";\n//\n// ## Why AsyncLocalStorage on a globalThis registry\n//\n// Cloudflare Workers can process multiple requests concurrently in the\n// same isolate. A naïve `globalThis.__env = env` write would race across\n// requests. AsyncLocalStorage gives us a per-request scope that survives\n// `await` points without interfering with sibling requests.\n//\n// We register the storage instance on `globalThis` under a stable key\n// (`__zfb_cf_adapter_als__`) so the wrapper at `_worker.js` and the user\n// pages bundled together can share the same instance even when the\n// adapter module ends up duplicated in the final bundle graph (e.g. when\n// the wrapper file is emitted side-by-side with the inner bundle and\n// each pulls in its own copy of this module). Module-instance identity\n// is the property AsyncLocalStorage relies on; the registry pattern is\n// what makes it survive bundler duplication.\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Cloudflare execution context — minimal projection of the workerd\n * `ExecutionContext` interface. We do not depend on `@cloudflare/workers-types`\n * at the type level here because that would force every consumer of this\n * package to install it; instead we keep a minimal structural shape and\n * let users widen it via the generic on [`getCloudflareContext`] when\n * they need richer bindings.\n */\nexport interface CloudflareExecutionContext {\n /** Extends the lifetime of the request beyond the response. */\n waitUntil(promise: Promise<unknown>): void;\n /** Falls through to the static origin on uncaught exceptions. */\n passThroughOnException(): void;\n}\n\n/**\n * Per-request Cloudflare context. `Env` defaults to `unknown` so the\n * caller can narrow it at the call site (recommended) or leave it open.\n */\nexport interface CloudflareContext<Env = unknown> {\n /** CF env bindings (secrets, KV, D1, …) wired up via wrangler.toml. */\n readonly env: Env;\n /** ExecutionContext for waitUntil / passThroughOnException. */\n readonly ctx: CloudflareExecutionContext;\n /** The original Request, useful when handlers want headers / URL. */\n readonly request: Request;\n}\n\n/** Stable globalThis key the registry pattern uses. */\nconst STORAGE_KEY = \"__zfb_cf_adapter_als__\";\n\ninterface RegistryGlobal {\n [STORAGE_KEY]?: AsyncLocalStorage<CloudflareContext>;\n}\n\n/**\n * Acquire (or lazily create) the singleton AsyncLocalStorage instance.\n *\n * Stored on `globalThis` under a stable key so the wrapper at\n * `_worker.js` and the user bundle share state even if the adapter\n * module ends up duplicated in the final module graph.\n */\nfunction getStorage(): AsyncLocalStorage<CloudflareContext> {\n const g = globalThis as unknown as RegistryGlobal;\n let als = g[STORAGE_KEY];\n if (!als) {\n als = new AsyncLocalStorage<CloudflareContext>();\n g[STORAGE_KEY] = als;\n }\n return als;\n}\n\n/**\n * Establish a Cloudflare context for the duration of `fn`. Used by the\n * `_worker.js` wrapper; not normally called by user code.\n */\nexport function runWithCloudflareContext<T>(context: CloudflareContext, fn: () => T): T {\n return getStorage().run(context, fn);\n}\n\n/**\n * Read the current Cloudflare context. Throws if called outside a\n * Cloudflare request scope (e.g. from a build-time SSG render). Catch\n * the error and gate on `prerender = false` if you need a route to work\n * in both modes.\n *\n * The `Env` generic narrows the bindings shape — passing it is\n * recommended so TypeScript catches typos like `env.ANTRHOPIC_KEY`.\n */\nexport function getCloudflareContext<Env = unknown>(): CloudflareContext<Env> {\n const c = getStorage().getStore();\n if (!c) {\n throw new Error(\n \"[zfb-adapter-cloudflare] getCloudflareContext() called outside a Cloudflare request scope. \" +\n \"This usually means the route was rendered at build time (SSG) instead of dispatched by \" +\n \"the Worker. Add `export const prerender = false;` to the page if it needs Cloudflare bindings.\",\n );\n }\n return c as CloudflareContext<Env>;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,qEAAqE;AACrE,kBAAkB;AAClB,EAAE;AACF,sEAAsE;AACtE,uEAAuE;AACvE,iEAAiE;AACjE,EAAE;AACF,wCAAwC;AACxC,EAAE;AACF,6EAA6E;AAC7E,EAAE;AACF,oCAAoC;AACpC,+CAA+C;AAC/C,kFAAkF;AAClF,yDAAyD;AACzD,MAAM;AACN,EAAE;AACF,wEAAwE;AACxE,qBAAqB;AACrB,EAAE;AACF,yEAAyE;AACzE,EAAE;AACF,oDAAoD;AACpD,EAAE;AACF,uEAAuE;AACvE,yEAAyE;AACzE,yEAAyE;AACzE,4DAA4D;AAC5D,EAAE;AACF,sEAAsE;AACtE,yEAAyE;AACzE,mEAAmE;AACnE,yEAAyE;AACzE,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,6CAA6C;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAoCrD,uDAAuD;AACvD,MAAM,WAAW,GAAG,wBAAwB,CAAC;AAM7C;;;;;;GAMG;AACH,SAAS,UAAU;IACjB,MAAM,CAAC,GAAG,UAAuC,CAAC;IAClD,IAAI,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;IACzB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,GAAG,IAAI,iBAAiB,EAAqB,CAAC;QACjD,CAAC,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC;IACvB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAU,EACV,EAAW;IAEX,OAAO,UAAU,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB;IAIlC,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC;IAClC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,6FAA6F;YAC3F,yFAAyF;YACzF,gGAAgG,CACnG,CAAC;IACJ,CAAC;IACD,OAAO,CAAgC,CAAC;AAC1C,CAAC","sourcesContent":["// `@takazudo/zfb-adapter-cloudflare` — Cloudflare Workers Static Assets\n// adapter for the zfb framework (also deployable to Cloudflare Pages\n// advanced mode).\n//\n// This entry (`./`) is the **Workers-runtime** surface. It is safe to\n// bundle into a Cloudflare Worker and does not depend on any Node-only\n// built-ins beyond `node:async_hooks` (which workerd polyfills).\n//\n// Usage in a `prerender = false` route:\n//\n// import { getCloudflareContext } from \"@takazudo/zfb-adapter-cloudflare\";\n//\n// export const prerender = false;\n// export default async function ApiRoute() {\n// const { env, ctx } = getCloudflareContext<{ ANTHROPIC_API_KEY: string }>();\n// // env.ANTHROPIC_API_KEY, ctx.waitUntil(...), etc.\n// }\n//\n// For Node-only build helpers (e.g. `emitWorker`), import the `./build`\n// sub-entry instead:\n//\n// import { emitWorker } from \"@takazudo/zfb-adapter-cloudflare/build\";\n//\n// ## Why AsyncLocalStorage on a globalThis registry\n//\n// Cloudflare Workers can process multiple requests concurrently in the\n// same isolate. A naïve `globalThis.__env = env` write would race across\n// requests. AsyncLocalStorage gives us a per-request scope that survives\n// `await` points without interfering with sibling requests.\n//\n// We register the storage instance on `globalThis` under a stable key\n// (`__zfb_cf_adapter_als__`) so the wrapper at `_worker.js` and the user\n// pages bundled together can share the same instance even when the\n// adapter module ends up duplicated in the final bundle graph (e.g. when\n// the wrapper file is emitted side-by-side with the inner bundle and\n// each pulls in its own copy of this module). Module-instance identity\n// is the property AsyncLocalStorage relies on; the registry pattern is\n// what makes it survive bundler duplication.\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Cloudflare execution context — minimal projection of the workerd\n * `ExecutionContext` interface. We do not depend on `@cloudflare/workers-types`\n * at the type level here because that would force every consumer of this\n * package to install it; instead we keep a minimal structural shape and\n * let consumers describe a compatible richer shape with the second generic\n * on [`getCloudflareContext`] when they need additional execution-context\n * bindings. That type is supplied by the caller; it is not detected or\n * validated at runtime.\n */\nexport interface CloudflareExecutionContext {\n /** Extends the lifetime of the request beyond the response. */\n waitUntil(promise: Promise<unknown>): void;\n /** Falls through to the static origin on uncaught exceptions. */\n passThroughOnException(): void;\n}\n\n/**\n * Per-request Cloudflare context. `Env` defaults to `unknown` and `Ctx` to\n * the minimal execution-context shape. Consumers can supply narrower or\n * richer types at the call site when they know the runtime shape.\n */\nexport interface CloudflareContext<\n Env = unknown,\n Ctx extends CloudflareExecutionContext = CloudflareExecutionContext,\n> {\n /** CF env bindings (secrets, KV, D1, …) wired up via wrangler.toml. */\n readonly env: Env;\n /** ExecutionContext for waitUntil / passThroughOnException and any declared extensions. */\n readonly ctx: Ctx;\n /** The original Request, useful when handlers want headers / URL. */\n readonly request: Request;\n}\n\n/** Stable globalThis key the registry pattern uses. */\nconst STORAGE_KEY = \"__zfb_cf_adapter_als__\";\n\ninterface RegistryGlobal {\n [STORAGE_KEY]?: AsyncLocalStorage<CloudflareContext>;\n}\n\n/**\n * Acquire (or lazily create) the singleton AsyncLocalStorage instance.\n *\n * Stored on `globalThis` under a stable key so the wrapper at\n * `_worker.js` and the user bundle share state even if the adapter\n * module ends up duplicated in the final module graph.\n */\nfunction getStorage(): AsyncLocalStorage<CloudflareContext> {\n const g = globalThis as unknown as RegistryGlobal;\n let als = g[STORAGE_KEY];\n if (!als) {\n als = new AsyncLocalStorage<CloudflareContext>();\n g[STORAGE_KEY] = als;\n }\n return als;\n}\n\n/**\n * Establish a Cloudflare context for the duration of `fn`. Used by the\n * `_worker.js` wrapper; not normally called by user code.\n */\nexport function runWithCloudflareContext<T, C extends CloudflareContext = CloudflareContext>(\n context: C,\n fn: () => T,\n): T {\n return getStorage().run(context, fn);\n}\n\n/**\n * Read the current Cloudflare context. Throws if called outside a\n * Cloudflare request scope (e.g. from a build-time SSG render). Catch\n * the error and gate on `prerender = false` if you need a route to work\n * in both modes.\n *\n * `Env` describes the bindings available to the caller, while `Ctx` can\n * describe a compatible richer execution context. These generic arguments\n * are caller assertions: this function does not inspect runtime bindings or\n * feature-detect optional properties. Guard optional fields before use.\n */\nexport function getCloudflareContext<\n Env = unknown,\n Ctx extends CloudflareExecutionContext = CloudflareExecutionContext,\n>(): CloudflareContext<Env, Ctx> {\n const c = getStorage().getStore();\n if (!c) {\n throw new Error(\n \"[zfb-adapter-cloudflare] getCloudflareContext() called outside a Cloudflare request scope. \" +\n \"This usually means the route was rendered at build time (SSG) instead of dispatched by \" +\n \"the Worker. Add `export const prerender = false;` to the page if it needs Cloudflare bindings.\",\n );\n }\n return c as CloudflareContext<Env, Ctx>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takazudo/zfb-adapter-cloudflare",
3
- "version": "2.22.1",
3
+ "version": "3.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Content-site engine on the web's Request/Response model — prerender to static HTML, serve on Cloudflare Workers, or run as a local content server inside your app. One Rust binary. Cloudflare adapter: Workers Static Assets (Pages-compatible).",
@@ -64,8 +64,10 @@
64
64
  },
65
65
  "scripts": {
66
66
  "build": "tsc && cp src/worker-wrapper.mjs dist/worker-wrapper.mjs && cp src/emit-worker.mjs dist/emit-worker.mjs",
67
- "test": "vitest run",
67
+ "test": "pnpm typecheck:consumer && vitest run && pnpm test:packed-types",
68
+ "test:packed-types": "pnpm build && node scripts/check-packed-types.mjs",
68
69
  "test:watch": "vitest",
69
- "typecheck": "tsc --noEmit"
70
+ "typecheck": "tsc --noEmit && pnpm typecheck:consumer",
71
+ "typecheck:consumer": "tsc --project tsconfig.consumer.json --noEmit"
70
72
  }
71
73
  }