@weftui/router 0.20.9 → 0.21.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.
@@ -17,9 +17,11 @@ interface RouterLiveOptions {
17
17
  * The app's `Boundary.rpc` foundation: the merged `RpcGroup` contract (shared
18
18
  * with the server handler Layer). Backs the {@link AppRpcClientTag} seam so a
19
19
  * hydrated boundary refetch — and a client-first SPA mount — resolve over the
20
- * network rpc client.
20
+ * network rpc client. Optional: omit when the app has no `Boundary.rpc` — then
21
+ * no network rpc client is built and a stray `Boundary.rpc` fails with a
22
+ * descriptive error.
21
23
  */
22
- readonly rpc: {
24
+ readonly rpc?: {
23
25
  /** The app's merged `RpcGroup` (pure Schema contract). */readonly group: RpcGroup.RpcGroup<any>;
24
26
  };
25
27
  }
@@ -41,7 +43,7 @@ interface RouterLiveOptions {
41
43
  * refetch and client-first mount) without depending on this package or
42
44
  * `@effect/rpc`.
43
45
  */
44
- declare function RouterLive(def: RouterDef, options: RouterLiveOptions): Layer.Layer<Router | AppRpcClientTag>;
46
+ declare function RouterLive(def: RouterDef, options?: RouterLiveOptions): Layer.Layer<Router | AppRpcClientTag>;
45
47
  //#endregion
46
48
  //#region src/client/link.d.ts
