@nifrajs/web-preact 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.
Files changed (51) hide show
  1. package/README.md +3 -3
  2. package/dist/await.js +1 -1
  3. package/dist/client.d.ts +2 -2
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +11 -5
  6. package/dist/client.js.map +1 -1
  7. package/dist/compose.d.ts +4 -2
  8. package/dist/compose.d.ts.map +1 -1
  9. package/dist/compose.js +19 -7
  10. package/dist/compose.js.map +1 -1
  11. package/dist/content.d.ts +2 -2
  12. package/dist/content.js +2 -2
  13. package/dist/error.d.ts +1 -1
  14. package/dist/error.js +3 -3
  15. package/dist/fetcher.d.ts +2 -2
  16. package/dist/fetcher.js +5 -5
  17. package/dist/fn.d.ts +26 -0
  18. package/dist/fn.d.ts.map +1 -0
  19. package/dist/fn.js +28 -0
  20. package/dist/fn.js.map +1 -0
  21. package/dist/i18n.d.ts +2 -2
  22. package/dist/i18n.js +3 -3
  23. package/dist/image.d.ts +4 -4
  24. package/dist/image.js +2 -2
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +4 -4
  27. package/dist/preact-render.d.ts +3 -3
  28. package/dist/preact-render.js +3 -3
  29. package/dist/query.d.ts +1 -1
  30. package/dist/query.js +2 -2
  31. package/dist/read-deferred.d.ts +2 -2
  32. package/dist/read-deferred.js +1 -1
  33. package/dist/router.d.ts +34 -0
  34. package/dist/router.d.ts.map +1 -0
  35. package/dist/router.js +64 -0
  36. package/dist/router.js.map +1 -0
  37. package/package.json +16 -5
  38. package/src/await.ts +1 -1
  39. package/src/client.ts +11 -6
  40. package/src/compose.ts +29 -7
  41. package/src/content.ts +2 -2
  42. package/src/error.ts +3 -3
  43. package/src/fetcher.ts +5 -5
  44. package/src/fn.ts +40 -0
  45. package/src/i18n.ts +3 -3
  46. package/src/image.ts +4 -4
  47. package/src/index.ts +4 -4
  48. package/src/preact-render.ts +4 -4
  49. package/src/query.ts +3 -3
  50. package/src/read-deferred.ts +3 -3
  51. package/src/router.ts +88 -0
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @nifrajs/web-preact
2
2
 
3
- Preact render adapter for @nifrajs/web — streaming SSR + hydration (no build plugin).
3
+ Preact render adapter for @nifrajs/web - streaming SSR + hydration (no build plugin).
4
4
 
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`.
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) — this package's contract card (the exports you call + its footguns),
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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@nifrajs/web-preact/await` — the `<Await>` primitive for deferred loader data (`defer()`). Isomorphic
2
+ * `@nifrajs/web-preact/await` - the `<Await>` primitive for deferred loader data (`defer()`). Isomorphic
3
3
  * and imports only `preact`/`preact/compat` + a type from `@nifrajs/web` (never the server renderer), so
4
4
  * route components can use it without dragging server code into the client bundle. No JSX (the package
5
5
  * builds with plain `tsc`).
package/dist/client.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { MountRouterOptions, RenderProps } from "@nifrajs/web";
1
+ import { type MountRouterOptions, type RenderProps } from "@nifrajs/web";
2
2
  export { errorBoundary } from "./error.js";
3
3
  /** Hydrate a server-rendered Preact 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 Preact Router. `useSyncExternalStore` (preact/compat) subscribes to the
7
- * agnostic store and re-renders the matched layout chain on each store change — so client
7
+ * agnostic store and re-renders the matched layout chain on each store change - so client
8
8
  * navigations swap routes without a full reload. Preact's compat `useSyncExternalStore` is 2-arg
9
9
  * (no `getServerSnapshot`); `router.snapshot` is deterministic, so hydration matches the SSR markup.
10
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAenE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,oGAAoG;AACpG,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,CAc7D"}
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;AAevF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,oGAAoG;AACpG,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,CAmB7D"}
package/dist/client.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { searchOfChain } from "@nifrajs/web";
1
2
  /**
2
- * @nifrajs/web-preact/client — Preact client runtime. `hydrate` hydrates a single SSR'd route;
3
+ * @nifrajs/web-preact/client - Preact 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` from preact/compat) and re-renders the matched chain on every client
5
6
  * navigation (no full reload). Kept in its own entry so server code (preact-render-to-string) stays
@@ -9,7 +10,7 @@ import { h, hydrate as preactHydrate } from "preact";
9
10
  import { useSyncExternalStore } from "preact/compat";
10
11
  import { compose } from "./compose.js";
11
12
  import { setMountedRouter } from "./fetcher.js";
12
- // The `_error` boundary chain element — defined in its own (DOM-free) module, re-exported here so
13
+ // The `_error` boundary chain element - defined in its own (DOM-free) module, re-exported here so
13
14
  // nifra's client codegen resolves it from `@nifrajs/web-preact/client` alongside `mountRouter`.
14
15
  export { errorBoundary } from "./error.js";
15
16
  /** Hydrate a server-rendered Preact layout `chain` (with the loader `props`) inside `container`. */
