@timber-js/app 0.2.0-alpha.7 → 0.2.0-alpha.71
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/LICENSE +8 -0
- package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-BJARkOcu.js} +1 -1
- package/dist/_chunks/als-registry-BJARkOcu.js.map +1 -0
- package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
- package/dist/_chunks/{debug-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-CGuYoRHU.js +199 -0
- package/dist/_chunks/define-CGuYoRHU.js.map +1 -0
- package/dist/_chunks/define-Dz1bqwaS.js +106 -0
- package/dist/_chunks/define-Dz1bqwaS.js.map +1 -0
- package/dist/_chunks/define-cookie-B5mewxwM.js +93 -0
- package/dist/_chunks/define-cookie-B5mewxwM.js.map +1 -0
- package/dist/_chunks/error-boundary-D9hzsveV.js +216 -0
- package/dist/_chunks/error-boundary-D9hzsveV.js.map +1 -0
- package/dist/_chunks/{format-DviM89f0.js → format-Rn922VH2.js} +3 -20
- package/dist/_chunks/format-Rn922VH2.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → handler-store-BVePM7hp.js} +68 -3
- package/dist/_chunks/handler-store-BVePM7hp.js.map +1 -0
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-CEdHHviP.js} +171 -97
- package/dist/_chunks/interception-CEdHHviP.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
- package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-CywiO4jV.js} +181 -69
- package/dist/_chunks/request-context-CywiO4jV.js.map +1 -0
- package/dist/_chunks/schema-bridge-C4SwjCQD.js +86 -0
- package/dist/_chunks/schema-bridge-C4SwjCQD.js.map +1 -0
- package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
- package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
- package/dist/_chunks/segment-context-hzuJ048X.js +72 -0
- package/dist/_chunks/segment-context-hzuJ048X.js.map +1 -0
- package/dist/_chunks/stale-reload-BLUC_Pl_.js +64 -0
- package/dist/_chunks/stale-reload-BLUC_Pl_.js.map +1 -0
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DAhgj8Gx.js} +1 -1
- package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -0
- package/dist/_chunks/wrappers-LZbghvn0.js +63 -0
- package/dist/_chunks/wrappers-LZbghvn0.js.map +1 -0
- package/dist/adapters/cloudflare-dev.d.ts +109 -0
- package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
- package/dist/adapters/cloudflare-dev.js +73 -0
- package/dist/adapters/cloudflare-dev.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +148 -12
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +135 -11
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +17 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +56 -13
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/cache-api.d.ts +24 -0
- package/dist/cache/cache-api.d.ts.map +1 -0
- package/dist/cache/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/handler-store.d.ts +31 -0
- package/dist/cache/handler-store.d.ts.map +1 -0
- package/dist/cache/index.d.ts +7 -5
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +111 -73
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/singleflight.d.ts +18 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +12 -5
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -125
- package/dist/client/error-reconstituter.d.ts +54 -0
- package/dist/client/error-reconstituter.d.ts.map +1 -0
- package/dist/client/form.d.ts +2 -2
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/history.d.ts +19 -4
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +6 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +537 -166
- package/dist/client/index.js.map +1 -1
- package/dist/client/link-pending-store.d.ts +78 -0
- package/dist/client/link-pending-store.d.ts.map +1 -0
- package/dist/client/link.d.ts +90 -32
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/nav-link-store.d.ts +36 -0
- package/dist/client/nav-link-store.d.ts.map +1 -0
- package/dist/client/navigation-api-types.d.ts +90 -0
- package/dist/client/navigation-api-types.d.ts.map +1 -0
- package/dist/client/navigation-api.d.ts +115 -0
- package/dist/client/navigation-api.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +13 -2
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
- package/dist/client/navigation-root.d.ts.map +1 -0
- package/dist/client/nuqs-adapter.d.ts.map +1 -1
- package/dist/client/router.d.ts +70 -4
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +38 -3
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -1
- package/dist/client/segment-cache.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/segment-outlet.d.ts +63 -0
- package/dist/client/segment-outlet.d.ts.map +1 -0
- package/dist/client/ssr-data.d.ts +13 -4
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/stale-reload.d.ts +15 -0
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +3 -3
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +6 -4
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/codec.d.ts +23 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +2 -0
- package/dist/cookies/define-cookie.d.ts +35 -14
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.d.ts +2 -0
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +3 -84
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/index.d.ts +154 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12092 -11916
- package/dist/index.js.map +1 -1
- package/dist/plugins/adapter-build.d.ts +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +2 -2
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +3 -3
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/client-chunks.d.ts +32 -0
- package/dist/plugins/client-chunks.d.ts.map +1 -0
- package/dist/plugins/content.d.ts +1 -1
- package/dist/plugins/content.d.ts.map +1 -1
- package/dist/plugins/dev-browser-logs.d.ts +84 -0
- package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
- package/dist/plugins/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/dev-logs.d.ts +1 -1
- package/dist/plugins/dev-logs.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +19 -5
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +1 -1
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts +6 -5
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts +2 -2
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/index.d.ts +2 -0
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +3 -2
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +46 -0
- package/dist/routing/segment-classify.d.ts.map +1 -0
- package/dist/routing/status-file-lint.d.ts +2 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/routing/types.d.ts +16 -4
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/rsc-runtime/ssr.d.ts +12 -0
- package/dist/rsc-runtime/ssr.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +76 -0
- package/dist/schema-bridge.d.ts.map +1 -0
- package/dist/search-params/define.d.ts +139 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -6
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +4 -474
- package/dist/search-params/registry.d.ts +1 -1
- package/dist/search-params/wrappers.d.ts +53 -0
- package/dist/search-params/wrappers.d.ts.map +1 -0
- package/dist/segment-params/define.d.ts +78 -0
- package/dist/segment-params/define.d.ts.map +1 -0
- package/dist/segment-params/index.d.ts +7 -0
- package/dist/segment-params/index.d.ts.map +1 -0
- package/dist/segment-params/index.js +4 -0
- package/dist/server/access-gate.d.ts +4 -0
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-client.d.ts +12 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/action-encryption.d.ts +76 -0
- package/dist/server/action-encryption.d.ts.map +1 -0
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +3 -6
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +32 -4
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +2 -2
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/debug.d.ts +1 -1
- package/dist/server/default-logger.d.ts +22 -0
- package/dist/server/default-logger.d.ts.map +1 -0
- package/dist/server/deny-page-resolver.d.ts +52 -0
- package/dist/server/deny-page-resolver.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/dev-warnings.d.ts +0 -14
- package/dist/server/dev-warnings.d.ts.map +1 -1
- package/dist/server/early-hints.d.ts +13 -5
- package/dist/server/early-hints.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +7 -1
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +4 -3
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/flight-injection-state.d.ts +66 -0
- package/dist/server/flight-injection-state.d.ts.map +1 -0
- package/dist/server/flight-scripts.d.ts +42 -0
- package/dist/server/flight-scripts.d.ts.map +1 -0
- package/dist/server/flush.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +29 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +51 -11
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2176 -1663
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +25 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware-runner.d.ts +19 -4
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +113 -0
- package/dist/server/node-stream-transforms.d.ts.map +1 -0
- package/dist/server/page-deny-boundary.d.ts +31 -0
- package/dist/server/page-deny-boundary.d.ts.map +1 -0
- package/dist/server/pipeline-interception.d.ts +1 -1
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-metadata.d.ts +6 -0
- package/dist/server/pipeline-metadata.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +32 -10
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +30 -3
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts +51 -0
- package/dist/server/render-timeout.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +76 -37
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +27 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +9 -2
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/api-handler.d.ts +2 -2
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +48 -5
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +8 -3
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts +46 -0
- package/dist/server/safe-load.d.ts.map +1 -0
- package/dist/server/sitemap-generator.d.ts +129 -0
- package/dist/server/sitemap-generator.d.ts.map +1 -0
- package/dist/server/sitemap-handler.d.ts +22 -0
- package/dist/server/sitemap-handler.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +22 -0
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +39 -21
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +50 -0
- package/dist/server/ssr-wrappers.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +1 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts +36 -0
- package/dist/server/stream-utils.d.ts.map +1 -0
- package/dist/server/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +22 -19
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -4
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/version-skew.d.ts +61 -0
- package/dist/server/version-skew.d.ts.map +1 -0
- package/dist/server/waituntil-bridge.d.ts.map +1 -1
- package/dist/shared/merge-search-params.d.ts +22 -0
- package/dist/shared/merge-search-params.d.ts.map +1 -0
- package/dist/shims/font-google.d.ts +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/dist/shims/font-google.js +42 -0
- package/dist/shims/font-google.js.map +1 -0
- package/dist/shims/font-local.d.ts +26 -0
- package/dist/shims/font-local.d.ts.map +1 -0
- package/dist/shims/font-local.js +20 -0
- package/dist/shims/font-local.js.map +1 -0
- package/dist/shims/navigation-client.d.ts +1 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/utils/directive-parser.d.ts +5 -2
- package/dist/utils/directive-parser.d.ts.map +1 -1
- package/dist/utils/state-machine.d.ts +80 -0
- package/dist/utils/state-machine.d.ts.map +1 -0
- package/package.json +37 -17
- package/src/adapters/cloudflare-dev.ts +177 -0
- package/src/adapters/cloudflare.ts +342 -28
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/adapters/wrangler.d.ts +7 -0
- package/src/cache/cache-api.ts +38 -0
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/handler-store.ts +68 -0
- package/src/cache/index.ts +9 -5
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +40 -29
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +314 -142
- package/src/client/error-boundary.tsx +48 -16
- package/src/client/error-reconstituter.tsx +65 -0
- package/src/client/form.tsx +2 -2
- package/src/client/history.ts +26 -4
- package/src/client/index.ts +13 -4
- package/src/client/link-pending-store.ts +136 -0
- package/src/client/link.tsx +346 -105
- package/src/client/nav-link-store.ts +47 -0
- package/src/client/navigation-api-types.ts +112 -0
- package/src/client/navigation-api.ts +332 -0
- package/src/client/navigation-context.ts +27 -6
- package/src/client/navigation-root.tsx +346 -0
- package/src/client/nuqs-adapter.tsx +16 -3
- package/src/client/router.ts +302 -77
- package/src/client/rsc-fetch.ts +93 -5
- package/src/client/segment-cache.ts +1 -1
- package/src/client/segment-context.ts +6 -1
- package/src/client/segment-merger.ts +2 -8
- package/src/client/segment-outlet.tsx +86 -0
- package/src/client/ssr-data.ts +13 -5
- package/src/client/stale-reload.ts +73 -6
- package/src/client/top-loader.tsx +22 -13
- package/src/client/use-navigation-pending.ts +1 -1
- package/src/client/use-params.ts +7 -5
- package/src/client/use-query-states.ts +2 -2
- package/src/codec.ts +34 -0
- package/src/cookies/define-cookie.ts +72 -21
- package/src/cookies/index.ts +7 -0
- package/src/fonts/css.ts +2 -1
- package/src/index.ts +328 -92
- package/src/plugins/adapter-build.ts +8 -2
- package/src/plugins/build-manifest.ts +13 -2
- package/src/plugins/build-report.ts +3 -3
- package/src/plugins/client-chunks.ts +65 -0
- package/src/plugins/content.ts +1 -1
- package/src/plugins/dev-browser-logs.ts +288 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-logs.ts +1 -1
- package/src/plugins/dev-server.ts +55 -9
- package/src/plugins/entries.ts +70 -9
- package/src/plugins/fonts.ts +167 -61
- package/src/plugins/mdx.ts +1 -1
- package/src/plugins/routing.ts +57 -17
- package/src/plugins/server-action-exports.ts +1 -1
- package/src/plugins/server-bundle.ts +32 -1
- package/src/plugins/shims.ts +76 -33
- package/src/plugins/static-build.ts +10 -6
- package/src/routing/codegen.ts +165 -105
- package/src/routing/index.ts +2 -0
- package/src/routing/scanner.ts +93 -23
- package/src/routing/segment-classify.ts +89 -0
- package/src/routing/status-file-lint.ts +3 -2
- package/src/routing/types.ts +17 -4
- package/src/rsc-runtime/rsc.ts +2 -0
- package/src/rsc-runtime/ssr.ts +50 -0
- package/src/rsc-runtime/vendor-types.d.ts +7 -0
- package/src/{search-params/codecs.ts → schema-bridge.ts} +57 -20
- package/src/search-params/define.ts +482 -0
- package/src/search-params/index.ts +13 -19
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/segment-params/define.ts +279 -0
- package/src/segment-params/index.ts +28 -0
- package/src/server/access-gate.tsx +70 -29
- package/src/server/action-client.ts +28 -3
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +20 -3
- package/src/server/actions.ts +10 -9
- package/src/server/als-registry.ts +32 -4
- package/src/server/build-manifest.ts +10 -4
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +1 -1
- package/src/server/default-logger.ts +99 -0
- package/src/server/deny-page-resolver.ts +154 -0
- package/src/server/deny-renderer.ts +24 -38
- package/src/server/dev-warnings.ts +2 -28
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +74 -22
- package/src/server/fallback-error.ts +31 -15
- package/src/server/flight-injection-state.ts +113 -0
- package/src/server/flight-scripts.ts +62 -0
- package/src/server/flush.ts +2 -1
- package/src/server/form-data.ts +76 -0
- package/src/server/html-injectors.ts +277 -117
- package/src/server/index.ts +9 -5
- package/src/server/logger.ts +44 -36
- package/src/server/middleware-runner.ts +31 -4
- package/src/server/node-stream-transforms.ts +509 -0
- package/src/server/page-deny-boundary.tsx +56 -0
- package/src/server/pipeline-interception.ts +17 -16
- package/src/server/pipeline-metadata.ts +13 -0
- package/src/server/pipeline.ts +195 -51
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +240 -117
- package/src/server/route-element-builder.ts +284 -197
- package/src/server/route-handler.ts +24 -4
- package/src/server/route-matcher.ts +24 -20
- package/src/server/rsc-entry/api-handler.ts +15 -16
- package/src/server/rsc-entry/error-renderer.ts +300 -89
- package/src/server/rsc-entry/helpers.ts +134 -5
- package/src/server/rsc-entry/index.ts +202 -113
- package/src/server/rsc-entry/rsc-payload.ts +100 -21
- package/src/server/rsc-entry/rsc-stream.ts +74 -18
- package/src/server/rsc-entry/ssr-bridge.ts +14 -5
- package/src/server/rsc-entry/ssr-renderer.ts +173 -40
- package/src/server/safe-load.ts +60 -0
- package/src/server/sitemap-generator.ts +338 -0
- package/src/server/sitemap-handler.ts +126 -0
- package/src/server/slot-resolver.ts +243 -228
- package/src/server/ssr-entry.ts +211 -32
- package/src/server/ssr-render.ts +289 -67
- package/src/server/ssr-wrappers.tsx +139 -0
- package/src/server/status-code-resolver.ts +1 -1
- package/src/server/stream-utils.ts +213 -0
- package/src/server/tracing.ts +37 -3
- package/src/server/tree-builder.ts +92 -58
- package/src/server/types.ts +3 -6
- package/src/server/version-skew.ts +104 -0
- package/src/server/waituntil-bridge.ts +4 -1
- package/src/shared/merge-search-params.ts +55 -0
- package/src/shims/font-google.ts +1 -1
- package/src/shims/font-local.ts +34 -0
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +2 -1
- package/src/utils/directive-parser.ts +5 -2
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
- package/dist/_chunks/format-DviM89f0.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
- package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
- package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
- package/dist/_chunks/tracing-Cwn7697K.js.map +0 -1
- package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
- package/dist/cache/register-cached-function.d.ts +0 -17
- package/dist/cache/register-cached-function.d.ts.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/client/link-status-provider.d.ts +0 -11
- package/dist/client/link-status-provider.d.ts.map +0 -1
- package/dist/client/transition-root.d.ts.map +0 -1
- package/dist/cookies/index.js.map +0 -1
- package/dist/plugins/cache-transform.d.ts +0 -36
- package/dist/plugins/cache-transform.d.ts.map +0 -1
- package/dist/plugins/dynamic-transform.d.ts +0 -72
- package/dist/plugins/dynamic-transform.d.ts.map +0 -1
- package/dist/search-params/analyze.d.ts +0 -54
- package/dist/search-params/analyze.d.ts.map +0 -1
- package/dist/search-params/builtin-codecs.d.ts +0 -105
- package/dist/search-params/builtin-codecs.d.ts.map +0 -1
- package/dist/search-params/codecs.d.ts +0 -53
- package/dist/search-params/codecs.d.ts.map +0 -1
- package/dist/search-params/create.d.ts +0 -106
- package/dist/search-params/create.d.ts.map +0 -1
- package/dist/search-params/index.js.map +0 -1
- package/dist/server/prerender.d.ts +0 -77
- package/dist/server/prerender.d.ts.map +0 -1
- package/dist/server/response-cache.d.ts +0 -53
- package/dist/server/response-cache.d.ts.map +0 -1
- package/src/cache/register-cached-function.ts +0 -99
- package/src/client/link-status-provider.tsx +0 -30
- package/src/client/transition-root.tsx +0 -160
- package/src/plugins/cache-transform.ts +0 -199
- package/src/plugins/dynamic-transform.ts +0 -161
- package/src/search-params/analyze.ts +0 -192
- package/src/search-params/builtin-codecs.ts +0 -228
- package/src/search-params/create.ts +0 -321
- package/src/server/prerender.ts +0 -139
- package/src/server/response-cache.ts +0 -277
package/src/adapters/nitro.ts
CHANGED
|
@@ -149,6 +149,23 @@ export interface NitroAdapterOptions {
|
|
|
149
149
|
*/
|
|
150
150
|
preset?: NitroPreset;
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Enable application-level gzip compression for HTML and RSC responses.
|
|
154
|
+
*
|
|
155
|
+
* When `true` (default), the origin compresses responses using the Web
|
|
156
|
+
* `CompressionStream` API. This is useful for self-hosted deployments
|
|
157
|
+
* where no reverse proxy or CDN handles compression.
|
|
158
|
+
*
|
|
159
|
+
* Set to `false` when deploying behind a reverse proxy (nginx, caddy)
|
|
160
|
+
* or CDN (Cloudflare, Fastly, Vercel) that compresses at the edge.
|
|
161
|
+
* Disabling origin compression saves CPU on the Node.js event loop —
|
|
162
|
+
* compressing 1MB+ streaming HTML responses takes 10-15ms of main
|
|
163
|
+
* thread time per request, directly reducing throughput under load.
|
|
164
|
+
*
|
|
165
|
+
* @default true
|
|
166
|
+
*/
|
|
167
|
+
compress?: boolean;
|
|
168
|
+
|
|
152
169
|
/**
|
|
153
170
|
* Additional Nitro configuration to merge into the generated config.
|
|
154
171
|
* Overrides default values for the selected preset.
|
|
@@ -176,6 +193,7 @@ export interface NitroAdapterOptions {
|
|
|
176
193
|
*/
|
|
177
194
|
export function nitro(options: NitroAdapterOptions = {}): TimberPlatformAdapter {
|
|
178
195
|
const preset = options.preset ?? 'node-server';
|
|
196
|
+
const compress = options.compress ?? true;
|
|
179
197
|
const presetConfig = PRESET_CONFIGS[preset];
|
|
180
198
|
const pendingPromises: Promise<unknown>[] = [];
|
|
181
199
|
|
|
@@ -229,7 +247,7 @@ export function nitro(options: NitroAdapterOptions = {}): TimberPlatformAdapter
|
|
|
229
247
|
}
|
|
230
248
|
|
|
231
249
|
// Generate the Nitro entry point (imports from ./rsc/ within nitro dir)
|
|
232
|
-
const entry = generateNitroEntry(buildDir, outDir, preset);
|
|
250
|
+
const entry = generateNitroEntry(buildDir, outDir, preset, compress);
|
|
233
251
|
await writeFile(join(outDir, 'entry.ts'), entry);
|
|
234
252
|
|
|
235
253
|
// Run the Nitro build to produce a production-ready server bundle.
|
|
@@ -273,6 +291,7 @@ export function generateNitroEntry(
|
|
|
273
291
|
buildDir: string,
|
|
274
292
|
outDir: string,
|
|
275
293
|
preset: NitroPreset,
|
|
294
|
+
compress = true,
|
|
276
295
|
hasManifestInit = false
|
|
277
296
|
): string {
|
|
278
297
|
// The RSC entry is the main request handler — it exports the fetch handler as default.
|
|
@@ -338,12 +357,14 @@ export function generateNitroEntry(
|
|
|
338
357
|
// Import runWithWaitUntil only when the preset supports it.
|
|
339
358
|
const waitUntilImport = supportsWaitUntil ? ', runWithWaitUntil' : '';
|
|
340
359
|
|
|
360
|
+
const compressImport = compress ? "import { compressResponse } from './_compress.mjs'\n" : '';
|
|
361
|
+
const compressCall = compress ? 'compressResponse(webRequest, webResponse)' : 'webResponse';
|
|
362
|
+
|
|
341
363
|
return `// Generated by @timber-js/app/adapters/nitro
|
|
342
364
|
// Do not edit — this file is regenerated on each build.
|
|
343
365
|
|
|
344
366
|
${manifestImport}import handler, { runWithEarlyHintsSender${waitUntilImport} } from '${serverEntryRelative}'
|
|
345
|
-
|
|
346
|
-
|
|
367
|
+
${compressImport}
|
|
347
368
|
// Set TIMBER_RUNTIME for instrumentation.ts conditional SDK initialization.
|
|
348
369
|
// See design/25-production-deployments.md §"TIMBER_RUNTIME".
|
|
349
370
|
process.env.TIMBER_RUNTIME = '${runtimeName}'
|
|
@@ -357,7 +378,7 @@ export default async function timberHandler(event) {
|
|
|
357
378
|
// h3 v2: event.req is the Web Request
|
|
358
379
|
const webRequest = event.req
|
|
359
380
|
${handlerCall}
|
|
360
|
-
return
|
|
381
|
+
return ${compressCall}
|
|
361
382
|
}
|
|
362
383
|
`;
|
|
363
384
|
}
|
|
@@ -544,14 +565,37 @@ const server = createServer(async (req, res) => {
|
|
|
544
565
|
|
|
545
566
|
if (webResponse.body) {
|
|
546
567
|
const reader = webResponse.body.getReader();
|
|
547
|
-
|
|
568
|
+
|
|
569
|
+
// Cancel the reader when the client disconnects. This causes any
|
|
570
|
+
// pending reader.read() to reject, breaking the pump loop. Critical
|
|
571
|
+
// for SSE and other infinite streams — without this, disconnected
|
|
572
|
+
// clients leak readers.
|
|
573
|
+
let clientDisconnected = false;
|
|
574
|
+
const onClose = () => {
|
|
575
|
+
clientDisconnected = true;
|
|
576
|
+
reader.cancel('Client disconnected').catch(() => {});
|
|
577
|
+
};
|
|
578
|
+
res.on('close', onClose);
|
|
579
|
+
|
|
580
|
+
try {
|
|
548
581
|
while (true) {
|
|
549
582
|
const { done, value } = await reader.read();
|
|
550
|
-
if (done)
|
|
583
|
+
if (done) break;
|
|
551
584
|
res.write(value);
|
|
552
585
|
}
|
|
553
|
-
}
|
|
554
|
-
|
|
586
|
+
} catch (err) {
|
|
587
|
+
// reader.cancel() from the close handler causes read() to reject.
|
|
588
|
+
// This is expected on client disconnect — not an error.
|
|
589
|
+
if (!clientDisconnected) {
|
|
590
|
+
throw err;
|
|
591
|
+
}
|
|
592
|
+
} finally {
|
|
593
|
+
res.off('close', onClose);
|
|
594
|
+
reader.releaseLock();
|
|
595
|
+
if (!res.writableEnded) {
|
|
596
|
+
res.end();
|
|
597
|
+
}
|
|
598
|
+
}
|
|
555
599
|
} else {
|
|
556
600
|
res.end();
|
|
557
601
|
}
|
|
@@ -611,7 +655,12 @@ async function runNitroBuild(
|
|
|
611
655
|
userConfig?: Record<string, unknown>
|
|
612
656
|
): Promise<void> {
|
|
613
657
|
const presetConfig = PRESET_CONFIGS[preset];
|
|
614
|
-
const {
|
|
658
|
+
const {
|
|
659
|
+
createNitro,
|
|
660
|
+
build: nitroBuild,
|
|
661
|
+
prepare,
|
|
662
|
+
copyPublicAssets,
|
|
663
|
+
} = await import('nitro/builder');
|
|
615
664
|
|
|
616
665
|
const nitro = await createNitro({
|
|
617
666
|
rootDir: nitroDir,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CacheOptions } from './index';
|
|
2
|
+
import { createCache } from './timber-cache';
|
|
3
|
+
import { getCacheHandler } from './handler-store';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Public caching API: `cache(fn, opts)`.
|
|
7
|
+
*
|
|
8
|
+
* Wraps an async function with cross-request caching. Uses the configured
|
|
9
|
+
* cache handler (defaults to MemoryCacheHandler, overridable via timber.config.ts).
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { cache } from '@timber-js/app/cache';
|
|
13
|
+
*
|
|
14
|
+
* const getUser = cache(
|
|
15
|
+
* async (id: string) => db.users.findUnique({ where: { id } }),
|
|
16
|
+
* { ttl: 60, tags: (id) => [`user:${id}`] }
|
|
17
|
+
* );
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
export function cache<Fn extends (...args: any[]) => Promise<any>>(
|
|
22
|
+
fn: Fn,
|
|
23
|
+
opts: CacheOptions<Fn>
|
|
24
|
+
): Fn {
|
|
25
|
+
return createCache(fn, opts, getCacheHandler());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Invalidate cache entries by tag or key.
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* cache.invalidate({ tag: 'products' });
|
|
33
|
+
* cache.invalidate({ key: 'user:abc' });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
cache.invalidate = async function invalidate(opts: { key?: string; tag?: string }): Promise<void> {
|
|
37
|
+
await getCacheHandler().invalidate(opts);
|
|
38
|
+
};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-level cache handler singleton.
|
|
3
|
+
*
|
|
4
|
+
* Lazily initialized to MemoryCacheHandler on first access. The framework
|
|
5
|
+
* replaces this at boot from timber.config.ts via setCacheHandler().
|
|
6
|
+
*
|
|
7
|
+
* This module avoids importing from ./index to prevent circular dependencies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Inline the interface to avoid circular import with index.ts
|
|
11
|
+
interface CacheHandlerLike {
|
|
12
|
+
get(key: string): Promise<{ value: unknown; stale: boolean } | null>;
|
|
13
|
+
set(key: string, value: unknown, opts: { ttl: number; tags: string[] }): Promise<void>;
|
|
14
|
+
invalidate(opts: { key?: string; tag?: string }): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let handler: CacheHandlerLike | null = null;
|
|
18
|
+
|
|
19
|
+
/** Replace the active cache handler. Called by the framework at boot. */
|
|
20
|
+
export function setCacheHandler(h: CacheHandlerLike): void {
|
|
21
|
+
handler = h;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the active cache handler. Creates a default MemoryCacheHandler on
|
|
26
|
+
* first access if none has been set via setCacheHandler().
|
|
27
|
+
*/
|
|
28
|
+
export function getCacheHandler(): CacheHandlerLike {
|
|
29
|
+
if (!handler) {
|
|
30
|
+
// Inline a minimal LRU cache to avoid circular dep with index.ts.
|
|
31
|
+
// In production, the framework always calls setCacheHandler() at boot.
|
|
32
|
+
handler = createDefaultHandler();
|
|
33
|
+
}
|
|
34
|
+
return handler;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createDefaultHandler(): CacheHandlerLike {
|
|
38
|
+
const store = new Map<string, { value: unknown; expiresAt: number; tags: string[] }>();
|
|
39
|
+
const maxSize = 1000;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
async get(key) {
|
|
43
|
+
const entry = store.get(key);
|
|
44
|
+
if (!entry) return null;
|
|
45
|
+
store.delete(key);
|
|
46
|
+
store.set(key, entry);
|
|
47
|
+
const stale = Date.now() > entry.expiresAt;
|
|
48
|
+
return { value: entry.value, stale };
|
|
49
|
+
},
|
|
50
|
+
async set(key, value, opts) {
|
|
51
|
+
if (store.has(key)) store.delete(key);
|
|
52
|
+
while (store.size >= maxSize) {
|
|
53
|
+
const oldest = store.keys().next().value;
|
|
54
|
+
if (oldest !== undefined) store.delete(oldest);
|
|
55
|
+
else break;
|
|
56
|
+
}
|
|
57
|
+
store.set(key, { value, expiresAt: Date.now() + opts.ttl * 1000, tags: opts.tags });
|
|
58
|
+
},
|
|
59
|
+
async invalidate(opts) {
|
|
60
|
+
if (opts.key) store.delete(opts.key);
|
|
61
|
+
if (opts.tag) {
|
|
62
|
+
for (const [key, entry] of store) {
|
|
63
|
+
if (entry.tags.includes(opts.tag)) store.delete(key);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
package/src/cache/index.ts
CHANGED
|
@@ -12,6 +12,9 @@ export interface CacheOptions<Fn extends (...args: any[]) => any> {
|
|
|
12
12
|
key?: (...args: Parameters<Fn>) => string;
|
|
13
13
|
staleWhileRevalidate?: boolean;
|
|
14
14
|
tags?: string[] | ((...args: Parameters<Fn>) => string[]);
|
|
15
|
+
/** Timeout (ms) for singleflight-coalesced calls. Prevents hung fn() from
|
|
16
|
+
* permanently blocking all future callers for the same cache key. See TIM-518. */
|
|
17
|
+
timeoutMs?: number;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export interface MemoryCacheHandlerOptions {
|
|
@@ -83,9 +86,10 @@ export class MemoryCacheHandler implements CacheHandler {
|
|
|
83
86
|
|
|
84
87
|
export { RedisCacheHandler } from './redis-handler';
|
|
85
88
|
export type { RedisClient } from './redis-handler';
|
|
86
|
-
export {
|
|
87
|
-
export {
|
|
88
|
-
|
|
89
|
+
export { cache } from './cache-api';
|
|
90
|
+
export { setCacheHandler, getCacheHandler } from './handler-store';
|
|
91
|
+
// NOTE: registerCachedFunction (runtime for 'use cache' directive) removed.
|
|
92
|
+
// Future feature pending design doc. See design/06-caching.md.
|
|
89
93
|
export { stableStringify } from './stable-stringify';
|
|
90
|
-
export { createSingleflight } from './singleflight';
|
|
91
|
-
export type { Singleflight } from './singleflight';
|
|
94
|
+
export { createSingleflight, SingleflightTimeoutError } from './singleflight';
|
|
95
|
+
export type { Singleflight, SingleflightOptions } from './singleflight';
|
|
@@ -3,24 +3,82 @@
|
|
|
3
3
|
* execution. All callers receive the same result (or error).
|
|
4
4
|
*
|
|
5
5
|
* Per-process, in-memory. Each process coalesces independently.
|
|
6
|
+
*
|
|
7
|
+
* An optional `timeoutMs` prevents hung `fn()` calls from permanently
|
|
8
|
+
* blocking all future callers for that key. When set, `fn()` is raced
|
|
9
|
+
* against a timeout — if the timeout fires first, the promise rejects
|
|
10
|
+
* with `SingleflightTimeoutError`, `finally` cleans up the key, and
|
|
11
|
+
* subsequent callers can retry. See TIM-518.
|
|
6
12
|
*/
|
|
13
|
+
|
|
14
|
+
export interface SingleflightOptions {
|
|
15
|
+
/** Maximum time (ms) a coalesced call may run before being rejected. */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
export interface Singleflight {
|
|
8
20
|
do<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
9
21
|
}
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Error thrown when a singleflight call exceeds `timeoutMs`.
|
|
25
|
+
* Exported so callers can distinguish timeout from other errors.
|
|
26
|
+
*/
|
|
27
|
+
export class SingleflightTimeoutError extends Error {
|
|
28
|
+
constructor(key: string, timeoutMs: number) {
|
|
29
|
+
super(`Singleflight timeout: key "${key}" exceeded ${timeoutMs}ms`);
|
|
30
|
+
this.name = 'SingleflightTimeoutError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createSingleflight(opts?: SingleflightOptions): Singleflight {
|
|
12
35
|
const inflight = new Map<string, Promise<unknown>>();
|
|
36
|
+
const timeoutMs = opts?.timeoutMs;
|
|
13
37
|
|
|
14
38
|
return {
|
|
15
39
|
do<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
16
40
|
const existing = inflight.get(key);
|
|
17
41
|
if (existing) return existing as Promise<T>;
|
|
18
42
|
|
|
19
|
-
|
|
43
|
+
let promise: Promise<T>;
|
|
44
|
+
|
|
45
|
+
if (timeoutMs != null && timeoutMs > 0) {
|
|
46
|
+
// Race fn() against a timeout to prevent hung calls from
|
|
47
|
+
// permanently blocking the key. See TIM-518.
|
|
48
|
+
promise = new Promise<T>((resolve, reject) => {
|
|
49
|
+
const timer = setTimeout(
|
|
50
|
+
() => reject(new SingleflightTimeoutError(key, timeoutMs)),
|
|
51
|
+
timeoutMs
|
|
52
|
+
);
|
|
53
|
+
// Wrap in try/catch so a synchronous throw from fn()
|
|
54
|
+
// (e.g. argument validation) still clears the timer.
|
|
55
|
+
// Without this, the timer leaks until expiry.
|
|
56
|
+
try {
|
|
57
|
+
fn().then(
|
|
58
|
+
(value) => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
resolve(value);
|
|
61
|
+
},
|
|
62
|
+
(err) => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
reject(err);
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
reject(err);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
promise = fn();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const tracked = promise.finally(() => {
|
|
20
77
|
inflight.delete(key);
|
|
21
78
|
});
|
|
22
|
-
|
|
23
|
-
|
|
79
|
+
|
|
80
|
+
inflight.set(key, tracked);
|
|
81
|
+
return tracked as Promise<T>;
|
|
24
82
|
},
|
|
25
83
|
};
|
|
26
84
|
}
|
|
@@ -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
|
-
const
|
|
7
|
+
const defaultSingleflight = 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
|
/**
|
|
@@ -47,18 +53,25 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
|
|
|
47
53
|
fn: Fn,
|
|
48
54
|
opts: CacheOptions<Fn>,
|
|
49
55
|
handler: CacheHandler
|
|
50
|
-
):
|
|
56
|
+
): Fn {
|
|
51
57
|
const fnId = `timber-cache:${fnIdCounter++}`;
|
|
58
|
+
const sf = opts.timeoutMs
|
|
59
|
+
? createSingleflight({ timeoutMs: opts.timeoutMs })
|
|
60
|
+
: defaultSingleflight;
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
// Cast to Fn to preserve the original function's generic call signature.
|
|
63
|
+
// Without this, generic type parameters (e.g. <T> in apiFetch<T>) are
|
|
64
|
+
// erased and callers lose type safety on the return type.
|
|
65
|
+
return (async (...args: Parameters<Fn>): Promise<Awaited<ReturnType<Fn>>> => {
|
|
54
66
|
const key = opts.key ? opts.key(...args) : defaultKeyGenerator(fnId, args);
|
|
55
67
|
|
|
56
68
|
const cacheStart = performance.now();
|
|
57
69
|
const cached = await handler.get(key);
|
|
58
70
|
|
|
59
71
|
if (cached && !cached.stale) {
|
|
60
|
-
// Record as OTEL span event on enclosing span (not a child span)
|
|
61
|
-
|
|
72
|
+
// Record as OTEL span event on enclosing span (not a child span).
|
|
73
|
+
// Fire-and-forget — no microtask overhead on the cache hot path.
|
|
74
|
+
addSpanEventSync('timber.cache.hit', {
|
|
62
75
|
key,
|
|
63
76
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
64
77
|
});
|
|
@@ -66,43 +79,41 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
|
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
if (cached && cached.stale && opts.staleWhileRevalidate) {
|
|
69
|
-
// Record stale cache hit as OTEL span event
|
|
70
|
-
|
|
82
|
+
// Record stale cache hit as OTEL span event (fire-and-forget).
|
|
83
|
+
addSpanEventSync('timber.cache.hit', {
|
|
71
84
|
key,
|
|
72
85
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
73
86
|
stale: true,
|
|
74
87
|
});
|
|
75
88
|
// Serve stale immediately, trigger background refetch
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Singleflight promise rejection handled — stale continues.
|
|
89
|
-
});
|
|
89
|
+
sf.do(`swr:${key}`, async () => {
|
|
90
|
+
try {
|
|
91
|
+
const fresh = await fn(...args);
|
|
92
|
+
const tags = resolveTags(opts, args);
|
|
93
|
+
await handler.set(key, fresh, { ttl: opts.ttl, tags });
|
|
94
|
+
} catch {
|
|
95
|
+
// Failed refetch — stale entry continues to be served.
|
|
96
|
+
// Error is swallowed per design doc: "Error is logged."
|
|
97
|
+
}
|
|
98
|
+
}).catch(() => {
|
|
99
|
+
// Singleflight promise rejection handled — stale continues.
|
|
100
|
+
});
|
|
90
101
|
return cached.value as Awaited<ReturnType<Fn>>;
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
// Cache miss (or stale without SWR) — execute with singleflight
|
|
94
|
-
const result = await
|
|
105
|
+
const result = await sf.do(key, () => fn(...args));
|
|
95
106
|
const tags = resolveTags(opts, args);
|
|
96
107
|
await handler.set(key, result, { ttl: opts.ttl, tags });
|
|
97
108
|
|
|
98
|
-
// Record cache miss as OTEL span event
|
|
99
|
-
|
|
109
|
+
// Record cache miss as OTEL span event (fire-and-forget).
|
|
110
|
+
addSpanEventSync('timber.cache.miss', {
|
|
100
111
|
key,
|
|
101
112
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
102
113
|
});
|
|
103
114
|
|
|
104
115
|
return result as Awaited<ReturnType<Fn>>;
|
|
105
|
-
};
|
|
116
|
+
}) as unknown as Fn;
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
/**
|
package/src/cli.ts
CHANGED
|
File without changes
|