@timber-js/app 0.2.0-alpha.6 → 0.2.0-alpha.8
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/{tracing-Cwn7697K.js → tracing-CemImE6h.js} +16 -2
- package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CemImE6h.js.map} +1 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/index.js +51 -9
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/register-cached-function.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/segment-context.d.ts +1 -1
- package/dist/client/segment-context.d.ts.map +1 -1
- package/dist/client/segment-merger.d.ts.map +1 -1
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/transition-root.d.ts +1 -1
- package/dist/client/transition-root.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/index.js +9 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/response-cache.d.ts +5 -4
- package/dist/server/response-cache.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.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 +6 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/waituntil-bridge.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/nitro.ts +6 -1
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/register-cached-function.ts +7 -3
- package/src/cache/timber-cache.ts +17 -10
- package/src/client/browser-entry.ts +10 -6
- package/src/client/link.tsx +14 -9
- package/src/client/router.ts +4 -6
- package/src/client/segment-context.ts +6 -1
- package/src/client/segment-merger.ts +2 -8
- package/src/client/stale-reload.ts +4 -6
- package/src/client/top-loader.tsx +8 -7
- package/src/client/transition-root.tsx +7 -1
- package/src/index.ts +2 -0
- package/src/plugins/entries.ts +1 -0
- package/src/plugins/fonts.ts +24 -17
- package/src/server/action-client.ts +7 -1
- package/src/server/pipeline.ts +7 -0
- package/src/server/response-cache.ts +169 -36
- package/src/server/route-element-builder.ts +1 -6
- package/src/server/rsc-entry/index.ts +9 -1
- package/src/server/rsc-entry/rsc-payload.ts +42 -10
- package/src/server/rsc-entry/rsc-stream.ts +9 -5
- package/src/server/rsc-entry/ssr-renderer.ts +11 -8
- package/src/server/tracing.ts +23 -0
- package/src/server/waituntil-bridge.ts +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAgC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,uDAAuD;IACvD,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;AAEnE,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,CACjC,QAAQ,EAAE,MAAM,KACb,OAAO,oBAAoB,EAAE,kBAAkB,GAAG,IAAI,CAAC;AAE5D,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,YAAY,CAAC,EAAE,mBAAmB,KAC/B,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElC,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAI1B,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACtD,qEAAqE;IACrE,UAAU,EAAE,YAAY,CAAC;IACzB,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,kEAAkE;IAClE,MAAM,EAAE,aAAa,CAAC;IACtB,kEAAkE;IAClE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzF,kFAAkF;IAClF,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yGAAyG;IACzG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,2BAA2B,EAAE,mBAAmB,EAAE,CAAC;IACjF;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,CACpB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC;AAID;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAgC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,uDAAuD;IACvD,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;AAEnE,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,CACjC,QAAQ,EAAE,MAAM,KACb,OAAO,oBAAoB,EAAE,kBAAkB,GAAG,IAAI,CAAC;AAE5D,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,YAAY,CAAC,EAAE,mBAAmB,KAC/B,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElC,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAI1B,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACtD,qEAAqE;IACrE,UAAU,EAAE,YAAY,CAAC;IACzB,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,kEAAkE;IAClE,MAAM,EAAE,aAAa,CAAC;IACtB,kEAAkE;IAClE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzF,kFAAkF;IAClF,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yGAAyG;IACzG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,2BAA2B,EAAE,mBAAmB,EAAE,CAAC;IACjF;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,CACpB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC;AAID;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAmW1F"}
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* re-executing the RSC-to-SSR pipeline. Entries have a short TTL
|
|
11
11
|
* (default 5s) and the cache has a bounded size (default 150 entries).
|
|
12
12
|
*
|
|
13
|
-
* Cache keys are compound:
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Cache keys are compound: pathname + search + isRscPayload + Vary'd headers.
|
|
14
|
+
* Only GET requests are cached. Responses with Set-Cookie, Cache-Control:
|
|
15
|
+
* no-store/private, or error/redirect status codes are never cached.
|
|
16
|
+
* When `publicOnly` is true (default), requests with Cookie or Authorization
|
|
17
|
+
* headers bypass the cache entirely.
|
|
17
18
|
*
|
|
18
19
|
* See design/02-rendering-pipeline.md, design/31-benchmarking.md.
|
|
19
20
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-cache.d.ts","sourceRoot":"","sources":["../../src/server/response-cache.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"response-cache.d.ts","sourceRoot":"","sources":["../../src/server/response-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,0BAA0B,CACxC,MAAM,CAAC,EAAE,mBAAmB,GAAG,KAAK,GACnC,2BAA2B,GAAG,IAAI,CASpC;AA+ED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,WAAW,CACT,GAAG,EAAE,OAAO,EACZ,YAAY,EAAE,OAAO,EACrB,QAAQ,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,GAChC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAErB,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,gCAAgC;IAChC,KAAK,IAAI,IAAI,CAAC;CACf;AAuDD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,2BAA2B,GAAG,aAAa,CAyMtF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-element-builder.d.ts","sourceRoot":"","sources":["../../src/server/route-element-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAO7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAKzD,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACvC;AAED,+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,2CAA2C;IAC3C,YAAY,EAAE,WAAW,EAAE,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;CAC3B;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,MAAM,EAAE,UAAU,GAAG,cAAc;aACnC,gBAAgB,EAAE,oBAAoB,EAAE;aACxC,QAAQ,EAAE,mBAAmB,EAAE;gBAF/B,MAAM,EAAE,UAAU,GAAG,cAAc,EACnC,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,QAAQ,EAAE,mBAAmB,EAAE;CAIlD;AAID;;;;;;;;;GASG;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,GACnC,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"route-element-builder.d.ts","sourceRoot":"","sources":["../../src/server/route-element-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAO7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAKzD,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACvC;AAED,+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,2CAA2C;IAC3C,YAAY,EAAE,WAAW,EAAE,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;CAC3B;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,MAAM,EAAE,UAAU,GAAG,cAAc;aACnC,gBAAgB,EAAE,oBAAoB,EAAE;aACxC,QAAQ,EAAE,mBAAmB,EAAE;gBAF/B,MAAM,EAAE,UAAU,GAAG,cAAc,EACnC,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,QAAQ,EAAE,mBAAmB,EAAE;CAIlD;AAID;;;;;;;;;GASG;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,GACnC,OAAO,CAAC,kBAAkB,CAAC,CAsW7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAgFA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAgFA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;AAiaD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BArQ3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AAuQhD,wBAAiE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rsc-payload.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC3F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAQrE,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,YAAY,EAAE,WAAW,EAAE,EAC3B,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;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC3F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAQrE,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,YAAY,EAAE,WAAW,EAAE,EAC3B,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,OAAO,CAAC,QAAQ,CAAC,CA0HnB"}
|
|
@@ -15,6 +15,10 @@ import { DenySignal, RedirectSignal } from '#/server/primitives.js';
|
|
|
15
15
|
*
|
|
16
16
|
* Signals fire asynchronously via `onError` during stream consumption.
|
|
17
17
|
* The first signal of each type wins — subsequent signals are ignored.
|
|
18
|
+
*
|
|
19
|
+
* `onSignal` is an optional callback fired when a DenySignal or
|
|
20
|
+
* RedirectSignal is captured. Consumers use it with Promise.race to
|
|
21
|
+
* react immediately instead of polling with setTimeout/queueMicrotask.
|
|
18
22
|
*/
|
|
19
23
|
export interface RenderSignals {
|
|
20
24
|
denySignal: DenySignal | null;
|
|
@@ -23,6 +27,8 @@ export interface RenderSignals {
|
|
|
23
27
|
error: unknown;
|
|
24
28
|
status: number;
|
|
25
29
|
} | null;
|
|
30
|
+
/** Callback fired when a redirect or deny signal is captured in onError. */
|
|
31
|
+
onSignal?: () => void;
|
|
26
32
|
}
|
|
27
33
|
export interface RscStreamResult {
|
|
28
34
|
rscStream: ReadableStream<Uint8Array> | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rsc-stream.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,wBAAwB,CAAC;AAMjF
|
|
1
|
+
{"version":3,"file":"rsc-stream.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,wBAAwB,CAAC;AAMjF;;;;;;;;;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,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;IAClD,OAAO,EAAE,aAAa,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,eAAe,CA4G1F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAYrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAYrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsKjF"}
|
package/dist/server/tracing.d.ts
CHANGED
|
@@ -84,6 +84,16 @@ export declare function setSpanAttribute(key: string, value: string | number | b
|
|
|
84
84
|
* Used for timber.cache HIT/MISS events — recorded as span events, not child spans.
|
|
85
85
|
*/
|
|
86
86
|
export declare function addSpanEvent(name: string, attributes?: Record<string, string | number | boolean>): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Fire-and-forget span event — no await, no microtask overhead.
|
|
89
|
+
*
|
|
90
|
+
* Used on the cache hot path where awaiting addSpanEvent creates an
|
|
91
|
+
* unnecessary microtask per cache operation. If OTEL is not loaded yet,
|
|
92
|
+
* the event is silently dropped (acceptable for diagnostics).
|
|
93
|
+
*
|
|
94
|
+
* See TIM-370 for perf motivation.
|
|
95
|
+
*/
|
|
96
|
+
export declare function addSpanEventSync(name: string, attributes?: Record<string, string | number | boolean>): void;
|
|
87
97
|
/**
|
|
88
98
|
* Try to extract the OTEL trace ID from the current active span context.
|
|
89
99
|
* Returns undefined if OTEL is not active or no span exists.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/server/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG9D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD;;;;;;;GAOG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAShC;AAED;;GAEG;AACH,wBAAgB,MAAM,IAAI,MAAM,GAAG,SAAS,CAE3C;AAID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAM3E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAKhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG,SAAS,CAEtD;AAID;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,OAAO,iBAAiB,EAAE,eAAe,GAChD,OAAO,CAAC,IAAI,CAAC,CA4Bf;AA4BD;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,OAAO,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC,CAUrF;AAED;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EACrD,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAC/B,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAc/F"}
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/server/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG9D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD;;;;;;;GAOG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAShC;AAED;;GAEG;AACH,wBAAgB,MAAM,IAAI,MAAM,GAAG,SAAS,CAE3C;AAID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAM3E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAKhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG,SAAS,CAEtD;AAID;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,OAAO,iBAAiB,EAAE,eAAe,GAChD,OAAO,CAAC,IAAI,CAAC,CA4Bf;AA4BD;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,OAAO,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC,CAUrF;AAED;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EACrD,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAC/B,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GACrD,IAAI,CASN;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAc/F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"waituntil-bridge.d.ts","sourceRoot":"","sources":["../../src/server/waituntil-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"waituntil-bridge.d.ts","sourceRoot":"","sources":["../../src/server/waituntil-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,EAChD,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAEH;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAEhF"}
|
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.8",
|
|
4
4
|
"description": "Vite-native React framework for Cloudflare Workers — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
package/src/adapters/nitro.ts
CHANGED
|
@@ -611,7 +611,12 @@ async function runNitroBuild(
|
|
|
611
611
|
userConfig?: Record<string, unknown>
|
|
612
612
|
): Promise<void> {
|
|
613
613
|
const presetConfig = PRESET_CONFIGS[preset];
|
|
614
|
-
const {
|
|
614
|
+
const {
|
|
615
|
+
createNitro,
|
|
616
|
+
build: nitroBuild,
|
|
617
|
+
prepare,
|
|
618
|
+
copyPublicAssets,
|
|
619
|
+
} = await import('nitro/builder');
|
|
615
620
|
|
|
616
621
|
const nitro = await createNitro({
|
|
617
622
|
rootDir: nitroDir,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fast non-cryptographic hash for cache keys.
|
|
3
|
+
*
|
|
4
|
+
* FNV-1a 64-bit produces a well-distributed hash with a collision
|
|
5
|
+
* probability of ~1 in 5 billion at 77k keys (birthday paradox).
|
|
6
|
+
* Not suitable for security, but ideal for cache key generation
|
|
7
|
+
* where we need speed over crypto strength.
|
|
8
|
+
*
|
|
9
|
+
* Uses BigInt for 64-bit arithmetic — supported in all modern runtimes
|
|
10
|
+
* including Cloudflare Workers. No node:crypto dependency.
|
|
11
|
+
*
|
|
12
|
+
* See TIM-370.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// FNV-1a constants for 64-bit hash
|
|
16
|
+
const FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
|
|
17
|
+
const FNV_PRIME = 0x100000001b3n;
|
|
18
|
+
const MASK_64 = 0xffffffffffffffffn;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Compute a 64-bit FNV-1a hash of a string, returned as a 16-char hex string.
|
|
22
|
+
*
|
|
23
|
+
* 64 bits gives ~5 billion keys before a 50% collision probability
|
|
24
|
+
* (birthday paradox), making accidental collisions effectively impossible
|
|
25
|
+
* for cache key use cases.
|
|
26
|
+
*/
|
|
27
|
+
export function fnv1aHash(input: string): string {
|
|
28
|
+
let hash = FNV_OFFSET_BASIS;
|
|
29
|
+
for (let i = 0; i < input.length; i++) {
|
|
30
|
+
hash ^= BigInt(input.charCodeAt(i));
|
|
31
|
+
hash = (hash * FNV_PRIME) & MASK_64;
|
|
32
|
+
}
|
|
33
|
+
return hash.toString(16).padStart(16, '0');
|
|
34
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import type { CacheHandler } from './index';
|
|
3
2
|
import { stableStringify } from './stable-stringify';
|
|
4
3
|
import { createSingleflight } from './singleflight';
|
|
4
|
+
import { fnv1aHash } from './fast-hash.js';
|
|
5
5
|
|
|
6
6
|
const singleflight = createSingleflight();
|
|
7
7
|
|
|
@@ -27,11 +27,15 @@ export interface RegisterCachedFunctionOptions<Fn extends (...args: any[]) => an
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Generate a
|
|
30
|
+
* Generate a cache key from a stable function ID and serialized args.
|
|
31
|
+
*
|
|
32
|
+
* Uses FNV-1a (fast non-crypto hash) instead of SHA-256. The id prefix
|
|
33
|
+
* provides namespace isolation; the hash covers the args.
|
|
34
|
+
* See TIM-370.
|
|
31
35
|
*/
|
|
32
36
|
function generateKey(id: string, args: unknown[]): string {
|
|
33
37
|
const raw = id + ':' + stableStringify(args);
|
|
34
|
-
return
|
|
38
|
+
return id + ':' + fnv1aHash(raw);
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
/**
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import type { CacheHandler, CacheOptions } from './index';
|
|
3
2
|
import { stableStringify } from './stable-stringify';
|
|
4
3
|
import { createSingleflight } from './singleflight';
|
|
5
|
-
import {
|
|
4
|
+
import { addSpanEventSync } from '#/server/tracing.js';
|
|
5
|
+
import { fnv1aHash } from './fast-hash.js';
|
|
6
6
|
|
|
7
7
|
const singleflight = createSingleflight();
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Generate a
|
|
10
|
+
* Generate a cache key from function identity and serialized args.
|
|
11
|
+
*
|
|
12
|
+
* Uses FNV-1a (fast non-crypto hash) instead of SHA-256. Cache keys don't
|
|
13
|
+
* need collision resistance — they need speed. The fnId prefix provides
|
|
14
|
+
* namespace isolation; the hash covers the args.
|
|
15
|
+
*
|
|
16
|
+
* See TIM-370 for perf motivation.
|
|
11
17
|
*/
|
|
12
18
|
function defaultKeyGenerator(fnId: string, args: unknown[]): string {
|
|
13
19
|
const raw = fnId + ':' + stableStringify(args);
|
|
14
|
-
return
|
|
20
|
+
return fnId + ':' + fnv1aHash(raw);
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
/**
|
|
@@ -57,8 +63,9 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
|
|
|
57
63
|
const cached = await handler.get(key);
|
|
58
64
|
|
|
59
65
|
if (cached && !cached.stale) {
|
|
60
|
-
// Record as OTEL span event on enclosing span (not a child span)
|
|
61
|
-
|
|
66
|
+
// Record as OTEL span event on enclosing span (not a child span).
|
|
67
|
+
// Fire-and-forget — no microtask overhead on the cache hot path.
|
|
68
|
+
addSpanEventSync('timber.cache.hit', {
|
|
62
69
|
key,
|
|
63
70
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
64
71
|
});
|
|
@@ -66,8 +73,8 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
|
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
if (cached && cached.stale && opts.staleWhileRevalidate) {
|
|
69
|
-
// Record stale cache hit as OTEL span event
|
|
70
|
-
|
|
76
|
+
// Record stale cache hit as OTEL span event (fire-and-forget).
|
|
77
|
+
addSpanEventSync('timber.cache.hit', {
|
|
71
78
|
key,
|
|
72
79
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
73
80
|
stale: true,
|
|
@@ -95,8 +102,8 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
|
|
|
95
102
|
const tags = resolveTags(opts, args);
|
|
96
103
|
await handler.set(key, result, { ttl: opts.ttl, tags });
|
|
97
104
|
|
|
98
|
-
// Record cache miss as OTEL span event
|
|
99
|
-
|
|
105
|
+
// Record cache miss as OTEL span event (fire-and-forget).
|
|
106
|
+
addSpanEventSync('timber.cache.miss', {
|
|
100
107
|
key,
|
|
101
108
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
102
109
|
});
|
|
@@ -52,7 +52,11 @@ import { setupServerLogReplay, setupClientErrorForwarding } from './browser-dev.
|
|
|
52
52
|
// browser-links.ts removed — Link components own their click/hover handlers directly.
|
|
53
53
|
// See LOCAL-340.
|
|
54
54
|
import { TransitionRoot, transitionRender, navigateTransition } from './transition-root.js';
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
isStaleClientReference,
|
|
57
|
+
triggerStaleReload,
|
|
58
|
+
clearStaleReloadFlag,
|
|
59
|
+
} from './stale-reload.js';
|
|
56
60
|
|
|
57
61
|
// ─── Server Action Dispatch ──────────────────────────────────────
|
|
58
62
|
|
|
@@ -213,10 +217,7 @@ function findOverflowContainer(): HTMLElement | null {
|
|
|
213
217
|
if (!(el instanceof HTMLElement)) continue;
|
|
214
218
|
const style = getComputedStyle(el);
|
|
215
219
|
const overflowY = style.overflowY;
|
|
216
|
-
if (
|
|
217
|
-
(overflowY === 'auto' || overflowY === 'scroll') &&
|
|
218
|
-
el.scrollHeight > el.clientHeight
|
|
219
|
-
) {
|
|
220
|
+
if ((overflowY === 'auto' || overflowY === 'scroll') && el.scrollHeight > el.clientHeight) {
|
|
220
221
|
return el;
|
|
221
222
|
}
|
|
222
223
|
}
|
|
@@ -381,7 +382,10 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
381
382
|
element as React.ReactNode
|
|
382
383
|
);
|
|
383
384
|
const wrapped = createElement(TimberNuqsAdapter, null, withNav);
|
|
384
|
-
const rootElement = createElement(TransitionRoot, {
|
|
385
|
+
const rootElement = createElement(TransitionRoot, {
|
|
386
|
+
initial: wrapped,
|
|
387
|
+
topLoaderConfig: _config.topLoader,
|
|
388
|
+
});
|
|
385
389
|
_reactRoot = hydrateRoot(document, rootElement, {
|
|
386
390
|
// Suppress recoverable hydration errors from deny/error signals
|
|
387
391
|
// inside Suspense boundaries. The server already handled these
|
package/src/client/link.tsx
CHANGED
|
@@ -335,7 +335,11 @@ export function Link({
|
|
|
335
335
|
// Call onNavigate if provided — allows caller to cancel
|
|
336
336
|
if (onNavigate) {
|
|
337
337
|
let prevented = false;
|
|
338
|
-
onNavigate({
|
|
338
|
+
onNavigate({
|
|
339
|
+
preventDefault: () => {
|
|
340
|
+
prevented = true;
|
|
341
|
+
},
|
|
342
|
+
});
|
|
339
343
|
if (prevented) {
|
|
340
344
|
event.preventDefault();
|
|
341
345
|
return;
|
|
@@ -352,15 +356,16 @@ export function Link({
|
|
|
352
356
|
: userOnClick; // External links — just pass through user's onClick
|
|
353
357
|
|
|
354
358
|
// ─── Hover prefetch ──────────────────────────────────────────
|
|
355
|
-
const handleMouseEnter =
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
router
|
|
359
|
+
const handleMouseEnter =
|
|
360
|
+
internal && prefetch
|
|
361
|
+
? (event: ReactMouseEvent<HTMLAnchorElement>) => {
|
|
362
|
+
userOnMouseEnter?.(event);
|
|
363
|
+
const router = getRouterOrNull();
|
|
364
|
+
if (router) {
|
|
365
|
+
router.prefetch(resolvedHref);
|
|
366
|
+
}
|
|
361
367
|
}
|
|
362
|
-
|
|
363
|
-
: userOnMouseEnter;
|
|
368
|
+
: userOnMouseEnter;
|
|
364
369
|
|
|
365
370
|
return (
|
|
366
371
|
<a {...rest} href={resolvedHref} onClick={handleClick} onMouseEnter={handleMouseEnter}>
|
package/src/client/router.ts
CHANGED
|
@@ -7,11 +7,7 @@ import { HistoryStack } from './history';
|
|
|
7
7
|
import type { HeadElement } from './head';
|
|
8
8
|
import { setCurrentParams } from './use-params.js';
|
|
9
9
|
import { setNavigationState } from './navigation-context.js';
|
|
10
|
-
import {
|
|
11
|
-
SegmentElementCache,
|
|
12
|
-
cacheSegmentElements,
|
|
13
|
-
mergeSegmentTree,
|
|
14
|
-
} from './segment-merger.js';
|
|
10
|
+
import { SegmentElementCache, cacheSegmentElements, mergeSegmentTree } from './segment-merger.js';
|
|
15
11
|
import { fetchRscPayload, RedirectError } from './rsc-fetch.js';
|
|
16
12
|
import type { FetchResult } from './rsc-fetch.js';
|
|
17
13
|
|
|
@@ -421,7 +417,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
421
417
|
setPending(true, url);
|
|
422
418
|
try {
|
|
423
419
|
const headElements = await renderViaTransition(url, async () => {
|
|
424
|
-
const stateTree = segmentCache.serializeStateTree(
|
|
420
|
+
const stateTree = segmentCache.serializeStateTree(
|
|
421
|
+
segmentElementCache.getMergeablePaths()
|
|
422
|
+
);
|
|
425
423
|
const result = await fetchRscPayload(url, deps, stateTree);
|
|
426
424
|
updateSegmentCache(result.segmentInfo);
|
|
427
425
|
updateNavigationState(result.params, url);
|
|
@@ -52,7 +52,12 @@ interface SegmentProviderProps {
|
|
|
52
52
|
* Wraps each layout to provide segment position context.
|
|
53
53
|
* Injected by rsc-entry.ts during element tree construction.
|
|
54
54
|
*/
|
|
55
|
-
export function SegmentProvider({
|
|
55
|
+
export function SegmentProvider({
|
|
56
|
+
segments,
|
|
57
|
+
segmentId: _segmentId,
|
|
58
|
+
parallelRouteKeys,
|
|
59
|
+
children,
|
|
60
|
+
}: SegmentProviderProps) {
|
|
56
61
|
const value = useMemo(
|
|
57
62
|
() => ({ segments, parallelRouteKeys }),
|
|
58
63
|
// segments and parallelRouteKeys are static per layout — they don't change
|
|
@@ -186,10 +186,7 @@ function walkChildren(children: ReactNode, out: CachedSegmentEntry[]): void {
|
|
|
186
186
|
* Cache all segment subtrees from a fully-rendered RSC element tree.
|
|
187
187
|
* Call this after every full RSC payload render (navigate, refresh, hydration).
|
|
188
188
|
*/
|
|
189
|
-
export function cacheSegmentElements(
|
|
190
|
-
element: unknown,
|
|
191
|
-
cache: SegmentElementCache
|
|
192
|
-
): void {
|
|
189
|
+
export function cacheSegmentElements(element: unknown, cache: SegmentElementCache): void {
|
|
193
190
|
const segments = extractSegments(element);
|
|
194
191
|
for (const entry of segments) {
|
|
195
192
|
cache.set(entry.segmentPath, entry);
|
|
@@ -208,10 +205,7 @@ export function cacheSegmentElements(
|
|
|
208
205
|
*/
|
|
209
206
|
type TreePath = Array<{ element: ReactElement; childIndex: number }>;
|
|
210
207
|
|
|
211
|
-
function findSegmentProviderPath(
|
|
212
|
-
node: ReactElement,
|
|
213
|
-
targetPath?: string
|
|
214
|
-
): TreePath | null {
|
|
208
|
+
function findSegmentProviderPath(node: ReactElement, targetPath?: string): TreePath | null {
|
|
215
209
|
const children = (node.props as { children?: ReactNode }).children;
|
|
216
210
|
if (children == null) return null;
|
|
217
211
|
|
|
@@ -48,8 +48,8 @@ export function triggerStaleReload(): boolean {
|
|
|
48
48
|
if (sessionStorage.getItem(RELOAD_FLAG_KEY)) {
|
|
49
49
|
console.warn(
|
|
50
50
|
'[timber] Stale client reference detected again after reload. ' +
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
'Not reloading to prevent infinite loop. ' +
|
|
52
|
+
'This may indicate a deployment issue — try a hard refresh.'
|
|
53
53
|
);
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
@@ -59,7 +59,7 @@ export function triggerStaleReload(): boolean {
|
|
|
59
59
|
|
|
60
60
|
console.warn(
|
|
61
61
|
'[timber] Stale client reference detected — the server has been ' +
|
|
62
|
-
|
|
62
|
+
'redeployed with new bundles. Reloading to pick up the new version.'
|
|
63
63
|
);
|
|
64
64
|
|
|
65
65
|
window.location.reload();
|
|
@@ -67,9 +67,7 @@ export function triggerStaleReload(): boolean {
|
|
|
67
67
|
} catch {
|
|
68
68
|
// sessionStorage may be unavailable (private browsing, storage full, etc.)
|
|
69
69
|
// Fall back to reloading without loop protection
|
|
70
|
-
console.warn(
|
|
71
|
-
'[timber] Stale client reference detected. Reloading page.'
|
|
72
|
-
);
|
|
70
|
+
console.warn('[timber] Stale client reference detected. Reloading page.');
|
|
73
71
|
window.location.reload();
|
|
74
72
|
return true;
|
|
75
73
|
}
|
|
@@ -183,18 +183,19 @@ export function TopLoader({ config }: { config?: TopLoaderConfig }): React.React
|
|
|
183
183
|
};
|
|
184
184
|
|
|
185
185
|
// Clean up the finishing phase when the finish animation completes.
|
|
186
|
-
const handleAnimationEnd =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
const handleAnimationEnd =
|
|
187
|
+
phase === 'finishing'
|
|
188
|
+
? (e: React.AnimationEvent) => {
|
|
189
|
+
if (e.animationName === FINISH_KEYFRAMES) {
|
|
190
|
+
setPhase('hidden');
|
|
191
|
+
}
|
|
190
192
|
}
|
|
191
|
-
|
|
192
|
-
: undefined;
|
|
193
|
+
: undefined;
|
|
193
194
|
|
|
194
195
|
return createElement(
|
|
195
196
|
'div',
|
|
196
197
|
{
|
|
197
|
-
style: containerStyle,
|
|
198
|
+
'style': containerStyle,
|
|
198
199
|
'aria-hidden': 'true',
|
|
199
200
|
'data-timber-top-loader': '',
|
|
200
201
|
},
|
|
@@ -62,7 +62,13 @@ let _navigateTransition:
|
|
|
62
62
|
* Non-navigation renders:
|
|
63
63
|
* transitionRender(newWrappedElement);
|
|
64
64
|
*/
|
|
65
|
-
export function TransitionRoot({
|
|
65
|
+
export function TransitionRoot({
|
|
66
|
+
initial,
|
|
67
|
+
topLoaderConfig,
|
|
68
|
+
}: {
|
|
69
|
+
initial: ReactNode;
|
|
70
|
+
topLoaderConfig?: TopLoaderConfig;
|
|
71
|
+
}): ReactNode {
|
|
66
72
|
const [element, setElement] = useState<ReactNode>(initial);
|
|
67
73
|
const [pendingUrl, setPendingUrl] = useState<string | null>(null);
|
|
68
74
|
const [, startTransition] = useTransition();
|
package/src/index.ts
CHANGED
|
@@ -223,6 +223,8 @@ export interface PluginContext {
|
|
|
223
223
|
buildManifest: BuildManifest | null;
|
|
224
224
|
/** Startup timer for profiling cold start phases (active in dev, no-op in prod) */
|
|
225
225
|
timer: StartupTimer;
|
|
226
|
+
/** URL path to font CSS file, set by timber-fonts when fonts are registered */
|
|
227
|
+
fontCssUrl?: string;
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
/**
|
package/src/plugins/entries.ts
CHANGED
package/src/plugins/fonts.ts
CHANGED
|
@@ -33,10 +33,11 @@ import {
|
|
|
33
33
|
|
|
34
34
|
const VIRTUAL_GOOGLE = '@timber/fonts/google';
|
|
35
35
|
const VIRTUAL_LOCAL = '@timber/fonts/local';
|
|
36
|
-
const VIRTUAL_FONT_CSS = 'virtual:timber-fonts.css';
|
|
37
36
|
const RESOLVED_GOOGLE = '\0@timber/fonts/google';
|
|
38
37
|
const RESOLVED_LOCAL = '\0@timber/fonts/local';
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
/** URL path where font CSS is served (dev middleware and prod asset). */
|
|
40
|
+
const FONT_CSS_PATH = '/_timber/fonts/fonts.css';
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* Registry of fonts extracted during transform.
|
|
@@ -371,34 +372,32 @@ export function timberFonts(ctx: PluginContext): Plugin {
|
|
|
371
372
|
name: 'timber-fonts',
|
|
372
373
|
|
|
373
374
|
/**
|
|
374
|
-
* Resolve `@timber/fonts/google
|
|
375
|
-
* virtual font CSS module to internal IDs.
|
|
375
|
+
* Resolve `@timber/fonts/google` and `@timber/fonts/local` to virtual modules.
|
|
376
376
|
*/
|
|
377
377
|
resolveId(id: string) {
|
|
378
378
|
if (id === VIRTUAL_GOOGLE) return RESOLVED_GOOGLE;
|
|
379
379
|
if (id === VIRTUAL_LOCAL) return RESOLVED_LOCAL;
|
|
380
|
-
if (id === VIRTUAL_FONT_CSS) return RESOLVED_FONT_CSS;
|
|
381
380
|
return null;
|
|
382
381
|
},
|
|
383
382
|
|
|
384
383
|
/**
|
|
385
384
|
* Return generated source for font virtual modules.
|
|
386
|
-
*
|
|
387
|
-
* The font CSS virtual module returns the combined @font-face rules,
|
|
388
|
-
* fallback CSS, and scoped classes for all registered fonts.
|
|
389
385
|
*/
|
|
390
386
|
load(id: string) {
|
|
391
387
|
if (id === RESOLVED_GOOGLE) return generateGoogleVirtualModule(registry);
|
|
392
388
|
if (id === RESOLVED_LOCAL) return generateLocalVirtualModule();
|
|
393
|
-
if (id === RESOLVED_FONT_CSS) return generateAllFontCss(registry);
|
|
394
389
|
return null;
|
|
395
390
|
},
|
|
396
391
|
|
|
397
392
|
/**
|
|
398
|
-
* Serve local font files in dev mode under `/_timber/fonts/`.
|
|
393
|
+
* Serve local font files and font CSS in dev mode under `/_timber/fonts/`.
|
|
399
394
|
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
395
|
+
* Serves:
|
|
396
|
+
* - `/_timber/fonts/fonts.css` — combined @font-face + scoped class CSS
|
|
397
|
+
* - `/_timber/fonts/<filename>` — individual font files from the registry
|
|
398
|
+
*
|
|
399
|
+
* Only files registered in the font registry are served.
|
|
400
|
+
* Paths are validated to prevent directory traversal.
|
|
402
401
|
*/
|
|
403
402
|
configureServer(server: ViteDevServer) {
|
|
404
403
|
server.middlewares.use((req, res, next) => {
|
|
@@ -413,6 +412,15 @@ export function timberFonts(ctx: PluginContext): Plugin {
|
|
|
413
412
|
return;
|
|
414
413
|
}
|
|
415
414
|
|
|
415
|
+
// Serve generated font CSS
|
|
416
|
+
if (requestedFilename === 'fonts.css') {
|
|
417
|
+
const css = generateAllFontCss(registry);
|
|
418
|
+
res.setHeader('Content-Type', 'text/css');
|
|
419
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
420
|
+
res.end(css);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
416
424
|
// Find the matching font file in the registry
|
|
417
425
|
for (const font of registry.values()) {
|
|
418
426
|
if (font.provider !== 'local' || !font.localSources) continue;
|
|
@@ -420,7 +428,6 @@ export function timberFonts(ctx: PluginContext): Plugin {
|
|
|
420
428
|
const basename = src.path.split('/').pop() ?? '';
|
|
421
429
|
if (basename === requestedFilename) {
|
|
422
430
|
const absolutePath = normalize(resolve(src.path));
|
|
423
|
-
// Verify the resolved path hasn't escaped (extra safety)
|
|
424
431
|
if (!existsSync(absolutePath)) {
|
|
425
432
|
res.statusCode = 404;
|
|
426
433
|
res.end('Not found');
|
|
@@ -571,10 +578,10 @@ export function timberFonts(ctx: PluginContext): Plugin {
|
|
|
571
578
|
}
|
|
572
579
|
|
|
573
580
|
if (transformedCode !== code) {
|
|
574
|
-
//
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
581
|
+
// Mark that fonts are in use so the RSC entry injects a <link> tag.
|
|
582
|
+
if (registry.size > 0 && !ctx.fontCssUrl) {
|
|
583
|
+
ctx.fontCssUrl = FONT_CSS_PATH;
|
|
584
|
+
}
|
|
578
585
|
return { code: transformedCode, map: null };
|
|
579
586
|
}
|
|
580
587
|
|
|
@@ -295,8 +295,14 @@ export function createActionClient<TCtx = Record<string, never>>(
|
|
|
295
295
|
// Determine input — either FormData (from useActionState) or direct arg
|
|
296
296
|
let rawInput: unknown;
|
|
297
297
|
if (args.length === 2 && args[1] instanceof FormData) {
|
|
298
|
-
// Called as (prevState, formData) by React useActionState
|
|
298
|
+
// Called as (prevState, formData) by React useActionState (with-JS path)
|
|
299
299
|
rawInput = schema ? parseFormData(args[1]) : args[1];
|
|
300
|
+
} else if (args.length === 1 && args[0] instanceof FormData) {
|
|
301
|
+
// No-JS path: React's decodeAction binds FormData as the sole argument.
|
|
302
|
+
// The form POSTs without JavaScript, decodeAction resolves the server
|
|
303
|
+
// reference and binds the FormData, then executeAction calls fn() with
|
|
304
|
+
// no additional args — so the bound FormData arrives as args[0].
|
|
305
|
+
rawInput = schema ? parseFormData(args[0]) : args[0];
|
|
300
306
|
} else {
|
|
301
307
|
// Direct call: action(input)
|
|
302
308
|
rawInput = args[0];
|