@@ -18,20 +19,25 @@ export function hydrate(chain, props, container) {
18
19
  }
19
20
  /**
20
21
  * Hydrate a stateful Preact Router. `useSyncExternalStore` (preact/compat) subscribes to the
21
- * agnostic store and re-renders the matched layout chain on each store change — so client
22
+ * agnostic store and re-renders the matched layout chain on each store change - so client
22
23
  * navigations swap routes without a full reload. Preact's compat `useSyncExternalStore` is 2-arg
23
24
  * (no `getServerSnapshot`); `router.snapshot` is deterministic, so hydration matches the SSR markup.
24
25
  */
25
26
  export function mountRouter(options) {
26
- const { router, routes, container } = options;
27
+ const { router, routes, searchSchemas, container } = options;
27
28
  setMountedRouter(router); // expose it to useFetcher/useFetchers (same page, client-only)
28
29
  const Router = () => {
29
30
  const state = useSyncExternalStore(router.subscribe, router.snapshot);
31
+ // Derive this route's typed `search` from the URL + the route's schema chain (the SAME `searchOfChain`
32
+ // the server ran), so `useSearch` reads an identical value and hydrates with no drift.
33
+ const q = state.path.indexOf("?");
34
+ const rawSearch = q === -1 ? "" : state.path.slice(q);
30
35
  return compose(routes[state.routeId] ?? [], {
31
36
  data: state.data,
32
37
  actionData: state.actionData,
33
38
  pending: state.pending,
34
- // The in-flight submission (for optimistic UI) — spread only when present.
39
+ search: searchOfChain(searchSchemas?.[state.routeId] ?? [], rawSearch),
40
+ // The in-flight submission (for optimistic UI) - spread only when present.
35
41
  ...(state.submission ? { submission: state.submission } : {}),
36
42
  });
37
43
  };
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AACH,OAAO,EAA0B,CAAC,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,QAAQ,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,kGAAkG;AAClG,gGAAgG;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,oGAAoG;AACpG,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB,EAAE,SAAkB;IACvF,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,SAAoB,CAAC,CAAA;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAC7C,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,CAAC,CAAA;QACrE,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,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,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,SAAoB,CAAC,CAAA;AACtD,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6C,aAAa,EAAE,MAAM,cAAc,CAAA;AACvF;;;;;;GAMG;AACH,OAAO,EAA0B,CAAC,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,QAAQ,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,kGAAkG;AAClG,gGAAgG;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,oGAAoG;AACpG,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB,EAAE,SAAkB;IACvF,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,SAAoB,CAAC,CAAA;AAC5D,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,CAAC,CAAA;QACrE,uGAAuG;QACvG,uFAAuF;QACvF,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,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,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,SAAoB,CAAC,CAAA;AACtD,CAAC"}
package/dist/compose.d.ts CHANGED
@@ -3,8 +3,10 @@ import { type VNode } from "preact";
3
3
  /**
4
4
  * Fold a layout chain (outermost layout → page) into a single Preact tree: the page (innermost)
5
5
  * receives `props` (the loader data); each layout wraps the child via its `children` (Preact passes
6
- * the 3rd `h` arg as `props.children`). Shared by the server adapter (renderToReadableStream) and
7
- * the client (hydrate / mountRouter) — the Preact analogue of the React adapter's `compose`.
6
+ * the 3rd `h` arg as `props.children`). The whole tree is wrapped in a {@link SearchContext} provider
7
+ * carrying the validated `search` (threaded through `RenderProps` identically on SSR + client), so
8
+ * `useSearch` reads the same value on both sides - no hydration mismatch. Shared by the server adapter
9
+ * (renderToReadableStream) and the client (hydrate / mountRouter) - the Preact analogue of React's `compose`.
8
10
  */
9
11
  export declare function compose(chain: readonly unknown[], props: RenderProps): VNode;
10
12
  //# sourceMappingURL=compose.d.ts.map
@@ -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,EAAyB,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAE1D;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,KAAK,CAa5E"}
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,EAAgE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAejG;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,KAAK,CAoB5E"}
package/dist/compose.js CHANGED
@@ -1,9 +1,18 @@
1
- import { h } from "preact";
1
+ import { createElement, h } from "preact";
2
+ import { SearchContext } from "./router.js";
3
+ // Frozen empty search so a render with no search context has a stable provider value.
4
+ const EMPTY_SEARCH = Object.freeze({});
5
+ // Preact's `createElement` overloads drop a typed `Context.Provider`'s required `value` under
6
+ // exactOptionalPropertyTypes (same issue i18n's provider hits); invoke it through a precisely-typed
7
+ // factory that states the real `{ value }` + children contract rather than scatter `any`.
8
+ const createSearchProvider = createElement;
2
9
  /**
3
10
  * Fold a layout chain (outermost layout → page) into a single Preact tree: the page (innermost)
4
11
  * receives `props` (the loader data); each layout wraps the child via its `children` (Preact passes
5
- * the 3rd `h` arg as `props.children`). Shared by the server adapter (renderToReadableStream) and
6
- * the client (hydrate / mountRouter) — the Preact analogue of the React adapter's `compose`.
12
+ * the 3rd `h` arg as `props.children`). The whole tree is wrapped in a {@link SearchContext} provider
13
+ * carrying the validated `search` (threaded through `RenderProps` identically on SSR + client), so
14
+ * `useSearch` reads the same value on both sides - no hydration mismatch. Shared by the server adapter
15
+ * (renderToReadableStream) and the client (hydrate / mountRouter) - the Preact analogue of React's `compose`.
7
16
  */
8
17
  export function compose(chain, props) {
9
18
  const last = chain.length - 1;
@@ -11,12 +20,15 @@ export function compose(chain, props) {
11
20
  // loader props (typed via ComponentType<RenderProps>), then we widen `h`'s VNode<Attributes &
12
21
  // RenderProps> to the uniform VNode the fold accumulates into. Preact's VNode<P> is invariant in P
13
22
  // under exactOptionalPropertyTypes, so the widen needs the explicit `as VNode` (a comparability
14
- // cast — Attributes & RenderProps is assignable to {}, so this is not an unsafe coercion).
23
+ // cast - Attributes & RenderProps is assignable to {}, so this is not an unsafe coercion).
15
24
  let node = h(chain[last], props);
16
25
  for (let i = last - 1; i >= 0; i--) {
17
- // children passed as the 3rd arg — Preact's canonical form (mirrors the React adapter).
18
- node = h(chain[i], null, node);
26
+ // children passed as the 3rd arg - Preact's canonical form (mirrors the React adapter).
27
+ // Each layout receives its own loader data at its own index. Layouts are the chain's leading
28
+ // prefix, so `layoutData[i]` belongs to `chain[i]`; anything past that end (a client-only `_error`
29
+ // boundary marker, the page) reads `undefined` and is unaffected.
30
+ node = h(chain[i], { data: props.layoutData?.[i] ?? null }, node);
19
31
  }
20
- return node;
32
+ return createSearchProvider(SearchContext.Provider, { value: props.search ?? EMPTY_SEARCH }, node);
21
33
  }
22
34
  //# sourceMappingURL=compose.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,CAAC,EAAc,MAAM,QAAQ,CAAA;AAE1D;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,iGAAiG;IACjG,8FAA8F;IAC9F,mGAAmG;IACnG,gGAAgG;IAChG,2FAA2F;IAC3F,IAAI,IAAI,GAAU,CAAC,CAAC,KAAK,CAAC,IAAI,CAA+B,EAAE,KAAK,CAAU,CAAA;IAC9E,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,wFAAwF;QACxF,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IACjD,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AACA,OAAO,EAA8C,aAAa,EAAE,CAAC,EAAc,MAAM,QAAQ,CAAA;AACjG,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,sFAAsF;AACtF,MAAM,YAAY,GAAsC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAEzE,8FAA8F;AAC9F,oGAAoG;AACpG,0FAA0F;AAC1F,MAAM,oBAAoB,GAAG,aAInB,CAAA;AAEV;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,iGAAiG;IACjG,8FAA8F;IAC9F,mGAAmG;IACnG,gGAAgG;IAChG,2FAA2F;IAC3F,IAAI,IAAI,GAAU,CAAC,CAAC,KAAK,CAAC,IAAI,CAA+B,EAAE,KAAK,CAAU,CAAA;IAC9E,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,wFAAwF;QACxF,6FAA6F;QAC7F,mGAAmG;QACnG,kEAAkE;QAClE,IAAI,GAAG,CAAC,CACN,KAAK,CAAC,CAAC,CAAqC,EAC5C,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EACvC,IAAI,CACI,CAAA;IACZ,CAAC;IACD,OAAO,oBAAoB,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,IAAI,YAAY,EAAE,EAAE,IAAI,CAAC,CAAA;AACpG,CAAC"}
package/dist/content.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * `@nifrajs/web-preact/content` — render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
2
+ * `@nifrajs/web-preact/content` - render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
3
3
  * `.html`) into the DOM via `dangerouslySetInnerHTML`. Pairs with content collections:
4
4
  * `<Content html={entry.html} />`.
5
5
  *
6
6
  * Security: injects raw HTML, so `html` MUST be trusted (your own Markdown/content, rendered at
7
- * build/server time) — never pass user-supplied HTML without sanitizing it first.
7
+ * build/server time) - never pass user-supplied HTML without sanitizing it first.
8
8
  */
