@weftui/router 0.27.1 → 0.29.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 (38) hide show
  1. package/README.md +3 -3
  2. package/dist/client/index.d.ts +21 -21
  3. package/dist/client/index.js +15 -15
  4. package/dist/{compile-BJFIgBbE.d.ts → compile-Bb7AknG_.d.ts} +46 -62
  5. package/dist/{href-uvJ6b7zz.js → href-3swSSbn_.js} +7 -10
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +2 -2
  8. package/dist/{outlet-eoUQ-W0k.d.ts → outlet-2AnUWKQD.d.ts} +1 -2
  9. package/dist/{outlet-C9N4a4_F.js → outlet-BgJmsO_g.js} +38 -46
  10. package/dist/server/index.d.ts +9 -10
  11. package/dist/server/index.js +10 -10
  12. package/docs/explanation/boundaries-and-suspense.md +37 -18
  13. package/docs/explanation/combinator-api.md +14 -12
  14. package/docs/explanation/reactive-primitives.md +15 -15
  15. package/docs/explanation/rendering-model.md +20 -18
  16. package/docs/explanation/services-and-context.md +39 -29
  17. package/docs/how-to/add-routing.md +76 -52
  18. package/docs/how-to/author-components.md +46 -32
  19. package/docs/how-to/compose-behavior-and-markup.md +144 -0
  20. package/docs/how-to/handle-forms.md +6 -6
  21. package/docs/how-to/load-async-data.md +15 -13
  22. package/docs/how-to/load-data-with-rpc.md +34 -30
  23. package/docs/how-to/provide-services.md +54 -74
  24. package/docs/how-to/render-keyed-lists.md +10 -8
  25. package/docs/how-to/render-on-the-server.md +19 -14
  26. package/docs/how-to/show-navigation-progress.md +10 -8
  27. package/docs/how-to/split-routes-lazily.md +16 -14
  28. package/docs/how-to/style-reactively.md +13 -13
  29. package/docs/how-to/use-element-refs.md +10 -8
  30. package/docs/index.md +20 -18
  31. package/docs/reference/core.md +44 -40
  32. package/docs/reference/dom.md +395 -58
  33. package/docs/reference/router.md +71 -49
  34. package/docs/tutorial/01-your-first-app.md +10 -11
  35. package/docs/tutorial/02-reactivity.md +11 -8
  36. package/docs/tutorial/03-services-and-async.md +19 -13
  37. package/docs/tutorial/04-errors-and-server.md +17 -7
  38. package/package.json +10 -9
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @weftui/router
2
2
 