47
49
  /**
@@ -1 +1 @@
1
- import{n as e,r as t,t as n}from"../outlet-C7p8KXmO.js";import{r,t as i}from"../href-Dl30XcF4.js";import{FetchHttpClient as a,HttpApiClient as o}from"@effect/platform";import{Context as s,Effect as c,Layer as l,Option as u,Runtime as d,Schema as f,Stream as p,Subscribable as m,SubscriptionRef as h}from"effect";import{AppRpcClientTag as g}from"@weftui/core";import{RpcClient as _,RpcSerialization as v}from"@effect/rpc";function y(e,t){return c.gen(function*(){let n=yield*c.runtime(),i=i=>{if(i.defaultPrevented||i.button!==0||i.metaKey||i.ctrlKey||i.shiftKey||i.altKey)return;let a=i.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}`&&r(e,u)._tag===`Matched`&&(i.preventDefault(),d.runFork(n)(t(u)))};yield*c.acquireRelease(c.sync(()=>document.addEventListener(`click`,i)),()=>c.sync(()=>document.removeEventListener(`click`,i)))})}function b(){return`${window.location.pathname}${window.location.search}`}function x(e){let t=new URL(e,window.location.href);return`${t.pathname}${t.search}`}function S(e,n){return l.scopedContext(c.gen(function*(){let i=yield*h.make(b()),f=yield*c.runtime(),S=yield*o.make(e.httpApi,{baseUrl:n.baseUrl??window.location.origin}).pipe(c.provide(a.layer)),C=()=>{d.runFork(f)(h.set(i,b()))};yield*c.acquireRelease(c.sync(()=>window.addEventListener(`popstate`,C)),()=>c.sync(()=>window.removeEventListener(`popstate`,C)));let w=(e,t)=>c.gen(function*(){let n=x(e);yield*c.sync(()=>{t?.replace===!0?window.history.replaceState(null,``,n):window.history.pushState(null,``,n)}),yield*h.set(i,n)});yield*y(e,w);let T=m.make({get:c.map(h.get(i),t=>r(e,t)),changes:p.map(i.changes,t=>r(e,t))}),E=String(n.baseUrl??window.location.origin).replace(/\/$/,``),D=yield*_.make(n.rpc.group,{flatten:!0}).pipe(c.provide(_.layerProtocolHttp({url:`${E}/_eui/rpc`}).pipe(l.provide(l.mergeAll(a.layer,v.layerJson))))),O=g.of({call:(e,t)=>D(e,t)}),k=t.of({currentMatch:T,navigate:w,httpApiClient:u.some(S)});return s.make(t,k).pipe(s.add(g,O))}))}function C(e,...n){let[r,a]=n,o=i(e,r);return c.flatMap(t,e=>e.navigate(o,a))}const w=e=>c.flatMap(t,t=>t.navigate(e)),T=e=>c.flatMap(t,t=>t.navigate(e,{replace:!0})),E=()=>c.sync(()=>window.history.go(-1)),D=()=>c.sync(()=>window.history.go(1));function O(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 k(e){let t=e.indexOf(`?`);return t===-1?e:e.slice(0,t)}function A(e,n){return c.gen(function*(){let r=yield*t,i=yield*r.currentMatch.get;if(i._tag!==`Matched`)return;let a=e(i.query),o=O(f.encodeUnknownSync(i.leaf.querySchema)(a)),s=k(i.url);yield*r.navigate(o.length>0?`${s}?${o}`:s,n)})}const j=(e,t)=>A(()=>e,t),M=(e,t)=>A(t=>({...t,...e}),t);export{t as Router,n as RouterApp,S as RouterLive,e as RouterOutlet,e as outletNode,E as back,D as forward,y as installLinkInterceptor,C as navigate,M as patchQuery,w as push,T as replace,j as setQuery};
1
+ import{n as e,r as t,t as n}from"../outlet-C7p8KXmO.js";import{r,t as i}from"../href-Dl30XcF4.js";import{FetchHttpClient as a,HttpApiClient as o}from"@effect/platform";import{Context as s,Effect as c,Layer as l,Option as u,Runtime as d,Schema as f,Stream as p,Subscribable as m,SubscriptionRef as h}from"effect";import{AppRpcClientTag as g}from"@weftui/core";import{RpcClient as _,RpcSerialization as v}from"@effect/rpc";function y(e,t){return c.gen(function*(){let n=yield*c.runtime(),i=i=>{if(i.defaultPrevented||i.button!==0||i.metaKey||i.ctrlKey||i.shiftKey||i.altKey)return;let a=i.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}`&&r(e,u)._tag===`Matched`&&(i.preventDefault(),d.runFork(n)(t(u)))};yield*c.acquireRelease(c.sync(()=>document.addEventListener(`click`,i)),()=>c.sync(()=>document.removeEventListener(`click`,i)))})}function b(){return`${window.location.pathname}${window.location.search}`}function x(e){let t=new URL(e,window.location.href);return`${t.pathname}${t.search}`}function S(e,n={}){return l.scopedContext(c.gen(function*(){let i=yield*h.make(b()),f=yield*c.runtime(),S=yield*o.make(e.httpApi,{baseUrl:n.baseUrl??window.location.origin}).pipe(c.provide(a.layer)),C=()=>{d.runFork(f)(h.set(i,b()))};yield*c.acquireRelease(c.sync(()=>window.addEventListener(`popstate`,C)),()=>c.sync(()=>window.removeEventListener(`popstate`,C)));let w=(e,t)=>c.gen(function*(){let n=x(e);yield*c.sync(()=>{t?.replace===!0?window.history.replaceState(null,``,n):window.history.pushState(null,``,n)}),yield*h.set(i,n)});yield*y(e,w);let T=m.make({get:c.map(h.get(i),t=>r(e,t)),changes:p.map(i.changes,t=>r(e,t))}),E=n.rpc,D;if(E===void 0)D=g.of({call:e=>c.fail(Error(`Boundary.rpc "${e}" cannot resolve: no \`rpc\` option was passed to RouterLive`))});else{let e=String(n.baseUrl??window.location.origin).replace(/\/$/,``),t=yield*_.make(E.group,{flatten:!0}).pipe(c.provide(_.layerProtocolHttp({url:`${e}/_eui/rpc`}).pipe(l.provide(l.mergeAll(a.layer,v.layerJson)))));D=g.of({call:(e,n)=>t(e,n)})}let O=t.of({currentMatch:T,navigate:w,httpApiClient:u.some(S)});return s.make(t,O).pipe(s.add(g,D))}))}function C(e,...n){let[r,a]=n,o=i(e,r);return c.flatMap(t,e=>e.navigate(o,a))}const w=e=>c.flatMap(t,t=>t.navigate(e)),T=e=>c.flatMap(t,t=>t.navigate(e,{replace:!0})),E=()=>c.sync(()=>window.history.go(-1)),D=()=>c.sync(()=>window.history.go(1));function O(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 k(e){let t=e.indexOf(`?`);return t===-1?e:e.slice(0,t)}function A(e,n){return c.gen(function*(){let r=yield*t,i=yield*r.currentMatch.get;if(i._tag!==`Matched`)return;let a=e(i.query),o=O(f.encodeUnknownSync(i.leaf.querySchema)(a)),s=k(i.url);yield*r.navigate(o.length>0?`${s}?${o}`:s,n)})}const j=(e,t)=>A(()=>e,t),M=(e,t)=>A(t=>({...t,...e}),t);export{t as Router,n as RouterApp,S as RouterLive,e as RouterOutlet,e as outletNode,E as back,D as forward,y as installLinkInterceptor,C as navigate,M as patchQuery,w as push,T as replace,j as setQuery};
@@ -37,8 +37,13 @@ declare namespace RouterServer {
37
37
  interface Options {
38
38
  /** The document shell slot; reads the app to splice via `yield* Router.Outlet`. */