9
9
  import { type JSX, type VNode } from "preact";
10
10
  export interface ContentProps extends Omit<JSX.HTMLAttributes<HTMLElement>, "dangerouslySetInnerHTML"> {
package/dist/content.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * `@nifrajs/web-preact/content` — render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
2
+ * `@nifrajs/web-preact/content` - render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
3
3
  * `.html`) into the DOM via `dangerouslySetInnerHTML`. Pairs with content collections:
4
4
  * `<Content html={entry.html} />`.
5
5
  *
6
6
  * Security: injects raw HTML, so `html` MUST be trusted (your own Markdown/content, rendered at
7
- * build/server time) — never pass user-supplied HTML without sanitizing it first.
7
+ * build/server time) - never pass user-supplied HTML without sanitizing it first.
8
8
  */
9
9
  import { createElement } from "preact";
10
10
  /** Render trusted HTML into a wrapper element. Extra props (`class`, `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 — no wrapper element), so it adds no markup and never disturbs hydration.
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` — the Preact error-boundary chain element for nifra's `_error.tsx`. Its own module
2
+ * `errorBoundary` - the Preact error-boundary chain element for nifra's `_error.tsx`. Its own module
3
3
  * (imports only `preact`) so it's unit-testable off the DOM and the client codegen can import it from
4
4
  * `@nifrajs/web-preact/client` (which re-exports it).
5
5
  */