3
- > Universal nested router for [Weft](https://weftui.dev) one route tree, rendered on the server and the client, with type-safe params and `href`s.
3
+ > Universal nested router for [Weft](https://weftui.dev): one route tree, rendered on the server and the client, with type-safe params and `href`s.
4
4
 
5
5
  Maps a URL to a nested page tree that renders identically on the server (`@weftui/router/server`) and the client (`@weftui/router/client`). Route params and query are decoded through [Effect Schema](https://effect.website/docs/schema/introduction/), `href` builds type-safe URLs that round-trip with the matcher, layouts persist across navigations, and `Router.lazy` code-splits a branch while keeping its descriptor eager.
6
6
 
@@ -26,7 +26,7 @@ Weft tracks Effect 4's beta line. This release is built and tested against `effe
26
26
  | `Router.params` / `Router.query` | Read the live match (snapshot); `…Stream` variants for reactive query-in-place updates. |
27
27
  | `Router.navigating` | Reactive `Idle`/`Navigating` signal for pending UI during deferred-commit navigation. |
28
28
  | `href(ref, args)` | Builds a type-safe URL for a leaf route reference. |
29
- | `RouterApp` / `RouterOutlet` | The universal router root node render on both server and client. |
29
+ | `RouterApp` / `RouterOutlet` | The universal router root node: render on both server and client. |
30
30
  | `RouterLive` (client) | History-backed `Router` layer; also provides the `AppRpcClientTag` seam. |
31
31
  | `RouterServer` (server) | `RouterServer.render` / `RouterServer.toWebHandler` for SSR dispatch. |
32
32
 
@@ -54,7 +54,7 @@ href(userRoute, { path: { id: 42 } }); // "/users/42"
54
54
  - Full docs: **https://weftui.dev**
55
55
  - `@weftui/router` API reference: **https://weftui.dev/docs/reference/router**
56
56
  - Routing guide: **https://weftui.dev/docs/how-to/add-routing**
57
- - Bundled with this package: see the [`./docs`](./docs) directory in `node_modules/@weftui/router/docs` the complete tutorial, how-to, explanation, and reference tree ships on disk for offline and agent use.
57
+ - Bundled with this package: see the [`./docs`](./docs) directory in `node_modules/@weftui/router/docs`. The complete tutorial, how-to, explanation, and reference tree ships on disk for offline and agent use.
58
58
 
59
59
  ## License
60
60
 
@@ -1,18 +1,17 @@
1
- import { S as Router, b as NavState, i as RouterDef, m as RouteNode, u as Fields, x as NavigateOptions } from "../compile-BJFIgBbE.js";
2
- import { n as outletNode, r as HrefArgs, t as RouterApp } from "../outlet-eoUQ-W0k.js";
1
+ import { S as Router, b as NavState, i as RouterDef, m as RouteNode, u as Fields, x as NavigateOptions } from "../compile-Bb7AknG_.js";
2
+ import { n as outletNode, r as HrefArgs, t as RouterApp } from "../outlet-2AnUWKQD.js";
3
3
  import { Effect, Layer, Scope } from "effect";
4
4
  import { AppRpcClientTag } from "@weftui/core";
5
5
  import { RpcGroup } from "effect/unstable/rpc";
6
-
7
6
  //#region src/client/router-live.d.ts
8
7
  /**
9
8
  * The residual app services a caller must still provide through the {@link RouterLiveOptions.context}
10
- * seam a def's aggregate `R` minus the services `RouterLive` already threads
9
+ * seam: a def's aggregate `R` minus the services `RouterLive` already threads
11
10
  * (`Router`, `Router.Outlet`, `AppRpcClientTag`). The client mirror of
12
11
  * `RouterServer.AppServices`; resolves to `never` for an app with no app-wide service.
13
12
  */
14
13
  type AppServices<R> = Exclude<R, Router | Router.Outlet | AppRpcClientTag>;
15
- /** True only for the exact `any` type a loosely-typed `RouterDef<any, any>`. */
14
+ /** True only for the exact `any` type (a loosely-typed `RouterDef<any, any>`). */
16
15
  type IsAny<T> = 0 extends 1 & T ? true : false;
17
16
  /**
18
17
  * Conditionally shapes the `context` field: **required** when the def has statically
@@ -38,13 +37,14 @@ interface RouterLiveOptions {
38
37
  /**
39
38
  * The app's `Boundary.rpc` foundation: the merged `RpcGroup` contract (shared
40
39
  * with the server handler Layer). Backs the {@link AppRpcClientTag} seam so a
41
- * hydrated boundary refetch and a client-first SPA mount resolve over the
42
- * network rpc client. Optional: omit when the app has no `Boundary.rpc` — then
40
+ * hydrated boundary refetch (and a client-first SPA mount) resolve over the
41
+ * network rpc client. Optional: omit when the app has no `Boundary.rpc`. Then
43
42
  * no network rpc client is built and a stray `Boundary.rpc` fails with a
44
43
  * descriptive error.
45
44
  */
46
45
  readonly rpc?: {
47
- /** The app's merged `RpcGroup` (pure Schema contract). */readonly group: RpcGroup.RpcGroup<any>;
46
+ /** The app's merged `RpcGroup` (pure Schema contract). */
47
+ readonly group: RpcGroup.RpcGroup<any>;
48
48
  };
49
49
  }
50
50
  /**
@@ -57,11 +57,11 @@ interface RouterLiveOptions {
57
57
  * It additionally derives a real {@link RouterHttpApiClient} from `def.httpApi`
58
58
  * (over `FetchHttpClient`, `baseUrl` default same-origin) and exposes it on the
59
59
  * `Router` service for network work. SPA URL→leaf resolution stays local via the
60
- * shared {@link match}er both sides read the one `def.httpApi` definition.
60
+ * shared {@link match}er: both sides read the one `def.httpApi` definition.
61
61
  *
62
- * Alongside `Router` it provides the core {@link AppRpcClientTag} seam a
62
+ * Alongside `Router` it provides the core {@link AppRpcClientTag} seam: a
63
63
  * **network** flat rpc client (`RpcClient.make` over `layerProtocolHttp` →
64
- * `POST /_eui/rpc`) so `@weftui/dom` can resolve a `Boundary.rpc` (hydrated
64
+ * `POST /_eui/rpc`), so `@weftui/dom` can resolve a `Boundary.rpc` (hydrated
65
65
  * refetch and client-first mount) without depending on this package or
66
66
  * `effect/unstable/rpc`.
67
67
  */
@@ -74,8 +74,8 @@ declare function RouterLive<R>(def: RouterDef<any, R>, options?: RouterLiveOptio
74
74
  * the href resolves to a route (L1). Modified clicks, non-left buttons,
75
75
  * `target=_blank`, `download`, external origins, same-document (hash-only or
76
76
  * identical-URL) navigations, and non-matching hrefs fall through to the
77
- * browser's native handling the interceptor leaves `preventDefault` untouched
78
- * in those cases (L2). The listener is removed on scope teardown (L3).
77
+ * browser's native handling. In those cases the interceptor leaves
78
+ * `preventDefault` untouched (L2). The listener is removed on scope teardown (L3).
79
79
  *
80
80
  * @param def - The router definition, used to decide whether an href matches a route.
81
81
  * @param navigate - The router's `navigate`, run via the captured runtime on a match.
@@ -88,10 +88,10 @@ declare function installLinkInterceptor(def: RouterDef, navigate: (to: string) =
88
88
  * type-safe {@link href} builder. They mirror the History API the client `Router`
89
89
  * layer (`RouterLive`) is backed by:
90
90
  *
91
- * - {@link navigate} go to a leaf route reference with typed `{ path, query }`.
92
- * - {@link push} / {@link replace} go to a raw `path + search` string.
93
- * - {@link back} / {@link forward} step through History (`history.go`).
94
- * - {@link setQuery} / {@link patchQuery} change the current route's query in
91
+ * - {@link navigate}: go to a leaf route reference with typed `{ path, query }`.
92
+ * - {@link push} / {@link replace}: go to a raw `path + search` string.
93
+ * - {@link back} / {@link forward}: step through History (`history.go`).
94
+ * - {@link setQuery} / {@link patchQuery}: change the current route's query in
95
95
  * place, re-encoding through the matched leaf's `querySchema`.
96
96
  *
97
97
  * All but `back`/`forward` require the `Router` service (run them within the layer
@@ -99,10 +99,10 @@ declare function installLinkInterceptor(def: RouterDef, navigate: (to: string) =
99
99
  */
100
100
  /**
101
101
  * Navigates to a leaf route `ref` with typed `path`/`query` args, building the URL
102
- * via {@link href} (so it round-trips with `match`) and pushing or, with
103
- * `options.replace`, replacing the History entry. `path` is required when the
104
- * route has path params; `query` is optional when every query field is optional
105
- * (same requiredness rules as `href`).
102
+ * via {@link href} (so it round-trips with `match`) and pushing the History entry,
103
+ * or replacing it with `options.replace`. `path` is required when the route has
104
+ * path params; `query` is optional when every query field is optional (same
105
+ * requiredness rules as `href`).
106
106
  *
107
107
  * @example
108
108
  * ```ts
@@ -1,5 +1,5 @@
1
- import { a as Router, i as setResolvedCommit, n as outletNode, o as getPreload, r as preRunLeaf, t as RouterApp } from "../outlet-C9N4a4_F.js";
2
- import { r as match, t as href } from "../href-uvJ6b7zz.js";
1
+ import { a as Router, i as setResolvedCommit, n as outletNode, o as getPreload, r as preRunLeaf, t as RouterApp } from "../outlet-BgJmsO_g.js";
2
+ import { r as match, t as href } from "../href-3swSSbn_.js";
3
3
  import { HttpApiClient } from "effect/unstable/httpapi";
4
4
  import { Context, Effect, Exit, Fiber, Layer, Option, Schema, Scope, Stream, SubscriptionRef } from "effect";
5
5
  import { AppRpcClientTag, Subscribable } from "@weftui/core";
@@ -11,8 +11,8 @@ import { FetchHttpClient } from "effect/unstable/http";
11
11
  * the href resolves to a route (L1). Modified clicks, non-left buttons,
12
12
  * `target=_blank`, `download`, external origins, same-document (hash-only or
13
13
  * identical-URL) navigations, and non-matching hrefs fall through to the
14
- * browser's native handling the interceptor leaves `preventDefault` untouched
15
- * in those cases (L2). The listener is removed on scope teardown (L3).
14
+ * browser's native handling. In those cases the interceptor leaves
15
+ * `preventDefault` untouched (L2). The listener is removed on scope teardown (L3).
16
16
  *
17
17
  * @param def - The router definition, used to decide whether an href matches a route.
18
18
  * @param navigate - The router's `navigate`, run via the captured runtime on a match.
@@ -68,11 +68,11 @@ function normalizeTo(to) {
68
68
  * It additionally derives a real {@link RouterHttpApiClient} from `def.httpApi`
69
69
  * (over `FetchHttpClient`, `baseUrl` default same-origin) and exposes it on the
70
70
  * `Router` service for network work. SPA URL→leaf resolution stays local via the
71
- * shared {@link match}er both sides read the one `def.httpApi` definition.
71
+ * shared {@link match}er: both sides read the one `def.httpApi` definition.
72
72
  *
73
- * Alongside `Router` it provides the core {@link AppRpcClientTag} seam a
73
+ * Alongside `Router` it provides the core {@link AppRpcClientTag} seam: a
74
74
  * **network** flat rpc client (`RpcClient.make` over `layerProtocolHttp` →
75
- * `POST /_eui/rpc`) so `@weftui/dom` can resolve a `Boundary.rpc` (hydrated
75
+ * `POST /_eui/rpc`), so `@weftui/dom` can resolve a `Boundary.rpc` (hydrated
76
76
  * refetch and client-first mount) without depending on this package or
77
77
  * `effect/unstable/rpc`.
78
78
  */
@@ -187,10 +187,10 @@ function RouterLive(def, options = {}) {
187
187
  * type-safe {@link href} builder. They mirror the History API the client `Router`
188
188
  * layer (`RouterLive`) is backed by:
189
189
  *
190
- * - {@link navigate} go to a leaf route reference with typed `{ path, query }`.
191
- * - {@link push} / {@link replace} go to a raw `path + search` string.
192
- * - {@link back} / {@link forward} step through History (`history.go`).
193
- * - {@link setQuery} / {@link patchQuery} change the current route's query in
190
+ * - {@link navigate}: go to a leaf route reference with typed `{ path, query }`.
191
+ * - {@link push} / {@link replace}: go to a raw `path + search` string.
192
+ * - {@link back} / {@link forward}: step through History (`history.go`).
193
+ * - {@link setQuery} / {@link patchQuery}: change the current route's query in
194
194
  * place, re-encoding through the matched leaf's `querySchema`.
195
195
  *
196
196
  * All but `back`/`forward` require the `Router` service (run them within the layer
@@ -198,10 +198,10 @@ function RouterLive(def, options = {}) {
198
198
  */
199
199
  /**
200
200
  * Navigates to a leaf route `ref` with typed `path`/`query` args, building the URL
201
- * via {@link href} (so it round-trips with `match`) and pushing or, with
202
- * `options.replace`, replacing the History entry. `path` is required when the
203
- * route has path params; `query` is optional when every query field is optional
204
- * (same requiredness rules as `href`).
201
+ * via {@link href} (so it round-trips with `match`) and pushing the History entry,
202
+ * or replacing it with `options.replace`. `path` is required when the route has
203
+ * path params; `query` is optional when every query field is optional (same
204
+ * requiredness rules as `href`).
205
205
  *
206
206
  * @example
207
207
  * ```ts
@@ -1,10 +1,10 @@
1
1
  import { HttpApi, HttpApiClient } from "effect/unstable/httpapi";
2
2
  import { Context, Effect, Option, Schema } from "effect";
3
3
  import { Component, Node, Subscribable } from "@weftui/core";
4
-
5
4
  //#region src/errors.d.ts
6
5
  declare const RouterNotFound_base: Schema.Class<RouterNotFound, Schema.TaggedStruct<"RouterNotFound", {
7
- /** The path that could not be resolved, when known. */readonly path: Schema.optional<Schema.String>;
6
+ /** The path that could not be resolved, when known. */
7
+ readonly path: Schema.optional<Schema.String>;
8
8
  }>, import("effect/Cause").YieldableError>;
9
9
  /**
10
10
  * Tagged error raised by {@link notFound} and caught by the router's internal
@@ -28,12 +28,14 @@ declare const notFound: (path?: string) => Effect.Effect<never, RouterNotFound>;
28
28
  /** Type guard recognising a {@link RouterNotFound} value regardless of its prototype. */
29
29
  declare const isRouterNotFound: (u: unknown) => u is RouterNotFound;
30
30
  declare const RouterParamsError_base: Schema.Class<RouterParamsError, Schema.TaggedStruct<"RouterParamsError", {
31
- /** Which side of the match failed validation. */readonly source: Schema.Literals<readonly ["path", "query"]>; /** The requested field names, for diagnostics. */
31
+ /** Which side of the match failed validation. */
32
+ readonly source: Schema.Literals<readonly ["path", "query"]>;
33
+ /** The requested field names, for diagnostics. */
32
34
  readonly keys: Schema.$Array<Schema.String>;
33
35
  }>, import("effect/Cause").YieldableError>;
34
36
  /**
35
37
  * Tagged error raised by `Router.params` / `Router.query` when the live match does
36
- * not satisfy the requested fields either no route is matched, or a requested
38
+ * not satisfy the requested fields: either no route is matched, or a requested
37
39
  * key is missing / fails its schema's `Type`-side validation. `source` records
38
40
  * whether the failure was on the path params or the query, and `keys` lists the
39
41
  * requested field names for diagnostics.
@@ -52,7 +54,8 @@ type RouteMatch = {
52
54
  readonly _tag: "Matched";
53
55
  readonly leaf: CompiledLeaf;
54
56
  readonly path: Record<string, unknown>;
55
- readonly query: Record<string, unknown>; /** Normalized request URL (path + search), used by the outlet as a dedupe key. */
57
+ readonly query: Record<string, unknown>;
58
+ /** Normalized request URL (path + search), used by the outlet as a dedupe key. */
56
59
  readonly url: string;
57
60
  } | {
58
61
  readonly _tag: "NotFound";
@@ -73,19 +76,16 @@ interface MatcherEntry {
73
76
  }
74
77
  /**
75
78
  * Precompiles a {@link RouterDef} into ordered matcher entries (memoized per
76
- * `RouterDef`). The patterns and path/query schemas are read from the authoritative
77
- * `def.httpApi` `"pages"` endpoints the single source of truth the server dispatch
78
- * also reads and each entry's render metadata leaf is resolved from `def.compiled`
79
+ * `RouterDef`). Patterns and path/query schemas are read from the authoritative
80
+ * `def.httpApi` `"pages"` endpoints (the single source of truth the server dispatch
81
+ * also reads), and each entry's render metadata leaf is resolved from `def.compiled`
79
82
  * by endpoint id. Matching stays local (SPA URL→leaf); see the refactor plan's
80
83
  * _Feasibility constraint_.
81
84
  *
82
85
  * Entries are sorted most-specific first (fewer params, then longer pattern) so a
83
- * static segment wins over a param segment at the same position (M6).
84
- *
85
- * Note: the specificity order is a global heuristic (param count, then length).
86
- * It resolves the common "static beats param at the same position" case, but two
87
- * patterns with the same param count and length (e.g. `/a/:b/c` vs `/a/x/:d`)
88
- * fall back to endpoint order.
86
+ * static segment wins over a param segment at the same position (M6). That order is
87
+ * a global heuristic, so two patterns with the same param count and length (e.g.
88
+ * `/a/:b/c` vs `/a/x/:d`) fall back to endpoint order.
89
89
  */
90
90
  declare function compileMatchers(def: RouterDef): readonly MatcherEntry[];
91
91
  /**
@@ -98,7 +98,7 @@ declare function match(def: RouterDef, url: string): RouteMatch;
98
98
  //#endregion
99
99
  //#region src/router-service.d.ts
100
100
  /**
101
- * The universal router service. Provided per render by `RouterLive` on the
101
+ * The universal router service. Provided per render: by `RouterLive` on the
102
102
  * client (History-API backed) and by a fixed per-request implementation on the
103
103
  * server. Layouts and pages read it anywhere via `yield* Router`.
104
104
  *
@@ -106,12 +106,12 @@ declare function match(def: RouterDef, url: string): RouteMatch;
106
106
  * {@link Router.layout}, and {@link Router.router} build the route tree (mirroring
107
107
  * `Component.gen` / `Boundary.catchTag` / `h.div`), and {@link Router.Outlet} /
108
108
  * {@link Router.params} / {@link Router.query} deliver the outlet and the live
109
- * match's params/query by dependency injection. The roles merge by declaration
109
+ * match's params/query by dependency injection. The roles merge by declaration:
110
110
  * `yield* Router` reads the service; `Router.route(…)` authors a tree.
111
111
  */
112
112
  /**
113
113
  * The platform `HttpApiClient` derived from a router's `HttpApi` spine. Typed
114
- * opaquely (`Client<any, …>`) because the spine is `HttpApi.Top` its
114
+ * opaquely (`Client<any, …>`) because the spine is `HttpApi.Top`: its
115
115
  * group/endpoint shapes are assembled in a runtime loop by `buildHttpApi`, so a
116
116
  * precise client type is not recoverable. Present (`Option.some`) on the client
117
117
  * (`RouterLive`), absent (`Option.none`) on the server, which is itself the origin.
@@ -142,7 +142,8 @@ interface NavigateOptions {
142
142
  readonly replace?: boolean;
143
143
  }
144
144
  declare const Router_base: Context.ServiceClass<Router, "@weftui/router/Router", {
145
- /** The current match as a hot `Subscribable`; drives the outlet. */readonly currentMatch: Subscribable.Subscribable<RouteMatch>;
145
+ /** The current match as a hot `Subscribable`; drives the outlet. */
146
+ readonly currentMatch: Subscribable.Subscribable<RouteMatch>;
146
147
  /**
147
148
  * Navigates to `to` (a path, optionally with a query). On the client this
148
149
  * pushes History state and re-renders the affected outlet; on the server it
@@ -152,8 +153,8 @@ declare const Router_base: Context.ServiceClass<Router, "@weftui/router/Router",
152
153
  /**
153
154
  * The derived {@link RouterHttpApiClient} for network work (route prefetch,
154
155
  * foundation for future loaders/data). `Option.some` on the client,
155
- * `Option.none` on the server. SPA URL→leaf resolution does **not** use this
156
- * it stays local via the shared matcher (see the refactor _Feasibility constraint_).
156
+ * `Option.none` on the server. SPA URL→leaf resolution does **not** use this.
157
+ * It stays local via the shared matcher (see the refactor _Feasibility constraint_).
157
158
  */
158
159
  readonly httpApiClient: Option.Option<RouterHttpApiClient>;
159
160
  /**
@@ -174,17 +175,17 @@ declare const OutletTag_base: Context.ServiceClass<OutletTag, "@weftui/router/Ou
174
175
  * reads it with `yield* Router.Outlet`.
175
176
  *
176
177
  * Typed **opaque** as `Node<never, never>` so splicing `[outlet]` adds nothing to
177
- * a layout's local channels the subtree's real channels are aggregated
178
+ * a layout's local channels: the subtree's real channels are aggregated
178
179
  * structurally by {@link makeLayout} / {@link makeRouter}, never inferred across
179
180
  * this DI boundary. Re-exported on the namespace as `Router.Outlet`.
180
181
  */
181
182
  declare class OutletTag extends OutletTag_base {}
182
183
  /**
183
184
  * Reads the live match's **path params** for the requested `fields`. Snapshot
184
- * semantics reads `yield* Router` then `currentMatch.get` and returns the
185
+ * semantics (reads `yield* Router`, then `currentMatch.get`), returning the
185
186
  * already-decoded values **directly**: the matcher decoded them against the leaf's
186
- * full path schema, so no re-validation is needed (the cast is sound the picked
187
- * subset is the `Type` side of `fields`). Fails with a {@link RouterParamsError}
187
+ * full path schema, so no re-validation is needed. The cast is sound because the
188
+ * picked subset is the `Type` side of `fields`. Fails with a {@link RouterParamsError}
188
189
  * (`source: "path"`) only when no route is matched. Re-exported as `Router.params`.
189
190
  */
190
191
  declare function readParams<F extends Fields>(fields: F): Effect.Effect<FieldsType<F>, RouterParamsError, Router>;
@@ -248,7 +249,7 @@ type FieldsType<F extends Fields> = Schema.Struct.Type<F>;
248
249
  * The router passes `{ path, query }` into the leaf slot at render time (the
249
250
  * {@link makeRoute} props-form overload), so a page can read them directly as props
250
251
  * instead of via the `Router.params` / `Router.query` dependency-injection
251
- * accessors. Layouts and deeper nodes which can't take handler args — keep DI.
252
+ * accessors. Layouts and deeper nodes keep DI, since they can't take handler args.
252
253
  */
253
254
  interface RouteHandlerProps<Path extends Fields = {}, Query extends Fields = {}> {
254
255
  /** The live match's decoded path params (`Type` side of the route's `path`). */
@@ -260,26 +261,26 @@ interface RouteHandlerProps<Path extends Fields = {}, Query extends Fields = {}>
260
261
  * The shape of a route/layout `component` slot: a callable producing a {@link Node},
261
262
  * invoked by the router at render time. It accepts both a plain zero-arg thunk
262
263
  * (`() => h.div(…)`) and a {@link Component} produced by `Component.make` /
263
- * `Component.gen` (a generic `(props, children?) => Node`). The `props: any` arm
264
- * rather than `()` is what keeps a required-props `Component<…>` structurally
264
+ * `Component.gen` (a generic `(props, children?) => Node`). The `props: any` arm,
265
+ * rather than `()`, is what keeps a required-props `Component<…>` structurally
265
266
  * assignable; the router calls the slot with no arguments.
266
267
  */
267
268
  type ComponentSlot<N extends Node<any, any> = Node<any, any>> = (props: any) => N;
268
269
  /**
269
270
  * The {@link Node} a {@link ComponentSlot} produces when the router invokes it with no
270
- * props/children used to recover the slot's `E`/`R` channels for the route tree.
271
+ * props/children. Used to recover the slot's `E`/`R` channels for the route tree.
271
272
  *
272
273
  * A plain zero-arg thunk is matched first (`() => infer N`): a required-props
273
274
  * `Component` is *not* assignable to `() => unknown`, so it falls through to the
274
275
  * `Component` arm, where the internal `E`/`R` type parameters are read directly. This
275
- * two-step form is deliberate `ReturnType<S>` collapses a generic `Component`'s
276
+ * two-step form is deliberate: `ReturnType<S>` collapses a generic `Component`'s
276
277
  * channels to `unknown` (they depend on the erased `GenP`/`GenC`), whereas extracting
277
278
  * the `Component<…, E, R>` parameters preserves them. Caller prop/children channels are
278
279
  * never relevant here because the router supplies neither.
279
280
  */
280
281
  type SlotNode<S> = S extends (() => infer N) ? N : S extends Component.Component<any, any, infer E, infer R> ? Node<E, R> : never;
281
282
  /**
282
- * A leaf page in the route tree. Its `component` *is* its handler a
283
+ * A leaf page in the route tree. Its `component` *is* its handler: a
283
284
  * {@link ComponentSlot} (a `Component.gen` / `Component.make` component, or a plain
284
285
  * `() => Node` thunk) that the router invokes at render time and that reads the live
285
286
  * match's params via `Router.params` / `Router.query`. `Path`/`Query` drive matching
@@ -300,7 +301,7 @@ interface RouteNode<Path extends Fields = {}, Query extends Fields = {}, E = nev
300
301
  }
301
302
  /**
302
303
  * A layout wrapping an outlet (the next level down) in the route tree. A layout is
303
- * **purely UI nesting** it owns **no path or segment**; all path structure lives
304
+ * **purely UI nesting**: it owns **no path or segment**; all path structure lives
304
305
  * on routes. Its `component` is a {@link ComponentSlot} that splices the injected
305
306
  * outlet via `yield* Router.Outlet`; the router invokes it per render and discharges
306
307
  * that `Outlet` requirement. A layout that needs a param reads it via `Router.params`.
@@ -315,7 +316,7 @@ interface LayoutNode<E = never, R = never> {
315
316
  /**
316
317
  * Phantom marker for this layout subtree's aggregate error channel (see
317
318
  * {@link TreeE}). Covariant (stores `E` directly) so a fully-discharged layout
318
- * (`LayoutNode<never, never>` its `Outlet` provided, no subtree errors) stays
319
+ * (`LayoutNode<never, never>`, its `Outlet` provided and no subtree errors) stays
319
320
  * assignable to the `LayoutNode<any, any>` arm of {@link TreeNode}.
320
321
  */
321
322
  readonly _E?: E;
@@ -333,26 +334,17 @@ type SubtreeE<C extends readonly TreeNode[]> = TreeE<C[number]>;
333
334
  /** Aggregate requirement channel over a children tuple (distributes over `C[number]`). */
334
335
  type SubtreeR<C extends readonly TreeNode[]> = TreeR<C[number]>;
335
336
  /**
336
- * Declares a leaf page. The `component` *is* the route handler a thunk the
337
+ * Declares a leaf page. The `component` *is* the route handler: a thunk the
337
338
  * router invokes at render time; its error / requirement channels propagate up the
338
339
  * tree. Two authoring forms are accepted:
339
340
  *
340
- * - **Handler-arg props** the slot declares `(props: {@link RouteHandlerProps})`
341
+ * - **Handler-arg props**: the slot declares `(props: {@link RouteHandlerProps})`
341
342
  * and the router passes the live match's decoded `{ path, query }` in directly
342
343
  * (first overload; `path`/`query` are inferred from the route's `path`/`query`
343
- * fields). A plain zero-arg thunk works too it just ignores the props.
344
- * - **Dependency injection** a `Component.make` / `Component.gen` component that
344
+ * fields). A plain zero-arg thunk works too. It just ignores the props.
345
+ * - **Dependency injection**: a `Component.make` / `Component.gen` component that
345
346
  * reads the live match via `Router.params` / `Router.query` (second overload).
346
347
  *
347
- * @example Handler-arg props (decoded `{ path, query }`)
348
- * ```ts
349
- * Router.route("users/:id", {
350
- * path: { id: Schema.NumberFromString },
351
- * query: { tab: Schema.optional(Schema.String) },
352
- * component: ({ path, query }) => h.div({}, `User ${path.id} (${query.tab ?? "info"})`),
353
- * });
354
- * ```
355
- *
356
348
  * @example Dependency injection (`Router.params` / a `Component`)
357
349
  * ```ts
358
350
  * Router.route("about", { component: Component.make(() => h.h1({}, "About")) });
@@ -400,24 +392,16 @@ declare function makeLayout<C extends readonly TreeNode[], S extends ComponentSl
400
392
  }, children: C): LayoutNode<Node.Error<SlotNode<S>> | SubtreeE<C>, Exclude<Node.Context<SlotNode<S>>, Router.Outlet> | SubtreeR<C>>;
401
393
  /**
402
394
  * Wraps a dynamic-import `load` as a lazy {@link ComponentSlot}: the route's descriptor
403
- * (`segment`, `path`/`query`) stays eager and matchable, while the component the render
404
- * body and its module's deps is split into the chunk `load` resolves. The router invokes
395
+ * (`segment`, `path`/`query`) stays eager and matchable, while the component (the render
396
+ * body and its module's deps) is split into the chunk `load` resolves. The router invokes
405
397
  * the returned slot at render time; it awaits `load` then renders the resolved component,
406
398
  * adopting the server DOM in place on hydration (flash-free) and fetching the chunk on
407
399
  * client navigation. Exposed as {@link Router.lazy}. See `lazy-component.specs.md`.
408
400
  *
409
401
  * The resolved value is a component slot (`Component.gen` / `Component.make`, or a
410
- * `() => Node` thunk) the shape `component:` already accepts so its `E`/`R` channels
402
+ * `() => Node` thunk), the shape `component:` already accepts, so its `E`/`R` channels
411
403
  * are recovered via {@link SlotNode} and propagate up the tree exactly as an eager
412
404
  * component's do.
413
- *
414
- * @example
415
- * ```ts
416
- * Router.route("docs/:category/:slug", {
417
- * path: { category: Schema.String, slug: Schema.String },
418
- * component: Router.lazy(() => import("./doc-page").then((m) => m.DocPage)),
419
- * });
420
- * ```
421
405
  */
422
406
  declare function lazyComponent<S extends ComponentSlot>(load: () => Promise<S>): () => Node<Node.Error<SlotNode<S>>, Node.Context<SlotNode<S>>>;
423
407
  //#endregion
@@ -426,7 +410,7 @@ declare function lazyComponent<S extends ComponentSlot>(load: () => Promise<S>):
426
410
  * A compiled layout level: its component slot plus the dedupe `patternPrefix` used
427
411
  * by the client outlet to key the level. A layout owns no path of its own, so the
428
412
  * prefix is derived as the **longest common path-segment prefix of every leaf in
429
- * the layout's subtree** it changes (and the level re-renders) exactly when a
413
+ * the layout's subtree**: it changes (and the level re-renders) exactly when a
430
414
  * param shared by all those leaves changes, and persists otherwise.
431
415
  */
432
416
  interface CompiledLayout {
@@ -452,7 +436,7 @@ interface CompiledLeaf {
452
436
  /**
453
437
  * Path-param schema. Its **encoded** side is typed string-encodeable
454
438
  * (`Record<string, string | undefined>`) so it satisfies platform's
455
- * `HttpApiEndpoint` `params` constraint without an `as any` cast param schemas
439
+ * `HttpApiEndpoint` `params` constraint without an `as any` cast: param schemas
456
440
  * round-trip strings, so the `Schema.Struct` value is asserted to this shape.
457
441
  */
458
442
  readonly pathSchema: Schema.Codec<Record<string, unknown>, Readonly<Record<string, string | undefined>>>;
@@ -516,9 +500,9 @@ declare const leafRegistry: WeakMap<RouteNode<any, any, any, any>, CompiledLeaf>
516
500
  * Pass 1 walks the tree: only **routes** contribute path parts (layouts own no
517
501
  * path), so each leaf's `parts` come solely from the route segments on its branch,
518
502
  * and its ancestor `LayoutNode`s are recorded in order. Pass 2 derives one shared
519
- * {@link CompiledLayout} per distinct layout node its `patternPrefix` is the
520
- * longest common path prefix of that layout's subtree leaves then assembles each
521
- * leaf's `layoutChain` (root → parent) and merged path schema.
503
+ * {@link CompiledLayout} per distinct layout node, whose `patternPrefix` is the
504
+ * longest common path prefix of that layout's subtree leaves. It then assembles
505
+ * each leaf's `layoutChain` (root → parent) and merged path schema.
522
506
  */
523
507
  declare function compile(def: {
524
508
  root: TreeNode;
@@ -526,7 +510,7 @@ declare function compile(def: {
526
510
  }): Compiled;
527
511
  /**
528
512
  * Builds the authoritative `HttpApi` for a compiled tree (S4): a single `"pages"`
529
- * group whose endpoints are GET endpoints — one per leaf at each leaf's full path
513
+ * group holding one GET endpoint per leaf, at each leaf's full path
530
514
  * pattern, carrying `params: pathSchema`, `query: querySchema`, a
531
515
  * `Schema.String` (text/HTML) success, and a `RouterNotFound → 404` error. The tree
532
516
  * (not `HttpApi`) is the authoring surface; this is the single source of truth the
@@ -1,4 +1,4 @@
1
- import { l as leafRegistry } from "./outlet-C9N4a4_F.js";
1
+ import { l as leafRegistry } from "./outlet-BgJmsO_g.js";
2
2
  import { Result, Schema } from "effect";
3
3
  //#region src/matcher.ts
4
4
  /** Escapes a literal path segment for inclusion in a `RegExp`. */
@@ -28,19 +28,16 @@ const emptySchema = Schema.Struct({});
28
28
  const matchersCache = /* @__PURE__ */ new WeakMap();
29
29
  /**
30
30
  * Precompiles a {@link RouterDef} into ordered matcher entries (memoized per
31
- * `RouterDef`). The patterns and path/query schemas are read from the authoritative
32
- * `def.httpApi` `"pages"` endpoints the single source of truth the server dispatch
33
- * also reads and each entry's render metadata leaf is resolved from `def.compiled`
31
+ * `RouterDef`). Patterns and path/query schemas are read from the authoritative
32
+ * `def.httpApi` `"pages"` endpoints (the single source of truth the server dispatch
33
+ * also reads), and each entry's render metadata leaf is resolved from `def.compiled`
34
34
  * by endpoint id. Matching stays local (SPA URL→leaf); see the refactor plan's
35
35
  * _Feasibility constraint_.
36
36
  *
37
37
  * Entries are sorted most-specific first (fewer params, then longer pattern) so a
38
- * static segment wins over a param segment at the same position (M6).
39
- *
40
- * Note: the specificity order is a global heuristic (param count, then length).
41
- * It resolves the common "static beats param at the same position" case, but two
42
- * patterns with the same param count and length (e.g. `/a/:b/c` vs `/a/x/:d`)
43
- * fall back to endpoint order.
38
+ * static segment wins over a param segment at the same position (M6). That order is
39
+ * a global heuristic, so two patterns with the same param count and length (e.g.
40
+ * `/a/:b/c` vs `/a/x/:d`) fall back to endpoint order.
44
41
  */
45
42
  function compileMatchers(def) {
46
43
  const cached = matchersCache.get(def);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { A as notFound, C as RouterHttpApiClient, D as RouterNotFound, E as match, O as RouterParamsError, S as Router, T as compileMatchers, _ as TreeE, a as RouterOptions, b as NavState, c as leafRegistry, d as FieldsType, f as LayoutNode, g as SubtreeR, h as SubtreeE, i as RouterDef, k as isRouterNotFound, l as ComponentSlot, m as RouteNode, n as CompiledLayout, o as buildHttpApi, p as RouteHandlerProps, r as CompiledLeaf, s as compile, t as Compiled, u as Fields, v as TreeNode, w as RouteMatch, x as NavigateOptions, y as TreeR } from "./compile-BJFIgBbE.js";
2
- import { i as href, n as outletNode, r as HrefArgs, t as RouterApp } from "./outlet-eoUQ-W0k.js";
1
+ import { A as notFound, C as RouterHttpApiClient, D as RouterNotFound, E as match, O as RouterParamsError, S as Router, T as compileMatchers, _ as TreeE, a as RouterOptions, b as NavState, c as leafRegistry, d as FieldsType, f as LayoutNode, g as SubtreeR, h as SubtreeE, i as RouterDef, k as isRouterNotFound, l as ComponentSlot, m as RouteNode, n as CompiledLayout, o as buildHttpApi, p as RouteHandlerProps, r as CompiledLeaf, s as compile, t as Compiled, u as Fields, v as TreeNode, w as RouteMatch, x as NavigateOptions, y as TreeR } from "./compile-Bb7AknG_.js";
2
+ import { i as href, n as outletNode, r as HrefArgs, t as RouterApp } from "./outlet-2AnUWKQD.js";
3
3
  export { type Compiled, type CompiledLayout, type CompiledLeaf, type ComponentSlot, type Fields, type FieldsType, type HrefArgs, type LayoutNode, type NavState, type NavigateOptions, type RouteHandlerProps, type RouteMatch, type RouteNode, Router, RouterApp, type RouterDef, type RouterHttpApiClient, RouterNotFound, type RouterOptions, RouterParamsError, type SubtreeE, type SubtreeR, type TreeE, type TreeNode, type TreeR, buildHttpApi, compile, compileMatchers, href, isRouterNotFound, leafRegistry, match, notFound, outletNode };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as Router, c as compile, d as RouterParamsError, f as isRouterNotFound, l as leafRegistry, n as outletNode, p as notFound, s as buildHttpApi, t as RouterApp, u as RouterNotFound } from "./outlet-C9N4a4_F.js";
2
- import { n as compileMatchers, r as match, t as href } from "./href-uvJ6b7zz.js";
1
+ import { a as Router, c as compile, d as RouterParamsError, f as isRouterNotFound, l as leafRegistry, n as outletNode, p as notFound, s as buildHttpApi, t as RouterApp, u as RouterNotFound } from "./outlet-BgJmsO_g.js";
2
+ import { n as compileMatchers, r as match, t as href } from "./href-3swSSbn_.js";
3
3
  export { Router, RouterApp, RouterNotFound, RouterParamsError, buildHttpApi, compile, compileMatchers, href, isRouterNotFound, leafRegistry, match, notFound, outletNode };
@@ -1,6 +1,5 @@
1
- import { D as RouterNotFound, S as Router, d as FieldsType, i as RouterDef, m as RouteNode, u as Fields } from "./compile-BJFIgBbE.js";
1
+ import { D as RouterNotFound, S as Router, d as FieldsType, i as RouterDef, m as RouteNode, u as Fields } from "./compile-Bb7AknG_.js";
2
2
  import { Node } from "@weftui/core";
3
-
4
3
  //#region src/href.d.ts
5
4
  /**
6
5
  * The `href` argument object for a leaf with path fields `Path` and query fields