@weftui/router 0.20.9 → 0.22.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/dist/client/index.d.ts +30 -6
- package/dist/client/index.js +1 -1
- package/dist/{compile-C0JShTTR.d.ts → compile-HOyeyWRy.d.ts} +47 -1
- package/dist/{href-Dl30XcF4.js → href-BgbDo3hx.js} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/{outlet-C7p8KXmO.js → outlet-BS31W71_.js} +0 -0
- package/dist/{outlet-BBEjKu3z.d.ts → outlet-DbqTgkXa.d.ts} +1 -1
- package/dist/server/index.d.ts +62 -9
- package/dist/server/index.js +1 -1
- package/package.json +7 -7
package/dist/client/index.d.ts
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import { b as
|
|
2
|
-
import { n as outletNode, r as HrefArgs, t as RouterApp } from "../outlet-
|
|
1
|
+
import { S as Router, b as NavState, i as RouterDef, m as RouteNode, u as Fields, x as NavigateOptions } from "../compile-HOyeyWRy.js";
|
|
2
|
+
import { n as outletNode, r as HrefArgs, t as RouterApp } from "../outlet-DbqTgkXa.js";
|
|
3
3
|
import { Effect, Layer, Scope } from "effect";
|
|
4
4
|
import { AppRpcClientTag } from "@weftui/core";
|
|
5
5
|
import { RpcGroup } from "@effect/rpc";
|
|
6
6
|
|
|
7
7
|
//#region src/client/router-live.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* 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
|
|
11
|
+
* (`Router`, `Router.Outlet`, `AppRpcClientTag`). The client mirror of
|
|
12
|
+
* `RouterServer.AppServices`; resolves to `never` for an app with no app-wide service.
|
|
13
|
+
*/
|
|
14
|
+
type AppServices<R> = Exclude<R, Router | Router.Outlet | AppRpcClientTag>;
|
|
15
|
+
/** True only for the exact `any` type — a loosely-typed `RouterDef<any, any>`. */
|
|
16
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
17
|
+
/**
|
|
18
|
+
* Conditionally shapes the `context` field: **required** when the def has statically
|
|
19
|
+
* known residual {@link AppServices}, **absent** when it has none, and **optional**
|
|
20
|
+
* for a loosely-typed `RouterDef<any, any>`. Client parity with the server seam (AC4)
|
|
21
|
+
* is thus a compile-time guarantee, and no-service / loosely-typed apps stay unchanged (AC3).
|
|
22
|
+
*/
|
|
23
|
+
type ContextOption<R> = [AppServices<R>] extends [never] ? {
|
|
24
|
+
readonly context?: undefined;
|
|
25
|
+
} : IsAny<AppServices<R>> extends true ? {
|
|
26
|
+
readonly context?: Layer.Layer<any, never, never>;
|
|
27
|
+
} : {
|
|
28
|
+
readonly context: Layer.Layer<AppServices<R>, never, never>;
|
|
29
|
+
};
|
|
8
30
|
/** Options for {@link RouterLive}. */
|
|
9
31
|
interface RouterLiveOptions {
|
|
10
32
|
/**
|
|
@@ -17,9 +39,11 @@ interface RouterLiveOptions {
|
|
|
17
39
|
* The app's `Boundary.rpc` foundation: the merged `RpcGroup` contract (shared
|
|
18
40
|
* with the server handler Layer). Backs the {@link AppRpcClientTag} seam so a
|
|
19
41
|
* hydrated boundary refetch — and a client-first SPA mount — resolve over the
|
|
20
|
-
* network rpc client.
|
|
42
|
+
* network rpc client. Optional: omit when the app has no `Boundary.rpc` — then
|
|
43
|
+
* no network rpc client is built and a stray `Boundary.rpc` fails with a
|
|
44
|
+
* descriptive error.
|
|
21
45
|
*/
|
|
22
|
-
readonly rpc
|
|
46
|
+
readonly rpc?: {
|
|
23
47
|
/** The app's merged `RpcGroup` (pure Schema contract). */readonly group: RpcGroup.RpcGroup<any>;
|
|
24
48
|
};
|
|
25
49
|
}
|
|
@@ -41,7 +65,7 @@ interface RouterLiveOptions {
|
|
|
41
65
|
* refetch and client-first mount) without depending on this package or
|
|
42
66
|
* `@effect/rpc`.
|
|
43
67
|
*/
|
|
44
|
-
declare function RouterLive(def: RouterDef, options
|
|
68
|
+
declare function RouterLive<R>(def: RouterDef<any, R>, options?: RouterLiveOptions & ContextOption<R>): Layer.Layer<Router | AppRpcClientTag | AppServices<R>>;
|
|
45
69
|
//#endregion
|
|
46
70
|
//#region src/client/link.d.ts
|
|
47
71
|
/**
|
|
@@ -106,4 +130,4 @@ declare const setQuery: (query: Record<string, unknown>, options?: NavigateOptio
|
|
|
106
130
|
*/
|
|
107
131
|
declare const patchQuery: (partial: Record<string, unknown>, options?: NavigateOptions) => Effect.Effect<void, never, Router>;
|
|
108
132
|
//#endregion
|
|
109
|
-
export { Router, RouterApp, RouterLive, outletNode as RouterOutlet, outletNode, back, forward, installLinkInterceptor, navigate, patchQuery, push, replace, setQuery };
|
|
133
|
+
export { type NavState, Router, RouterApp, RouterLive, outletNode as RouterOutlet, outletNode, back, forward, installLinkInterceptor, navigate, patchQuery, push, replace, setQuery };
|
package/dist/client/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{i as e,n as t,r as n,t as r}from"../outlet-BS31W71_.js";import{r as i,t as a}from"../href-BgbDo3hx.js";import{FetchHttpClient as o,HttpApiClient as s}from"@effect/platform";import{Context as c,Effect as l,Layer as u,Option as d,Runtime as f,Schema as p,Stream as m,Subscribable as h,SubscriptionRef as g}from"effect";import{AppRpcClientTag as _}from"@weftui/core";function v(e,t){return l.gen(function*(){let n=yield*l.runtime(),r=r=>{if(r.defaultPrevented||r.button!==0||r.metaKey||r.ctrlKey||r.shiftKey||r.altKey)return;let a=r.target,o=a instanceof Element?a.closest(`a`):null;if(o===null)return;let s=o.getAttribute(`target`);if(o.hasAttribute(`download`)||s!==null&&s!==`_self`||o.getAttribute(`rel`)===`external`)return;let c=o.getAttribute(`href`);if(c===null||c.length===0)return;let l;try{l=new URL(c,window.location.href)}catch{return}if(l.origin!==window.location.origin)return;let u=`${l.pathname}${l.search}`;u!==`${window.location.pathname}${window.location.search}`&&i(e,u)._tag===`Matched`&&(r.preventDefault(),f.runFork(n)(t(u)))};yield*l.acquireRelease(l.sync(()=>document.addEventListener(`click`,r)),()=>l.sync(()=>document.removeEventListener(`click`,r)))})}function y(){return`${window.location.pathname}${window.location.search}`}function b(e){let t=new URL(e,window.location.href);return`${t.pathname}${t.search}`}function x(t,r={}){let a=u.scopedContext(l.gen(function*(){let a=yield*g.make(y()),p=yield*g.make({_tag:`Idle`}),x=yield*l.runtime(),S=yield*s.make(t.httpApi,{baseUrl:r.baseUrl??window.location.origin}).pipe(l.provide(o.layer)),C=0,w=t=>t._tag===`Matched`?[t.leaf.component,...t.leaf.layoutChain.map(e=>e.component)].map(e).filter(e=>e!==void 0):[],T=(e,t)=>l.sync(()=>{t?window.history.replaceState(null,``,e):window.history.pushState(null,``,e)}),E=(e,n,r)=>l.gen(function*(){let o=w(i(t,e));if(o.length===0){n&&(yield*T(e,r)),yield*g.set(a,e);return}let s=++C;yield*g.set(p,{_tag:`Navigating`,to:e}),yield*l.promise(()=>Promise.all(o.map(e=>e()))).pipe(l.onError(()=>s===C?g.set(p,{_tag:`Idle`}):l.void)),s===C&&(n&&(yield*T(e,r)),yield*g.set(a,e),yield*g.set(p,{_tag:`Idle`}))}),D=(e,t)=>E(b(e),!0,t?.replace===!0),O=()=>{f.runFork(x)(E(y(),!1,!1))};yield*l.acquireRelease(l.sync(()=>window.addEventListener(`popstate`,O)),()=>l.sync(()=>window.removeEventListener(`popstate`,O))),yield*v(t,D);let k=h.make({get:l.map(g.get(a),e=>i(t,e)),changes:m.map(a.changes,e=>i(t,e))}),A=r.rpc,j;if(A===void 0)j=_.of({call:e=>l.fail(Error(`Boundary.rpc "${e}" cannot resolve: no \`rpc\` option was passed to RouterLive`))});else{let{RpcClient:e,RpcSerialization:t}=yield*l.promise(()=>import(`@effect/rpc`)),n=String(r.baseUrl??window.location.origin).replace(/\/$/,``),i=yield*e.make(A.group,{flatten:!0}).pipe(l.provide(e.layerProtocolHttp({url:`${n}/_eui/rpc`}).pipe(u.provide(u.mergeAll(o.layer,t.layerJson)))));j=_.of({call:(e,t)=>i(e,t)})}let M=n.of({currentMatch:k,navigate:D,httpApiClient:d.some(S),navigating:p});return c.make(n,M).pipe(c.add(_,j))})),p=r.context;return p===void 0?a:u.merge(a,p)}function S(e,...t){let[r,i]=t,o=a(e,r);return l.flatMap(n,e=>e.navigate(o,i))}const C=e=>l.flatMap(n,t=>t.navigate(e)),w=e=>l.flatMap(n,t=>t.navigate(e,{replace:!0})),T=()=>l.sync(()=>window.history.go(-1)),E=()=>l.sync(()=>window.history.go(1));function D(e){let t=new URLSearchParams;for(let n of Object.keys(e).sort()){let r=e[n];r!=null&&t.append(n,String(r))}return t.toString()}function O(e){let t=e.indexOf(`?`);return t===-1?e:e.slice(0,t)}function k(e,t){return l.gen(function*(){let r=yield*n,i=yield*r.currentMatch.get;if(i._tag!==`Matched`)return;let a=e(i.query),o=D(p.encodeUnknownSync(i.leaf.querySchema)(a)),s=O(i.url);yield*r.navigate(o.length>0?`${s}?${o}`:s,t)})}const A=(e,t)=>k(()=>e,t),j=(e,t)=>k(t=>({...t,...e}),t);export{n as Router,r as RouterApp,x as RouterLive,t as RouterOutlet,t as outletNode,T as back,E as forward,v as installLinkInterceptor,S as navigate,j as patchQuery,C as push,w as replace,A as setQuery};
|
|
@@ -121,6 +121,19 @@ declare function match(def: RouterDef, url: string): RouteMatch;
|
|
|
121
121
|
* (`RouterLive`), absent (`Option.none`) on the server, which is itself the origin.
|
|
122
122
|
*/
|
|
123
123
|
type RouterHttpApiClient = HttpApiClient.Client<any, any, never>;
|
|
124
|
+
/**
|
|
125
|
+
* The client navigation state exposed reactively as {@link Router.navigating}. A
|
|
126
|
+
* navigation to a `Router.lazy` route is **deferred-commit** (see
|
|
127
|
+
* `pending-navigation.specs.md`): while its chunk resolves the state is
|
|
128
|
+
* `Navigating` (carrying the target `to`), returning to `Idle` on commit. An
|
|
129
|
+
* eager-route navigation stays `Idle`. Always `Idle` on the server (buffered render).
|
|
130
|
+
*/
|
|
131
|
+
type NavState = {
|
|
132
|
+
readonly _tag: "Idle";
|
|
133
|
+
} | {
|
|
134
|
+
readonly _tag: "Navigating";
|
|
135
|
+
readonly to: string;
|
|
136
|
+
};
|
|
124
137
|
/** Options for a router {@link Router} `navigate` call. */
|
|
125
138
|
interface NavigateOptions {
|
|
126
139
|
/**
|
|
@@ -145,6 +158,13 @@ declare const Router_base: Context.TagClass<Router, "@weftui/router/Router", {
|
|
|
145
158
|
* it stays local via the shared matcher (see the refactor _Feasibility constraint_).
|
|
146
159
|
*/
|
|
147
160
|
readonly httpApiClient: Option.Option<RouterHttpApiClient>;
|
|
161
|
+
/**
|
|
162
|
+
* Reactive client navigation state (see {@link NavState}). `Navigating{to}` while
|
|
163
|
+
* a deferred-commit navigation resolves its lazy chunk(s), `Idle` otherwise. An
|
|
164
|
+
* app reads it via {@link Router.navigatingStream} to render pending UI (e.g. a
|
|
165
|
+
* top progress bar). Constant `Idle` on the server.
|
|
166
|
+
*/
|
|
167
|
+
readonly navigating: Subscribable.Subscribable<NavState>;
|
|
148
168
|
}>;
|
|
149
169
|
declare class Router extends Router_base {}
|
|
150
170
|
declare const OutletTag_base: Context.TagClass<OutletTag, "@weftui/router/Outlet", Node<never, never>>;
|
|
@@ -198,6 +218,8 @@ declare namespace Router {
|
|
|
198
218
|
const route: typeof makeRoute;
|
|
199
219
|
/** Declares a layout wrapping an injected outlet. See {@link makeLayout}. */
|
|
200
220
|
const layout: typeof makeLayout;
|
|
221
|
+
/** Wraps a dynamic-import loader as a lazy component slot. See {@link lazyComponent}. */
|
|
222
|
+
const lazy: typeof lazyComponent;
|
|
201
223
|
/** Seals a route tree into a `RouterDef`. See {@link makeRouter}. */
|
|
202
224
|
const router: typeof makeRouter;
|
|
203
225
|
/** The injected outlet service value (yieldable Tag). See {@link OutletTag}. */
|
|
@@ -212,6 +234,8 @@ declare namespace Router {
|
|
|
212
234
|
const paramsStream: typeof subscribeParams;
|
|
213
235
|
/** Reactive {@link Subscribable} of the live match's query. See {@link subscribeQuery}. */
|
|
214
236
|
const queryStream: typeof subscribeQuery;
|
|
237
|
+
/** Reactive {@link Subscribable} of the client navigation state. See {@link subscribeNavigating}. */
|
|
238
|
+
const navigatingStream: Effect.Effect<Subscribable.Subscribable<NavState, never, never>, never, Router>;
|
|
215
239
|
}
|
|
216
240
|
//#endregion
|
|
217
241
|
//#region src/route-tree.d.ts
|
|
@@ -375,6 +399,28 @@ declare function makeRoute<Path extends Fields = {}, Query extends Fields = {},
|
|
|
375
399
|
declare function makeLayout<C extends readonly TreeNode[], S extends ComponentSlot = ComponentSlot>(config: {
|
|
376
400
|
readonly component: S;
|
|
377
401
|
}, children: C): LayoutNode<Node.Error<SlotNode<S>> | SubtreeE<C>, Exclude<Node.Context<SlotNode<S>>, Router.Outlet> | SubtreeR<C>>;
|
|
402
|
+
/**
|
|
403
|
+
* Wraps a dynamic-import `load` as a lazy {@link ComponentSlot}: the route's descriptor
|
|
404
|
+
* (`segment`, `path`/`query`) stays eager and matchable, while the component — the render
|
|
405
|
+
* body and its module's deps — is split into the chunk `load` resolves. The router invokes
|
|
406
|
+
* the returned slot at render time; it awaits `load` then renders the resolved component,
|
|
407
|
+
* adopting the server DOM in place on hydration (flash-free) and fetching the chunk on
|
|
408
|
+
* client navigation. Exposed as {@link Router.lazy}. See `lazy-component.specs.md`.
|
|
409
|
+
*
|
|
410
|
+
* The resolved value is a component slot (`Component.gen` / `Component.make`, or a
|
|
411
|
+
* `() => Node` thunk) — the shape `component:` already accepts — so its `E`/`R` channels
|
|
412
|
+
* are recovered via {@link SlotNode} and propagate up the tree exactly as an eager
|
|
413
|
+
* component's do.
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```ts
|
|
417
|
+
* Router.route("docs/:category/:slug", {
|
|
418
|
+
* path: { category: Schema.String, slug: Schema.String },
|
|
419
|
+
* component: Router.lazy(() => import("./doc-page").then((m) => m.DocPage)),
|
|
420
|
+
* });
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
declare function lazyComponent<S extends ComponentSlot>(load: () => Promise<S>): () => Node<Node.Error<SlotNode<S>>, Node.Context<SlotNode<S>>>;
|
|
378
424
|
//#endregion
|
|
379
425
|
//#region src/compile.d.ts
|
|
380
426
|
/**
|
|
@@ -505,4 +551,4 @@ declare function buildHttpApi(leaves: readonly CompiledLeaf[]): HttpApi.HttpApi.
|
|
|
505
551
|
*/
|
|
506
552
|
declare function makeRouter<T extends TreeNode, NF extends Node<any, any> = Node>(root: T, options: RouterOptions<NF>): RouterDef<TreeE<T> | Node.Error<NF>, TreeR<T> | Node.Context<NF>>;
|
|
507
553
|
//#endregion
|
|
508
|
-
export {
|
|
554
|
+
export { notFound as A, RouterHttpApiClient as C, RouterNotFound as D, match as E, RouterParamsError as O, Router as S, compileMatchers as T, TreeE as _, RouterOptions as a, NavState as b, leafRegistry as c, FieldsType as d, LayoutNode as f, SubtreeR as g, SubtreeE as h, RouterDef as i, isRouterNotFound as k, ComponentSlot as l, RouteNode as m, CompiledLayout as n, buildHttpApi as o, RouteHandlerProps as p, CompiledLeaf as r, compile as s, Compiled as t, Fields as u, TreeNode as v, RouteMatch as w, NavigateOptions as x, TreeR as y };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{s as e}from"./outlet-BS31W71_.js";import{Either as t,Option as n,Schema as r}from"effect";function i(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function a(e){return e.split(`/`).filter(e=>e.startsWith(`:`)).map(e=>e.slice(1))}function o(e){return e.split(`/`).filter(e=>e.startsWith(`:`)).length}function s(e){let t=e.split(`/`).filter(e=>e.length>0).map(e=>e.startsWith(`:`)?`([^/]+)`:i(e)).join(`/`);return RegExp(t.length===0?`^/?$`:`^/${t}/?$`)}const c=r.Struct({}),l=new WeakMap;function u(e){let t=l.get(e);if(t!==void 0)return t;let r=new Map;for(let t of e.compiled.leaves)r.set(t.id,t);let i=e.httpApi.groups.pages?.endpoints??{},u=[];for(let e of Object.values(i)){let t=r.get(e.name);t!==void 0&&u.push({leaf:t,regex:s(e.path),paramNames:a(e.path),pathSchema:n.getOrElse(e.pathSchema,()=>c),querySchema:n.getOrElse(e.urlParamsSchema,()=>c)})}return u.sort((e,t)=>{let n=o(e.leaf.fullPathPattern)-o(t.leaf.fullPathPattern);return n===0?t.leaf.fullPathPattern.length-e.leaf.fullPathPattern.length:n}),l.set(e,u),u}function d(e){let t=e.indexOf(`#`),n=t===-1?e:e.slice(0,t),r=n.indexOf(`?`),i=r===-1?n:n.slice(0,r),a=r===-1?``:n.slice(r+1),o=i.length===0?`/`:i;return o.startsWith(`/`)||(o=`/${o}`),o.length>1&&o.endsWith(`/`)&&(o=o.slice(0,-1)),{path:o,search:a}}function f(e){let t={};if(e.length===0)return t;for(let[n,r]of new URLSearchParams(e))t[n]=r;return t}function p(e,n){let i=u(e),{path:a,search:o}=d(n),s=o.length===0?a:`${a}?${o}`;for(let e of i){let n=e.regex.exec(a);if(n===null)continue;let i={};e.paramNames.forEach((e,t)=>{let r=n[t+1];r!==void 0&&(i[e]=decodeURIComponent(r))});let c=r.decodeUnknownEither(e.pathSchema)(i);if(t.isLeft(c))continue;let l=r.decodeUnknownEither(e.querySchema)(f(o));if(!t.isLeft(l))return{_tag:`Matched`,leaf:e.leaf,path:c.right,query:l.right,url:s}}return{_tag:`NotFound`,url:s}}function m(t,...n){let i=e.get(t);if(i===void 0)throw Error(`href: route has not been compiled. Seal the tree with Router.router() before calling href().`);let{path:a={},query:o={}}=n[0]??{},s=r.encodeUnknownSync(i.pathSchema)(a),c=i.fullPathPattern.replace(/:([A-Za-z0-9_]+)/g,(e,t)=>encodeURIComponent(String(s[t]))),l=r.encodeUnknownSync(i.querySchema)(o),u=new URLSearchParams;for(let e of Object.keys(l).sort()){let t=l[e];t!=null&&u.append(e,String(t))}let d=u.toString();return d.length>0&&(c=`${c}?${d}`),c}export{u as n,p as r,m as t};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { i as href, n as outletNode, r as HrefArgs, t as RouterApp } from "./outlet-
|
|
3
|
-
export { type Compiled, type CompiledLayout, type CompiledLeaf, type ComponentSlot, type Fields, type FieldsType, type HrefArgs, type LayoutNode, 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 };
|
|
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-HOyeyWRy.js";
|
|
2
|
+
import { i as href, n as outletNode, r as HrefArgs, t as RouterApp } from "./outlet-DbqTgkXa.js";
|
|
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 +1 @@
|
|
|
1
|
-
import{a as e,c as t,
|
|
1
|
+
import{a as e,c as t,d as n,l as r,n as i,o as a,r as o,s,t as c,u as l}from"./outlet-BS31W71_.js";import{n as u,r as d,t as f}from"./href-BgbDo3hx.js";export{o as Router,c as RouterApp,t as RouterNotFound,r as RouterParamsError,e as buildHttpApi,a as compile,u as compileMatchers,f as href,l as isRouterNotFound,s as leafRegistry,d as match,n as notFound,i as outletNode};
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as RouterNotFound, S as Router, d as FieldsType, i as RouterDef, m as RouteNode, u as Fields } from "./compile-HOyeyWRy.js";
|
|
2
2
|
import { Node } from "@weftui/core";
|
|
3
3
|
|
|
4
4
|
//#region src/href.d.ts
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { i as RouterDef, l as ComponentSlot } from "../compile-
|
|
1
|
+
import { S as Router, i as RouterDef, l as ComponentSlot } from "../compile-HOyeyWRy.js";
|
|
2
2
|
import { Effect, Layer } from "effect";
|
|
3
|
+
import { AppRpcClientTag } from "@weftui/core";
|
|
3
4
|
import { RpcGroup } from "@effect/rpc";
|
|
4
5
|
|
|
5
6
|
//#region src/server/router-server.d.ts
|
|
@@ -19,7 +20,7 @@ declare namespace RouterServer {
|
|
|
19
20
|
* registry) — `toWebHandler` serves it at `POST /_eui/rpc`, and an in-process
|
|
20
21
|
* client over the same handlers resolves SSR boundaries in-process.
|
|
21
22
|
*/
|
|
22
|
-
interface RpcOptions {
|
|
23
|
+
export interface RpcOptions {
|
|
23
24
|
/** The app's merged `RpcGroup` (pure Schema contract; shared with the client). */
|
|
24
25
|
readonly group: RpcGroup.RpcGroup<any>;
|
|
25
26
|
/** The server-only handler Layer (`group.toLayer(...)` ⊕ its dependencies). */
|
|
@@ -33,15 +34,53 @@ declare namespace RouterServer {
|
|
|
33
34
|
* params), so the document may use either. Mirrors the route/layout `component` slot,
|
|
34
35
|
* so it accepts both a plain thunk and a `Component.make` / `Component.gen` component.
|
|
35
36
|
* `<!DOCTYPE html>` is prepended at serialize time.
|
|
37
|
+
*
|
|
38
|
+
* `context` is the render-time provide seam (spec:
|
|
39
|
+
* `ambient-context-propagation.specs.md`): an app-wide `Layer` provided to the
|
|
40
|
+
* document shell **and** every route/layout leaf. It is required exactly when the
|
|
41
|
+
* def carries residual app services ({@link AppServices} — its aggregate `R` minus
|
|
42
|
+
* the `Router` / `Router.Outlet` / `AppRpcClientTag` the router already threads) and
|
|
43
|
+
* disallowed otherwise, so a missing provide is a compile error and `rpc`-only /
|
|
44
|
+
* no-service apps stay unchanged (surfaced via {@link ContextOption} at each entry).
|
|
36
45
|
*/
|
|
37
|
-
interface Options {
|
|
46
|
+
export interface Options {
|
|
38
47
|
/** The document shell slot; reads the app to splice via `yield* Router.Outlet`. */
|
|
39
48
|
readonly document: ComponentSlot;
|
|
40
|
-
/**
|
|
41
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The app's `Boundary.rpc` foundation (contract + server handlers). Optional:
|
|
51
|
+
* omit when the app has no `Boundary.rpc` — then `POST /_eui/rpc` is not
|
|
52
|
+
* served (it falls through to page dispatch) and a stray `Boundary.rpc`
|
|
53
|
+
* fails with a descriptive error instead of rendering.
|
|
54
|
+
*/
|
|
55
|
+
readonly rpc?: RpcOptions;
|
|
42
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The residual app services a caller must still provide through the {@link Options.context}
|
|
59
|
+
* seam: a def's aggregate requirement `R` **minus** the services the router already
|
|
60
|
+
* threads in per render — `Router` and `Router.Outlet` (provided by the outlet /
|
|
61
|
+
* document plumbing) and `AppRpcClientTag` (provided from the `rpc` option). When this
|
|
62
|
+
* resolves to `never` the app has no app-wide service to inject and `context` is disallowed.
|
|
63
|
+
*/
|
|
64
|
+
export type AppServices<R> = Exclude<R, Router | Router.Outlet | AppRpcClientTag>;
|
|
65
|
+
/** True only for the exact `any` type — a loosely-typed `RouterDef<any, any>`. */
|
|
66
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
67
|
+
/**
|
|
68
|
+
* Conditionally shapes the `context` field at each entry point: **required** (a
|
|
69
|
+
* `Layer` supplying every residual {@link AppServices}) when the def has statically
|
|
70
|
+
* known app-wide services, **absent** when it has none, and **optional** for a
|
|
71
|
+
* loosely-typed `RouterDef<any, any>` (residual services can't be tracked, so the
|
|
72
|
+
* seam is not forced). This makes a missing provide a compile error for a precisely
|
|
73
|
+
* typed def (AC2) while keeping no-service / loosely-typed apps unchanged (AC3).
|
|
74
|
+
*/
|
|
75
|
+
export type ContextOption<R> = [AppServices<R>] extends [never] ? {
|
|
76
|
+
readonly context?: undefined;
|
|
77
|
+
} : IsAny<AppServices<R>> extends true ? {
|
|
78
|
+
readonly context?: Layer.Layer<any, never, never>;
|
|
79
|
+
} : {
|
|
80
|
+
readonly context: Layer.Layer<AppServices<R>, never, never>;
|
|
81
|
+
};
|
|
43
82
|
/** The result of {@link render}. */
|
|
44
|
-
interface Rendered {
|
|
83
|
+
export interface Rendered {
|
|
45
84
|
readonly html: string;
|
|
46
85
|
readonly status: number;
|
|
47
86
|
}
|
|
@@ -51,16 +90,30 @@ declare namespace RouterServer {
|
|
|
51
90
|
* with `<!DOCTYPE html>` prepended and the status sourced from the platform
|
|
52
91
|
* pipeline (200, or 404 for a no-match / page-raised `RouterNotFound`).
|
|
53
92
|
*/
|
|
54
|
-
function render(def: RouterDef, options: Options & {
|
|
93
|
+
export function render<R>(def: RouterDef<any, R>, options: Options & {
|
|
55
94
|
readonly url: string;
|
|
56
|
-
}): Effect.Effect<Rendered, Error>;
|
|
95
|
+
} & ContextOption<R>): Effect.Effect<Rendered, Error>;
|
|
57
96
|
/**
|
|
58
97
|
* The platform web `fetch`-style handler `(Request) => Promise<Response>` that
|
|
59
98
|
* dispatches through `HttpApiBuilder` and renders the matched route to
|
|
60
99
|
* `text/html`. Suitable for bridging into a dev server (e.g. Vite) or any
|
|
61
100
|
* Web-platform server.
|
|
62
101
|
*/
|
|
63
|
-
function toWebHandler(def: RouterDef, options: Options): (request: Request) => Promise<Response>;
|
|
102
|
+
export function toWebHandler<R>(def: RouterDef<any, R>, options: Options & ContextOption<R>): (request: Request) => Promise<Response>;
|
|
103
|
+
/**
|
|
104
|
+
* Streaming variant of {@link toWebHandler} (spec: "Streaming SSR" in
|
|
105
|
+
* `router.specs.md`, SW1 … SW7). Same `Options`, memoized separately. Per
|
|
106
|
+
* matched leaf the document renders via the dom shell-split API
|
|
107
|
+
* (`renderToHydratableShell`): the buffered shell decides the HTTP status
|
|
108
|
+
* before any bytes flush, then streams as the first chunk with the Suspense
|
|
109
|
+
* patch chunks after it. A `RouterNotFound` escaping `Boundary.suspend`
|
|
110
|
+
* children after the flush keeps 200 and patches in the `notFound` page plus
|
|
111
|
+
* a noindex robots meta (soft-404). No-match and shell-raised
|
|
112
|
+
* `RouterNotFound` stay real 404s; `POST /_eui/rpc` delegation is unchanged.
|
|
113
|
+
* `render` and `toWebHandler` remain fully buffered.
|
|
114
|
+
*/
|
|
115
|
+
export function toStreamingWebHandler<R>(def: RouterDef<any, R>, options: Options & ContextOption<R>): (request: Request) => Promise<Response>;
|
|
116
|
+
export {};
|
|
64
117
|
}
|
|
65
118
|
//#endregion
|
|
66
119
|
export { RouterServer };
|
package/dist/server/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{c as e,n as t,r as n,u as r}from"../outlet-BS31W71_.js";import{HttpApiBuilder as i,HttpApiEndpoint as a,HttpApiGroup as o,HttpServer as s,HttpServerResponse as c}from"@effect/platform";import{Cause as l,Effect as u,Exit as d,Layer as f,Option as p,Schema as m,Scope as h,Stream as g,Subscribable as _}from"effect";import{AppRpcClientTag as v}from"@weftui/core";import{SuspenseFailureHandlerTag as y,renderToHydratableShell as b,renderToStringHydratable as x}from"@weftui/dom/server";import{RpcSerialization as S,RpcServer as C,RpcTest as w}from"@effect/rpc";let T;(function(T){function E(e,t){return c.text(`<!DOCTYPE html>\n${e}`,{status:t,contentType:`text/html; charset=utf-8`})}function D(e){let t={_tag:`Idle`};return n.of({currentMatch:_.make({get:u.succeed(e),changes:g.make(e)}),navigate:()=>u.void,httpApiClient:p.none(),navigating:_.make({get:u.succeed(t),changes:g.make(t)})})}function O(e){return e===void 0?f.succeed(v,v.of({call:e=>u.fail(Error(`Boundary.rpc "${e}" cannot resolve: no \`rpc\` option was passed to RouterServer`))})):f.scoped(v,u.map(w.makeClient(e.group,{flatten:!0}),e=>v.of({call:(t,n)=>e(t,n)}))).pipe(f.provide(e.handlers))}function k(e,t,r){return x(u.provideService(e.document({}),n.Outlet,t)).pipe(u.provideService(n,r),u.provide(O(e.rpc)),u.provide(e.context??f.empty))}function A(e,t,n,r){let i=D({_tag:`NotFound`,url:n});return k(t,e.compiled.notFound(),i).pipe(u.map(e=>E(e,r)))}function j(e,n,r){let i=D({_tag:`NotFound`,url:r});return k(n,t(e),i).pipe(u.map(e=>E(e,404)))}function M(e,n,i){let a=D(i);return k(n,t(e),a).pipe(u.map(e=>E(e,200)),u.catchIf(r,()=>A(e,n,i.url,404)))}function N(t){return{handle:n=>{let i=l.failureOption(n);return p.isSome(i)&&r(i.value)?p.some({content:t.compiled.notFound(),markNoindex:!0,failureReplay:m.encodeSync(e)(i.value)}):p.none()}}}function P(e,i,a){let o=D(a),s=t(e);return u.gen(function*(){let t=yield*h.make(),{shell:r,patches:a}=yield*b(u.provideService(i.document({}),n.Outlet,s)).pipe(u.provideService(n,o),u.provideService(y,N(e)),u.provide(O(i.rpc)),u.provide(i.context??f.empty),h.extend(t),u.onError(e=>h.close(t,d.failCause(e)))),l=g.make(`<!DOCTYPE html>\n${r}`).pipe(g.concat(a),g.ensuring(h.close(t,d.void)),g.encodeText);return c.stream(l,{status:200,contentType:`text/html; charset=utf-8`})}).pipe(u.catchIf(r,()=>A(e,i,a.url,404)))}let F=new WeakMap,I=new WeakMap;function L(e,t,n,r){let c=n.get(e)??new WeakMap;n.set(e,c);let l=c.get(t.document);if(l!==void 0)return l;let u=e.compiled.leaves,d=i,p=o.make(`fallback`).add(a.get(`catchAll`,`*`).addSuccess(m.String)),h=e.httpApi.add(p),g=d.group(h,`pages`,n=>u.reduce((n,i)=>n.handle(i.id,n=>r(e,t,{_tag:`Matched`,leaf:i,path:n.path,query:n.urlParams,url:n.request.url})),n)),_=d.group(h,`fallback`,n=>n.handle(`catchAll`,n=>j(e,t,n.request.url))),v=d.api(h).pipe(f.provide(f.mergeAll(g,_))),{handler:y}=i.toWebHandler(f.mergeAll(v,s.layerContext)),b=t.rpc,x=y;if(b!==void 0){let{handler:e}=C.toWebHandler(b.group,{layer:f.mergeAll(b.handlers,S.layerJson)});x=t=>new URL(t.url).pathname===`/_eui/rpc`?e(t):y(t)}return c.set(t.document,x),x}function R(e,t){return L(e,t,F,M)}function z(e){return e.startsWith(`http://`)||e.startsWith(`https://`)?e:`http://localhost${e.startsWith(`/`)?e:`/${e}`}`}function B(e,t){let n=t;return u.tryPromise({try:async()=>{let t=await R(e,n)(new Request(z(n.url)));return{html:await t.text(),status:t.status}},catch:e=>e instanceof Error?e:Error(String(e))})}T.render=B;function V(e,t){return R(e,t)}T.toWebHandler=V;function H(e,t){return L(e,t,I,P)}T.toStreamingWebHandler=H})(T||={});export{T as RouterServer};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weftui/router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Universal nested router for Weft",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Stef van Wijchen",
|
|
@@ -32,17 +32,17 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@effect/platform": "^0.96.1",
|
|
34
34
|
"@effect/rpc": "^0.75.1",
|
|
35
|
-
"@weftui/core": "0.
|
|
36
|
-
"@weftui/dom": "0.
|
|
35
|
+
"@weftui/core": "0.22.0",
|
|
36
|
+
"@weftui/dom": "0.22.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/jsdom": "^28.0.3",
|
|
40
|
-
"@types/node": "^25.9.
|
|
41
|
-
"effect": "^3.21.
|
|
40
|
+
"@types/node": "^25.9.2",
|
|
41
|
+
"effect": "^3.21.3",
|
|
42
42
|
"jsdom": "^29.1.1",
|
|
43
|
-
"tsx": "^4.22.
|
|
43
|
+
"tsx": "^4.22.4",
|
|
44
44
|
"typescript": "^6.0.3",
|
|
45
|
-
"vite-plus": "
|
|
45
|
+
"vite-plus": "0.2.1"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"effect": "^3.21"
|