@nifrajs/web 0.1.0-alpha.1 → 0.1.0-alpha.2

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 (49) hide show
  1. package/README.md +8 -0
  2. package/dist/build.d.ts +252 -0
  3. package/dist/build.d.ts.map +1 -1
  4. package/dist/build.js +755 -6
  5. package/dist/build.js.map +1 -1
  6. package/dist/chunk-names.d.ts +4 -0
  7. package/dist/chunk-names.d.ts.map +1 -0
  8. package/dist/chunk-names.js +46 -0
  9. package/dist/chunk-names.js.map +1 -0
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +19 -2
  12. package/dist/client.js.map +1 -1
  13. package/dist/dev.d.ts +1 -1
  14. package/dist/dev.d.ts.map +1 -1
  15. package/dist/dev.js +2 -1
  16. package/dist/dev.js.map +1 -1
  17. package/dist/fonts-google.d.ts +2 -1
  18. package/dist/fonts-google.d.ts.map +1 -1
  19. package/dist/fonts-google.js.map +1 -1
  20. package/dist/fonts.d.ts +2 -1
  21. package/dist/fonts.d.ts.map +1 -1
  22. package/dist/fonts.js +0 -16
  23. package/dist/fonts.js.map +1 -1
  24. package/dist/index.d.ts +138 -6
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +268 -23
  27. package/dist/index.js.map +1 -1
  28. package/dist/manifest.d.ts +82 -5
  29. package/dist/manifest.d.ts.map +1 -1
  30. package/dist/manifest.js.map +1 -1
  31. package/dist/server-only.d.ts +21 -0
  32. package/dist/server-only.d.ts.map +1 -0
  33. package/dist/server-only.js +25 -0
  34. package/dist/server-only.js.map +1 -0
  35. package/dist/vite.d.ts +21 -1
  36. package/dist/vite.d.ts.map +1 -1
  37. package/dist/vite.js +68 -4
  38. package/dist/vite.js.map +1 -1
  39. package/package.json +6 -1
  40. package/src/build.ts +1066 -12
  41. package/src/chunk-names.ts +45 -0
  42. package/src/client.ts +19 -2
  43. package/src/dev.ts +3 -2
  44. package/src/fonts-google.ts +3 -2
  45. package/src/fonts.ts +2 -1
  46. package/src/index.ts +373 -32
  47. package/src/manifest.ts +84 -6
  48. package/src/server-only.ts +25 -0
  49. package/src/vite.ts +82 -5
package/dist/index.d.ts CHANGED
@@ -7,17 +7,46 @@
7
7
  * the adapter interprets them. That keeps this package both framework-agnostic and free
8
8
  * of DOM types: it is pure server orchestration + string building.
9
9
  */
10
- import { type Server } from "@nifrajs/core";
10
+ import { server } from "@nifrajs/core";
11
11
  import { type Deferred, defer } from "./deferred.js";
12
- import type { Manifest, Meta, MetaArgs, MetaInput } from "./manifest.js";
12
+ import type { LinkDescriptor, Manifest, Meta, MetaArgs, MetaInput, ScriptDescriptor } from "./manifest.js";
13
13
  import { type Submission } from "./router.js";
14
14
  export { DRAFT_COOKIE, type DraftCookieControls, disableDraft, type EnableDraftOptions, enableDraft, isDraftEnabled, } from "./draft.js";
15
15
  export { type FontDisplay, type FontFace, type FontPreloadInput, type FontSource, fontFace, fontPreload, } from "./fonts.js";
16
16
  export { type CachedResponse, type CacheStore, ISR_REVALIDATE_HEADER, ISR_STATUS_HEADER, type ISRApp, type ISROptions, type ISRPlatform, KVCacheStore, type KVCacheStoreOptions, type KVNamespaceLike, MemoryCacheStore, type MemoryCacheStoreOptions, type RevalidateEndpointOptions, revalidateEndpoint, withISR, } from "./isr.js";
17
- export { type Action, buildManifest, enumeratePrerenderedPaths, enumerateStaticRoutes, filePathToPattern, filePathToPatterns, type GetStaticPaths, type LayoutEntry, type Loader, type LoaderContext, type Manifest, type Meta, type MetaArgs, type MetaInput, type RouteEntry, type RouteModule, type StaticPath, type StaticPaths, type StaticRoutes, } from "./manifest.js";
17
+ export { type Action, buildManifest, enumeratePrerenderedPaths, enumerateStaticRoutes, filePathToPattern, filePathToPatterns, type GetStaticPaths, type LayoutEntry, type LinkDescriptor, type Loader, type LoaderContext, type Manifest, type Meta, type MetaArgs, type MetaInput, type RouteEntry, type RouteModule, type ScriptDescriptor, type StaticPath, type StaticPaths, type StaticRoutes, } from "./manifest.js";
18
18
  export { createQueryClient, hashQueryKey, type QueryClient, type QueryClientOptions, type QueryHandle, type QueryState, type QueryStatus, } from "./query.js";
19
19
  export { type ClientRouter, type ClientRouterOptions, createClientRouter, createMatcher, DATA_HEADER, type Fetcher, type FetcherState, type FetchRouteData, type MountRouterOptions, REDIRECT_HEADER, REVALIDATE_HEADER, type RouteMatch, type RoutePattern, type RouterState, type Submission, type SubmitOptions, } from "./router.js";
20
20
  export { type Deferred, defer };
