@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
package/dist/index.d.ts
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/web
|
|
2
|
+
* @nifrajs/web - the framework-agnostic SSR core. It owns the render *seam* and the HTML
|
|
3
3
|
* document orchestration, and knows nothing about any specific UI framework: a render
|
|
4
4
|
* adapter (@nifrajs/web-solid, @nifrajs/web-react, …) supplies the actual render + hydrate.
|
|
5
5
|
*
|
|
6
|
-
* The core treats a "component" and a hydration "container" as opaque `unknown`
|
|
6
|
+
* The core treats a "component" and a hydration "container" as opaque `unknown` - only
|
|
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
10
|
import { server } from "@nifrajs/core/server";
|
|
11
11
|
import { type Deferred, defer } from "./deferred.js";
|
|
12
|
-
import type { LinkDescriptor, Manifest, Meta, MetaArgs, MetaInput, ScriptDescriptor } from "./manifest.js";
|
|
12
|
+
import type { LinkDescriptor, Manifest, Meta, MetaArgs, MetaDescriptor, MetaInput, ScriptDescriptor, UnsafeScriptDescriptor } from "./manifest.js";
|
|
13
13
|
import { type Submission } from "./router.js";
|
|
14
|
-
export { DRAFT_COOKIE, type DraftCookieControls, disableDraft, type EnableDraftOptions, enableDraft, isDraftEnabled, } from "./draft.js";
|
|
14
|
+
export { DRAFT_COOKIE, type DraftCookieControls, disableDraft, type EnableDraftOptions, enableDraft, isDraftEnabled, type PreviewEndpointOptions, previewEndpoint, } 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, 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
|
-
export { type BrowserNavigate, getBrowserNavigate, type NavigateOptions, setBrowserNavigate, } from "./navigation.js";
|
|
17
|
+
export { type Action, buildManifest, enumerateStaticRoutes, filePathToPattern, filePathToPatterns, type GetStaticPaths, type InertScriptType, 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, type UnsafeScriptDescriptor, } from "./manifest.js";
|
|
18
|
+
export { type Blocker, type BlockerController, type BlockerFunction, type BlockerLocation, type BlockerState, type BrowserNavigate, getBrowserNavigate, IDLE_BLOCKER, type NavigateFunction, type NavigateOptions, type NavigateSearchOf, type NavigateTargetInput, type RouteSearch, registerBlocker, resolveNavigate, setBlockerController, setBrowserNavigate, } from "./navigation.js";
|
|
19
|
+
export { type PublicDirCache, resolvePublicPath, type ServePublicDirOptions, servePublicDir, } from "./public-dir.js";
|
|
19
20
|
export { createMutation, createQueryClient, type DehydratedState, hashQueryKey, type InfiniteData, type InfiniteQueryHandle, type InfiniteQueryOptions, type MutationCallbacks, type MutationHandle, type MutationState, type MutationStatus, type QueryClient, type QueryClientOptions, type QueryHandle, type QueryOptions, type QueryState, type QueryStatus, } from "./query.js";
|
|
20
|
-
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";
|
|
21
|
+
export { type ClientRouter, type ClientRouterOptions, createClientRouter, createMatcher, DATA_HEADER, type Fetcher, type FetcherState, type FetchRouteData, type MountRouterOptions, NAV_FROM_HEADER, REDIRECT_HEADER, RETAIN_HEADER, REVALIDATE_HEADER, type RouteMatch, type RoutePattern, type RouterState, STATUS_HEADER, type Submission, type SubmitOptions, } from "./router.js";
|
|
21
22
|
export { type Deferred, defer };
|
|
22
23
|
/** Phantom brand key for {@link ServerOnly}. A unique symbol so the brand can't be forged by a plain
|
|
23
24
|
* object literal; `unique symbol` keys never appear in the emitted JS, so the brand is purely
|
|
24
25
|
* type-level (zero runtime cost). The property is optional + `never`-typed, so a `ServerOnly<T>`
|
|
25
|
-
* stays assignment-compatible with `T`
|
|
26
|
+
* stays assignment-compatible with `T` - it documents intent without obstructing real use. */
|
|
26
27
|
declare const SERVER_ONLY_BRAND: unique symbol;
|
|
27
28
|
/**
|
|
28
|
-
* Type-level intent marker for a value that must only exist on the server
|
|
29
|
+
* Type-level intent marker for a value that must only exist on the server - a secret, a DB handle, a
|
|
29
30
|
* server-only client. `ServerOnly<T>` is structurally `T` (the brand is an optional phantom field, so
|
|
30
31
|
* existing code keeps type-checking), but it advertises to readers + the compiler that the value is
|
|
31
|
-
* not meant to cross to the browser. It is **purely type-level** and erases at build
|
|
32
|
+
* not meant to cross to the browser. It is **purely type-level** and erases at build - it does NOT,
|
|
32
33
|
* by itself, keep the value out of the client bundle.
|
|
33
34
|
*
|
|
34
35
|
* The enforcement is two runtime conventions:
|
|
35
|
-
* - add the side-effect import `import "@nifrajs/web/server-only"` at the top of the module
|
|
36
|
+
* - add the side-effect import `import "@nifrajs/web/server-only"` at the top of the module - the
|
|
36
37
|
* client build ({@link buildClient}) fails loud, with the import chain, if it reaches a browser
|
|
37
38
|
* chunk (the poison-import marker);
|
|
38
|
-
* - or name the file `*.server.ts`
|
|
39
|
+
* - or name the file `*.server.ts` - the `.server` convention empties it in the client build.
|
|
39
40
|
*
|
|
40
41
|
* Use this brand to express the intent in the types; pair it with one of those runtime markers so a
|
|
41
42
|
* leak is caught at build time rather than shipping a secret to the client.
|
|
@@ -49,11 +50,20 @@ export type ServerOnly<T> = T & {
|
|
|
49
50
|
readonly [SERVER_ONLY_BRAND]?: never;
|
|
50
51
|
};
|
|
51
52
|
export { assertRenderAdapterConformance, RenderAdapterConformanceError, type RenderAdapterConformanceFixture, } from "./conformance.js";
|
|
53
|
+
export { type SearchOf, searchOf, searchOfChain, serializeSearch } from "./search.js";
|
|
52
54
|
/** The data handed to a route component. Opaque to the core. `actionData` is the return of a
|
|
53
55
|
* route `action` after a POST (absent on plain GETs). `pending` + `submission` are client-only
|
|
54
|
-
* (absent on SSR): they drive **optimistic UI**
|
|
56
|
+
* (absent on SSR): they drive **optimistic UI** - render from `submission.formData` while `pending`. */
|
|
55
57
|
export interface RenderProps {
|
|
56
58
|
readonly data: unknown;
|
|
59
|
+
/**
|
|
60
|
+
* Per-layout loader data, aligned index-for-index with the layout prefix of `chain`.
|
|
61
|
+
*
|
|
62
|
+
* Absent when no layout in the chain exports a `loader`, so a page-only app is byte-identical to
|
|
63
|
+
* before. An adapter that does not read it renders exactly as it did; one that does passes
|
|
64
|
+
* `layoutData[i]` to `chain[i]`.
|
|
65
|
+
*/
|
|
66
|
+
readonly layoutData?: readonly unknown[];
|
|
57
67
|
readonly actionData?: unknown;
|
|
58
68
|
/** True while a client navigation or submit is in flight (client-only; absent/false on SSR). */
|
|
59
69
|
readonly pending?: boolean;
|
|
@@ -66,10 +76,15 @@ export interface RenderProps {
|
|
|
66
76
|
* SSR (from the request match) and on client navigation (from router state) so an adapter's
|
|
67
77
|
* `useParams` reads the same value on both, with no hydration mismatch. Absent ⇒ no params (`{}`). */
|
|
68
78
|
readonly params?: Readonly<Record<string, string>>;
|
|
69
|
-
/** The current URL's `pathname + search` (no hash
|
|
79
|
+
/** The current URL's `pathname + search` (no hash - the hash never reaches the server). Threaded on
|
|
70
80
|
* SSR and client alike so an adapter's `useLocation`/`useSearchParams` hydrate consistently. Absent
|
|
71
81
|
* on adapters/callers that don't supply it (treated as `""`). */
|
|
72
82
|
readonly path?: string;
|
|
83
|
+
/** The route's typed, validated search params (the value the loader also gets as `ctx.search`),
|
|
84
|
+
* threaded to the adapter's `useSearch`. Derived identically on SSR (from the request) and on client
|
|
85
|
+
* navigation (adapter mount) via the shared `searchOf`, so a component reading it hydrates with no
|
|
86
|
+
* drift. Absent ⇒ no search context (`{}`). */
|
|
87
|
+
readonly search?: Record<string, unknown>;
|
|
73
88
|
}
|
|
74
89
|
/**
|
|
75
90
|
* The seam every render adapter implements. New adapters should prove these invariants with
|
|
@@ -82,17 +97,17 @@ export interface RenderAdapter {
|
|
|
82
97
|
* (the loader data); each layout wraps the child via its `children`. Returns (or resolves to)
|
|
83
98
|
* a Web `ReadableStream<Uint8Array>`: a streaming renderer flushes the shell first and streams
|
|
84
99
|
* Suspense boundaries as they resolve; a non-streaming renderer may return a one-chunk stream.
|
|
85
|
-
* May be async
|
|
100
|
+
* May be async - e.g. React resolves once the shell is renderable, so a shell-render throw can
|
|
86
101
|
* still map to an error status before any byte is sent.
|
|
87
102
|
*/
|
|
88
103
|
renderToStream(chain: readonly unknown[], props: RenderProps): ReadableStream<Uint8Array> | Promise<ReadableStream<Uint8Array>>;
|
|
89
104
|
/**
|
|
90
105
|
* Server (optional): render the chain to a complete HTML **string** in one pass, with the same
|
|
91
106
|
* hydration markers `renderToStream` produces. When provided, `renderPage` uses it for any page
|
|
92
|
-
* that does **not** `defer()`
|
|
107
|
+
* that does **not** `defer()` - buffering the document is faster than the streaming pipeline there,
|
|
93
108
|
* because the framework's synchronous renderer (React `renderToString`, Solid `renderToString`,
|
|
94
109
|
* Preact `render`, Vue `renderToString`, Svelte `render`) is markedly cheaper than its streaming
|
|
95
|
-
* renderer
|
|
110
|
+
* renderer - most visibly on Bun, where React Fizz / Solid `renderToStream` are the heaviest. Pages
|
|
96
111
|
* that `defer()` keep the streaming path (progressive `<Await>` needs it). Markup MUST be
|
|
97
112
|
* hydration-equivalent to `renderToStream`; a throw surfaces like the streaming shell-readiness
|
|
98
113
|
* await (the `_error` boundary maps it to a status). Omit it to always stream.
|
|
@@ -102,22 +117,37 @@ export interface RenderAdapter {
|
|
|
102
117
|
* Server: per-document bootstrap markup injected into `<head>` that the client
|
|
103
118
|
* `hydrate` requires (Solid: `generateHydrationScript()`). Empty string if none.
|
|
104
119
|
*/
|
|
105
|
-
hydrationHead(): string;
|
|
120
|
+
hydrationHead(nonce?: string): string;
|
|
106
121
|
}
|
|
107
122
|
/** Global the server serializes loader data into; the client reads it to hydrate. */
|
|
108
123
|
export declare const DATA_GLOBAL = "__NIFRA_DATA__";
|
|
124
|
+
/** Per-layout loader data for hydration. Emitted ONLY when some layout in the chain has a loader, so
|
|
125
|
+
* a page-only app's HTML is byte-identical to before layout loaders existed. */
|
|
126
|
+
export declare const LAYOUT_DATA_GLOBAL = "__NIFRA_LAYOUT_DATA__";
|
|
109
127
|
/** Global the server writes the matched route id into; the client uses it to pick the chain. */
|
|
110
128
|
export declare const ROUTE_GLOBAL = "__NIFRA_ROUTE__";
|
|
111
129
|
/** Global the server serializes an action's data return into (absent on GETs); the client
|
|
112
130
|
* reads it so hydration after a native form POST matches the server-rendered markup. */
|
|
113
131
|
export declare const ACTION_GLOBAL = "__NIFRA_ACTION__";
|
|
114
132
|
/**
|
|
115
|
-
*
|
|
133
|
+
* Marker attribute on the app container, emitted ONLY when `rootId` is not the default.
|
|
134
|
+
*
|
|
135
|
+
* `rootId` is a per-RENDER option; the client entry is a per-BUILD artifact, so it cannot be
|
|
136
|
+
* generated knowing which id a given render will use. The container therefore announces itself, and
|
|
137
|
+
* the entry finds it by this attribute before falling back to `#root`. It has to be the DOM rather
|
|
138
|
+
* than another `window.__NIFRA_*` global: a second copy of the id can drift from the markup, while
|
|
139
|
+
* this is written by the same expression that writes the id and cannot.
|
|
140
|
+
*
|
|
141
|
+
* Absent on a default render, so an app that never sets `rootId` emits the same bytes it always has.
|
|
142
|
+
*/
|
|
143
|
+
export declare const ROOT_ATTRIBUTE = "data-nifra-root";
|
|
144
|
+
/**
|
|
145
|
+
* Pre-hydration form guard - a tiny inline script flushed in `<head>` (it runs in the window between
|
|
116
146
|
* first paint and the island bundle taking over). It neutralizes the one real hydration footgun: a
|
|
117
147
|
* JS-only form (a hand-wired `onSubmit` with no native fallback) submitting *natively* before its
|
|
118
148
|
* handler is attached, which navigates the browser to a broken `?field=…` GET of the current page.
|
|
119
149
|
*
|
|
120
|
-
* It blocks ONLY that shape
|
|
150
|
+
* It blocks ONLY that shape - an effective-GET form whose action targets the current document - and
|
|
121
151
|
* only until hydration commits (`data-nifra-hydrated`, set by the client entry). Method-`post` forms
|
|
122
152
|
* (progressive enhancement: the native POST hits the route `action`) and GET forms with a real action
|
|
123
153
|
* (a search box → `/search`) pass through untouched. Opt out per-form with `data-native`. Static text
|
|
@@ -127,7 +157,7 @@ export declare const PRE_HYDRATION_GUARD: string;
|
|
|
127
157
|
/**
|
|
128
158
|
* The single default port for the dev server (`@nifrajs/web/dev`, `@nifrajs/web/vite`) **and**
|
|
129
159
|
* `nifra start`. Deliberately uncommon: `3000`/`5173`/`8080` collide with whatever else is running
|
|
130
|
-
* (Next, Vite, a stray Node API). `4321` rarely is
|
|
160
|
+
* (Next, Vite, a stray Node API). `4321` rarely is - and being the *same* constant across `nifra dev`
|
|
131
161
|
* and `nifra start` means a project's URL doesn't change between commands. Override per-run with
|
|
132
162
|
* `--port <n>` or the `PORT` env var. */
|
|
133
163
|
export declare const DEFAULT_DEV_PORT = 4321;
|
|
@@ -144,21 +174,27 @@ export interface RenderedPage {
|
|
|
144
174
|
}
|
|
145
175
|
export interface RenderPageOptions {
|
|
146
176
|
readonly adapter: RenderAdapter;
|
|
147
|
-
/** The layout chain to render
|
|
177
|
+
/** The layout chain to render - outermost layout → page (opaque; the adapter renders it). */
|
|
148
178
|
readonly chain: readonly unknown[];
|
|
149
179
|
/** The loader output for this request. */
|
|
150
180
|
readonly data: unknown;
|
|
151
|
-
/** An action's data return (POST only)
|
|
181
|
+
/** An action's data return (POST only) - surfaced to the page as `actionData` + serialized
|
|
152
182
|
* for the client so post-POST hydration matches. Omit on GETs. */
|
|
153
183
|
readonly actionData?: unknown;
|
|
154
|
-
/**
|
|
155
|
-
|
|
184
|
+
/**
|
|
185
|
+
* URL of the built client entry (loaded as a module script).
|
|
186
|
+
*
|
|
187
|
+
* Optional ONLY on a `hydrate: false` page, where there is no client takeover to feed and this value
|
|
188
|
+
* reaches no output at all. The union below is what enforces that: omitting it on a hydrating page
|
|
189
|
+
* stays a compile error rather than rendering `src=""`.
|
|
190
|
+
*/
|
|
191
|
+
readonly clientEntry?: string;
|
|
156
192
|
/** Chunk URLs for the **matched** route (its layout chain + own chunk) to `modulepreload` in the
|
|
157
|
-
* shell
|
|
193
|
+
* shell - so the route code downloads in parallel with the entry instead of after it
|
|
158
194
|
* (`buildClient`'s per-route map). Empty/omitted ⇒ only the entry is preloaded (unchanged). */
|
|
159
195
|
readonly preload?: readonly string[];
|
|
160
196
|
/** Stylesheet URLs for the **matched** route (its layout chain + own CSS, from `buildClient`'s
|
|
161
|
-
* `BuildManifest.css`)
|
|
197
|
+
* `BuildManifest.css`) - injected as `<link rel="stylesheet">` in `<head>` so styles arrive with the
|
|
162
198
|
* first paint (no FOUC). Rendered even on non-hydrated pages. Empty/omitted ⇒ none (unchanged). */
|
|
163
199
|
readonly styles?: readonly string[];
|
|
164
200
|
/** SSG: the prerendered-path set, serialized to `window.__NIFRA_PRERENDERED__` so the client fetches
|
|
@@ -169,52 +205,78 @@ export interface RenderPageOptions {
|
|
|
169
205
|
readonly revalidate?: number;
|
|
170
206
|
/** Matched route id; written to `window.__NIFRA_ROUTE__` so the client hydrates this chain. */
|
|
171
207
|
readonly routeId?: string;
|
|
172
|
-
/** The matched route's decoded path params
|
|
208
|
+
/** The matched route's decoded path params - surfaced to the page as `params` (via {@link RenderProps})
|
|
173
209
|
* so an adapter's `useParams` is SSR-correct. Omit ⇒ `{}` (a route with no dynamic segments, or an
|
|
174
210
|
* error/404 render). */
|
|
175
211
|
readonly params?: Readonly<Record<string, string>>;
|
|
176
|
-
/** The request's `pathname + search`
|
|
212
|
+
/** The request's `pathname + search` - surfaced as `path` (via {@link RenderProps}) so an adapter's
|
|
177
213
|
* `useLocation`/`useSearchParams` render the right URL server-side and hydrate without drift. Omit ⇒
|
|
178
214
|
* `""`. */
|
|
179
215
|
readonly path?: string;
|
|
216
|
+
/** The route's validated search (`searchOf(mod.searchSchema, url.search)`, the same value handed to
|
|
217
|
+
* the loader as `ctx.search`), surfaced as `search` (via {@link RenderProps}) so an adapter's
|
|
218
|
+
* `useSearch` is SSR-correct. Omit ⇒ `{}` (a render with no search context). */
|
|
219
|
+
readonly search?: Record<string, unknown>;
|
|
220
|
+
/** Per-layout loader data, forwarded to the adapter as `RenderProps.layoutData`. Aligned with the
|
|
221
|
+
* chain's layout prefix; omitted when no layout in the chain has a loader. */
|
|
222
|
+
readonly layoutData?: readonly unknown[];
|
|
180
223
|
/** HTTP status for the response (default 200; e.g. 404 for a not-found page). */
|
|
181
224
|
readonly status?: number;
|
|
225
|
+
/** Extra response headers - e.g. the `cache-control` a terminal status page wants. `content-type`
|
|
226
|
+
* is ignored (the document is always HTML) and the ISR freshness header is applied after these,
|
|
227
|
+
* so neither can be overridden from here. */
|
|
228
|
+
readonly headers?: HeadersLike;
|
|
182
229
|
/** Document title (fallback when `head.title` is unset). */
|
|
183
230
|
readonly title?: string;
|
|
184
|
-
/** Resolved route head
|
|
231
|
+
/** Resolved route head - `title` overrides `title` above; `meta`/`link` render as managed
|
|
185
232
|
* (`data-nifra`) tags the client updates on navigation.
|
|
186
233
|
*
|
|
187
234
|
* **Head contract (the layout chain contributes).** `createWebApp` resolves this via
|
|
188
235
|
* {@link mergeHeads}: a route's head is its **layout chain's** `meta`/`head` exports merged with the
|
|
189
|
-
* page's. A `_layout.tsx` may `export const meta` (or `export function meta(args)`)
|
|
236
|
+
* page's. A `_layout.tsx` may `export const meta` (or `export function meta(args)`) - its tags land
|
|
190
237
|
* on every page below it (the home for `hreflang`, `preconnect`, a section-default `<title>`). The
|
|
191
238
|
* merge is **nearest-wins for scalars** (the page's `title` overrides an inner layout's, which
|
|
192
239
|
* overrides an outer one; an undefined page title keeps the layout's) and **concatenated for the
|
|
193
|
-
* `meta`/`link` arrays** (outermost layout first, page last). `<link>` attributes
|
|
194
|
-
* (
|
|
195
|
-
* `integrity`/`fetchpriority`/…) and value-escaped against XSS. */
|
|
240
|
+
* `meta`/`link` arrays** (outermost layout first, page last). `<meta>`/`<link>` attributes pass a
|
|
241
|
+
* shared tag-specific allowlist (including inert `data-*` metadata) and values are escaped. */
|
|
196
242
|
readonly head?: Meta;
|
|
197
|
-
/** Id of the container wrapping the app markup (default `"root"`).
|
|
243
|
+
/** Id of the container wrapping the app markup (default `"root"`). A non-default id also gets
|
|
244
|
+
* {@link ROOT_ATTRIBUTE}, which is how the generated client entry finds the container to hydrate -
|
|
245
|
+
* the entry is built once and cannot know what a given render chose. */
|
|
198
246
|
readonly rootId?: string;
|
|
199
|
-
/** When `false`, emit a complete but **non-hydrated** document
|
|
247
|
+
/** When `false`, emit a complete but **non-hydrated** document - no client entry script, data
|
|
200
248
|
* globals, or modulepreloads. Used for server-rendered `_error` pages: a terminal state that needs no
|
|
201
249
|
* client takeover, and it sidesteps an SSR/hydrate mismatch (the server rendered the boundary, not
|
|
202
250
|
* the page the client manifest maps this route id to). Default `true`. */
|
|
203
251
|
readonly hydrate?: boolean;
|
|
204
252
|
/** Island client bundles (`@nifrajs/web/islands`) to load as `<script type="module">` in the document
|
|
205
|
-
* tail
|
|
253
|
+
* tail - emitted **regardless of `hydrate`**, so a static (`hydrate: false`) page can still mount
|
|
206
254
|
* no-framework islands. URLs are attribute-escaped. Empty/omitted ⇒ none (unchanged output). */
|
|
207
255
|
readonly islandScripts?: readonly string[];
|
|
256
|
+
/** CSP nonce applied to every framework-owned executable script in this document. */
|
|
257
|
+
readonly nonce?: string;
|
|
208
258
|
}
|
|
209
259
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
260
|
+
* `renderPage` input. A hydrating page (the default) must supply `clientEntry`, because the document
|
|
261
|
+
* loads it as a module script; a `hydrate: false` page may omit it, because nothing in the emitted
|
|
262
|
+
* document references it. Expressed as a union so the compiler enforces the pairing rather than the
|
|
263
|
+
* renderer discovering an empty `src` at runtime.
|
|
264
|
+
*/
|
|
265
|
+
export type RenderPageInput = RenderPageOptions & ({
|
|
266
|
+
readonly hydrate?: true;
|
|
267
|
+
readonly clientEntry: string;
|
|
268
|
+
} | {
|
|
269
|
+
readonly hydrate: false;
|
|
270
|
+
});
|
|
271
|
+
/**
|
|
272
|
+
* Server: render a full HTML document for a page - the adapter's hydration head + the SSR
|
|
273
|
+
* markup (**streamed**) + the serialized loader data + the client module - as a `Response`.
|
|
212
274
|
* The shell (`<head>` + the open container) flushes first, the adapter's app stream follows,
|
|
213
275
|
* then the tail (data globals + client entry). Pure Web Standards, so it returns straight from
|
|
214
276
|
* a nifra route handler and streams on any fetch runtime (Bun/Node/Deno/Workers).
|
|
215
277
|
*/
|
|
216
|
-
export declare function renderPage(options:
|
|
217
|
-
export declare function renderPageResult(options:
|
|
278
|
+
export declare function renderPage(options: RenderPageInput): MaybePromise<Response>;
|
|
279
|
+
export declare function renderPageResult(options: RenderPageInput): MaybePromise<RenderedPage>;
|
|
218
280
|
/** Options for {@link redirect}. */
|
|
219
281
|
export interface RedirectOptions {
|
|
220
282
|
/** HTTP status (default 303 See Other; pass 307/308 to preserve the method). */
|
|
@@ -225,21 +287,76 @@ export interface RedirectOptions {
|
|
|
225
287
|
readonly external?: boolean;
|
|
226
288
|
}
|
|
227
289
|
/**
|
|
228
|
-
* Build a redirect `Response`
|
|
290
|
+
* Build a redirect `Response` - return it from a route `action` for the Post/Redirect/Get
|
|
229
291
|
* pattern (POST mutates, 303 sends the browser to a fresh GET, so a reload doesn't re-submit).
|
|
230
292
|
* Defaults to 303 (See Other); pass `{ status: 307 }` or `{ status: 308 }` to preserve the method.
|
|
231
293
|
*
|
|
232
294
|
* **Secure by default:** `location` must be a same-origin path (begins with `/`, not `//`). An
|
|
233
|
-
* off-origin/absolute destination throws unless you pass `{ external: true }`
|
|
295
|
+
* off-origin/absolute destination throws unless you pass `{ external: true }` - this closes the
|
|
234
296
|
* open-redirect footgun of `return redirect(formData.get("next"))` on the no-JS (native-form) path,
|
|
235
297
|
* which returns the action's `Response` verbatim.
|
|
236
298
|
*
|
|
237
299
|
* @param options redirect status and whether an off-origin destination is intentional.
|
|
238
300
|
*/
|
|
239
301
|
export declare function redirect(location: string, options?: RedirectOptions): Response;
|
|
302
|
+
/**
|
|
303
|
+
* Brand marking a `Response` as a **terminal-status signal** rather than a response to serve verbatim.
|
|
304
|
+
*
|
|
305
|
+
* A loader that throws a bare `Response` has always been passed straight through - that is how
|
|
306
|
+
* `throw redirect(...)` works, and apps rely on it. So "render the `_404` boundary at 404" cannot be
|
|
307
|
+
* expressed by throwing a plain `Response`: the framework would have no way to tell it apart from a
|
|
308
|
+
* hand-rolled body the app wants served exactly as written. The brand is that distinction, and
|
|
309
|
+
* checking it *before* the pass-through is what keeps every existing throw working unchanged.
|
|
310
|
+
*
|
|
311
|
+
* `Symbol.for` matches `RESPONSE_RESULT` above: a registry symbol survives two copies of this module,
|
|
312
|
+
* which a `unique symbol` would not - and a duplicate-install turning a 404 into a raw 404 body would
|
|
313
|
+
* be a maddening bug to trace.
|
|
314
|
+
*/
|
|
315
|
+
/** Derived from the `Headers` constructor rather than the DOM lib's `HeadersInit`: `@nifrajs/web`
|
|
316
|
+
* is consumed from DOM-free programs (the root tsconfig runs `types: ["bun"]`), and naming the DOM
|
|
317
|
+
* type here would break their build. Same workaround as `SSEInit.headers` in core. */
|
|
318
|
+
type HeadersLike = ConstructorParameters<typeof Headers>[0];
|
|
319
|
+
/** Options shared by {@link notFound}, {@link gone}, and {@link statusPage}. */
|
|
320
|
+
export interface StatusPageOptions {
|
|
321
|
+
/**
|
|
322
|
+
* Extra headers for the rendered response - `cache-control` above all.
|
|
323
|
+
*
|
|
324
|
+
* The defaults differ by status for a reason: a 404 may be a race with publication, so it wants a
|
|
325
|
+
* short TTL, while a 410 is a promise that the URL is permanently gone and can be cached hard.
|
|
326
|
+
* Getting this wrong is the difference between a crawler dropping a dead URL and re-fetching it
|
|
327
|
+
* for weeks.
|
|
328
|
+
*/
|
|
329
|
+
readonly headers?: HeadersLike;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Render the nearest `_404` page at status **404**. `throw` it from a loader when the record does not
|
|
333
|
+
* exist.
|
|
334
|
+
*
|
|
335
|
+
* This is the fix for the soft 404: a matched route whose loader finds nothing has otherwise no way to
|
|
336
|
+
* answer 404, so the path of least resistance is to return empty data and render "not found" inside a
|
|
337
|
+
* **200**. That looks correct in a browser and is invisible in review, which is why it ships and stays
|
|
338
|
+
* shipped - while search engines penalise it and keep the dead URL indexed.
|
|
339
|
+
*
|
|
340
|
+
* Returns `never`: these throw, so a loader narrows without a redundant `return`, and the type states
|
|
341
|
+
* the thing the "loaders `throw` redirect, actions `return` it" rule already trips people on.
|
|
342
|
+
*/
|
|
343
|
+
export declare function notFound(options?: StatusPageOptions): never;
|
|
344
|
+
/**
|
|
345
|
+
* Render a terminal page at status **410 Gone**. `throw` it from a loader for a record that existed and
|
|
346
|
+
* was deliberately removed - a withdrawn listing, a deleted post.
|
|
347
|
+
*
|
|
348
|
+
* 410 is not a pedantic 404: it tells a crawler to **drop** the URL rather than re-fetch it for weeks
|
|
349
|
+
* on the assumption the 404 was transient. Uses `_410.tsx` if the app has one, otherwise `_404`.
|
|
350
|
+
*/
|
|
351
|
+
export declare function gone(options?: StatusPageOptions): never;
|
|
352
|
+
/**
|
|
353
|
+
* Render a terminal page at any 4xx/5xx status - the escape hatch behind {@link notFound} and
|
|
354
|
+
* {@link gone} (402, 451, …). Uses `_<status>.tsx` if present, otherwise `_404`.
|
|
355
|
+
*/
|
|
356
|
+
export declare function statusPage(status: number, options?: StatusPageOptions): never;
|
|
240
357
|
/** The wrapper `revalidate()` returns: the action's `data` plus the paths it changed. A plain tagged
|
|
241
358
|
* shape (not a class) so `@nifrajs/client`'s `ActionData` can unwrap it structurally without importing
|
|
242
|
-
* from `@nifrajs/web`. `createWebApp` strips the wrapper
|
|
359
|
+
* from `@nifrajs/web`. `createWebApp` strips the wrapper - the client receives `data` as the body and
|
|
243
360
|
* the paths via the `X-Nifra-Revalidate` header. */
|
|
244
361
|
export interface RevalidateResult<T> {
|
|
245
362
|
readonly __nifraRevalidate: readonly string[];
|
|
@@ -248,7 +365,7 @@ export interface RevalidateResult<T> {
|
|
|
248
365
|
/**
|
|
249
366
|
* Return this from an action to declare which routes the mutation changed (alongside the action's
|
|
250
367
|
* `data`). `createWebApp` sets the `X-Nifra-Revalidate` response header; after the submit the client
|
|
251
|
-
* marks those cached routes stale
|
|
368
|
+
* marks those cached routes stale - refetching the active one and any mounted fetcher showing them -
|
|
252
369
|
* so a mutation can refresh views beyond the one that was submitted. `data` is still surfaced to the
|
|
253
370
|
* component as `actionData` (the wrapper is transparent to `ActionData<typeof action>`).
|
|
254
371
|
*/
|
|
@@ -271,7 +388,7 @@ export declare function resolveMeta(meta: MetaInput | undefined, args: MetaArgs)
|
|
|
271
388
|
* ```
|
|
272
389
|
*/
|
|
273
390
|
export declare function canonical(href: string): LinkDescriptor;
|
|
274
|
-
/** Inputs for {@link openGraph}
|
|
391
|
+
/** Inputs for {@link openGraph} - the common Open Graph properties. All optional; only the provided
|
|
275
392
|
* ones become tags. `type` defaults to `"website"`. */
|
|
276
393
|
export interface OpenGraphInput {
|
|
277
394
|
readonly title?: string;
|
|
@@ -280,7 +397,7 @@ export interface OpenGraphInput {
|
|
|
280
397
|
readonly image?: string;
|
|
281
398
|
/** Canonical URL of the page (`og:url`). */
|
|
282
399
|
readonly url?: string;
|
|
283
|
-
/** Object type (`og:type`)
|
|
400
|
+
/** Object type (`og:type`) - `"website"`, `"article"`, … Default `"website"`. */
|
|
284
401
|
readonly type?: string;
|
|
285
402
|
}
|
|
286
403
|
/**
|
|
@@ -291,7 +408,7 @@ export interface OpenGraphInput {
|
|
|
291
408
|
* export const meta = { meta: [...openGraph({ title: "Nifra", image: "https://site.com/og.png" })] }
|
|
292
409
|
* ```
|
|
293
410
|
*/
|
|
294
|
-
export declare function openGraph(input: OpenGraphInput):
|
|
411
|
+
export declare function openGraph(input: OpenGraphInput): MetaDescriptor[];
|
|
295
412
|
/**
|
|
296
413
|
* Build a JSON-LD `<script type="application/ld+json">` entry for a route's `meta.script` from a plain
|
|
297
414
|
* object. `JSON.stringify` produces the body; the head renderer breakout-escapes it (see
|
|
@@ -304,6 +421,14 @@ export declare function openGraph(input: OpenGraphInput): Array<Record<string, s
|
|
|
304
421
|
* ```
|
|
305
422
|
*/
|
|
306
423
|
export declare function jsonLd(data: Record<string, unknown>): ScriptDescriptor;
|
|
424
|
+
/**
|
|
425
|
+
* Deliberately unsafe escape hatch for executable inline code. The required nonce keeps the result
|
|
426
|
+
* compatible with a strict CSP and makes the security-sensitive choice visible at the call site.
|
|
427
|
+
*/
|
|
428
|
+
export declare function unsafeInlineScript(content: string, options: {
|
|
429
|
+
readonly nonce: string;
|
|
430
|
+
readonly type?: "module" | "text/javascript";
|
|
431
|
+
}): UnsafeScriptDescriptor;
|
|
307
432
|
/**
|
|
308
433
|
* Merge a route's `<head>` contributions from its layout chain + the page into one {@link Meta}.
|
|
309
434
|
*
|
|
@@ -311,15 +436,15 @@ export declare function jsonLd(data: Record<string, unknown>): ScriptDescriptor;
|
|
|
311
436
|
* `meta` to put sitewide tags (`hreflang`, `preconnect`, a default `<title>`, …) on every page below
|
|
312
437
|
* it. `heads` is passed **outermost layout → … → innermost layout → page** (the same order as the
|
|
313
438
|
* render chain), and merges:
|
|
314
|
-
* - **`title`**
|
|
439
|
+
* - **`title`** - *nearest-wins*: the last defined value across the list, so the page overrides an
|
|
315
440
|
* inner layout, which overrides an outer one. A layout `title` is the section default; an undefined
|
|
316
441
|
* page `title` keeps it.
|
|
317
|
-
* - **`meta` / `link`**
|
|
442
|
+
* - **`meta` / `link`** - *concatenated* in list order (outermost first, page last). Duplicate-tag
|
|
318
443
|
* de-duplication is the caller's concern; the framework emits exactly what's declared so a layout
|
|
319
444
|
* can ship N `<link rel="alternate" hreflang>` tags and a page can add its own canonical.
|
|
320
445
|
*
|
|
321
446
|
* Returns a fresh object whose identity is stable per `heads` *content* only when every entry is a
|
|
322
|
-
* static (by-reference) `Meta` and there is exactly one
|
|
447
|
+
* static (by-reference) `Meta` and there is exactly one - otherwise a new object each call. That is
|
|
323
448
|
* fine: {@link headTags}'s memo is keyed on identity, so a per-request merge simply recomputes (its
|
|
324
449
|
* content can vary with loader data anyway).
|
|
325
450
|
*/
|
|
@@ -331,7 +456,7 @@ export interface CreateWebAppOptions {
|
|
|
331
456
|
readonly clientEntry: string;
|
|
332
457
|
/** Default document title for all pages. */
|
|
333
458
|
readonly title?: string;
|
|
334
|
-
/** Injected into each loader's `ctx.api`
|
|
459
|
+
/** Injected into each loader's `ctx.api` - typically an `inProcessClient(app)` (typed
|
|
335
460
|
* per-route via `@nifrajs/client`'s `createRoutes`). Opaque to the core.
|
|
336
461
|
*
|
|
337
462
|
* **Auto-mount.** Every `inProcessClient(backend)` exposes the symbol-keyed platform-aware backend
|
|
@@ -347,19 +472,48 @@ export interface CreateWebAppOptions {
|
|
|
347
472
|
* auto-mount entirely (the app serves pages only and `api` stays a loader-only `ctx.api`). Mounting
|
|
348
473
|
* is also a no-op when `api` does not expose the symbol mount. */
|
|
349
474
|
readonly apiPrefix?: string;
|
|
475
|
+
/**
|
|
476
|
+
* Strip {@link apiPrefix} from the pathname before dispatching to `api` (default `false`).
|
|
477
|
+
*
|
|
478
|
+
* The default suits a backend that declares FULL paths (`server().post("/api/sync", …)`), which is
|
|
479
|
+
* right when it is only ever mounted here. Set this when the backend declares its routes WITHOUT the
|
|
480
|
+
* prefix because it also runs standalone behind its own shell, so its paths cannot carry a prefix that
|
|
481
|
+
* only exists when it is mounted. Without it every request 404s inside the backend, and the workaround
|
|
482
|
+
* is a `Proxy` that rewrites each URL.
|
|
483
|
+
*/
|
|
484
|
+
readonly apiStrip?: boolean;
|
|
485
|
+
/**
|
|
486
|
+
* Sub-apps mounted ahead of page routing - an auth handler, a webhook receiver, a stack's routes.
|
|
487
|
+
*
|
|
488
|
+
* Structural on purpose: anything with `{ path, app: { fetch } }` fits, so a library that exposes its
|
|
489
|
+
* routes as such a list mounts as `mounts: theirRoutes` without `@nifrajs/web` taking a dependency on
|
|
490
|
+
* it. `better-auth` is the motivating case - it is not a `backend` route, so `/api/auth/*` used to 404
|
|
491
|
+
* silently.
|
|
492
|
+
*
|
|
493
|
+
* Tried longest-path-first and BEFORE the `api` mount, so a mount at `/api/auth` wins over a backend
|
|
494
|
+
* at `/api` no matter which was declared first. `stripPrefix` is the per-mount form of {@link apiStrip}:
|
|
495
|
+
* leave it off to pass the full path through.
|
|
496
|
+
*/
|
|
497
|
+
readonly mounts?: ReadonlyArray<{
|
|
498
|
+
readonly path: string;
|
|
499
|
+
readonly app: {
|
|
500
|
+
fetch(request: Request): Response | Promise<Response>;
|
|
501
|
+
};
|
|
502
|
+
readonly stripPrefix?: boolean;
|
|
503
|
+
}>;
|
|
350
504
|
/** Secret for **draft / preview mode** (see `enableDraft`). When set, a request carrying a valid
|
|
351
505
|
* signed `__nifra_draft` cookie gets `ctx.draft === true` in loaders/actions (else always `false`).
|
|
352
506
|
* Pair with `withISR({ draftSecret })` so editors bypass the cache. Omit to disable draft mode. */
|
|
353
507
|
readonly draftSecret?: string;
|
|
354
|
-
/** Per-route chunk URLs (`buildClient`'s `BuildManifest.routes`)
|
|
508
|
+
/** Per-route chunk URLs (`buildClient`'s `BuildManifest.routes`) - `routeId → [layout chunks…, own
|
|
355
509
|
* chunk]`. When present, each page `modulepreload`s its matched route's chunks alongside the entry,
|
|
356
510
|
* so the route code downloads in parallel (no entry→route-chunk waterfall). Omit ⇒ entry-only. */
|
|
357
511
|
readonly routePreload?: Readonly<Record<string, readonly string[]>>;
|
|
358
|
-
/** The app's bundled stylesheet URLs (`buildClient`'s `BuildManifest.css`)
|
|
512
|
+
/** The app's bundled stylesheet URLs (`buildClient`'s `BuildManifest.css`) - the aggregate, injected
|
|
359
513
|
* as `<link rel="stylesheet">` in a page's `<head>`. Used as the fallback for any route absent from
|
|
360
514
|
* {@link routeStyles}. Omit ⇒ no links (dev, where Vite injects CSS, or a CSS-free app). */
|
|
361
515
|
readonly styles?: readonly string[];
|
|
362
|
-
/** Per-route stylesheet URLs (`buildClient`'s `BuildManifest.routeStyles`)
|
|
516
|
+
/** Per-route stylesheet URLs (`buildClient`'s `BuildManifest.routeStyles`) - `routeId → [chain CSS]`.
|
|
363
517
|
* When a matched route has an entry here, only those (its layout chain + own CSS) are linked instead
|
|
364
518
|
* of the aggregate `styles`, so a page ships only the CSS it uses. An empty array ⇒ no `<link>` (the
|
|
365
519
|
* page imports no CSS). Routes absent here fall back to `styles`. Omit ⇒ always use `styles`. */
|
|
@@ -370,14 +524,14 @@ export interface CreateWebAppOptions {
|
|
|
370
524
|
readonly prerenderedPaths?: readonly string[];
|
|
371
525
|
/** SSG: per dynamic route pattern, its `getStaticPaths` `fallback` (from `enumerateStaticRoutes` or
|
|
372
526
|
* the build's `prerendered.json`). A route mapped to `"404"` rejects any path NOT in
|
|
373
|
-
* `prerenderedPaths` with the 404 page
|
|
527
|
+
* `prerenderedPaths` with the 404 page - the unlisted path simply doesn't exist. `"ssr"` (the
|
|
374
528
|
* default for unmapped routes) renders unlisted paths on-demand. */
|
|
375
529
|
readonly staticFallbacks?: Readonly<Record<string, "ssr" | "404">>;
|
|
376
|
-
/** Observe every loader/action failure
|
|
530
|
+
/** Observe every loader/action failure - for error-reporting plugins (Sentry-style). Called for
|
|
377
531
|
* real throws (not control-flow `Response`s like `redirect`), **before** the nearest `_error`
|
|
378
|
-
* boundary renders / a soft-nav 500 / a rethrow
|
|
532
|
+
* boundary renders / a soft-nav 500 / a rethrow - so it sees errors that the boundary would
|
|
379
533
|
* otherwise hide. Observation only; its own throws are swallowed so a faulty reporter can't break
|
|
380
|
-
* rendering. (`beforeLoader` is intentionally omitted
|
|
534
|
+
* rendering. (`beforeLoader` is intentionally omitted - the core HTTP hooks already cover
|
|
381
535
|
* pre-request work.) */
|
|
382
536
|
readonly onLoaderError?: (error: unknown, ctx: {
|
|
383
537
|
readonly request: Request;
|
|
@@ -388,21 +542,29 @@ export interface CreateWebAppOptions {
|
|
|
388
542
|
/**
|
|
389
543
|
* Build a nifra app from a route manifest: every route SSRs its layout chain via `renderPage`,
|
|
390
544
|
* and a wildcard catch-all renders `_404` (or a plain 404). Reuses @nifrajs/core's router +
|
|
391
|
-
* lifecycle, so matching, params, and precedence are battle-tested. fs-free
|
|
545
|
+
* lifecycle, so matching, params, and precedence are battle-tested. fs-free - feed it a
|
|
392
546
|
* manifest from `discoverRoutes` (`@nifrajs/web/fs`) at startup, so the served app stays portable.
|
|
393
547
|
*
|
|
394
|
-
* **Typed platform bindings.** Pass `Env`
|
|
548
|
+
* **Typed platform bindings.** Pass `Env` - `createWebApp<Env>({ … })` - to declare the app's Workers
|
|
395
549
|
* bindings ONCE. It seeds the returned `Server`'s context with `{ env: Env }` (exactly as the backend's
|
|
396
550
|
* `server<Env>()` does), so `app.fetch(req, { env })` / `toFetchHandler(app)` type-check the `env`
|
|
397
|
-
* argument against the declared shape
|
|
551
|
+
* argument against the declared shape - no per-binding cast at the edge entry. Per-route loaders/actions
|
|
398
552
|
* stay typed independently of this call: annotate them with `@nifrajs/client`'s `LoaderArgs<Api, Env>`
|
|
399
|
-
* (same `Env`) so `ctx.env.MY_KV` is typed there too. Omit the parameter and `Env` is `unknown`
|
|
553
|
+
* (same `Env`) so `ctx.env.MY_KV` is typed there too. Omit the parameter and `Env` is `unknown` - the
|
|
400
554
|
* secure default; validate at the trust boundary before use.
|
|
401
555
|
*/
|
|
402
556
|
export declare function createWebApp<Env = unknown>(options: CreateWebAppOptions): ReturnType<typeof server<Env>>;
|
|
403
557
|
export interface GenerateClientEntryOptions {
|
|
404
|
-
/**
|
|
405
|
-
* `"@nifrajs/web-solid/client"`.
|
|
558
|
+
/**
|
|
559
|
+
* Module specifier for the adapter's client runtime, e.g. `"@nifrajs/web-solid/client"`.
|
|
560
|
+
*
|
|
561
|
+
* **Contract:** the module MUST export `mountRouter({ router, routes, container })`. The generated
|
|
562
|
+
* bootstrap imports the specifier and calls it - so a self-executing entry that mounts on import
|
|
563
|
+
* builds cleanly and then does nothing, which is the trap this contract exists to name. The type is
|
|
564
|
+
* a bare `string` because a specifier is resolved by the bundler, not the type system, so the
|
|
565
|
+
* requirement is enforced by the bootstrap instead: it throws immediately, naming this module and
|
|
566
|
+
* the missing export, rather than failing later as `mountRouter is not a function`.
|
|
567
|
+
*/
|
|
406
568
|
readonly clientModule: string;
|
|
407
569
|
/** Turn a route/layout source file (relative to the routes dir) into an import specifier. */
|
|
408
570
|
readonly resolve: (file: string) => string;
|
|
@@ -418,36 +580,66 @@ export interface GenerateClientEntryOptions {
|
|
|
418
580
|
* bundle it with `buildClient` / `Bun.build` (+ the adapter's transform).
|
|
419
581
|
*/
|
|
420
582
|
export declare function generateClientEntry(manifest: Manifest, options: GenerateClientEntryOptions): string;
|
|
583
|
+
export interface GenerateRouteSearchTypesOptions {
|
|
584
|
+
/** Turn a route source file into a module specifier for a `typeof import(...)` type - the same contract
|
|
585
|
+
* as `generateClientEntry`'s `resolve`, but WITHOUT a file extension (a `.d.ts` imports a module, not a
|
|
586
|
+
* path). Relative to wherever the emitted `.d.ts` is written. */
|
|
587
|
+
readonly resolve: (file: string) => string;
|
|
588
|
+
/** The module whose `RouteSearch` interface is augmented (default `"@nifrajs/web"`). Every adapter's
|
|
589
|
+
* `NavigateTarget` reads `@nifrajs/web`'s `RouteSearch`, so the default types `navigate` on all of them. */
|
|
590
|
+
readonly module?: string;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Codegen: emit a `.d.ts` that types cross-route `navigate({ to, search })` against each route's
|
|
594
|
+
* `searchSchema`. It augments the {@link RouteSearch} map with `"<pattern>": SearchOf<typeof import(...)>`
|
|
595
|
+
* for every **static** route (one with no `:param`/`*` segment - a concrete `to` a caller can pass; a
|
|
596
|
+
* dynamic route's concrete path can't match its pattern key, so those keep the loose object form). Written
|
|
597
|
+
* by `nifra sync-routes` next to the route tree and included in the app's `tsconfig`; because it is
|
|
598
|
+
* regenerated from the route files, a `navigate` whose `search` no longer matches the route's schema
|
|
599
|
+
* becomes a `tsc` error - the same drift guarantee as nifra's typed client, no hand-maintained map.
|
|
600
|
+
*/
|
|
601
|
+
export declare function generateRouteSearchTypes(manifest: Manifest, options: GenerateRouteSearchTypesOptions): string;
|
|
421
602
|
export interface GenerateServerManifestOptions {
|
|
422
|
-
/** Turn a route/layout source file (relative to the routes dir) into an import specifier
|
|
603
|
+
/** Turn a route/layout source file (relative to the routes dir) into an import specifier -
|
|
423
604
|
* same contract as `generateClientEntry`'s `resolve`. */
|
|
424
605
|
readonly resolve: (file: string) => string;
|
|
425
606
|
/** The content-hashed client entry URL (from `buildClient`'s manifest), **baked** into the emitted
|
|
426
|
-
* module
|
|
607
|
+
* module - a disk-less worker can't read `manifest.json` at runtime. */
|
|
427
608
|
readonly clientEntry: string;
|
|
428
|
-
/** The app's aggregate stylesheet URLs (`buildClient`'s `BuildManifest.css`)
|
|
609
|
+
/** The app's aggregate stylesheet URLs (`buildClient`'s `BuildManifest.css`) - baked as
|
|
429
610
|
* `export const styles` so the server entry can hand them to `createWebApp` (→ `<link rel="stylesheet">`
|
|
430
611
|
* in `<head>`). Without this the built SSR page ships no CSS link and renders unstyled. */
|
|
431
612
|
readonly styles?: readonly string[] | undefined;
|
|
432
|
-
/** Per-route stylesheet URLs (`buildClient`'s `BuildManifest.routeStyles`)
|
|
613
|
+
/** Per-route stylesheet URLs (`buildClient`'s `BuildManifest.routeStyles`) - baked as
|
|
433
614
|
* `export const routeStyles` so a page links only the CSS its route chain uses. */
|
|
434
615
|
readonly routeStyles?: Readonly<Record<string, readonly string[]>> | undefined;
|
|
435
616
|
/** Emit **lazy** per-route loaders (`() => import("./routes/x")`, a static specifier) instead of
|
|
436
|
-
* eager `import * as`, so a bundler with code-splitting emits one chunk per route
|
|
617
|
+
* eager `import * as`, so a bundler with code-splitting emits one chunk per route - loaded on the
|
|
437
618
|
* first request to it, not all at boot (smaller cold-start parse). Default `false` (eager). Both
|
|
438
619
|
* modes are fs-free with statically-analyzable specifiers; only the *when* differs. */
|
|
439
620
|
readonly lazy?: boolean;
|
|
440
621
|
}
|
|
441
622
|
/**
|
|
442
623
|
* Codegen: emit a **server manifest** module (as source) for disk-less edge runtimes (Cloudflare
|
|
443
|
-
* Workers, …)
|
|
444
|
-
* and dynamic-imports each route by a *runtime* path
|
|
445
|
-
* **statically-analyzable** imports of every route/layout/`_error
|
|
446
|
-
* rebuilds the manifest with `buildManifest`
|
|
624
|
+
* Workers, …) - and, with a `target`, any portable server bundle. `discoverRoutes` scans `node:fs`
|
|
625
|
+
* and dynamic-imports each route by a *runtime* path - neither exists on workerd. This instead emits
|
|
626
|
+
* **statically-analyzable** imports of every route/layout/`_error`/terminal status page (so the bundler includes them) and
|
|
627
|
+
* rebuilds the manifest with `buildManifest` - the SAME pure logic `discoverRoutes` feeds, so patterns
|
|
447
628
|
* + layout chains are identical. Eager (`import * as`) by default; `lazy` emits `() => import(...)` so
|
|
448
629
|
* a code-splitting bundler chunks per route. The emitted module exports `manifest` (consumed by
|
|
449
630
|
* `createWebApp`, unchanged) + `clientEntry` (baked). Write it to a file and bundle it into the worker
|
|
450
631
|
* entry (see `buildServer` in `@nifrajs/web/build`).
|
|
451
632
|
*/
|
|
452
633
|
export declare function generateServerManifest(manifest: Manifest, options: GenerateServerManifestOptions): string;
|
|
634
|
+
/**
|
|
635
|
+
* The two file-name conventions that decide what never reaches a browser: a `*.server` module is
|
|
636
|
+
* EMPTIED in the client build, a `*.fn` module is REPLACED with client stubs.
|
|
637
|
+
*
|
|
638
|
+
* Exported because more than the bundlers need them. `nifra dev --bun` cannot transform (Bun's dev
|
|
639
|
+
* bundler takes no plugins) so it must REFUSE instead, and a refusal driven by its own hand-written
|
|
640
|
+
* glob drifts from the transform - which is how `.fn.mts` came to be stubbed by both build pipelines
|
|
641
|
+
* and waved through by the guard that exists to stop it leaking. Anything deciding "is this module
|
|
642
|
+
* server-only" should import the matcher rather than re-encode it.
|
|
643
|
+
*/
|
|
644
|
+
export { SERVER_FN_MODULE, SERVER_ONLY_MODULE } from "./internal/server-boundary.js";
|
|
453
645
|
//# sourceMappingURL=index.d.ts.map
|