@@ -8,7 +8,7 @@ import { Component, createElement } from "preact";
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 — no wrapper element), so it adds no markup and never disturbs hydration.
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 — opaque to the core, a component here.
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 — called by `mountRouter`. Not for app use. */
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 — for a global busy view (e.g. "3 saving…"). Read
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,21 +1,21 @@
1
1
  /**
2
- * `@nifrajs/web-preact/fetcher` — Preact bindings for concurrent fetchers. `useFetcher(key)` subscribes a
2
+ * `@nifrajs/web-preact/fetcher` - Preact bindings for concurrent fetchers. `useFetcher(key)` subscribes a
3
3
  * component to an independent {@link Fetcher} (via `useSyncExternalStore` from preact/compat) and
4
4
  * returns its reactive state plus `load`/`submit`; `useFetchers()` subscribes to the whole live
5
5
  * collection. Imports only `preact/compat` + `@nifrajs/web` types, so route components can use it on the
6
6
  * server *and* client. No JSX (the package builds with plain `tsc`).
7
7
  *
8
- * The router that owns the fetchers is the one `mountRouter` hydrated — it registers itself here via
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). Preact's compat `useSyncExternalStore` is 2-arg (no `getServerSnapshot`);
11
11
  * with no router the `getSnapshot` already returns idle, so the first client render after `mountRouter`
12
- * matches the SSR markup — no hydration mismatch.
12
+ * matches the SSR markup - no hydration mismatch.
13
13
  */
14
14
  import { useSyncExternalStore } from "preact/compat";
15
15
  // The active client router (set by `mountRouter`). Module-scoped: the browser mounts one app per
16
16
  // page, and fetchers never exist on the server. Shared with `client.ts`'s `mountRouter`.
17
17
  let mountedRouter;
18
- /** Register (or clear) the router that owns fetchers — called by `mountRouter`. Not for app use. */
18
+ /** Register (or clear) the router that owns fetchers - called by `mountRouter`. Not for app use. */
19
19
  export function setMountedRouter(router) {
20
20
  mountedRouter = router;
21
21
  }
@@ -38,7 +38,7 @@ export function useFetcher(key) {
38
38
  return { ...state, load: fetcher?.load ?? noopAsync, submit: fetcher?.submit ?? noopAsync };
39
39
  }
40
40
  /**
41
- * Subscribe to the whole live fetcher collection — for a global busy view (e.g. "3 saving…"). Read
41
+ * Subscribe to the whole live fetcher collection - for a global busy view (e.g. "3 saving…"). Read
42
42
  * each entry's `.snapshot()` for its state. Re-renders whenever any fetcher transitions or a new one
43
43
  * is created.
44
44
  */
