@nifrajs/web-react 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/await.d.ts +2 -2
- package/dist/await.js +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +13 -6
- package/dist/client.js.map +1 -1
- package/dist/compose.d.ts +4 -3
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +12 -5
- package/dist/compose.js.map +1 -1
- package/dist/content.d.ts +2 -2
- package/dist/content.js +2 -2
- package/dist/error.d.ts +1 -1
- package/dist/error.js +3 -3
- package/dist/fetcher.d.ts +2 -2
- package/dist/fetcher.js +4 -4
- package/dist/fn.d.ts +29 -0
- package/dist/fn.d.ts.map +1 -0
- package/dist/fn.js +31 -0
- package/dist/fn.js.map +1 -0
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +1 -1
- package/dist/image.d.ts +3 -3
- package/dist/image.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/island.d.ts +3 -3
- package/dist/island.js +2 -2
- package/dist/query.d.ts +8 -8
- package/dist/query.js +5 -5
- package/dist/react-dom-server.d.ts +28 -7
- package/dist/react-dom-server.d.ts.map +1 -1
- package/dist/react-dom-server.js +69 -8
- package/dist/react-dom-server.js.map +1 -1
- package/dist/router.d.ts +66 -18
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +80 -15
- package/dist/router.js.map +1 -1
- package/package.json +11 -6
- package/src/await.ts +2 -2
- package/src/client.ts +13 -7
- package/src/compose.ts +17 -5
- package/src/content.ts +2 -2
- package/src/error.ts +3 -3
- package/src/fetcher.ts +4 -4
- package/src/fn.ts +43 -0
- package/src/i18n.ts +1 -1
- package/src/image.ts +3 -3
- package/src/index.ts +7 -7
- package/src/island.ts +3 -3
- package/src/query.ts +9 -9
- package/src/react-dom-server.ts +83 -10
- package/src/router.ts +114 -24
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @nifrajs/web-react
|
|
2
2
|
|
|
3
|
-
React render adapter for @nifrajs/web
|
|
3
|
+
React render adapter for @nifrajs/web - SSR + hydration (Bun-native JSX, no Babel).
|
|
4
4
|
|
|
5
|
-
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework
|
|
5
|
+
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@ MIT
|
|
|
19
19
|
|
|
20
20
|
## For AI agents
|
|
21
21
|
|
|
22
|
-
Start with [`LLM.md`](./LLM.md)
|
|
22
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
23
23
|
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
24
24
|
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
25
25
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
package/dist/await.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Deferred } from "@nifrajs/web";
|
|
2
2
|
/**
|
|
3
|
-
* `@nifrajs/web-react/await`
|
|
3
|
+
* `@nifrajs/web-react/await` - the `<Await>` primitive for deferred loader data (`defer()`). Isomorphic
|
|
4
4
|
* and imports only `react` + a type from `@nifrajs/web` (never `react-dom/server`), so route components
|
|
5
5
|
* can use it without dragging server code into the client bundle. No JSX (the package builds with
|
|
6
6
|
* plain `tsc`).
|
|
@@ -8,7 +8,7 @@ import type { Deferred } from "@nifrajs/web";
|
|
|
8
8
|
* The server streams the boundary: `use(promise)` suspends, React flushes the fallback then the
|
|
9
9
|
* resolved content mid-stream. On the client the registry promise is already settled (the core's
|
|
10
10
|
* streamed `__nifraResolve` script tagged it `fulfilled`), so `use()` returns the value synchronously
|
|
11
|
-
*
|
|
11
|
+
* - React hydrates the server-revealed boundary's content directly (no re-fetch, no fallback flash).
|
|
12
12
|
*/
|
|
13
13
|
import { type ReactNode } from "react";
|
|
14
14
|
export interface AwaitProps<T> {
|
package/dist/await.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/await`
|
|
2
|
+
* `@nifrajs/web-react/await` - the `<Await>` primitive for deferred loader data (`defer()`). Isomorphic
|
|
3
3
|
* and imports only `react` + a type from `@nifrajs/web` (never `react-dom/server`), so route components
|
|
4
4
|
* can use it without dragging server code into the client bundle. No JSX (the package builds with
|
|
5
5
|
* plain `tsc`).
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* The server streams the boundary: `use(promise)` suspends, React flushes the fallback then the
|
|
8
8
|
* resolved content mid-stream. On the client the registry promise is already settled (the core's
|
|
9
9
|
* streamed `__nifraResolve` script tagged it `fulfilled`), so `use()` returns the value synchronously
|
|
10
|
-
*
|
|
10
|
+
* - React hydrates the server-revealed boundary's content directly (no re-fetch, no fallback flash).
|
|
11
11
|
*/
|
|
12
12
|
import { Component, createElement, Suspense, use } from "react";
|
|
13
13
|
// Catches the rejection `use()` throws when the deferred rejects → renders `errorFallback`. A class
|
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MountRouterOptions, type RenderProps } from "@nifrajs/web";
|
|
2
2
|
export { errorBoundary } from "./error.js";
|
|
3
3
|
/** Hydrate a server-rendered React layout `chain` (with the loader `props`) inside `container`. */
|
|
4
4
|
export declare function hydrate(chain: readonly unknown[], props: RenderProps, container: unknown): void;
|
|
5
5
|
/**
|
|
6
6
|
* Hydrate a stateful React Router. `useSyncExternalStore` subscribes to the agnostic store and
|
|
7
|
-
* re-renders the matched layout chain on each store change
|
|
7
|
+
* re-renders the matched layout chain on each store change - so client navigations swap routes
|
|
8
8
|
* without a full reload. `getServerSnapshot` (3rd arg) returns the initial state, matching the
|
|
9
9
|
* SSR markup on hydration.
|
|
10
10
|
*/
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAiB,MAAM,cAAc,CAAA;AAcvF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,mGAAmG;AACnG,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAE/F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAyB7D"}
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { searchOfChain } from "@nifrajs/web";
|
|
1
2
|
/**
|
|
2
|
-
* @nifrajs/web-react/client
|
|
3
|
+
* @nifrajs/web-react/client - React client runtime. `hydrate` hydrates a single SSR'd route;
|
|
3
4
|
* `mountRouter` hydrates a stateful Router that subscribes to the agnostic store (via
|
|
4
5
|
* `useSyncExternalStore`) and re-renders the matched chain on every client navigation (no full
|
|
5
6
|
* reload). Kept in its own entry so server code stays out of the client bundle.
|
|
@@ -8,7 +9,7 @@ import { createElement, useSyncExternalStore } from "react";
|
|
|
8
9
|
import { hydrateRoot } from "react-dom/client";
|
|
9
10
|
import { compose } from "./compose.js";
|
|
10
11
|
import { setMountedRouter } from "./fetcher.js";
|
|
11
|
-
// The `_error` boundary chain element
|
|
12
|
+
// The `_error` boundary chain element - defined in its own (react-dom-free) module, re-exported here so
|
|
12
13
|
// nifra's client codegen resolves it from `@nifrajs/web-react/client` alongside `mountRouter`.
|
|
13
14
|
export { errorBoundary } from "./error.js";
|
|
14
15
|
/** Hydrate a server-rendered React layout `chain` (with the loader `props`) inside `container`. */
|
|
@@ -17,25 +18,31 @@ export function hydrate(chain, props, container) {
|
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* Hydrate a stateful React Router. `useSyncExternalStore` subscribes to the agnostic store and
|
|
20
|
-
* re-renders the matched layout chain on each store change
|
|
21
|
+
* re-renders the matched layout chain on each store change - so client navigations swap routes
|
|
21
22
|
* without a full reload. `getServerSnapshot` (3rd arg) returns the initial state, matching the
|
|
22
23
|
* SSR markup on hydration.
|
|
23
24
|
*/
|
|
24
25
|
export function mountRouter(options) {
|
|
25
|
-
const { router, routes, container } = options;
|
|
26
|
+
const { router, routes, searchSchemas, container } = options;
|
|
26
27
|
setMountedRouter(router); // expose it to useFetcher/useFetchers (same page, client-only)
|
|
27
28
|
const Router = () => {
|
|
28
29
|
const state = useSyncExternalStore(router.subscribe, router.snapshot, router.snapshot);
|
|
30
|
+
// Derive this route's typed `search` from the URL query + the route's schema CHAIN (layout schemas +
|
|
31
|
+
// page, merged page-wins), the SAME `searchOfChain` the server ran, so `useSearch` reads an identical
|
|
32
|
+
// value and hydrates with no drift.
|
|
33
|
+
const q = state.path.indexOf("?");
|
|
34
|
+
const rawSearch = q === -1 ? "" : state.path.slice(q);
|
|
29
35
|
return compose(routes[state.routeId] ?? [], {
|
|
30
36
|
data: state.data,
|
|
31
37
|
actionData: state.actionData,
|
|
32
38
|
pending: state.pending,
|
|
33
39
|
...(state.pendingPath !== undefined ? { pendingPath: state.pendingPath } : {}),
|
|
34
|
-
// `params`/`path` feed the routing hooks (useParams/useLocation) via compose's RouterContext
|
|
40
|
+
// `params`/`path` feed the routing hooks (useParams/useLocation) via compose's RouterContext -
|
|
35
41
|
// sourced from router state here, matching the SSR render's request-derived values on hydration.
|
|
36
42
|
params: state.params,
|
|
37
43
|
path: state.path,
|
|
38
|
-
|
|
44
|
+
search: searchOfChain(searchSchemas?.[state.routeId] ?? [], rawSearch),
|
|
45
|
+
// The in-flight submission (for optimistic UI) - spread only when present.
|
|
39
46
|
...(state.submission ? { submission: state.submission } : {}),
|
|
40
47
|
});
|
|
41
48
|
};
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6C,aAAa,EAAE,MAAM,cAAc,CAAA;AACvF;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAA0B,oBAAoB,EAAE,MAAM,OAAO,CAAA;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,wGAAwG;AACxG,+FAA+F;AAC/F,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,mGAAmG;AACnG,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB,EAAE,SAAkB;IACvF,WAAW,CAAC,SAAoB,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;AAC1D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAC5D,gBAAgB,CAAC,MAAM,CAAC,CAAA,CAAC,+DAA+D;IACxF,MAAM,MAAM,GAAsB,GAAG,EAAE;QACrC,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;QACtF,qGAAqG;QACrG,sGAAsG;QACtG,oCAAoC;QACpC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACjC,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACrD,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE;YAC1C,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9E,+FAA+F;YAC/F,iGAAiG;YACjG,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,aAAa,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,SAAS,CAAC;YACtE,2EAA2E;YAC3E,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC,CAAA;IACJ,CAAC,CAAA;IACD,WAAW,CAAC,SAAoB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;AAC1D,CAAC"}
|
package/dist/compose.d.ts
CHANGED
|
@@ -6,9 +6,10 @@ import { type ReactNode } from "react";
|
|
|
6
6
|
* `children`. Shared by the server adapter (renderToString) and the client (hydrateRoot).
|
|
7
7
|
*
|
|
8
8
|
* The whole tree is wrapped in a {@link RouterContext} provider carrying the matched `params`, the
|
|
9
|
-
* current `path`, and the client `pending` flag (all threaded through
|
|
10
|
-
* and client), so the routing hooks
|
|
11
|
-
*
|
|
9
|
+
* current `path`, the validated `search`, and the client `pending` flag (all threaded through
|
|
10
|
+
* `RenderProps` identically on SSR and client), so the routing hooks
|
|
11
|
+
* (`useParams`/`useLocation`/`useSearch`/`useNavigation`) read the same value on both sides - no
|
|
12
|
+
* hydration mismatch (`pending` is `false` on SSR and on the initial client render).
|
|
12
13
|
* A render with no routing fields (a non-router adapter usage) provides the empty default.
|
|
13
14
|
*/
|
|
14
15
|
export declare function compose(chain: readonly unknown[], props: RenderProps): ReactNode;
|
package/dist/compose.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAyC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAyC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAS7E;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,SAAS,CA2BhF"}
|
package/dist/compose.js
CHANGED
|
@@ -2,28 +2,35 @@ import { createElement } from "react";
|
|
|
2
2
|
import { RouterContext } from "./router.js";
|
|
3
3
|
// Stable empty params so the provider value's `params` has a fixed reference when a route has none.
|
|
4
4
|
const EMPTY_PARAMS = Object.freeze({});
|
|
5
|
+
// Stable empty search for the same reason: a render with no search context (or a non-router usage).
|
|
6
|
+
const EMPTY_SEARCH = Object.freeze({});
|
|
5
7
|
/**
|
|
6
8
|
* Fold a layout chain (outermost layout → page) into a single React tree: the page
|
|
7
9
|
* (innermost) receives `props` (the loader data); each layout wraps the child via its
|
|
8
10
|
* `children`. Shared by the server adapter (renderToString) and the client (hydrateRoot).
|
|
9
11
|
*
|
|
10
12
|
* The whole tree is wrapped in a {@link RouterContext} provider carrying the matched `params`, the
|
|
11
|
-
* current `path`, and the client `pending` flag (all threaded through
|
|
12
|
-
* and client), so the routing hooks
|
|
13
|
-
*
|
|
13
|
+
* current `path`, the validated `search`, and the client `pending` flag (all threaded through
|
|
14
|
+
* `RenderProps` identically on SSR and client), so the routing hooks
|
|
15
|
+
* (`useParams`/`useLocation`/`useSearch`/`useNavigation`) read the same value on both sides - no
|
|
16
|
+
* hydration mismatch (`pending` is `false` on SSR and on the initial client render).
|
|
14
17
|
* A render with no routing fields (a non-router adapter usage) provides the empty default.
|
|
15
18
|
*/
|
|
16
19
|
export function compose(chain, props) {
|
|
17
20
|
const last = chain.length - 1;
|
|
18
21
|
let node = createElement(chain[last], props);
|
|
19
22
|
for (let i = last - 1; i >= 0; i--) {
|
|
20
|
-
// children passed as the 3rd arg (not a `children` prop)
|
|
21
|
-
|
|
23
|
+
// children passed as the 3rd arg (not a `children` prop) - React's canonical form.
|
|
24
|
+
// Each layout receives its own loader data at its own index. Layouts are the chain's leading
|
|
25
|
+
// prefix, so `layoutData[i]` belongs to `chain[i]`; anything past that end (a client-only `_error`
|
|
26
|
+
// boundary marker, the page) reads `undefined` and is unaffected.
|
|
27
|
+
node = createElement(chain[i], { data: props.layoutData?.[i] ?? null }, node);
|
|
22
28
|
}
|
|
23
29
|
return createElement(RouterContext.Provider, {
|
|
24
30
|
value: {
|
|
25
31
|
params: props.params ?? EMPTY_PARAMS,
|
|
26
32
|
path: props.path ?? "",
|
|
33
|
+
search: props.search ?? EMPTY_SEARCH,
|
|
27
34
|
pending: props.pending ?? false,
|
|
28
35
|
pendingPath: props.pendingPath,
|
|
29
36
|
},
|
package/dist/compose.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAA0C,MAAM,OAAO,CAAA;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,oGAAoG;AACpG,MAAM,YAAY,GAAqC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAExE
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAA0C,MAAM,OAAO,CAAA;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,oGAAoG;AACpG,MAAM,YAAY,GAAqC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAExE,oGAAoG;AACpG,MAAM,YAAY,GAAsC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAEzE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,IAAI,IAAI,GAAc,aAAa,CAAC,KAAK,CAAC,IAAI,CAAmC,EAAE,KAAK,CAAC,CAAA;IACzF,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,mFAAmF;QACnF,6FAA6F;QAC7F,mGAAmG;QACnG,kEAAkE;QAClE,IAAI,GAAG,aAAa,CAClB,KAAK,CAAC,CAAC,CAAyC,EAChD,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EACvC,IAAI,CACL,CAAA;IACH,CAAC;IACD,OAAO,aAAa,CAClB,aAAa,CAAC,QAAQ,EACtB;QACE,KAAK,EAAE;YACL,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,YAAY;YACpC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;YACtB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,YAAY;YACpC,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,KAAK;YAC/B,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B;KACF,EACD,IAAI,CACL,CAAA;AACH,CAAC"}
|
package/dist/content.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/content`
|
|
2
|
+
* `@nifrajs/web-react/content` - render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
|
|
3
3
|
* `.html`, or any Markdown→HTML string) into the DOM via `dangerouslySetInnerHTML`. Pairs with content
|
|
4
4
|
* collections: `<Content html={entry.html} />`.
|
|
5
5
|
*
|
|
6
6
|
* Security: this injects raw HTML, so the `html` MUST be trusted (your own Markdown/content, rendered
|
|
7
|
-
* at build/server time)
|
|
7
|
+
* at build/server time) - never pass user-supplied HTML without sanitizing it first.
|
|
8
8
|
*/
|
|
9
9
|
import { type ComponentPropsWithoutRef, type ElementType, type ReactElement } from "react";
|
|
10
10
|
export interface ContentProps extends Omit<ComponentPropsWithoutRef<"div">, "dangerouslySetInnerHTML" | "children"> {
|
package/dist/content.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/content`
|
|
2
|
+
* `@nifrajs/web-react/content` - render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
|
|
3
3
|
* `.html`, or any Markdown→HTML string) into the DOM via `dangerouslySetInnerHTML`. Pairs with content
|
|
4
4
|
* collections: `<Content html={entry.html} />`.
|
|
5
5
|
*
|
|
6
6
|
* Security: this injects raw HTML, so the `html` MUST be trusted (your own Markdown/content, rendered
|
|
7
|
-
* at build/server time)
|
|
7
|
+
* at build/server time) - never pass user-supplied HTML without sanitizing it first.
|
|
8
8
|
*/
|
|
9
9
|
import { createElement, } from "react";
|
|
10
10
|
/** Render trusted HTML into a wrapper element. Extra props (`className`, `id`, `style`, …) pass through. */
|
package/dist/error.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Build an error-boundary chain element bound to `fallback` (a route's `_error` component). nifra's
|
|
3
3
|
* client codegen inserts it before the page in the matched chain; a render error in the subtree renders
|
|
4
4
|
* `fallback` with `{ data: { name, message } }` instead of crashing the app. DOM-transparent (it renders
|
|
5
|
-
* its children directly
|
|
5
|
+
* its children directly - no wrapper element), so it adds no markup and never disturbs hydration.
|
|
6
6
|
*/
|
|
7
7
|
export declare function errorBoundary(fallback: unknown): unknown;
|
|
8
8
|
//# sourceMappingURL=error.d.ts.map
|
package/dist/error.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `errorBoundary`
|
|
2
|
+
* `errorBoundary` - the React error-boundary chain element for nifra's `_error.tsx`. Kept in its own
|
|
3
3
|
* module (imports only `react`, never `react-dom/client`) so it's unit-testable off the DOM and the
|
|
4
4
|
* client codegen can import it from `@nifrajs/web-react/client` (which re-exports it).
|
|
5
5
|
*/
|
|
@@ -8,7 +8,7 @@ import { Component, createElement } from "react";
|
|
|
8
8
|
* Build an error-boundary chain element bound to `fallback` (a route's `_error` component). nifra's
|
|
9
9
|
* client codegen inserts it before the page in the matched chain; a render error in the subtree renders
|
|
10
10
|
* `fallback` with `{ data: { name, message } }` instead of crashing the app. DOM-transparent (it renders
|
|
11
|
-
* its children directly
|
|
11
|
+
* its children directly - no wrapper element), so it adds no markup and never disturbs hydration.
|
|
12
12
|
*/
|
|
13
13
|
export function errorBoundary(fallback) {
|
|
14
14
|
return class NifraErrorBoundary extends Component {
|
|
@@ -23,7 +23,7 @@ export function errorBoundary(fallback) {
|
|
|
23
23
|
const { error } = this.state;
|
|
24
24
|
if (error === null)
|
|
25
25
|
return this.props.children;
|
|
26
|
-
// `fallback` is the route's `_error` default export
|
|
26
|
+
// `fallback` is the route's `_error` default export - opaque to the core, a component here.
|
|
27
27
|
const Fallback = fallback;
|
|
28
28
|
return createElement(Fallback, { data: { name: error.name, message: error.message } });
|
|
29
29
|
}
|
package/dist/fetcher.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClientRouter, Fetcher, FetcherState } from "@nifrajs/web";
|
|
2
|
-
/** Register (or clear) the router that owns fetchers
|
|
2
|
+
/** Register (or clear) the router that owns fetchers - called by `mountRouter`. Not for app use. */
|
|
3
3
|
export declare function setMountedRouter(router: ClientRouter | undefined): void;
|
|
4
4
|
/** A fetcher's reactive {@link FetcherState} plus its imperative `load`/`submit`. */
|
|
5
5
|
export interface FetcherHandle extends FetcherState {
|
|
@@ -15,7 +15,7 @@ export interface FetcherHandle extends FetcherState {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function useFetcher(key: string): FetcherHandle;
|
|
17
17
|
/**
|
|
18
|
-
* Subscribe to the whole live fetcher collection
|
|
18
|
+
* Subscribe to the whole live fetcher collection - for a global busy view (e.g. "3 saving…"). Read
|
|
19
19
|
* each entry's `.snapshot()` for its state. Re-renders whenever any fetcher transitions or a new one
|
|
20
20
|
* is created.
|
|
21
21
|
*/
|
package/dist/fetcher.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/fetcher`
|
|
2
|
+
* `@nifrajs/web-react/fetcher` - React bindings for concurrent fetchers. `useFetcher(key)` subscribes a
|
|
3
3
|
* component to an independent {@link Fetcher} (via `useSyncExternalStore`) and returns its reactive
|
|
4
4
|
* state plus `load`/`submit`; `useFetchers()` subscribes to the whole live collection. Imports only
|
|
5
5
|
* `react` (never `react-dom/*`), so route components can use it on the server *and* client without
|
|
6
6
|
* dragging either DOM build into the wrong bundle. No JSX (the package builds with plain `tsc`).
|
|
7
7
|
*
|
|
8
|
-
* The router that owns the fetchers is the one `mountRouter` hydrated
|
|
8
|
+
* The router that owns the fetchers is the one `mountRouter` hydrated - it registers itself here via
|
|
9
9
|
* `setMountedRouter`. On the server (no mount) there is no router, so the hooks return an idle state
|
|
10
10
|
* (fetchers are client-only); the first client render after `mountRouter` sees the real fetcher, so
|
|
11
11
|
* there's no hydration mismatch.
|
|
@@ -14,7 +14,7 @@ import { useSyncExternalStore } from "react";
|
|
|
14
14
|
// The active client router (set by `mountRouter`). Module-scoped: the browser mounts one app per
|
|
15
15
|
// page, and fetchers never exist on the server. Shared with `client.ts`'s `mountRouter`.
|
|
16
16
|
let mountedRouter;
|
|
17
|
-
/** Register (or clear) the router that owns fetchers
|
|
17
|
+
/** Register (or clear) the router that owns fetchers - called by `mountRouter`. Not for app use. */
|
|
18
18
|
export function setMountedRouter(router) {
|
|
19
19
|
mountedRouter = router;
|
|
20
20
|
}
|
|
@@ -35,7 +35,7 @@ export function useFetcher(key) {
|
|
|
35
35
|
return { ...state, load: fetcher?.load ?? noopAsync, submit: fetcher?.submit ?? noopAsync };
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* Subscribe to the whole live fetcher collection
|
|
38
|
+
* Subscribe to the whole live fetcher collection - for a global busy view (e.g. "3 saving…"). Read
|
|
39
39
|
* each entry's `.snapshot()` for its state. Re-renders whenever any fetcher transitions or a new one
|
|
40
40
|
* is created.
|
|
41
41
|
*/
|
package/dist/fn.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-react/fn` - React binding for a server function's pending/error state.
|
|
3
|
+
*
|
|
4
|
+
* Calling a server function needs no hook: the client stub is `(input) => Promise<Output>`. This adds
|
|
5
|
+
* the part a component usually wants around it - in flight, last result, last error - and nothing more.
|
|
6
|
+
*
|
|
7
|
+
* The state machine is `@nifrajs/web`'s `createServerFnStore`, shared with every other adapter, so
|
|
8
|
+
* "is it pending" has one answer rather than five that drift. This file contributes only the `useSyncExternalStore` subscription.
|
|
9
|
+
*/
|
|
10
|
+
import type { ServerFnReference } from "@nifrajs/web/fn";
|
|
11
|
+
import { type ServerFnState } from "@nifrajs/web/fn-state";
|
|
12
|
+
/** A server function's state plus the call itself. */
|
|
13
|
+
export interface ServerFnHandle<Input, Output> extends ServerFnState<Output> {
|
|
14
|
+
/** Invoke it. Records state, and rejects on failure - attach `.catch` if you only render state. */
|
|
15
|
+
readonly call: (input: Input) => Promise<Output>;
|
|
16
|
+
/** Back to idle. */
|
|
17
|
+
readonly reset: () => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Track one server function's call state.
|
|
21
|
+
*
|
|
22
|
+
* const addTodo = useServerFn(fns.addTodo)
|
|
23
|
+
* <button disabled={addTodo.pending} onClick={() => addTodo.call({ text }).catch(() => {})}>
|
|
24
|
+
*
|
|
25
|
+
* The store is per component instance and created once; `fn` is read on the first render only, so a
|
|
26
|
+
* freshly-created inline function neither resets the state nor goes stale.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useServerFn<Input, Output>(fn: ServerFnReference<Input, Output>): ServerFnHandle<Input, Output>;
|
|
29
|
+
//# sourceMappingURL=fn.d.ts.map
|
package/dist/fn.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fn.d.ts","sourceRoot":"","sources":["../src/fn.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAkB,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAG/E,sDAAsD;AACtD,MAAM,WAAW,cAAc,CAAC,KAAK,EAAE,MAAM,CAAE,SAAQ,aAAa,CAAC,MAAM,CAAC;IAC1E,mGAAmG;IACnG,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAChD,oBAAoB;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EACvC,EAAE,EAAE,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,GACnC,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,CAS/B"}
|
package/dist/fn.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-react/fn` - React binding for a server function's pending/error state.
|
|
3
|
+
*
|
|
4
|
+
* Calling a server function needs no hook: the client stub is `(input) => Promise<Output>`. This adds
|
|
5
|
+
* the part a component usually wants around it - in flight, last result, last error - and nothing more.
|
|
6
|
+
*
|
|
7
|
+
* The state machine is `@nifrajs/web`'s `createServerFnStore`, shared with every other adapter, so
|
|
8
|
+
* "is it pending" has one answer rather than five that drift. This file contributes only the `useSyncExternalStore` subscription.
|
|
9
|
+
*/
|
|
10
|
+
import { createServerFnStore } from "@nifrajs/web/fn-state";
|
|
11
|
+
import { useMemo, useRef, useSyncExternalStore } from "react";
|
|
12
|
+
/**
|
|
13
|
+
* Track one server function's call state.
|
|
14
|
+
*
|
|
15
|
+
* const addTodo = useServerFn(fns.addTodo)
|
|
16
|
+
* <button disabled={addTodo.pending} onClick={() => addTodo.call({ text }).catch(() => {})}>
|
|
17
|
+
*
|
|
18
|
+
* The store is per component instance and created once; `fn` is read on the first render only, so a
|
|
19
|
+
* freshly-created inline function neither resets the state nor goes stale.
|
|
20
|
+
*/
|
|
21
|
+
export function useServerFn(fn) {
|
|
22
|
+
// The store is created once, but always calls the CURRENT `fn`. Capturing `fn` directly would
|
|
23
|
+
// either recreate the store on every render (losing the state) or hold the first render's closure
|
|
24
|
+
// (calling a stale one); the ref is what makes "created once" and "never stale" both true.
|
|
25
|
+
const latest = useRef(fn);
|
|
26
|
+
latest.current = fn;
|
|
27
|
+
const store = useMemo(() => createServerFnStore((input) => latest.current(input)), []);
|
|
28
|
+
const state = useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot);
|
|
29
|
+
return { ...state, call: store.call, reset: store.reset };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=fn.js.map
|
package/dist/fn.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fn.js","sourceRoot":"","sources":["../src/fn.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,mBAAmB,EAAsB,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAU7D;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,EAAoC;IAEpC,8FAA8F;IAC9F,kGAAkG;IAClG,2FAA2F;IAC3F,MAAM,MAAM,GAAG,MAAM,CAAC,EAAmC,CAAC,CAAA;IAC1D,MAAM,CAAC,OAAO,GAAG,EAAmC,CAAA;IACpD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC,KAAY,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAC7F,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;IACnF,OAAO,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAA;AAC3D,CAAC"}
|
package/dist/i18n.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/i18n`
|
|
2
|
+
* `@nifrajs/web-react/i18n` - React bindings for `@nifrajs/i18n`. `<I18nProvider locale messages>` builds a
|
|
3
3
|
* `Formatter` (memoized) and provides it; `useT()` reads it. Both `locale` + `messages` are
|
|
4
4
|
* serializable, so a loader returns them, the page renders with the negotiated catalog on the server,
|
|
5
5
|
* and the client rebuilds the same formatter from the same props (no mismatch). Imports only `react` +
|
package/dist/i18n.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/i18n`
|
|
2
|
+
* `@nifrajs/web-react/i18n` - React bindings for `@nifrajs/i18n`. `<I18nProvider locale messages>` builds a
|
|
3
3
|
* `Formatter` (memoized) and provides it; `useT()` reads it. Both `locale` + `messages` are
|
|
4
4
|
* serializable, so a loader returns them, the page renders with the negotiated catalog on the server,
|
|
5
5
|
* and the client rebuilds the same formatter from the same props (no mismatch). Imports only `react` +
|
package/dist/image.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/image`
|
|
2
|
+
* `@nifrajs/web-react/image` - a CLS-safe responsive React `<Image>`. A thin wrapper over
|
|
3
3
|
* `resolveImage` from `@nifrajs/image`: it computes `src`/`srcSet`/`sizes`/`width`/`height`/`loading`/
|
|
4
4
|
* `decoding`/`fetchpriority` and renders a plain `<img>`, forwarding any extra DOM props
|
|
5
5
|
* (`className`, `style`, `id`, event handlers, `data-*`, …). Resizing is delegated to the `loader`
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type ImageLoader, type ImageProps } from "@nifrajs/image";
|
|
9
9
|
import { type ComponentPropsWithoutRef, type ReactElement } from "react";
|
|
10
|
-
/** Attributes `resolveImage` computes
|
|
10
|
+
/** Attributes `resolveImage` computes - callers can't override them via DOM passthrough. */
|
|
11
11
|
type ComputedAttrs = "src" | "srcSet" | "sizes" | "width" | "height" | "alt" | "loading" | "decoding" | "fetchPriority";
|
|
12
12
|
export interface ImageComponentProps extends ImageProps, Omit<ComponentPropsWithoutRef<"img">, ComputedAttrs | keyof ImageProps> {
|
|
13
|
-
/** CDN URL builder. Defaults to the identity loader (no transform
|
|
13
|
+
/** CDN URL builder. Defaults to the identity loader (no transform - still CLS-safe + lazy). */
|
|
14
14
|
readonly loader?: ImageLoader;
|
|
15
15
|
/** `data-*` attributes pass through to the `<img>` (React doesn't auto-allow these on a custom
|
|
16
16
|
* component, so they're declared explicitly). */
|
package/dist/image.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/image`
|
|
2
|
+
* `@nifrajs/web-react/image` - a CLS-safe responsive React `<Image>`. A thin wrapper over
|
|
3
3
|
* `resolveImage` from `@nifrajs/image`: it computes `src`/`srcSet`/`sizes`/`width`/`height`/`loading`/
|
|
4
4
|
* `decoding`/`fetchpriority` and renders a plain `<img>`, forwarding any extra DOM props
|
|
5
5
|
* (`className`, `style`, `id`, event handlers, `data-*`, …). Resizing is delegated to the `loader`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { RenderAdapter } from "@nifrajs/web";
|
|
2
|
-
/** The React server render adapter
|
|
2
|
+
/** The React server render adapter - pass to @nifrajs/web's `renderPage`. */
|
|
3
3
|
export declare const reactAdapter: RenderAdapter;
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/web-react
|
|
2
|
+
* @nifrajs/web-react - the React render adapter for @nifrajs/web (server side). Streaming SSR via
|
|
3
3
|
* `renderToReadableStream`; the layout-chain fold is in `./compose`. Client hydration lives in
|
|
4
|
-
* `@nifrajs/web-react/client`. React's JSX is Bun-native
|
|
4
|
+
* `@nifrajs/web-react/client`. React's JSX is Bun-native - no build plugin needed.
|
|
5
5
|
*/
|
|
6
6
|
import { compose } from "./compose.js";
|
|
7
7
|
import { reactDomServer } from "./react-dom-server.js";
|
|
8
|
-
/** The React server render adapter
|
|
8
|
+
/** The React server render adapter - pass to @nifrajs/web's `renderPage`. */
|
|
9
9
|
export const reactAdapter = {
|
|
10
10
|
// Synchronous one-pass render for non-deferred pages (renderPage's buffered fast path). React's
|
|
11
11
|
// `renderToString` emits the same hydration-compatible markup as the Fizz stream for non-Suspense
|
|
12
|
-
// content and is markedly cheaper than `renderToReadableStream`
|
|
12
|
+
// content and is markedly cheaper than `renderToReadableStream` - most visibly on Bun, where Fizz's
|
|
13
13
|
// streaming machinery is the heaviest. A page that defer()s/Suspends takes `renderToStream` below.
|
|
14
14
|
//
|
|
15
15
|
// Async because the `react-dom/server` module is resolved lazily (from the consumer app's node_modules
|
|
16
|
-
// under Bun runtime SSR
|
|
16
|
+
// under Bun runtime SSR - see ./react-dom-server). `RenderAdapter.renderToString` permits a Promise;
|
|
17
17
|
// renderPage awaits it.
|
|
18
18
|
async renderToString(chain, props) {
|
|
19
19
|
const { renderToString } = await reactDomServer();
|
|
@@ -21,13 +21,13 @@ export const reactAdapter = {
|
|
|
21
21
|
},
|
|
22
22
|
async renderToStream(chain, props) {
|
|
23
23
|
// Resolves a Web `ReadableStream<Uint8Array>` once the shell is renderable; Suspense
|
|
24
|
-
// boundaries stream as they resolve. No `bootstrapModules`
|
|
24
|
+
// boundaries stream as they resolve. No `bootstrapModules` - nifra injects the client entry in
|
|
25
25
|
// the document tail. React's default `onError` logs to console.error (errors aren't swallowed).
|
|
26
26
|
const { renderToReadableStream } = await reactDomServer();
|
|
27
27
|
return renderToReadableStream(compose(chain, props));
|
|
28
28
|
},
|
|
29
29
|
// React reconciles against the existing DOM on hydrate, so no per-document bootstrap
|
|
30
|
-
// script is needed (contrast Solid's generateHydrationScript)
|
|
30
|
+
// script is needed (contrast Solid's generateHydrationScript) - the seam allows both.
|
|
31
31
|
hydrationHead() {
|
|
32
32
|
return "";
|
|
33
33
|
},
|
package/dist/island.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { IslandStrategy } from "@nifrajs/web/islands";
|
|
2
2
|
/**
|
|
3
|
-
* `@nifrajs/web-react/island`
|
|
3
|
+
* `@nifrajs/web-react/island` - the `<Island>` marker for no-framework islands. Server-renders its
|
|
4
4
|
* children inside a `<nifra-island>` element that the framework-agnostic `mountIslands` runtime
|
|
5
|
-
* (`@nifrajs/web/islands`) picks up on the client. Imports only `react` (+ a type)
|
|
5
|
+
* (`@nifrajs/web/islands`) picks up on the client. Imports only `react` (+ a type) - no `react-dom`,
|
|
6
6
|
* no JSX (the package builds with plain `tsc`).
|
|
7
7
|
*
|
|
8
8
|
* Use on a route with `export const hydrate = false`: the page ships zero framework JS, and only the
|
|
@@ -17,7 +17,7 @@ export interface IslandProps {
|
|
|
17
17
|
readonly props?: unknown;
|
|
18
18
|
/** When the enhancer runs (default `load`). `visible` (IntersectionObserver) wins TTI on long pages. */
|
|
19
19
|
readonly strategy?: IslandStrategy;
|
|
20
|
-
/** The island's server-rendered content
|
|
20
|
+
/** The island's server-rendered content - real HTML, present with JS off. */
|
|
21
21
|
readonly children?: ReactNode;
|
|
22
22
|
}
|
|
23
23
|
/** Render a `<nifra-island>` marker around server-rendered `children`. The client enhancer mounts the
|
package/dist/island.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/island`
|
|
2
|
+
* `@nifrajs/web-react/island` - the `<Island>` marker for no-framework islands. Server-renders its
|
|
3
3
|
* children inside a `<nifra-island>` element that the framework-agnostic `mountIslands` runtime
|
|
4
|
-
* (`@nifrajs/web/islands`) picks up on the client. Imports only `react` (+ a type)
|
|
4
|
+
* (`@nifrajs/web/islands`) picks up on the client. Imports only `react` (+ a type) - no `react-dom`,
|
|
5
5
|
* no JSX (the package builds with plain `tsc`).
|
|
6
6
|
*
|
|
7
7
|
* Use on a route with `export const hydrate = false`: the page ships zero framework JS, and only the
|
package/dist/query.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web-react/query`
|
|
2
|
+
* `@nifrajs/web-react/query` - React bindings for the keyed query-cache + mutations: `useQuery`,
|
|
3
3
|
* `useInfiniteQuery`, `useMutation`, `useQueryClient`, `QueryClientProvider`, and the SSR
|
|
4
4
|
* `HydrationBoundary` (+ `dehydrate` re-exported). A drop-in for the TanStack Query surface,
|
|
5
5
|
* backed by `@nifrajs/web`'s agnostic engine. Imports only `react` + `@nifrajs/web` (never `react-dom/*`),
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Resolution order for the client a hook uses: a `QueryClientProvider` in the tree (required for SSR
|
|
9
9
|
* dehydrate/hydrate and for tests), else a lazily-created **client-side** module singleton (the simple
|
|
10
|
-
* client-only app
|
|
10
|
+
* client-only app - the `typeof window` guard means the server has none, so hooks render idle/pending
|
|
11
11
|
* and the first client render matches for a clean hydration).
|
|
12
12
|
*/
|
|
13
13
|
import { type DehydratedState, type InfiniteData, type InfiniteQueryOptions, type MutationCallbacks, type MutationState, type QueryClient, type QueryOptions, type QueryState } from "@nifrajs/web";
|
|
14
14
|
import { type ReactNode } from "react";
|
|
15
15
|
export type { DehydratedState } from "@nifrajs/web";
|
|
16
|
-
/** Provide a {@link QueryClient} to the tree
|
|
16
|
+
/** Provide a {@link QueryClient} to the tree - required for SSR dehydrate/hydrate and for tests; a
|
|
17
17
|
* client-only app can omit it and rely on the built-in client-side singleton. */
|
|
18
18
|
export declare function QueryClientProvider(props: {
|
|
19
19
|
readonly client: QueryClient;
|
|
@@ -24,12 +24,12 @@ export declare function QueryClientProvider(props: {
|
|
|
24
24
|
export declare function useQueryClient(): QueryClient;
|
|
25
25
|
/** Options for {@link useQuery}. */
|
|
26
26
|
export interface UseQueryOptions extends QueryOptions {
|
|
27
|
-
/** When `false`, don't fetch (the query stays idle)
|
|
27
|
+
/** When `false`, don't fetch (the query stays idle) - for dependent queries. Default `true`. */
|
|
28
28
|
readonly enabled?: boolean;
|
|
29
29
|
}
|
|
30
30
|
/** A query's reactive {@link QueryState} plus `isPending` + `refetch`. */
|
|
31
31
|
export interface UseQueryResult<T> extends QueryState<T> {
|
|
32
|
-
/** `status === "pending"`
|
|
32
|
+
/** `status === "pending"` - no data yet (initial load). */
|
|
33
33
|
readonly isPending: boolean;
|
|
34
34
|
/** `status === "error"`. */
|
|
35
35
|
readonly isError: boolean;
|
|
@@ -54,7 +54,7 @@ export interface UseMutationResult<TData, TVariables> extends MutationState<TDat
|
|
|
54
54
|
readonly isSuccess: boolean;
|
|
55
55
|
/** Fire-and-forget: runs the mutation and swallows rejection (read `error`/`isError` for failures). */
|
|
56
56
|
readonly mutate: (variables: TVariables) => void;
|
|
57
|
-
/** Run the mutation and return the promise (rejects on failure)
|
|
57
|
+
/** Run the mutation and return the promise (rejects on failure) - for `await`. */
|
|
58
58
|
readonly mutateAsync: (variables: TVariables) => Promise<TData>;
|
|
59
59
|
/** Reset back to idle. */
|
|
60
60
|
readonly reset: () => void;
|
|
@@ -66,7 +66,7 @@ export interface UseMutationResult<TData, TVariables> extends MutationState<TDat
|
|
|
66
66
|
* callbacks re-bind each render.
|
|
67
67
|
*/
|
|
68
68
|
export declare function useMutation<TData, TVariables = void>(fn: (variables: TVariables) => Promise<TData>, callbacks?: MutationCallbacks<TData, TVariables>): UseMutationResult<TData, TVariables>;
|
|
69
|
-
/** Options for {@link useInfiniteQuery}
|
|
69
|
+
/** Options for {@link useInfiniteQuery} - the engine's {@link InfiniteQueryOptions} plus `enabled`. */
|
|
70
70
|
export interface UseInfiniteQueryOptions<T, P> extends InfiniteQueryOptions<T, P> {
|
|
71
71
|
readonly enabled?: boolean;
|
|
72
72
|
}
|
|
@@ -88,7 +88,7 @@ export interface UseInfiniteQueryResult<T, P> extends QueryState<InfiniteData<T,
|
|
|
88
88
|
*/
|
|
89
89
|
export declare function useInfiniteQuery<T, P>(key: unknown, fn: (pageParam: P) => Promise<T>, options: UseInfiniteQueryOptions<T, P>): UseInfiniteQueryResult<T, P>;
|
|
90
90
|
/**
|
|
91
|
-
* Seed the context's {@link QueryClient} from a server {@link dehydrate} snapshot
|
|
91
|
+
* Seed the context's {@link QueryClient} from a server {@link dehydrate} snapshot - the SSR data bridge.
|
|
92
92
|
* Wrap the app (inside `QueryClientProvider`) so server-prefetched queries are in the cache before the
|
|
93
93
|
* first client render, avoiding a loading flash. Hydration runs during render (idempotent, fresher-wins),
|
|
94
94
|
* so the data is available synchronously to child `useQuery`s.
|