@timber-js/app 0.2.0-alpha.7 → 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.
Files changed (58) hide show
  1. package/dist/_chunks/{tracing-Cwn7697K.js → tracing-CemImE6h.js} +16 -2
  2. package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CemImE6h.js.map} +1 -1
  3. package/dist/adapters/nitro.d.ts.map +1 -1
  4. package/dist/adapters/nitro.js.map +1 -1
  5. package/dist/cache/fast-hash.d.ts +22 -0
  6. package/dist/cache/fast-hash.d.ts.map +1 -0
  7. package/dist/cache/index.js +51 -9
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/cache/register-cached-function.d.ts.map +1 -1
  10. package/dist/cache/timber-cache.d.ts.map +1 -1
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/client/link.d.ts.map +1 -1
  13. package/dist/client/router.d.ts.map +1 -1
  14. package/dist/client/segment-context.d.ts +1 -1
  15. package/dist/client/segment-context.d.ts.map +1 -1
  16. package/dist/client/segment-merger.d.ts.map +1 -1
  17. package/dist/client/stale-reload.d.ts.map +1 -1
  18. package/dist/client/top-loader.d.ts.map +1 -1
  19. package/dist/client/transition-root.d.ts +1 -1
  20. package/dist/client/transition-root.d.ts.map +1 -1
  21. package/dist/server/action-client.d.ts.map +1 -1
  22. package/dist/server/index.js +9 -1
  23. package/dist/server/index.js.map +1 -1
  24. package/dist/server/pipeline.d.ts.map +1 -1
  25. package/dist/server/response-cache.d.ts +5 -4
  26. package/dist/server/response-cache.d.ts.map +1 -1
  27. package/dist/server/route-element-builder.d.ts.map +1 -1
  28. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  29. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  30. package/dist/server/rsc-entry/rsc-stream.d.ts +6 -0
  31. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  32. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  33. package/dist/server/tracing.d.ts +10 -0
  34. package/dist/server/tracing.d.ts.map +1 -1
  35. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/adapters/nitro.ts +6 -1
  38. package/src/cache/fast-hash.ts +34 -0
  39. package/src/cache/register-cached-function.ts +7 -3
  40. package/src/cache/timber-cache.ts +17 -10
  41. package/src/client/browser-entry.ts +10 -6
  42. package/src/client/link.tsx +14 -9
  43. package/src/client/router.ts +4 -6
  44. package/src/client/segment-context.ts +6 -1
  45. package/src/client/segment-merger.ts +2 -8
  46. package/src/client/stale-reload.ts +4 -6
  47. package/src/client/top-loader.tsx +8 -7
  48. package/src/client/transition-root.tsx +7 -1
  49. package/src/server/action-client.ts +7 -1
  50. package/src/server/pipeline.ts +7 -0
  51. package/src/server/response-cache.ts +169 -36
  52. package/src/server/route-element-builder.ts +1 -6
  53. package/src/server/rsc-entry/index.ts +2 -1
  54. package/src/server/rsc-entry/rsc-payload.ts +42 -10
  55. package/src/server/rsc-entry/rsc-stream.ts +9 -5
  56. package/src/server/rsc-entry/ssr-renderer.ts +11 -8
  57. package/src/server/tracing.ts +23 -0
  58. 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,CA4V1F"}
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: method + pathname + isRscPayload. Responses
14
- * with Set-Cookie headers are never cached (they contain user-specific
15
- * state). When `publicOnly` is true (default), requests with Cookie or
16
- * Authorization headers bypass the cache entirely.
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;;;;;;;;;;;;;;;;;;GAkBG;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;AAoFD,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;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,2BAA2B,GAAG,aAAa,CAqHtF"}
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,CA2W7B"}
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;AAgaD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BApQ3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AAsQhD,wBAAiE"}
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,CA0FnB"}
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;;;;;GAKG;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;CACxD;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,CA8G1F"}
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,CAmKjF"}
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"}
@@ -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,EAAE,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEpG;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAEhF"}
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.7",
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",
@@ -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 { createNitro, build: nitroBuild, prepare, copyPublicAssets } = await import('nitro/builder');
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 SHA-256 cache key from a stable function ID and serialized args.
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 createHash('sha256').update(raw).digest('hex');
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 { addSpanEvent } from '#/server/tracing.js';
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 SHA-256 cache key from function identity and serialized args.
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 createHash('sha256').update(raw).digest('hex');
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
- await addSpanEvent('timber.cache.hit', {
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
- await addSpanEvent('timber.cache.hit', {
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
- await addSpanEvent('timber.cache.miss', {
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 { isStaleClientReference, triggerStaleReload, clearStaleReloadFlag } from './stale-reload.js';
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, { initial: wrapped, topLoaderConfig: _config.topLoader });
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
@@ -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({ preventDefault: () => { prevented = true; } });
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 = internal && prefetch
356
- ? (event: ReactMouseEvent<HTMLAnchorElement>) => {
357
- userOnMouseEnter?.(event);
358
- const router = getRouterOrNull();
359
- if (router) {
360
- router.prefetch(resolvedHref);
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}>
@@ -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(segmentElementCache.getMergeablePaths());
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({ segments, segmentId: _segmentId, parallelRouteKeys, children }: SegmentProviderProps) {
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
- 'Not reloading to prevent infinite loop. ' +
52
- 'This may indicate a deployment issue — try a hard refresh.'
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
- 'redeployed with new bundles. Reloading to pick up the new version.'
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 = phase === 'finishing'
187
- ? (e: React.AnimationEvent) => {
188
- if (e.animationName === FINISH_KEYFRAMES) {
189
- setPhase('hidden');
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({ initial, topLoaderConfig }: { initial: ReactNode; topLoaderConfig?: TopLoaderConfig }): ReactNode {
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();
@@ -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];
@@ -487,7 +487,14 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
487
487
  return new Response(null, { status: error.status });
488
488
  }
489
489
  // RedirectSignal leaked from render — honour the redirect.
490
+ // For RSC payload requests, return 204 + X-Timber-Redirect so the
491
+ // client router can perform a soft SPA redirect (same as middleware path).
490
492
  if (error instanceof RedirectSignal) {
493
+ const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');
494
+ if (isRsc) {
495
+ responseHeaders.set('X-Timber-Redirect', error.location);
496
+ return new Response(null, { status: 204, headers: responseHeaders });
497
+ }
491
498
  responseHeaders.set('Location', error.location);
492
499
  return new Response(null, { status: error.status, headers: responseHeaders });
493
500
  }