package/dist/fn.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `@nifrajs/web-preact/fn` - Preact 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
+ * Preact's compat `useSyncExternalStore` takes two arguments (no server snapshot); the store's idle
23
+ * state is shared by reference anyway, so a server render and the first client render agree.
24
+ */
25
+ export declare function useServerFn<Input, Output>(fn: ServerFnReference<Input, Output>): ServerFnHandle<Input, Output>;
26
+ //# sourceMappingURL=fn.d.ts.map
@@ -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;;;;;GAKG;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,28 @@
1
+ /**
2
+ * `@nifrajs/web-preact/fn` - Preact 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 "preact/compat";
12
+ /**
13
+ * Track one server function's call state.
14
+ *
15
+ * Preact's compat `useSyncExternalStore` takes two arguments (no server snapshot); the store's idle
16
+ * state is shared by reference anyway, so a server render and the first client render agree.
17
+ */
18
+ export function useServerFn(fn) {
19
+ // The store is created once, but always calls the CURRENT `fn`. Capturing `fn` directly would
20
+ // either recreate the store on every render (losing the state) or hold the first render's closure
21
+ // (calling a stale one); the ref is what makes "created once" and "never stale" both true.
22
+ const latest = useRef(fn);
23
+ latest.current = fn;
24
+ const store = useMemo(() => createServerFnStore((input) => latest.current(input)), []);
25
+ const state = useSyncExternalStore(store.subscribe, store.snapshot);
26
+ return { ...state, call: store.call, reset: store.reset };
27
+ }
28
+ //# 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,eAAe,CAAA;AAUrE;;;;;GAKG;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,CAAC,CAAA;IACnE,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,9 +1,9 @@
1
1
  /**
2
- * `@nifrajs/web-preact/i18n` — Preact bindings for `@nifrajs/i18n`. `<I18nProvider locale messages>` builds a
2
+ * `@nifrajs/web-preact/i18n` - Preact 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). Uses `preact` +
6
- * `preact/hooks` — no JSX.
6
+ * `preact/hooks` - no JSX.
7
7
  */
8
8
  import { type Formatter, type Messages } from "@nifrajs/i18n";
9
9
  import { type ComponentChildren, type VNode } from "preact";
package/dist/i18n.js CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
- * `@nifrajs/web-preact/i18n` — Preact bindings for `@nifrajs/i18n`. `<I18nProvider locale messages>` builds a
2
+ * `@nifrajs/web-preact/i18n` - Preact 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). Uses `preact` +
6
- * `preact/hooks` — no JSX.
6
+ * `preact/hooks` - no JSX.
7
7
  */
8
8
  import { createFormatter } from "@nifrajs/i18n";
9
9
  import { createContext, createElement } from "preact";
10
10
  import { useContext, useMemo } from "preact/hooks";
11
11
  const I18nContext = createContext(null);
12
12
  // Preact's `createElement` overloads don't reconcile a typed `Context.Provider` (required `value`)
13
- // under `exactOptionalPropertyTypes` — overload resolution drops `value` and infers the props as `{}`.
13
+ // under `exactOptionalPropertyTypes` - overload resolution drops `value` and infers the props as `{}`.
14
14
  // The call is correct at runtime (proven by the tests), so invoke it through a precisely-typed element
15
15
  // factory that states the real contract (`{ value }` + children), rather than scatter `any`.
16
16
  const createProvider = createElement;
package/dist/image.d.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
- * `@nifrajs/web-preact/image` — a CLS-safe responsive Preact `<Image>`. A thin wrapper over `resolveImage`
2
+ * `@nifrajs/web-preact/image` - a CLS-safe responsive Preact `<Image>`. A thin wrapper over `resolveImage`
3
3
  * from `@nifrajs/image`: it computes the responsive `<img>` attributes (`src`/`srcset`/`sizes`/`width`/
4
- * `height`/`loading`/`decoding`/`fetchpriority` — Preact uses lowercase HTML attribute names, so it
4
+ * `height`/`loading`/`decoding`/`fetchpriority` - Preact uses lowercase HTML attribute names, so it
5
5
  * spreads `toHtmlAttrs`) and forwards any extra DOM props (`class`, `style`, `id`, `data-*`, handlers).
6
6
  * Resizing is delegated to the `loader` (an image CDN); nifra bundles no codec. No JSX (`h`).
7
7
  */
8
8
  import { type ImageLoader, type ImageProps } from "@nifrajs/image";
9
9
  import { type JSX, type VNode } from "preact";
