@takazudo/zfb-adapter-cloudflare 3.0.0 → 3.2.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/dist/build.d.ts CHANGED
@@ -49,7 +49,7 @@ export interface EmitWorkerOutput {
49
49
  * more copied Wasm assets in `outdir`):
50
50
  *
51
51
  * _worker.js — Worker entry point (`main` in wrangler.toml)
52
- * _zfb_inner.mjs — the input bundle, copied verbatim
52
+ * _zfb_inner.mjs — the input bundle, minus any trailing external `sourceMappingURL` comment
53
53
  * <asset>.wasm — each bundle-relative Wasm input, copied by basename
54
54
  * .assetsignore — excludes every generated JavaScript and Wasm basename
55
55
  * from the asset upload so they are only reachable
package/dist/build.js CHANGED
@@ -27,7 +27,7 @@ export const WORKER_WRAPPER_SOURCE = _wrapper;
27
27
  * more copied Wasm assets in `outdir`):
28
28
  *
29
29
  * _worker.js — Worker entry point (`main` in wrangler.toml)
30
- * _zfb_inner.mjs — the input bundle, copied verbatim
30
+ * _zfb_inner.mjs — the input bundle, minus any trailing external `sourceMappingURL` comment
31
31
  * <asset>.wasm — each bundle-relative Wasm input, copied by basename
32
32
  * .assetsignore — excludes every generated JavaScript and Wasm basename
33
33
  * from the asset upload so they are only reachable
