@nifrajs/web 4.0.2 → 5.0.0
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/README.md +11 -0
- package/dist/build-vite.d.ts +19 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +31 -10
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +8 -10
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +90 -31
- package/dist/build.js.map +1 -1
- package/dist/cdn.d.ts +8 -0
- package/dist/cdn.d.ts.map +1 -1
- package/dist/cdn.js +14 -15
- package/dist/cdn.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +25 -7
- package/dist/client.js.map +1 -1
- package/dist/deferred.d.ts +2 -2
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +28 -7
- package/dist/deferred.js.map +1 -1
- package/dist/dev-feed.d.ts.map +1 -1
- package/dist/dev-feed.js +24 -5
- package/dist/dev-feed.js.map +1 -1
- package/dist/dev-session.d.ts.map +1 -1
- package/dist/dev-session.js +4 -14
- package/dist/dev-session.js.map +1 -1
- package/dist/dev.d.ts +7 -17
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +13 -1
- package/dist/dev.js.map +1 -1
- package/dist/draft.d.ts +8 -5
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +30 -13
- package/dist/draft.js.map +1 -1
- package/dist/fonts-google.d.ts.map +1 -1
- package/dist/fonts-google.js +7 -0
- package/dist/fonts-google.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/codegen.d.ts +1 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +56 -29
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/disposable-dir.d.ts +8 -0
- package/dist/internal/disposable-dir.d.ts.map +1 -0
- package/dist/internal/disposable-dir.js +25 -0
- package/dist/internal/disposable-dir.js.map +1 -0
- package/dist/internal/entry-chunks.d.ts +9 -0
- package/dist/internal/entry-chunks.d.ts.map +1 -0
- package/dist/internal/entry-chunks.js +15 -0
- package/dist/internal/entry-chunks.js.map +1 -0
- package/dist/internal/evidence-traversal.d.ts +12 -0
- package/dist/internal/evidence-traversal.d.ts.map +1 -0
- package/dist/internal/evidence-traversal.js +48 -0
- package/dist/internal/evidence-traversal.js.map +1 -0
- package/dist/internal/head-merge.d.ts +6 -0
- package/dist/internal/head-merge.d.ts.map +1 -1
- package/dist/internal/head-merge.js +9 -0
- package/dist/internal/head-merge.js.map +1 -1
- package/dist/internal/js-string.d.ts +2 -0
- package/dist/internal/js-string.d.ts.map +1 -1
- package/dist/internal/js-string.js +5 -1
- package/dist/internal/js-string.js.map +1 -1
- package/dist/internal/mount-shadow.js +1 -1
- package/dist/internal/mount-shadow.js.map +1 -1
- package/dist/internal/output-guard.d.ts +7 -0
- package/dist/internal/output-guard.d.ts.map +1 -1
- package/dist/internal/output-guard.js +80 -8
- package/dist/internal/output-guard.js.map +1 -1
- package/dist/internal/page-execution.d.ts +6 -0
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +377 -108
- package/dist/internal/page-execution.js.map +1 -1
- package/dist/internal/progressive-cookies.d.ts +14 -0
- package/dist/internal/progressive-cookies.d.ts.map +1 -0
- package/dist/internal/progressive-cookies.js +43 -0
- package/dist/internal/progressive-cookies.js.map +1 -0
- package/dist/internal/public-env.d.ts +2 -0
- package/dist/internal/public-env.d.ts.map +1 -0
- package/dist/internal/public-env.js +13 -0
- package/dist/internal/public-env.js.map +1 -0
- package/dist/internal/render-document.d.ts +1 -0
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +28 -5
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/response-controls.d.ts +2 -0
- package/dist/internal/response-controls.d.ts.map +1 -1
- package/dist/internal/response-controls.js +26 -16
- package/dist/internal/response-controls.js.map +1 -1
- package/dist/internal/revive-dates.d.ts +16 -0
- package/dist/internal/revive-dates.d.ts.map +1 -0
- package/dist/internal/revive-dates.js +32 -0
- package/dist/internal/revive-dates.js.map +1 -0
- package/dist/internal/route-path.d.ts +8 -0
- package/dist/internal/route-path.d.ts.map +1 -0
- package/dist/internal/route-path.js +8 -0
- package/dist/internal/route-path.js.map +1 -0
- package/dist/internal/secret-scan.d.ts.map +1 -1
- package/dist/internal/secret-scan.js +88 -20
- package/dist/internal/secret-scan.js.map +1 -1
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +33 -5
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/web-app.d.ts +21 -16
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +73 -21
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/wire-date.d.ts +3 -0
- package/dist/internal/wire-date.d.ts.map +1 -0
- package/dist/internal/wire-date.js +14 -0
- package/dist/internal/wire-date.js.map +1 -0
- package/dist/internal/zone-graph.d.ts +4 -0
- package/dist/internal/zone-graph.d.ts.map +1 -1
- package/dist/internal/zone-graph.js +54 -14
- package/dist/internal/zone-graph.js.map +1 -1
- package/dist/isr.d.ts +58 -2
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +296 -39
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +2 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +29 -3
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +28 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +3 -0
- package/dist/manifest.js.map +1 -1
- package/dist/open-resolved.d.ts +18 -0
- package/dist/open-resolved.d.ts.map +1 -0
- package/dist/open-resolved.js +62 -0
- package/dist/open-resolved.js.map +1 -0
- package/dist/plugins/kit.d.ts +1 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +26 -11
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +3 -1
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +4 -0
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/public-dir.d.ts +8 -1
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +17 -6
- package/dist/public-dir.js.map +1 -1
- package/dist/query.d.ts +13 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +89 -26
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +2 -0
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +1 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-types.d.ts.map +1 -1
- package/dist/route-types.js +20 -9
- package/dist/route-types.js.map +1 -1
- package/dist/router.d.ts +9 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +39 -46
- package/dist/router.js.map +1 -1
- package/dist/server-components/build-info.d.ts +3 -0
- package/dist/server-components/build-info.d.ts.map +1 -0
- package/dist/server-components/build-info.js +7 -0
- package/dist/server-components/build-info.js.map +1 -0
- package/dist/server-components/build-target.d.ts +12 -0
- package/dist/server-components/build-target.d.ts.map +1 -0
- package/dist/server-components/build-target.js +153 -0
- package/dist/server-components/build-target.js.map +1 -0
- package/dist/server-components/build.d.ts +24 -0
- package/dist/server-components/build.d.ts.map +1 -0
- package/dist/server-components/build.js +366 -0
- package/dist/server-components/build.js.map +1 -0
- package/dist/server-components/bundle-proof.d.ts +3 -0
- package/dist/server-components/bundle-proof.d.ts.map +1 -0
- package/dist/server-components/bundle-proof.js +23 -0
- package/dist/server-components/bundle-proof.js.map +1 -0
- package/dist/server-components/client.d.ts +46 -0
- package/dist/server-components/client.d.ts.map +1 -0
- package/dist/server-components/client.js +594 -0
- package/dist/server-components/client.js.map +1 -0
- package/dist/server-components/definition.d.ts +11 -0
- package/dist/server-components/definition.d.ts.map +1 -0
- package/dist/server-components/definition.js +94 -0
- package/dist/server-components/definition.js.map +1 -0
- package/dist/server-components/dev-http.d.ts +5 -0
- package/dist/server-components/dev-http.d.ts.map +1 -0
- package/dist/server-components/dev-http.js +71 -0
- package/dist/server-components/dev-http.js.map +1 -0
- package/dist/server-components/error.d.ts +9 -0
- package/dist/server-components/error.d.ts.map +1 -0
- package/dist/server-components/error.js +24 -0
- package/dist/server-components/error.js.map +1 -0
- package/dist/server-components/frames.d.ts +7 -0
- package/dist/server-components/frames.d.ts.map +1 -0
- package/dist/server-components/frames.js +169 -0
- package/dist/server-components/frames.js.map +1 -0
- package/dist/server-components/html-policy.d.ts +6 -0
- package/dist/server-components/html-policy.d.ts.map +1 -0
- package/dist/server-components/html-policy.js +75 -0
- package/dist/server-components/html-policy.js.map +1 -0
- package/dist/server-components/html.d.ts +4 -0
- package/dist/server-components/html.d.ts.map +1 -0
- package/dist/server-components/html.js +67 -0
- package/dist/server-components/html.js.map +1 -0
- package/dist/server-components/islands.d.ts +44 -0
- package/dist/server-components/islands.d.ts.map +1 -0
- package/dist/server-components/islands.js +42 -0
- package/dist/server-components/islands.js.map +1 -0
- package/dist/server-components/native.d.ts +4 -0
- package/dist/server-components/native.d.ts.map +1 -0
- package/dist/server-components/native.js +81 -0
- package/dist/server-components/native.js.map +1 -0
- package/dist/server-components/portal.d.ts +6 -0
- package/dist/server-components/portal.d.ts.map +1 -0
- package/dist/server-components/portal.js +35 -0
- package/dist/server-components/portal.js.map +1 -0
- package/dist/server-components/props.d.ts +3 -0
- package/dist/server-components/props.d.ts.map +1 -0
- package/dist/server-components/props.js +14 -0
- package/dist/server-components/props.js.map +1 -0
- package/dist/server-components/protocol.d.ts +44 -0
- package/dist/server-components/protocol.d.ts.map +1 -0
- package/dist/server-components/protocol.js +139 -0
- package/dist/server-components/protocol.js.map +1 -0
- package/dist/server-components/range.d.ts +24 -0
- package/dist/server-components/range.d.ts.map +1 -0
- package/dist/server-components/range.js +263 -0
- package/dist/server-components/range.js.map +1 -0
- package/dist/server-components/registry.d.ts +6 -0
- package/dist/server-components/registry.d.ts.map +1 -0
- package/dist/server-components/registry.js +30 -0
- package/dist/server-components/registry.js.map +1 -0
- package/dist/server-components/response.d.ts +4 -0
- package/dist/server-components/response.d.ts.map +1 -0
- package/dist/server-components/response.js +32 -0
- package/dist/server-components/response.js.map +1 -0
- package/dist/server-components/router-integration.d.ts +9 -0
- package/dist/server-components/router-integration.d.ts.map +1 -0
- package/dist/server-components/router-integration.js +29 -0
- package/dist/server-components/router-integration.js.map +1 -0
- package/dist/server-components/runtime.d.ts +14 -0
- package/dist/server-components/runtime.d.ts.map +1 -0
- package/dist/server-components/runtime.js +276 -0
- package/dist/server-components/runtime.js.map +1 -0
- package/dist/server-components/scheduler.d.ts +14 -0
- package/dist/server-components/scheduler.d.ts.map +1 -0
- package/dist/server-components/scheduler.js +74 -0
- package/dist/server-components/scheduler.js.map +1 -0
- package/dist/server-components/schema.d.ts +4 -0
- package/dist/server-components/schema.d.ts.map +1 -0
- package/dist/server-components/schema.js +77 -0
- package/dist/server-components/schema.js.map +1 -0
- package/dist/server-components/server-entry.d.ts +12 -0
- package/dist/server-components/server-entry.d.ts.map +1 -0
- package/dist/server-components/server-entry.js +90 -0
- package/dist/server-components/server-entry.js.map +1 -0
- package/dist/server-components/source.d.ts +15 -0
- package/dist/server-components/source.d.ts.map +1 -0
- package/dist/server-components/source.js +269 -0
- package/dist/server-components/source.js.map +1 -0
- package/dist/server-components/state.d.ts +4 -0
- package/dist/server-components/state.d.ts.map +1 -0
- package/dist/server-components/state.js +123 -0
- package/dist/server-components/state.js.map +1 -0
- package/dist/server-components/stream.d.ts +6 -0
- package/dist/server-components/stream.d.ts.map +1 -0
- package/dist/server-components/stream.js +70 -0
- package/dist/server-components/stream.js.map +1 -0
- package/dist/server-components/trusted-html.d.ts +3 -0
- package/dist/server-components/trusted-html.d.ts.map +1 -0
- package/dist/server-components/trusted-html.js +14 -0
- package/dist/server-components/trusted-html.js.map +1 -0
- package/dist/server-components/types.d.ts +107 -0
- package/dist/server-components/types.d.ts.map +1 -0
- package/dist/server-components/types.js +2 -0
- package/dist/server-components/types.js.map +1 -0
- package/dist/server-components/typescript.d.ts +11 -0
- package/dist/server-components/typescript.d.ts.map +1 -0
- package/dist/server-components/typescript.js +28 -0
- package/dist/server-components/typescript.js.map +1 -0
- package/dist/server-components/value.d.ts +5 -0
- package/dist/server-components/value.d.ts.map +1 -0
- package/dist/server-components/value.js +77 -0
- package/dist/server-components/value.js.map +1 -0
- package/dist/server-components/vite.d.ts +12 -0
- package/dist/server-components/vite.d.ts.map +1 -0
- package/dist/server-components/vite.js +197 -0
- package/dist/server-components/vite.js.map +1 -0
- package/dist/server-components.d.ts +5 -0
- package/dist/server-components.d.ts.map +1 -0
- package/dist/server-components.js +4 -0
- package/dist/server-components.js.map +1 -0
- package/dist/service-worker.d.ts +8 -1
- package/dist/service-worker.d.ts.map +1 -1
- package/dist/service-worker.js +20 -6
- package/dist/service-worker.js.map +1 -1
- package/dist/vite.d.ts +12 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +7 -1
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +40 -0
- package/dist/zones.d.ts.map +1 -1
- package/dist/zones.js +170 -6
- package/dist/zones.js.map +1 -1
- package/package.json +97 -4
- package/src/build-vite.ts +31 -9
- package/src/build.ts +109 -31
- package/src/cdn.ts +18 -12
- package/src/client.ts +25 -6
- package/src/deferred.ts +27 -9
- package/src/dev-feed.ts +24 -5
- package/src/dev-session.ts +4 -13
- package/src/dev.ts +29 -1
- package/src/draft.ts +35 -14
- package/src/fonts-google.ts +10 -0
- package/src/index.ts +3 -2
- package/src/internal/codegen.ts +60 -29
- package/src/internal/disposable-dir.ts +24 -0
- package/src/internal/entry-chunks.ts +18 -0
- package/src/internal/evidence-traversal.ts +57 -0
- package/src/internal/head-merge.ts +16 -0
- package/src/internal/js-string.ts +6 -1
- package/src/internal/mount-shadow.ts +1 -1
- package/src/internal/output-guard.ts +88 -7
- package/src/internal/page-execution.ts +506 -108
- package/src/internal/progressive-cookies.ts +60 -0
- package/src/internal/public-env.ts +14 -0
- package/src/internal/render-document.ts +26 -11
- package/src/internal/response-controls.ts +31 -14
- package/src/internal/revive-dates.ts +35 -0
- package/src/internal/route-path.ts +8 -0
- package/src/internal/secret-scan.ts +90 -18
- package/src/internal/server-boundary.ts +29 -5
- package/src/internal/web-app.ts +116 -37
- package/src/internal/wire-date.ts +16 -0
- package/src/internal/zone-graph.ts +59 -19
- package/src/isr.ts +351 -43
- package/src/llms-txt.ts +32 -3
- package/src/manifest.ts +28 -4
- package/src/open-resolved.ts +64 -0
- package/src/plugins/kit.ts +31 -11
- package/src/plugins/vite-leak-guard.ts +5 -1
- package/src/public-dir.ts +27 -8
- package/src/query.ts +107 -26
- package/src/render-seam.ts +2 -0
- package/src/route-types.ts +34 -7
- package/src/router.ts +58 -55
- package/src/server-components/build-info.ts +11 -0
- package/src/server-components/build-target.ts +194 -0
- package/src/server-components/build.ts +419 -0
- package/src/server-components/bundle-proof.ts +33 -0
- package/src/server-components/client.ts +663 -0
- package/src/server-components/definition.ts +125 -0
- package/src/server-components/dev-http.ts +75 -0
- package/src/server-components/error.ts +24 -0
- package/src/server-components/frames.ts +181 -0
- package/src/server-components/html-policy.ts +94 -0
- package/src/server-components/html.ts +81 -0
- package/src/server-components/islands.ts +111 -0
- package/src/server-components/native.ts +101 -0
- package/src/server-components/portal.ts +42 -0
- package/src/server-components/props.ts +19 -0
- package/src/server-components/protocol.ts +167 -0
- package/src/server-components/range.ts +313 -0
- package/src/server-components/registry.ts +34 -0
- package/src/server-components/response.ts +44 -0
- package/src/server-components/router-integration.ts +40 -0
- package/src/server-components/runtime.ts +376 -0
- package/src/server-components/scheduler.ts +84 -0
- package/src/server-components/schema.ts +69 -0
- package/src/server-components/server-entry.ts +98 -0
- package/src/server-components/source.ts +324 -0
- package/src/server-components/state.ts +143 -0
- package/src/server-components/stream.ts +71 -0
- package/src/server-components/trusted-html.ts +21 -0
- package/src/server-components/types.ts +138 -0
- package/src/server-components/typescript.ts +39 -0
- package/src/server-components/value.ts +82 -0
- package/src/server-components/vite.ts +242 -0
- package/src/server-components.ts +32 -0
- package/src/service-worker.ts +24 -7
- package/src/vite.ts +8 -1
- package/src/zones.ts +208 -7
package/src/deferred.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* resolution scripts settle. The per-adapter `<Await>` lives in `@nifrajs/web-{solid,react}/client`.
|
|
7
7
|
*/
|
|
8
8
|
import { plainJsonCodec, type TransportCodec } from "@nifrajs/core/transport-codec"
|
|
9
|
+
import { parsePageData } from "./internal/revive-dates.ts"
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* A loader value marked to stream in after the shell. The component consumes it with the adapter's
|
|
@@ -164,25 +165,29 @@ export function ndjsonStream(
|
|
|
164
165
|
deferred: ReadonlyArray<{ readonly id: number; readonly promise: Promise<unknown> }>,
|
|
165
166
|
codec: TransportCodec = plainJsonCodec,
|
|
166
167
|
): ReadableStream<Uint8Array> {
|
|
168
|
+
// A client that goes away cancels the body; a value settling after that has nowhere to go.
|
|
169
|
+
let cancelled = false
|
|
167
170
|
return new ReadableStream({
|
|
168
171
|
async start(controller) {
|
|
169
172
|
controller.enqueue(NDJSON_ENCODER.encode(`${codec.encode(forClient)}\n`))
|
|
170
173
|
await Promise.all(
|
|
171
174
|
deferred.map(async (d) => {
|
|
175
|
+
let line: string
|
|
172
176
|
try {
|
|
173
|
-
|
|
174
|
-
controller.enqueue(NDJSON_ENCODER.encode(`${codec.encode({ i: d.id, v: value })}\n`))
|
|
177
|
+
line = codec.encode({ i: d.id, v: await d.promise })
|
|
175
178
|
} catch (err) {
|
|
176
179
|
// Redact: stream a stable opaque code, never the raw error text; log the real reason
|
|
177
180
|
// server-side. The client's <Await> errorFallback receives this code.
|
|
178
181
|
console.error("[nifra/web] deferred value rejected:", err)
|
|
179
|
-
|
|
180
|
-
NDJSON_ENCODER.encode(`${codec.encode({ i: d.id, e: DEFERRED_ERROR_CODE })}\n`),
|
|
181
|
-
)
|
|
182
|
+
line = codec.encode({ i: d.id, e: DEFERRED_ERROR_CODE })
|
|
182
183
|
}
|
|
184
|
+
if (!cancelled) controller.enqueue(NDJSON_ENCODER.encode(`${line}\n`))
|
|
183
185
|
}),
|
|
184
186
|
)
|
|
185
|
-
controller.close()
|
|
187
|
+
if (!cancelled) controller.close()
|
|
188
|
+
},
|
|
189
|
+
cancel() {
|
|
190
|
+
cancelled = true
|
|
186
191
|
},
|
|
187
192
|
})
|
|
188
193
|
}
|
|
@@ -213,7 +218,16 @@ export const DEFERRED_RUNTIME = `(() => {
|
|
|
213
218
|
// Tag the promise (status/value/reason) - React's use() reads them and returns SYNCHRONOUSLY at
|
|
214
219
|
// hydration, so it renders the resolved content directly into the <Suspense> boundary the server
|
|
215
220
|
// streamed (no re-suspend, no fallback flash). r/j also wake awaiters (Solid uses its own _$HY).
|
|
216
|
-
|
|
221
|
+
// A t.date() value arrives tagged; rebuild it before anything reads the settled value.
|
|
222
|
+
const __nifraDates = (d) => {
|
|
223
|
+
if (!d || typeof d !== "object") return d
|
|
224
|
+
if (Array.isArray(d)) { for (let i = 0; i < d.length; i++) d[i] = __nifraDates(d[i]); return d }
|
|
225
|
+
const k = Object.keys(d)
|
|
226
|
+
if (k.length === 1 && k[0] === "__nifra_date" && typeof d.__nifra_date === "string") return new Date(d.__nifra_date)
|
|
227
|
+
for (const x of k) { const v = __nifraDates(d[x]); if (v !== d[x]) Object.defineProperty(d, x, { value: v, enumerable: true, writable: true, configurable: true }) }
|
|
228
|
+
return d
|
|
229
|
+
};
|
|
230
|
+
window.__nifraResolve = (id, v) => { v = __nifraDates(v); const e = get(id); e.p.status = "fulfilled"; e.p.value = v; e.r(v); };
|
|
217
231
|
window.__nifraReject = (id, x) => { const e = get(id); e.p.status = "rejected"; e.p.reason = x; e.j(x); };
|
|
218
232
|
})();`
|
|
219
233
|
|
|
@@ -225,6 +239,7 @@ export const DEFERRED_RUNTIME = `(() => {
|
|
|
225
239
|
export const MAP_DEFERRED_SOURCE = `const mapDeferred = (d) => {
|
|
226
240
|
if (!d || typeof d !== "object") return d
|
|
227
241
|
if (typeof d.__nifra_deferred === "number") return { __nifra_deferred: true, id: d.__nifra_deferred, promise: window.__nifraDeferred(d.__nifra_deferred) }
|
|
242
|
+
if (typeof d.__nifra_date === "string" && Object.keys(d).length === 1) return new Date(d.__nifra_date)
|
|
228
243
|
if (Array.isArray(d)) return d.map(mapDeferred)
|
|
229
244
|
const out = {}
|
|
230
245
|
for (const k of Object.keys(d)) Object.defineProperty(out, k, { value: mapDeferred(d[k]), enumerable: true, writable: true, configurable: true })
|
|
@@ -326,8 +341,11 @@ export async function parseNdjsonData(
|
|
|
326
341
|
}
|
|
327
342
|
|
|
328
343
|
const pending: Pending = new Map()
|
|
344
|
+
// Only plain JSON carries t.date() tags; a richer codec decodes its own dates.
|
|
345
|
+
const decode = (line: string): unknown =>
|
|
346
|
+
codec === plainJsonCodec ? parsePageData(line) : codec.decode(line)
|
|
329
347
|
const first = await readLine()
|
|
330
|
-
const data = markersFromPlaceholders(first === null ? null :
|
|
348
|
+
const data = markersFromPlaceholders(first === null ? null : decode(first), pending)
|
|
331
349
|
|
|
332
350
|
void (async () => {
|
|
333
351
|
try {
|
|
@@ -335,7 +353,7 @@ export async function parseNdjsonData(
|
|
|
335
353
|
const line = await readLine()
|
|
336
354
|
if (line === null) break
|
|
337
355
|
if (line === "") continue
|
|
338
|
-
const msg =
|
|
356
|
+
const msg = decode(line) as { i: number; v?: unknown; e?: string }
|
|
339
357
|
const entry = pending.get(msg.i)
|
|
340
358
|
if (entry === undefined) continue
|
|
341
359
|
pending.delete(msg.i)
|
package/src/dev-feed.ts
CHANGED
|
@@ -392,6 +392,25 @@ function fnv1a(text: string): string {
|
|
|
392
392
|
return (hash >>> 0).toString(16).padStart(8, "0")
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
/**
|
|
396
|
+
* A query value may be a single-use ticket or a token no redaction pattern knows, so the feed keeps
|
|
397
|
+
* which keys a request sent and never their values. `/x?ticket=abc&page` -> `/x?ticket=[redacted]&[redacted]`.
|
|
398
|
+
*/
|
|
399
|
+
function queryKeysOnly(target: string): string {
|
|
400
|
+
const at = target.indexOf("?")
|
|
401
|
+
if (at === -1) return target
|
|
402
|
+
const hash = target.indexOf("#", at)
|
|
403
|
+
const parts = target
|
|
404
|
+
.slice(at + 1, hash === -1 ? undefined : hash)
|
|
405
|
+
.split("&")
|
|
406
|
+
.filter((part) => part.length > 0)
|
|
407
|
+
.map((part) => {
|
|
408
|
+
const eq = part.indexOf("=")
|
|
409
|
+
return eq === -1 ? "[redacted]" : `${part.slice(0, eq)}=[redacted]`
|
|
410
|
+
})
|
|
411
|
+
return parts.length === 0 ? target.slice(0, at) : `${target.slice(0, at)}?${parts.join("&")}`
|
|
412
|
+
}
|
|
413
|
+
|
|
395
414
|
const cap = (text: string, max: number): string =>
|
|
396
415
|
text.length <= max ? text : `${text.slice(0, max)}... [${text.length - max} more chars]`
|
|
397
416
|
|
|
@@ -477,7 +496,7 @@ export function createDevFeed(options: DevFeedOptions): DevFeed {
|
|
|
477
496
|
if (diagnostic.request !== undefined) {
|
|
478
497
|
redacted.request = {
|
|
479
498
|
method: diagnostic.request.method,
|
|
480
|
-
url: cap(redactText(diagnostic.request.url), 2048),
|
|
499
|
+
url: cap(redactText(queryKeysOnly(diagnostic.request.url)), 2048),
|
|
481
500
|
}
|
|
482
501
|
}
|
|
483
502
|
if (diagnostic.codeframe !== undefined) {
|
|
@@ -530,7 +549,7 @@ export function createDevFeed(options: DevFeedOptions): DevFeed {
|
|
|
530
549
|
record.resolvedAt = undefined
|
|
531
550
|
if (requestId !== undefined) record.requestId = requestId
|
|
532
551
|
if (meta.route !== undefined) record.route = meta.route
|
|
533
|
-
if (meta.page !== undefined) record.page = meta.page
|
|
552
|
+
if (meta.page !== undefined) record.page = cap(redactText(queryKeysOnly(meta.page)), 2048)
|
|
534
553
|
// Re-insert: Map order is recency order, which eviction relies on.
|
|
535
554
|
errors.delete(key)
|
|
536
555
|
errors.set(key, record)
|
|
@@ -557,7 +576,7 @@ export function createDevFeed(options: DevFeedOptions): DevFeed {
|
|
|
557
576
|
message: text,
|
|
558
577
|
}
|
|
559
578
|
if (requestId !== undefined) entry.requestId = requestId
|
|
560
|
-
if (meta.page !== undefined) entry.page = meta.page
|
|
579
|
+
if (meta.page !== undefined) entry.page = cap(redactText(queryKeysOnly(meta.page)), 2048)
|
|
561
580
|
logs.push(entry)
|
|
562
581
|
logBytes += text.length
|
|
563
582
|
if (logs.length > limits.logs || logBytes > limits.logBytes) {
|
|
@@ -607,7 +626,7 @@ export function createDevFeed(options: DevFeedOptions): DevFeed {
|
|
|
607
626
|
seq,
|
|
608
627
|
requestId,
|
|
609
628
|
method,
|
|
610
|
-
path: cap(redactText(path), 2048),
|
|
629
|
+
path: cap(redactText(queryKeysOnly(path)), 2048),
|
|
611
630
|
at: nowIso(),
|
|
612
631
|
status: undefined,
|
|
613
632
|
durationMs: undefined,
|
|
@@ -856,7 +875,7 @@ export function writeDevServerRecord(root: string, record: DevServerRecord): voi
|
|
|
856
875
|
|
|
857
876
|
/** Remove the record only while it is still this server's: a newer server may have replaced it. */
|
|
858
877
|
export function removeDevServerRecord(root: string, token: string): void {
|
|
859
|
-
//
|
|
878
|
+
// nifra-expect NF-S002: the server comparing its own token with a file on this machine; no request
|
|
860
879
|
// reaches this comparison.
|
|
861
880
|
if (readDevServerRecord(root)?.token !== token) return
|
|
862
881
|
rmSync(join(root, DEV_SERVER_RECORD_FILE), { force: true })
|
package/src/dev-session.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* either. `/__nifra/last-error` keeps its older, token-free contract behind the same Host check.
|
|
9
9
|
*/
|
|
10
10
|
import { performance } from "node:perf_hooks"
|
|
11
|
+
import { writableResponse } from "@nifrajs/core/server"
|
|
11
12
|
import {
|
|
12
13
|
type BrowserError,
|
|
13
14
|
CLIENT_BATCH_MAX_BYTES,
|
|
@@ -649,19 +650,9 @@ export function createDevSession(options: DevSessionOptions): DevSession {
|
|
|
649
650
|
},
|
|
650
651
|
performance.now() - started,
|
|
651
652
|
)
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
} catch {
|
|
656
|
-
// `Response.redirect()` and fetched responses carry immutable headers on some runtimes.
|
|
657
|
-
const headers = new Headers(response.headers)
|
|
658
|
-
headers.set(DEV_REQUEST_ID_HEADER, requestId)
|
|
659
|
-
return new Response(response.body, {
|
|
660
|
-
status: response.status,
|
|
661
|
-
statusText: response.statusText,
|
|
662
|
-
headers,
|
|
663
|
-
})
|
|
664
|
-
}
|
|
653
|
+
const target = writableResponse(response)
|
|
654
|
+
target.headers.set(DEV_REQUEST_ID_HEADER, requestId)
|
|
655
|
+
return target
|
|
665
656
|
}
|
|
666
657
|
|
|
667
658
|
return {
|
package/src/dev.ts
CHANGED
|
@@ -58,7 +58,12 @@ import { dirname, relative, resolve } from "node:path"
|
|
|
58
58
|
import { type BuildClientOptions, buildClient } from "./build.ts"
|
|
59
59
|
import { type DevEntryMatch, resolveDevEntry } from "./bun-dev-entry.ts"
|
|
60
60
|
import { explainBindFailure } from "./dev-port.ts"
|
|
61
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
createDevSession,
|
|
63
|
+
type DevAppHooks,
|
|
64
|
+
type DevSession,
|
|
65
|
+
isLoopbackHost,
|
|
66
|
+
} from "./dev-session.ts"
|
|
62
67
|
import { createSsrGraph, type SsrGraph } from "./dev-ssr-graph.ts"
|
|
63
68
|
import { discoverRoutes } from "./fs.ts"
|
|
64
69
|
import { DEFAULT_DEV_PORT, generateClientEntry } from "./index.ts"
|
|
@@ -112,6 +117,13 @@ export interface DevServerOptions extends Omit<BuildClientOptions, "minify"> {
|
|
|
112
117
|
* fix (default `true`). Off, the errors still reach the feed.
|
|
113
118
|
*/
|
|
114
119
|
readonly indicator?: boolean
|
|
120
|
+
/**
|
|
121
|
+
* Host names the dev server answers besides loopback ones (`localhost`, `*.localhost`, `127.0.0.1`,
|
|
122
|
+
* `[::1]`), port ignored. Every other `Host` gets a 403: a page on another site whose name a DNS
|
|
123
|
+
* rebind points at 127.0.0.1 would otherwise read the dev server's pages, loader data and error
|
|
124
|
+
* overlay. Add a name here when a hosts-file entry or tunnel reaches the server under it.
|
|
125
|
+
*/
|
|
126
|
+
readonly allowedHosts?: readonly string[]
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
export interface DevServer {
|
|
@@ -465,6 +477,7 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
465
477
|
}
|
|
466
478
|
}
|
|
467
479
|
|
|
480
|
+
const allowedHosts = new Set((options.allowedHosts ?? []).map(hostName))
|
|
468
481
|
try {
|
|
469
482
|
server = serve({
|
|
470
483
|
port,
|
|
@@ -474,6 +487,12 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
474
487
|
routes: { [PROBE_PATH]: htmlModule.default },
|
|
475
488
|
fetch: async (req: Request): Promise<Response> => {
|
|
476
489
|
const url = new URL(req.url)
|
|
490
|
+
const host = req.headers.get("host") ?? url.host
|
|
491
|
+
if (!isLoopbackHost(host) && !allowedHosts.has(hostName(host)))
|
|
492
|
+
return new Response(
|
|
493
|
+
`[nifra/web/dev] ${hostName(host)} is not a loopback host; add it to allowedHosts if this server should answer it.\n`,
|
|
494
|
+
{ status: 403, headers: { "content-type": "text/plain; charset=utf-8" } },
|
|
495
|
+
)
|
|
477
496
|
// The stable client-entry URL. Pages reference THIS; it redirects to whichever hashed chunk Bun
|
|
478
497
|
// is serving at the moment the browser asks. A redirect rather than a proxy so the module's final
|
|
479
498
|
// URL is Bun's own - its HMR client derives its socket and module identity from `import.meta.url`,
|
|
@@ -620,6 +639,15 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
620
639
|
* whose message is just "Bundle failed" - the actionable part (which file, which import) lives in
|
|
621
640
|
* `.errors`. Surface it, or the guard reports a failure while hiding the reason.
|
|
622
641
|
*/
|
|
642
|
+
/** A Host header's name: lowercased, without its port or IPv6 brackets. */
|
|
643
|
+
function hostName(host: string): string {
|
|
644
|
+
const name = host.trim().toLowerCase()
|
|
645
|
+
if (name.startsWith("["))
|
|
646
|
+
return name.slice(1, name.indexOf("]") === -1 ? undefined : name.indexOf("]"))
|
|
647
|
+
const colon = name.lastIndexOf(":")
|
|
648
|
+
return colon === -1 ? name : name.slice(0, colon)
|
|
649
|
+
}
|
|
650
|
+
|
|
623
651
|
export function buildFailureDetail(err: unknown): string {
|
|
624
652
|
return err instanceof AggregateError && err.errors.length > 0
|
|
625
653
|
? err.errors.map((e) => ` ${e instanceof Error ? e.message : String(e)}`).join("\n")
|
package/src/draft.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Draft / preview mode - let an editor see unpublished content by flipping a signed cookie that
|
|
3
3
|
* loaders read (`ctx.draft`) and ISR bypasses (fresh render, never cached). The cookie is **HMAC-signed**
|
|
4
|
-
* (so a visitor can't forge it)
|
|
4
|
+
* (so a visitor can't forge it), **HttpOnly** (JS can't read it) and carries its own expiry, which the
|
|
5
|
+
* server checks (a copied cookie stops working when the browser would have dropped it). You gate
|
|
6
|
+
* `enableDraft` yourself -
|
|
5
7
|
* behind an editor login or a `?token=` check - exactly like Next's `draftMode().enable()`.
|
|
6
8
|
*
|
|
7
9
|
* // a route you've protected (e.g. checked ?token= against a secret):
|
|
@@ -26,9 +28,23 @@ import { assertTokenSecret, timingSafeEqual } from "./internal/timing-safe-equal
|
|
|
26
28
|
/** The cookie name nifra uses for draft/preview mode. */
|
|
27
29
|
export const DRAFT_COOKIE = "__nifra_draft"
|
|
28
30
|
|
|
29
|
-
// The signed
|
|
30
|
-
//
|
|
31
|
-
const
|
|
31
|
+
// The signed payload is `1:<expiry in Unix seconds>`. A valid signature says the server issued it; the
|
|
32
|
+
// expiry bounds a replayed copy, which Max-Age alone cannot, since a client can resend any cookie it holds.
|
|
33
|
+
const DRAFT_PREFIX = "1:"
|
|
34
|
+
|
|
35
|
+
function draftPayload(options: EnableDraftOptions): string {
|
|
36
|
+
return `${DRAFT_PREFIX}${Math.floor(Date.now() / 1000) + draftMaxAge(options)}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function draftMaxAge(options: EnableDraftOptions): number {
|
|
40
|
+
const seconds = options.maxAgeSeconds ?? 3600
|
|
41
|
+
if (!Number.isSafeInteger(seconds) || seconds <= 0) {
|
|
42
|
+
throw new RangeError(
|
|
43
|
+
`[nifra] draft maxAgeSeconds must be a positive whole number of seconds, got ${seconds}`,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return seconds
|
|
47
|
+
}
|
|
32
48
|
|
|
33
49
|
/** The response-cookie surface `enableDraft`/`disableDraft` need - nifra's `c.set`. Structural, so any
|
|
34
50
|
* nifra handler context satisfies it without importing the full `Context`. */
|
|
@@ -38,7 +54,8 @@ export interface DraftCookieControls {
|
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
export interface EnableDraftOptions {
|
|
41
|
-
/**
|
|
57
|
+
/** Draft session lifetime in **seconds** (default `3600` = 1h): the cookie's `Max-Age`, and the expiry
|
|
58
|
+
* signed into the cookie that the server enforces. Keep it short - draft is an editor session. */
|
|
42
59
|
readonly maxAgeSeconds?: number
|
|
43
60
|
/** Cookie `Path` (default `"/"`). */
|
|
44
61
|
readonly path?: string
|
|
@@ -57,8 +74,8 @@ export async function enableDraft(
|
|
|
57
74
|
secret: string,
|
|
58
75
|
options: EnableDraftOptions = {},
|
|
59
76
|
): Promise<void> {
|
|
60
|
-
const
|
|
61
|
-
c.set.cookie(DRAFT_COOKIE,
|
|
77
|
+
const cookieOptions = draftCookieOptions(options)
|
|
78
|
+
c.set.cookie(DRAFT_COOKIE, await signValue(draftPayload(options), secret), cookieOptions)
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
/**
|
|
@@ -77,7 +94,7 @@ function draftCookieOptions(options: EnableDraftOptions): CookieOptions {
|
|
|
77
94
|
httpOnly: true,
|
|
78
95
|
sameSite: "lax",
|
|
79
96
|
path: options.path ?? "/",
|
|
80
|
-
maxAge: options
|
|
97
|
+
maxAge: draftMaxAge(options),
|
|
81
98
|
secure: options.secure ?? true,
|
|
82
99
|
}
|
|
83
100
|
}
|
|
@@ -91,14 +108,17 @@ export function disableDraft(
|
|
|
91
108
|
}
|
|
92
109
|
|
|
93
110
|
/**
|
|
94
|
-
* Whether `request` carries a **valid
|
|
95
|
-
* `createWebApp` uses it to set `ctx.draft`; `withISR` uses it to bypass the cache for
|
|
96
|
-
* missing, forged, or
|
|
111
|
+
* Whether `request` carries a **valid**, unexpired signed draft cookie (constant-time verify via
|
|
112
|
+
* `unsignValue`). `createWebApp` uses it to set `ctx.draft`; `withISR` uses it to bypass the cache for
|
|
113
|
+
* editors. A missing, forged, tampered, or expired cookie returns `false`.
|
|
97
114
|
*/
|
|
98
115
|
export async function isDraftEnabled(request: Request, secret: string): Promise<boolean> {
|
|
99
116
|
const cookie = parseCookies(request.headers.get("cookie"))[DRAFT_COOKIE]
|
|
100
117
|
if (cookie === undefined) return false
|
|
101
|
-
|
|
118
|
+
const payload = await unsignValue(cookie, secret)
|
|
119
|
+
if (payload === null || !payload.startsWith(DRAFT_PREFIX)) return false
|
|
120
|
+
const expires = Number(payload.slice(DRAFT_PREFIX.length))
|
|
121
|
+
return Number.isSafeInteger(expires) && Date.now() / 1000 < expires
|
|
102
122
|
}
|
|
103
123
|
|
|
104
124
|
/**
|
|
@@ -173,7 +193,8 @@ export function previewEndpoint(
|
|
|
173
193
|
`[nifra] previewEndpoint fallbackPath must be a site-relative path, got ${JSON.stringify(fallbackPath)}`,
|
|
174
194
|
)
|
|
175
195
|
}
|
|
176
|
-
const
|
|
196
|
+
const draftOptions = options.cookie ?? {}
|
|
197
|
+
const cookieOptions = draftCookieOptions(draftOptions)
|
|
177
198
|
return async (request) => {
|
|
178
199
|
const url = new URL(request.url)
|
|
179
200
|
if (!timingSafeEqual(url.searchParams.get(tokenParam) ?? "", options.secret)) {
|
|
@@ -184,7 +205,7 @@ export function previewEndpoint(
|
|
|
184
205
|
if (destination === null) {
|
|
185
206
|
return Response.json({ ok: false, error: "invalid_redirect" }, { status: 400 })
|
|
186
207
|
}
|
|
187
|
-
const signed = await signValue(
|
|
208
|
+
const signed = await signValue(draftPayload(draftOptions), options.draftSecret)
|
|
188
209
|
return new Response(null, {
|
|
189
210
|
status: 302,
|
|
190
211
|
headers: {
|
package/src/fonts-google.ts
CHANGED
|
@@ -113,6 +113,10 @@ const DEFAULT_MAX_FONT_BYTES = 5 * 1024 * 1024
|
|
|
113
113
|
const MAX_CSS_BYTES = 512 * 1024
|
|
114
114
|
const MAX_TEXT_LEN = 2048
|
|
115
115
|
|
|
116
|
+
/** A face's weight as Google writes it: `400`, a variable `100 900`, or a keyword. It lands in the
|
|
117
|
+
* written file's name, so anything else (a tampered `400/../..`) is refused. */
|
|
118
|
+
const FACE_WEIGHT = /^(?:\d{1,4}(?: \d{1,4})?|normal|bold)$/
|
|
119
|
+
|
|
116
120
|
const VALID_DISPLAY: ReadonlySet<string> = new Set([
|
|
117
121
|
"auto",
|
|
118
122
|
"block",
|
|
@@ -420,6 +424,12 @@ export async function loadGoogleFont(
|
|
|
420
424
|
"the stylesheet may be tampered with",
|
|
421
425
|
)
|
|
422
426
|
}
|
|
427
|
+
if (!FACE_WEIGHT.test(face.weight)) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
`loadGoogleFont: refusing an unexpected font-weight ${JSON.stringify(face.weight.slice(0, 40))}; ` +
|
|
430
|
+
"the stylesheet may be tampered with",
|
|
431
|
+
)
|
|
432
|
+
}
|
|
423
433
|
const bytes = await fetchBounded(fetchImpl, source.url, maxBytes)
|
|
424
434
|
const hash = await contentHash(bytes)
|
|
425
435
|
const style = face.style === "italic" ? "italic" : "normal"
|
package/src/index.ts
CHANGED
|
@@ -85,8 +85,8 @@ export {
|
|
|
85
85
|
generateServerManifest,
|
|
86
86
|
} from "./internal/codegen.ts"
|
|
87
87
|
// Generated client entries intentionally import the browser router from the client subpath:
|
|
88
|
-
// `import { createClientRouter, createMatcher, mergeHeads,
|
|
89
|
-
export { mergeHeads, resolveMeta } from "./internal/head-merge.ts"
|
|
88
|
+
// `import { createClientRouter, createMatcher, mergeHeads, resolveChainMeta } from "@nifrajs/web/client"`.
|
|
89
|
+
export { mergeHeads, resolveChainMeta, resolveMeta } from "./internal/head-merge.ts"
|
|
90
90
|
export {
|
|
91
91
|
canonical,
|
|
92
92
|
gone,
|
|
@@ -141,6 +141,7 @@ export {
|
|
|
141
141
|
type ISROptions,
|
|
142
142
|
type ISRPlatform,
|
|
143
143
|
type ISRQuery,
|
|
144
|
+
type ISRStoreOp,
|
|
144
145
|
isCacheablePage,
|
|
145
146
|
KVCacheStore,
|
|
146
147
|
type KVCacheStoreOptions,
|