10
- /** Attributes `resolveImage` computes — callers can't override them via DOM passthrough. */
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<JSX.IntrinsicElements["img"], ComputedAttrs | keyof ImageProps> {
13
- /** CDN URL builder. Defaults to the identity loader (no transform — still CLS-safe + lazy). */
13
+ /** CDN URL builder. Defaults to the identity loader (no transform - still CLS-safe + lazy). */
14
14
  readonly loader?: ImageLoader;
15
15
  }
16
16
  /**
package/dist/image.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * `@nifrajs/web-preact/image` — a CLS-safe responsive Preact `<Image>`. A thin wrapper over `resolveImage`
2
+ * `@nifrajs/web-preact/image` - a CLS-safe responsive Preact `<Image>`. A thin wrapper over `resolveImage`
3
3
  * from `@nifrajs/image`: it computes the responsive `<img>` attributes (`src`/`srcset`/`sizes`/`width`/
4
- * `height`/`loading`/`decoding`/`fetchpriority` — Preact uses lowercase HTML attribute names, so it
4
+ * `height`/`loading`/`decoding`/`fetchpriority` - Preact uses lowercase HTML attribute names, so it
5
5
  * spreads `toHtmlAttrs`) and forwards any extra DOM props (`class`, `style`, `id`, `data-*`, handlers).
6
6
  * Resizing is delegated to the `loader` (an image CDN); nifra bundles no codec. No JSX (`h`).
7
7
  */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import type { RenderAdapter } from "@nifrajs/web";
2
- /** The Preact server render adapter — pass to @nifrajs/web's `renderPage`. */
2
+ /** The Preact server render adapter - pass to @nifrajs/web's `renderPage`. */
3
3
  export declare const preactAdapter: RenderAdapter;