21
+ /** Phantom brand key for {@link ServerOnly}. A unique symbol so the brand can't be forged by a plain
22
+ * object literal; `unique symbol` keys never appear in the emitted JS, so the brand is purely
23
+ * type-level (zero runtime cost). The property is optional + `never`-typed, so a `ServerOnly<T>`
24
+ * stays assignment-compatible with `T` — it documents intent without obstructing real use. */
25
+ declare const SERVER_ONLY_BRAND: unique symbol;
26
+ /**
27
+ * Type-level intent marker for a value that must only exist on the server — a secret, a DB handle, a
28
+ * server-only client. `ServerOnly<T>` is structurally `T` (the brand is an optional phantom field, so
29
+ * existing code keeps type-checking), but it advertises to readers + the compiler that the value is
30
+ * not meant to cross to the browser. It is **purely type-level** and erases at build — it does NOT,
31
+ * by itself, keep the value out of the client bundle.
32
+ *
33
+ * The enforcement is two runtime conventions:
34
+ * - add the side-effect import `import "@nifrajs/web/server-only"` at the top of the module — the
35
+ * client build ({@link buildClient}) fails loud, with the import chain, if it reaches a browser
36
+ * chunk (the poison-import marker);
37
+ * - or name the file `*.server.ts` — the `.server` convention empties it in the client build.
38
+ *
39
+ * Use this brand to express the intent in the types; pair it with one of those runtime markers so a
40
+ * leak is caught at build time rather than shipping a secret to the client.
41
+ *
42
+ * @example
43
+ * import "@nifrajs/web/server-only"
44
+ * import type { ServerOnly } from "@nifrajs/web"
45
+ * export const apiKey: ServerOnly<string> = process.env.SECRET_API_KEY!
46
+ */
47
+ export type ServerOnly<T> = T & {
48
+ readonly [SERVER_ONLY_BRAND]?: never;
49
+ };
21
50
  /** The data handed to a route component. Opaque to the core. `actionData` is the return of a
22
51
  * route `action` after a POST (absent on plain GETs). `pending` + `submission` are client-only
23
52
  * (absent on SSR): they drive **optimistic UI** — render from `submission.formData` while `pending`. */
@@ -66,6 +95,13 @@ export declare const ROUTE_GLOBAL = "__NIFRA_ROUTE__";
66
95
  /** Global the server serializes an action's data return into (absent on GETs); the client
67
96
  * reads it so hydration after a native form POST matches the server-rendered markup. */
68
97
  export declare const ACTION_GLOBAL = "__NIFRA_ACTION__";
98
+ /**
99
+ * The single default port for the dev server (`@nifrajs/web/dev`, `@nifrajs/web/vite`) **and**
100
+ * `nifra start`. Deliberately uncommon: `3000`/`5173`/`8080` collide with whatever else is running
101
+ * (Next, Vite, a stray Node API). `4321` rarely is — and being the *same* constant across `nifra dev`
102
+ * and `nifra start` means a project's URL doesn't change between commands. Override per-run with
103
+ * `--port <n>` or the `PORT` env var. */
104
+ export declare const DEFAULT_DEV_PORT = 4321;
69
105
  type MaybePromise<T> = T | Promise<T>;
70
106
  declare const RESPONSE_RESULT: unique symbol;
