@timber-js/app 0.2.0-alpha.159 → 0.2.0-alpha.160

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.
Files changed (66) hide show
  1. package/dist/_chunks/{actions-CBbP1hHO.js → actions-C9yAuoX3.js} +3 -3
  2. package/dist/_chunks/{actions-CBbP1hHO.js.map → actions-C9yAuoX3.js.map} +1 -1
  3. package/dist/_chunks/{cache-api-DsWEYwVZ.js → cache-api-swbefQ6F.js} +8 -3
  4. package/dist/_chunks/{cache-api-DsWEYwVZ.js.map → cache-api-swbefQ6F.js.map} +1 -1
  5. package/dist/_chunks/{cli-schema-sync-Bivx4Rn7.js → cli-schema-sync-BhYDz_-x.js} +2 -2
  6. package/dist/_chunks/{cli-schema-sync-Bivx4Rn7.js.map → cli-schema-sync-BhYDz_-x.js.map} +1 -1
  7. package/dist/_chunks/{logger-CrRmZMlo.js → logger-CPoQkGK6.js} +2 -1
  8. package/dist/_chunks/logger-CPoQkGK6.js.map +1 -0
  9. package/dist/_chunks/plugin-context-DeAxFRMq.js.map +1 -1
  10. package/dist/_chunks/{walkers-DXbJkeCY.js → walkers-c8aReVgo.js} +2 -2
  11. package/dist/_chunks/{walkers-DXbJkeCY.js.map → walkers-c8aReVgo.js.map} +1 -1
  12. package/dist/adapters/cloudflare.d.ts +7 -0
  13. package/dist/adapters/cloudflare.d.ts.map +1 -1
  14. package/dist/adapters/cloudflare.js +35 -10
  15. package/dist/adapters/cloudflare.js.map +1 -1
  16. package/dist/cache/index.js +1 -1
  17. package/dist/cdn/headers.d.ts +15 -0
  18. package/dist/cdn/headers.d.ts.map +1 -1
  19. package/dist/cdn/index.d.ts +1 -1
  20. package/dist/cdn/index.d.ts.map +1 -1
  21. package/dist/cdn/workers-cache-purge.d.ts +16 -0
  22. package/dist/cdn/workers-cache-purge.d.ts.map +1 -0
  23. package/dist/cli.js +1 -1
  24. package/dist/index.js +22 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/plugin-context.d.ts +8 -0
  27. package/dist/plugin-context.d.ts.map +1 -1
  28. package/dist/plugins/prebuilt-capture.d.ts.map +1 -1
  29. package/dist/plugins/prebuilt.d.ts.map +1 -1
  30. package/dist/plugins/shims.d.ts.map +1 -1
  31. package/dist/routing/index.js +2 -2
  32. package/dist/server/als-registry.d.ts +8 -0
  33. package/dist/server/als-registry.d.ts.map +1 -1
  34. package/dist/server/index.js +2 -2
  35. package/dist/server/internal.js +4 -4
  36. package/dist/server/internal.js.map +1 -1
  37. package/dist/server/prebuilt/payload-source.d.ts +3 -0
  38. package/dist/server/prebuilt/payload-source.d.ts.map +1 -1
  39. package/dist/server/prebuilt/synthetic-store.d.ts +2 -0
  40. package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
  41. package/dist/server/prebuilt-builder.d.ts +10 -0
  42. package/dist/server/prebuilt-builder.d.ts.map +1 -1
  43. package/dist/server/prebuilt-runtime.d.ts.map +1 -1
  44. package/dist/server/rsc-entry/index.d.ts +1 -0
  45. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  46. package/dist/server/workers-cache-bridge.d.ts +26 -0
  47. package/dist/server/workers-cache-bridge.d.ts.map +1 -0
  48. package/docs/learn/09-caching.mdx +4 -0
  49. package/docs/learn/14-deploying.mdx +9 -3
  50. package/package.json +5 -1
  51. package/src/adapters/cloudflare.ts +40 -9
  52. package/src/cdn/headers.ts +26 -0
  53. package/src/cdn/index.ts +4 -3
  54. package/src/cdn/workers-cache-purge.ts +30 -0
  55. package/src/plugin-context.ts +8 -0
  56. package/src/plugins/prebuilt-capture.ts +2 -0
  57. package/src/plugins/prebuilt.ts +28 -1
  58. package/src/plugins/shims.ts +1 -0
  59. package/src/server/als-registry.ts +14 -0
  60. package/src/server/prebuilt/payload-source.ts +9 -0
  61. package/src/server/prebuilt/synthetic-store.ts +16 -3
  62. package/src/server/prebuilt-builder.ts +74 -18
  63. package/src/server/prebuilt-runtime.ts +3 -1
  64. package/src/server/rsc-entry/index.ts +11 -0
  65. package/src/server/workers-cache-bridge.ts +33 -0
  66. package/dist/_chunks/logger-CrRmZMlo.js.map +0 -1