4
4
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @nifrajs/web-preact — the Preact render adapter for @nifrajs/web (server side). Streaming SSR via
2
+ * @nifrajs/web-preact - the Preact render adapter for @nifrajs/web (server side). Streaming SSR via
3
3
  * `renderToReadableStream` from `preact-render-to-string/stream` (a Web `ReadableStream<Uint8Array>`,
4
4
  * the seam's native shape); the layout-chain fold is in `./compose`. Client hydration lives in
5
5
  * `@nifrajs/web-preact/client`. Preact uses `h()` render functions, so no JSX/build plugin is needed
@@ -7,13 +7,13 @@
7
7
  */
8
8
  import { compose } from "./compose.js";
9
9
  import { preactRenderToStream, preactRenderToString } from "./preact-render.js";
10
- /** The Preact server render adapter — pass to @nifrajs/web's `renderPage`. */
10
+ /** The Preact server render adapter - pass to @nifrajs/web's `renderPage`. */
11
11
  export const preactAdapter = {
12
12
  // Synchronous one-pass render for non-deferred pages (renderPage's buffered fast path). The sync
13
13
  // `renderToString` emits the same markup as the stream renderer without the chunking machinery.
14
14
  //
15
15
  // Async because the renderer module is resolved lazily (from the consumer app's node_modules under Bun
16
- // runtime SSR — see ./preact-render). `RenderAdapter.renderToString` permits a Promise; renderPage awaits.
16
+ // runtime SSR - see ./preact-render). `RenderAdapter.renderToString` permits a Promise; renderPage awaits.
17
17
  async renderToString(chain, props) {
18
18
  const { renderToString } = await preactRenderToString();
19
19
  return renderToString(compose(chain, props));
@@ -25,7 +25,7 @@ export const preactAdapter = {
25
25
  return renderToReadableStream(compose(chain, props));
26
26
  },
27
27
  // Preact reconciles against the existing DOM on hydrate (like React), so there's no per-document
28
- // bootstrap script — the seam allows the empty string (contrast Solid's generateHydrationScript).
28
+ // bootstrap script - the seam allows the empty string (contrast Solid's generateHydrationScript).
29
29
  hydrationHead() {
30
30
  return "";
31
31
  },
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * Resolve `preact-render-to-string` (+ its `/stream` subpath) so the renderer shares ONE `preact`
3
- * module-global with the route components — the fix for the dual-Preact SSR crash (`undefined is not an
3
+ * module-global with the route components - the fix for the dual-Preact SSR crash (`undefined is not an
4
4
  * object (evaluating '…__H')` / silently-empty hook output).
5
5
  *
6
6
  * WHY this exists: `preact-render-to-string` imports `options` and `h` from `preact` and mutates the
7
7
  * shared `options` hooks; `preact/hooks` registers its render hook on the SAME `options` global. Under Bun
8
8
  * **runtime** SSR (`nifra dev`, `nifra start`, `nifra_render`, all in-process), a static
9
9
  * `import "preact-render-to-string"` in this adapter is resolved from THIS package's own (symlinked)
10
- * node_modules — which can be a DIFFERENT physical `preact` than the consumer app's components import. Two
10
+ * node_modules - which can be a DIFFERENT physical `preact` than the consumer app's components import. Two
11
11
  * `preact` copies → two `options` globals → the renderer walks one while `preact/hooks` wrote the other →
12
12
  * the vnode never gets its hook-state list → the crash. Resolving the renderer from the consumer **app
13
13
  * root** makes it pull the app's `preact` transitively, matching the components: one `options`, one core,
14
- * no crash. (Empirically verified against a two-copy install fixture — see test/dual-preact.test.ts.)
14
+ * no crash. (Empirically verified against a two-copy install fixture - see test/dual-preact.test.ts.)
15
15
  *
16
16
  * Guarding precisely so the PRODUCTION/BUILT path is untouched: when `Bun.resolveSync` is unavailable
17
17
  * (Node / Deno / Cloudflare / Vercel / any bundled output), the build has already bundled a single
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * Resolve `preact-render-to-string` (+ its `/stream` subpath) so the renderer shares ONE `preact`
3
- * module-global with the route components — the fix for the dual-Preact SSR crash (`undefined is not an
3
+ * module-global with the route components - the fix for the dual-Preact SSR crash (`undefined is not an
4
4
  * object (evaluating '…__H')` / silently-empty hook output).
5
5
  *
6
6
  * WHY this exists: `preact-render-to-string` imports `options` and `h` from `preact` and mutates the
7
7
  * shared `options` hooks; `preact/hooks` registers its render hook on the SAME `options` global. Under Bun
8
8
  * **runtime** SSR (`nifra dev`, `nifra start`, `nifra_render`, all in-process), a static
9
9
  * `import "preact-render-to-string"` in this adapter is resolved from THIS package's own (symlinked)
10
- * node_modules — which can be a DIFFERENT physical `preact` than the consumer app's components import. Two
10
+ * node_modules - which can be a DIFFERENT physical `preact` than the consumer app's components import. Two
11
11
  * `preact` copies → two `options` globals → the renderer walks one while `preact/hooks` wrote the other →
12
12
  * the vnode never gets its hook-state list → the crash. Resolving the renderer from the consumer **app
13
13
  * root** makes it pull the app's `preact` transitively, matching the components: one `options`, one core,
14
- * no crash. (Empirically verified against a two-copy install fixture — see test/dual-preact.test.ts.)
14
+ * no crash. (Empirically verified against a two-copy install fixture - see test/dual-preact.test.ts.)
15
15
  *
16
16
  * Guarding precisely so the PRODUCTION/BUILT path is untouched: when `Bun.resolveSync` is unavailable
17
17
  * (Node / Deno / Cloudflare / Vercel / any bundled output), the build has already bundled a single
package/dist/query.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type QueryClient, type QueryState } from "@nifrajs/web";
3
3
  export declare function useQueryClient(): Pick<QueryClient, "invalidateQueries">;
4
4
  /** A query's reactive {@link QueryState} plus `isPending` + `refetch`. */
5
5
  export interface UseQueryResult<T> extends QueryState<T> {
6
- /** `status === "pending"` — no data yet (initial load). */
6
+ /** `status === "pending"` - no data yet (initial load). */
7
7
  readonly isPending: boolean;
8
8
  /** Force a refetch (ignores `staleTime`). */
9
9
  readonly refetch: () => Promise<T>;
package/dist/query.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { createQueryClient, } from "@nifrajs/web";
2
2
  /**
3
- * `@nifrajs/web-preact/query` — Preact bindings for the keyed query-cache. `useQuery(key, fn)` subscribes a
3
+ * `@nifrajs/web-preact/query` - Preact bindings for the keyed query-cache. `useQuery(key, fn)` subscribes a
4
4
  * component to a query (via `useSyncExternalStore` from preact/compat) and fetches on mount / key change;
5
5
  * `useQueryClient` exposes `invalidateQueries` for after a mutation. Imports only `preact/compat` +
6
6
  * `preact/hooks` + `@nifrajs/web`, so route components use it on the server *and* client. No JSX.
7
7
  *
8
- * The query client is created lazily **client-side only** (the `typeof window` guard) — on the server
8
+ * The query client is created lazily **client-side only** (the `typeof window` guard) - on the server
9
9
  * there is none, so `useQuery` renders the idle/pending state (queries are client-first; loaders are
10
10
  * the SSR data source). The first client render uses the same idle snapshot, so no mismatch.
11
11
  */
@@ -1,10 +1,10 @@
1
1
  /**
2
- * `readDeferred` — Preact's analogue of React 19's `use()` for a deferred promise (preact/compat has
2
+ * `readDeferred` - Preact's analogue of React 19's `use()` for a deferred promise (preact/compat has
3
3
  * no `use`). Reads a React-style tagged thenable synchronously: returns the value if fulfilled, throws
4
4
  * the reason if rejected, else throws the promise so `<Suspense>` awaits it. The core tags
5
5
  * `status`/`value`/`reason` via the streamed `__nifraResolve`/`__nifraReject`; this also self-tags on
6
6
  * settle so a server re-render (which has no `__nifraResolve`) reads "fulfilled" and stops re-throwing
7
- * the already-resolved promise. Pure + isomorphic — no `preact` import, so it unit-tests directly.
7
+ * the already-resolved promise. Pure + isomorphic - no `preact` import, so it unit-tests directly.
8
8
  */
9
9
  export type Thenable<T> = Promise<T> & {
10
10
  status?: "pending" | "fulfilled" | "rejected";
@@ -13,6 +13,6 @@ export function readDeferred(promise) {
13
13
  promise.reason = e;
14
14
  });
15
15
  }
16
- throw promise; // pending — <Suspense> awaits it; the self-tag above wakes the re-render.
16
+ throw promise; // pending - <Suspense> awaits it; the self-tag above wakes the re-render.
17
17
  }
18
18
  //# sourceMappingURL=read-deferred.js.map
@@ -0,0 +1,34 @@
1
+ import type { InferOutput, StandardSchemaV1 } from "@nifrajs/core/server";
2
+ import { type Blocker, type BlockerFunction, type NavigateFunction } from "@nifrajs/web";
3
+ export type { Blocker, BlockerFunction, BlockerState, NavigateFunction } from "@nifrajs/web";
4
+ /** The current route's validated search, provided by `compose` on SSR + client mount alike (derived from
5
+ * the URL via the shared `searchOfChain`). `{}` outside a nifra route tree. Read via {@link useSearch}. */
6
+ export declare const SearchContext: import("preact").Context<Record<string, unknown>>;
7
+ /**
8
+ * The route's typed, validated search params - the SAME value the loader received as `ctx.search`.
9
+ * SSR-correct: `compose` provides it from the URL server-side and from the identical derivation on the
10
+ * client mount, so a value rendered from it doesn't flash on hydration. Hostile input already failed
11
+ * closed to the schema's defaults at match time. Pass the route's `searchSchema` as the type argument to
12
+ * get its output type; bare, it's the raw parsed query (`Record<string, unknown>`).
13
+ *
14
+ * ```tsx
15
+ * export const searchSchema = v.object({ page: v.optional(v.fallback(v.number(), 1), 1) })
16
+ * const { page } = useSearch<typeof searchSchema>() // page: number
17
+ * ```
18
+ */
19
+ export declare function useSearch<Schema extends StandardSchemaV1 | undefined = undefined>(): Schema extends StandardSchemaV1 ? InferOutput<Schema> : Record<string, unknown>;
20
+ /** Get the {@link NavigateFunction} (a string path, a history delta, or a typed `{ to, search }` object).
21
+ * Stable across renders; resolves the browser navigate at call time, so it works as soon as
22
+ * `installHistory` has run and no-ops before then / on the server. */
23
+ export declare function useNavigate(): NavigateFunction;
24
+ /**
25
+ * Guard navigation away from a page with unsaved work, confirming with your OWN async UI. Mirrors
26
+ * react-router's `useBlocker`: pass a boolean (`useBlocker(isDirty)`) or a predicate
27
+ * `({ currentLocation, nextLocation }) => boolean`, and get back a {@link Blocker}. When a navigation
28
+ * (an anchor click, `useNavigate`, or a browser back/forward) is intercepted, `blocker.state` becomes
29
+ * `"blocked"` and `proceed`/`reset` go live - render a dialog and call `proceed()` to continue or
30
+ * `reset()` to stay put. It also arms the browser's native "Leave site?" prompt on tab close / reload.
31
+ * Idle (never blocks) on the server and before hydration.
32
+ */
33
+ export declare function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker;
34
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACzE,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EAGpB,KAAK,gBAAgB,EAKtB,MAAM,cAAc,CAAA;AAWrB,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAK5F;2GAC2G;AAC3G,eAAO,MAAM,aAAa,mDAAuD,CAAA;AAEjF;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,MAAM,SAAS,gBAAgB,GAAG,SAAS,GAAG,SAAS,KACpD,MAAM,SAAS,gBAAgB,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAInF;AAED;;sEAEsE;AACtE,wBAAgB,WAAW,IAAI,gBAAgB,CAO9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,OAAO,GAAG,eAAe,GAAG,OAAO,CAe1E"}