@nifrajs/web 0.1.0-alpha.1

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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/css-modules.d.ts +34 -0
  4. package/dist/build.d.ts +110 -0
  5. package/dist/build.d.ts.map +1 -0
  6. package/dist/build.js +238 -0
  7. package/dist/build.js.map +1 -0
  8. package/dist/client.d.ts +32 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +204 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/deferred.d.ts +90 -0
  13. package/dist/deferred.d.ts.map +1 -0
  14. package/dist/deferred.js +310 -0
  15. package/dist/deferred.js.map +1 -0
  16. package/dist/dev-error.d.ts +17 -0
  17. package/dist/dev-error.d.ts.map +1 -0
  18. package/dist/dev-error.js +59 -0
  19. package/dist/dev-error.js.map +1 -0
  20. package/dist/dev.d.ts +25 -0
  21. package/dist/dev.d.ts.map +1 -0
  22. package/dist/dev.js +83 -0
  23. package/dist/dev.js.map +1 -0
  24. package/dist/draft.d.ts +58 -0
  25. package/dist/draft.d.ts.map +1 -0
  26. package/dist/draft.js +55 -0
  27. package/dist/draft.js.map +1 -0
  28. package/dist/fonts-google.d.ts +109 -0
  29. package/dist/fonts-google.d.ts.map +1 -0
  30. package/dist/fonts-google.js +291 -0
  31. package/dist/fonts-google.js.map +1 -0
  32. package/dist/fonts.d.ts +67 -0
  33. package/dist/fonts.d.ts.map +1 -0
  34. package/dist/fonts.js +103 -0
  35. package/dist/fonts.js.map +1 -0
  36. package/dist/fs.d.ts +10 -0
  37. package/dist/fs.d.ts.map +1 -0
  38. package/dist/fs.js +24 -0
  39. package/dist/fs.js.map +1 -0
  40. package/dist/index.d.ts +280 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +784 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/islands.d.ts +37 -0
  45. package/dist/islands.d.ts.map +1 -0
  46. package/dist/islands.js +107 -0
  47. package/dist/islands.js.map +1 -0
  48. package/dist/isr.d.ts +154 -0
  49. package/dist/isr.d.ts.map +1 -0
  50. package/dist/isr.js +331 -0
  51. package/dist/isr.js.map +1 -0
  52. package/dist/manifest.d.ts +178 -0
  53. package/dist/manifest.d.ts.map +1 -0
  54. package/dist/manifest.js +213 -0
  55. package/dist/manifest.js.map +1 -0
  56. package/dist/plugins/css-modules.d.ts +40 -0
  57. package/dist/plugins/css-modules.d.ts.map +1 -0
  58. package/dist/plugins/css-modules.js +0 -0
  59. package/dist/plugins/css-modules.js.map +1 -0
  60. package/dist/plugins/kit.d.ts +44 -0
  61. package/dist/plugins/kit.d.ts.map +1 -0
  62. package/dist/plugins/kit.js +105 -0
  63. package/dist/plugins/kit.js.map +1 -0
  64. package/dist/plugins/scss.d.ts +27 -0
  65. package/dist/plugins/scss.d.ts.map +1 -0
  66. package/dist/plugins/scss.js +78 -0
  67. package/dist/plugins/scss.js.map +1 -0
  68. package/dist/prerender.d.ts +77 -0
  69. package/dist/prerender.d.ts.map +1 -0
  70. package/dist/prerender.js +139 -0
  71. package/dist/prerender.js.map +1 -0
  72. package/dist/query.d.ts +62 -0
  73. package/dist/query.d.ts.map +1 -0
  74. package/dist/query.js +184 -0
  75. package/dist/query.js.map +1 -0
  76. package/dist/router.d.ts +169 -0
  77. package/dist/router.d.ts.map +1 -0
  78. package/dist/router.js +531 -0
  79. package/dist/router.js.map +1 -0
  80. package/dist/vite.d.ts +49 -0
  81. package/dist/vite.d.ts.map +1 -0
  82. package/dist/vite.js +144 -0
  83. package/dist/vite.js.map +1 -0
  84. package/package.json +127 -0
  85. package/src/build.ts +375 -0
  86. package/src/client.ts +218 -0
  87. package/src/deferred.ts +339 -0
  88. package/src/dev-error.ts +62 -0
  89. package/src/dev.ts +108 -0
  90. package/src/draft.ts +82 -0
  91. package/src/fonts-google.ts +397 -0
  92. package/src/fonts.ts +147 -0
  93. package/src/fs.ts +26 -0
  94. package/src/index.ts +1202 -0
  95. package/src/islands.ts +121 -0
  96. package/src/isr.ts +461 -0
  97. package/src/manifest.ts +366 -0
  98. package/src/plugins/css-modules.ts +0 -0
  99. package/src/plugins/kit.ts +123 -0
  100. package/src/plugins/scss.ts +108 -0
  101. package/src/prerender.ts +209 -0
  102. package/src/query.ts +250 -0
  103. package/src/router.ts +676 -0
  104. package/src/vite.ts +212 -0
