@nifrajs/web 2.1.0 → 2.3.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 +3 -3
- package/dist/build-vite.d.ts +68 -0
- package/dist/build-vite.d.ts.map +1 -0
- package/dist/build-vite.js +433 -0
- package/dist/build-vite.js.map +1 -0
- package/dist/build.d.ts +174 -65
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +376 -135
- package/dist/build.js.map +1 -1
- package/dist/bun-dev-entry.d.ts +83 -0
- package/dist/bun-dev-entry.d.ts.map +1 -0
- package/dist/bun-dev-entry.js +136 -0
- package/dist/bun-dev-entry.js.map +1 -0
- package/dist/chunk-names.js +1 -1
- package/dist/client.d.ts +3 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +209 -27
- package/dist/client.js.map +1 -1
- package/dist/conformance.d.ts +2 -2
- package/dist/conformance.js +3 -3
- package/dist/deferred.d.ts +11 -11
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +16 -16
- package/dist/dev-error.d.ts +2 -2
- package/dist/dev-error.js +3 -3
- package/dist/dev-port.d.ts +36 -0
- package/dist/dev-port.d.ts.map +1 -0
- package/dist/dev-port.js +48 -0
- package/dist/dev-port.js.map +1 -0
- package/dist/dev.d.ts +62 -5
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +419 -60
- package/dist/dev.js.map +1 -1
- package/dist/draft.d.ts +44 -7
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +92 -9
- package/dist/draft.js.map +1 -1
- package/dist/fn-state.d.ts +56 -0
- package/dist/fn-state.d.ts.map +1 -0
- package/dist/fn-state.js +77 -0
- package/dist/fn-state.js.map +1 -0
- package/dist/fn.d.ts +111 -0
- package/dist/fn.d.ts.map +1 -0
- package/dist/fn.js +125 -0
- package/dist/fn.js.map +1 -0
- package/dist/fonts-google.d.ts +9 -9
- package/dist/fonts-google.js +10 -10
- package/dist/fonts.d.ts +7 -7
- package/dist/fonts.js +1 -1
- package/dist/forms.d.ts +8 -8
- package/dist/fs.d.ts +15 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +8 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +266 -74
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +869 -179
- package/dist/index.js.map +1 -1
- package/dist/internal/head-attributes.d.ts +15 -0
- package/dist/internal/head-attributes.d.ts.map +1 -0
- package/dist/internal/head-attributes.js +99 -0
- package/dist/internal/head-attributes.js.map +1 -0
- package/dist/internal/script-types.d.ts +14 -0
- package/dist/internal/script-types.d.ts.map +1 -0
- package/dist/internal/script-types.js +17 -0
- package/dist/internal/script-types.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +60 -0
- package/dist/internal/server-boundary.d.ts.map +1 -0
- package/dist/internal/server-boundary.js +174 -0
- package/dist/internal/server-boundary.js.map +1 -0
- package/dist/internal/timing-safe-equal.d.ts +16 -0
- package/dist/internal/timing-safe-equal.d.ts.map +1 -0
- package/dist/internal/timing-safe-equal.js +26 -0
- package/dist/internal/timing-safe-equal.js.map +1 -0
- package/dist/internal/vite-import.d.ts +63 -0
- package/dist/internal/vite-import.d.ts.map +1 -0
- package/dist/internal/vite-import.js +94 -0
- package/dist/internal/vite-import.js.map +1 -0
- package/dist/islands.d.ts +5 -5
- package/dist/islands.js +5 -5
- package/dist/isr.d.ts +15 -15
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +8 -21
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +12 -6
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +121 -40
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +136 -26
- package/dist/manifest.js.map +1 -1
- package/dist/module-graph.d.ts +91 -0
- package/dist/module-graph.d.ts.map +1 -0
- package/dist/module-graph.js +92 -0
- package/dist/module-graph.js.map +1 -0
- package/dist/navigation.d.ts +120 -5
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +37 -5
- package/dist/navigation.js.map +1 -1
- package/dist/plugins/css-modules.d.ts +3 -3
- package/dist/plugins/css-modules.d.ts.map +1 -1
- package/dist/plugins/css-modules.js +0 -0
- package/dist/plugins/css-modules.js.map +1 -1
- package/dist/plugins/kit.d.ts +8 -8
- package/dist/plugins/kit.js +9 -9
- package/dist/plugins/scss.js +3 -3
- package/dist/plugins/vite-leak-guard.d.ts +45 -0
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -0
- package/dist/plugins/vite-leak-guard.js +67 -0
- package/dist/plugins/vite-leak-guard.js.map +1 -0
- package/dist/plugins/vite-server-fn.d.ts +23 -0
- package/dist/plugins/vite-server-fn.d.ts.map +1 -0
- package/dist/plugins/vite-server-fn.js +51 -0
- package/dist/plugins/vite-server-fn.js.map +1 -0
- package/dist/plugins/vite-server-only.d.ts +49 -0
- package/dist/plugins/vite-server-only.d.ts.map +1 -0
- package/dist/plugins/vite-server-only.js +57 -0
- package/dist/plugins/vite-server-only.js.map +1 -0
- package/dist/prerender.d.ts +5 -5
- package/dist/prerender.js +7 -7
- package/dist/public-dir.d.ts +36 -0
- package/dist/public-dir.d.ts.map +1 -0
- package/dist/public-dir.js +89 -0
- package/dist/public-dir.js.map +1 -0
- package/dist/query.d.ts +10 -10
- package/dist/query.js +15 -15
- package/dist/route-manifest.d.ts +91 -0
- package/dist/route-manifest.d.ts.map +1 -0
- package/dist/route-manifest.js +128 -0
- package/dist/route-manifest.js.map +1 -0
- package/dist/router.d.ts +94 -22
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +170 -58
- package/dist/router.js.map +1 -1
- package/dist/search.d.ts +94 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +232 -0
- package/dist/search.js.map +1 -0
- package/dist/server-only.d.ts +6 -6
- package/dist/server-only.js +7 -7
- package/dist/service-worker.d.ts +56 -0
- package/dist/service-worker.d.ts.map +1 -0
- package/dist/service-worker.js +127 -0
- package/dist/service-worker.js.map +1 -0
- package/dist/vite.d.ts +25 -17
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +75 -33
- package/dist/vite.js.map +1 -1
- package/package.json +48 -4
- package/server-manifest.d.ts +31 -0
- package/src/build-vite.ts +553 -0
- package/src/build.ts +563 -172
- package/src/bun-dev-entry.ts +184 -0
- package/src/chunk-names.ts +1 -1
- package/src/client.ts +221 -30
- package/src/conformance.ts +3 -3
- package/src/deferred.ts +17 -17
- package/src/dev-error.ts +3 -3
- package/src/dev-port.ts +63 -0
- package/src/dev.ts +468 -66
- package/src/draft.ts +124 -12
- package/src/fn-state.ts +107 -0
- package/src/fn.ts +199 -0
- package/src/fonts-google.ts +13 -13
- package/src/fonts.ts +7 -7
- package/src/forms.ts +8 -8
- package/src/fs.ts +24 -6
- package/src/index.ts +1174 -225
- package/src/internal/head-attributes.ts +109 -0
- package/src/internal/script-types.ts +18 -0
- package/src/internal/server-boundary.ts +187 -0
- package/src/internal/timing-safe-equal.ts +24 -0
- package/src/internal/vite-import.ts +92 -0
- package/src/islands.ts +7 -7
- package/src/isr.ts +18 -30
- package/src/llms-txt.ts +13 -5
- package/src/manifest.ts +264 -48
- package/src/module-graph.ts +146 -0
- package/src/navigation.ts +151 -5
- package/src/plugins/css-modules.ts +0 -0
- package/src/plugins/kit.ts +11 -11
- package/src/plugins/scss.ts +3 -3
- package/src/plugins/vite-leak-guard.ts +110 -0
- package/src/plugins/vite-server-fn.ts +67 -0
- package/src/plugins/vite-server-only.ts +75 -0
- package/src/prerender.ts +10 -10
- package/src/public-dir.ts +108 -0
- package/src/query.ts +22 -22
- package/src/route-manifest.ts +240 -0
- package/src/router.ts +288 -74
- package/src/search.ts +297 -0
- package/src/server-only.ts +7 -7
- package/src/service-worker.ts +165 -0
- package/src/vite.ts +103 -49
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `public/` - user-authored static files, served identically in dev and in production.
|
|
3
|
+
*
|
|
4
|
+
* The asymmetry this closes: `nifra dev` served `public/` for free because the HMR path runs on Vite
|
|
5
|
+
* and Vite serves `public/` by default, while production had no `publicDir` concept at all. So a file
|
|
6
|
+
* worked all the way through development and 404'd the moment it was deployed. The failure is
|
|
7
|
+
* inverted - it appears only in production, and only for the assets nobody smoke-tests - and it has
|
|
8
|
+
* already shipped once as a self-hosted webfont that silently fell back to a system font in prod.
|
|
9
|
+
*
|
|
10
|
+
* One owner for both sides is the fix, not a second implementation that happens to agree today.
|
|
11
|
+
*
|
|
12
|
+
* Distinct from `publicPath` in `build.ts`, which is the URL prefix for content-hashed bundle chunks.
|
|
13
|
+
* The names collide and the concepts do not: `publicPath` never covers user-authored files.
|
|
14
|
+
*/
|
|
15
|
+
import { realpath } from "node:fs/promises";
|
|
16
|
+
import { normalize, resolve, sep } from "node:path";
|
|
17
|
+
const IMMUTABLE = "public, max-age=31536000, immutable";
|
|
18
|
+
const ONE_DAY = "public, max-age=86400";
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a URL pathname to an absolute path **confined** to `root`, or `undefined` if it escapes.
|
|
21
|
+
*
|
|
22
|
+
* This is a user-path-to-filesystem sink, which makes it the one part of this feature with a security
|
|
23
|
+
* consequence. Decode first (`%2e%2e%2f` is `../`), then normalize, then verify the result is still
|
|
24
|
+
* under `root` by prefix - checking the *resolved* path rather than scanning the input for `..`,
|
|
25
|
+
* because a blocklist over encodings is exactly the kind of check that gets bypassed.
|
|
26
|
+
*/
|
|
27
|
+
export function resolvePublicPath(root, pathname) {
|
|
28
|
+
let decoded;
|
|
29
|
+
try {
|
|
30
|
+
decoded = decodeURIComponent(pathname);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined; // malformed percent-encoding: not a path we will guess at
|
|
34
|
+
}
|
|
35
|
+
// A NUL can truncate a path in a downstream syscall; refuse rather than normalize it away.
|
|
36
|
+
if (decoded.includes("\0"))
|
|
37
|
+
return undefined;
|
|
38
|
+
const rootResolved = resolve(root);
|
|
39
|
+
const candidate = resolve(rootResolved, `.${normalize(decoded)}`);
|
|
40
|
+
if (candidate !== rootResolved && !candidate.startsWith(rootResolved + sep))
|
|
41
|
+
return undefined;
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build a static-file handler for `dir`.
|
|
46
|
+
*
|
|
47
|
+
* Returns `undefined` on any miss so the caller **falls through to routing** - a static probe must
|
|
48
|
+
* never shadow a route. That ordering is also why a `routes/robots.txt.tsx` beats a
|
|
49
|
+
* `public/robots.txt` only if the caller checks routes first; the documented precedence is that the
|
|
50
|
+
* static probe runs first, so `public/` wins, and an app wanting the route should not ship both.
|
|
51
|
+
*/
|
|
52
|
+
export function servePublicDir(options) {
|
|
53
|
+
const root = resolve(options.dir);
|
|
54
|
+
const rootReal = realpath(root).catch(() => undefined);
|
|
55
|
+
const hashedPrefix = options.hashedPrefix ?? "/assets/";
|
|
56
|
+
const hashed = options.cache?.hashed ?? IMMUTABLE;
|
|
57
|
+
const assets = options.cache?.assets ?? ONE_DAY;
|
|
58
|
+
return async (request) => {
|
|
59
|
+
if (request.method !== "GET" && request.method !== "HEAD")
|
|
60
|
+
return undefined;
|
|
61
|
+
const { pathname } = new URL(request.url);
|
|
62
|
+
// A production manifest can reject page routes without touching the filesystem.
|
|
63
|
+
if (options.files !== undefined && !options.files.has(pathname))
|
|
64
|
+
return undefined;
|
|
65
|
+
const abs = resolvePublicPath(root, pathname);
|
|
66
|
+
if (abs === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
const [resolvedRoot, resolvedFile] = await Promise.all([
|
|
69
|
+
rootReal,
|
|
70
|
+
realpath(abs).catch(() => undefined),
|
|
71
|
+
]);
|
|
72
|
+
if (resolvedRoot === undefined ||
|
|
73
|
+
resolvedFile === undefined ||
|
|
74
|
+
(resolvedFile !== resolvedRoot && !resolvedFile.startsWith(resolvedRoot + sep))) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const file = Bun.file(resolvedFile);
|
|
78
|
+
if (!(await file.exists()))
|
|
79
|
+
return undefined;
|
|
80
|
+
const headers = new Headers({
|
|
81
|
+
"cache-control": pathname.startsWith(hashedPrefix) ? hashed : assets,
|
|
82
|
+
});
|
|
83
|
+
// HEAD must not carry a body, but must otherwise match GET's headers.
|
|
84
|
+
return request.method === "HEAD"
|
|
85
|
+
? new Response(null, { headers })
|
|
86
|
+
: new Response(file, { headers });
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=public-dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-dir.js","sourceRoot":"","sources":["../src/public-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAqBnD,MAAM,SAAS,GAAG,qCAAqC,CAAA;AACvD,MAAM,OAAO,GAAG,uBAAuB,CAAA;AAEvC;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,QAAgB;IAC9D,IAAI,OAAe,CAAA;IACnB,IAAI,CAAC;QACH,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA,CAAC,0DAA0D;IAC7E,CAAC;IACD,2FAA2F;IAC3F,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IAC5C,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,IAAI,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACjE,IAAI,SAAS,KAAK,YAAY,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,YAAY,GAAG,GAAG,CAAC;QAAE,OAAO,SAAS,CAAA;IAC7F,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,OAA8B;IAE9B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACjC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IACtD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,UAAU,CAAA;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,SAAS,CAAA;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,OAAO,CAAA;IAE/C,OAAO,KAAK,EAAE,OAAgB,EAAiC,EAAE;QAC/D,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,SAAS,CAAA;QAC3E,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACzC,gFAAgF;QAChF,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,SAAS,CAAA;QACjF,MAAM,GAAG,GAAG,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAC7C,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QACvC,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACrD,QAAQ;YACR,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;SACrC,CAAC,CAAA;QACF,IACE,YAAY,KAAK,SAAS;YAC1B,YAAY,KAAK,SAAS;YAC1B,CAAC,YAAY,KAAK,YAAY,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,EAC/E,CAAC;YACD,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QACnC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YAAE,OAAO,SAAS,CAAA;QAC5C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;YAC1B,eAAe,EAAE,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;SACrE,CAAC,CAAA;QACF,sEAAsE;QACtE,OAAO,OAAO,CAAC,MAAM,KAAK,MAAM;YAC9B,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC;YACjC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAA;IACrC,CAAC,CAAA;AACH,CAAC"}
|
package/dist/query.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The agnostic keyed query-cache
|
|
2
|
+
* The agnostic keyed query-cache - a `query(key, fn)` primitive with in-flight dedup, `staleTime`
|
|
3
3
|
* freshness, prefix `invalidateQueries`, imperative cache read/write, prefetch, SSR dehydrate/hydrate,
|
|
4
4
|
* infinite (paged) queries, and standalone mutations. Pure logic, no `window`, no framework, no
|
|
5
|
-
* `Date.now`/timers (the clock is injected)
|
|
5
|
+
* `Date.now`/timers (the clock is injected) - so it unit-tests deterministically and is safe to import
|
|
6
6
|
* from the SSR core's main entry. A per-adapter binding (`useQuery`/`useMutation`/`createQuery`)
|
|
7
7
|
* subscribes to a query/mutation's `subscribe`/`snapshot` store (the same shape the router + fetchers
|
|
8
8
|
* use).
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
*/
|
|
14
14
|
/** A query's lifecycle status. `pending` = no data yet; `success`/`error` once it has settled once. */
|
|
15
15
|
export type QueryStatus = "pending" | "success" | "error";
|
|
16
|
-
/** A query's observable state
|
|
16
|
+
/** A query's observable state - what a binding renders. A new (frozen) object per transition, so a
|
|
17
17
|
* `useSyncExternalStore`/signal binding can compare by reference. */
|
|
18
18
|
export interface QueryState<T = unknown> {
|
|
19
19
|
readonly status: QueryStatus;
|
|
20
20
|
readonly data: T | undefined;
|
|
21
21
|
readonly error: unknown;
|
|
22
|
-
/** True while a fetch is in flight
|
|
22
|
+
/** True while a fetch is in flight - the initial load OR a background refetch (data may still show). */
|
|
23
23
|
readonly isFetching: boolean;
|
|
24
24
|
/** When `data` was last set (via the injected clock); drives `staleTime`. `-Infinity` until first set. */
|
|
25
25
|
readonly updatedAt: number;
|
|
@@ -41,14 +41,14 @@ export interface QueryOptions {
|
|
|
41
41
|
readonly staleTime?: number;
|
|
42
42
|
}
|
|
43
43
|
export interface QueryClientOptions {
|
|
44
|
-
/** Monotonic clock in ms
|
|
44
|
+
/** Monotonic clock in ms - injected so the core stays deterministic + testable; the adapter binding
|
|
45
45
|
* passes `() => Date.now()`. */
|
|
46
46
|
readonly now: () => number;
|
|
47
47
|
/** Data stays fresh this long after a fetch; older ⇒ refetch on the next `fetch()` (default `0`). */
|
|
48
48
|
readonly staleTime?: number;
|
|
49
49
|
/** Evict an entry this long (ms) after its last subscriber leaves (default 5 min). */
|
|
50
50
|
readonly gcTime?: number;
|
|
51
|
-
/** Hard cap on cached entries
|
|
51
|
+
/** Hard cap on cached entries - LRU-evict the oldest *unsubscribed* entry past it (default 1000). */
|
|
52
52
|
readonly max?: number;
|
|
53
53
|
}
|
|
54
54
|
/** An infinite (paged) query's accumulated data: the fetched `pages` in order + the `pageParam` each
|
|
@@ -58,7 +58,7 @@ export interface InfiniteData<T, P> {
|
|
|
58
58
|
readonly pageParams: readonly P[];
|
|
59
59
|
}
|
|
60
60
|
/** Options for an {@link InfiniteQueryHandle}. `getNextPageParam` (required) derives the param for the
|
|
61
|
-
* next page from the last page
|
|
61
|
+
* next page from the last page - return `undefined`/`null` to signal there is no next page. */
|
|
62
62
|
export interface InfiniteQueryOptions<T, P> extends QueryOptions {
|
|
63
63
|
/** The param the FIRST page is fetched with. */
|
|
64
64
|
readonly initialPageParam: P;
|
|
@@ -85,7 +85,7 @@ export interface InfiniteQueryHandle<T, P> {
|
|
|
85
85
|
/** Whether a page can be prepended (derived from `getPreviousPageParam` of the first page). */
|
|
86
86
|
hasPreviousPage: () => boolean;
|
|
87
87
|
}
|
|
88
|
-
/** A serializable snapshot of the cache's successful queries
|
|
88
|
+
/** A serializable snapshot of the cache's successful queries - the SSR→client bridge payload. */
|
|
89
89
|
export interface DehydratedState {
|
|
90
90
|
readonly queries: ReadonlyArray<{
|
|
91
91
|
readonly key: unknown;
|
|
@@ -119,7 +119,7 @@ export interface QueryClient {
|
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
121
|
* Hash a query key to a stable cache string. Object keys are sorted (so `{a,b}` ≡ `{b,a}`); arrays
|
|
122
|
-
* keep order. Keys must be serializable
|
|
122
|
+
* keep order. Keys must be serializable - a function/symbol in the key throws (it can't be a stable
|
|
123
123
|
* identity). Mirrors TanStack Query's structural hashing.
|
|
124
124
|
*/
|
|
125
125
|
export declare function hashQueryKey(key: unknown): string;
|
|
@@ -154,7 +154,7 @@ export interface MutationHandle<TData, TVariables> {
|
|
|
154
154
|
reset: () => void;
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
|
-
* Create a standalone mutation state machine
|
|
157
|
+
* Create a standalone mutation state machine - framework-agnostic, so a per-adapter `useMutation`
|
|
158
158
|
* binding just subscribes to it. Single-flight by a monotonic token: overlapping `mutate` calls each
|
|
159
159
|
* run their `fn`, but only the latest publishes state (an older, slower response can't clobber a newer
|
|
160
160
|
* one). The callbacks fire in TanStack order: `onMutate` (before), then `onSuccess`/`onError`, then
|
package/dist/query.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The agnostic keyed query-cache
|
|
2
|
+
* The agnostic keyed query-cache - a `query(key, fn)` primitive with in-flight dedup, `staleTime`
|
|
3
3
|
* freshness, prefix `invalidateQueries`, imperative cache read/write, prefetch, SSR dehydrate/hydrate,
|
|
4
4
|
* infinite (paged) queries, and standalone mutations. Pure logic, no `window`, no framework, no
|
|
5
|
-
* `Date.now`/timers (the clock is injected)
|
|
5
|
+
* `Date.now`/timers (the clock is injected) - so it unit-tests deterministically and is safe to import
|
|
6
6
|
* from the SSR core's main entry. A per-adapter binding (`useQuery`/`useMutation`/`createQuery`)
|
|
7
7
|
* subscribes to a query/mutation's `subscribe`/`snapshot` store (the same shape the router + fetchers
|
|
8
8
|
* use).
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
/**
|
|
15
15
|
* Hash a query key to a stable cache string. Object keys are sorted (so `{a,b}` ≡ `{b,a}`); arrays
|
|
16
|
-
* keep order. Keys must be serializable
|
|
16
|
+
* keep order. Keys must be serializable - a function/symbol in the key throws (it can't be a stable
|
|
17
17
|
* identity). Mirrors TanStack Query's structural hashing.
|
|
18
18
|
*/
|
|
19
19
|
export function hashQueryKey(key) {
|
|
@@ -54,7 +54,7 @@ export function createQueryClient(options) {
|
|
|
54
54
|
const max = options.max ?? 1000;
|
|
55
55
|
const cache = new Map();
|
|
56
56
|
// Lazily evict entries whose GC timer has elapsed (no subscribers), then enforce the hard cap by
|
|
57
|
-
// dropping the oldest-inserted unsubscribed entries. Called on every `query()`
|
|
57
|
+
// dropping the oldest-inserted unsubscribed entries. Called on every `query()` - no background timer.
|
|
58
58
|
const sweep = () => {
|
|
59
59
|
const t = now();
|
|
60
60
|
for (const [h, e] of cache) {
|
|
@@ -91,8 +91,8 @@ export function createQueryClient(options) {
|
|
|
91
91
|
handle: undefined,
|
|
92
92
|
};
|
|
93
93
|
const emit = () => {
|
|
94
|
-
// Iterate the live set
|
|
95
|
-
// listener must not synchronously subscribe/unsubscribe during notification
|
|
94
|
+
// Iterate the live set - no per-emit copy (the common case is 0-1 subscribers). Contract: a
|
|
95
|
+
// listener must not synchronously subscribe/unsubscribe during notification - `useSyncExternalStore`/
|
|
96
96
|
// signal/store bindings don't (un/subscribe happens in a later effect/cleanup tick).
|
|
97
97
|
if (entry.listeners.size === 0)
|
|
98
98
|
return;
|
|
@@ -105,20 +105,20 @@ export function createQueryClient(options) {
|
|
|
105
105
|
};
|
|
106
106
|
const isStale = () => entry.invalidated || now() - entry.state.updatedAt >= entry.staleTime;
|
|
107
107
|
entry.run = (force) => {
|
|
108
|
-
// Dedup
|
|
108
|
+
// Dedup - but join the in-flight fetch ONLY when it belongs to the current generation. An
|
|
109
109
|
// `invalidateQueries` bumps `generation`, so a fetch that started *before* the invalidation is
|
|
110
110
|
// superseded and must NOT satisfy this call (its data is pre-mutation); fall through and kick a
|
|
111
111
|
// fresh fetch instead of returning the stale in-flight one.
|
|
112
112
|
if (entry.promise !== undefined && entry.promiseGen === entry.generation)
|
|
113
113
|
return entry.promise;
|
|
114
114
|
if (!force && entry.state.status === "success" && !isStale())
|
|
115
|
-
return Promise.resolve(entry.state.data); // fresh cache hit
|
|
115
|
+
return Promise.resolve(entry.state.data); // fresh cache hit - no fn call
|
|
116
116
|
const gen = entry.generation;
|
|
117
117
|
entry.setState({ ...entry.state, isFetching: true }); // show the background/initial fetch
|
|
118
118
|
const promise = (async () => {
|
|
119
119
|
try {
|
|
120
120
|
const data = await entry.fn();
|
|
121
|
-
// A newer generation (a later invalidate/refetch) owns the entry now
|
|
121
|
+
// A newer generation (a later invalidate/refetch) owns the entry now - discard this result
|
|
122
122
|
// rather than publishing pre-mutation data as fresh or clearing `invalidated`. The fetch
|
|
123
123
|
// that superseded this one publishes the post-mutation data.
|
|
124
124
|
if (gen !== entry.generation)
|
|
@@ -148,7 +148,7 @@ export function createQueryClient(options) {
|
|
|
148
148
|
snapshot: () => entry.state,
|
|
149
149
|
subscribe: (listener) => {
|
|
150
150
|
entry.subscribers++;
|
|
151
|
-
entry.gcAt = undefined; // in use again
|
|
151
|
+
entry.gcAt = undefined; // in use again - cancel any pending eviction
|
|
152
152
|
entry.listeners.add(listener);
|
|
153
153
|
return () => {
|
|
154
154
|
entry.listeners.delete(listener);
|
|
@@ -185,7 +185,7 @@ export function createQueryClient(options) {
|
|
|
185
185
|
if (!keyMatchesPrefix(entry.key, keyOrPrefix))
|
|
186
186
|
continue;
|
|
187
187
|
entry.invalidated = true; // next fetch refetches even within staleTime
|
|
188
|
-
// Supersede any in-flight fetch begun before this point
|
|
188
|
+
// Supersede any in-flight fetch begun before this point - its result is pre-mutation and must
|
|
189
189
|
// not resolve as "fresh" or clear `invalidated`. The refetch below (or the next `fetch()`)
|
|
190
190
|
// starts a new generation that publishes the post-mutation data.
|
|
191
191
|
entry.generation++;
|
|
@@ -200,13 +200,13 @@ export function createQueryClient(options) {
|
|
|
200
200
|
: undefined;
|
|
201
201
|
};
|
|
202
202
|
const setQueryData = (key, updater) => {
|
|
203
|
-
// Create a placeholder entry if none exists
|
|
203
|
+
// Create a placeholder entry if none exists - its `fn` throws until a real `query(key, fn)` re-binds
|
|
204
204
|
// it, so a `setQueryData` for a key nobody queries yet never triggers a fetch on its own.
|
|
205
205
|
const entry = upsert(key, cache.get(hashQueryKey(key))?.fn ??
|
|
206
206
|
(() => Promise.reject(new Error("[nifra/web] query has no fetcher (setQueryData-only key)"))), defaultStaleTime);
|
|
207
207
|
const prev = entry.state.status === "success" ? entry.state.data : undefined;
|
|
208
208
|
const data = typeof updater === "function" ? updater(prev) : updater;
|
|
209
|
-
// A direct write is the new truth
|
|
209
|
+
// A direct write is the new truth - bump the generation so any in-flight fetch can't overwrite it,
|
|
210
210
|
// and clear the invalidated flag (the caller just supplied fresh data).
|
|
211
211
|
entry.generation++;
|
|
212
212
|
entry.invalidated = false;
|
|
@@ -391,7 +391,7 @@ const IDLE_MUTATION = Object.freeze({
|
|
|
391
391
|
variables: undefined,
|
|
392
392
|
});
|
|
393
393
|
/**
|
|
394
|
-
* Create a standalone mutation state machine
|
|
394
|
+
* Create a standalone mutation state machine - framework-agnostic, so a per-adapter `useMutation`
|
|
395
395
|
* binding just subscribes to it. Single-flight by a monotonic token: overlapping `mutate` calls each
|
|
396
396
|
* run their `fn`, but only the latest publishes state (an older, slower response can't clobber a newer
|
|
397
397
|
* one). The callbacks fire in TanStack order: `onMutate` (before), then `onSuccess`/`onError`, then
|
|
@@ -427,7 +427,7 @@ export function createMutation(fn, callbacks = {}) {
|
|
|
427
427
|
mutate: async (variables) => {
|
|
428
428
|
const mine = ++token;
|
|
429
429
|
setState({ status: "pending", data: undefined, error: undefined, variables });
|
|
430
|
-
// Only await a callback when one is actually supplied
|
|
430
|
+
// Only await a callback when one is actually supplied - an `await undefined` would insert a
|
|
431
431
|
// needless microtask before the mutationFn runs (and complicates single-flight timing).
|
|
432
432
|
if (cbs.onMutate !== undefined)
|
|
433
433
|
await cbs.onMutate(variables);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web/route-manifest` - what each route actually DOES, as one artifact.
|
|
3
|
+
*
|
|
4
|
+
* The facts are already in the route modules: `prerender`, `getStaticPaths`, `revalidate`, `hydrate`.
|
|
5
|
+
* What has never existed is anywhere to read them together. To answer "which pages are static?", "which
|
|
6
|
+
* ones revalidate, and how often?", "which ship no JS?", you open every route file and hold the answer in
|
|
7
|
+
* your head - and the answer changes on the target you deploy to, which is nowhere near the route file.
|
|
8
|
+
*
|
|
9
|
+
* That gap is not just inconvenient. The interesting cases are the ones where a route's declaration and
|
|
10
|
+
* its deploy target disagree, and there is currently nothing that can notice: an ISR route on a target
|
|
11
|
+
* with no revalidation story regenerates on every request; a `static` build containing a route that was
|
|
12
|
+
* never prerenderable ships a page that 404s. Both are silent, and both are decided at build time by
|
|
13
|
+
* information no single place holds.
|
|
14
|
+
*
|
|
15
|
+
* So this derives one record per route - render mode, hydration, cache policy - resolves it against the
|
|
16
|
+
* target, and reports what the target cannot honour. Pure and fs-free: it takes an already-built
|
|
17
|
+
* `Manifest` so it runs at build time, in `nifra routes`, and in a test with a hand-built manifest.
|
|
18
|
+
*/
|
|
19
|
+
import type { Manifest, RouteModule } from "./manifest.js";
|
|
20
|
+
/**
|
|
21
|
+
* How a route produces its HTML.
|
|
22
|
+
*
|
|
23
|
+
* - `static` - prerendered at build; served as a file, no server render at request time.
|
|
24
|
+
* - `isr` - server-rendered, then cached and revalidated on a timer (`export const revalidate`).
|
|
25
|
+
* - `ssr` - server-rendered per request. The default, and the only mode that needs a live server.
|
|
26
|
+
*/
|
|
27
|
+
export type RenderMode = "static" | "isr" | "ssr";
|
|
28
|
+
/** What a route needs from its host in order to behave as declared. */
|
|
29
|
+
export type RouteCapability = "server" | "revalidation";
|
|
30
|
+
/** One route's resolved behaviour. */
|
|
31
|
+
export interface RouteManifestEntry {
|
|
32
|
+
/** The route's id (its key in the build manifest and in `routes`). */
|
|
33
|
+
readonly id: string;
|
|
34
|
+
/** The matching pattern, e.g. `/blog/:slug`. */
|
|
35
|
+
readonly pattern: string;
|
|
36
|
+
readonly mode: RenderMode;
|
|
37
|
+
/** Whether the full-document client is shipped. `false` means the page loads no framework JS. */
|
|
38
|
+
readonly hydrate: boolean;
|
|
39
|
+
/** Revalidation window in seconds. Present only for `isr`. */
|
|
40
|
+
readonly revalidate?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Concrete paths prerendered at build. Present for `static` routes: a single path for a static route,
|
|
43
|
+
* one per `getStaticPaths` entry for a dynamic one. Absent means "no path was enumerated", which for a
|
|
44
|
+
* dynamic route is exactly the condition that makes it unprerenderable.
|
|
45
|
+
*/
|
|
46
|
+
readonly prerenderedPaths?: readonly string[];
|
|
47
|
+
/** What this route needs from the host to behave as declared. */
|
|
48
|
+
readonly requires: readonly RouteCapability[];
|
|
49
|
+
}
|
|
50
|
+
/** A route whose declaration the chosen target cannot honour, and what actually happens if it ships. */
|
|
51
|
+
export interface RouteManifestConflict {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly pattern: string;
|
|
54
|
+
readonly capability: RouteCapability;
|
|
55
|
+
/** What goes wrong on this target - the consequence, not the rule that was broken. */
|
|
56
|
+
readonly consequence: string;
|
|
57
|
+
}
|
|
58
|
+
/** The whole artifact: every route's behaviour, plus anything the target cannot honour. */
|
|
59
|
+
export interface RouteManifest {
|
|
60
|
+
/** The deploy target this was resolved against, when one was given. */
|
|
61
|
+
readonly target?: string;
|
|
62
|
+
readonly routes: readonly RouteManifestEntry[];
|
|
63
|
+
readonly conflicts: readonly RouteManifestConflict[];
|
|
64
|
+
/** Route counts by mode - the summary a report leads with. */
|
|
65
|
+
readonly totals: Readonly<Record<RenderMode, number>>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Derive one route's behaviour from its module exports.
|
|
69
|
+
*
|
|
70
|
+
* `prerender`/`getStaticPaths` win over `revalidate`: a page rendered at build time is not revalidated at
|
|
71
|
+
* runtime, so if a route declares both, the build-time answer is the one that describes what ships. That
|
|
72
|
+
* combination is worth surfacing rather than silently resolving, which is what `conflicts` is for once a
|
|
73
|
+
* target is known.
|
|
74
|
+
*/
|
|
75
|
+
export declare function deriveRouteEntry(id: string, pattern: string, module: Pick<RouteModule, "prerender" | "getStaticPaths" | "revalidate" | "hydrate">, prerenderedPaths?: readonly string[]): RouteManifestEntry;
|
|
76
|
+
/**
|
|
77
|
+
* Build the route manifest for a discovered app, optionally resolved against a deploy target.
|
|
78
|
+
*
|
|
79
|
+
* `prerendered` maps a route id to the concrete paths the build actually emitted for it - pass what
|
|
80
|
+
* `prerenderRoutes` produced. Without it, dynamic routes are reported by their declaration alone, which
|
|
81
|
+
* is the right answer before a build has run and the wrong one after.
|
|
82
|
+
*/
|
|
83
|
+
export declare function buildRouteManifest(manifest: Manifest, options?: {
|
|
84
|
+
readonly target?: string;
|
|
85
|
+
readonly prerendered?: Readonly<Record<string, readonly string[]>>;
|
|
86
|
+
/** Runtime capabilities supplied outside generated entries (for example an explicit `withISR`). */
|
|
87
|
+
readonly capabilities?: readonly RouteCapability[];
|
|
88
|
+
}): Promise<RouteManifest>;
|
|
89
|
+
/** Render the manifest as a readable report - the `nifra routes --modes` output. */
|
|
90
|
+
export declare function renderRouteManifest(manifest: RouteManifest): string;
|
|
91
|
+
//# sourceMappingURL=route-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-manifest.d.ts","sourceRoot":"","sources":["../src/route-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE1D;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAA;AAEjD,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,cAAc,CAAA;AAEvD,sCAAsC;AACtC,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,iGAAiG;IACjG,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7C,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAA;CAC9C;AAED,wGAAwG;AACxG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAA;IACpC,sFAAsF;IACtF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAC9C,QAAQ,CAAC,SAAS,EAAE,SAAS,qBAAqB,EAAE,CAAA;IACpD,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA;CACtD;AAKD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,gBAAgB,GAAG,YAAY,GAAG,SAAS,CAAC,EACpF,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GACnC,kBAAkB,CAyBpB;AA0BD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE;IACP,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;IAClE,mGAAmG;IACnG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;CAC9C,GACL,OAAO,CAAC,aAAa,CAAC,CAuCxB;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CA2CnE"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/** A route is dynamic when its pattern carries a param or wildcard segment. */
|
|
2
|
+
const isDynamic = (pattern) => /[:*]/.test(pattern);
|
|
3
|
+
/**
|
|
4
|
+
* Derive one route's behaviour from its module exports.
|
|
5
|
+
*
|
|
6
|
+
* `prerender`/`getStaticPaths` win over `revalidate`: a page rendered at build time is not revalidated at
|
|
7
|
+
* runtime, so if a route declares both, the build-time answer is the one that describes what ships. That
|
|
8
|
+
* combination is worth surfacing rather than silently resolving, which is what `conflicts` is for once a
|
|
9
|
+
* target is known.
|
|
10
|
+
*/
|
|
11
|
+
export function deriveRouteEntry(id, pattern, module, prerenderedPaths) {
|
|
12
|
+
const wantsPrerender = module.prerender === true || module.getStaticPaths !== undefined;
|
|
13
|
+
// A dynamic route is only really static if concrete paths were enumerated for it. `getStaticPaths`
|
|
14
|
+
// declares the intent; the paths are the evidence, and without them there is nothing prerendered to
|
|
15
|
+
// serve. Treating intent as sufficient is how a "static" build ships a page that 404s.
|
|
16
|
+
const enumerated = prerenderedPaths !== undefined && prerenderedPaths.length > 0;
|
|
17
|
+
const isStatic = wantsPrerender && (!isDynamic(pattern) || enumerated);
|
|
18
|
+
const mode = isStatic ? "static" : module.revalidate !== undefined ? "isr" : "ssr";
|
|
19
|
+
const requires = [];
|
|
20
|
+
if (mode !== "static")
|
|
21
|
+
requires.push("server");
|
|
22
|
+
if (mode === "isr")
|
|
23
|
+
requires.push("revalidation");
|
|
24
|
+
const entry = { id, pattern, mode, hydrate: module.hydrate !== false, requires };
|
|
25
|
+
if (mode === "isr" && module.revalidate !== undefined)
|
|
26
|
+
entry.revalidate = module.revalidate;
|
|
27
|
+
if (prerenderedPaths !== undefined)
|
|
28
|
+
entry.prerenderedPaths = prerenderedPaths;
|
|
29
|
+
return entry;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What each deploy target can actually do.
|
|
33
|
+
*
|
|
34
|
+
* `static` is the one that matters: it has no server at all, so every non-prerendered route is a page
|
|
35
|
+
* that will 404 in production while working perfectly in dev. The rest all run a server; they differ in
|
|
36
|
+
* how revalidation is wired, not in whether it exists, so this deliberately does not invent finer
|
|
37
|
+
* distinctions it cannot verify.
|
|
38
|
+
*/
|
|
39
|
+
const TARGET_CAPABILITIES = {
|
|
40
|
+
bun: ["server"],
|
|
41
|
+
node: ["server"],
|
|
42
|
+
deno: ["server"],
|
|
43
|
+
"cf-pages": ["server"],
|
|
44
|
+
vercel: ["server"],
|
|
45
|
+
static: [],
|
|
46
|
+
};
|
|
47
|
+
const CONSEQUENCE = {
|
|
48
|
+
server: "this target ships no server, so the route is never rendered - the URL 404s in production while working in dev. Add `export const prerender = true` (or `getStaticPaths` for a dynamic route), or build for a target that runs a server.",
|
|
49
|
+
revalidation: "this target cannot revalidate a cached page, so `revalidate` has no effect and the route renders on every request.",
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Build the route manifest for a discovered app, optionally resolved against a deploy target.
|
|
53
|
+
*
|
|
54
|
+
* `prerendered` maps a route id to the concrete paths the build actually emitted for it - pass what
|
|
55
|
+
* `prerenderRoutes` produced. Without it, dynamic routes are reported by their declaration alone, which
|
|
56
|
+
* is the right answer before a build has run and the wrong one after.
|
|
57
|
+
*/
|
|
58
|
+
export async function buildRouteManifest(manifest, options = {}) {
|
|
59
|
+
const entries = [];
|
|
60
|
+
for (const route of manifest.routes) {
|
|
61
|
+
// Loading the module is what makes this authoritative rather than a guess from the filename: the
|
|
62
|
+
// render mode lives in the module's exports and nowhere else.
|
|
63
|
+
const module = (await route.load());
|
|
64
|
+
entries.push(deriveRouteEntry(route.id, route.pattern, module, options.prerendered?.[route.id]));
|
|
65
|
+
}
|
|
66
|
+
entries.sort((a, b) => a.pattern.localeCompare(b.pattern));
|
|
67
|
+
const conflicts = [];
|
|
68
|
+
const capabilities = options.capabilities ??
|
|
69
|
+
(options.target === undefined ? undefined : TARGET_CAPABILITIES[options.target]);
|
|
70
|
+
if (capabilities !== undefined) {
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
for (const capability of entry.requires) {
|
|
73
|
+
if (capabilities.includes(capability))
|
|
74
|
+
continue;
|
|
75
|
+
conflicts.push({
|
|
76
|
+
id: entry.id,
|
|
77
|
+
pattern: entry.pattern,
|
|
78
|
+
capability,
|
|
79
|
+
consequence: CONSEQUENCE[capability],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const totals = { static: 0, isr: 0, ssr: 0 };
|
|
85
|
+
for (const entry of entries)
|
|
86
|
+
totals[entry.mode] += 1;
|
|
87
|
+
const result = { routes: entries, conflicts, totals };
|
|
88
|
+
if (options.target !== undefined)
|
|
89
|
+
result.target = options.target;
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
/** Render the manifest as a readable report - the `nifra routes --modes` output. */
|
|
93
|
+
export function renderRouteManifest(manifest) {
|
|
94
|
+
const lines = [];
|
|
95
|
+
lines.push(manifest.target === undefined
|
|
96
|
+
? "nifra route manifest"
|
|
97
|
+
: `nifra route manifest - target: ${manifest.target}`);
|
|
98
|
+
lines.push("");
|
|
99
|
+
if (manifest.routes.length === 0) {
|
|
100
|
+
lines.push("no routes found.");
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
const width = Math.max(10, ...manifest.routes.map((r) => r.pattern.length)) + 2;
|
|
104
|
+
lines.push(`${"route".padEnd(width)}${"mode".padEnd(9)}${"hydrate".padEnd(9)}cache`);
|
|
105
|
+
lines.push("-".repeat(width + 18 + 24));
|
|
106
|
+
for (const route of manifest.routes) {
|
|
107
|
+
const cache = route.mode === "isr"
|
|
108
|
+
? `revalidate ${route.revalidate}s`
|
|
109
|
+
: route.mode === "static"
|
|
110
|
+
? `prerendered${route.prerenderedPaths ? ` (${route.prerenderedPaths.length})` : ""}`
|
|
111
|
+
: "per request";
|
|
112
|
+
lines.push(route.pattern.padEnd(width) +
|
|
113
|
+
route.mode.padEnd(9) +
|
|
114
|
+
(route.hydrate ? "yes" : "no").padEnd(9) +
|
|
115
|
+
cache);
|
|
116
|
+
}
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push(`${manifest.totals.static} static · ${manifest.totals.isr} isr · ${manifest.totals.ssr} ssr`);
|
|
119
|
+
if (manifest.conflicts.length > 0) {
|
|
120
|
+
lines.push("");
|
|
121
|
+
lines.push(`✗ ${manifest.conflicts.length} route(s) the target cannot honour:`);
|
|
122
|
+
for (const conflict of manifest.conflicts) {
|
|
123
|
+
lines.push(` ✗ ${conflict.pattern} - ${conflict.consequence}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return lines.join("\n");
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=route-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-manifest.js","sourceRoot":"","sources":["../src/route-manifest.ts"],"names":[],"mappings":"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"}
|