@timber-js/app 0.2.0-alpha.165 → 0.2.0-alpha.167
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/_chunks/{actions-CSDD6x7U.js → actions-TSxpXLHJ.js} +3 -3
- package/dist/_chunks/{actions-CSDD6x7U.js.map → actions-TSxpXLHJ.js.map} +1 -1
- package/dist/_chunks/{cache-api-eb1gydM7.js → cache-api-DzpQQOEx.js} +53 -49
- package/dist/_chunks/{cache-api-eb1gydM7.js.map → cache-api-DzpQQOEx.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-mGfRbjh2.js → cli-schema-sync-wX-i90Og.js} +6 -4
- package/dist/_chunks/cli-schema-sync-wX-i90Og.js.map +1 -0
- package/dist/_chunks/cloudflare-AHoWYTYr.js +1188 -0
- package/dist/_chunks/cloudflare-AHoWYTYr.js.map +1 -0
- package/dist/_chunks/{define-CFmvb4Bt.js → define-COtkxMRT.js} +16 -28
- package/dist/_chunks/define-COtkxMRT.js.map +1 -0
- package/dist/_chunks/{define-Bssfp6ot.js → define-c4au4I9R.js} +2 -2
- package/dist/_chunks/{define-Bssfp6ot.js.map → define-c4au4I9R.js.map} +1 -1
- package/dist/_chunks/{logger-B_O6-mdJ.js → logger-t3uxAmbX.js} +3 -3
- package/dist/_chunks/logger-t3uxAmbX.js.map +1 -0
- package/dist/_chunks/{plugin-context-BnaiU_cF.js → plugin-context---kTF5v8.js} +2 -2
- package/dist/_chunks/{plugin-context-BnaiU_cF.js.map → plugin-context---kTF5v8.js.map} +1 -1
- package/dist/_chunks/{resolve-schema-3iUvBV5T.js → resolve-schema-Dz3fcFUo.js} +2 -2
- package/dist/_chunks/{resolve-schema-3iUvBV5T.js.map → resolve-schema-Dz3fcFUo.js.map} +1 -1
- package/dist/_chunks/{schema-bridge-BY3QLBL7.js → schema-bridge-DT_Tn0Xf.js} +2 -2
- package/dist/_chunks/{schema-bridge-BY3QLBL7.js.map → schema-bridge-DT_Tn0Xf.js.map} +1 -1
- package/dist/_chunks/{use-query-states-CbeQmext.js → use-query-states-DFvWd-EA.js} +65 -7
- package/dist/_chunks/use-query-states-DFvWd-EA.js.map +1 -0
- package/dist/_chunks/{walkers-BL3MCMgO.js → walkers-Cfwvl-UC.js} +3 -3
- package/dist/_chunks/{walkers-BL3MCMgO.js.map → walkers-Cfwvl-UC.js.map} +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.d.ts +12 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +2 -461
- package/dist/adapters/nitro.js +1 -1
- package/dist/adapters/types.d.ts +2 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cache/cache-api.d.ts +33 -11
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/history.d.ts +10 -0
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/internal.js +139 -115
- package/dist/client/internal.js.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/logs.d.ts +15 -1
- package/dist/dev-tools/logs.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +138 -45
- package/dist/index.js.map +1 -1
- package/dist/params/index.js +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +8 -8
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/schema-bridge.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +35 -4
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +3 -7
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/wrappers.d.ts +2 -2
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/segment-params/index.js +1 -1
- package/dist/server/als-registry.d.ts +7 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +3 -1
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.js +12 -7
- package/dist/server/internal.js.map +1 -1
- package/dist/server/route-element-builder.d.ts +9 -0
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +8 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +4 -4
- package/docs/api/33-api-search-params.mdx +13 -0
- package/docs/learn/00-introduction.mdx +1 -2
- package/docs/learn/09-caching.mdx +5 -5
- package/docs/more/03-coming-from-nextjs.mdx +2 -2
- package/docs/more/50-ai-agent-instructions.mdx +5 -5
- package/package.json +10 -8
- package/src/adapters/cloudflare.ts +63 -25
- package/src/adapters/types.ts +2 -0
- package/src/cache/cache-api.ts +84 -84
- package/src/cli.ts +0 -0
- package/src/client/browser-entry/post-hydration.ts +16 -9
- package/src/client/history.ts +11 -0
- package/src/client/router.ts +212 -206
- package/src/client/use-query-states.ts +99 -7
- package/src/dev-tools/logs.ts +119 -34
- package/src/index.ts +14 -1
- package/src/plugins/adapter-build.ts +1 -0
- package/src/plugins/cache.ts +45 -30
- package/src/routing/scanner.ts +7 -0
- package/src/schema-bridge.ts +4 -3
- package/src/search-params/define.ts +57 -51
- package/src/search-params/wrappers.ts +17 -26
- package/src/server/als-registry.ts +7 -0
- package/src/server/deny-boundary.ts +6 -3
- package/src/server/route-element-builder.ts +49 -8
- package/src/server/rsc-entry/render-route.ts +3 -1
- package/src/server/rsc-entry/rsc-payload.ts +21 -4
- package/src/server/rsc-entry/rsc-stream.ts +8 -0
- package/src/server/stream-utils.ts +8 -6
- package/LICENSE +0 -8
- package/dist/_chunks/cli-schema-sync-mGfRbjh2.js.map +0 -1
- package/dist/_chunks/define-CFmvb4Bt.js.map +0 -1
- package/dist/_chunks/logger-B_O6-mdJ.js.map +0 -1
- package/dist/_chunks/use-query-states-CbeQmext.js.map +0 -1
- package/dist/adapters/cloudflare.js.map +0 -1
|
@@ -73,6 +73,15 @@ export interface RouteElementResult {
|
|
|
73
73
|
* See design/19-client-navigation.md §"X-Timber-State-Tree Header"
|
|
74
74
|
*/
|
|
75
75
|
skippedSegments: string[];
|
|
76
|
+
/**
|
|
77
|
+
* Resolves when every deny-capable shell component (PageDenyBoundary and
|
|
78
|
+
* each TracedLayout with a deny page chain) has finished executing, OR
|
|
79
|
+
* immediately when any of them catches a DenySignal in-tree (the HTTP
|
|
80
|
+
* status is known at that point; inner components may never run).
|
|
81
|
+
* Undefined when the route has no deny-capable server components.
|
|
82
|
+
* See TIM-1045 (pages), TIM-1208 (layouts).
|
|
83
|
+
*/
|
|
84
|
+
shellSettled?: Promise<void>;
|
|
76
85
|
}
|
|
77
86
|
/**
|
|
78
87
|
* Build a React element tree from a matched route.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-element-builder.d.ts","sourceRoot":"","sources":["../../src/server/route-element-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"route-element-builder.d.ts","sourceRoot":"","sources":["../../src/server/route-element-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AA6B9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC,YAAY,CAW/E;AAwCD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAM7D;AAID;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAID,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IAC3C,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC;IAC5B,wDAAwD;IACxD,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,qCAAqC;IACrC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AA8DD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,YAAY,CAAC,EAAE,mBAAmB,EAClC,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EACpC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3C,OAAO,CAAC,kBAAkB,CAAC,CAyiB7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/render-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAW1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAMtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAa/D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,mBAAmB,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,IAAI,EAAE,eAAe,EACrB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/render-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAW1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAMtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAa/D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,mBAAmB,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,IAAI,EAAE,eAAe,EACrB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAiNnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rsc-payload.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAQ/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"rsc-payload.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAQ/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,OAAO,CAAC,QAAQ,CAAC,CAoLnB"}
|
|
@@ -40,6 +40,14 @@ export interface RenderSignals {
|
|
|
40
40
|
lastUnhandledError: unknown | null;
|
|
41
41
|
/** Callback fired when a redirect or deny signal is captured in onError. */
|
|
42
42
|
onSignal?: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Resolves when every deny-capable shell component (PageDenyBoundary +
|
|
45
|
+
* TracedLayouts with deny chains) has settled, OR immediately when any
|
|
46
|
+
* catches a DenySignal in-tree (status is known). Set by renderRoute
|
|
47
|
+
* after element-tree construction. Awaited by buildRscPayloadResponse
|
|
48
|
+
* before committing HTTP status. See TIM-1045, TIM-1208.
|
|
49
|
+
*/
|
|
50
|
+
shellSettled?: Promise<void>;
|
|
43
51
|
/** Callback fired when an unhandled error (non-signal) is captured. Used
|
|
44
52
|
* to halt Flight data injection into the inline stream. */
|
|
45
53
|
onUnhandledError?: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rsc-stream.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAItB;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,WAAW,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACvD;;;;;;;;OAQG;IACH,kBAAkB,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;gEAC4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,mBAAmB,EAAE,CAAC;CAClD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,eAAe,CA6J1F"}
|
|
1
|
+
{"version":3,"file":"rsc-stream.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAItB;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,WAAW,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACvD;;;;;;;;OAQG;IACH,kBAAkB,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B;gEAC4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,mBAAmB,EAAE,CAAC;CAClD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,eAAe,CA6J1F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-utils.d.ts","sourceRoot":"","sources":["../../src/server/stream-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,MAAM,WAAW,UAAU;IACzB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,CAAC,EAAE,UAAU,GACnB,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC,CAmK1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,SAAS,EAAE,MAAM,GAChB,cAAc,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"stream-utils.d.ts","sourceRoot":"","sources":["../../src/server/stream-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,MAAM,WAAW,UAAU;IACzB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,CAAC,EAAE,UAAU,GACnB,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC,CAmK1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,SAAS,EAAE,MAAM,GAChB,cAAc,CAAC,UAAU,CAAC,CA4D5B"}
|
|
@@ -8,7 +8,7 @@ slug: 'api-cache'
|
|
|
8
8
|
|
|
9
9
|
Caching primitives and cache handler interface. Import from `@timber-js/app/cache`.
|
|
10
10
|
|
|
11
|
-
## `cache(fn, options)`
|
|
11
|
+
## `cache.data(fn, options)`
|
|
12
12
|
|
|
13
13
|
Wrap an async function with cross-request caching.
|
|
14
14
|
|
|
@@ -17,7 +17,7 @@ import { cache } from '@timber-js/app/cache';
|
|
|
17
17
|
|
|
18
18
|
declare const db: { users: { findUnique(opts: { where: { id: string } }): Promise<{ id: string; name: string }> } };
|
|
19
19
|
|
|
20
|
-
const getUser = cache(
|
|
20
|
+
const getUser = cache.data(
|
|
21
21
|
async (userId: string) => {
|
|
22
22
|
return db.users.findUnique({ where: { id: userId } });
|
|
23
23
|
},
|
|
@@ -41,9 +41,9 @@ const getUser = cache(
|
|
|
41
41
|
|
|
42
42
|
Default key: FNV-1a hash of function identity + deterministic JSON serialization of arguments.
|
|
43
43
|
|
|
44
|
-
The function identity is derived at build time from the source text of the `cache(...)` call, so it is stable across restarts, instances, and deploys. With a persistent shared handler (Redis/KV), cached entries survive deploys as long as the call itself is unchanged — reordering calls or editing unrelated code preserves the cache, while editing the wrapped function or its options gives the call a fresh identity (old entries simply expire via TTL). Renaming or moving the file also resets identity for every call in it.
|
|
44
|
+
The function identity is derived at build time from the source text of the `cache.data(...)` call, so it is stable across restarts, instances, and deploys. With a persistent shared handler (Redis/KV), cached entries survive deploys as long as the call itself is unchanged — reordering calls or editing unrelated code preserves the cache, while editing the wrapped function or its options gives the call a fresh identity (old entries simply expire via TTL). Renaming or moving the file also resets identity for every call in it.
|
|
45
45
|
|
|
46
|
-
Build-time identity covers calls the bundler can see statically: named imports (including aliases like `import { cache as c }`) and namespace imports (`import * as timber` + `timber.cache(fn, opts)`). Calls it can't trace — a local alias like `const c = cache`, or `cache` re-exported through your own wrapper module — fall back to a hash of the function's source text at runtime. The fallback is still deterministic across instances of the same build, but it resets across deploys, and byte-identical closures produced by a factory can collide — pass an explicit `key` in those cases (timber logs a warning when the fallback is used).
|
|
46
|
+
Build-time identity covers calls the bundler can see statically: named imports (including aliases like `import { cache as c }`) and namespace imports (`import * as timber` + `timber.cache.data(fn, opts)`). Calls it can't trace — a local alias like `const c = cache`, or `cache` re-exported through your own wrapper module — fall back to a hash of the function's source text at runtime. The fallback is still deterministic across instances of the same build, but it resets across deploys, and byte-identical closures produced by a factory can collide — pass an explicit `key` in those cases (timber logs a warning when the fallback is used).
|
|
47
47
|
|
|
48
48
|
### `cache.invalidate(options)`
|
|
49
49
|
|
|
@@ -23,6 +23,19 @@ export const searchParams = defineSearchParams({
|
|
|
23
23
|
});
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
### Implicit optionality
|
|
27
|
+
|
|
28
|
+
Search params are optional by nature — the URL might not contain them. A schema without `.default()` or `.optional()` (e.g. bare `z.string()`) is treated as implicitly optional: absent or invalid input parses to `undefined`, and the field's inferred type widens to `T | undefined`. Definitions never throw at module-eval time, and schema-backed parsing never throws at request time.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const def = defineSearchParams({ name: z.string() });
|
|
32
|
+
def.parse(new URLSearchParams('')); // { name: undefined }
|
|
33
|
+
def.parse(new URLSearchParams('name=x')); // { name: 'x' }
|
|
34
|
+
// Inferred type: { name: string | undefined }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Note: `z.coerce.*` schemas declare input `unknown`, so a coerce schema without `.default()` keeps its narrow type even though absent input yields `undefined` at runtime — add `.default()` to coerce schemas for accurate types.
|
|
38
|
+
|
|
26
39
|
### Returns: `SearchParamsDefinition<T>`
|
|
27
40
|
|
|
28
41
|
| Method / Property | Description |
|
|
@@ -61,8 +61,7 @@ export const searchParams = defineSearchParams({
|
|
|
61
61
|
const sp = searchParams.get();
|
|
62
62
|
|
|
63
63
|
// or on the client
|
|
64
|
-
|
|
65
|
-
const [sp, setSearchParams] = searchParams.useQueryState();
|
|
64
|
+
const [sp, setSearchParams] = searchParams.useQueryStates();
|
|
66
65
|
```
|
|
67
66
|
|
|
68
67
|
### Typed Routes
|
|
@@ -36,7 +36,7 @@ export const getUser = cache(async (id: string) => {
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
// Caches across requests for 60 seconds
|
|
39
|
-
export const getPopularProducts = timberCache(
|
|
39
|
+
export const getPopularProducts = timberCache.data(
|
|
40
40
|
async () => {
|
|
41
41
|
return db.products.findPopular();
|
|
42
42
|
},
|
|
@@ -44,7 +44,7 @@ export const getPopularProducts = timberCache(
|
|
|
44
44
|
);
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
## `
|
|
47
|
+
## `cache.data()` Options
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
50
|
import { cache } from '@timber-js/app/cache';
|
|
@@ -53,7 +53,7 @@ declare const db: {
|
|
|
53
53
|
users: { findUnique(opts: { where: { id: string } }): Promise<{ id: string; name: string }> };
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
const getUser = cache(
|
|
56
|
+
const getUser = cache.data(
|
|
57
57
|
async (userId: string) => {
|
|
58
58
|
return db.users.findUnique({ where: { id: userId } });
|
|
59
59
|
},
|
|
@@ -74,7 +74,7 @@ Without an explicit `key`, the cache key is derived from the call site plus a de
|
|
|
74
74
|
|
|
75
75
|
### Content-Derived Cache Identities
|
|
76
76
|
|
|
77
|
-
The call-site identity is content-based — derived from the source code of the `cache()` call, not its position in the file. This means:
|
|
77
|
+
The call-site identity is content-based — derived from the source code of the `cache.data()` call, not its position in the file. This means:
|
|
78
78
|
|
|
79
79
|
- **Unchanged code = cache survives deploys.** If you redeploy without changing a cached function, its entries remain valid on persistent handlers like Redis or KV.
|
|
80
80
|
- **Changed code = automatic invalidation.** Editing the wrapped function or its options changes the cache identity, which invalidates old entries without manual cache busting.
|
|
@@ -86,7 +86,7 @@ Object key order doesn't matter — `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` produc
|
|
|
86
86
|
Arguments that can't be serialized faithfully — functions, symbols, or class instances whose data lives behind getters with no `toJSON()` — throw a `TypeError` instead of silently colliding. If you hit this, pass an explicit `key`:
|
|
87
87
|
|
|
88
88
|
```ts
|
|
89
|
-
cache(fn, { ttl: 60, key: (session) => `events:${session.userId}` });
|
|
89
|
+
cache.data(fn, { ttl: 60, key: (session) => `events:${session.userId}` });
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
## Invalidation
|
|
@@ -80,14 +80,14 @@ export const searchParams = defineSearchParams({
|
|
|
80
80
|
|
|
81
81
|
**Next.js:** Implicit fetch caching, `unstable_cache`, ISR with `revalidate`.
|
|
82
82
|
|
|
83
|
-
**timber:** No implicit caching. No ISR. Explicit `
|
|
83
|
+
**timber:** No implicit caching. No ISR. Explicit `cache.data()` with TTL and tags:
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
86
|
import { cache } from '@timber-js/app/cache';
|
|
87
87
|
|
|
88
88
|
declare const db: { products: { findPopular(): Promise<{ id: string; name: string }[]> } };
|
|
89
89
|
|
|
90
|
-
const getProducts = cache(
|
|
90
|
+
const getProducts = cache.data(
|
|
91
91
|
async () => db.products.findPopular(),
|
|
92
92
|
{ ttl: 300, tags: ['products'] }
|
|
93
93
|
);
|
|
@@ -53,8 +53,8 @@ These are the things you will get wrong if you assume Next.js behavior.
|
|
|
53
53
|
| Concept | Next.js | timber.js |
|
|
54
54
|
| --- | --- | --- |
|
|
55
55
|
| Default component type | Client components need `'use client'` | **All components are server components** by default. Only add `'use client'` when you need browser APIs or hooks. |
|
|
56
|
-
| Fetch caching | `fetch()` is patched with implicit caching | `fetch()` is **never patched**. Use `cache()` from `@timber-js/app/cache` explicitly. |
|
|
57
|
-
| ISR | `revalidate` option | **Does not exist.** Use `cache()` with TTL and tags. |
|
|
56
|
+
| Fetch caching | `fetch()` is patched with implicit caching | `fetch()` is **never patched**. Use `cache.data()` from `@timber-js/app/cache` explicitly. |
|
|
57
|
+
| ISR | `revalidate` option | **Does not exist.** Use `cache.data()` with TTL and tags. |
|
|
58
58
|
| `loading.tsx` | Convention for auto-Suspense | **Does not exist.** Use `<Suspense>` explicitly. |
|
|
59
59
|
| Middleware | Single global `middleware.ts` with matchers | Per-segment `middleware.ts` files + global `proxy.ts`. One-arg signature: `middleware(ctx)`. |
|
|
60
60
|
| `notFound()` | `notFound()` from `next/navigation` | `deny(404)` from `@timber-js/app/server`. Sends a real HTTP 404. |
|
|
@@ -104,11 +104,11 @@ content-collections.ts # Collection definitions (optional)
|
|
|
104
104
|
|
|
105
105
|
2. **Adding `'use client'` to everything** — components are server components by default. Only add `'use client'` when you need browser APIs, event handlers, or React hooks like `useState`/`useEffect`.
|
|
106
106
|
|
|
107
|
-
3. **Using `fetch()` and expecting caching** — timber never patches `fetch`. Wrap data-fetching functions in `cache()` from `@timber-js/app/cache` when you want caching.
|
|
107
|
+
3. **Using `fetch()` and expecting caching** — timber never patches `fetch`. Wrap data-fetching functions in `cache.data()` from `@timber-js/app/cache` when you want caching.
|
|
108
108
|
|
|
109
109
|
4. **Creating `loading.tsx` files** — this convention does not exist. Use `<Suspense>` with an explicit fallback where you want streaming.
|
|
110
110
|
|
|
111
|
-
5. **Using ISR patterns** (`revalidate`, `unstable_cache`) — ISR does not exist. Use `cache()` with `{ ttl: seconds, tags: ['tag'] }`.
|
|
111
|
+
5. **Using ISR patterns** (`revalidate`, `unstable_cache`) — ISR does not exist. Use `cache.data()` with `{ ttl: seconds, tags: ['tag'] }`.
|
|
112
112
|
|
|
113
113
|
6. **Calling `notFound()`** — use `deny(404)` from `@timber-js/app/server` instead. It sends a real HTTP 404 status code.
|
|
114
114
|
|
|
@@ -123,7 +123,7 @@ content-collections.ts # Collection definitions (optional)
|
|
|
123
123
|
```tsx
|
|
124
124
|
import { cache } from '@timber-js/app/cache';
|
|
125
125
|
|
|
126
|
-
const getProducts = cache(
|
|
126
|
+
const getProducts = cache.data(
|
|
127
127
|
async () => db.products.findMany(),
|
|
128
128
|
{ ttl: 300, tags: ['products'] }
|
|
129
129
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.167",
|
|
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",
|
|
@@ -150,6 +150,12 @@
|
|
|
150
150
|
"publishConfig": {
|
|
151
151
|
"access": "public"
|
|
152
152
|
},
|
|
153
|
+
"scripts": {
|
|
154
|
+
"copy-docs": "node scripts/copy-docs.js",
|
|
155
|
+
"build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist && pnpm run copy-docs",
|
|
156
|
+
"typecheck": "tsgo --noEmit",
|
|
157
|
+
"prepublishOnly": "pnpm run build"
|
|
158
|
+
},
|
|
153
159
|
"dependencies": {
|
|
154
160
|
"@opentelemetry/api": "^1.9.1",
|
|
155
161
|
"@opentelemetry/context-async-hooks": "^2.8.0",
|
|
@@ -157,7 +163,8 @@
|
|
|
157
163
|
"cookie": "^1.1.1",
|
|
158
164
|
"magic-string": "^0.30.21",
|
|
159
165
|
"nitro": "3.0.260610-beta",
|
|
160
|
-
"srvx": "^0.11.17"
|
|
166
|
+
"srvx": "^0.11.17",
|
|
167
|
+
"jsonc-parser": "^3.3.1"
|
|
161
168
|
},
|
|
162
169
|
"peerDependencies": {
|
|
163
170
|
"@content-collections/core": "^0.14.0 || ^0.15.0",
|
|
@@ -195,10 +202,5 @@
|
|
|
195
202
|
},
|
|
196
203
|
"engines": {
|
|
197
204
|
"node": ">=22.18.0"
|
|
198
|
-
},
|
|
199
|
-
"scripts": {
|
|
200
|
-
"copy-docs": "node scripts/copy-docs.js",
|
|
201
|
-
"build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist && pnpm run copy-docs",
|
|
202
|
-
"typecheck": "tsgo --noEmit"
|
|
203
205
|
}
|
|
204
|
-
}
|
|
206
|
+
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// Primary deployment target. Generates a Workers-compatible entry point
|
|
4
4
|
// and wrangler.jsonc configuration. See design/11-platform.md §"Cloudflare Workers".
|
|
5
5
|
|
|
6
|
-
import { writeFile, readFile, cp, rm, readdir } from 'node:fs/promises';
|
|
6
|
+
import { writeFile, readFile, cp, rm, readdir, access } from 'node:fs/promises';
|
|
7
7
|
import { execFile } from 'node:child_process';
|
|
8
8
|
import { join, relative } from 'node:path';
|
|
9
|
+
import { parse as parseJsonc } from 'jsonc-parser';
|
|
9
10
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
10
11
|
import type { TimberPlatformAdapter, TimberConfig } from './types';
|
|
11
12
|
import { runSharedBuildSteps } from './build-output-helper.js';
|
|
@@ -158,6 +159,12 @@ export interface CloudflareBindings {
|
|
|
158
159
|
|
|
159
160
|
/** Options for the Cloudflare Workers adapter. */
|
|
160
161
|
export interface CloudflareAdapterOptions {
|
|
162
|
+
/**
|
|
163
|
+
* Worker name used in the generated wrangler.jsonc.
|
|
164
|
+
* @default 'timber-app'
|
|
165
|
+
*/
|
|
166
|
+
name?: string;
|
|
167
|
+
|
|
161
168
|
/**
|
|
162
169
|
* Cloudflare compatibility date.
|
|
163
170
|
* @default Current date in YYYY-MM-DD format at build time.
|
|
@@ -291,8 +298,10 @@ export function cloudflare(options: CloudflareAdapterOptions = {}): TimberPlatfo
|
|
|
291
298
|
const workerEntry = generateWorkerEntry(outDir, outDir, hasManifestInit, hasWorkerHandlers);
|
|
292
299
|
await writeFile(join(outDir, '_worker.js'), workerEntry);
|
|
293
300
|
|
|
294
|
-
// Generate wrangler.jsonc
|
|
295
|
-
const
|
|
301
|
+
// Generate wrangler.jsonc — merges user's wrangler config from disk if present
|
|
302
|
+
const root = config.root ?? process.cwd();
|
|
303
|
+
const userConfig = await readUserWranglerConfig(root);
|
|
304
|
+
const wranglerConfig = generateWranglerConfig(config, options, userConfig);
|
|
296
305
|
await writeFile(join(outDir, 'wrangler.jsonc'), JSON.stringify(wranglerConfig, null, 2));
|
|
297
306
|
},
|
|
298
307
|
|
|
@@ -682,22 +691,40 @@ function isPlainObject(val: unknown): val is Record<string, unknown> {
|
|
|
682
691
|
return val !== null && typeof val === 'object' && !Array.isArray(val);
|
|
683
692
|
}
|
|
684
693
|
|
|
694
|
+
const WRANGLER_CONFIG_FILES = ['wrangler.jsonc', 'wrangler.json'] as const;
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Read the user's wrangler config from the project root, if one exists.
|
|
698
|
+
* Checks wrangler.jsonc first, then wrangler.json.
|
|
699
|
+
* @internal Exported for testing.
|
|
700
|
+
*/
|
|
701
|
+
export async function readUserWranglerConfig(
|
|
702
|
+
root: string
|
|
703
|
+
): Promise<Record<string, unknown> | null> {
|
|
704
|
+
for (const filename of WRANGLER_CONFIG_FILES) {
|
|
705
|
+
const filepath = join(root, filename);
|
|
706
|
+
try {
|
|
707
|
+
await access(filepath);
|
|
708
|
+
} catch {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
const content = await readFile(filepath, 'utf-8');
|
|
712
|
+
return parseJsonc(content) as Record<string, unknown>;
|
|
713
|
+
}
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
|
|
685
717
|
/** @internal Exported for testing. */
|
|
686
718
|
export function generateWranglerConfig(
|
|
687
719
|
config: TimberConfig,
|
|
688
|
-
options: CloudflareAdapterOptions
|
|
720
|
+
options: CloudflareAdapterOptions,
|
|
721
|
+
userConfig?: Record<string, unknown> | null
|
|
689
722
|
): Record<string, unknown> {
|
|
690
|
-
const compatDate = options.compatibilityDate ?? new Date().toISOString().slice(0, 10);
|
|
691
|
-
|
|
692
|
-
const flags = options.compatibilityFlags ?? ['nodejs_compat'];
|
|
693
|
-
|
|
694
723
|
const base: Record<string, unknown> = {
|
|
695
724
|
name: 'timber-app',
|
|
696
725
|
main: '_worker.js',
|
|
697
|
-
compatibility_date:
|
|
698
|
-
compatibility_flags:
|
|
699
|
-
// The build output is already fully bundled by Vite — skip wrangler's
|
|
700
|
-
// esbuild pass to avoid issues with top-level await and module format.
|
|
726
|
+
compatibility_date: new Date().toISOString().slice(0, 10),
|
|
727
|
+
compatibility_flags: ['nodejs_compat'],
|
|
701
728
|
no_bundle: true,
|
|
702
729
|
find_additional_modules: true,
|
|
703
730
|
rules: [{ type: 'ESModule', globs: ['**/*.js'] }],
|
|
@@ -705,29 +732,40 @@ export function generateWranglerConfig(
|
|
|
705
732
|
directory: './static',
|
|
706
733
|
binding: 'ASSETS',
|
|
707
734
|
},
|
|
708
|
-
// Native trace destination — required for timber's pipeline spans
|
|
709
|
-
// (emitted via tracing.enterSpan in the generated _worker.js) to appear
|
|
710
|
-
// in the Cloudflare Observability dashboard. Gates traces only; does
|
|
711
|
-
// not enable Workers Logs. Override via the `wrangler` escape hatch:
|
|
712
|
-
// wrangler: { observability: { traces: { enabled: false } } }.
|
|
713
735
|
observability: {
|
|
714
736
|
traces: { enabled: true },
|
|
715
737
|
},
|
|
716
738
|
};
|
|
717
739
|
|
|
718
|
-
// Layer 1:
|
|
740
|
+
// Layer 1: user's wrangler.jsonc / wrangler.json from disk
|
|
741
|
+
let merged: Record<string, unknown> = userConfig ? shallowDeepMerge(base, userConfig) : base;
|
|
742
|
+
|
|
743
|
+
// Layer 2: explicit adapter options override disk config
|
|
744
|
+
const explicit: Record<string, unknown> = {};
|
|
745
|
+
if (options.name != null) explicit.name = options.name;
|
|
746
|
+
if (options.compatibilityDate != null) explicit.compatibility_date = options.compatibilityDate;
|
|
747
|
+
if (options.compatibilityFlags != null) explicit.compatibility_flags = options.compatibilityFlags;
|
|
748
|
+
if (Object.keys(explicit).length > 0) {
|
|
749
|
+
merged = { ...merged, ...explicit };
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Layer 3: declarative bindings
|
|
719
753
|
const bindingsConfig = generateBindingsConfig(options.bindings);
|
|
720
|
-
|
|
754
|
+
merged = shallowDeepMerge(merged, bindingsConfig as Record<string, unknown>);
|
|
721
755
|
|
|
722
|
-
// Layer
|
|
723
|
-
// A shallow spread would replace entire sections like durable_objects and
|
|
724
|
-
// queues — e.g. adding migrations via wrangler would drop the generated
|
|
725
|
-
// durable_objects.bindings. Deep merge preserves generated keys while
|
|
726
|
-
// letting wrangler override on actual key-level conflicts.
|
|
756
|
+
// Layer 4: wrangler escape hatch — highest priority, deep merge.
|
|
727
757
|
if (options.wrangler) {
|
|
728
|
-
|
|
758
|
+
merged = shallowDeepMerge(merged, options.wrangler);
|
|
729
759
|
}
|
|
730
760
|
|
|
761
|
+
// Generated config targets a single environment — env blocks can override
|
|
762
|
+
// main/assets per-environment and break the build output.
|
|
763
|
+
delete merged.env;
|
|
764
|
+
|
|
765
|
+
// These fields are required for the build output to work correctly.
|
|
766
|
+
merged.main = '_worker.js';
|
|
767
|
+
merged.find_additional_modules = true;
|
|
768
|
+
|
|
731
769
|
return merged;
|
|
732
770
|
}
|
|
733
771
|
|
package/src/adapters/types.ts
CHANGED
package/src/cache/cache-api.ts
CHANGED
|
@@ -6,102 +6,102 @@ import { recordInvalidation } from './invalidation-epoch';
|
|
|
6
6
|
import { writeTombstonesForTag } from '../server/prebuilt/overlay.js';
|
|
7
7
|
import { getCdnPurgeHandler } from '../cdn/purge-store.js';
|
|
8
8
|
|
|
9
|
+
const componentFallbackWarned = new WeakSet<object>();
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Wraps an async function with cross-request caching. Uses the configured
|
|
13
|
-
* cache handler (defaults to MemoryCacheHandler, overridable via timber.config.ts).
|
|
12
|
+
* Cache namespace — `cache.data`, `cache.component`, `cache.invalidate`.
|
|
14
13
|
*
|
|
15
14
|
* ```ts
|
|
16
15
|
* import { cache } from '@timber-js/app/cache';
|
|
17
16
|
*
|
|
18
|
-
* const getUser = cache(
|
|
17
|
+
* const getUser = cache.data(
|
|
19
18
|
* async (id: string) => db.users.findUnique({ where: { id } }),
|
|
20
19
|
* { ttl: 60, tags: (id) => [`user:${id}`] }
|
|
21
20
|
* );
|
|
22
21
|
* ```
|
|
23
22
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
export const cache = {
|
|
24
|
+
/**
|
|
25
|
+
* Wrap an async function with cross-request caching.
|
|
26
|
+
*
|
|
27
|
+
* The configured cache handler (defaults to MemoryCacheHandler, overridable
|
|
28
|
+
* via timber.config.ts) is re-resolved on every call, so module-scope
|
|
29
|
+
* wrappers created before `setCacheHandler()` still pick up the configured
|
|
30
|
+
* handler (TIM-1029).
|
|
31
|
+
*/
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
data<Fn extends (...args: any[]) => Promise<any>>(
|
|
34
|
+
fn: Fn,
|
|
35
|
+
opts: CacheOptions<Fn>,
|
|
36
|
+
stableId?: string
|
|
37
|
+
): Fn {
|
|
38
|
+
return createCache(fn, opts, getCacheHandler, stableId);
|
|
39
|
+
},
|
|
36
40
|
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Invalidate cache entries by tag or key.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* cache.invalidate({ tag: 'products' });
|
|
46
|
+
* cache.invalidate({ key: 'user:abc' });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
async invalidate(opts: { key?: string; tag?: string }): Promise<void> {
|
|
50
|
+
// Record before the handler delete so a cached fn in flight skips its
|
|
51
|
+
// post-resolve set instead of resurrecting the invalidated value (TIM-1028).
|
|
52
|
+
recordInvalidation(opts);
|
|
53
|
+
await getCacheHandler().invalidate(opts);
|
|
54
|
+
// Write tombstones for build-seed entries carrying the tag (design/45
|
|
55
|
+
// §ISR mechanics). Runs AFTER handler.invalidate so the tombstone
|
|
56
|
+
// overwrites the cleared overlay slot. This covers ALL invalidation
|
|
57
|
+
// paths — actions, webhooks, route handlers — not just executeAction.
|
|
58
|
+
if (opts.tag) {
|
|
59
|
+
await writeTombstonesForTag(opts.tag);
|
|
60
|
+
}
|
|
61
|
+
// Trigger CDN purge if a handler is configured (design/25 §Layer 2).
|
|
62
|
+
// Purge is best-effort: a CDN API failure or timeout must not block
|
|
63
|
+
// cache invalidation. 10s ceiling prevents a hung CDN API from stalling
|
|
64
|
+
// the entire revalidateTag/action flow.
|
|
65
|
+
if (opts.tag) {
|
|
66
|
+
const purgeHandler = getCdnPurgeHandler();
|
|
67
|
+
if (purgeHandler) {
|
|
68
|
+
try {
|
|
69
|
+
await Promise.race([
|
|
70
|
+
purgeHandler.purgeTags([opts.tag]),
|
|
71
|
+
new Promise<void>((_, reject) =>
|
|
72
|
+
setTimeout(() => reject(new Error('CDN purge timed out (10s)')), 10_000)
|
|
73
|
+
),
|
|
74
|
+
]);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error('[timber] CDN purge failed for tag:', err);
|
|
77
|
+
}
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const componentFallbackWarned = new WeakSet<object>();
|
|
80
|
+
},
|
|
79
81
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
return componentFn;
|
|
82
|
+
/**
|
|
83
|
+
* Runtime fallback for `cache.component(...)` callsites the timber-prebuilt
|
|
84
|
+
* transform does not rewrite — nested/computed/argument-position forms (see
|
|
85
|
+
* plugins/prebuilt.ts). Only module-scope `const X = cache.component(...)`
|
|
86
|
+
* declarations are transformed and eligible for prebuilding; everything else
|
|
87
|
+
* lands here, warns once per component, and renders dynamically. Same
|
|
88
|
+
* safety-net pattern as the data cache's runtime fnId fallback (TIM-1054):
|
|
89
|
+
* an untraceable callsite degrades in performance, never in correctness.
|
|
90
|
+
*/
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
component<C extends (props: any) => unknown>(
|
|
93
|
+
componentFn: C,
|
|
94
|
+
_options?: PrebuiltComponentOptions
|
|
95
|
+
): C {
|
|
96
|
+
if (!componentFallbackWarned.has(componentFn)) {
|
|
97
|
+
componentFallbackWarned.add(componentFn);
|
|
98
|
+
const name = componentFn.name || 'anonymous component';
|
|
99
|
+
console.warn(
|
|
100
|
+
`[timber] cache.component: "${name}" was not transformed at build time — ` +
|
|
101
|
+
`rendering dynamically. Only module-scope declarations ` +
|
|
102
|
+
`(const X = cache.component(...)) are prebuilt.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return componentFn;
|
|
106
|
+
},
|
|
107
107
|
};
|
package/src/cli.ts
CHANGED
|
File without changes
|