71
107
  export interface RenderedPage {
@@ -109,7 +145,17 @@ export interface RenderPageOptions {
109
145
  /** Document title (fallback when `head.title` is unset). */
110
146
  readonly title?: string;
111
147
  /** Resolved route head — `title` overrides `title` above; `meta`/`link` render as managed
112
- * (`data-nifra`) tags the client updates on navigation. */
148
+ * (`data-nifra`) tags the client updates on navigation.
149
+ *
150
+ * **Head contract (the layout chain contributes).** `createWebApp` resolves this via
151
+ * {@link mergeHeads}: a route's head is its **layout chain's** `meta`/`head` exports merged with the
152
+ * page's. A `_layout.tsx` may `export const meta` (or `export function meta(args)`) — its tags land
153
+ * on every page below it (the home for `hreflang`, `preconnect`, a section-default `<title>`). The
154
+ * merge is **nearest-wins for scalars** (the page's `title` overrides an inner layout's, which
155
+ * overrides an outer one; an undefined page title keeps the layout's) and **concatenated for the
156
+ * `meta`/`link` arrays** (outermost layout first, page last). `<link>` attributes are name-validated
157
+ * (any letter/digit/hyphen name — covers `rel`/`href`/`hreflang`/`crossorigin`/`media`/`sizes`/`as`/
158
+ * `integrity`/`fetchpriority`/…) and value-escaped against XSS. */
113
159
  readonly head?: Meta;
114
160
  /** Id of the container wrapping the app markup (default `"root"`). */
115
161
  readonly rootId?: string;
@@ -179,6 +225,68 @@ export declare function revalidate<T>(paths: readonly string[], data: T): Revali
179
225
  export declare function serializeData(data: unknown): string;
180
226
  /** Resolve a route's `meta` (static or a function of the loader data + params) to a {@link Meta}. */
181
227
  export declare function resolveMeta(meta: MetaInput | undefined, args: MetaArgs): Meta;
228
+ /**
229
+ * A `<link rel="canonical">` descriptor for a route's `meta.link`. The canonical URL tells search
230
+ * engines which URL is authoritative for a page (deduping query-string / tracking variants).
231
+ *
232
+ * ```ts
233
+ * export const meta = (a) => ({ link: [canonical(`https://site.com/posts/${a.params.slug}`)] })
234
+ * ```
235
+ */
236
+ export declare function canonical(href: string): LinkDescriptor;
237
+ /** Inputs for {@link openGraph} — the common Open Graph properties. All optional; only the provided
238
+ * ones become tags. `type` defaults to `"website"`. */
239
+ export interface OpenGraphInput {
240
+ readonly title?: string;
241
+ readonly description?: string;
242
+ /** Absolute URL of the share image (`og:image`). */
243
+ readonly image?: string;
244
+ /** Canonical URL of the page (`og:url`). */
245
+ readonly url?: string;
246
+ /** Object type (`og:type`) — `"website"`, `"article"`, … Default `"website"`. */
247
+ readonly type?: string;
248
+ }
249
+ /**
250
+ * Build the Open Graph `<meta property="og:*">` entries for a route's `meta.meta`. Returns only the
251
+ * properties you supplied (plus `og:type`, defaulting to `"website"`), so it composes with other meta.
252
+ *
253
+ * ```ts
254
+ * export const meta = { meta: [...openGraph({ title: "Nifra", image: "https://site.com/og.png" })] }
255
+ * ```
256
+ */
257
+ export declare function openGraph(input: OpenGraphInput): Array<Record<string, string>>;
258
+ /**
259
+ * Build a JSON-LD `<script type="application/ld+json">` entry for a route's `meta.script` from a plain
260
+ * object. `JSON.stringify` produces the body; the head renderer breakout-escapes it (see
261
+ * `escapeScriptContent`), so a string field containing `</script>` is embedded safely.
262
+ *
263
+ * ```ts
264
+ * export const meta = {
265
+ * script: [jsonLd({ "@context": "https://schema.org", "@type": "Article", headline: "Hi" })],
266
+ * }
267
+ * ```
268
+ */
269
+ export declare function jsonLd(data: Record<string, unknown>): ScriptDescriptor;
270
+ /**
271
+ * Merge a route's `<head>` contributions from its layout chain + the page into one {@link Meta}.
272
+ *
273
+ * The head contract (see {@link CreateWebAppOptions} and `LayoutEntry`): a `_layout.tsx` may export
274
+ * `meta` to put sitewide tags (`hreflang`, `preconnect`, a default `<title>`, …) on every page below
275
+ * it. `heads` is passed **outermost layout → … → innermost layout → page** (the same order as the
276
+ * render chain), and merges:
277
+ * - **`title`** — *nearest-wins*: the last defined value across the list, so the page overrides an
278
+ * inner layout, which overrides an outer one. A layout `title` is the section default; an undefined
279
+ * page `title` keeps it.
280
+ * - **`meta` / `link`** — *concatenated* in list order (outermost first, page last). Duplicate-tag
281
+ * de-duplication is the caller's concern; the framework emits exactly what's declared so a layout
282
+ * can ship N `<link rel="alternate" hreflang>` tags and a page can add its own canonical.
283
+ *
284
+ * Returns a fresh object whose identity is stable per `heads` *content* only when every entry is a
285
+ * static (by-reference) `Meta` and there is exactly one — otherwise a new object each call. That is
286
+ * fine: {@link headTags}'s memo is keyed on identity, so a per-request merge simply recomputes (its
287
+ * content can vary with loader data anyway).
288
+ */
289
+ export declare function mergeHeads(heads: readonly Meta[]): Meta;
182
290
  export interface CreateWebAppOptions {
183
291
  readonly adapter: RenderAdapter;
184
292
  readonly manifest: Manifest;
@@ -187,8 +295,24 @@ export interface CreateWebAppOptions {
187
295
  /** Default document title for all pages. */
188
296
  readonly title?: string;
189
297
  /** Injected into each loader's `ctx.api` — typically an `inProcessClient(app)` (typed
190
- * per-route via `@nifrajs/client`'s `createRoutes`). Opaque to the core. */
298
+ * per-route via `@nifrajs/client`'s `createRoutes`). Opaque to the core.
299
+ *
300
+ * **Auto-mount.** When `api` exposes a callable `.fetch(url, init)` (every `inProcessClient(backend)`
301
+ * does — see {@link isMountableApi}), `createWebApp` also serves that backend over HTTP at
302
+ * {@link apiPrefix} (default `/api`): a request whose pathname starts with the prefix is dispatched
303
+ * straight to `api.fetch(req.url, req)` before page routing, and the backend's `Response` is returned
304
+ * untouched. This closes the reported gap where `POST /api/sync` / `/api/auth/*` 404/405'd against the
305
+ * page router until the app author hand-wrote an `if (pathname.startsWith("/api/")) …` branch in
306
+ * `server-bun.ts`. The mount runs in `nifra dev` too (the Vite dev server dispatches every request
307
+ * through this same app). Pass `apiPrefix: ""` to disable it (pages-only). */
191
308
  readonly api?: unknown;
309
+ /** HTTP path prefix the {@link api} backend is auto-mounted at (default `"/api"`). A request whose
310
+ * pathname is exactly the prefix or starts with `prefix + "/"` is dispatched to the backend before
311
+ * page routing; the backend therefore defines its routes at the **full** path (`server().post("/api/
312
+ * sync", …)`), matching the in-process `inProcessClient` call sites. Set to `""` to disable the
313
+ * auto-mount entirely (the app serves pages only and `api` stays a loader-only `ctx.api`). Mounting
314
+ * is also a no-op when `api` has no callable `.fetch` (e.g. a plain object passed as `ctx.api`). */
315
+ readonly apiPrefix?: string;
192
316
  /** Secret for **draft / preview mode** (see `enableDraft`). When set, a request carrying a valid
193
317
  * signed `__nifra_draft` cookie gets `ctx.draft === true` in loaders/actions (else always `false`).
194
318
  * Pair with `withISR({ draftSecret })` so editors bypass the cache. Omit to disable draft mode. */
@@ -232,8 +356,16 @@ export interface CreateWebAppOptions {
232
356
  * and a wildcard catch-all renders `_404` (or a plain 404). Reuses @nifrajs/core's router +
233
357
  * lifecycle, so matching, params, and precedence are battle-tested. fs-free — feed it a
234
358
  * manifest from `discoverRoutes` (`@nifrajs/web/fs`) at startup, so the served app stays portable.
359
+ *
360
+ * **Typed platform bindings.** Pass `Env` — `createWebApp<Env>({ … })` — to declare the app's Workers
361
+ * bindings ONCE. It seeds the returned `Server`'s context with `{ env: Env }` (exactly as the backend's
362
+ * `server<Env>()` does), so `app.fetch(req, { env })` / `toFetchHandler(app)` type-check the `env`
363
+ * argument against the declared shape — no per-binding cast at the edge entry. Per-route loaders/actions
364
+ * stay typed independently of this call: annotate them with `@nifrajs/client`'s `LoaderArgs<Api, Env>`
365
+ * (same `Env`) so `ctx.env.MY_KV` is typed there too. Omit the parameter and `Env` is `unknown` — the
366
+ * secure default; validate at the trust boundary before use.
235
367
  */
236
- export declare function createWebApp(options: CreateWebAppOptions): Server;
368
+ export declare function createWebApp<Env = unknown>(options: CreateWebAppOptions): ReturnType<typeof server<Env>>;
237
369
  export interface GenerateClientEntryOptions {
238
370
  /** Module specifier for the adapter's client runtime (exports `mountRouter`), e.g.
239
371
  * `"@nifrajs/web-solid/client"`. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -36,6 +36,13 @@ export const ROUTE_GLOBAL = "__NIFRA_ROUTE__";
36
36
  /** Global the server serializes an action's data return into (absent on GETs); the client
37
37
  * reads it so hydration after a native form POST matches the server-rendered markup. */
38
38
  export const ACTION_GLOBAL = "__NIFRA_ACTION__";
39
+ /**
40
+ * The single default port for the dev server (`@nifrajs/web/dev`, `@nifrajs/web/vite`) **and**
41
+ * `nifra start`. Deliberately uncommon: `3000`/`5173`/`8080` collide with whatever else is running
42
+ * (Next, Vite, a stray Node API). `4321` rarely is — and being the *same* constant across `nifra dev`
43
+ * and `nifra start` means a project's URL doesn't change between commands. Override per-run with
44
+ * `--port <n>` or the `PORT` env var. */
45
+ export const DEFAULT_DEV_PORT = 4321;
39
46
  // Shared, stateless — allocated once at module load, not per render/stream.
40
47
  const TEXT_ENCODER = new TextEncoder();
41
48
  // XSS-safe `<script>` JSON escaping in a SINGLE pass: `<`/`>` would break out of the
@@ -302,6 +309,22 @@ export function serializeData(data) {
302
309
  function escapeHtml(value) {
303
310
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
304
311
  }
312
+ /**
313
+ * Escape a `<script>` element's text content so it cannot break out of the element. Per the HTML spec,
314
+ * a script's text may contain any character EXCEPT the sequences that the parser treats as element
315
+ * boundaries: `</` (which would begin the end tag — `</script>` is the obvious XSS vector, but ANY `</`
316
+ * starts script-data-end-tag parsing), and the comment/CDATA edges `<!--` / `]]>`. We escape the `<`
317
+ * (and the `>` of `]]>`) to its JS unicode escape: inside a JS/JSON string literal `<` is the
318
+ * identical character, so a JSON-LD payload is byte-equivalent after parsing — but the raw `<`/`>` the
319
+ * HTML tokenizer scans for is gone. This is the JSON-LD-in-HTML rule (content is JSON, never raw HTML),
320
+ * mirroring {@link serializeData}'s posture for the data-global script. Idempotent on already-safe text.
321
+ */
322
+ function escapeScriptContent(content) {
323
+ // Order matters: rewrite `]]>` first (its `>` becomes `>`), then every `<` (covers `</` and
324
+ // `<!--` in one pass). `<` → `<`; the lone `>` of `]]>` → `>`. A bare `>` elsewhere is
325
+ // harmless in script data, so only the `]]>` close is targeted for `>`.
326
+ return content.replaceAll("]]>", "]]\\u003e").replaceAll("<", "\\u003c");
327
+ }
305
328
  function escapeAttr(value) {
306
329
  return value
307
330
  .replaceAll("&", "&amp;")
@@ -309,14 +332,33 @@ function escapeAttr(value) {
309
332
  .replaceAll("<", "&lt;")
310
333
  .replaceAll(">", "&gt;");
311
334
  }
335
+ // Attribute-NAME guard for emitted `<meta>`/`<link>` tags. Intentionally a *name-shape* allowlist, not
336
+ // a hardcoded per-attribute one: any letter/digit/hyphen name beginning with a letter is permitted, so
337
+ // every standard `<link>` attribute flows through — `rel`, `href`, `hreflang`, `crossorigin`, `media`,
338
+ // `sizes`, `type`, `as`, `title`, `integrity`, `referrerpolicy`, `imagesrcset`, `imagesizes`,
339
+ // `disabled`, `color`, `fetchpriority` — alongside `<meta>`'s `name`/`property`/`content` and custom
340
+ // `data-*`. A hardcoded list would silently drop valid attrs (the bug this guards against: `hreflang`
341
+ // /`crossorigin` getting filtered out). What it rejects is name-shape abuse only — a name with a space,
342
+ // `=`, `>`, quote, or a leading digit can't break out of the tag. Attribute VALUES are escaped
343
+ // separately (they may carry loader data → XSS), so a widened name set never widens the injection
344
+ // surface. Names that fail the shape check are dropped.
312
345
  const SAFE_ATTR_NAME = /^[a-zA-Z][a-zA-Z0-9-]*$/;
313
- /** Serialize a meta/link tag's attributes: validate the name (dev-authored) + escape the value
314
- * (may carry loader data → XSS). Invalid attribute names are dropped. */
346
+ /** Serialize a meta/link tag's attributes: validate the name shape (see {@link SAFE_ATTR_NAME}) +
347
+ * escape the value against XSS. Invalid attribute names are dropped. Values follow the HTML attribute
348
+ * conventions {@link LinkDescriptor} types: a string renders `name="escaped"`; `true` renders the bare
349
+ * boolean attribute (`disabled`); `false` and `undefined` are skipped (a conditionally-absent attr). */
315
350
  function tagAttrs(attrs) {
316
- return Object.entries(attrs)
317
- .filter(([name]) => SAFE_ATTR_NAME.test(name))
318
- .map(([name, value]) => `${name}="${escapeAttr(value)}"`)
319
- .join(" ");
351
+ let out = "";
352
+ for (const [name, value] of Object.entries(attrs)) {
353
+ if (value === undefined || value === false)
354
+ continue; // omitted / absent boolean attribute
355
+ if (!SAFE_ATTR_NAME.test(name))
356
+ continue; // name-shape abuse — dropped (see SAFE_ATTR_NAME)
357
+ if (out !== "")
358
+ out += " ";
359
+ out += value === true ? name : `${name}="${escapeAttr(value)}"`;
360
+ }
361
+ return out;
320
362
  }
321
363
  // Memoize the serialized head-tag string by the resolved `Meta` object's identity. A STATIC route
322
364
  // `meta` is returned by-reference from resolveMeta, so the same object recurs every request → cache
@@ -324,10 +366,13 @@ function tagAttrs(attrs) {
324
366
  // builds a fresh object each request (new identity) → miss → recompute, exactly as needed (its
325
367
  // content can vary). WeakMap so a route module that's GC'd takes its entry with it.
326
368
  const headTagsCache = new WeakMap();
327
- /** Render a route's `meta`/`link` as managed (`data-nifra`) head tags. Title is set separately.
328
- * String concatenation (no intermediate `.map()` arrays + spread) — parity with the already
329
- * concat-based preloadLinks/styleLinks/islandPreloads loops; byte-identical output.
330
- * Result is memoized per resolved-`Meta` identity (static meta → serialized once per route). */
369
+ /** Render a route's `meta`/`link`/`script` as managed (`data-nifra`) head tags. Title is set
370
+ * separately. XSS-safe by construction: every attribute flows through {@link tagAttrs} (name shape-
371
+ * validated, value HTML-escaped), and each `script[].content` through `escapeScriptContent`
372
+ * (breakout-escaped) — so loader-derived strings (LLM-authored `og:*`, user content) can't inject markup
373
+ * or close the tag early. String concatenation (no intermediate `.map()` arrays + spread) — parity with
374
+ * the already concat-based preloadLinks/styleLinks/islandPreloads loops; byte-identical output. Result
375
+ * is memoized per resolved-`Meta` identity (static meta → serialized once per route). */
331
376
  function headTags(head) {
332
377
  if (head === undefined)
333
378
  return "";
@@ -341,6 +386,12 @@ function headTags(head) {
341
386
  if (head.link !== undefined)
342
387
  for (const l of head.link)
343
388
  out += `<link ${tagAttrs(l)} data-nifra>`;
389
+ // `<script>` slot — JSON-LD + other inert head scripts. `type` is attribute-escaped; `content` is
390
+ // breakout-escaped (`escapeScriptContent`) so a `</script>` (or `<!--`/`]]>`) payload can't close the
391
+ // element early. Managed (`data-nifra`) so a soft-nav's `applyHead` cleanly replaces it.
392
+ if (head.script !== undefined)
393
+ for (const s of head.script)
394
+ out += `<script type="${escapeAttr(s.type ?? "application/ld+json")}" data-nifra>${escapeScriptContent(s.content)}</script>`;
344
395
  headTagsCache.set(head, out);
345
396
  return out;
346
397
  }
@@ -350,11 +401,133 @@ export function resolveMeta(meta, args) {
350
401
  return {};
351
402
  return typeof meta === "function" ? meta(args) : meta;
352
403
  }
404
+ /**
405
+ * A `<link rel="canonical">` descriptor for a route's `meta.link`. The canonical URL tells search
406
+ * engines which URL is authoritative for a page (deduping query-string / tracking variants).
407
+ *
408
+ * ```ts
409
+ * export const meta = (a) => ({ link: [canonical(`https://site.com/posts/${a.params.slug}`)] })
410
+ * ```
411
+ */
412
+ export function canonical(href) {
413
+ return { rel: "canonical", href };
414
+ }
415
+ /**
416
+ * Build the Open Graph `<meta property="og:*">` entries for a route's `meta.meta`. Returns only the
417
+ * properties you supplied (plus `og:type`, defaulting to `"website"`), so it composes with other meta.
418
+ *
419
+ * ```ts
420
+ * export const meta = { meta: [...openGraph({ title: "Nifra", image: "https://site.com/og.png" })] }
421
+ * ```
422
+ */
423
+ export function openGraph(input) {
424
+ const tags = [];
425
+ const add = (property, content) => {
426
+ if (content !== undefined)
427
+ tags.push({ property, content });
428
+ };
429
+ add("og:title", input.title);
430
+ add("og:description", input.description);
431
+ add("og:image", input.image);
432
+ add("og:url", input.url);
433
+ // `og:type` always present (the spec's required property) — default "website" when unset.
434
+ tags.push({ property: "og:type", content: input.type ?? "website" });
435
+ return tags;
436
+ }
437
+ /**
438
+ * Build a JSON-LD `<script type="application/ld+json">` entry for a route's `meta.script` from a plain
439
+ * object. `JSON.stringify` produces the body; the head renderer breakout-escapes it (see
440
+ * `escapeScriptContent`), so a string field containing `</script>` is embedded safely.
441
+ *
442
+ * ```ts
443
+ * export const meta = {
444
+ * script: [jsonLd({ "@context": "https://schema.org", "@type": "Article", headline: "Hi" })],
445
+ * }
446
+ * ```
447
+ */
448
+ export function jsonLd(data) {
449
+ return { type: "application/ld+json", content: JSON.stringify(data) };
450
+ }
451
+ /**
452
+ * Merge a route's `<head>` contributions from its layout chain + the page into one {@link Meta}.
453
+ *
454
+ * The head contract (see {@link CreateWebAppOptions} and `LayoutEntry`): a `_layout.tsx` may export
455
+ * `meta` to put sitewide tags (`hreflang`, `preconnect`, a default `<title>`, …) on every page below
456
+ * it. `heads` is passed **outermost layout → … → innermost layout → page** (the same order as the
457
+ * render chain), and merges:
458
+ * - **`title`** — *nearest-wins*: the last defined value across the list, so the page overrides an
459
+ * inner layout, which overrides an outer one. A layout `title` is the section default; an undefined
460
+ * page `title` keeps it.
461
+ * - **`meta` / `link`** — *concatenated* in list order (outermost first, page last). Duplicate-tag
462
+ * de-duplication is the caller's concern; the framework emits exactly what's declared so a layout
463
+ * can ship N `<link rel="alternate" hreflang>` tags and a page can add its own canonical.
464
+ *
465
+ * Returns a fresh object whose identity is stable per `heads` *content* only when every entry is a
466
+ * static (by-reference) `Meta` and there is exactly one — otherwise a new object each call. That is
467
+ * fine: {@link headTags}'s memo is keyed on identity, so a per-request merge simply recomputes (its
468
+ * content can vary with loader data anyway).
469
+ */
470
+ export function mergeHeads(heads) {
471
+ // Single-head fast path (a route with no layout `meta`, by far the common case) — return the
472
+ // resolved object by reference so headTags' identity-keyed memo hits across requests for static meta.
473
+ if (heads.length === 1)
474
+ return heads[0];
475
+ let title;
476
+ const meta = [];
477
+ const link = [];
478
+ const script = [];
479
+ for (const h of heads) {
480
+ if (h.title !== undefined)
481
+ title = h.title; // nearest-wins: later (more specific) overrides
482
+ if (h.meta !== undefined)
483
+ meta.push(...h.meta);
484
+ if (h.link !== undefined)
485
+ link.push(...h.link);
486
+ if (h.script !== undefined)
487
+ script.push(...h.script); // concatenated like meta/link (outermost first)
488
+ }
489
+ // Build the result with only the fields that were actually contributed — an empty `meta`/`link`/
490
+ // `script` array would otherwise be a spurious (if harmless) key. A mutable local; the cast to `Meta`
491
+ // is sound because a key is assigned only when defined (so `exactOptionalPropertyTypes` never sees
492
+ // `undefined`).
493
+ const merged = {};
494
+ if (title !== undefined)
495
+ merged.title = title;
496
+ if (meta.length > 0)
497
+ merged.meta = meta;
498
+ if (link.length > 0)
499
+ merged.link = link;
500
+ if (script.length > 0)
501
+ merged.script = script;
502
+ return merged;
503
+ }
504
+ /**
505
+ * Narrow an opaque `api` to a {@link MountableApi}. Guards the `/api/*` auto-mount: an `api` set to a
506
+ * plain loader-only value (no `.fetch`) leaves the app pages-only rather than crashing. Kept a
507
+ * structural shape check (not an `instanceof`) so any in-process bridge — `inProcessClient`, a test
508
+ * stub, a custom agent — is mountable without `@nifrajs/web` depending on `@nifrajs/client`.
509
+ */
510
+ function isMountableApi(api) {
511
+ // inProcessClient(backend) is a CALLABLE Eden proxy — `typeof` is "function", not "object" — so the
512
+ // guard must accept both; it only needs a callable `.fetch` (the in-process bridge the outer proxy
513
+ // exposes for the mount). An object-only check silently disabled the whole /api/* auto-mount.
514
+ return ((typeof api === "object" || typeof api === "function") &&
515
+ api !== null &&
516
+ typeof api.fetch === "function");
517
+ }
353
518
  /**
354
519
  * Build a nifra app from a route manifest: every route SSRs its layout chain via `renderPage`,
355
520
  * and a wildcard catch-all renders `_404` (or a plain 404). Reuses @nifrajs/core's router +
356
521
  * lifecycle, so matching, params, and precedence are battle-tested. fs-free — feed it a
357
522
  * manifest from `discoverRoutes` (`@nifrajs/web/fs`) at startup, so the served app stays portable.
523
+ *
524
+ * **Typed platform bindings.** Pass `Env` — `createWebApp<Env>({ … })` — to declare the app's Workers
525
+ * bindings ONCE. It seeds the returned `Server`'s context with `{ env: Env }` (exactly as the backend's
526
+ * `server<Env>()` does), so `app.fetch(req, { env })` / `toFetchHandler(app)` type-check the `env`
527
+ * argument against the declared shape — no per-binding cast at the edge entry. Per-route loaders/actions
528
+ * stay typed independently of this call: annotate them with `@nifrajs/client`'s `LoaderArgs<Api, Env>`
529
+ * (same `Env`) so `ctx.env.MY_KV` is typed there too. Omit the parameter and `Env` is `unknown` — the
530
+ * secure default; validate at the trust boundary before use.
358
531
  */
359
532
  export function createWebApp(options) {
360
533
  const { adapter, manifest, clientEntry, title, api } = options;
@@ -378,18 +551,74 @@ export function createWebApp(options) {
378
551
  return { styles: perRoute };
379
552
  return options.styles && options.styles.length > 0 ? { styles: options.styles } : {};
380
553
  };
554
+ // Seed the context with the declared `Env` so `app.fetch(req, { env })` / `toFetchHandler(app)` type
555
+ // the platform bindings (see the `createWebApp` doc). The runtime `env` still arrives per-request via
556
+ // `app.fetch(req, { env })`; this is a compile-time-only seed (`server<Env>()` casts, doesn't store).
381
557
  const app = server();
558
+ // Auto-mount the in-process backend over HTTP at `apiPrefix` (default `/api`), BEFORE page routing.
559
+ // `onRequest` runs on the raw request ahead of the router, so this wins over the page wildcard `/*`
560
+ // for every method (POST/GET/PUT/…) — not just the GET page routes — and a backend 404 (`/api/none`)
561
+ // surfaces as the backend's own response, never the page 404. We dispatch the SAME `Request` object
562
+ // (its body unread) so streamed/large bodies pass through untouched; the backend defines its routes
563
+ // at the full prefixed path (`server().post("/api/sync", …)`), matching the `inProcessClient` call
564
+ // sites. `apiPrefix: ""` opts out; a non-mountable `api` (no `.fetch`) is left pages-only. The hook
565
+ // is registered ONLY when both hold, so a pages-only app keeps core's synchronous no-hook fast path.
566
+ //
567
+ // Backward-compat: an app that still hand-dispatches `/api/*` in its server entry (the old
568
+ // `if (pathname.startsWith("/api/")) return backend.fetch(req)`) intercepts BEFORE `app.fetch` is
569
+ // ever called, so that branch returns first and this hook never sees the request — no double
570
+ // handling. Apps that drop the hand-dispatch now rely on this mount instead (dev + prod alike).
571
+ const apiPrefix = options.apiPrefix ?? "/api";
572
+ if (apiPrefix !== "" && isMountableApi(api)) {
573
+ const mountedApi = api;
574
+ app.onRequest((req) => {
575
+ const { pathname } = new URL(req.url);
576
+ // Exactly the prefix (`/api`) or a sub-path (`/api/…`) — NOT a sibling like `/apixyz` that merely
577
+ // shares the prefix as a string head. Dispatch the original `req` (body intact) to the backend.
578
+ if (pathname === apiPrefix || pathname.startsWith(`${apiPrefix}/`)) {
579
+ return mountedApi.fetch(req.url, req);
580
+ }
581
+ return undefined; // not an API path → continue to page routing
582
+ });
583
+ }
382
584
  // SSG `fallback: "404"`: the set of concrete paths that actually exist for those routes. An unlisted
383
585
  // path under a `"404"` route is rejected (it isn't a static file, and the route declared it shouldn't
384
586
  // SSR on demand). O(1) membership via a Set.
385
587
  const prerenderedSet = new Set(options.prerenderedPaths ?? []);
386
- // Resolve a route's layout chain (outermost layout → page). Loaded lazily — the data-only and
387
- // 405 branches return before any layout is needed.
388
- const loadLayouts = (route) => Promise.all(
588
+ // Load a route's layout modules (outermost layout → innermost), keeping each module whole so the
589
+ // render path can take both its `default` (the component chain) AND its `meta` (the sitewide head it
590
+ // contributes). Loaded lazily — the data-only and 405 branches return before any layout is needed.
591
+ const loadLayoutModules = (route) => Promise.all(
389
592
  // layoutIds only reference layouts present in the manifest (buildManifest invariant).
390
- route.layoutIds.map((id) => manifest.layouts[id].load().then((m) => m.default)));
593
+ route.layoutIds.map((id) => manifest.layouts[id].load()));
594
+ // Build a route's render chain + its merged `<head>` from the already-loaded layout modules + the
595
+ // page module. The chain is `[…layout components, page]`; the head merges each layout's `meta`
596
+ // export (outermost→innermost) with the page's (last), so a `head`/`meta` on `_layout.tsx`
597
+ // contributes sitewide tags. `metaArgs` (loader data + params) feed function-form `meta`s.
598
+ const resolveChainAndHead = (layoutModules, page, metaArgs) => {
599
+ const chain = [...layoutModules.map((m) => m.default), page.default];
600
+ const heads = [
601
+ ...layoutModules.map((m) => resolveMeta(m.meta, metaArgs)),
602
+ resolveMeta(page.meta, metaArgs),
603
+ ];
604
+ return { chain, head: mergeHeads(heads) };
605
+ };
391
606
  // Dir a special-file id lives in: `_error`→"" , `a/b/_error`→"a/b" (and likewise for `_layout`).
392
607
  const dirOfId = (id, suffix) => id === suffix ? "" : id.slice(0, id.length - suffix.length - 1);
608
+ // The request's origin (scheme + host + port, NO trailing slash) — the one server fact `meta()` needs
609
+ // for absolute `canonical`/`og:url`/`og:image` but can't otherwise see (it also runs on the client).
610
+ // `URL.origin` is exactly that shape; it matches the browser's `location.origin` for the same URL, so
611
+ // a soft-nav's `applyHead` resolves an identical `<head>` (no hydration drift). A malformed `req.url`
612
+ // (shouldn't happen on a real request) degrades to `""` — the documented unknown-origin default —
613
+ // rather than throwing during render.
614
+ const originOf = (req) => {
615
+ try {
616
+ return new URL(req.url).origin;
617
+ }
618
+ catch {
619
+ return "";
620
+ }
621
+ };
393
622
  /**
394
623
  * Render the **nearest `_error` boundary** when a route's loader throws (the agnostic, server-side
395
624
  * half of error UI — works on every adapter, no client takeover). The boundary renders in place of
@@ -501,8 +730,11 @@ export function createWebApp(options) {
501
730
  headers: { "content-type": "application/x-ndjson; charset=utf-8" },
502
731
  });
503
732
  }
504
- const chain = [...(await loadLayouts(route)), mod.default];
505
- const head = resolveMeta(mod.meta, { data, params: c.params });
733
+ const { chain, head } = resolveChainAndHead(await loadLayoutModules(route), mod, {
734
+ data,
735
+ params: c.params,
736
+ origin: originOf(c.req),
737
+ });
506
738
  const hydrateRoute = mod.hydrate !== false;
507
739
  try {
508
740
  // `await` so a shell-render throw (renderToStream rejects before any byte) is caught here and
@@ -588,8 +820,11 @@ export function createWebApp(options) {
588
820
  const data = mod.loader
589
821
  ? await mod.loader({ params: c.params, request: c.req, api, env: c.env, draft })
590
822
  : null;
591
- const chain = [...(await loadLayouts(route)), mod.default];
592
- const head = resolveMeta(mod.meta, { data, params: c.params });
823
+ const { chain, head } = resolveChainAndHead(await loadLayoutModules(route), mod, {
824
+ data,
825
+ params: c.params,
826
+ origin: originOf(c.req),
827
+ });
593
828
  const hydrateRoute = mod.hydrate !== false;
594
829
  // A full-page POST streams the action's `defer()`'d parts mid-document behind `<Await>` too —
595
830
  // `renderPage` splits `actionData` like loader data (works with JS off; hydrates after).
@@ -655,7 +890,7 @@ export function generateClientEntry(manifest, options) {
655
890
  loaderRows.push(` "_404": ${lazyLoader([manifest.notFound.file])},`);
656
891
  }
657
892
  return `${[
658
- 'import { createClientRouter, createMatcher, resolveMeta } from "@nifrajs/web"',
893
+ 'import { createClientRouter, createMatcher, mergeHeads, resolveMeta } from "@nifrajs/web"',
659
894
  'import { applyHead, installForms, installHistory } from "@nifrajs/web/client"',
660
895
  // Namespace import: `errorBoundary` is optional (an adapter may not export it). A namespace member
661
896
  // access yields `undefined` if absent — unlike a named import, which would be a link error.
@@ -665,7 +900,7 @@ export function generateClientEntry(manifest, options) {
665
900
  `const errorRouteIds = new Set(${JSON.stringify(errorRouteIds)})`,
666
901
  // Each route is a lazy loader: dynamic imports → Bun.build (splitting) emits one chunk per
667
902
  // route, shared layouts/deps deduped into shared chunks, so a route's code loads only when
668
- // visited. loadModule caches the [layouts…, page] component chain + the page's meta per id.
903
+ // visited. loadModule caches the [layouts…, page] component chain + the chain's meta list per id.
669
904
  "const loaders = {",
670
905
  ...loaderRows,
671
906
  "}",
@@ -678,15 +913,19 @@ export function generateClientEntry(manifest, options) {
678
913
  // the adapter's boundary so a client render error renders the `_error` UI. DOM-transparent, so the
679
914
  // hydrated tree matches the SSR markup (which has no boundary). Falls back to the plain chain when
680
915
  // the adapter has no `errorBoundary`.
916
+ // `metas[id]` is the chain's `meta` exports in head order (outermost layout → … → page), so a
917
+ // soft-nav merges the layout chain's head with the page's — matching the SSR `<head>` (sitewide
918
+ // layout tags persist across client navigation, no flash of page-only head). `_error` carries no
919
+ // head (a terminal boundary), so it's excluded from the meta list for error routes.
681
920
  " if (errorBoundary && errorRouteIds.has(id)) {",
682
921
  " const fallback = mods[mods.length - 1].default",
683
922
  " const page = mods[mods.length - 2].default",
684
923
  " const layouts = mods.slice(0, mods.length - 2).map((m) => m.default)",
685
924
  " chains[id] = [...layouts, errorBoundary(fallback), page]",
686
- " metas[id] = mods[mods.length - 2].meta",
925
+ " metas[id] = mods.slice(0, mods.length - 1).map((m) => m.meta)",
687
926
  " } else {",
688
927
  " chains[id] = mods.map((m) => m.default)",
689
- " metas[id] = mods[mods.length - 1].meta",
928
+ " metas[id] = mods.map((m) => m.meta)",
690
929
  " }",
691
930
  "}",
692
931
  "const patterns = [",
@@ -719,7 +958,13 @@ export function generateClientEntry(manifest, options) {
719
958
  " mountRouter({ router, routes: chains, container: root })",
720
959
  " router.subscribe(() => {",
721
960
  " const s = router.snapshot()",
722
- " if (!s.pending) applyHead(resolveMeta(metas[s.routeId], { data: s.data, params: s.params }))",
961
+ // Merge the matched route's chain meta (layouts→page) into one head — same contract as SSR.
962
+ " if (!s.pending) {",
963
+ // `origin: location.origin` matches the SSR `originOf(req)` (both are `URL.origin` for the same
964
+ // page URL), so a soft-nav re-resolves the SAME absolute canonical/og:url — no head drift.
965
+ " const args = { data: s.data, params: s.params, origin: location.origin }",
966
+ " applyHead(mergeHeads((metas[s.routeId] ?? [undefined]).map((m) => resolveMeta(m, args))))",
967
+ " }",
723
968
  " })",
724
969
  "})",
725
970
  ].join("\n")}\n`;