39
39
  readonly document: ComponentSlot;
40
- /** The app's `Boundary.rpc` foundation (contract + server handlers). */
41
- readonly rpc: RpcOptions;
40
+ /**
41
+ * The app's `Boundary.rpc` foundation (contract + server handlers). Optional:
42
+ * omit when the app has no `Boundary.rpc` — then `POST /_eui/rpc` is not
43
+ * served (it falls through to page dispatch) and a stray `Boundary.rpc`
44
+ * fails with a descriptive error instead of rendering.
45
+ */
46
+ readonly rpc?: RpcOptions;
42
47
  }
43
48
  /** The result of {@link render}. */
44
49
  interface Rendered {
@@ -61,6 +66,19 @@ declare namespace RouterServer {
61
66
  * Web-platform server.
62
67
  */
63
68
  function toWebHandler(def: RouterDef, options: Options): (request: Request) => Promise<Response>;
69
+ /**
70
+ * Streaming variant of {@link toWebHandler} (spec: "Streaming SSR" in
71
+ * `router.specs.md`, SW1 … SW7). Same `Options`, memoized separately. Per
72
+ * matched leaf the document renders via the dom shell-split API
73
+ * (`renderToHydratableShell`): the buffered shell decides the HTTP status
74
+ * before any bytes flush, then streams as the first chunk with the Suspense
75
+ * patch chunks after it. A `RouterNotFound` escaping `Boundary.suspend`
76
+ * children after the flush keeps 200 and patches in the `notFound` page plus
77
+ * a noindex robots meta (soft-404). No-match and shell-raised
78
+ * `RouterNotFound` stay real 404s; `POST /_eui/rpc` delegation is unchanged.
79
+ * `render` and `toWebHandler` remain fully buffered.
80
+ */
81
+ function toStreamingWebHandler(def: RouterDef, options: Options): (request: Request) => Promise<Response>;
64
82
  }
65
83
  //#endregion
66
84
  export { RouterServer };
@@ -1 +1 @@
1
- import{l as e,n as t,r as n}from"../outlet-C7p8KXmO.js";import{HttpApiBuilder as r,HttpApiEndpoint as i,HttpApiGroup as a,HttpServer as o,HttpServerResponse as s}from"@effect/platform";import{Effect as c,Layer as l,Option as u,Schema as d,Stream as f,Subscribable as p}from"effect";import{AppRpcClientTag as m}from"@weftui/core";import{RpcSerialization as h,RpcServer as g,RpcTest as _}from"@effect/rpc";import{renderToStringHydratable as v}from"@weftui/dom/server";let y;(function(y){function b(e,t){return s.text(`<!DOCTYPE html>\n${e}`,{status:t,contentType:`text/html; charset=utf-8`})}function x(e){return n.of({currentMatch:p.make({get:c.succeed(e),changes:f.make(e)}),navigate:()=>c.void,httpApiClient:u.none()})}function S(e){return l.scoped(m,c.map(_.makeClient(e.group,{flatten:!0}),e=>m.of({call:(t,n)=>e(t,n)}))).pipe(l.provide(e.handlers))}function C(e,t,r){return v(c.provideService(e.document({}),n.Outlet,t)).pipe(c.provideService(n,r),c.provide(S(e.rpc)))}function w(e,t,n,r){let i=x({_tag:`NotFound`,url:n});return C(t,e.compiled.notFound(),i).pipe(c.map(e=>b(e,r)))}function T(e,n,r){let i=x({_tag:`NotFound`,url:r});return C(n,t(e),i).pipe(c.map(e=>b(e,404)))}function E(n,r,i){let a=x(i);return C(r,t(n),a).pipe(c.map(e=>b(e,200)),c.catchIf(e,()=>w(n,r,i.url,404)))}let D=new WeakMap;function O(e,t){let n=D.get(e)??new WeakMap;D.set(e,n);let s=n.get(t.document);if(s!==void 0)return s;let c=e.compiled.leaves,u=r,f=a.make(`fallback`).add(i.get(`catchAll`,`*`).addSuccess(d.String)),p=e.httpApi.add(f),m=u.group(p,`pages`,n=>c.reduce((n,r)=>n.handle(r.id,n=>E(e,t,{_tag:`Matched`,leaf:r,path:n.path,query:n.urlParams,url:n.request.url})),n)),_=u.group(p,`fallback`,n=>n.handle(`catchAll`,n=>T(e,t,n.request.url))),v=u.api(p).pipe(l.provide(l.mergeAll(m,_))),{handler:y}=r.toWebHandler(l.mergeAll(v,o.layerContext)),{handler:b}=g.toWebHandler(t.rpc.group,{layer:l.mergeAll(t.rpc.handlers,h.layerJson)}),x=e=>new URL(e.url).pathname===`/_eui/rpc`?b(e):y(e);return n.set(t.document,x),x}function k(e){return e.startsWith(`http://`)||e.startsWith(`https://`)?e:`http://localhost${e.startsWith(`/`)?e:`/${e}`}`}function A(e,t){return c.tryPromise({try:async()=>{let n=await O(e,t)(new Request(k(t.url)));return{html:await n.text(),status:n.status}},catch:e=>e instanceof Error?e:Error(String(e))})}y.render=A;function j(e,t){return O(e,t)}y.toWebHandler=j})(y||={});export{y as RouterServer};
1
+ import{l as e,n as t,r as n,s as r}from"../outlet-C7p8KXmO.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{RpcSerialization as y,RpcServer as b,RpcTest as x}from"@effect/rpc";import{SuspenseFailureHandlerTag as S,renderToHydratableShell as C,renderToStringHydratable as w}from"@weftui/dom/server";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){return n.of({currentMatch:_.make({get:u.succeed(e),changes:g.make(e)}),navigate:()=>u.void,httpApiClient:p.none()})}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(x.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 w(u.provideService(e.document({}),n.Outlet,t)).pipe(u.provideService(n,r),u.provide(O(e.rpc)))}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(n,r,i){let a=D(i);return k(r,t(n),a).pipe(u.map(e=>E(e,200)),u.catchIf(e,()=>A(n,r,i.url,404)))}function N(t){return{handle:n=>{let i=l.failureOption(n);return p.isSome(i)&&e(i.value)?p.some({content:t.compiled.notFound(),markNoindex:!0,failureReplay:m.encodeSync(r)(i.value)}):p.none()}}}function P(r,i,a){let o=D(a),s=t(r);return u.gen(function*(){let e=yield*h.make(),{shell:t,patches:a}=yield*C(u.provideService(i.document({}),n.Outlet,s)).pipe(u.provideService(n,o),u.provideService(S,N(r)),u.provide(O(i.rpc)),h.extend(e),u.onError(t=>h.close(e,d.failCause(t)))),l=g.make(`<!DOCTYPE html>\n${t}`).pipe(g.concat(a),g.ensuring(h.close(e,d.void)),g.encodeText);return c.stream(l,{status:200,contentType:`text/html; charset=utf-8`})}).pipe(u.catchIf(e,()=>A(r,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:x}=i.toWebHandler(f.mergeAll(v,s.layerContext)),S=t.rpc,C=x;if(S!==void 0){let{handler:e}=b.toWebHandler(S.group,{layer:f.mergeAll(S.handlers,y.layerJson)});C=t=>new URL(t.url).pathname===`/_eui/rpc`?e(t):x(t)}return c.set(t.document,C),C}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){return u.tryPromise({try:async()=>{let n=await R(e,t)(new Request(z(t.url)));return{html:await n.text(),status:n.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.20.9",
3
+ "version": "0.21.0",
4
4
  "description": "Universal nested router for Weft",
5
5
  "license": "MIT",
6
6
  "author": "Stef van Wijchen",
@@ -32,15 +32,15 @@
32
32
  "dependencies": {
33
33
  "@effect/platform": "^0.96.1",
34
34
  "@effect/rpc": "^0.75.1",
35
- "@weftui/core": "0.20.9",
36
- "@weftui/dom": "0.20.9"
35
+ "@weftui/core": "0.21.0",
36
+ "@weftui/dom": "0.21.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/jsdom": "^28.0.3",
40
- "@types/node": "^25.9.1",
41
- "effect": "^3.21.2",
40
+ "@types/node": "^25.9.2",
41
+ "effect": "^3.21.3",
42
42
  "jsdom": "^29.1.1",
43
- "tsx": "^4.22.3",
43
+ "tsx": "^4.22.4",
44
44
  "typescript": "^6.0.3",
45
45
  "vite-plus": "latest"
46
46
  },