@@ -30,6 +30,8 @@ export interface PrebuiltManifestEntry {
30
30
  export interface PrebuiltManifest {
31
31
  [componentId: string]: {
32
32
  entries: Record<string, PrebuiltManifestEntry>;
33
+ /** When true, the component does not read segment params — the cache key uses `{}`. */
34
+ paramIndependent?: boolean;
33
35
  };
34
36
  }
35
37
  export interface PrebuiltPayloadSource {
@@ -60,6 +62,7 @@ export declare function ensureManifestLoaded(): Promise<void>;
60
62
  * Look up a manifest entry (metadata only, no payload read).
61
63
  * Used by the overlay to check seed existence and freshness.
62
64
  */
65
+ export declare function isParamIndependent(componentId: string): Promise<boolean>;
63
66
  export declare function getManifestEntry(componentId: string, cacheKey: string): Promise<PrebuiltManifestEntry | null>;
64
67
  /**
65
68
  * Look up a cached payload: manifest entry → payload bytes.
@@ -1 +1 @@
1
- {"version":3,"file":"payload-source.d.ts","sourceRoot":"","sources":["../../../src/server/prebuilt/payload-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,CAAC,WAAW,EAAE,MAAM,GAAG;QACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;KAChD,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,YAAY,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACjD,yEAAyE;IACzE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CACvD;AAsED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAIjF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAI1D;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAKvC;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAmB5B"}
1
+ {"version":3,"file":"payload-source.d.ts","sourceRoot":"","sources":["../../../src/server/prebuilt/payload-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,CAAC,WAAW,EAAE,MAAM,GAAG;QACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QAC/C,uFAAuF;QACvF,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,YAAY,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACjD,yEAAyE;IACzE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CACvD;AAsED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAIjF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAI1D;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAK9E;AAED,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAKvC;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAmB5B"}
@@ -54,6 +54,8 @@ export interface BuildTimeStoreHandle {
54
54
  * render ⇒ the entry must fail as a hard build error.
55
55
  */
56
56
  violations: BuildTimeContextError[];
57
+ /** Whether the component read `segmentParams` during the render. */
58
+ segmentParamsRead: boolean;
57
59
  }
58
60
  /**
59
61
  * Create the synthetic per-combo store. `segmentParams` must already be
@@ -1 +1 @@
1
- {"version":3,"file":"synthetic-store.d.ts","sourceRoot":"","sources":["../../../src/server/prebuilt/synthetic-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAW7C;AAED,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,mBAAmB,CAAC;IAC3B;;;;OAIG;IACH,UAAU,EAAE,qBAAqB,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAC/C,oBAAoB,CA2BtB"}
1
+ {"version":3,"file":"synthetic-store.d.ts","sourceRoot":"","sources":["../../../src/server/prebuilt/synthetic-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAW7C;AAED,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,mBAAmB,CAAC;IAC3B;;;;OAIG;IACH,UAAU,EAAE,qBAAqB,EAAE,CAAC;IACpC,oEAAoE;IACpE,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAC/C,oBAAoB,CAsCtB"}
@@ -54,6 +54,8 @@ export interface CapturedEntry {
54
54
  bytes: Uint8Array;
55
55
  /** Resolved tags from callsite options — static or function-form, evaluated at capture time. */
56
56
  tags?: string[];
57
+ /** Component does not read segment params — cache key uses `{}`. */
58
+ paramIndependent?: boolean;
57
59
  }
58
60
  /** A per-entry problem — either a skip (build continues) or a hard error. */