package/dist/build.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,0EAA0E;AAC1E,kEAAkE;AAClE,sEAAsE;AACtE,+DAA+D;AAC/D,EAAE;AACF,yEAAyE;AACzE,mEAAmE;AAEnE,0EAA0E;AAC1E,sCAAsC;AACtC,OAAO,EAAE,qBAAqB,IAAI,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACzE,uEAAuE;AACvE,gEAAgE;AAChE,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAW,QAAkB,CAAC;AAyChE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAsB;IACrD,OAAO,WAAW,CAAC;QACjB,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,mBAAmB,EAAE,qBAAqB;KAC3C,CAA8B,CAAC;AAClC,CAAC","sourcesContent":["// `@takazudo/zfb-adapter-cloudflare/build` — Node-only build helpers.\n//\n// This sub-entry is intentionally **not** imported by the Workers-runtime\n// entry (`./`). Code in this module may freely use Node built-ins\n// (`node:fs`, `node:path`, …) because it only ever runs in a Node 22+\n// build environment, never inside a Cloudflare Worker isolate.\n//\n// The `./` entry (`src/index.ts`) exports only Workers-runtime-safe code\n// (AsyncLocalStorage helpers) that can be bundled into the worker.\n\n// @ts-expect-error worker-wrapper.mjs has no declaration file; the export\n// shape is narrowed explicitly below.\nimport { WORKER_WRAPPER_SOURCE as _wrapper } from \"./worker-wrapper.mjs\";\n// @ts-expect-error emit-worker.mjs has no declaration file; the export\n// shape is narrowed via EmitWorkerInput/EmitWorkerOutput below.\nimport { emitWorker as _emitWorker } from \"./emit-worker.mjs\";\n\n/**\n * The wrapper source written to `_worker.js`. Imported from the single\n * canonical `.mjs` file so `src/build.ts` and `bin/cli.mjs` always stay\n * in sync without any duplication.\n */\nexport const WORKER_WRAPPER_SOURCE: string = _wrapper as string;\n\n/**\n * Inputs to [`emitWorker`].\n */\nexport interface EmitWorkerInput {\n /**\n * Absolute path to the input ESM bundle produced by `zfb-build`. The\n * bundle must export a Workers-shaped `default { fetch: (request) =>\n * Promise<Response> }` (this is the contract `zfb_build::bundler`\n * pins). The file is copied verbatim next to the emitted wrapper so\n * relative imports inside it keep resolving.\n */\n readonly inputBundlePath: string;\n /**\n * Absolute path to the output directory. The emitter creates it if\n * missing and writes `_worker.js`, `_zfb_inner.mjs` (the copied input\n * bundle), copied Wasm assets, and `.assetsignore` into it. The ignore\n * file protects every generated JavaScript and Wasm basename from the\n * public asset upload.\n */\n readonly outdir: string;\n /**\n * Wasm modules emitted beside the input bundle. Relative paths resolve from\n * the input bundle's directory and each module is copied into the Worker\n * package under its basename, then added to `.assetsignore` so it remains a\n * Worker module rather than a public static asset.\n */\n readonly assets?: readonly string[];\n}\n\n/**\n * Output paths the emitter produced. Returned for callers that want to\n * log them (the Rust orchestrator surfaces them in build output).\n */\nexport interface EmitWorkerOutput {\n readonly workerPath: string;\n readonly innerBundlePath: string;\n readonly assetsIgnorePath: string;\n}\n\n/**\n * Emit a Cloudflare Workers Static Assets `_worker.js` that wraps the zfb\n * input bundle. Cloudflare Pages advanced mode is unverified.\n *\n * Output shape (two generated JavaScript files, `.assetsignore`, and zero or\n * more copied Wasm assets in `outdir`):\n *\n * _worker.js — Worker entry point (`main` in wrangler.toml)\n * _zfb_inner.mjs — the input bundle, copied verbatim\n * <asset>.wasm — each bundle-relative Wasm input, copied by basename\n * .assetsignore — excludes every generated JavaScript and Wasm basename\n * from the asset upload so they are only reachable\n * through the Worker's module graph\n *\n * The wrapper imports the inner bundle via the relative path\n * `./_zfb_inner.mjs`. Workerd's Module loader resolves relative ESM\n * imports inside the `_worker.js` directory, so this layout works\n * without re-bundling.\n *\n * Why two bundle files instead of one: re-bundling here would require a\n * second esbuild pass and would force the adapter to ship its own\n * esbuild binary slot. The two-file layout keeps the adapter\n * dependency-free at runtime — it is just `node:fs` glue.\n */\nexport async function emitWorker(input: EmitWorkerInput): Promise<EmitWorkerOutput> {\n return _emitWorker({\n inputBundlePath: input.inputBundlePath,\n outdir: input.outdir,\n assets: input.assets,\n workerWrapperSource: WORKER_WRAPPER_SOURCE,\n }) as Promise<EmitWorkerOutput>;\n}\n"]}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,0EAA0E;AAC1E,kEAAkE;AAClE,sEAAsE;AACtE,+DAA+D;AAC/D,EAAE;AACF,yEAAyE;AACzE,mEAAmE;AAEnE,0EAA0E;AAC1E,sCAAsC;AACtC,OAAO,EAAE,qBAAqB,IAAI,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACzE,uEAAuE;AACvE,gEAAgE;AAChE,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAW,QAAkB,CAAC;AAyChE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAsB;IACrD,OAAO,WAAW,CAAC;QACjB,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,mBAAmB,EAAE,qBAAqB;KAC3C,CAA8B,CAAC;AAClC,CAAC","sourcesContent":["// `@takazudo/zfb-adapter-cloudflare/build` — Node-only build helpers.\n//\n// This sub-entry is intentionally **not** imported by the Workers-runtime\n// entry (`./`). Code in this module may freely use Node built-ins\n// (`node:fs`, `node:path`, …) because it only ever runs in a Node 22+\n// build environment, never inside a Cloudflare Worker isolate.\n//\n// The `./` entry (`src/index.ts`) exports only Workers-runtime-safe code\n// (AsyncLocalStorage helpers) that can be bundled into the worker.\n\n// @ts-expect-error worker-wrapper.mjs has no declaration file; the export\n// shape is narrowed explicitly below.\nimport { WORKER_WRAPPER_SOURCE as _wrapper } from \"./worker-wrapper.mjs\";\n// @ts-expect-error emit-worker.mjs has no declaration file; the export\n// shape is narrowed via EmitWorkerInput/EmitWorkerOutput below.\nimport { emitWorker as _emitWorker } from \"./emit-worker.mjs\";\n\n/**\n * The wrapper source written to `_worker.js`. Imported from the single\n * canonical `.mjs` file so `src/build.ts` and `bin/cli.mjs` always stay\n * in sync without any duplication.\n */\nexport const WORKER_WRAPPER_SOURCE: string = _wrapper as string;\n\n/**\n * Inputs to [`emitWorker`].\n */\nexport interface EmitWorkerInput {\n /**\n * Absolute path to the input ESM bundle produced by `zfb-build`. The\n * bundle must export a Workers-shaped `default { fetch: (request) =>\n * Promise<Response> }` (this is the contract `zfb_build::bundler`\n * pins). The file is copied verbatim next to the emitted wrapper so\n * relative imports inside it keep resolving.\n */\n readonly inputBundlePath: string;\n /**\n * Absolute path to the output directory. The emitter creates it if\n * missing and writes `_worker.js`, `_zfb_inner.mjs` (the copied input\n * bundle), copied Wasm assets, and `.assetsignore` into it. The ignore\n * file protects every generated JavaScript and Wasm basename from the\n * public asset upload.\n */\n readonly outdir: string;\n /**\n * Wasm modules emitted beside the input bundle. Relative paths resolve from\n * the input bundle's directory and each module is copied into the Worker\n * package under its basename, then added to `.assetsignore` so it remains a\n * Worker module rather than a public static asset.\n */\n readonly assets?: readonly string[];\n}\n\n/**\n * Output paths the emitter produced. Returned for callers that want to\n * log them (the Rust orchestrator surfaces them in build output).\n */\nexport interface EmitWorkerOutput {\n readonly workerPath: string;\n readonly innerBundlePath: string;\n readonly assetsIgnorePath: string;\n}\n\n/**\n * Emit a Cloudflare Workers Static Assets `_worker.js` that wraps the zfb\n * input bundle. Cloudflare Pages advanced mode is unverified.\n *\n * Output shape (two generated JavaScript files, `.assetsignore`, and zero or\n * more copied Wasm assets in `outdir`):\n *\n * _worker.js — Worker entry point (`main` in wrangler.toml)\n * _zfb_inner.mjs — the input bundle, minus any trailing external `sourceMappingURL` comment\n * <asset>.wasm — each bundle-relative Wasm input, copied by basename\n * .assetsignore — excludes every generated JavaScript and Wasm basename\n * from the asset upload so they are only reachable\n * through the Worker's module graph\n *\n * The wrapper imports the inner bundle via the relative path\n * `./_zfb_inner.mjs`. Workerd's Module loader resolves relative ESM\n * imports inside the `_worker.js` directory, so this layout works\n * without re-bundling.\n *\n * Why two bundle files instead of one: re-bundling here would require a\n * second esbuild pass and would force the adapter to ship its own\n * esbuild binary slot. The two-file layout keeps the adapter\n * dependency-free at runtime — it is just `node:fs` glue.\n */\nexport async function emitWorker(input: EmitWorkerInput): Promise<EmitWorkerOutput> {\n return _emitWorker({\n inputBundlePath: input.inputBundlePath,\n outdir: input.outdir,\n assets: input.assets,\n workerWrapperSource: WORKER_WRAPPER_SOURCE,\n }) as Promise<EmitWorkerOutput>;\n}\n"]}
@@ -70,6 +70,21 @@ async function resolveAssets(inputBundlePath, assetPaths) {
70
70
  return resolvedAssets;
71
71
  }