package/dist/isr.js ADDED
@@ -0,0 +1,331 @@
1
+ /**
2
+ * ISR (Incremental Static Regeneration). A caching layer that sits ABOVE the agnostic SSR
3
+ * handler: it stores a rendered `Response` keyed by URL and serves it with stale-while-revalidate
4
+ * freshness, so dynamic pages get static-like speed and revalidate in the background. Agnostic to the
5
+ * UI framework (it caches bytes), coupled to the runtime only through this pluggable {@link CacheStore}.
6
+ *
7
+ * This module is the store primitive; the SWR wrapper (`withISR`) builds on it next.
8
+ */
9
+ import { isDraftEnabled } from "./draft.js";
10
+ /**
11
+ * In-process ISR cache. Refuses to run in production unless explicitly allowed (mirrors the
12
+ * rate-limit `MemoryStore` — a per-instance cache is unsafe across instances). Bounded **LRU**: a
13
+ * read or write bumps the entry, so the least-recently-used evicts past `max` (a hot, frequently-read
14
+ * page survives a burst of new pages).
15
+ */
16
+ export class MemoryCacheStore {
17
+ cache = new Map();
18
+ max;
19
+ constructor(options = {}) {
20
+ const isProd = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
21
+ if (options.allowInProduction !== true && isProd) {
22
+ throw new Error("[nifra/web] MemoryCacheStore is per-instance and unsafe in production (each instance caches " +
23
+ "separately, and on-demand revalidation won't propagate). Use a shared store (Workers KV, " +
24
+ "Redis, the Cache API), or pass { allowInProduction: true } for a single-instance deploy.");
25
+ }
26
+ this.max = options.max ?? 500;
27
+ }
28
+ get(key) {
29
+ const value = this.cache.get(key);
30
+ // LRU: a read bumps the entry to the tail so the bounded evict drops the LEAST-RECENTLY-USED,
31
+ // not the oldest-written — otherwise a burst of new pages would evict a hot, frequently-read one.
32
+ if (value !== undefined) {
33
+ this.cache.delete(key);
34
+ this.cache.set(key, value);
35
+ }
36
+ return Promise.resolve(value);
37
+ }
38
+ set(key, value) {
39
+ this.cache.delete(key); // re-insert at the tail so Map order tracks recency (with get) for the LRU evict
40
+ this.cache.set(key, value);
41
+ while (this.cache.size > this.max) {
42
+ const oldest = this.cache.keys().next().value;
43
+ if (oldest === undefined)
44
+ break;
45
+ this.cache.delete(oldest);
46
+ }
47
+ return Promise.resolve();
48
+ }
49
+ delete(key) {
50
+ this.cache.delete(key);
51
+ return Promise.resolve();
52
+ }
53
+ }
54
+ /**
55
+ * Structural validation of a KV-read value before it's trusted as a {@link CachedResponse}. The store
56
+ * is a trust boundary (version skew across a deploy, corruption, tampering), so a malformed entry is
57
+ * treated as a miss (the page re-renders + overwrites it) rather than served as a broken response.
58
+ */
59
+ const isCachedResponse = (value) => {
60
+ if (typeof value !== "object" || value === null)
61
+ return false;
62
+ const v = value;
63
+ if (typeof v.body !== "string" ||
64
+ typeof v.status !== "number" ||
65
+ typeof v.storedAt !== "number" ||
66
+ typeof v.revalidate !== "number" ||
67
+ typeof v.headers !== "object" ||
68
+ v.headers === null) {
69
+ return false;
70
+ }
71
+ for (const headerValue of Object.values(v.headers)) {
72
+ if (typeof headerValue !== "string")
73
+ return false;
74
+ }
75
+ return true;
76
+ };
77
+ /**
78
+ * A {@link CacheStore} backed by a **Cloudflare Workers KV** namespace (or any {@link KVNamespaceLike}
79
+ * binding) — the production-grade shared/durable store ISR wants: cached pages and on-demand purges
80
+ * hold *across* worker instances (unlike the per-instance {@link MemoryCacheStore}). Entries serialize
81
+ * to JSON; every read is validated before it's trusted (a malformed/version-skewed entry is treated as
82
+ * a miss). Construct it in your Workers `fetch` from the binding: `new KVCacheStore(env.ISR_CACHE)`.
83
+ */
84
+ export class KVCacheStore {
85
+ kv;
86
+ putOptions;
87
+ constructor(kv, options = {}) {
88
+ if (options.expirationTtl !== undefined && options.expirationTtl < 60) {
89
+ throw new Error("[nifra/web] KVCacheStore expirationTtl must be >= 60 (Cloudflare KV's minimum), and should " +
90
+ "exceed your longest `revalidate` window so stale-while-revalidate isn't turned into a " +
91
+ "blocking miss by KV expiry.");
92
+ }
93
+ this.kv = kv;
94
+ // Precompute the put options object once: omit it entirely when no TTL (KV then never expires).
95
+ this.putOptions =
96
+ options.expirationTtl !== undefined ? { expirationTtl: options.expirationTtl } : undefined;
97
+ }
98
+ async get(key) {
99
+ const raw = await this.kv.get(key);
100
+ if (raw === null)
101
+ return undefined;
102
+ let parsed;
103
+ try {
104
+ parsed = JSON.parse(raw);
105
+ }
106
+ catch {
107
+ // A corrupt (non-JSON) entry: treat as a miss so the page re-renders; the next set overwrites it.
108
+ return undefined;
109
+ }
110
+ return isCachedResponse(parsed) ? parsed : undefined;
111
+ }
112
+ async set(key, value) {
113
+ await this.kv.put(key, JSON.stringify(value), this.putOptions);
114
+ }
115
+ async delete(key) {
116
+ await this.kv.delete(key);
117
+ }
118
+ }
119
+ /** Response header marking how an ISR response was served: a cache `hit` (fresh), `stale` (served +
120
+ * regenerating behind it), or `miss` (rendered now + stored). Useful for debugging + tests. */
121
+ export const ISR_STATUS_HEADER = "x-nifra-isr";
122
+ /**
123
+ * Response header a route uses to advertise its ISR freshness (**seconds**) to a {@link withISR}
124
+ * wrapper — `createWebApp` emits it from a route's `export const revalidate`. Deliberately distinct
125
+ * from the action-revalidation `x-nifra-revalidate` header (a CSV path list the *client* parses to
126
+ * refetch): this one is an integer TTL the *wrapper* reads, so the two channels never alias.
127
+ */
128
+ export const ISR_REVALIDATE_HEADER = "x-nifra-isr-revalidate";
129
+ const defaultKey = (req) => {
130
+ const url = new URL(req.url);
131
+ return url.origin + url.pathname + url.search;
132
+ };
133
+ /** ISR caches a full-document SSR response: a `GET` (not a data-mode soft-nav fetch), `200`, `text/html`.
134
+ * Assets, data-mode GETs, redirects, and errors pass through uncached. */
135
+ const cacheControlHas = (headers, names) => {
136
+ const value = headers.get("cache-control");
137
+ if (value === null)
138
+ return false;
139
+ const directives = value
140
+ .toLowerCase()
141
+ .split(",")
142
+ .map((part) => part.trim().split("=", 1)[0]);
143
+ return names.some((name) => directives.includes(name));
144
+ };
145
+ const hasSetCookie = (headers) => headers.has("set-cookie") || (headers.getSetCookie?.().length ?? 0) > 0;
146
+ const requestCarriesPrivateState = (req) => req.headers.has("authorization") || req.headers.has("cookie");
147
+ const responseIsExplicitlyPublic = (res) => cacheControlHas(res.headers, ["public"]);
148
+ const isCacheablePage = (req, res) => {
149
+ if (req.method !== "GET")
150
+ return false;
151
+ if (req.headers.get("x-nifra-data") !== null)
152
+ return false;
153
+ if (res.status !== 200)
154
+ return false;
155
+ if (!(res.headers.get("content-type") ?? "").includes("text/html"))
156
+ return false;
157
+ if (hasSetCookie(res.headers))
158
+ return false;
159
+ if (cacheControlHas(res.headers, ["private", "no-store"]))
160
+ return false;
161
+ // Cookie/Authorization requests often personalize HTML without setting a new cookie. Cache them only
162
+ // when the route explicitly declares the response public.
163
+ if (requestCarriesPrivateState(req) && !responseIsExplicitlyPublic(res))
164
+ return false;
165
+ return true;
166
+ };
167
+ const CACHEABLE_RESPONSE_HEADERS = new Set([
168
+ "cache-control",
169
+ "content-language",
170
+ "content-type",
171
+ "etag",
172
+ "last-modified",
173
+ "link",
174
+ ]);
175
+ const headersOf = (res) => {
176
+ const out = {};
177
+ res.headers.forEach((value, key) => {
178
+ if (CACHEABLE_RESPONSE_HEADERS.has(key))
179
+ out[key] = value;
180
+ });
181
+ return out;
182
+ };
183
+ const responseFrom = (entry, status) => new Response(entry.body, {
184
+ status: entry.status,
185
+ headers: { ...sanitizeCachedHeaders(entry.headers), [ISR_STATUS_HEADER]: status },
186
+ });
187
+ function sanitizeCachedHeaders(headers) {
188
+ const out = {};
189
+ for (const [key, value] of Object.entries(headers)) {
190
+ const normalized = key.toLowerCase();
191
+ if (CACHEABLE_RESPONSE_HEADERS.has(normalized))
192
+ out[normalized] = value;
193
+ }
194
+ return out;
195
+ }
196
+ /**
197
+ * Wrap a nifra app with **Incremental Static Regeneration**: a cacheable page is served from
198
+ * {@link CacheStore} when fresh, served **stale while a fresh copy regenerates in the background**
199
+ * (`platform.waitUntil` on edge), or rendered + stored on a miss. Framework-agnostic (it caches the
200
+ * rendered bytes). Returns a `fetch(req, platform?)` handler — hand it to `Bun.serve`/the Workers
201
+ * `export default`, etc. Regeneration is single-flight per key (no stampede on a hot stale page).
202
+ *
203
+ * Each route's freshness comes from the `revalidate` header the app sets (per-route
204
+ * `export const revalidate`), falling back to `options.revalidate`. Only full-document `text/html`
205
+ * GET 200s are cached; everything else (assets, data-mode GETs, redirects, errors) passes through.
206
+ */
207
+ export function withISR(app, options) {
208
+ const { store, now } = options;
209
+ const keyOf = options.key ?? defaultKey;
210
+ const draftSecret = options.draftSecret;
211
+ const regenerating = new Set();
212
+ // Per-page TTL (ms): the app's `x-nifra-isr-revalidate` header (seconds) if present, else the default.
213
+ const ttlMs = (res) => {
214
+ const header = res.headers.get(ISR_REVALIDATE_HEADER);
215
+ const seconds = header === null ? options.revalidate : Number(header);
216
+ return (Number.isFinite(seconds) ? seconds : options.revalidate) * 1000;
217
+ };
218
+ const render = async (req, platform, key) => {
219
+ const res = await app.fetch(req, platform);
220
+ if (!isCacheablePage(req, res))
221
+ return res;
222
+ const body = await res.text();
223
+ const entry = {
224
+ body,
225
+ status: res.status,
226
+ headers: headersOf(res),
227
+ storedAt: now(),
228
+ revalidate: ttlMs(res),
229
+ };
230
+ await store.set(key, entry);
231
+ return new Response(body, {
232
+ status: res.status,
233
+ headers: { ...entry.headers, [ISR_STATUS_HEADER]: "miss" },
234
+ });
235
+ };
236
+ // Background regeneration (single-flight per key) — a failed regen keeps the stale entry; the next
237
+ // stale hit retries. Never throws (it runs detached / under waitUntil).
238
+ const regenerate = async (req, platform, key) => {
239
+ if (regenerating.has(key))
240
+ return;
241
+ regenerating.add(key);
242
+ try {
243
+ const res = await app.fetch(req, platform);
244
+ if (isCacheablePage(req, res)) {
245
+ await store.set(key, {
246
+ body: await res.text(),
247
+ status: res.status,
248
+ headers: headersOf(res),
249
+ storedAt: now(),
250
+ revalidate: ttlMs(res),
251
+ });
252
+ }
253
+ else {
254
+ await res.body?.cancel();
255
+ }
256
+ }
257
+ catch {
258
+ // keep the stale entry; a later request retries the regeneration
259
+ }
260
+ finally {
261
+ regenerating.delete(key);
262
+ }
263
+ };
264
+ return async (req, platform) => {
265
+ const key = req.method === "GET" ? keyOf(req) : null;
266
+ if (key === null)
267
+ return app.fetch(req, platform);
268
+ // Draft/preview: an editor (valid signed cookie) always renders fresh and is never cached, so
269
+ // unpublished content can't leak into the public cache (and the editor isn't served a stale page).
270
+ if (draftSecret !== undefined && (await isDraftEnabled(req, draftSecret))) {
271
+ return app.fetch(req, platform);
272
+ }
273
+ const hit = await store.get(key);
274
+ if (hit !== undefined) {
275
+ if (now() - hit.storedAt < hit.revalidate)
276
+ return responseFrom(hit, "hit");
277
+ // Stale: serve it now, regenerate behind it (waitUntil keeps the edge worker alive for the regen).
278
+ const task = regenerate(req, platform, key);
279
+ if (typeof platform?.waitUntil === "function")
280
+ platform.waitUntil(task);
281
+ else
282
+ void task.catch(() => { });
283
+ return responseFrom(hit, "stale");
284
+ }
285
+ return render(req, platform, key);
286
+ };
287
+ }
288
+ const jsonError = (status, error) => Response.json({ ok: false, error }, { status });
289
+ /** Constant-time string equality (XOR-accumulate, no early exit). A length mismatch returns `false`
290
+ * up front — the length isn't the secret, and different-length buffers can't be compared in constant
291
+ * time anyway. Used to check the on-demand revalidation token without leaking it via timing. */
292
+ function timingSafeEqual(a, b) {
293
+ const enc = new TextEncoder();
294
+ const ba = enc.encode(a);
295
+ const bb = enc.encode(b);
296
+ if (ba.length !== bb.length)
297
+ return false;
298
+ let diff = 0;
299
+ for (let i = 0; i < ba.length; i++)
300
+ diff |= ba[i] ^ bb[i];
301
+ return diff === 0;
302
+ }
303
+ /**
304
+ * An **on-demand revalidation** (purge) endpoint — a `fetch` handler that drops a path's cached entry
305
+ * so the next request re-renders. `POST` with the secret in the token header and the path as `?path=`
306
+ * or a JSON `{ "path": "/blog/x" }` body. The token is checked in **constant time** (wrong/missing →
307
+ * `401`); a missing/relative path → `400`; non-POST → `405`. Mount it on a nifra route, e.g.
308
+ * `app.post("/__nifra/revalidate", (c) => handler(c.req))`.
309
+ */
310
+ export function revalidateEndpoint(options) {
311
+ const tokenHeader = options.tokenHeader ?? "x-nifra-revalidate-token";
312
+ const keyOf = options.key ?? ((path, req) => new URL(req.url).origin + path);
313
+ return async (req) => {
314
+ if (req.method !== "POST")
315
+ return jsonError(405, "method_not_allowed");
316
+ if (!timingSafeEqual(req.headers.get(tokenHeader) ?? "", options.secret)) {
317
+ return jsonError(401, "unauthorized");
318
+ }
319
+ let path = new URL(req.url).searchParams.get("path");
320
+ if (path === null) {
321
+ const body = await req.json().catch(() => null);
322
+ const candidate = typeof body === "object" && body !== null ? body.path : undefined;
323
+ path = typeof candidate === "string" ? candidate : null;
324
+ }
325
+ if (path === null || !path.startsWith("/"))
326
+ return jsonError(400, "invalid_path");
327
+ await options.store.delete(keyOf(path, req));
328
+ return Response.json({ revalidated: path });
329
+ };
330
+ }
331
+ //# sourceMappingURL=isr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isr.js","sourceRoot":"","sources":["../src/isr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Route manifest — the fs-free heart of file-based routing. Maps route file paths to nifra
3
+ * router patterns + their nested layout chain. `discoverRoutes` (in `@nifrajs/web/fs`) scans
4
+ * the filesystem and feeds `buildManifest`; everything here is pure logic, so it stays
5
+ * portable (no fs, no DOM) and fully unit-testable. Edge deploys pre-build the manifest.
6
+ */
7
+ /** Context passed to a route `loader`. The `api` + `env` are injected by `createWebApp` and typed
8
+ * per-route via `@nifrajs/client`'s `LoaderArgs<Api, Env>` (here they are opaque to the agnostic core). */
9
+ export interface LoaderContext {
10
+ readonly params: Record<string, string>;
11
+ readonly request: Request;
12
+ readonly api: unknown;
13
+ /** Platform bindings forwarded from the request `c.env` (Workers env/KV/D1). Opaque here. */
14
+ readonly env: unknown;
15
+ /** `true` when the request carries a valid draft/preview cookie (only when `createWebApp` is given a
16
+ * `draftSecret`; otherwise always `false`). Branch on it to load unpublished content for editors. */
17
+ readonly draft: boolean;
18
+ }
19
+ /** A route's optional data loader: params/request in, data out. */
20
+ export type Loader = (ctx: LoaderContext) => unknown | Promise<unknown>;
21
+ /**
22
+ * A route's optional mutation, run on POST. Shares the loader context (params/request/api);
23
+ * read the form/JSON body off `request`. Returns either a `Response` (e.g. a redirect —
24
+ * passed straight through) or data, surfaced to the page component as `actionData`.
25
+ */
26
+ export type Action = (ctx: LoaderContext) => unknown | Promise<unknown>;
27
+ /** The document head a route contributes — title + `<meta>`/`<link>` tag attribute sets. */
28
+ export interface Meta {
29
+ readonly title?: string;
30
+ readonly meta?: ReadonlyArray<Record<string, string>>;
31
+ readonly link?: ReadonlyArray<Record<string, string>>;
32
+ }
33
+ /** Args for a route's `meta` function: the loader's data + the route params. */
34
+ export interface MetaArgs<Data = unknown> {
35
+ readonly data: Data;
36
+ readonly params: Record<string, string>;
37
+ }
38
+ /** A route's `meta`: a static {@link Meta}, or a function of the loader data + params. */
39
+ export type MetaInput = Meta | ((args: MetaArgs) => Meta);
40
+ /** One concrete parameterization of a dynamic route, returned by {@link GetStaticPaths}. */
41
+ export interface StaticPath {
42
+ /** Values for the route's `:param` segments, e.g. `{ id: "7" }` for `/users/:id`. */
43
+ readonly params: Record<string, string>;
44
+ }
45
+ /** What a route's `getStaticPaths` returns: the param sets to prerender + the unlisted-path policy. */
46
+ export interface StaticPaths {
47
+ readonly paths: readonly StaticPath[];
48
+ /**
49
+ * How a path NOT in `paths` is handled. `"ssr"` (default) → rendered on-demand by the worker
50
+ * (the natural hybrid behavior — an unlisted path simply isn't a static file); `"404"` → only the
51
+ * listed paths exist. Recorded by `prerenderRoutes` for the deploy layer.
52
+ */
53
+ readonly fallback?: "ssr" | "404";
54
+ }
55
+ /** A dynamic route's build-time param enumeration (the SSG equivalent of "which pages exist"). */
56
+ export type GetStaticPaths = () => StaticPaths | Promise<StaticPaths>;
57
+ /** A route module — the default component + optional loader / action / meta. */
58
+ export interface RouteModule {
59
+ readonly default: unknown;
60
+ readonly loader?: Loader;
61
+ readonly action?: Action;
62
+ readonly meta?: MetaInput;
63
+ /**
64
+ * Opt this route out of nifra's full-document client hydration. The server still renders the full
65
+ * HTML document, loaders/actions still run, and native links/forms still work; the generated app
66
+ * client, route chunks, and loader globals are omitted for hard navigations. Intended for static or
67
+ * island-hydrated pages where interactivity is mounted by smaller, explicit client entries.
68
+ */
69
+ readonly hydrate?: boolean;
70
+ /**
71
+ * Opt a **static** route (no `:param`/`*`) into build-time prerendering (SSG): `prerenderRoutes`
72
+ * (from `@nifrajs/web/build`) renders it to a static `index.html` at build. The loader runs at build
73
+ * with the in-process `api` (build-safe data only — no per-request cookies/secrets); `defer()` on a
74
+ * prerendered route resolves at build. For **dynamic** routes use {@link getStaticPaths} instead.
75
+ */
76
+ readonly prerender?: boolean;
77
+ /**
78
+ * Enumerate the concrete params to prerender for a **dynamic** (`:param`) route — the SSG path list
79
+ * (blogs/docs/etc.). Runs at build; `prerenderRoutes` renders one `index.html` per returned path.
80
+ */
81
+ readonly getStaticPaths?: GetStaticPaths;
82
+ /**
83
+ * ISR freshness for this route, in **seconds**. `createWebApp` emits it as an `x-nifra-revalidate`
84
+ * response header that `withISR` reads to set the page's cache TTL (overriding the wrapper's
85
+ * default). Older-than-`revalidate` cached pages are served stale while regenerating.
86
+ */
87
+ readonly revalidate?: number;
88
+ /**
89
+ * No-framework island bundles (`@nifrajs/web/islands`) to load on this route, as `<script
90
+ * type="module">` in the document tail. Loaded **regardless of `hydrate`** — pair with
91
+ * `export const hydrate = false` for a static page that ships zero framework JS and mounts
92
+ * interactivity through `<Island>` markers + `mountIslands` enhancers instead.
93
+ */
94
+ readonly islandScripts?: readonly string[];
95
+ }
96
+ /** A layout (or `_404`/`_error`) entry: its source file (for client codegen) + a lazy loader. */
97
+ export interface LayoutEntry {
98
+ readonly file: string;
99
+ readonly load: () => Promise<{
100
+ default: unknown;
101
+ }>;
102
+ }
103
+ /** One matched route: pattern, nested layout ids (outermost → innermost), source file, loader. */
104
+ export interface RouteEntry {
105
+ readonly id: string;
106
+ readonly pattern: string;
107
+ readonly layoutIds: readonly string[];
108
+ /** `_error` boundary ids in this route's ancestor chain (outermost → innermost). The last is the
109
+ * **nearest** boundary, rendered when the route's loader throws. Always set by `buildManifest`
110
+ * (optional only so hand-built test manifests may omit it); absent/empty ⇒ no boundary (error 500s). */
111
+ readonly errorIds?: readonly string[];
112
+ readonly file: string;
113
+ readonly load: () => Promise<RouteModule>;
114
+ }
115
+ /** The full route manifest. */
116
+ export interface Manifest {
117
+ readonly routes: readonly RouteEntry[];
118
+ readonly layouts: Readonly<Record<string, LayoutEntry>>;
119
+ /** Per-segment `_error` boundary components, keyed by id (`_error`, `a/_error`, …). Always set by
120
+ * `buildManifest` (optional only so hand-built test manifests may omit it). */
121
+ readonly errors?: Readonly<Record<string, LayoutEntry>>;
122
+ readonly notFound?: LayoutEntry;
123
+ }
124
+ /**
125
+ * Derive **every** nifra router pattern a route file maps to (relative to the routes dir):
126
+ * `index` → the parent path, `[id]` → `:id`, `[...slug]` → `*slug` (catch-all, captures the rest of
127
+ * the path into one param), `(group)` folders are dropped from the URL (organization only), and an
128
+ * optional `[[lang]]` expands the set — once with the segment present (`:lang`) and once absent. A file
129
+ * with no optionals yields exactly one pattern. Throws on an invalid param or a catch-all that isn't
130
+ * the last segment.
131
+ */
132
+ export declare function filePathToPatterns(file: string): string[];
133
+ /**
134
+ * The **canonical** single pattern for a route file — all optional segments present. A file with no
135
+ * optionals yields its one pattern. Use {@link filePathToPatterns} to get every pattern (optionals
136
+ * expand the set).
137
+ */
138
+ export declare function filePathToPattern(file: string): string;
139
+ /**
140
+ * Build a manifest from route file paths (relative to the routes dir) + an `importer` that
141
+ * turns a path into a lazy module loader. Pure — no fs. Throws at boot (the loud-and-early
142
+ * RouteConfigError ethos) on duplicate patterns. `_layout`/`_404`/`_error` files are special; other
143
+ * `_`-prefixed files are ignored (private/colocated, never routed).
144
+ */
145
+ export declare function buildManifest(files: readonly string[], importer: (file: string) => () => Promise<RouteModule>): Manifest;
146
+ /**
147
+ * Substitute a route pattern's `:param` segments with concrete values: `/users/:id` + `{id:"7"}` →
148
+ * `/users/7`. Returns the filled path plus any params that had no value (a `getStaticPaths` bug) so
149
+ * the caller can skip rather than emit a path with a literal `:name`. Shared by the SSG driver and
150
+ * {@link enumeratePrerenderedPaths}.
151
+ */
152
+ export declare function fillRoutePattern(pattern: string, params: Record<string, string>): {
153
+ path: string;
154
+ missing: string[];
155
+ };
156
+ /** The static-routing facts a server needs from the route modules: which concrete paths are
157
+ * prerendered, plus each dynamic route's `getStaticPaths` fallback policy. */
158
+ export interface StaticRoutes {
159
+ /** Concrete prerendered paths — static `prerender` routes + each `getStaticPaths` entry. */
160
+ readonly paths: string[];
161
+ /** Per dynamic route pattern, its `getStaticPaths` `fallback` (`"ssr"` default). `createWebApp` uses
162
+ * `"404"` to reject an unlisted path under that route at runtime (the path simply doesn't exist). */
163
+ readonly fallbacks: Record<string, "ssr" | "404">;
164
+ }
165
+ /**
166
+ * Enumerate the static-routing facts `prerenderRoutes` would produce — static routes opted in via
167
+ * `export const prerender = true`, each `getStaticPaths` entry of a dynamic route, and each dynamic
168
+ * route's `fallback` policy. Pure (no rendering), so a server can compute what to hand `createWebApp`
169
+ * (the prerendered set for the client's static-`_data.json` soft-nav + the fallback map for
170
+ * `"404"` enforcement). A production server may instead read the build's `prerendered.json` to avoid
171
+ * loading every route module at startup. Catch-all/wildcard routes and dynamic routes without
172
+ * `getStaticPaths` are omitted from `paths`.
173
+ */
174
+ export declare function enumerateStaticRoutes(routes: readonly RouteEntry[]): Promise<StaticRoutes>;
175
+ /** The prerendered-path subset of {@link enumerateStaticRoutes} — kept for callers that only need the
176
+ * paths (e.g. injecting `window.__NIFRA_PRERENDERED__`). */
177
+ export declare function enumeratePrerenderedPaths(routes: readonly RouteEntry[]): Promise<string[]>;
178
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}