59
61
  export interface CaptureIssue {
@@ -92,6 +94,14 @@ export interface CaptureOptions {
92
94
  * failure, not a per-entry data problem.
93
95
  */
94
96
  onEntry: (entry: CapturedEntry) => Promise<void>;
97
+ /**
98
+ * Component ID → built chunk file name (or URL) for declaring modules
99
+ * behind dynamic imports. The capture pass imports these to trigger
100
+ * cache.component() registration before rendering.
101
+ */
102
+ lazyChunks?: Record<string, string>;
103
+ /** @internal Override chunk loader for testing. */
104
+ _loadChunk?: (chunkFile: string) => Promise<void>;
95
105
  /**
96
106
  * Per-entry ceiling on render + drain. A component that hangs (stalled
97
107
  * fetch, unbounded recursion the cycle guard can't see) fails its entry
@@ -1 +1 @@
1
- {"version":3,"file":"prebuilt-builder.d.ts","sourceRoot":"","sources":["../../src/server/prebuilt-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAsBH,OAAO,KAAK,EAAgB,YAAY,EAAuB,MAAM,oBAAoB,CAAC;AAO1F,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,gGAAgG;IAChG,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,mEAAmE;IACnE,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,qEAAqE;IACrE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1D;;;;OAIG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAmSD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,CAAC,CAgNzB"}
1
+ {"version":3,"file":"prebuilt-builder.d.ts","sourceRoot":"","sources":["../../src/server/prebuilt-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAsBH,OAAO,KAAK,EAAgB,YAAY,EAAuB,MAAM,oBAAoB,CAAC;AAO1F,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,gGAAgG;IAChG,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,mEAAmE;IACnE,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,qEAAqE;IACrE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1D;;;;OAIG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAmTD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,CAAC,CA8OzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"prebuilt-runtime.d.ts","sourceRoot":"","sources":["../../src/server/prebuilt-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAiCH;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGlE;AAID;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC;IACjE,2EAA2E;IAC3E,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED,2DAA2D;AAE3D,KAAK,eAAe,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC;AAY/C,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,eAAe,CAAC;IAC3B,OAAO,EAAE,wBAAwB,CAAC;CACnC;AA8ED,UAAU,YAAY;IACpB,wBAAwB,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnF,sBAAsB,EAAE,CACtB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;KAAE,KAC/C,cAAc,CAAC,UAAU,CAAC,CAAC;CACjC;AAID;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAEvD;AAmCD,eAAO,MAAM,mBAAmB,aAAoB,CAAC;AAoXrD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,eAAe,EAClD,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,CAAC,EACZ,OAAO,GAAE,wBAA6B,GACrC,CAAC,CAyCH;AAED,qDAAqD;AACrD,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAEpF;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,IAAI,WAAW,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAE/E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,eAAe,EACrD,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,EACZ,QAAQ,CAAC,EAAE,wBAAwB,GAClC,CAAC,CAYH"}
1
+ {"version":3,"file":"prebuilt-runtime.d.ts","sourceRoot":"","sources":["../../src/server/prebuilt-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAkCH;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGlE;AAID;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC;IACjE,2EAA2E;IAC3E,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED,2DAA2D;AAE3D,KAAK,eAAe,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC;AAY/C,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,eAAe,CAAC;IAC3B,OAAO,EAAE,wBAAwB,CAAC;CACnC;AA8ED,UAAU,YAAY;IACpB,wBAAwB,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnF,sBAAsB,EAAE,CACtB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;KAAE,KAC/C,cAAc,CAAC,UAAU,CAAC,CAAC;CACjC;AAID;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAEvD;AAmCD,eAAO,MAAM,mBAAmB,aAAoB,CAAC;AAqXrD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,eAAe,EAClD,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,CAAC,EACZ,OAAO,GAAE,wBAA6B,GACrC,CAAC,CAyCH;AAED,qDAAqD;AACrD,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAEpF;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,IAAI,WAAW,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAE/E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,eAAe,EACrD,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,EACZ,QAAQ,CAAC,EAAE,wBAAwB,GAClC,CAAC,CAYH"}
@@ -13,6 +13,7 @@ import type { PipelineConfig } from '../pipeline.js';
13
13
  export declare function setDevSourceMapHandler(handler: (error: Error) => void): void;
14
14
  export { runWithEarlyHintsSender } from '../early-hints-sender.js';
15
15
  export { runWithWaitUntil } from '../waituntil-bridge.js';
16
+ export { runWithWorkersCachePurge } from '../workers-cache-bridge.js';
16
17
  /**
17
18
  * Post-build flight capture entry point (TIM-1119). The capture pass must
18
19
  * run inside the built RSC bundle — the prebuilt component registry, the
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAuBA,OAAO,uCAAuC,CAAC;AAuB/C,OAAO,KAAK,EAAuB,cAAc,EAAc,MAAM,gBAAgB,CAAC;AAuCtF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAE5E;AAgbD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,wBAAwB,EAAE,cAAc,GACvD,OAAO,CAAC,OAAO,wBAAwB,EAAE,cAAc,CAAC,CAE1D;AAED,QAAA,MAAiB,QAAQ,qCAGxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,gBAAkB,CAAC;AAE9C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAuBA,OAAO,uCAAuC,CAAC;AAuB/C,OAAO,KAAK,EAAuB,cAAc,EAAc,MAAM,gBAAgB,CAAC;AAuCtF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAE5E;AAsbD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,wBAAwB,EAAE,cAAc,GACvD,OAAO,CAAC,OAAO,wBAAwB,EAAE,cAAc,CAAC,CAE1D;AAED,QAAA,MAAiB,QAAQ,qCAGxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,gBAAkB,CAAC;AAE9C,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Per-request Workers Cache purge bridge — ALS bridge for Cloudflare adapter.
3
+ *
4
+ * The generated _worker.js wraps each request with `runWithWorkersCachePurge`,
5
+ * binding `ctx.cache` from the Cloudflare ExecutionContext. The
6
+ * `WorkersCachePurge` CDN purge handler reads from this ALS to call
7
+ * `ctx.cache.purge({ tags })` without needing an API token.
8
+ *
9
+ * Design doc: design/06-caching.md, design/25-production-deployments.md
10
+ */
11
+ import type { WorkersCachePurgeCtx } from './als-registry.js';
12
+ /**
13
+ * Run a function with Workers Cache purge context available.
14
+ *
15
+ * Called by the generated Cloudflare _worker.js entry to bind
16
+ * `ctx.cache` for the request duration.
17
+ */
18
+ export declare function runWithWorkersCachePurge<T>(ctx: WorkersCachePurgeCtx, fn: () => T): T;
19
+ /**
20
+ * Get the current request's Workers Cache context, if available.
21
+ *
22
+ * Returns undefined outside a Cloudflare Workers request context
23
+ * or on platforms without Workers Cache support.
24
+ */
25
+ export declare function getWorkersCachePurgeCtx(): WorkersCachePurgeCtx | undefined;
26
+ //# sourceMappingURL=workers-cache-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers-cache-bridge.d.ts","sourceRoot":"","sources":["../../src/server/workers-cache-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,GAAG,EAAE,oBAAoB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,oBAAoB,GAAG,SAAS,CAE1E"}
@@ -130,3 +130,7 @@ export default {
130
130
  ```
131
131
 
132
132
  Implement the `CacheHandler` interface for Redis, Cloudflare KV, or any other backend. See the [Cache API Reference](/docs/api-cache).
133
+
134
+ import SlowStaticComponent from '@/ui/SlowStaticComponent';
135
+
136
+ <SlowStaticComponent />
@@ -203,7 +203,7 @@ timber.js provides typed helpers for CDN caching headers and tag-based purging.
203
203
  ### Header Helpers
204
204
 
205
205
  ```ts title="app/proxy.ts"
206
- import { cacheControl, surrogateKey } from '@timber-js/app/cdn';
206
+ import { cacheControl, cacheTag } from '@timber-js/app/cdn';
207
207
 
208
208
  export default async function proxy(ctx) {
209
209
  if (ctx.pathname.startsWith('/products/')) {
@@ -212,19 +212,25 @@ export default async function proxy(ctx) {
212
212
  maxAge: 600,
213
213
  staleWhileRevalidate: 60,
214
214
  }));
215
- ctx.headers.set('Surrogate-Key', surrogateKey(['products']));
215
+ // Use cacheTag for Cloudflare Workers Cache (all plans)
216
+ ctx.headers.set('Cache-Tag', cacheTag(['products']));
217
+ // Or surrogateKey for Fastly/Akamai/Varnish
218
+ // ctx.headers.set('Surrogate-Key', surrogateKey(['products']));
216
219
  }
217
220
  }
218
221
  ```
219
222
 
220
223
  ### Purge on `revalidateTag`
221
224
 
222
- Configure a CDN purge handler so `revalidateTag()` also purges the CDN:
225
+ When using the Cloudflare adapter, CDN purge is **automatic** — no configuration needed. The adapter auto-wires `WorkersCachePurge`, which uses `ctx.cache.purge({ tags })` from the Workers runtime. Available on all Cloudflare plans with no API token required. Just set `Cache-Tag` headers on your responses using `cacheTag()`.
226
+
227
+ For non-Workers Cloudflare zones (Enterprise), or other CDNs, configure a purge handler manually:
223
228
 
224
229
  ```ts title="timber.config.ts"
225
230
  import { CloudflarePurge } from '@timber-js/app/cdn/cloudflare';
226
231
 
227
232
  export default {
233
+ // Only needed for non-Workers Cloudflare zones (Enterprise)
228
234
  cdnPurge: new CloudflarePurge({
229
235
  zoneId: process.env.CF_ZONE_ID!,
230
236
  apiToken: process.env.CF_API_TOKEN!,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.159",
3
+ "version": "0.2.0-alpha.160",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -92,6 +92,10 @@
92
92
  "types": "./dist/cdn/cloudflare-purge.d.ts",
93
93
  "import": "./dist/cdn/cloudflare-purge.js"
94
94
  },
95
+ "./cdn/cloudflare-workers": {
96
+ "types": "./dist/cdn/workers-cache-purge.d.ts",
97
+ "import": "./dist/cdn/workers-cache-purge.js"
98
+ },
95
99
  "./cdn/fastly": {
96
100
  "types": "./dist/cdn/fastly-purge.d.ts",
97
101
  "import": "./dist/cdn/fastly-purge.js"
@@ -285,6 +285,12 @@ export function cloudflare(options: CloudflareAdapterOptions = {}): TimberPlatfo
285
285
  generatePrebuiltSourceModule(prebuiltData)
286
286
  );
287
287
 
288
+ // Write the runtime init module — sets process.env.TIMBER_RUNTIME
289
+ // BEFORE the RSC entry evaluates (ESM import order). Without this,
290
+ // createRequestHandler's auto-wire for WorkersCachePurge would not
291
+ // detect the Cloudflare runtime (TIM-1170).
292
+ await writeFile(join(outDir, '_timber-runtime-init.js'), generateRuntimeInitModule());
293
+
288
294
  // Generate the Workers entry point
289
295
  const hasManifestInit = !!config.manifestInit;
290
296
  const workerEntry = generateWorkerEntry(outDir, outDir, hasManifestInit, hasWorkerHandlers);
@@ -342,16 +348,16 @@ export function generateWorkerEntry(
342
348
  // Do not edit — this file is regenerated on each build.
343
349
 
344
350
  import { AsyncLocalStorage } from 'node:async_hooks'
345
- // Side-effect import: sets globalThis.__TIMBER_PREBUILT_SOURCE__ before the
346
- // RSC handler evaluates. Must be a static import (not body code) so ESM
347
- // evaluation order guarantees it runs before ./rsc/index.js.
351
+ // Side-effect imports: evaluated IN ORDER before the _worker.js body runs.
352
+ // _timber-runtime-init.js sets TIMBER_RUNTIME before createRequestHandler()
353
+ // runs (during the RSC entry's top-level await) so the auto-wire for
354
+ // WorkersCachePurge can detect the Cloudflare runtime (TIM-1170).
355
+ // _timber-prebuilt-source.js sets globalThis.__TIMBER_PREBUILT_SOURCE__
356
+ // for cache.component payloads.
357
+ import './_timber-runtime-init.js'
348
358
  import './_timber-prebuilt-source.js'
349
- ${manifestImport}import handler, { runWithWaitUntil } from '${rscEntryRelative}'
359
+ ${manifestImport}import handler, { runWithWaitUntil, runWithWorkersCachePurge } from '${rscEntryRelative}'
350
360
  ${handlersImport}
351
- // Set TIMBER_RUNTIME for instrumentation.ts conditional SDK initialization.
352
- // See design/25-production-deployments.md §"TIMBER_RUNTIME".
353
- globalThis.process ??= { env: {} }
354
- process.env.TIMBER_RUNTIME = 'cloudflare'
355
361
 
356
362
  // Bind Cloudflare env to ALS so getCloudflareBindings() works at runtime.
357
363
  // Uses the same Symbol.for key as getCloudflareBindings() reads from.
@@ -406,8 +412,16 @@ ${handlersSpread} async fetch(request, env, ctx) {
406
412
  if (cached) return cached
407
413
  }
408
414
 
409
- const response = await bindingsAls.run(env, () =>
415
+ // Bridge ctx.cache into the per-request ALS so WorkersCachePurge can
416
+ // call ctx.cache.purge({ tags }) during cache.invalidate/revalidateTag.
417
+ // Workers Cache is available on all Cloudflare plans — no API token needed.
418
+ const workersCacheCtx = ctx.cache && typeof ctx.cache.purge === 'function' ? ctx.cache : null
419
+ const runHandler = () =>
410
420
  runWithWaitUntil((promise) => ctx.waitUntil(promise), () => handler(request))
421
+ const response = await bindingsAls.run(env, () =>
422
+ workersCacheCtx
423
+ ? runWithWorkersCachePurge(workersCacheCtx, runHandler)
424
+ : runHandler()
411
425
  )
412
426
 
413
427
  // Cache public responses at the edge. A response is edge-cacheable if
@@ -525,6 +539,23 @@ globalThis.__TIMBER_PREBUILT_SOURCE__ = {
525
539
  `;
526
540
  }
527
541
 
542
+ /**
543
+ * Generate the runtime init module that sets TIMBER_RUNTIME before the
544
+ * RSC entry evaluates. Imported as a side-effect module in _worker.js.
545
+ *
546
+ * @internal Exported for testing.
547
+ */
548
+ export function generateRuntimeInitModule(): string {
549
+ return `// Generated by @timber-js/app/adapters/cloudflare
550
+ // Sets TIMBER_RUNTIME before the RSC handler evaluates (TIM-1170).
551
+ // Must be a separate module imported before rsc/index.js so that ESM
552
+ // evaluation order guarantees the env var is set when
553
+ // createRequestHandler() runs its auto-wire logic.
554
+ globalThis.process ??= { env: {} }
555
+ process.env.TIMBER_RUNTIME = 'cloudflare'
556
+ `;
557
+ }
558
+
528
559
  /** Wrangler binding sections generated from `CloudflareBindings`. */
529
560
  export interface WranglerBindingsConfig {
530
561
  kv_namespaces?: Array<{ binding: string; id: string }>;
@@ -125,3 +125,29 @@ export function surrogateKey(tags: string[]): string {
125
125
  }
126
126
  return [...new Set(tags)].join(' ');
127
127
  }
128
+
129
+ /**
130
+ * Build a `Cache-Tag` header value — comma-separated tags for Cloudflare
131
+ * Workers Cache tag-based purging. Available on ALL Cloudflare plans.
132
+ *
133
+ * Tags are deduplicated. Tags must not contain commas (the delimiter).
134
+ * Maximum tag length is 1024 characters per Cloudflare's limits.
135
+ *
136
+ * Use this instead of `surrogateKey()` when using Cloudflare Workers Cache
137
+ * (ctx.cache.purge). Workers Cache reads `Cache-Tag`, not `Surrogate-Key`.
138
+ *
139
+ * ```ts
140
+ * ctx.headers.set('Cache-Tag', cacheTag(['products', `product:${id}`]));
141
+ * ```
142
+ */
143
+ export function cacheTag(tags: string[]): string {
144
+ for (const tag of tags) {
145
+ if (tag.includes(',')) {
146
+ throw new Error(`cacheTag: tag "${tag}" must not contain commas`);
147
+ }
148
+ if (tag.length > 1024) {
149
+ throw new Error(`cacheTag: tag "${tag}" exceeds 1024-character limit`);
150
+ }
151
+ }
152
+ return [...new Set(tags)].join(',');
153
+ }
package/src/cdn/index.ts CHANGED
@@ -3,14 +3,15 @@
3
3
  // Header utilities and purge handler for CDN caching. Developer-elected,
4
4
  // never automatic. See design/25-production-deployments.md §Layer 2.
5
5
 
6
- export { cacheControl, surrogateKey, surrogateControl, cdnCacheControl } from './headers';
6
+ export { cacheControl, surrogateKey, surrogateControl, cdnCacheControl, cacheTag } from './headers';
7
7
  export type { CacheControlOptions, SurrogateControlOptions } from './headers';
8
8
 
9
9
  export { setCdnPurgeHandler, getCdnPurgeHandler } from './purge-store';
10
10
  export type { CdnPurgeHandler } from './purge-store';
11
11
 
12
12
  // Platform-specific purge handlers are at their own entry points:
13
- // @timber-js/app/cdn/cloudflare → CloudflarePurge
14
- // @timber-js/app/cdn/fastly → FastlyPurge
13
+ // @timber-js/app/cdn/cloudflare → CloudflarePurge (REST API, Enterprise)
14
+ // @timber-js/app/cdn/cloudflare-workers → WorkersCachePurge (ctx.cache, all plans)
15
+ // @timber-js/app/cdn/fastly → FastlyPurge
15
16
  // Same pattern as @timber-js/app/cache/stores/* — keeps platform
16
17
  // dependencies out of the main CDN import.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Cloudflare Workers Cache purge handler — purges by cache tag via
3
+ * ctx.cache.purge() on the ExecutionContext.
4
+ *
5
+ * Available on ALL Cloudflare plans (no Enterprise requirement).
6
+ * No API token needed — uses the Workers runtime's built-in cache API.
7
+ *
8
+ * Auto-wired by the Cloudflare adapter when no user-configured CDN
9
+ * purge handler is set. Uses the Workers Cache ALS bridge to access
10
+ * ctx.cache from within the request context.
11
+ */
12
+
13
+ import type { CdnPurgeHandler } from './purge-store';
14
+ import { getWorkersCachePurgeCtx } from '../server/workers-cache-bridge.js';
15
+
16
+ export class WorkersCachePurge implements CdnPurgeHandler {
17
+ async purgeTags(tags: string[]): Promise<void> {
18
+ if (tags.length === 0) return;
19
+
20
+ const ctx = getWorkersCachePurgeCtx();
21
+ if (!ctx) {
22
+ throw new Error(
23
+ 'WorkersCachePurge: no Workers Cache context available. ' +
24
+ 'This handler can only be used within a Cloudflare Workers request context.'
25
+ );
26
+ }
27
+
28
+ await ctx.purge({ tags });
29
+ }
30
+ }
@@ -137,6 +137,14 @@ export interface PluginContext {
137
137
  * the generated `cache.component(...)` call was picked up.
138
138
  */
139
139
  prerenderSugarPages?: Set<string>;
140
+ /**
141
+ * Component ID → built chunk file name for prebuilt-declaring modules
142
+ * that are only reachable via dynamic imports (import.meta.glob, lazy
143
+ * routes). The capture pass imports these chunks to trigger
144
+ * cache.component() registration before rendering. Populated during
145
+ * RSC generateBundle alongside routePrebuiltComponents.
146
+ */
147
+ prebuiltLazyChunks?: Map<string, string>;
140
148
  /** Post-build prebuilt capture summary (populated by timber-prebuilt, consumed by build-report). */
141
149
  captureSummary?: CaptureSummary;
142
150
  }
@@ -83,6 +83,7 @@ export function createPrebuiltArtifactWriter(prebuiltDir: string): PrebuiltArtif
83
83
  await writeFileAtomic(path, entry.bytes);
84
84
 
85
85
  manifest[entry.componentId] ??= { entries: {} };
86
+ if (entry.paramIndependent) manifest[entry.componentId].paramIndependent = true;
86
87
  manifest[entry.componentId].entries[entry.cacheKey] = {
87
88
  params: entry.params,
88
89
  file: relativeFile,
@@ -186,6 +187,7 @@ export async function runPrebuiltCapture(ctx: PluginContext): Promise<CaptureSum
186
187
  callSiteProps: ctx.prebuiltCallSiteProps
187
188
  ? Object.fromEntries(ctx.prebuiltCallSiteProps)
188
189
  : undefined,
190
+ lazyChunks: ctx.prebuiltLazyChunks ? Object.fromEntries(ctx.prebuiltLazyChunks) : undefined,
189
191
  onEntry: (entry) => writer.write(entry),
190
192
  });
191
193
 
@@ -582,7 +582,13 @@ export function timberPrebuilt(ctx: PluginContext): Plugin {
582
582
  * SHARED components work — a component imported by several routes is
583
583
  * captured under every importing route's param combos.
584
584
  */
585
- generateBundle() {
585
+ generateBundle(
586
+ _options?: unknown,
587
+ bundle?: Record<
588
+ string,
589
+ { type: string; modules?: Record<string, unknown>; fileName?: string }
590
+ >
591
+ ) {
586
592
  if (ctx.dev) return;
587
593
  if (this?.environment?.name !== 'rsc') return;
588
594
  // Recompute attribution from scratch every build — a stale map from
@@ -605,6 +611,27 @@ export function timberPrebuilt(ctx: PluginContext): Plugin {
605
611
  }
606
612
  if (routeComponents.size > 0) {
607
613
  ctx.routePrebuiltComponents = routeComponents;
614
+
615
+ // Build a map of componentId → chunk fileName for declaring
616
+ // modules that aren't route files themselves (behind dynamic
617
+ // imports like import.meta.glob). The capture pass imports these
618
+ // chunks to trigger cache.component() registration.
619
+ if (bundle) {
620
+ const routeFileSet = new Set(routeComponents.keys());
621
+ const lazyChunks = new Map<string, string>();
622
+ for (const [moduleId, componentIds] of moduleComponents) {
623
+ if (routeFileSet.has(moduleId)) continue;
624
+ for (const [, chunk] of Object.entries(bundle)) {
625
+ if (chunk.type !== 'chunk' || !chunk.modules) continue;
626
+ if (moduleId in chunk.modules) {
627
+ for (const id of componentIds) lazyChunks.set(id, chunk.fileName!);
628
+ break;
629
+ }
630
+ }
631
+ }
632
+ if (lazyChunks.size > 0) ctx.prebuiltLazyChunks = lazyChunks;
633
+ }
634
+
608
635
  // Cross-module call-site prop detection (TIM-1142): walk the
609
636
  // module graph from each route file and scan ALL reachable modules
610
637
  // for JSX usages of prebuilt components — not just the route files
@@ -64,6 +64,7 @@ export const SUBPATH_SRC_MAP: Record<string, string> = {
64
64
 
65
65
  'cdn': 'cdn/index.ts',
66
66
  'cdn/cloudflare': 'cdn/cloudflare-purge.ts',
67
+ 'cdn/cloudflare-workers': 'cdn/workers-cache-purge.ts',
67
68
  'cdn/fastly': 'cdn/fastly-purge.ts',
68
69
 
69
70
  'adapters/cloudflare': 'adapters/cloudflare.ts',
@@ -219,3 +219,17 @@ export type WaitUntilFn = (promise: Promise<unknown>) => void;
219
219
 
220
220
  /** @internal — import via waituntil-bridge.ts public API */
221
221
  export const waitUntilAls = getOrCreateAls<WaitUntilFn>(Symbol.for('timber:wait-until-als'));
222
+
223
+ // ─── Workers Cache Purge Bridge ─────────────────────────────────────────
224
+ // Used by: workers-cache-bridge.ts (runWithWorkersCachePurge())
225
+ // Design doc: design/06-caching.md, design/25-production-deployments.md
226
+
227
+ /** Workers Cache purge function from Cloudflare ExecutionContext.cache. */
228
+ export interface WorkersCachePurgeCtx {
229
+ purge(opts: { tags: string[] }): Promise<void>;
230
+ }
231
+
232
+ /** @internal — import via workers-cache-bridge.ts public API */
233
+ export const workersCachePurgeAls = getOrCreateAls<WorkersCachePurgeCtx>(
234
+ Symbol.for('timber:workers-cache-purge-als')
235
+ );
@@ -34,6 +34,8 @@ export interface PrebuiltManifestEntry {
34
34
  export interface PrebuiltManifest {
35
35
  [componentId: string]: {
36
36
  entries: Record<string, PrebuiltManifestEntry>;
37
+ /** When true, the component does not read segment params — the cache key uses `{}`. */
38
+ paramIndependent?: boolean;
37
39
  };
38
40
  }
39
41
 
@@ -147,6 +149,13 @@ export async function ensureManifestLoaded(): Promise<void> {
147
149
  * Look up a manifest entry (metadata only, no payload read).
148
150
  * Used by the overlay to check seed existence and freshness.
149
151
  */
152
+ export async function isParamIndependent(componentId: string): Promise<boolean> {
153
+ if (source === null) return false;
154
+ manifestPromise ??= loadManifestWithTimeout(source);
155
+ const manifest = await manifestPromise;
156
+ return manifest?.[componentId]?.paramIndependent === true;
157
+ }
158
+
150
159
  export async function getManifestEntry(
151
160
  componentId: string,
152
161
  cacheKey: string
@@ -68,6 +68,8 @@ export interface BuildTimeStoreHandle {
68
68
  * render ⇒ the entry must fail as a hard build error.
69
69
  */
70
70
  violations: BuildTimeContextError[];
71
+ /** Whether the component read `segmentParams` during the render. */
72
+ segmentParamsRead: boolean;
71
73
  }
72
74
 
73
75
  /**
@@ -80,6 +82,11 @@ export function createBuildTimeStore(
80
82
  segmentParams: Record<string, string | string[]>
81
83
  ): BuildTimeStoreHandle {
82
84
  const violations: BuildTimeContextError[] = [];
85
+ const handle: BuildTimeStoreHandle = {
86
+ store: null!,
87
+ violations,
88
+ segmentParamsRead: false,
89
+ };
83
90
  const store: RequestContextStore = {
84
91
  headers: new Headers(),
85
92
  originalHeaders: new Headers(),
@@ -100,9 +107,15 @@ export function createBuildTimeStore(
100
107
  headers: { get: () => violate('headers()') },
101
108
  parsedCookies: { get: () => violate('cookies()') },
102
109
  searchParams: { get: () => violate('searchParams') },
103
- // Reached by cookie mutators (set/delete/clear) via assertMutable —
104
- // its only reader — before any other store field.
105
110
  mutableContext: { get: () => violate('cookies()') },
111
+ segmentParams: {
112
+ get: () => {
113
+ handle.segmentParamsRead = true;
114
+ return segmentParams;
115
+ },
116
+ enumerable: true,
117
+ },
106
118
  });
107
- return { store, violations };
119
+ handle.store = store;
120
+ return handle;
108
121
  }