72
72
 
73
+ // The adapter never emits source maps (they are not published and would leak
74
+ // local source paths), so a trailing external `sourceMappingURL` reference in
75
+ // the copied bundle would dangle and make source-map-aware tools (Vite Workers
76
+ // integration) fail with ENOENT. Inline `data:` maps are self-contained and kept.
77
+ const TRAILING_EXTERNAL_SOURCE_MAP =
78
+ /(?:\r?\n)?[ \t]*\/\/[#@][ \t]*sourceMappingURL=(?!data:)\S*\s*$/;
79
+
80
+ export function stripDanglingSourceMapReference(source) {
81
+ const stripped = source.replace(TRAILING_EXTERNAL_SOURCE_MAP, "");
82
+ if (stripped === source) {
83
+ return source;
84
+ }
85
+ return stripped.endsWith("\n") || stripped.length === 0 ? stripped : stripped + "\n";
86
+ }
87
+
73
88
  async function pathExists(path) {
74
89
  try {
75
90
  await lstat(path);
@@ -112,7 +127,8 @@ function mergeAssetsIgnore(existing, requiredEntries) {
112
127
  * more copied Wasm assets in `outdir`):
113
128
  *
114
129
  * _worker.js — Worker entry point (`main` in wrangler.toml)
115
- * _zfb_inner.mjs — the input bundle, copied verbatim
130
+ * _zfb_inner.mjs — the input bundle, copied with any trailing external
131
+ * `sourceMappingURL` comment removed (no map is emitted)
116
132
  * <asset>.wasm — each bundle-relative Wasm input, copied by basename
117
133
  * .assetsignore — excludes every generated JavaScript and Wasm basename
118
134
  * from the asset upload
@@ -136,7 +152,8 @@ export async function emitWorker({ inputBundlePath, outdir, assets = [], workerW
136
152
  }
137
153
 
138
154
  const innerBundlePath = join(outdirAbs, "_zfb_inner.mjs");
139
- await copyFile(inputAbs, innerBundlePath);
155
+ const inputSource = await readFile(inputAbs, "utf8");
156
+ await writeFile(innerBundlePath, stripDanglingSourceMapReference(inputSource), "utf8");
140
157
 
141
158
  const workerPath = join(outdirAbs, "_worker.js");
142
159
  await writeFile(workerPath, workerWrapperSource, "utf8");
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": "3.0.0",
3
+ "version": "3.2.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).",
@@ -59,13 +59,17 @@
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^22.0.0",
62
- "typescript": "^5.9.0",
63
- "vitest": "^2.1.9"
62
+ "typescript": "7.0.2",
63
+ "typescript-5.9": "npm:typescript@5.9.3",
64
+ "typescript-6.0": "npm:typescript@6.0.3",
65
+ "vite-plus": "1.0.0"
64
66
  },
65
67
  "scripts": {
66
68
  "build": "tsc && cp src/worker-wrapper.mjs dist/worker-wrapper.mjs && cp src/emit-worker.mjs dist/emit-worker.mjs",
67
- "test": "vitest run",
68
- "test:watch": "vitest",
69
- "typecheck": "tsc --noEmit"
69
+ "test": "pnpm typecheck:consumer && vp test run && pnpm test:packed-types",
70
+ "test:packed-types": "pnpm build && node scripts/check-packed-types.mjs",
71
+ "test:watch": "vp test",
72
+ "typecheck": "tsc --noEmit && pnpm typecheck:consumer",
73
+ "typecheck:consumer": "tsc --project tsconfig.consumer.json --noEmit"
70
74
  }
71
75
  }
@@ -70,6 +70,21 @@ async function resolveAssets(inputBundlePath, assetPaths) {
70
70
  return resolvedAssets;
71
71
  }
72
72
 
73
+ // The adapter never emits source maps (they are not published and would leak
74
+ // local source paths), so a trailing external `sourceMappingURL` reference in
75
+ // the copied bundle would dangle and make source-map-aware tools (Vite Workers
76
+ // integration) fail with ENOENT. Inline `data:` maps are self-contained and kept.
77
+ const TRAILING_EXTERNAL_SOURCE_MAP =
78
+ /(?:\r?\n)?[ \t]*\/\/[#@][ \t]*sourceMappingURL=(?!data:)\S*\s*$/;
79
+
80
+ export function stripDanglingSourceMapReference(source) {
81
+ const stripped = source.replace(TRAILING_EXTERNAL_SOURCE_MAP, "");
82
+ if (stripped === source) {
83
+ return source;
84
+ }
85
+ return stripped.endsWith("\n") || stripped.length === 0 ? stripped : stripped + "\n";
86
+ }
87
+
73
88
  async function pathExists(path) {
74
89
  try {
75
90
  await lstat(path);
@@ -112,7 +127,8 @@ function mergeAssetsIgnore(existing, requiredEntries) {
112
127
  * more copied Wasm assets in `outdir`):
113
128
  *
114
129
  * _worker.js — Worker entry point (`main` in wrangler.toml)
115
- * _zfb_inner.mjs — the input bundle, copied verbatim
130
+ * _zfb_inner.mjs — the input bundle, copied with any trailing external
131
+ * `sourceMappingURL` comment removed (no map is emitted)
116
132
  * <asset>.wasm — each bundle-relative Wasm input, copied by basename
117
133
  * .assetsignore — excludes every generated JavaScript and Wasm basename
118
134
  * from the asset upload
@@ -136,7 +152,8 @@ export async function emitWorker({ inputBundlePath, outdir, assets = [], workerW
136
152
  }
137
153
 
138
154
  const innerBundlePath = join(outdirAbs, "_zfb_inner.mjs");
139
- await copyFile(inputAbs, innerBundlePath);
155
+ const inputSource = await readFile(inputAbs, "utf8");
156
+ await writeFile(innerBundlePath, stripDanglingSourceMapReference(inputSource), "utf8");
140
157
 
141
158
  const workerPath = join(outdirAbs, "_worker.js");
142
159
  await writeFile(workerPath, workerWrapperSource, "utf8");