@timber-js/app 0.2.0-alpha.4 → 0.2.0-alpha.40
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-Ba7URUIn.js} +1 -1
- package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
- package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
- package/dist/_chunks/debug-ECi_61pb.js +108 -0
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-cookie-BmKbSyp0.js +93 -0
- package/dist/_chunks/define-cookie-BmKbSyp0.js.map +1 -0
- package/dist/_chunks/error-boundary-BAN3751q.js +211 -0
- package/dist/_chunks/error-boundary-BAN3751q.js.map +1 -0
- package/dist/_chunks/{format-CwdaB0_2.js → format-cX7wzEp2.js} +2 -2
- package/dist/_chunks/{format-CwdaB0_2.js.map → format-cX7wzEp2.js.map} +1 -1
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-D2djYaIm.js} +112 -77
- package/dist/_chunks/interception-D2djYaIm.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
- package/dist/_chunks/{request-context-CZJi4CuK.js → request-context-BxYIJM24.js} +93 -69
- package/dist/_chunks/request-context-BxYIJM24.js.map +1 -0
- package/dist/_chunks/segment-context-C6byCyZU.js +69 -0
- package/dist/_chunks/segment-context-C6byCyZU.js.map +1 -0
- package/dist/_chunks/stale-reload-C0ValzG7.js +47 -0
- package/dist/_chunks/stale-reload-C0ValzG7.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → tracing-CuXiCP5p.js} +17 -3
- package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CuXiCP5p.js.map} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BvW0TKDn.js} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-BvW0TKDn.js.map} +1 -1
- package/dist/_chunks/wrappers-C6J0nNji.js +331 -0
- package/dist/_chunks/wrappers-C6J0nNji.js.map +1 -0
- 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/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/index.d.ts +5 -2
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +88 -18
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/register-cached-function.d.ts.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.map +1 -1
- package/dist/client/error-boundary.d.ts +10 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -125
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +213 -93
- package/dist/client/index.js.map +1 -1
- package/dist/client/link.d.ts +22 -8
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +2 -2
- package/dist/client/router.d.ts +25 -3
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +23 -2
- 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/stale-reload.d.ts +15 -0
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +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/client/use-params.d.ts +2 -2
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/codec.d.ts +21 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/cookies/define-cookie.d.ts +33 -12
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -83
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/fonts/local.d.ts +4 -2
- package/dist/fonts/local.d.ts.map +1 -1
- package/dist/index.d.ts +112 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +635 -233
- package/dist/index.js.map +1 -1
- package/dist/params/define.d.ts +76 -0
- package/dist/params/define.d.ts.map +1 -0
- package/dist/params/index.d.ts +8 -0
- package/dist/params/index.d.ts.map +1 -0
- package/dist/params/index.js +104 -0
- package/dist/params/index.js.map +1 -0
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.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/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +7 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +9 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +6 -0
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +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.js +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- 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 +6 -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/search-params/codecs.d.ts +1 -1
- package/dist/search-params/define.d.ts +153 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -5
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +3 -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/server/access-gate.d.ts +4 -0
- package/dist/server/access-gate.d.ts.map +1 -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/als-registry.d.ts +18 -4
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +2 -2
- package/dist/server/debug.d.ts +46 -15
- package/dist/server/debug.d.ts.map +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-renderer.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 +4 -0
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/flight-injection-state.d.ts +78 -0
- package/dist/server/flight-injection-state.d.ts.map +1 -0
- package/dist/server/flight-scripts.d.ts +39 -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 +5 -11
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1975 -1649
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +24 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +77 -0
- package/dist/server/node-stream-transforms.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +7 -4
- 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 +65 -38
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +7 -0
- 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 +2 -2
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +46 -3
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +6 -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 +9 -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/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/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +19 -12
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -3
- 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/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/state-machine.d.ts +80 -0
- package/dist/utils/state-machine.d.ts.map +1 -0
- package/package.json +17 -14
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/index.ts +5 -2
- package/src/cache/register-cached-function.ts +7 -3
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +34 -26
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +94 -90
- package/src/client/error-boundary.tsx +18 -1
- package/src/client/index.ts +10 -1
- package/src/client/link.tsx +78 -19
- package/src/client/navigation-context.ts +2 -2
- package/src/client/router.ts +105 -60
- package/src/client/rsc-fetch.ts +63 -2
- 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/stale-reload.ts +32 -6
- package/src/client/top-loader.tsx +10 -9
- package/src/client/transition-root.tsx +7 -1
- package/src/client/use-params.ts +3 -3
- package/src/client/use-query-states.ts +1 -1
- package/src/codec.ts +21 -0
- package/src/cookies/define-cookie.ts +69 -18
- package/src/fonts/css.ts +2 -1
- package/src/fonts/local.ts +7 -3
- package/src/index.ts +280 -85
- package/src/params/define.ts +260 -0
- package/src/params/index.ts +28 -0
- package/src/plugins/adapter-build.ts +6 -0
- package/src/plugins/build-manifest.ts +11 -0
- package/src/plugins/client-chunks.ts +65 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-server.ts +38 -4
- package/src/plugins/entries.ts +12 -11
- package/src/plugins/fonts.ts +171 -19
- package/src/plugins/mdx.ts +9 -5
- package/src/plugins/routing.ts +40 -14
- package/src/plugins/server-bundle.ts +32 -1
- package/src/plugins/shims.ts +1 -1
- package/src/plugins/static-build.ts +8 -4
- package/src/routing/codegen.ts +109 -88
- package/src/routing/scanner.ts +55 -6
- package/src/routing/status-file-lint.ts +2 -1
- package/src/routing/types.ts +7 -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 +1 -1
- package/src/search-params/define.ts +504 -0
- package/src/search-params/index.ts +12 -18
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/server/access-gate.tsx +40 -9
- package/src/server/action-client.ts +14 -5
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +19 -2
- package/src/server/als-registry.ts +18 -4
- package/src/server/build-manifest.ts +4 -4
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +55 -17
- package/src/server/default-logger.ts +98 -0
- package/src/server/deny-renderer.ts +2 -1
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +57 -14
- package/src/server/flight-injection-state.ts +152 -0
- package/src/server/flight-scripts.ts +59 -0
- package/src/server/flush.ts +2 -1
- package/src/server/form-data.ts +76 -0
- package/src/server/html-injectors.ts +103 -66
- package/src/server/index.ts +9 -4
- package/src/server/logger.ts +38 -35
- package/src/server/node-stream-transforms.ts +381 -0
- package/src/server/pipeline.ts +131 -39
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +112 -119
- package/src/server/route-element-builder.ts +106 -114
- package/src/server/route-handler.ts +2 -1
- package/src/server/route-matcher.ts +2 -2
- package/src/server/rsc-entry/error-renderer.ts +5 -3
- package/src/server/rsc-entry/helpers.ts +122 -3
- package/src/server/rsc-entry/index.ts +125 -49
- package/src/server/rsc-entry/rsc-payload.ts +52 -12
- package/src/server/rsc-entry/rsc-stream.ts +33 -8
- package/src/server/rsc-entry/ssr-renderer.ts +40 -13
- package/src/server/slot-resolver.ts +199 -210
- package/src/server/ssr-entry.ts +169 -17
- package/src/server/ssr-render.ts +266 -67
- package/src/server/tracing.ts +23 -0
- package/src/server/tree-builder.ts +91 -57
- package/src/server/types.ts +1 -3
- package/src/server/version-skew.ts +104 -0
- package/src/server/waituntil-bridge.ts +4 -1
- package/src/shared/merge-search-params.ts +48 -0
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +1 -1
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-B4WUeqJ-.js +0 -75
- package/dist/_chunks/debug-B4WUeqJ-.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/request-context-CZJi4CuK.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/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/cookies/index.js.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/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/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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
4
|
-
"description": "Vite-native React framework for
|
|
3
|
+
"version": "0.2.0-alpha.40",
|
|
4
|
+
"description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
7
7
|
"framework",
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
"types": "./dist/cookies/index.d.ts",
|
|
59
59
|
"import": "./dist/cookies/index.js"
|
|
60
60
|
},
|
|
61
|
+
"./params": {
|
|
62
|
+
"types": "./dist/params/index.d.ts",
|
|
63
|
+
"import": "./dist/params/index.js"
|
|
64
|
+
},
|
|
61
65
|
"./search-params": {
|
|
62
66
|
"types": "./dist/search-params/index.d.ts",
|
|
63
67
|
"import": "./dist/search-params/index.js"
|
|
@@ -79,15 +83,10 @@
|
|
|
79
83
|
"publishConfig": {
|
|
80
84
|
"access": "public"
|
|
81
85
|
},
|
|
82
|
-
"scripts": {
|
|
83
|
-
"build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
|
|
84
|
-
"typecheck": "tsgo --noEmit",
|
|
85
|
-
"prepublishOnly": "pnpm run build"
|
|
86
|
-
},
|
|
87
86
|
"dependencies": {
|
|
88
|
-
"@opentelemetry/api": "^1.9.
|
|
89
|
-
"@opentelemetry/context-async-hooks": "^2.6.
|
|
90
|
-
"@opentelemetry/sdk-trace-base": "^2.6.
|
|
87
|
+
"@opentelemetry/api": "^1.9.1",
|
|
88
|
+
"@opentelemetry/context-async-hooks": "^2.6.1",
|
|
89
|
+
"@opentelemetry/sdk-trace-base": "^2.6.1",
|
|
91
90
|
"nitro": "3.0.260311-beta"
|
|
92
91
|
},
|
|
93
92
|
"peerDependencies": {
|
|
@@ -98,8 +97,8 @@
|
|
|
98
97
|
"@vitejs/plugin-react": "^6.0.0",
|
|
99
98
|
"@vitejs/plugin-rsc": ">=0.5.21",
|
|
100
99
|
"nuqs": "^2.0.0",
|
|
101
|
-
"react": "
|
|
102
|
-
"react-dom": "
|
|
100
|
+
"react": "19.3.0-canary-ed69815c-20260323",
|
|
101
|
+
"react-dom": "19.3.0-canary-ed69815c-20260323",
|
|
103
102
|
"vite": "^8.0.1",
|
|
104
103
|
"zod": "^3.22.0 || ^4.0.0"
|
|
105
104
|
},
|
|
@@ -121,6 +120,10 @@
|
|
|
121
120
|
}
|
|
122
121
|
},
|
|
123
122
|
"engines": {
|
|
124
|
-
"node": ">=
|
|
123
|
+
"node": ">=22.12.0"
|
|
124
|
+
},
|
|
125
|
+
"scripts": {
|
|
126
|
+
"build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
|
|
127
|
+
"typecheck": "tsgo --noEmit"
|
|
125
128
|
}
|
|
126
|
-
}
|
|
129
|
+
}
|
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
export function generateCompressModule(): string {
|
|
23
23
|
return `// Generated by @timber-js/app — response compression for self-hosted deployments.
|
|
24
24
|
// Do not edit — this file is regenerated on each build.
|
|
25
|
-
// Uses
|
|
26
|
-
//
|
|
27
|
-
//
|
|
25
|
+
// Uses node:zlib createGzip() (C++ native) on Node.js, falls back to
|
|
26
|
+
// CompressionStream (Web API) on other runtimes. Brotli is left to CDNs/reverse
|
|
27
|
+
// proxies — at streaming quality levels its ratio advantage is marginal.
|
|
28
|
+
import { Readable } from 'node:stream';
|
|
29
|
+
import { createGzip, constants } from 'node:zlib';
|
|
28
30
|
|
|
29
31
|
const COMPRESSIBLE_TYPES = new Set([
|
|
30
32
|
'text/html', 'text/css', 'text/plain', 'text/xml', 'text/javascript',
|
|
@@ -71,7 +73,25 @@ function shouldCompress(response) {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
function compressWithGzip(body) {
|
|
74
|
-
|
|
76
|
+
// Use node:zlib (C++ native) for gzip compression. The Web Streams
|
|
77
|
+
// CompressionStream works but every chunk crosses the JS/Promise boundary.
|
|
78
|
+
// node:zlib.createGzip() compresses entirely in C++ with zero per-chunk
|
|
79
|
+
// Promise overhead.
|
|
80
|
+
//
|
|
81
|
+
// Convert: Web ReadableStream → Node Readable → pipe through gzip →
|
|
82
|
+
// Node Readable → Readable.toWeb() → Web ReadableStream
|
|
83
|
+
try {
|
|
84
|
+
const nodeReadable = Readable.fromWeb(body);
|
|
85
|
+
// Z_SYNC_FLUSH ensures each chunk is flushed immediately so the browser
|
|
86
|
+
// receives the HTML shell before Suspense boundaries resolve. Without it,
|
|
87
|
+
// gzip buffers internally and breaks streaming.
|
|
88
|
+
const gzip = createGzip({ flush: constants.Z_SYNC_FLUSH });
|
|
89
|
+
const compressed = nodeReadable.pipe(gzip);
|
|
90
|
+
return Readable.toWeb(compressed);
|
|
91
|
+
} catch {
|
|
92
|
+
// Fallback: CompressionStream (CF Workers, or if node:stream unavailable)
|
|
93
|
+
return body.pipeThrough(new CompressionStream('gzip'));
|
|
94
|
+
}
|
|
75
95
|
}
|
|
76
96
|
|
|
77
97
|
export function compressResponse(request, response) {
|
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,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
|
+
}
|
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 {
|
|
@@ -87,5 +90,5 @@ export { createCache } from './timber-cache';
|
|
|
87
90
|
export { registerCachedFunction } from './register-cached-function';
|
|
88
91
|
export type { RegisterCachedFunctionOptions } from './register-cached-function';
|
|
89
92
|
export { stableStringify } from './stable-stringify';
|
|
90
|
-
export { createSingleflight } from './singleflight';
|
|
91
|
-
export type { Singleflight } from './singleflight';
|
|
93
|
+
export { createSingleflight, SingleflightTimeoutError } from './singleflight';
|
|
94
|
+
export type { Singleflight, SingleflightOptions } from './singleflight';
|
|
@@ -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
|
/**
|
|
@@ -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
|
/**
|
|
@@ -49,6 +55,9 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
|
|
|
49
55
|
handler: CacheHandler
|
|
50
56
|
): (...args: Parameters<Fn>) => Promise<Awaited<ReturnType<Fn>>> {
|
|
51
57
|
const fnId = `timber-cache:${fnIdCounter++}`;
|
|
58
|
+
const sf = opts.timeoutMs
|
|
59
|
+
? createSingleflight({ timeoutMs: opts.timeoutMs })
|
|
60
|
+
: defaultSingleflight;
|
|
52
61
|
|
|
53
62
|
return async (...args: Parameters<Fn>): Promise<Awaited<ReturnType<Fn>>> => {
|
|
54
63
|
const key = opts.key ? opts.key(...args) : defaultKeyGenerator(fnId, args);
|
|
@@ -57,8 +66,9 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
|
|
|
57
66
|
const cached = await handler.get(key);
|
|
58
67
|
|
|
59
68
|
if (cached && !cached.stale) {
|
|
60
|
-
// Record as OTEL span event on enclosing span (not a child span)
|
|
61
|
-
|
|
69
|
+
// Record as OTEL span event on enclosing span (not a child span).
|
|
70
|
+
// Fire-and-forget — no microtask overhead on the cache hot path.
|
|
71
|
+
addSpanEventSync('timber.cache.hit', {
|
|
62
72
|
key,
|
|
63
73
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
64
74
|
});
|
|
@@ -66,37 +76,35 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
|
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
if (cached && cached.stale && opts.staleWhileRevalidate) {
|
|
69
|
-
// Record stale cache hit as OTEL span event
|
|
70
|
-
|
|
79
|
+
// Record stale cache hit as OTEL span event (fire-and-forget).
|
|
80
|
+
addSpanEventSync('timber.cache.hit', {
|
|
71
81
|
key,
|
|
72
82
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
73
83
|
stale: true,
|
|
74
84
|
});
|
|
75
85
|
// 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
|
-
});
|
|
86
|
+
sf.do(`swr:${key}`, async () => {
|
|
87
|
+
try {
|
|
88
|
+
const fresh = await fn(...args);
|
|
89
|
+
const tags = resolveTags(opts, args);
|
|
90
|
+
await handler.set(key, fresh, { ttl: opts.ttl, tags });
|
|
91
|
+
} catch {
|
|
92
|
+
// Failed refetch — stale entry continues to be served.
|
|
93
|
+
// Error is swallowed per design doc: "Error is logged."
|
|
94
|
+
}
|
|
95
|
+
}).catch(() => {
|
|
96
|
+
// Singleflight promise rejection handled — stale continues.
|
|
97
|
+
});
|
|
90
98
|
return cached.value as Awaited<ReturnType<Fn>>;
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
// Cache miss (or stale without SWR) — execute with singleflight
|
|
94
|
-
const result = await
|
|
102
|
+
const result = await sf.do(key, () => fn(...args));
|
|
95
103
|
const tags = resolveTags(opts, args);
|
|
96
104
|
await handler.set(key, result, { ttl: opts.ttl, tags });
|
|
97
105
|
|
|
98
|
-
// Record cache miss as OTEL span event
|
|
99
|
-
|
|
106
|
+
// Record cache miss as OTEL span event (fire-and-forget).
|
|
107
|
+
addSpanEventSync('timber.cache.miss', {
|
|
100
108
|
key,
|
|
101
109
|
duration_ms: Math.round(performance.now() - cacheStart),
|
|
102
110
|
});
|
package/src/cli.ts
CHANGED
|
File without changes
|