@nifrajs/web 4.0.0 → 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 +49 -26
- 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 +105 -19
- 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 +14 -3
- 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/plugins/zone-guard.d.ts.map +1 -1
- package/dist/plugins/zone-guard.js +10 -2
- package/dist/plugins/zone-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 +77 -17
- 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 +48 -23
- 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 +104 -17
- 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 +43 -7
- 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/plugins/zone-guard.ts +11 -2
- 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 +68 -17
- package/src/zones.ts +208 -7
package/src/query.ts
CHANGED
|
@@ -111,6 +111,12 @@ export interface DehydratedState {
|
|
|
111
111
|
readonly data: unknown
|
|
112
112
|
readonly updatedAt: number
|
|
113
113
|
}>
|
|
114
|
+
/** The successful infinite queries, pages and page params. */
|
|
115
|
+
readonly infiniteQueries?: ReadonlyArray<{
|
|
116
|
+
readonly key: unknown
|
|
117
|
+
readonly data: InfiniteData<unknown, unknown>
|
|
118
|
+
readonly updatedAt: number
|
|
119
|
+
}>
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
/** The keyed query cache. One per app (a binding registers it like the router). */
|
|
@@ -125,8 +131,9 @@ export interface QueryClient {
|
|
|
125
131
|
fn: (pageParam: P) => Promise<T>,
|
|
126
132
|
options: InfiniteQueryOptions<T, P>,
|
|
127
133
|
) => InfiniteQueryHandle<T, P>
|
|
128
|
-
/** Mark matching cached queries stale and refetch the **mounted** ones
|
|
129
|
-
* keys match by **prefix** (`["todo"]` ⇒ every `["todo", …]`); other keys
|
|
134
|
+
/** Mark matching cached queries, infinite ones included, stale and refetch the **mounted** ones
|
|
135
|
+
* (subscribers > 0). Array keys match by **prefix** (`["todo"]` ⇒ every `["todo", …]`); other keys
|
|
136
|
+
* match exactly. */
|
|
130
137
|
invalidateQueries: (keyOrPrefix: unknown) => void
|
|
131
138
|
/** Read a cached query's data (`undefined` if absent or not yet successful). Synchronous, no fetch. */
|
|
132
139
|
getQueryData: <T>(key: unknown) => T | undefined
|
|
@@ -218,17 +225,24 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
218
225
|
|
|
219
226
|
// Lazily evict entries whose GC timer has elapsed (no subscribers), then enforce the hard cap by
|
|
220
227
|
// dropping the oldest-inserted unsubscribed entries. Called on every `query()` - no background timer.
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
const sweepEntries = (
|
|
229
|
+
entries: Map<string, { readonly subscribers: number; readonly gcAt: number | undefined }>,
|
|
230
|
+
t: number,
|
|
231
|
+
): void => {
|
|
232
|
+
for (const [h, e] of entries) {
|
|
233
|
+
if (e.subscribers === 0 && e.gcAt !== undefined && t >= e.gcAt) entries.delete(h)
|
|
225
234
|
}
|
|
226
|
-
if (
|
|
227
|
-
for (const [h, e] of
|
|
228
|
-
if (
|
|
229
|
-
if (e.subscribers === 0)
|
|
235
|
+
if (entries.size <= max) return
|
|
236
|
+
for (const [h, e] of entries) {
|
|
237
|
+
if (entries.size <= max) break
|
|
238
|
+
if (e.subscribers === 0) entries.delete(h) // oldest-inserted unsubscribed first (Map order)
|
|
230
239
|
}
|
|
231
240
|
}
|
|
241
|
+
const sweep = (): void => {
|
|
242
|
+
const t = now()
|
|
243
|
+
sweepEntries(cache, t)
|
|
244
|
+
sweepEntries(infinite, t)
|
|
245
|
+
}
|
|
232
246
|
|
|
233
247
|
// Build a cache entry + its handle. `fn`/`staleTime` are mutated in place on re-bind (closures change
|
|
234
248
|
// between renders). All the fetch/dedup/generation machinery lives here so query(), prefetchQuery(),
|
|
@@ -348,6 +362,9 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
348
362
|
entry.generation++
|
|
349
363
|
if (entry.subscribers > 0) void entry.handle.refetch().catch(() => {}) // refresh mounted now (best-effort)
|
|
350
364
|
}
|
|
365
|
+
for (const entry of infinite.values()) {
|
|
366
|
+
if (keyMatchesPrefix(entry.key, keyOrPrefix)) entry.invalidate()
|
|
367
|
+
}
|
|
351
368
|
}
|
|
352
369
|
|
|
353
370
|
const getQueryData = <T>(key: unknown): T | undefined => {
|
|
@@ -392,7 +409,17 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
392
409
|
if (entry.state.status === "success")
|
|
393
410
|
queries.push({ key: entry.key, data: entry.state.data, updatedAt: entry.state.updatedAt })
|
|
394
411
|
}
|
|
395
|
-
|
|
412
|
+
const infiniteQueries: Array<{
|
|
413
|
+
key: unknown
|
|
414
|
+
data: InfiniteData<unknown, unknown>
|
|
415
|
+
updatedAt: number
|
|
416
|
+
}> = []
|
|
417
|
+
for (const entry of infinite.values()) {
|
|
418
|
+
const { status, data, updatedAt } = entry.handle.snapshot()
|
|
419
|
+
if (status === "success" && data !== undefined)
|
|
420
|
+
infiniteQueries.push({ key: entry.key, data, updatedAt })
|
|
421
|
+
}
|
|
422
|
+
return { queries, infiniteQueries }
|
|
396
423
|
}
|
|
397
424
|
|
|
398
425
|
const hydrate = (state: DehydratedState): void => {
|
|
@@ -409,6 +436,14 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
409
436
|
if (entry.state.status !== "success" || q.updatedAt > entry.state.updatedAt)
|
|
410
437
|
entry.setState(successState(q.data, q.updatedAt))
|
|
411
438
|
}
|
|
439
|
+
// An infinite entry needs its page options before it can exist, so a snapshot for a key not yet
|
|
440
|
+
// queried waits here until `infiniteQuery` creates it.
|
|
441
|
+
for (const q of state.infiniteQueries ?? []) {
|
|
442
|
+
const h = hashQueryKey(q.key)
|
|
443
|
+
const existing = infinite.get(h)
|
|
444
|
+
if (existing === undefined) seeded.set(h, q)
|
|
445
|
+
else existing.seed(q.data, q.updatedAt)
|
|
446
|
+
}
|
|
412
447
|
}
|
|
413
448
|
|
|
414
449
|
// --- Infinite (paged) queries -----------------------------------------------------------------
|
|
@@ -427,19 +462,33 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
427
462
|
)
|
|
428
463
|
return existing.handle as InfiniteQueryHandle<T, P>
|
|
429
464
|
}
|
|
430
|
-
const created = makeInfinite<T, P>(fn, opts)
|
|
465
|
+
const created = makeInfinite<T, P>(key, fn, opts)
|
|
431
466
|
infinite.set(h, created as InfiniteEntry<unknown, unknown>)
|
|
467
|
+
const snapshot = seeded.get(h)
|
|
468
|
+
if (snapshot !== undefined) {
|
|
469
|
+
seeded.delete(h)
|
|
470
|
+
created.seed(snapshot.data, snapshot.updatedAt)
|
|
471
|
+
}
|
|
432
472
|
return created.handle
|
|
433
473
|
}
|
|
434
474
|
|
|
435
475
|
// Infinite entries live in their own map (their data shape + fetch model differ from plain queries).
|
|
436
476
|
interface InfiniteEntry<T, P> {
|
|
477
|
+
readonly key: unknown
|
|
437
478
|
handle: InfiniteQueryHandle<T, P>
|
|
438
479
|
rebind: (fn: (p: P) => Promise<T>, opts: InfiniteQueryOptions<T, P>) => void
|
|
480
|
+
/** Mark stale, drop an in-flight fetch's result, and refetch when mounted. */
|
|
481
|
+
invalidate: () => void
|
|
482
|
+
/** Take a dehydrated snapshot unless the entry already holds fresher data. */
|
|
483
|
+
seed: (data: InfiniteData<unknown, unknown>, updatedAt: number) => void
|
|
484
|
+
subscribers: number
|
|
485
|
+
gcAt: number | undefined
|
|
439
486
|
}
|
|
440
487
|
const infinite = new Map<string, InfiniteEntry<unknown, unknown>>()
|
|
488
|
+
const seeded = new Map<string, NonNullable<DehydratedState["infiniteQueries"]>[number]>()
|
|
441
489
|
|
|
442
490
|
const makeInfinite = <T, P>(
|
|
491
|
+
key: unknown,
|
|
443
492
|
fetchPage: (pageParam: P) => Promise<T>,
|
|
444
493
|
initialOpts: InfiniteQueryOptions<T, P>,
|
|
445
494
|
): InfiniteEntry<T, P> => {
|
|
@@ -447,6 +496,7 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
447
496
|
let opts = initialOpts
|
|
448
497
|
let state: QueryState<InfiniteData<T, P>> = PENDING as QueryState<InfiniteData<T, P>>
|
|
449
498
|
let generation = 0
|
|
499
|
+
let invalidated = false
|
|
450
500
|
let promise: Promise<InfiniteData<T, P>> | undefined
|
|
451
501
|
const listeners = new Set<() => void>()
|
|
452
502
|
const emit = (): void => {
|
|
@@ -475,7 +525,8 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
475
525
|
data.pageParams,
|
|
476
526
|
)
|
|
477
527
|
}
|
|
478
|
-
const isStale = (): boolean =>
|
|
528
|
+
const isStale = (): boolean =>
|
|
529
|
+
invalidated || now() - state.updatedAt >= (opts.staleTime ?? defaultStaleTime)
|
|
479
530
|
|
|
480
531
|
// Run a paged operation under a fresh generation, guarding against a superseding op (like `run`).
|
|
481
532
|
const runPaged = (build: () => Promise<InfiniteData<T, P>>): Promise<InfiniteData<T, P>> => {
|
|
@@ -486,6 +537,7 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
486
537
|
try {
|
|
487
538
|
const data = await build()
|
|
488
539
|
if (gen !== generation) return data
|
|
540
|
+
invalidated = false
|
|
489
541
|
setState(successState(data, now()))
|
|
490
542
|
return data
|
|
491
543
|
} catch (error) {
|
|
@@ -511,9 +563,13 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
511
563
|
const handle: InfiniteQueryHandle<T, P> = {
|
|
512
564
|
snapshot: () => state,
|
|
513
565
|
subscribe: (listener) => {
|
|
566
|
+
entry.subscribers++
|
|
567
|
+
entry.gcAt = undefined
|
|
514
568
|
listeners.add(listener)
|
|
515
569
|
return () => {
|
|
516
570
|
listeners.delete(listener)
|
|
571
|
+
entry.subscribers--
|
|
572
|
+
if (entry.subscribers === 0) entry.gcAt = now() + gcTime
|
|
517
573
|
}
|
|
518
574
|
},
|
|
519
575
|
fetch: () => fetchFirst(false),
|
|
@@ -558,13 +614,30 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
558
614
|
return p !== undefined && p !== null
|
|
559
615
|
},
|
|
560
616
|
}
|
|
561
|
-
|
|
562
|
-
|
|
617
|
+
const entry: InfiniteEntry<T, P> = {
|
|
618
|
+
key,
|
|
619
|
+
handle,
|
|
563
620
|
rebind: (nextFn, nextOpts) => {
|
|
564
621
|
fn = nextFn
|
|
565
622
|
opts = nextOpts
|
|
566
623
|
},
|
|
567
|
-
|
|
624
|
+
invalidate: () => {
|
|
625
|
+
invalidated = true
|
|
626
|
+
// A fetch begun before this point read pre-mutation data: it may not publish, and a new fetch
|
|
627
|
+
// must not join it.
|
|
628
|
+
generation++
|
|
629
|
+
promise = undefined
|
|
630
|
+
if (entry.subscribers > 0) void handle.refetch().catch(() => {})
|
|
631
|
+
},
|
|
632
|
+
seed: (data, updatedAt) => {
|
|
633
|
+
if (state.status === "success" && state.updatedAt >= updatedAt) return
|
|
634
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: the snapshot was dehydrated from an entry under this key, whose pages and params are T and P
|
|
635
|
+
setState(successState(data as InfiniteData<T, P>, updatedAt))
|
|
636
|
+
},
|
|
637
|
+
subscribers: 0,
|
|
638
|
+
gcAt: undefined,
|
|
639
|
+
}
|
|
640
|
+
return entry
|
|
568
641
|
}
|
|
569
642
|
|
|
570
643
|
return {
|
|
@@ -609,8 +682,10 @@ export interface MutationCallbacks<TData, TVariables> {
|
|
|
609
682
|
export interface MutationHandle<TData, TVariables> {
|
|
610
683
|
snapshot: () => MutationState<TData, TVariables>
|
|
611
684
|
subscribe: (listener: () => void) => () => void
|
|
612
|
-
/** Run the mutation. Resolves to the data (and runs onSuccess/onSettled) or rejects (onError/onSettled)
|
|
613
|
-
*
|
|
685
|
+
/** Run the mutation. Resolves to the data (and runs onSuccess/onSettled) or rejects (onError/onSettled);
|
|
686
|
+
* a throwing `onMutate` fails it the same way. A throwing `onSuccess` or `onSettled` after the write
|
|
687
|
+
* rejects the call but leaves the state `success` and never calls `onError`. The latest concurrent
|
|
688
|
+
* call wins the published state (older results are dropped). */
|
|
614
689
|
mutate: (variables: TVariables) => Promise<TData>
|
|
615
690
|
/** Rebind the latest `fn`/callbacks (closures change between renders) without losing state. */
|
|
616
691
|
rebind: (
|
|
@@ -667,21 +742,27 @@ export function createMutation<TData, TVariables>(
|
|
|
667
742
|
mutate: async (variables) => {
|
|
668
743
|
const mine = ++latest
|
|
669
744
|
setState({ status: "pending", data: undefined, error: undefined, variables })
|
|
670
|
-
|
|
671
|
-
// needless microtask before the mutationFn runs (and complicates single-flight timing).
|
|
672
|
-
if (cbs.onMutate !== undefined) await cbs.onMutate(variables)
|
|
745
|
+
let data: TData
|
|
673
746
|
try {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (cbs.
|
|
677
|
-
|
|
678
|
-
return data
|
|
747
|
+
// Only await a callback when one is actually supplied - an `await undefined` would insert a
|
|
748
|
+
// needless microtask before the mutationFn runs (and complicates single-flight timing).
|
|
749
|
+
if (cbs.onMutate !== undefined) await cbs.onMutate(variables)
|
|
750
|
+
data = await mutationFn(variables)
|
|
679
751
|
} catch (error) {
|
|
680
752
|
if (mine === latest) setState({ status: "error", data: undefined, error, variables })
|
|
681
753
|
if (cbs.onError !== undefined) await cbs.onError(error, variables)
|
|
682
754
|
if (cbs.onSettled !== undefined) await cbs.onSettled(undefined, error, variables)
|
|
683
755
|
throw error
|
|
684
756
|
}
|
|
757
|
+
if (mine === latest) setState({ status: "success", data, error: undefined, variables })
|
|
758
|
+
// The write happened: a success callback that throws rejects the call, but is no failure of the
|
|
759
|
+
// write, so it never reaches onError (which would roll back what the server kept).
|
|
760
|
+
try {
|
|
761
|
+
if (cbs.onSuccess !== undefined) await cbs.onSuccess(data, variables)
|
|
762
|
+
} finally {
|
|
763
|
+
if (cbs.onSettled !== undefined) await cbs.onSettled(data, undefined, variables)
|
|
764
|
+
}
|
|
765
|
+
return data
|
|
685
766
|
},
|
|
686
767
|
}
|
|
687
768
|
}
|
package/src/render-seam.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { Submission } from "./router.ts"
|
|
|
12
12
|
|
|
13
13
|
/** The data handed to a route component. */
|
|
14
14
|
export interface RenderProps {
|
|
15
|
+
readonly serverComponents?: import("./server-components/types.ts").ComponentState
|
|
15
16
|
readonly data: unknown
|
|
16
17
|
/** Per-layout loader data, aligned index-for-index with the layout prefix of `chain`. */
|
|
17
18
|
readonly layoutData?: readonly unknown[]
|
|
@@ -123,5 +124,6 @@ export const ROUTE_GLOBAL = "__NIFRA_ROUTE__"
|
|
|
123
124
|
export const ACTION_GLOBAL = "__NIFRA_ACTION__"
|
|
124
125
|
/** Dynamic-boundary states for hydration; absent when a route declares no boundaries. */
|
|
125
126
|
export const BOUNDARY_GLOBAL = "__NIFRA_BOUNDARIES__"
|
|
127
|
+
export const COMPONENT_GLOBAL = "__NIFRA_COMPONENTS__"
|
|
126
128
|
/** Marker attribute used to find a non-default hydration container. */
|
|
127
129
|
export const ROOT_ATTRIBUTE = "data-nifra-root"
|
package/src/route-types.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "node:fs"
|
|
31
31
|
import { dirname, join, relative } from "node:path"
|
|
32
32
|
import { codeUnitOrder } from "./internal/code-unit-order.ts"
|
|
33
|
+
import { jsStringLiteral } from "./internal/js-string.ts"
|
|
33
34
|
import {
|
|
34
35
|
buildManifest,
|
|
35
36
|
isMiddlewareFile,
|
|
@@ -85,6 +86,8 @@ function routeFiles(manifest: Manifest): readonly RouteFile[] {
|
|
|
85
86
|
const PARAM = /^\[([A-Za-z_][A-Za-z0-9_]*)\]$/
|
|
86
87
|
const OPTIONAL = /^\[\[([A-Za-z_][A-Za-z0-9_]*)\]\]$/
|
|
87
88
|
const CATCH_ALL = /^\[\.\.\.([A-Za-z_][A-Za-z0-9_]*)\]$/
|
|
89
|
+
// A `[name]` inside a part-literal segment such as `post-[id]` or `[lang]-feed.xml`.
|
|
90
|
+
const EMBEDDED_PARAM = /\[([A-Za-z_][A-Za-z0-9_]*)\]/g
|
|
88
91
|
|
|
89
92
|
/** The params a route file's URL carries: its directories', plus its own name's for a page. */
|
|
90
93
|
function paramsType(file: string, kind: Kind): string {
|
|
@@ -96,10 +99,20 @@ function paramsType(file: string, kind: Kind): string {
|
|
|
96
99
|
const optional = OPTIONAL.exec(segment)
|
|
97
100
|
const name = optional?.[1] ?? PARAM.exec(segment)?.[1] ?? CATCH_ALL.exec(segment)?.[1]
|
|
98
101
|
if (name !== undefined) fields.push(`readonly ${name}${optional === null ? "" : "?"}: string`)
|
|
102
|
+
else
|
|
103
|
+
for (const embedded of segment.matchAll(EMBEDDED_PARAM))
|
|
104
|
+
fields.push(`readonly ${embedded[1]}: string`)
|
|
99
105
|
}
|
|
100
106
|
return fields.length === 0 ? "Record<string, never>" : `{ ${fields.join("; ")} }`
|
|
101
107
|
}
|
|
102
108
|
|
|
109
|
+
// A file name may hold a line terminator, which would end the `//` header comment it is quoted in.
|
|
110
|
+
const commentSafe = (text: string): string =>
|
|
111
|
+
text.replace(
|
|
112
|
+
/[\n\r\u2028\u2029]/g,
|
|
113
|
+
(char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`,
|
|
114
|
+
)
|
|
115
|
+
|
|
103
116
|
/** A module specifier for `target` from a file in `fromDir`, without a script extension. */
|
|
104
117
|
function specifier(fromDir: string, target: string): string {
|
|
105
118
|
let path = posix(relative(fromDir, target))
|
|
@@ -121,16 +134,24 @@ function routeTypesModule(
|
|
|
121
134
|
const backend = route.backend === undefined ? undefined : join(routesDir, route.backend)
|
|
122
135
|
// Only a script module's exports are visible to TypeScript; a .svelte/.vue/.mdx one is an ambient shim.
|
|
123
136
|
const frontend = /\.[jt]sx?$/.test(route.file) ? join(routesDir, route.file) : undefined
|
|
124
|
-
const lines = [
|
|
137
|
+
const lines = [
|
|
138
|
+
`// Generated by nifra from ${commentSafe(label)}. Do not edit: \`nifra types\` rewrites it.`,
|
|
139
|
+
]
|
|
125
140
|
if (register !== undefined)
|
|
126
141
|
lines.push(`/// <reference path="${posix(relative(dir, register))}" />`)
|
|
127
|
-
lines.push(
|
|
128
|
-
|
|
142
|
+
lines.push(
|
|
143
|
+
'import type { OutputOf, RouteActionReturn, RouteLoaderArgs, RouteLoaderReturn, SearchSchemaOf } from "@nifrajs/client"',
|
|
144
|
+
)
|
|
145
|
+
lines.push(
|
|
146
|
+
route.kind === "page"
|
|
147
|
+
? 'import type { MetaArgs as WebMetaArgs, RenderProps } from "@nifrajs/web"'
|
|
148
|
+
: 'import type { MetaArgs as WebMetaArgs } from "@nifrajs/web"',
|
|
149
|
+
)
|
|
129
150
|
lines.push("")
|
|
130
151
|
if (backend !== undefined)
|
|
131
|
-
lines.push(`type Backend = typeof import(
|
|
152
|
+
lines.push(`type Backend = typeof import(${jsStringLiteral(specifier(dir, backend))})`)
|
|
132
153
|
if (frontend !== undefined)
|
|
133
|
-
lines.push(`type Frontend = typeof import(
|
|
154
|
+
lines.push(`type Frontend = typeof import(${jsStringLiteral(specifier(dir, frontend))})`)
|
|
134
155
|
if (backend !== undefined || frontend !== undefined) lines.push("")
|
|
135
156
|
const output = (name: string) => (backend === undefined ? "null" : `OutputOf<Backend, "${name}">`)
|
|
136
157
|
lines.push(
|
|
@@ -145,6 +166,12 @@ function routeTypesModule(
|
|
|
145
166
|
` export type LoaderArgs = RouteLoaderArgs<Params${frontend === undefined ? "" : ", SearchSchemaOf<Frontend>"}>`,
|
|
146
167
|
" /** The context the backend half's action receives. */",
|
|
147
168
|
" export type ActionArgs = LoaderArgs",
|
|
169
|
+
" /** The backend half's loader. Annotate it, `export const loader: Route.Loader = ...`, and what it returns is checked against `loaderOutput`. */",
|
|
170
|
+
` export type Loader = (args: LoaderArgs) => RouteLoaderReturn<${backend === undefined ? "null" : "Backend"}>`,
|
|
171
|
+
" /** The backend half's action. Annotate it, `export const action: Route.Action = ...`, and what it returns is checked against `actionOutput`. */",
|
|
172
|
+
` export type Action = (args: ActionArgs) => RouteActionReturn<${backend === undefined ? "null" : "Backend"}>`,
|
|
173
|
+
" /** What this module's `meta` function receives: its own loader data as `data`. */",
|
|
174
|
+
" export type MetaArgs = WebMetaArgs<LoaderData>",
|
|
148
175
|
)
|
|
149
176
|
if (route.kind === "page") {
|
|
150
177
|
lines.push(
|
|
@@ -170,8 +197,8 @@ function routeTypesModule(
|
|
|
170
197
|
|
|
171
198
|
function registerModule(out: string, backendApp: string, label: string): string {
|
|
172
199
|
return [
|
|
173
|
-
`// Generated by nifra from ${label}. Do not edit: \`nifra types\` rewrites it.`,
|
|
174
|
-
`import type { backend } from
|
|
200
|
+
`// Generated by nifra from ${commentSafe(label)}. Do not edit: \`nifra types\` rewrites it.`,
|
|
201
|
+
`import type { backend } from ${jsStringLiteral(specifier(dirname(out), backendApp))}`,
|
|
175
202
|
"",
|
|
176
203
|
'declare module "@nifrajs/client" {',
|
|
177
204
|
" interface Register {",
|
package/src/router.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { Router as CoreRouter } from "@nifrajs/core/router"
|
|
|
9
9
|
import type { StandardSchemaV1 } from "@nifrajs/core/server"
|
|
10
10
|
import type { BoundaryDescriptor, BoundaryStates } from "./boundary.ts"
|
|
11
11
|
import { parseNdjsonData } from "./deferred.ts"
|
|
12
|
+
import { parsePageData } from "./internal/revive-dates.ts"
|
|
13
|
+
import { requestPattern } from "./internal/route-path.ts"
|
|
12
14
|
import type { ClientActionResult, ClientRequestBody, ClientRouteHooks } from "./manifest.ts"
|
|
13
15
|
import type { MatchChain } from "./render-seam.ts"
|
|
14
16
|
import { isClientOnlySearchChange } from "./search.ts"
|
|
@@ -90,6 +92,7 @@ export const NAV_FROM_HEADER = "x-nifra-from"
|
|
|
90
92
|
* structure rather than by assuming the deploy was atomic.
|
|
91
93
|
*/
|
|
92
94
|
export interface RouteDataEnvelope {
|
|
95
|
+
readonly serverComponents?: import("./server-components/types.ts").ComponentState | undefined
|
|
93
96
|
readonly v: 1
|
|
94
97
|
readonly data: unknown
|
|
95
98
|
readonly layoutData?: readonly unknown[]
|
|
@@ -129,6 +132,7 @@ export interface Submission {
|
|
|
129
132
|
|
|
130
133
|
/** The router's observable state. A new object is published on every transition. */
|
|
131
134
|
export interface RouterState {
|
|
135
|
+
readonly serverComponents?: import("./server-components/types.ts").ComponentState | undefined
|
|
132
136
|
readonly routeId: string
|
|
133
137
|
readonly params: Record<string, string>
|
|
134
138
|
/** The current URL path (used to revalidate the active loader after an action). */
|
|
@@ -173,7 +177,7 @@ export function createMatcher(
|
|
|
173
177
|
for (const pattern of patterns) {
|
|
174
178
|
// Pass the literal to the public router seam: package type resolution may point the two core
|
|
175
179
|
// subpaths at different source/dist declarations, while registration-time compilation is cheap.
|
|
176
|
-
routeIndex.add("GET", pattern.pattern, pattern.routeId)
|
|
180
|
+
routeIndex.add("GET", requestPattern(pattern.pattern), pattern.routeId)
|
|
177
181
|
}
|
|
178
182
|
return (path) => {
|
|
179
183
|
// Strip the query without allocating a `split("?")` array - matcher runs per match.
|
|
@@ -258,8 +262,9 @@ export interface ClientRouter {
|
|
|
258
262
|
* loader is revalidated so the mutation is reflected. Pass `{ revalidate: false }` to SKIP that
|
|
259
263
|
* revalidation - keep the current `data` and just publish the action's `actionData` (useful when
|
|
260
264
|
* the action already returned everything that changed, saving the extra round-trip). A redirect
|
|
261
|
-
* always loads its target regardless. Rejects on failure (caller falls back); once the
|
|
262
|
-
*
|
|
265
|
+
* always loads its target regardless. Rejects on failure (caller falls back); once the server has
|
|
266
|
+
* answered, at any status, the rejection's `redirectTo` names the page to load instead of posting
|
|
267
|
+
* again.
|
|
263
268
|
*/
|
|
264
269
|
submit: (
|
|
265
270
|
action: string,
|
|
@@ -310,6 +315,10 @@ export interface ClientRouterOptions {
|
|
|
310
315
|
/** Terminal status → client route id. Generated entries populate this from `_404` and
|
|
311
316
|
* `_<status>` files so soft navigation renders the same boundary as a hard request. */
|
|
312
317
|
readonly statusRoutes?: Readonly<Record<number, string>>
|
|
318
|
+
/** Status route ids whose page renders inside layouts (all but `inLayouts = false`), filled in by the generated entry's
|
|
319
|
+
* `loadModule`. A soft navigation that ends on one loads the URL as a document, as one ending on a
|
|
320
|
+
* nested `_404` does: only the server runs those layouts' loaders. */
|
|
321
|
+
readonly layoutStatusRoutes?: Readonly<Record<string, true>>
|
|
313
322
|
/** routeId → the route's client-only search keys (its `searchClientKeys` export). When a soft
|
|
314
323
|
* navigation stays on the same route + pathname and changes ONLY these keys, `navigate` publishes the
|
|
315
324
|
* new URL WITHOUT re-running the loader (re-render, not revalidate). Populated lazily by the generated
|
|
@@ -346,7 +355,7 @@ export interface MountRouterOptions {
|
|
|
346
355
|
const readResponseData = (res: Response, signal?: AbortSignal): Promise<unknown> =>
|
|
347
356
|
(res.headers.get("content-type") ?? "").includes("application/x-ndjson") && res.body !== null
|
|
348
357
|
? parseNdjsonData(res.body, signal)
|
|
349
|
-
: res.
|
|
358
|
+
: res.text().then(parsePageData)
|
|
350
359
|
|
|
351
360
|
// Index scan, not `/\/+$/` - the unanchored-start trailing-run replace backtracks quadratically and
|
|
352
361
|
// the pathname is caller-controlled.
|
|
@@ -404,8 +413,8 @@ const targetUrl = (to: string): URL | undefined => {
|
|
|
404
413
|
}
|
|
405
414
|
}
|
|
406
415
|
|
|
407
|
-
const redirected = (to: string): Error =>
|
|
408
|
-
Object.assign(new Error(`[nifra/web] redirected to ${to}
|
|
416
|
+
const redirected = (to: string, cause?: unknown): Error =>
|
|
417
|
+
Object.assign(new Error(`[nifra/web] redirected to ${to}`, { cause }), { redirectTo: to })
|
|
409
418
|
|
|
410
419
|
const defaultFetchData: FetchRouteData = async (path, _match, signal, navigation) => {
|
|
411
420
|
// SSG fast path: if this path was prerendered, its loader data is a static file - fetch that (no
|
|
@@ -519,11 +528,18 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
519
528
|
* an older build be read by newer client code.
|
|
520
529
|
*/
|
|
521
530
|
type LoadedRouteData = {
|
|
531
|
+
readonly serverComponents?: import("./server-components/types.ts").ComponentState | undefined
|
|
522
532
|
readonly data: unknown
|
|
523
533
|
readonly layoutData?: readonly unknown[] | undefined
|
|
524
534
|
readonly terminalRouteId?: string | undefined
|
|
525
535
|
readonly boundaries?: BoundaryStates
|
|
526
536
|
}
|
|
537
|
+
const dataFields = (value: LoadedRouteData) => ({
|
|
538
|
+
data: value.data,
|
|
539
|
+
layoutData: value.layoutData,
|
|
540
|
+
serverComponents: value.serverComponents,
|
|
541
|
+
...(value.boundaries === undefined ? {} : { boundaries: value.boundaries }),
|
|
542
|
+
})
|
|
527
543
|
const loadRouteData = async (
|
|
528
544
|
path: string,
|
|
529
545
|
match: { routeId: string; params: Record<string, string> },
|
|
@@ -551,24 +567,21 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
551
567
|
}
|
|
552
568
|
return { data: null, terminalRouteId }
|
|
553
569
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
570
|
+
let layoutData = payload.layoutData
|
|
571
|
+
if (retainFrom !== undefined && payload.retained !== undefined) {
|
|
572
|
+
const retained = new Set(
|
|
573
|
+
payload.retained.filter(
|
|
574
|
+
(index) => Number.isInteger(index) && index >= 0 && index < retainFrom.layoutData.length,
|
|
575
|
+
),
|
|
576
|
+
)
|
|
577
|
+
layoutData = layoutData?.map((value, index) =>
|
|
578
|
+
retained.has(index) ? retainFrom.layoutData[index] : value,
|
|
579
|
+
)
|
|
560
580
|
}
|
|
561
|
-
const retained = new Set(
|
|
562
|
-
payload.retained.filter(
|
|
563
|
-
(index) => Number.isInteger(index) && index >= 0 && index < retainFrom.layoutData.length,
|
|
564
|
-
),
|
|
565
|
-
)
|
|
566
|
-
const layoutData = payload.layoutData?.map((value, index) =>
|
|
567
|
-
retained.has(index) ? retainFrom.layoutData[index] : value,
|
|
568
|
-
)
|
|
569
581
|
return {
|
|
570
582
|
data: payload.data,
|
|
571
583
|
layoutData,
|
|
584
|
+
serverComponents: payload.serverComponents,
|
|
572
585
|
...(payload.boundaries !== undefined ? { boundaries: payload.boundaries } : {}),
|
|
573
586
|
}
|
|
574
587
|
}
|
|
@@ -644,11 +657,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
644
657
|
navAbort?.abort()
|
|
645
658
|
const ac = new AbortController()
|
|
646
659
|
navAbort = ac
|
|
647
|
-
const initial =
|
|
648
|
-
data: state.data,
|
|
649
|
-
layoutData: state.layoutData,
|
|
650
|
-
...(state.boundaries !== undefined ? { boundaries: state.boundaries } : {}),
|
|
651
|
-
}
|
|
660
|
+
const initial = dataFields(state)
|
|
652
661
|
const loaded = await applyClientLoader(
|
|
653
662
|
state.path,
|
|
654
663
|
route,
|
|
@@ -663,9 +672,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
663
672
|
} else {
|
|
664
673
|
state = {
|
|
665
674
|
...state,
|
|
666
|
-
|
|
667
|
-
layoutData: loaded.layoutData,
|
|
668
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
675
|
+
...dataFields(loaded),
|
|
669
676
|
}
|
|
670
677
|
}
|
|
671
678
|
emit()
|
|
@@ -707,6 +714,10 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
707
714
|
}
|
|
708
715
|
>()
|
|
709
716
|
const cachePut = (path: string, loaded: LoadedRouteData): void => {
|
|
717
|
+
if (loaded.serverComponents !== undefined) {
|
|
718
|
+
cache.delete(path)
|
|
719
|
+
return
|
|
720
|
+
}
|
|
710
721
|
if (!cache.has(path) && cache.size >= MAX_CACHE) {
|
|
711
722
|
const oldest = cache.keys().next().value
|
|
712
723
|
if (oldest !== undefined) cache.delete(oldest)
|
|
@@ -789,7 +800,14 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
789
800
|
() => loadRouteData(at, route, signal, keepLayouts),
|
|
790
801
|
prefetchedData,
|
|
791
802
|
)
|
|
792
|
-
if (loaded.terminalRouteId !== undefined)
|
|
803
|
+
if (loaded.terminalRouteId !== undefined) {
|
|
804
|
+
await loadModule?.(loaded.terminalRouteId)
|
|
805
|
+
if (options.layoutStatusRoutes?.[loaded.terminalRouteId] === true) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`[nifra/web] ${loaded.terminalRouteId} renders inside layouts; loading ${at} as a document`,
|
|
808
|
+
)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
793
811
|
return [at, route, loaded]
|
|
794
812
|
} catch (error) {
|
|
795
813
|
const next = hops < 20 ? appRedirect(redirectOf(error)) : undefined
|
|
@@ -817,9 +835,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
817
835
|
cachePut(state.path, loaded)
|
|
818
836
|
state = {
|
|
819
837
|
...state,
|
|
820
|
-
|
|
821
|
-
layoutData: loaded.layoutData,
|
|
822
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
838
|
+
...dataFields(loaded),
|
|
823
839
|
pending: false,
|
|
824
840
|
}
|
|
825
841
|
emit()
|
|
@@ -989,11 +1005,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
989
1005
|
) {
|
|
990
1006
|
++generation // supersede any in-flight navigation so its late result is dropped
|
|
991
1007
|
navAbort?.abort()
|
|
992
|
-
cachePut(path,
|
|
993
|
-
data: state.data,
|
|
994
|
-
layoutData: state.layoutData,
|
|
995
|
-
...(state.boundaries !== undefined ? { boundaries: state.boundaries } : {}),
|
|
996
|
-
})
|
|
1008
|
+
cachePut(path, dataFields(state))
|
|
997
1009
|
state = { ...state, path, pending: false, pendingPath: undefined }
|
|
998
1010
|
emit()
|
|
999
1011
|
return
|
|
@@ -1041,9 +1053,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1041
1053
|
routeId: loaded.terminalRouteId ?? target.routeId,
|
|
1042
1054
|
params: loaded.terminalRouteId === undefined ? target.params : {},
|
|
1043
1055
|
path: to,
|
|
1044
|
-
|
|
1045
|
-
layoutData: loaded.layoutData,
|
|
1046
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
1056
|
+
...dataFields(loaded),
|
|
1047
1057
|
actionData: undefined, // a fresh navigation has no action result
|
|
1048
1058
|
pending: false,
|
|
1049
1059
|
}
|
|
@@ -1089,8 +1099,9 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1089
1099
|
body: prepared.body,
|
|
1090
1100
|
headers: { [DATA_HEADER]: "1" },
|
|
1091
1101
|
})
|
|
1092
|
-
|
|
1102
|
+
// Whatever the status, the server took the POST: the action may have run before it failed.
|
|
1093
1103
|
posted = true
|
|
1104
|
+
if (!res.ok) throw new Error(`[nifra/web] action failed (${res.status}): ${action}`)
|
|
1094
1105
|
const redirectTo = res.headers.get(REDIRECT_HEADER)
|
|
1095
1106
|
if (redirectTo !== null) {
|
|
1096
1107
|
// The action redirected (Post/Redirect/Get) - treat it as a client navigation.
|
|
@@ -1114,9 +1125,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1114
1125
|
routeId: loaded.terminalRouteId ?? target.routeId,
|
|
1115
1126
|
params: loaded.terminalRouteId === undefined ? target.params : {},
|
|
1116
1127
|
path: to,
|
|
1117
|
-
|
|
1118
|
-
layoutData: loaded.layoutData,
|
|
1119
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
1128
|
+
...dataFields(loaded),
|
|
1120
1129
|
actionData: undefined,
|
|
1121
1130
|
pending: false,
|
|
1122
1131
|
}
|
|
@@ -1137,9 +1146,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1137
1146
|
const active = { routeId: state.routeId, params: state.params }
|
|
1138
1147
|
const loaded = skipActive
|
|
1139
1148
|
? {
|
|
1140
|
-
|
|
1141
|
-
layoutData: state.layoutData,
|
|
1142
|
-
...(state.boundaries !== undefined ? { boundaries: state.boundaries } : {}),
|
|
1149
|
+
...dataFields(state),
|
|
1143
1150
|
}
|
|
1144
1151
|
: await applyClientLoader(state.path, active, ac.signal, () =>
|
|
1145
1152
|
loadRouteData(state.path, active, ac.signal),
|
|
@@ -1154,9 +1161,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1154
1161
|
routeId: state.routeId,
|
|
1155
1162
|
params: state.params,
|
|
1156
1163
|
path: state.path,
|
|
1157
|
-
|
|
1158
|
-
layoutData: loaded.layoutData,
|
|
1159
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
1164
|
+
...dataFields(loaded),
|
|
1160
1165
|
actionData,
|
|
1161
1166
|
pending: false,
|
|
1162
1167
|
}
|
|
@@ -1172,9 +1177,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1172
1177
|
routeId: state.routeId,
|
|
1173
1178
|
params: state.params,
|
|
1174
1179
|
path: state.path,
|
|
1175
|
-
|
|
1176
|
-
layoutData: state.layoutData,
|
|
1177
|
-
...(state.boundaries !== undefined ? { boundaries: state.boundaries } : {}),
|
|
1180
|
+
...dataFields(state),
|
|
1178
1181
|
actionData: previousActionData,
|
|
1179
1182
|
pending: false,
|
|
1180
1183
|
}
|
|
@@ -1182,7 +1185,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1182
1185
|
}
|
|
1183
1186
|
if (!posted) throw err
|
|
1184
1187
|
if (mine !== generation) return // a newer navigation or submit owns the page
|
|
1185
|
-
throw redirectOf(err) === undefined ? redirected(landing ?? state.path) : err
|
|
1188
|
+
throw redirectOf(err) === undefined ? redirected(landing ?? state.path, err) : err
|
|
1186
1189
|
}
|
|
1187
1190
|
},
|
|
1188
1191
|
hydrate,
|
|
@@ -1216,7 +1219,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1216
1219
|
const oldest = prefetched.keys().next().value
|
|
1217
1220
|
if (oldest !== undefined) prefetched.delete(oldest)
|
|
1218
1221
|
}
|
|
1219
|
-
prefetched.set(path, [loaded, Date.now()])
|
|
1222
|
+
if (loaded.serverComponents === undefined) prefetched.set(path, [loaded, Date.now()])
|
|
1220
1223
|
} catch {
|
|
1221
1224
|
// Best-effort: a failed prefetch just means the eventual navigate fetches normally.
|
|
1222
1225
|
} finally {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const __NIFRA_COMPONENT_BUILD__: string
|
|
2
|
+
|
|
3
|
+
/** The coordinated compiler replaces this value in every environment. */
|
|
4
|
+
export const componentBuild: string =
|
|
5
|
+
typeof __NIFRA_COMPONENT_BUILD__ === "string"
|
|
6
|
+
? __NIFRA_COMPONENT_BUILD__
|
|
7
|
+
: (() => {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"[nifra/components] build-info requires serverComponentsConfig or nifra build",
|
|
10
|
+
)
|
|
11
|
+
})()
|