@weftui/dom 0.26.2 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/client/index.js +1 -1
- package/dist/{data-Bk62ePeP.js → data-uLmMpQMV.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +1 -1
- package/docs/explanation/boundaries-and-suspense.md +8 -8
- package/docs/explanation/reactive-primitives.md +13 -11
- package/docs/explanation/rendering-model.md +1 -1
- package/docs/explanation/services-and-context.md +3 -3
- package/docs/how-to/add-routing.md +17 -5
- package/docs/how-to/author-components.md +5 -5
- package/docs/how-to/handle-forms.md +10 -10
- package/docs/how-to/load-async-data.md +4 -4
- package/docs/how-to/load-data-with-rpc.md +5 -5
- package/docs/how-to/provide-services.md +1 -1
- package/docs/how-to/use-element-refs.md +5 -5
- package/docs/reference/core.md +31 -20
- package/docs/reference/router.md +9 -5
- package/docs/tutorial/02-reactivity.md +6 -6
- package/docs/tutorial/03-services-and-async.md +3 -4
- package/docs/tutorial/04-errors-and-server.md +2 -2
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ const Counter = () =>
|
|
|
47
47
|
Effect.gen(function* () {
|
|
48
48
|
const count = yield* SubscriptionRef.make(0);
|
|
49
49
|
return yield* h.button({ onclick: () => SubscriptionRef.update(count, (n) => n + 1) }, [
|
|
50
|
-
|
|
50
|
+
SubscriptionRef.changes(count),
|
|
51
51
|
]);
|
|
52
52
|
});
|
|
53
53
|
|
package/dist/client/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as e,n as t,o as n,r,s as i,t as a}from"../data-Bk62ePeP.js";import{a as o,c as s,d as c,f as l,i as u,l as d,m as f,n as p,o as m,p as h,s as g,u as _}from"../boundary-replay-BR26_puM.js";import{Cause as v,Context as y,Deferred as b,Effect as x,ExecutionStrategy as S,Exit as C,Fiber as ee,FiberRef as te,HashMap as w,HashSet as T,Layer as ne,LogLevel as re,ManagedRuntime as ie,Option as E,Ref as D,Schema as ae,Scope as O,Stream as k,SubscriptionRef as A,pipe as j}from"effect";import{AppRpcClientTag as oe,FAILURE_BOUNDARY as se,FRAGMENT as ce,LIST as le,SERVER_BOUNDARY as ue,SUSPENSE_BOUNDARY as de,Source as fe,getElementDescriptor as pe,isStream as M,toStream as N}from"@weftui/core";function P(){return x.gen(function*(){let e=yield*r;return++e.streamIdCounter.current})}const me=P;let he=0;const F=()=>++he;function ge(e){if(e.length<=2||!e.startsWith(`on`))return!1;let t=e[2];return t!==void 0&&t>=`a`&&t<=`z`}function I(e,t){return x.gen(function*(){for(let[n,r]of Object.entries(t))if(n!==`children`){if(ge(n)){yield*Ce(e,n,r);continue}if(n===`ref`&&typeof r==`object`&&D.RefTypeId in r){yield*D.set(r,E.some(e));continue}if(n===`style`){yield*xe(e,r);continue}_e(e,n)?yield*ve(e,n,r):yield*ye(e,n,r)}})}function _e(e,t){if(t.startsWith(`data-`)||t.startsWith(`aria-`))return!1;let n=Object.getPrototypeOf(e);for(;n!==null;){if(Object.hasOwn(n,t))return!0;n=Object.getPrototypeOf(n)}return t in e}function ve(e,t,n){return x.gen(function*(){M(n)||x.isEffect(n)?yield*z(N(n),n=>{n==null?delete e[t]:e[t]=n},`property:${t}`):n!=null&&(e[t]=n)})}function ye(e,t,n){return x.gen(function*(){if(M(n)||x.isEffect(n))yield*z(N(n),n=>{if(n==null)e.removeAttribute(t);else{let r=be(n);r!==void 0&&(typeof n==`boolean`?n?e.setAttribute(t,``):e.removeAttribute(t):e.setAttribute(t,r))}},`attribute:${t}`);else{let r=be(n);r!==void 0&&(typeof n==`boolean`?n?e.setAttribute(t,``):e.removeAttribute(t):e.setAttribute(t,r))}})}function be(e){if(e!=null)return String(e)}function xe(e,t){return x.gen(function*(){if(M(t)||x.isEffect(t)){yield*z(N(t),t=>{if(typeof t==`string`)e.setAttribute(`style`,t);else if(typeof t==`object`&&t){e.style.cssText=``;for(let[n,r]of Object.entries(t))r!=null&&e.style.setProperty(L(n),String(r))}},`style`);return}if(typeof t==`string`){e.setAttribute(`style`,t);return}typeof t==`object`&&t&&(yield*Se(e,t))})}function Se(e,t){return x.gen(function*(){for(let[n,r]of Object.entries(t))M(r)||x.isEffect(r)?yield*z(N(r),t=>{t!=null&&e.style.setProperty(L(n),String(t))},`style.${n}`):r!=null&&e.style.setProperty(L(n),String(r))})}function L(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function R(e,t,n){return x.gen(function*(){let r=yield*x.serviceOption(a);if(E.isNone(r)){let r=yield*te.get(te.unhandledErrorLogLevel);return yield*j(x.forkIn(x.withUnhandledErrorLogLevel(e,r),t),x.withUnhandledErrorLogLevel(E.some(re.Error)),x.annotateLogs(`weft.region`,n))}let i=yield*x.forkIn(e,t);return yield*j(ee.await(i),x.flatMap(e=>C.isFailure(e)?r.value.reportError(e.cause):x.void),x.forkIn(t)),i})}function z(e,t,n){return x.gen(function*(){let i=yield*r;yield*R(k.runForEach(e,e=>x.sync(()=>void t(e))),i.scope,n)})}function Ce(e,t,n){return x.gen(function*(){let i=yield*r,a=t.slice(2).toLowerCase(),o=null,s=()=>{o&&=(e.removeEventListener(a,o),null)},c=n=>{s(),!(n==null||n===!1)&&typeof n==`function`&&(o=e=>{let r=n(e);x.isEffect(r)&&i.runtime.runFork(j(r,x.catchAll(e=>process.env.NODE_ENV===`development`?x.logError(`Event handler error: ${t}`,{error:e}):x.void)))},e.addEventListener(a,o))};yield*O.addFinalizer(i.scope,x.sync(s)),M(n)||x.isEffect(n)?yield*z(N(n),e=>c(e),`event:${t}`):c(n)})}function we(e,t,n,r,i,a){return x.gen(function*(){let o=yield*b.await(t).pipe(x.flip,x.catchAll(()=>x.interrupt)),s=e.match(o);if(yield*O.close(n,C.void),s===null)return E.isSome(r)?yield*r.value.reportError(o):yield*x.logError(`Unhandled error escaped the outermost Boundary`,o);K(i,a);let c=yield*B(s),l=a.parentNode;if(l!==null&&c!==null)if(Array.isArray(c))for(let e of c)l.insertBefore(e,a);else l.insertBefore(c,a)})}function Te(e){return x.gen(function*(){let t=yield*r,n=yield*x.serviceOption(a),i=F(),s=document.createComment(o(i)),c=document.createComment(u(i)),l=yield*O.fork(t.scope,S.sequential),d={...t,scope:l},f=yield*b.make(),p=yield*j(V(e.children),x.provideService(a,{reportError:e=>b.fail(f,e).pipe(x.asVoid)}),x.provideService(r,d),x.provideService(O.Scope,l),x.catchAllCause(t=>{let n=e.match(t);return n===null?x.failCause(t):j(O.close(l,C.void),x.flatMap(()=>B(n)),x.map(e=>e===null?[]:Array.isArray(e)?e:[e]))}));return yield*x.forkIn(we(e,f,l,n,s,c),t.scope),[s,...p,c]})}function Ee(e){return x.gen(function*(){let t=yield*r,i=yield*D.make(1),a=yield*b.make(),o=j(D.updateAndGet(i,e=>e-1),x.flatMap(e=>e<=0?x.asVoid(b.succeed(a,void 0)):x.void)),s={register:D.update(i,e=>e+1),settle:o},c=e.children,l=yield*B((c===void 0?[]:Array.isArray(c)?c:[c]).map(e=>x.isEffect(e)||M(e)?{type:()=>e,props:{}}:e)).pipe(x.provideService(n,s)),u=l===null?[]:Array.isArray(l)?l:[l];yield*o;let d=yield*b.poll(a);if(E.isSome(d))return u;let p=yield*me(),m=document.createComment(f(p)),g=document.createComment(h(p)),_=yield*B(e.fallback??null),v=[];_!==null&&(Array.isArray(_)?v.push(..._):v.push(_));let y=document.createDocumentFragment();for(let e of u)y.appendChild(e);let S=x.gen(function*(){yield*b.await(a),K(m,g);let e=g.parentNode;e!==null&&(e.insertBefore(y,g),m.remove(),g.remove())});return yield*x.forkIn(S,t.scope),[m,...v,g]})}function De(t){return x.gen(function*(){let n=yield*r,i=yield*x.serviceOption(oe);if(E.isNone(i))return yield*x.fail(new e({cause:void 0,message:`Boundary.rpc "${t.tag}" was mounted client-first without an AppRpcClient in context. Mount the app under @weftui/router (RouterLive), which provides the rpc client, so the boundary can resolve its data.`}));let a=i.value,s=F(),c=document.createComment(o(s)),l=document.createComment(u(s)),d=yield*B(t.fallback??null),f=[];d!==null&&(Array.isArray(d)?f.push(...d):f.push(d));let p=x.gen(function*(){let e=yield*a.call(t.tag,t.payload()),n=yield*$e(t.tag,t.payload,e,i),r=yield*B(t.render(n));K(c,l);let o=l.parentNode;if(o!==null&&r!==null)if(Array.isArray(r))for(let e of r)o.insertBefore(e,l);else o.insertBefore(r,l)}).pipe(x.catchAllCause(e=>x.logError(`[weft] Boundary.rpc "${t.tag}" mount failed to resolve; fallback left in place.`,e)));return yield*x.forkIn(p,n.scope),[c,...f,l]})}function B(e){return x.gen(function*(){if(typeof e==`string`||typeof e==`number`||typeof e==`bigint`)return document.createTextNode(String(e));if(typeof e==`boolean`||e==null)return null;if(M(e)||x.isEffect(e)){let t=pe(e);if(t!==void 0)return yield*B(t);if(x.isEffect(e)){let t=x.runSyncExit(e);if(C.isSuccess(t))return yield*B(t.value)}return yield*H(N(e))}if(typeof e==`object`&&Symbol.iterator in e&&!(`type`in e))return yield*V(Oe(e));if(typeof e==`object`&&`type`in e&&!(Symbol.iterator in e)){let{type:t,props:n}=e;return t===ce?yield*ke(n):t===de?yield*Ee(n):t===ue?yield*De(n):t===se?yield*Te(n):t===le?yield*Ie(n):typeof t==`string`?yield*Ae(t,n):typeof t==`function`?yield*je(t,n):yield*x.fail(new i({type:t,message:`Invalid Renderable type: expected string, FRAGMENT, or function, got ${typeof t}`}))}return null})}function Oe(e){let t=[];function n(e){if(M(e)||x.isEffect(e)){t.push(e);return}if(typeof e==`object`&&e&&Symbol.iterator in e&&!(`type`in e))for(let t of e)n(t);else t.push(e)}return n(e),t}function V(e){return x.gen(function*(){let t=[];for(let n of e)if(M(n)||x.isEffect(n)){let e=yield*H(N(n));t.push(...e)}else{let e=yield*B(n);e!==null&&(Array.isArray(e)?t.push(...e):t.push(e))}return t})}function ke(e){return x.gen(function*(){let t=`children`in e?e.children:void 0;return t===void 0?[]:yield*V(Array.isArray(t)?t:[t])})}function Ae(e,t){return x.gen(function*(){let n=document.createElement(e);yield*I(n,t);let r=`children`in t?t.children:void 0;if(r!==void 0){let e=Array.isArray(r)?r:[r];for(let t of e)if(M(t)||x.isEffect(t)){let e=yield*H(N(t));for(let t of e)n.appendChild(t)}else{let e=yield*B(t);if(e!==null)if(Array.isArray(e))for(let t of e)n.appendChild(t);else n.appendChild(e)}}return n})}function je(e,t){return x.gen(function*(){let i=e(t);if(M(i)||x.isEffect(i)){let e=yield*r,t=yield*O.fork(e.scope,S.sequential),a={...e,scope:t},o=yield*x.serviceOption(n),s=N(i);return E.isSome(o)&&(yield*o.value.register,s=j(s,k.zipWithIndex,k.flatMap(([e,t])=>t===0?k.fromEffect(x.as(o.value.settle,e)):k.make(e)))),yield*H(s).pipe(x.provideService(r,a),x.provideService(O.Scope,t))}return yield*B(i)})}function H(e){return x.gen(function*(){let t=yield*r,n=yield*P(),[i,a]=Me(n),o=null;return yield*R(k.runForEach(e,e=>x.gen(function*(){o!==null&&(yield*O.close(o,C.void)),o=yield*O.fork(t.scope,S.sequential);let n={...t,scope:o};yield*U(i,a,e).pipe(x.provideService(r,n),x.provideService(O.Scope,o))})),t.scope,`child:stream-${n}`),[i,a]})}function Me(e){return[document.createComment(l(e)),document.createComment(c(e))]}function U(e,t,n){return x.gen(function*(){let r=e.nextSibling,i=r!==null&&r!==t&&r.nextSibling===t;if(i&&r.nodeType===ot&&W(n)){let e=String(n);r.data!==e&&(r.data=e);return}if(i&&r.nodeType===X){let e=G(n);if(e!==void 0&&typeof e.type==`string`&&r.tagName.toLowerCase()===e.type.toLowerCase()){yield*Ne(r,e);return}}K(e,t);let a=yield*B(n),o=e.parentNode;if(o!==null&&a!==null)if(Array.isArray(a))for(let e of a)o.insertBefore(e,t);else o.insertBefore(a,t)})}function W(e){return typeof e==`string`||typeof e==`number`||typeof e==`bigint`}function G(e){let t=pe(e);if(t!==void 0)return t;if(typeof e==`object`&&e&&`type`in e&&!(Symbol.iterator in e)&&!M(e)&&!x.isEffect(e))return e}function Ne(e,t){return x.gen(function*(){yield*I(e,t.props);let n=t.props.children,r=n===void 0?[]:Array.isArray(n)?n:[n];if(!(yield*Pe(e,r))){for(;e.firstChild!==null;)e.firstChild.remove();yield*Fe(e,r)}})}function Pe(e,t){return x.gen(function*(){let n=Array.from(e.childNodes);if(n.length!==t.length)return!1;for(let e=0;e<t.length;e++){let r=t[e],i=n[e];if(W(r)){if(i.nodeType!==ot)return!1}else{let e=G(r);if(e===void 0||typeof e.type!=`string`||i.nodeType!==X||i.tagName.toLowerCase()!==e.type.toLowerCase())return!1}}for(let e=0;e<t.length;e++){let r=t[e],i=n[e];if(W(r)){let e=String(r);i.data!==e&&(i.data=e)}else yield*Ne(i,G(r))}return!0})}function Fe(e,t){return x.gen(function*(){for(let n of t)if(M(n)||x.isEffect(n)){let t=yield*H(N(n));for(let n of t)e.appendChild(n)}else{let t=yield*B(n);if(t!==null)if(Array.isArray(t))for(let n of t)e.appendChild(n);else e.appendChild(t)}})}function K(e,t){let n=e.nextSibling;for(;n!==null&&n!==t;){let e=n.nextSibling;n.remove(),n=e}}function Ie(e){return x.gen(function*(){let t=yield*r,{of:n,by:i,render:a}=e,o=yield*P(),[s,c]=Me(o),l=yield*O.fork(t.scope,S.sequential),u=(yield*fe.toSubscribable(n).pipe(x.provideService(O.Scope,l))).changes,d={records:w.empty(),order:[]};return yield*R(k.runForEach(u,e=>x.gen(function*(){d=yield*q(Array.from(e),i,a,d,l,c,t)})),l,`list:stream-${o}`),[s,c]})}function Le(t,n){return x.gen(function*(){let r=[],i=T.empty();for(let a=0;a<t.length;a++){let o=n===void 0?t[a]:n(t[a],a);if(T.has(i,o))return yield*x.fail(new e({cause:o,message:`List.each: duplicate key ${Ve(o)} in a single emission; keys must be unique (set a stable \`by\`).`}));i=T.add(i,o),r.push(o)}return r})}function q(e,t,n,r,i,a,o){return x.gen(function*(){let s=yield*Le(e,t),c=w.empty();r.order.forEach((e,t)=>{c=w.set(c,e,t)});let l=[],u=[];for(let t=0;t<e.length;t++){let a=s[t],d=w.get(r.records,a);if(E.isSome(d))l.push(d.value),u.push(E.getOrElse(w.get(c,a),()=>-1));else{let r=yield*Re(a,e[t],t,n,i,o);l.push(r),u.push(-1)}}let d=T.empty();for(let e of s)d=T.add(d,e);for(let e of r.order)if(!T.has(d,e)){let t=w.get(r.records,e);E.isSome(t)&&(yield*O.close(t.value.scope,C.void),Be(t.value.startMarker,t.value.endMarker))}let f=He(u),p=a.parentNode;if(p!==null)for(let e=l.length-1;e>=0;e--){let t=l[e];if(u[e]!==-1&&f.has(e))continue;let n=e+1<l.length?l[e+1].startMarker:a,r=u[e]===-1?[t.startMarker,...t.nodes,t.endMarker]:ze(t.startMarker,t.endMarker);for(let e of r)p.insertBefore(e,n)}let m=w.empty();for(let e of l)m=w.set(m,e.key,e);return{records:m,order:s}})}function Re(e,t,n,i,a,o){return x.gen(function*(){let s=yield*O.fork(a,S.sequential),c={...o,scope:s},l=yield*P(),u=document.createComment(g(l)),d=document.createComment(m(l)),f=yield*B(i(t,n)).pipe(x.provideService(r,c),x.provideService(O.Scope,s));return{key:e,scope:s,startMarker:u,endMarker:d,nodes:f===null?[]:Array.isArray(f)?f:[f]}})}function ze(e,t){let n=[],r=e;for(;r!==null&&(n.push(r),r!==t);)r=r.nextSibling;return n}function Be(e,t){let n=e;for(;n!==null;){let e=n.nextSibling;if(n.remove(),n===t)break;n=e}}function Ve(e){if(typeof e==`string`)return JSON.stringify(e);if(typeof e==`object`&&e)try{return JSON.stringify(e)}catch{return Object.prototype.toString.call(e)}return String(e)}function He(e){let t=e.length,n=[],r=Array.from({length:t},()=>-1);for(let i=0;i<t;i++){let t=e[i];if(t===-1)continue;let a=0,o=n.length;for(;a<o;){let r=a+o>>1;e[n[r]]<t?a=r+1:o=r}a>0&&(r[i]=n[a-1]),n[a]=i}let i=new Set,a=n.length>0?n[n.length-1]:-1;for(;a!==-1;)i.add(a),a=r[a];return i}function Ue(e,t){return x.gen(function*(){let n=yield*x.context(),i=yield*x.serviceOption(O.Scope),a=yield*O.make(),o=ie.make(ne.succeedContext(y.add(n,O.Scope,a))),s={runtime:o,scope:a,streamIdCounter:{current:0}},c=x.zipRight(O.close(a,C.void),x.promise(()=>o.dispose()));t.innerHTML=``;let l=yield*B(e).pipe(x.provideService(r,s),x.provideService(O.Scope,a),x.tapError(()=>c));if(l!==null)if(Array.isArray(l))for(let e of l)t.appendChild(e);else t.appendChild(l);let u=!1,d={unmount:()=>x.gen(function*(){u||(u=!0,yield*O.close(a,C.void),yield*x.promise(()=>o.dispose()))})};return E.isSome(i)&&(yield*O.addFinalizer(i.value,d.unmount())),d})}function We(e,t){return x.gen(function*(){let n=yield*x.context(),i=yield*x.serviceOption(O.Scope),a=yield*O.make(),o=ie.make(ne.succeedContext(y.add(n,O.Scope,a))),s=yield*Ke(),c={runtime:o,scope:a,streamIdCounter:{current:0},hydrationReady:s};st(t,c.streamIdCounter);let l=x.zipRight(O.close(a,C.void),x.promise(()=>o.dispose()));yield*J(e,t.firstChild,`root`).pipe(x.provideService(r,c),x.provideService(O.Scope,a),x.tapError(()=>l)),yield*s.settle,yield*s.awaitReady;let u=!1,d={unmount:()=>x.gen(function*(){u||(u=!0,yield*O.close(a,C.void),yield*x.promise(()=>o.dispose()))})};return E.isSome(i)&&(yield*O.addFinalizer(i.value,d.unmount())),d})}function J(e,t,n){return x.gen(function*(){if(typeof e==`string`||typeof e==`number`||typeof e==`bigint`)return yield*Ge(String(e),t,n);if(typeof e==`boolean`||e==null)return t;if(M(e)||x.isEffect(e)){let r=pe(e);if(r!==void 0)return yield*J(r,t,n);if(x.isEffect(e)){let r=x.runSyncExit(e);if(C.isSuccess(r))return yield*J(r.value,t,n)}return yield*Je(N(e),t,n)}if(typeof e==`object`&&Symbol.iterator in e&&!(`type`in e)){let r=t,i=0;for(let t of e)r=yield*J(t,r,`${n}[${i}]`),i++;return r}if(typeof e==`object`&&`type`in e&&!(Symbol.iterator in e)){let{type:r,props:a}=e;if(r===ce)return yield*Y(a,t,n);if(r===de){if(t!==null&&t.nodeType===Z){let e=_(t);if(e!==null&&e.kind===`start`)return yield*Xe(t,n)}return yield*Y(a,t,n)}return r===se?yield*Qe(a,t,n):r===ue?yield*et(a,t,n):r===le?yield*nt(a,t,n):typeof r==`string`?yield*tt(r,a,t,n):typeof r==`function`?yield*J(r(a),t,n):yield*x.fail(new i({type:r,message:`Invalid Renderable type during hydration at ${n}: expected string, FRAGMENT, or function, got ${typeof r}`}))}return t})}function Ge(e,t,n){return x.gen(function*(){if(e.length===0)return t;if(t===null||t.nodeType!==ot)return yield*$(`text ${JSON.stringify(e)}`,Q(t),n);let r=t;return r.data.startsWith(e)?r.data.length>e.length?r.splitText(e.length):r.nextSibling:yield*$(`text ${JSON.stringify(e)}`,Q(t),n)})}function Ke(){return x.gen(function*(){let e=yield*D.make(1),t=yield*b.make(),n=j(D.updateAndGet(e,e=>e-1),x.flatMap(e=>e<=0?x.asVoid(b.succeed(t,void 0)):x.void));return{register:D.update(e,e=>e+1),settle:n,awaitReady:b.await(t)}})}function qe(e){let t=!1;return x.suspend(()=>t||e===void 0?x.void:(t=!0,e.settle))}function Je(e,t,n){return x.gen(function*(){let i=yield*r;if(t===null||t.nodeType!==Z)return yield*$(`reactive region start marker`,Q(t),n);let a=t,o=d(a);if(o===null||o.kind!==`start`)return yield*$(`reactive region start marker`,Q(t),n);let s=ct(a);if(s===null)return yield*$(`reactive region end marker`,`unterminated region starting at ${JSON.stringify(a.data)}`,n);let c=qe(i.hydrationReady),l=!0,u=null,f=k.runForEach(e,e=>x.gen(function*(){u!==null&&(yield*O.close(u,C.void)),u=yield*O.fork(i.scope,S.sequential);let t={...i,scope:u};yield*x.gen(function*(){l?(l=!1,yield*Ye(e,a,s,n),yield*c):yield*U(a,s,e)}).pipe(x.provideService(r,t),x.provideService(O.Scope,u))})).pipe(x.ensuring(c));return i.hydrationReady!==void 0&&(yield*i.hydrationReady.register),yield*R(f,i.scope,`hydrate:stream-${o.id} (${n})`),s.nextSibling})}function Ye(e,t,n,r){return x.gen(function*(){let i=yield*J(e,t.nextSibling,`${r}<resume>`).pipe(x.map(e=>e===n?null:`adopted content did not align with the end marker`),x.catchTag(`HydrationMismatchError`,e=>x.succeed(`expected ${e.expected}, found ${e.actual} at ${e.path}`)));i!==null&&(console.error(`[weft] hydrate: reactive region at ${r} diverged from server output (${i}); patching.`),yield*U(t,n,e))})}function Xe(e,t){return x.gen(function*(){let n=Ze(e);if(n===null)return yield*$(`substituted suspense end marker`,`unterminated region starting at ${JSON.stringify(e.data)}`,t);let r=null;for(let t=e.nextSibling;t!==null&&t!==n;t=t.nextSibling)if(t.nodeType===X&&t.tagName===`SCRIPT`&&t.getAttribute(`type`)===`application/json`&&t.hasAttribute(`data-weft-suspense-failure`)){r=t;break}let i=null;if(r!==null){let e=r.textContent??``;i=yield*x.try({try:()=>JSON.parse(e),catch:e=>e}).pipe(x.catchAll(()=>x.succeed(null)))}if(typeof i!=`object`||!i||!(`error`in i))return console.error(`[weft] hydrate: substituted suspense region at ${t} has no decodable failure sentinel; leaving its static content.`),n.nextSibling;let o=yield*x.serviceOption(a);return E.isNone(o)?(console.error(`[weft] hydrate: substituted suspense region at ${t} has no enclosing Boundary to replay its failure to; leaving its static content.`,i.error),n.nextSibling):(yield*o.value.reportError(v.fail(i.error)),n.nextSibling)})}function Ze(e){let t=0,n=e.nextSibling;for(;n!==null;){if(n.nodeType===Z){let e=_(n);if(e!==null)if(e.kind===`start`)t++;else if(t===0)return n;else t--}n=n.nextSibling}return null}function Qe(e,t,n){return x.gen(function*(){if(t===null||t.nodeType!==X||t.tagName!==`SCRIPT`||t.getAttribute(`type`)!==`application/json`||!t.hasAttribute(`data-weft-boundary-failure`)){let i=yield*r,s=yield*x.serviceOption(a),c=yield*O.fork(i.scope,S.sequential),l={...i,scope:c},d=yield*b.make(),f=yield*Y(e,t,n).pipe(x.provideService(a,{reportError:e=>b.fail(d,e).pipe(x.asVoid)}),x.provideService(r,l),x.provideService(O.Scope,c)),p=t?.parentNode??null;if(t===null||t===f||p===null)return console.error(`[weft] hydrate: boundary at ${n} adopted an empty extent; live failure recovery is not installed for it.`),f;let m=F(),h=document.createComment(o(m)),g=document.createComment(u(m));return p.insertBefore(h,t),f===null?p.appendChild(g):p.insertBefore(g,f),yield*x.forkIn(we(e,d,c,s,h,g),i.scope),f}let i=t,s=i.textContent??``,c=yield*x.gen(function*(){let t=yield*x.try({try:()=>JSON.parse(s),catch:e=>e}),n=p(e.children)[t.index];if(n===void 0)return null;let r=yield*ae.decodeUnknown(n.errorSchema)(t.error);return e.match(v.fail(r))}).pipe(x.catchAll(e=>(console.error(`[weft] hydrate: boundary failure payload at ${n} failed to decode; cannot replay.`,e),x.succeed(null))));if(c===null)return yield*$(`replayable boundary failure`,`undecodable failure payload`,n);let l=yield*J(c,i.nextSibling,n);return i.remove(),l})}function $e(e,t,n,r){return x.gen(function*(){let i=yield*A.make(n),a=yield*A.make(!1),o=yield*A.make(E.none());return{value:i,refetch:E.match(r,{onNone:()=>x.void,onSome:n=>x.gen(function*(){(yield*A.get(a))||(yield*A.set(a,!0),yield*x.gen(function*(){let r=yield*x.exit(n.call(e,t()));C.isSuccess(r)?(yield*A.set(i,r.value),yield*A.set(o,E.none())):yield*A.set(o,E.some(v.squash(r.cause)))}).pipe(x.ensuring(A.set(a,!1))))})}),pending:a,error:o}})}function et(e,t,n){return x.gen(function*(){if(t===null||t.nodeType!==X||t.tagName!==`SCRIPT`||t.getAttribute(`type`)!==`application/json`)return yield*$(`server boundary payload <script type="application/json">`,Q(t),n);if(t.hasAttribute(`data-weft-boundary-failure`))return yield*$(`server boundary success payload <script type="application/json">`,`boundary failure payload`,n);let r=t,i=r.textContent??``,a=yield*x.try({try:()=>JSON.parse(i),catch:e=>e}).pipe(x.flatMap(t=>ae.decodeUnknown(e.successSchema)(t)),x.catchAll(e=>(console.error(`[weft] hydrate: server boundary payload at ${n} failed to decode; cannot replay.`,e),$(`decodable server boundary payload`,`undecodable payload`,n)))),o=yield*x.serviceOption(oe),s=yield*$e(e.tag,e.payload,a,o),c=yield*J(e.render(s),r.nextSibling,n);return r.remove(),c})}function tt(e,t,n,r){return x.gen(function*(){if(n===null||n.nodeType!==X)return yield*$(`<${e}>`,Q(n),r);let i=n;return i.tagName.toLowerCase()===e.toLowerCase()?(yield*I(i,t),yield*Y(t,i.firstChild,`${r} > ${e}`),i.nextSibling):yield*$(`<${e}>`,Q(n),r)})}function Y(e,t,n){return x.gen(function*(){let r=`children`in e?e.children:void 0;if(r===void 0)return t;let i=Array.isArray(r)?r:[r],a=t,o=0;for(let e of i)a=yield*J(e,a,`${n}[${o}]`),o++;return a})}function nt(e,t,n){return x.gen(function*(){let i=yield*r,{of:a,by:o,render:s}=e;if(t===null||t.nodeType!==Z)return yield*$(`list region start marker`,Q(t),n);let c=t,l=d(c);if(l===null||l.kind!==`start`)return yield*$(`list region start marker`,Q(t),n);let u=ct(c);if(u===null)return yield*$(`list region end marker`,`unterminated region starting at ${JSON.stringify(c.data)}`,n);let f=rt(c,u),p=yield*O.fork(i.scope,S.sequential),m=(yield*fe.toSubscribable(a).pipe(x.provideService(O.Scope,p))).changes,h={records:w.empty(),order:[]},g=!0,_=qe(i.hydrationReady),v=k.runForEach(m,e=>x.gen(function*(){let t=Array.from(e);g?(g=!1,h=yield*it(t,o,s,f,p,c,u,i,n),yield*_):h=yield*q(t,o,s,h,p,u,i)})).pipe(x.ensuring(_));return i.hydrationReady!==void 0&&(yield*i.hydrationReady.register),yield*R(v,p,`hydrate:list-${l.id} (${n})`),u.nextSibling})}function rt(e,t){let n=[],r=e.nextSibling;for(;r!==null&&r!==t;){let e=r.nodeType===Z?s(r):null;if(e===null||e.kind!==`start`){r=r.nextSibling;continue}let i=r,a=[],o=0,c=null,l=i.nextSibling;for(;l!==null&&l!==t;){if(l.nodeType===Z){let e=s(l);if(e!==null)if(e.kind===`start`)o++;else if(o===0){c=l;break}else o--}a.push(l),l=l.nextSibling}if(c===null)break;n.push({startMarker:i,endMarker:c,nodes:a}),r=c.nextSibling}return n}function it(e,t,n,r,i,a,o,s,c){return x.gen(function*(){let l=yield*Le(e,t);if(l.length!==r.length)return console.error(`[weft] hydrate: list region at ${c} had ${r.length} server item(s) but the first emission has ${l.length}; rebuilding.`),K(a,o),yield*q(e,t,n,{records:w.empty(),order:[]},i,o,s);let u=[];for(let t=0;t<e.length;t++){let a=yield*at(l[t],e[t],t,n,r[t],i,s,c);u.push(a)}let d=w.empty();for(let e of u)d=w.set(d,e.key,e);return{records:d,order:l}})}function at(e,t,n,i,a,o,s,c){return x.gen(function*(){let l=yield*O.fork(o,S.sequential),u={...s,scope:l},d=i(t,n),f=yield*J(d,a.startMarker.nextSibling,`${c}<item>`).pipe(x.provideService(r,u),x.provideService(O.Scope,l),x.map(e=>e===a.endMarker?null:`adopted item content did not align with its end marker`),x.catchTag(`HydrationMismatchError`,e=>x.succeed(`expected ${e.expected}, found ${e.actual} at ${e.path}`)));if(f===null)return{key:e,scope:l,startMarker:a.startMarker,endMarker:a.endMarker,nodes:a.nodes};console.error(`[weft] hydrate: list item at ${c} diverged from server output (${f}); patching.`),yield*O.close(l,C.void);let p=yield*O.fork(o,S.sequential),m={...s,scope:p};K(a.startMarker,a.endMarker);let h=yield*B(d).pipe(x.provideService(r,m),x.provideService(O.Scope,p)),g=h===null?[]:Array.isArray(h)?h:[h],_=a.endMarker.parentNode;if(_!==null)for(let e of g)_.insertBefore(e,a.endMarker);return{key:e,scope:p,startMarker:a.startMarker,endMarker:a.endMarker,nodes:g}})}const X=1,ot=3,Z=8;function st(e,t){let n=document.createTreeWalker(e,128),r=t.current;for(let e=n.nextNode();e!==null;e=n.nextNode()){let t=e,n=d(t)??_(t)??s(t);n!==null&&n.id>r&&(r=n.id)}t.current=r}function ct(e){let t=0,n=e.nextSibling;for(;n!==null;){if(n.nodeType===8){let e=d(n);if(e!==null)if(e.kind===`start`)t++;else if(t===0)return n;else t--}n=n.nextSibling}return null}function Q(e){if(e===null)return`end of children`;switch(e.nodeType){case 1:return`<${e.tagName.toLowerCase()}>`;case 3:return`text ${JSON.stringify(e.data)}`;case 8:return`comment ${JSON.stringify(e.data)}`;default:return`node(type ${e.nodeType})`}}function $(e,n,r){return x.fail(new t({expected:e,actual:n,path:r}))}function lt(e,t){return j(Ue(e,t),x.tap(e=>x.addFinalizer(()=>e.unmount())))}function ut(e,t){return j(We(e,t),x.tap(e=>x.addFinalizer(()=>e.unmount())))}export{We as hydrate,ut as hydrateScoped,Ue as mount,lt as mountScoped};
|
|
1
|
+
import{i as e,n as t,o as n,r,s as i,t as a}from"../data-uLmMpQMV.js";import{a as o,c as s,d as c,f as l,i as u,l as d,m as f,n as p,o as m,p as h,s as g,u as _}from"../boundary-replay-BR26_puM.js";import{Cause as v,Context as y,Deferred as b,Effect as x,Exit as S,Fiber as ee,HashMap as C,HashSet as w,Layer as te,ManagedRuntime as ne,Option as T,Ref as E,Schema as re,Scope as D,Stream as O,SubscriptionRef as k,pipe as A}from"effect";import{AppRpcClientTag as ie,FAILURE_BOUNDARY as ae,FRAGMENT as oe,LIST as se,SERVER_BOUNDARY as ce,SUSPENSE_BOUNDARY as le,Source as ue,Subscribable as j,getElementDescriptor as M,isStream as N,toStream as P}from"@weftui/core";function F(){return x.gen(function*(){let e=yield*r;return++e.streamIdCounter.current})}const de=F;let fe=0;const pe=()=>++fe;function me(e){if(e.length<=2||!e.startsWith(`on`))return!1;let t=e[2];return t!==void 0&&t>=`a`&&t<=`z`}function I(e,t){return x.gen(function*(){for(let[n,r]of Object.entries(t))if(n!==`children`){if(me(n)){yield*xe(e,n,r);continue}if(n===`ref`&&typeof r==`object`&&k.isSubscriptionRef(r)){yield*k.set(r,T.some(e));continue}if(n===`style`){yield*ye(e,r);continue}he(e,n)?yield*ge(e,n,r):yield*_e(e,n,r)}})}function he(e,t){if(t.startsWith(`data-`)||t.startsWith(`aria-`))return!1;let n=Object.getPrototypeOf(e);for(;n!==null;){if(Object.hasOwn(n,t))return!0;n=Object.getPrototypeOf(n)}return t in e}function ge(e,t,n){return x.gen(function*(){N(n)||x.isEffect(n)?yield*z(P(n),n=>{n==null?delete e[t]:e[t]=n},`property:${t}`):n!=null&&(e[t]=n)})}function _e(e,t,n){return x.gen(function*(){if(N(n)||x.isEffect(n))yield*z(P(n),n=>{if(n==null)e.removeAttribute(t);else{let r=ve(n);r!==void 0&&(typeof n==`boolean`?n?e.setAttribute(t,``):e.removeAttribute(t):e.setAttribute(t,r))}},`attribute:${t}`);else{let r=ve(n);r!==void 0&&(typeof n==`boolean`?n?e.setAttribute(t,``):e.removeAttribute(t):e.setAttribute(t,r))}})}function ve(e){if(e!=null)return String(e)}function ye(e,t){return x.gen(function*(){if(N(t)||x.isEffect(t)){yield*z(P(t),t=>{if(typeof t==`string`)e.setAttribute(`style`,t);else if(typeof t==`object`&&t){e.style.cssText=``;for(let[n,r]of Object.entries(t))r!=null&&e.style.setProperty(L(n),String(r))}},`style`);return}if(typeof t==`string`){e.setAttribute(`style`,t);return}typeof t==`object`&&t&&(yield*be(e,t))})}function be(e,t){return x.gen(function*(){for(let[n,r]of Object.entries(t))N(r)||x.isEffect(r)?yield*z(P(r),t=>{t!=null&&e.style.setProperty(L(n),String(t))},`style.${n}`):r!=null&&e.style.setProperty(L(n),String(r))})}function L(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function R(e,t,n){return x.gen(function*(){let r=yield*x.serviceOption(a);if(T.isNone(r)){let r=yield*x.forkIn(e,t);return yield*A(ee.await(r),x.flatMap(e=>S.isFailure(e)&&!v.hasInterruptsOnly(e.cause)?A(x.logError(e.cause),x.annotateLogs(`weft.region`,n)):x.void),x.forkIn(t)),r}let i=yield*x.forkIn(e,t);return yield*A(ee.await(i),x.flatMap(e=>S.isFailure(e)?r.value.reportError(e.cause):x.void),x.forkIn(t)),i})}function z(e,t,n){return x.gen(function*(){let i=yield*r;yield*R(O.runForEach(e,e=>x.sync(()=>void t(e))),i.scope,n)})}function xe(e,t,n){return x.gen(function*(){let i=yield*r,a=t.slice(2).toLowerCase(),o=null,s=()=>{o&&=(e.removeEventListener(a,o),null)},c=n=>{s(),!(n==null||n===!1)&&typeof n==`function`&&(o=e=>{let r=n(e);x.isEffect(r)&&i.runtime.runFork(A(r,x.catch(e=>process.env.NODE_ENV===`development`?x.logError(`Event handler error: ${t}`,{error:e}):x.void)))},e.addEventListener(a,o))};yield*D.addFinalizer(i.scope,x.sync(s)),N(n)||x.isEffect(n)?yield*z(P(n),e=>c(e),`event:${t}`):c(n)})}function Se(e,t,n,r,i,a){return x.gen(function*(){let o=yield*b.await(t).pipe(x.flip,x.catch(()=>x.interrupt)),s=e.match(o);if(yield*D.close(n,S.void),s===null)return T.isSome(r)?yield*r.value.reportError(o):yield*x.logError(`Unhandled error escaped the outermost Boundary`,o);K(i,a);let c=yield*B(s),l=a.parentNode;if(l!==null&&c!==null)if(Array.isArray(c))for(let e of c)l.insertBefore(e,a);else l.insertBefore(c,a)})}function Ce(e){return x.gen(function*(){let t=yield*r,n=yield*x.serviceOption(a),i=pe(),s=document.createComment(o(i)),c=document.createComment(u(i)),l=yield*D.fork(t.scope,`sequential`),d={...t,scope:l},f=yield*b.make(),p=yield*A(V(e.children),x.provideService(a,{reportError:e=>b.fail(f,e).pipe(x.asVoid)}),x.provideService(r,d),x.provideService(D.Scope,l),x.catchCause(t=>{let n=e.match(t);return n===null?x.failCause(t):A(D.close(l,S.void),x.flatMap(()=>B(n)),x.map(e=>e===null?[]:Array.isArray(e)?e:[e]))}));return yield*x.forkIn(Se(e,f,l,n,s,c),t.scope),[s,...p,c]})}function we(e){return x.gen(function*(){let t=yield*r,i=yield*E.make(1),a=yield*b.make(),o=A(E.updateAndGet(i,e=>e-1),x.flatMap(e=>e<=0?x.asVoid(b.succeed(a,void 0)):x.void)),s={register:E.update(i,e=>e+1),settle:o},c=e.children,l=yield*B((c===void 0?[]:Array.isArray(c)?c:[c]).map(e=>M(e)===void 0&&(x.isEffect(e)||N(e))?{type:()=>e,props:{}}:e)).pipe(x.provideService(n,s)),u=l===null?[]:Array.isArray(l)?l:[l];yield*o;let d=yield*b.poll(a);if(T.isSome(d))return u;let p=yield*de(),m=document.createComment(f(p)),g=document.createComment(h(p)),_=yield*B(e.fallback??null),v=[];_!==null&&(Array.isArray(_)?v.push(..._):v.push(_));let y=document.createDocumentFragment();for(let e of u)y.appendChild(e);let S=x.gen(function*(){yield*b.await(a),K(m,g);let e=g.parentNode;e!==null&&(e.insertBefore(y,g),m.remove(),g.remove())});return yield*x.forkIn(S,t.scope),[m,...v,g]})}function Te(t){return x.gen(function*(){let n=yield*r,i=yield*x.serviceOption(ie);if(T.isNone(i))return yield*x.fail(new e({cause:void 0,message:`Boundary.rpc "${t.tag}" was mounted client-first without an AppRpcClient in context. Mount the app under @weftui/router (RouterLive), which provides the rpc client, so the boundary can resolve its data.`}));let a=i.value,s=pe(),c=document.createComment(o(s)),l=document.createComment(u(s)),d=yield*B(t.fallback??null),f=[];d!==null&&(Array.isArray(d)?f.push(...d):f.push(d));let p=x.gen(function*(){let e=yield*a.call(t.tag,t.payload()),n=yield*Ze(t.tag,t.payload,e,i),r=yield*B(t.render(n));K(c,l);let o=l.parentNode;if(o!==null&&r!==null)if(Array.isArray(r))for(let e of r)o.insertBefore(e,l);else o.insertBefore(r,l)}).pipe(x.catchCause(e=>x.logError(`[weft] Boundary.rpc "${t.tag}" mount failed to resolve; fallback left in place.`,e)));return yield*x.forkIn(p,n.scope),[c,...f,l]})}function B(e){return x.gen(function*(){if(typeof e==`string`||typeof e==`number`||typeof e==`bigint`)return document.createTextNode(String(e));if(typeof e==`boolean`||e==null)return null;if(N(e)||x.isEffect(e)){let t=M(e);if(t!==void 0)return yield*B(t);if(x.isEffect(e)){let t=yield*x.context(),n=x.runSyncExitWith(t)(e);if(S.isSuccess(n))return yield*B(n.value)}return yield*H(P(e))}if(typeof e==`object`&&Symbol.iterator in e&&!(`type`in e))return yield*V(Ee(e));if(typeof e==`object`&&`type`in e&&!(Symbol.iterator in e)){let{type:t,props:n}=e;return t===oe?yield*De(n):t===le?yield*we(n):t===ce?yield*Te(n):t===ae?yield*Ce(n):t===se?yield*Pe(n):typeof t==`string`?yield*Oe(t,n):typeof t==`function`?yield*ke(t,n):yield*x.fail(new i({type:t,message:`Invalid Renderable type: expected string, FRAGMENT, or function, got ${typeof t}`}))}return null})}function Ee(e){let t=[];function n(e){if(N(e)||x.isEffect(e)){t.push(e);return}if(typeof e==`object`&&e&&Symbol.iterator in e&&!(`type`in e))for(let t of e)n(t);else t.push(e)}return n(e),t}function V(e){return x.gen(function*(){let t=[];for(let n of e)if(M(n)===void 0&&(N(n)||x.isEffect(n))){let e=yield*H(P(n));t.push(...e)}else{let e=yield*B(n);e!==null&&(Array.isArray(e)?t.push(...e):t.push(e))}return t})}function De(e){return x.gen(function*(){let t=`children`in e?e.children:void 0;return t===void 0?[]:yield*V(Array.isArray(t)?t:[t])})}function Oe(e,t){return x.gen(function*(){let n=document.createElement(e);yield*I(n,t);let r=`children`in t?t.children:void 0;if(r!==void 0){let e=Array.isArray(r)?r:[r];for(let t of e)if(M(t)===void 0&&(N(t)||x.isEffect(t))){let e=yield*H(P(t));for(let t of e)n.appendChild(t)}else{let e=yield*B(t);if(e!==null)if(Array.isArray(e))for(let t of e)n.appendChild(t);else n.appendChild(e)}}return n})}function ke(e,t){return x.gen(function*(){let i=e(t);if(N(i)||x.isEffect(i)){let e=yield*r,t=yield*D.fork(e.scope,`sequential`),a={...e,scope:t},o=yield*x.serviceOption(n),s=P(i);return T.isSome(o)&&(yield*o.value.register,s=A(s,O.zipWithIndex,O.flatMap(([e,t])=>t===0?O.fromEffect(x.as(o.value.settle,e)):O.make(e)))),yield*H(s).pipe(x.provideService(r,a),x.provideService(D.Scope,t))}return yield*B(i)})}function H(e){return x.gen(function*(){let t=yield*r,n=yield*F(),[i,a]=Ae(n),o=null;return yield*R(O.runForEach(e,e=>x.gen(function*(){o!==null&&(yield*D.close(o,S.void)),o=yield*D.fork(t.scope,`sequential`);let n={...t,scope:o};yield*U(i,a,e).pipe(x.provideService(r,n),x.provideService(D.Scope,o))})),t.scope,`child:stream-${n}`),[i,a]})}function Ae(e){return[document.createComment(l(e)),document.createComment(c(e))]}function U(e,t,n){return x.gen(function*(){let r=e.nextSibling,i=r!==null&&r!==t&&r.nextSibling===t;if(i&&r.nodeType===it&&W(n)){let e=String(n);r.data!==e&&(r.data=e);return}if(i&&r.nodeType===X){let e=G(n);if(e!==void 0&&typeof e.type==`string`&&r.tagName.toLowerCase()===e.type.toLowerCase()){yield*je(r,e);return}}K(e,t);let a=yield*B(n),o=e.parentNode;if(o!==null&&a!==null)if(Array.isArray(a))for(let e of a)o.insertBefore(e,t);else o.insertBefore(a,t)})}function W(e){return typeof e==`string`||typeof e==`number`||typeof e==`bigint`}function G(e){let t=M(e);if(t!==void 0)return t;if(typeof e==`object`&&e&&`type`in e&&!(Symbol.iterator in e)&&!N(e)&&!x.isEffect(e))return e}function je(e,t){return x.gen(function*(){yield*I(e,t.props);let n=t.props.children,r=n===void 0?[]:Array.isArray(n)?n:[n];if(!(yield*Me(e,r))){for(;e.firstChild!==null;)e.firstChild.remove();yield*Ne(e,r)}})}function Me(e,t){return x.gen(function*(){let n=Array.from(e.childNodes);if(n.length!==t.length)return!1;for(let e=0;e<t.length;e++){let r=t[e],i=n[e];if(W(r)){if(i.nodeType!==it)return!1}else{let e=G(r);if(e===void 0||typeof e.type!=`string`||i.nodeType!==X||i.tagName.toLowerCase()!==e.type.toLowerCase())return!1}}for(let e=0;e<t.length;e++){let r=t[e],i=n[e];if(W(r)){let e=String(r);i.data!==e&&(i.data=e)}else yield*je(i,G(r))}return!0})}function Ne(e,t){return x.gen(function*(){for(let n of t)if(N(n)||x.isEffect(n)){let t=yield*H(P(n));for(let n of t)e.appendChild(n)}else{let t=yield*B(n);if(t!==null)if(Array.isArray(t))for(let n of t)e.appendChild(n);else e.appendChild(t)}})}function K(e,t){let n=e.nextSibling;for(;n!==null&&n!==t;){let e=n.nextSibling;n.remove(),n=e}}function Pe(e){return x.gen(function*(){let t=yield*r,{of:n,by:i,render:a}=e,o=yield*F(),[s,c]=Ae(o),l=yield*D.fork(t.scope,`sequential`),u=(yield*ue.toSubscribable(n).pipe(x.provideService(D.Scope,l))).changes,d={records:C.empty(),order:[]};return yield*R(O.runForEach(u,e=>x.gen(function*(){d=yield*q(Array.from(e),i,a,d,l,c,t)})),l,`list:stream-${o}`),[s,c]})}function Fe(t,n){return x.gen(function*(){let r=[],i=w.empty();for(let a=0;a<t.length;a++){let o=n===void 0?t[a]:n(t[a],a);if(w.has(i,o))return yield*x.fail(new e({cause:o,message:`List.each: duplicate key ${ze(o)} in a single emission; keys must be unique (set a stable \`by\`).`}));i=w.add(i,o),r.push(o)}return r})}function q(e,t,n,r,i,a,o){return x.gen(function*(){let s=yield*Fe(e,t),c=C.empty();r.order.forEach((e,t)=>{c=C.set(c,e,t)});let l=[],u=[];for(let t=0;t<e.length;t++){let a=s[t],d=C.get(r.records,a);if(T.isSome(d))l.push(d.value),u.push(T.getOrElse(C.get(c,a),()=>-1));else{let r=yield*Ie(a,e[t],t,n,i,o);l.push(r),u.push(-1)}}let d=w.empty();for(let e of s)d=w.add(d,e);for(let e of r.order)if(!w.has(d,e)){let t=C.get(r.records,e);T.isSome(t)&&(yield*D.close(t.value.scope,S.void),Re(t.value.startMarker,t.value.endMarker))}let f=Be(u),p=a.parentNode;if(p!==null)for(let e=l.length-1;e>=0;e--){let t=l[e];if(u[e]!==-1&&f.has(e))continue;let n=e+1<l.length?l[e+1].startMarker:a,r=u[e]===-1?[t.startMarker,...t.nodes,t.endMarker]:Le(t.startMarker,t.endMarker);for(let e of r)p.insertBefore(e,n)}let m=C.empty();for(let e of l)m=C.set(m,e.key,e);return{records:m,order:s}})}function Ie(e,t,n,i,a,o){return x.gen(function*(){let s=yield*D.fork(a,`sequential`),c={...o,scope:s},l=yield*F(),u=document.createComment(g(l)),d=document.createComment(m(l)),f=yield*B(i(t,n)).pipe(x.provideService(r,c),x.provideService(D.Scope,s));return{key:e,scope:s,startMarker:u,endMarker:d,nodes:f===null?[]:Array.isArray(f)?f:[f]}})}function Le(e,t){let n=[],r=e;for(;r!==null&&(n.push(r),r!==t);)r=r.nextSibling;return n}function Re(e,t){let n=e;for(;n!==null;){let e=n.nextSibling;if(n.remove(),n===t)break;n=e}}function ze(e){if(typeof e==`string`)return JSON.stringify(e);if(typeof e==`object`&&e)try{return JSON.stringify(e)}catch{return Object.prototype.toString.call(e)}return String(e)}function Be(e){let t=e.length,n=[],r=Array.from({length:t},()=>-1);for(let i=0;i<t;i++){let t=e[i];if(t===-1)continue;let a=0,o=n.length;for(;a<o;){let r=a+o>>1;e[n[r]]<t?a=r+1:o=r}a>0&&(r[i]=n[a-1]),n[a]=i}let i=new Set,a=n.length>0?n[n.length-1]:-1;for(;a!==-1;)i.add(a),a=r[a];return i}function Ve(e,t){return x.gen(function*(){let n=yield*x.context(),i=yield*x.serviceOption(D.Scope),a=yield*D.make(),o=ne.make(te.succeedContext(y.add(n,D.Scope,a))),s={runtime:o,scope:a,streamIdCounter:{current:0}},c=x.andThen(D.close(a,S.void),x.promise(()=>o.dispose()));t.innerHTML=``;let l=yield*B(e).pipe(x.provideService(r,s),x.provideService(D.Scope,a),x.tapError(()=>c));if(l!==null)if(Array.isArray(l))for(let e of l)t.appendChild(e);else t.appendChild(l);let u=!1,d={unmount:()=>x.gen(function*(){u||(u=!0,yield*D.close(a,S.void),yield*x.promise(()=>o.dispose()))})};return T.isSome(i)&&(yield*D.addFinalizer(i.value,d.unmount())),d})}function He(e,t){return x.gen(function*(){let n=yield*x.context(),i=yield*x.serviceOption(D.Scope),a=yield*D.make(),o=ne.make(te.succeedContext(y.add(n,D.Scope,a))),s=yield*We(),c={runtime:o,scope:a,streamIdCounter:{current:0},hydrationReady:s};at(t,c.streamIdCounter);let l=x.andThen(D.close(a,S.void),x.promise(()=>o.dispose()));yield*J(e,t.firstChild,`root`).pipe(x.provideService(r,c),x.provideService(D.Scope,a),x.tapError(()=>l)),yield*s.settle,yield*s.awaitReady;let u=!1,d={unmount:()=>x.gen(function*(){u||(u=!0,yield*D.close(a,S.void),yield*x.promise(()=>o.dispose()))})};return T.isSome(i)&&(yield*D.addFinalizer(i.value,d.unmount())),d})}function J(e,t,n){return x.gen(function*(){if(typeof e==`string`||typeof e==`number`||typeof e==`bigint`)return yield*Ue(String(e),t,n);if(typeof e==`boolean`||e==null)return t;if(N(e)||x.isEffect(e)){let r=M(e);if(r!==void 0)return yield*J(r,t,n);if(x.isEffect(e)){let r=x.runSyncExit(e);if(S.isSuccess(r))return yield*J(r.value,t,n)}return yield*Ke(P(e),t,n)}if(typeof e==`object`&&Symbol.iterator in e&&!(`type`in e)){let r=t,i=0;for(let t of e)r=yield*J(t,r,`${n}[${i}]`),i++;return r}if(typeof e==`object`&&`type`in e&&!(Symbol.iterator in e)){let{type:r,props:a}=e;if(r===oe)return yield*Y(a,t,n);if(r===le){if(t!==null&&t.nodeType===Z){let e=_(t);if(e!==null&&e.kind===`start`)return yield*Je(t,n)}return yield*Y(a,t,n)}return r===ae?yield*Xe(a,t,n):r===ce?yield*Qe(a,t,n):r===se?yield*et(a,t,n):typeof r==`string`?yield*$e(r,a,t,n):typeof r==`function`?yield*J(r(a),t,n):yield*x.fail(new i({type:r,message:`Invalid Renderable type during hydration at ${n}: expected string, FRAGMENT, or function, got ${typeof r}`}))}return t})}function Ue(e,t,n){return x.gen(function*(){if(e.length===0)return t;if(t===null||t.nodeType!==it)return yield*$(`text ${JSON.stringify(e)}`,Q(t),n);let r=t;return r.data.startsWith(e)?r.data.length>e.length?r.splitText(e.length):r.nextSibling:yield*$(`text ${JSON.stringify(e)}`,Q(t),n)})}function We(){return x.gen(function*(){let e=yield*E.make(1),t=yield*b.make(),n=A(E.updateAndGet(e,e=>e-1),x.flatMap(e=>e<=0?x.asVoid(b.succeed(t,void 0)):x.void));return{register:E.update(e,e=>e+1),settle:n,awaitReady:b.await(t)}})}function Ge(e){let t=!1;return x.suspend(()=>t||e===void 0?x.void:(t=!0,e.settle))}function Ke(e,t,n){return x.gen(function*(){let i=yield*r;if(t===null||t.nodeType!==Z)return yield*$(`reactive region start marker`,Q(t),n);let a=t,o=d(a);if(o===null||o.kind!==`start`)return yield*$(`reactive region start marker`,Q(t),n);let s=ot(a);if(s===null)return yield*$(`reactive region end marker`,`unterminated region starting at ${JSON.stringify(a.data)}`,n);let c=Ge(i.hydrationReady),l=!0,u=null,f=O.runForEach(e,e=>x.gen(function*(){u!==null&&(yield*D.close(u,S.void)),u=yield*D.fork(i.scope,`sequential`);let t={...i,scope:u};yield*x.gen(function*(){l?(l=!1,yield*qe(e,a,s,n),yield*c):yield*U(a,s,e)}).pipe(x.provideService(r,t),x.provideService(D.Scope,u))})).pipe(x.ensuring(c));return i.hydrationReady!==void 0&&(yield*i.hydrationReady.register),yield*R(f,i.scope,`hydrate:stream-${o.id} (${n})`),s.nextSibling})}function qe(e,t,n,r){return x.gen(function*(){let i=yield*J(e,t.nextSibling,`${r}<resume>`).pipe(x.map(e=>e===n?null:`adopted content did not align with the end marker`),x.catchTag(`HydrationMismatchError`,e=>x.succeed(`expected ${e.expected}, found ${e.actual} at ${e.path}`)));i!==null&&(console.error(`[weft] hydrate: reactive region at ${r} diverged from server output (${i}); patching.`),yield*U(t,n,e))})}function Je(e,t){return x.gen(function*(){let n=Ye(e);if(n===null)return yield*$(`substituted suspense end marker`,`unterminated region starting at ${JSON.stringify(e.data)}`,t);let r=null;for(let t=e.nextSibling;t!==null&&t!==n;t=t.nextSibling)if(t.nodeType===X&&t.tagName===`SCRIPT`&&t.getAttribute(`type`)===`application/json`&&t.hasAttribute(`data-weft-suspense-failure`)){r=t;break}let i=null;if(r!==null){let e=r.textContent??``;i=yield*x.try({try:()=>JSON.parse(e),catch:e=>e}).pipe(x.catch(()=>x.succeed(null)))}if(typeof i!=`object`||!i||!(`error`in i))return console.error(`[weft] hydrate: substituted suspense region at ${t} has no decodable failure sentinel; leaving its static content.`),n.nextSibling;let o=yield*x.serviceOption(a);return T.isNone(o)?(console.error(`[weft] hydrate: substituted suspense region at ${t} has no enclosing Boundary to replay its failure to; leaving its static content.`,i.error),n.nextSibling):(yield*o.value.reportError(v.fail(i.error)),n.nextSibling)})}function Ye(e){let t=0,n=e.nextSibling;for(;n!==null;){if(n.nodeType===Z){let e=_(n);if(e!==null)if(e.kind===`start`)t++;else if(t===0)return n;else t--}n=n.nextSibling}return null}function Xe(e,t,n){return x.gen(function*(){if(t===null||t.nodeType!==X||t.tagName!==`SCRIPT`||t.getAttribute(`type`)!==`application/json`||!t.hasAttribute(`data-weft-boundary-failure`)){let i=yield*r,s=yield*x.serviceOption(a),c=yield*D.fork(i.scope,`sequential`),l={...i,scope:c},d=yield*b.make(),f=yield*Y(e,t,n).pipe(x.provideService(a,{reportError:e=>b.fail(d,e).pipe(x.asVoid)}),x.provideService(r,l),x.provideService(D.Scope,c)),p=t?.parentNode??null;if(t===null||t===f||p===null)return console.error(`[weft] hydrate: boundary at ${n} adopted an empty extent; live failure recovery is not installed for it.`),f;let m=pe(),h=document.createComment(o(m)),g=document.createComment(u(m));return p.insertBefore(h,t),f===null?p.appendChild(g):p.insertBefore(g,f),yield*x.forkIn(Se(e,d,c,s,h,g),i.scope),f}let i=t,s=i.textContent??``,c=yield*x.gen(function*(){let t=yield*x.try({try:()=>JSON.parse(s),catch:e=>e}),n=p(e.children)[t.index];if(n===void 0)return null;let r=yield*re.decodeUnknownEffect(n.errorSchema)(t.error);return e.match(v.fail(r))}).pipe(x.catch(e=>(console.error(`[weft] hydrate: boundary failure payload at ${n} failed to decode; cannot replay.`,e),x.succeed(null))));if(c===null)return yield*$(`replayable boundary failure`,`undecodable failure payload`,n);let l=yield*J(c,i.nextSibling,n);return i.remove(),l})}function Ze(e,t,n,r){return x.gen(function*(){let i=yield*k.make(n),a=yield*k.make(!1),o=yield*k.make(T.none()),s=T.match(r,{onNone:()=>x.void,onSome:n=>x.gen(function*(){(yield*k.get(a))||(yield*k.set(a,!0),yield*x.gen(function*(){let r=yield*x.exit(n.call(e,t()));S.isSuccess(r)?(yield*k.set(i,r.value),yield*k.set(o,T.none())):yield*k.set(o,T.some(v.squash(r.cause)))}).pipe(x.ensuring(k.set(a,!1))))})});return{value:j.make({get:k.get(i),changes:k.changes(i)}),refetch:s,pending:j.make({get:k.get(a),changes:k.changes(a)}),error:j.make({get:k.get(o),changes:k.changes(o)})}})}function Qe(e,t,n){return x.gen(function*(){if(t===null||t.nodeType!==X||t.tagName!==`SCRIPT`||t.getAttribute(`type`)!==`application/json`)return yield*$(`server boundary payload <script type="application/json">`,Q(t),n);if(t.hasAttribute(`data-weft-boundary-failure`))return yield*$(`server boundary success payload <script type="application/json">`,`boundary failure payload`,n);let r=t,i=r.textContent??``,a=yield*x.try({try:()=>JSON.parse(i),catch:e=>e}).pipe(x.flatMap(t=>re.decodeUnknownEffect(e.successSchema)(t)),x.catch(e=>(console.error(`[weft] hydrate: server boundary payload at ${n} failed to decode; cannot replay.`,e),$(`decodable server boundary payload`,`undecodable payload`,n)))),o=yield*x.serviceOption(ie),s=yield*Ze(e.tag,e.payload,a,o),c=yield*J(e.render(s),r.nextSibling,n);return r.remove(),c})}function $e(e,t,n,r){return x.gen(function*(){if(n===null||n.nodeType!==X)return yield*$(`<${e}>`,Q(n),r);let i=n;return i.tagName.toLowerCase()===e.toLowerCase()?(yield*I(i,t),yield*Y(t,i.firstChild,`${r} > ${e}`),i.nextSibling):yield*$(`<${e}>`,Q(n),r)})}function Y(e,t,n){return x.gen(function*(){let r=`children`in e?e.children:void 0;if(r===void 0)return t;let i=Array.isArray(r)?r:[r],a=t,o=0;for(let e of i)a=yield*J(e,a,`${n}[${o}]`),o++;return a})}function et(e,t,n){return x.gen(function*(){let i=yield*r,{of:a,by:o,render:s}=e;if(t===null||t.nodeType!==Z)return yield*$(`list region start marker`,Q(t),n);let c=t,l=d(c);if(l===null||l.kind!==`start`)return yield*$(`list region start marker`,Q(t),n);let u=ot(c);if(u===null)return yield*$(`list region end marker`,`unterminated region starting at ${JSON.stringify(c.data)}`,n);let f=tt(c,u),p=yield*D.fork(i.scope,`sequential`),m=(yield*ue.toSubscribable(a).pipe(x.provideService(D.Scope,p))).changes,h={records:C.empty(),order:[]},g=!0,_=Ge(i.hydrationReady),v=O.runForEach(m,e=>x.gen(function*(){let t=Array.from(e);g?(g=!1,h=yield*nt(t,o,s,f,p,c,u,i,n),yield*_):h=yield*q(t,o,s,h,p,u,i)})).pipe(x.ensuring(_));return i.hydrationReady!==void 0&&(yield*i.hydrationReady.register),yield*R(v,p,`hydrate:list-${l.id} (${n})`),u.nextSibling})}function tt(e,t){let n=[],r=e.nextSibling;for(;r!==null&&r!==t;){let e=r.nodeType===Z?s(r):null;if(e===null||e.kind!==`start`){r=r.nextSibling;continue}let i=r,a=[],o=0,c=null,l=i.nextSibling;for(;l!==null&&l!==t;){if(l.nodeType===Z){let e=s(l);if(e!==null)if(e.kind===`start`)o++;else if(o===0){c=l;break}else o--}a.push(l),l=l.nextSibling}if(c===null)break;n.push({startMarker:i,endMarker:c,nodes:a}),r=c.nextSibling}return n}function nt(e,t,n,r,i,a,o,s,c){return x.gen(function*(){let l=yield*Fe(e,t);if(l.length!==r.length)return console.error(`[weft] hydrate: list region at ${c} had ${r.length} server item(s) but the first emission has ${l.length}; rebuilding.`),K(a,o),yield*q(e,t,n,{records:C.empty(),order:[]},i,o,s);let u=[];for(let t=0;t<e.length;t++){let a=yield*rt(l[t],e[t],t,n,r[t],i,s,c);u.push(a)}let d=C.empty();for(let e of u)d=C.set(d,e.key,e);return{records:d,order:l}})}function rt(e,t,n,i,a,o,s,c){return x.gen(function*(){let l=yield*D.fork(o,`sequential`),u={...s,scope:l},d=i(t,n),f=yield*J(d,a.startMarker.nextSibling,`${c}<item>`).pipe(x.provideService(r,u),x.provideService(D.Scope,l),x.map(e=>e===a.endMarker?null:`adopted item content did not align with its end marker`),x.catchTag(`HydrationMismatchError`,e=>x.succeed(`expected ${e.expected}, found ${e.actual} at ${e.path}`)));if(f===null)return{key:e,scope:l,startMarker:a.startMarker,endMarker:a.endMarker,nodes:a.nodes};console.error(`[weft] hydrate: list item at ${c} diverged from server output (${f}); patching.`),yield*D.close(l,S.void);let p=yield*D.fork(o,`sequential`),m={...s,scope:p};K(a.startMarker,a.endMarker);let h=yield*B(d).pipe(x.provideService(r,m),x.provideService(D.Scope,p)),g=h===null?[]:Array.isArray(h)?h:[h],_=a.endMarker.parentNode;if(_!==null)for(let e of g)_.insertBefore(e,a.endMarker);return{key:e,scope:p,startMarker:a.startMarker,endMarker:a.endMarker,nodes:g}})}const X=1,it=3,Z=8;function at(e,t){let n=document.createTreeWalker(e,128),r=t.current;for(let e=n.nextNode();e!==null;e=n.nextNode()){let t=e,n=d(t)??_(t)??s(t);n!==null&&n.id>r&&(r=n.id)}t.current=r}function ot(e){let t=0,n=e.nextSibling;for(;n!==null;){if(n.nodeType===8){let e=d(n);if(e!==null)if(e.kind===`start`)t++;else if(t===0)return n;else t--}n=n.nextSibling}return null}function Q(e){if(e===null)return`end of children`;switch(e.nodeType){case 1:return`<${e.tagName.toLowerCase()}>`;case 3:return`text ${JSON.stringify(e.data)}`;case 8:return`comment ${JSON.stringify(e.data)}`;default:return`node(type ${e.nodeType})`}}function $(e,n,r){return x.fail(new t({expected:e,actual:n,path:r}))}function st(e,t){return A(Ve(e,t),x.tap(e=>x.addFinalizer(()=>e.unmount())))}function ct(e,t){return A(He(e,t),x.tap(e=>x.addFinalizer(()=>e.unmount())))}export{He as hydrate,ct as hydrateScoped,Ve as mount,st as mountScoped};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Context as e,Data as t}from"effect";var n=class extends t.TaggedError(`UnsupportedNodeTypeError`){},r=class extends t.TaggedError(`StreamSubscriptionError`){},i=class extends t.TaggedError(`RenderError`){},a=class extends t.TaggedError(`HydrationMismatchError`){},o=class extends e.
|
|
1
|
+
import{Context as e,Data as t}from"effect";var n=class extends t.TaggedError(`UnsupportedNodeTypeError`){},r=class extends t.TaggedError(`StreamSubscriptionError`){},i=class extends t.TaggedError(`RenderError`){},a=class extends t.TaggedError(`HydrationMismatchError`){},o=class extends e.Service()(`BoundaryContext`){},s=class extends e.Service()(`SuspenseContext`){},c=class extends e.Service()(`RenderContext`){};export{r as a,i,a as n,s as o,c as r,n as s,o as t};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,i as t,n,s as r}from"./data-
|
|
1
|
+
import{a as e,i as t,n,s as r}from"./data-uLmMpQMV.js";export{n as HydrationMismatchError,t as RenderError,e as StreamSubscriptionError,r as UnsupportedNodeTypeError};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -123,9 +123,9 @@ interface SuspenseFailureHandler {
|
|
|
123
123
|
/** Decide a substitute for an unhandled Suspense resolution failure. */
|
|
124
124
|
readonly handle: (cause: Cause.Cause<unknown>) => Option.Option<SuspenseFailureSubstitute>;
|
|
125
125
|
}
|
|
126
|
-
declare const SuspenseFailureHandlerTag_base: Context.
|
|
126
|
+
declare const SuspenseFailureHandlerTag_base: Context.ServiceClass<SuspenseFailureHandlerTag, "@weftui/dom/SuspenseFailureHandler", SuspenseFailureHandler>;
|
|
127
127
|
/**
|
|
128
|
-
* `Context.
|
|
128
|
+
* `Context.Service` key for the {@link SuspenseFailureHandler} seam. Read via
|
|
129
129
|
* `Effect.serviceOption` inside the Suspense resolution fiber, so providing it
|
|
130
130
|
* is always optional. Lives in `@weftui/dom` (like `AppRpcClientTag`) so dom
|
|
131
131
|
* never imports its consumers.
|
package/dist/server/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{s as e}from"../data-
|
|
1
|
+
import{s as e}from"../data-uLmMpQMV.js";import{d as t,f as n,m as r,n as i,o as a,p as o,r as s,s as c,t as l}from"../boundary-replay-BR26_puM.js";import{Cause as u,Context as d,Effect as f,Exit as p,Option as m,Queue as h,Ref as g,Schema as _,Scope as v,Stream as y}from"effect";import{AppRpcClientTag as ee,FAILURE_BOUNDARY as b,FRAGMENT as x,LIST as S,SERVER_BOUNDARY as C,SUSPENSE_BOUNDARY as w,Source as T,Subscribable as E,getElementDescriptor as D,isStream as O,toStream as k}from"@weftui/core";function A(e){if(e.length<=2||!e.startsWith(`on`))return!1;let t=e[2];return t!==void 0&&t>=`a`&&t<=`z`}const j=new Set([`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`param`,`source`,`track`,`wbr`]),te={'"':`"`,"&":`&`,"'":`'`,"<":`<`,">":`>`};function M(e){return e.replace(/["'&<>]/g,e=>te[e]??e)}const N=new Set([38,60,62,8232,8233]);function P(e){let t=JSON.stringify(e),n=``;for(let e=0;e<t.length;e++){let r=t.charCodeAt(e);N.has(r)?n+=`\\u${r.toString(16).padStart(4,`0`)}`:n+=t[e]}return n}function ne(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function F(e){return f.gen(function*(){let t=``;for(let[n,r]of Object.entries(e))if(!(n===`children`||n===`ref`||A(n))){if(n===`style`){t+=yield*R(r);continue}t+=yield*L(n,r)}return t})}function I(e){return O(e)||f.isEffect(e)?k(e).pipe(y.runHead,f.map(m.getOrElse(()=>void 0))):f.succeed(e)}function L(e,t){return f.gen(function*(){let n=yield*I(t);return n==null?``:typeof n==`boolean`?n?` ${e}=""`:``:` ${e}="${M(String(n))}"`})}function R(e){return f.gen(function*(){let t=yield*I(e);if(t==null)return``;if(typeof t==`string`)return t===``?``:` style="${M(t)}"`;if(typeof t==`object`){let e=[];for(let[n,r]of Object.entries(t)){let t=yield*I(r);t!=null&&e.push(`${ne(n)}: ${String(t)}`)}return e.length===0?``:` style="${M(e.join(`; `))}"`}return``})}var z=class extends d.Service()(`@weftui/dom/SuspenseFailureHandler`){};function B(e,t,n=!1,i){let a=r(e),c=o(e),l=n?`var m=document.createElement("meta");m.setAttribute("name","robots");m.setAttribute("content","noindex");document.head.appendChild(m);`:``,u=i!==void 0,d=u?`<script type="application/json" ${s}>`+P({error:i})+`<\/script>`+t:t,f=u?``:`p.removeChild(s);p.removeChild(e);`;return`<template id="ef-s-${e}">${d}</template><script>(function(){`+l+`var w=document.createTreeWalker(document,128),s,e;while(w.nextNode()){var d=w.currentNode.data;if(d==="${a}")s=w.currentNode;if(d==="${c}"){e=w.currentNode;break;}}if(!s||!e)return;var p=s.parentNode,c=s.nextSibling,n;while(c&&c!==e){n=c.nextSibling;p.removeChild(c);c=n;}var t=document.getElementById("ef-s-${e}");p.insertBefore(t.content,e);`+f+`t.remove();document.currentScript.remove();})();<\/script>`}function V(e,t,n){return y.unwrap(f.gen(function*(){let i=++t.idCounter.current;yield*g.update(t.pendingCount,e=>e+1);let a=e.children===void 0?null:(Array.isArray(e.children),e.children),s=f.gen(function*(){let e=yield*y.mkString(n(a));yield*h.offer(t.patchQueue,m.some(B(i,e)))}).pipe(f.catchCause(e=>u.hasInterruptsOnly(e)?f.failCause(e):f.gen(function*(){let r=yield*f.serviceOption(z);if(m.isNone(r))return;let a=r.value.handle(e);if(m.isNone(a))return;let o=yield*y.mkString(n(a.value.content));yield*h.offer(t.patchQueue,m.some(B(i,o,a.value.markNoindex,a.value.failureReplay)))})),f.ensuring(g.updateAndGet(t.pendingCount,e=>e-1).pipe(f.flatMap(e=>e<=0?h.offer(t.patchQueue,m.none()).pipe(f.asVoid):f.void))),f.ignore);yield*f.forkIn(s,t.scope);let c=`<!--${r(i)}-->`,l=`<!--${o(i)}-->`,d=e.fallback??null;return y.make(c).pipe(y.concat(n(d)),y.concat(y.make(l)))}))}function H(e,t,n){let r=e.children.length===0?null:e.children.length===1?e.children[0]:e.children;return y.unwrap(f.gen(function*(){let a=yield*y.mkString(n(r)).pipe(f.catchCause(r=>f.gen(function*(){let a=e.match(r);if(a===null)return yield*f.failCause(r);let o=yield*y.mkString(n(a));if(t===null)return o;let s=yield*g.getAndSet(t,m.none());return m.isNone(s)?o:`<script type="application/json" ${l}>${P({index:i(e.children).indexOf(s.value.owner),error:s.value.encoded})}<\/script>`+o})));return y.make(a)}))}function re(e,t,n){if(t===null)return f.failCause(n);let r=u.findErrorOption(n);return m.isNone(r)?f.failCause(n):_.encodeEffect(e.errorSchema)(r.value).pipe(f.flatMap(n=>g.set(t,m.some({owner:e,encoded:n}))),f.matchCauseEffect({onFailure:()=>f.failCause(n),onSuccess:()=>f.failCause(n)}))}function U(e){return{value:E.make({get:f.succeed(e),changes:y.make(e)}),refetch:f.void,pending:E.make({get:f.succeed(!1),changes:y.make(!1)}),error:E.make({get:f.succeed(m.none()),changes:y.make(m.none())})}}function W(e,t,n,r){return y.unwrap(f.gen(function*(){let i=yield*(yield*ee).call(e.tag,e.payload()).pipe(f.catchCause(t=>re(e,n,t))),a=r(e.render(U(i)));if(!t)return a;let o=`<script type="application/json">${P(yield*_.encodeEffect(e.successSchema)(i))}<\/script>`;return y.make(o).pipe(y.concat(a))}))}function G(e){return f.scoped(T.toSubscribable(e).pipe(f.flatMap(e=>e.get),f.map(e=>Array.from(e)),f.catchTag(`NoPropValue`,()=>f.succeed([]))))}function K(t,n){if(t==null||typeof t==`boolean`)return y.empty;if(typeof t==`string`||typeof t==`number`||typeof t==`bigint`)return y.make(M(String(t)));if(O(t)||f.isEffect(t)){let e=D(t);if(e!==void 0)return K(e,n);if(f.isEffect(t)){let e=f.runSyncExit(t);if(p.isSuccess(e))return K(e.value,n)}return k(t).pipe(y.runHead,f.map(m.match({onNone:()=>y.empty,onSome:e=>K(e,n)})),y.unwrap)}if(typeof t==`object`&&Symbol.iterator in t&&!(`type`in t))return y.flatMap(y.fromIterable(t),e=>K(e,n));if(typeof t==`object`&&`type`in t&&!(Symbol.iterator in t)){let{type:e,props:r}=t;if(e===x)return J(r,n);if(e===w){let e=r;return n===null?K(e.fallback??null,null):V(e,n,e=>K(e,n))}if(e===b)return H(r,null,e=>K(e,n));if(e===C)return W(r,!1,null,e=>K(e,n));if(e===S)return q(r,n);if(typeof e==`string`){let t=y.fromEffect(F(r).pipe(f.map(t=>`<${e}${t}>`)));return j.has(e)?t:t.pipe(y.concat(J(r,n)),y.concat(y.make(`</${e}>`)))}if(typeof e==`function`)return K(e(r),n)}return y.fail(new e({type:t.type,message:`Invalid Renderable type: expected string, FRAGMENT, or function, got ${typeof t.type}`}))}function q(e,t){return y.unwrap(f.gen(function*(){let n=yield*G(e.of),r=y.empty;return n.forEach((n,i)=>{r=r.pipe(y.concat(K(e.render(n,i),t)))}),r}))}function J(e,t){let n=`children`in e?e.children:void 0;if(n==null)return y.empty;let r=Array.isArray(n)?n:[n];return y.flatMap(y.fromIterable(r),e=>K(e,t))}function Y(r,i,a){if(r==null||typeof r==`boolean`)return y.empty;if(typeof r==`string`||typeof r==`number`||typeof r==`bigint`)return y.make(M(String(r)));if(O(r)||f.isEffect(r)){let e=D(r);if(e!==void 0)return Y(e,i,a);if(f.isEffect(r)){let e=f.runSyncExit(r);if(p.isSuccess(e))return Y(e.value,i,a)}return k(r).pipe(y.runHead,f.map(e=>{let r=++i.current,o=m.match(e,{onNone:()=>y.empty,onSome:e=>Y(e,i,a)});return y.make(`<!--${n(r)}-->`).pipe(y.concat(o),y.concat(y.make(`<!--${t(r)}-->`)))}),y.unwrap)}if(typeof r==`object`&&Symbol.iterator in r&&!(`type`in r))return y.flatMap(y.fromIterable(r),e=>Y(e,i,a));if(typeof r==`object`&&`type`in r&&!(Symbol.iterator in r)){let{type:e,props:t}=r;if(e===x)return X(t,i,a);if(e===w){let e=t;return a===null?Y(e.fallback??null,i,null):V(e,a,e=>Y(e,i,a))}if(e===b)return H(t,a?.failureCollector??null,e=>Y(e,i,a));if(e===C)return W(t,!0,a?.failureCollector??null,e=>Y(e,i,a));if(e===S)return ie(t,i,a);if(typeof e==`string`){let n=y.fromEffect(F(t).pipe(f.map(t=>`<${e}${t}>`)));return j.has(e)?n:n.pipe(y.concat(X(t,i,a)),y.concat(y.make(`</${e}>`)))}if(typeof e==`function`)return Y(e(t),i,a)}return y.fail(new e({type:r.type,message:`Invalid Renderable type: expected string, FRAGMENT, or function, got ${typeof r.type}`}))}function ie(e,r,i){return y.unwrap(f.gen(function*(){let o=++r.current,s=yield*G(e.of),l=y.empty;return s.forEach((t,n)=>{let o=++r.current;l=l.pipe(y.concat(y.make(`<!--${c(o)}-->`)),y.concat(Y(e.render(t,n),r,i)),y.concat(y.make(`<!--${a(o)}-->`)))}),y.make(`<!--${n(o)}-->`).pipe(y.concat(l),y.concat(y.make(`<!--${t(o)}-->`)))}))}function X(e,t,n){let r=`children`in e?e.children:void 0;if(r==null)return y.empty;let i=Array.isArray(r)?r:[r];return y.flatMap(y.fromIterable(i),e=>Y(e,t,n))}const ae=e=>K(e,null),oe=e=>y.unwrap(f.gen(function*(){let t=yield*h.unbounded(),n=yield*g.make(0),r=K(e,{patchQueue:t,pendingCount:n,idCounter:{current:0},scope:yield*f.scope,failureCollector:null}).pipe(y.ensuring(g.get(n).pipe(f.flatMap(e=>e===0?h.offer(t,m.none()).pipe(f.asVoid):f.void))));return y.concat(r,Z(t))})),Z=e=>y.fromQueue(e).pipe(y.takeWhile(m.isSome),y.map(e=>e.value)),Q=(e,t)=>f.gen(function*(){let n=yield*h.unbounded();yield*v.addFinalizer(t,f.asVoid(h.offer(n,m.none())));let r=yield*g.make(0);return{mainStream:Y(e,{current:0},{patchQueue:n,pendingCount:r,idCounter:{current:0},scope:t,failureCollector:yield*g.make(m.none())}).pipe(y.ensuring(g.get(r).pipe(f.flatMap(e=>e===0?h.offer(n,m.none()).pipe(f.asVoid):f.void)))),patches:Z(n)}}),$=e=>y.unwrap(f.gen(function*(){let t=yield*f.scope,{mainStream:n,patches:r}=yield*Q(e,t);return y.concat(n,r)})),se=e=>ae(e).pipe(y.mkString),ce=e=>$(e).pipe(y.mkString),le=e=>f.gen(function*(){let t=yield*f.scope,n=yield*v.fork(t,`sequential`),{mainStream:r,patches:i}=yield*Q(e,n);return{shell:yield*y.mkString(r).pipe(f.onError(e=>v.close(n,p.failCause(e)))),patches:i}});export{z as SuspenseFailureHandlerTag,le as renderToHydratableShell,oe as renderToStream,$ as renderToStreamHydratable,se as renderToString,ce as renderToStringHydratable};
|
|
@@ -18,21 +18,21 @@ A component's `E` channel accumulates up the tree. A **failure boundary** is whe
|
|
|
18
18
|
```typescript
|
|
19
19
|
import { Boundary, h } from "@weftui/core";
|
|
20
20
|
|
|
21
|
-
Boundary.
|
|
21
|
+
Boundary.catch({ fallback: (e) => h.div({ class: "error" }, `Failed: ${e.message}`) }, [
|
|
22
22
|
RiskyWidget(),
|
|
23
23
|
]);
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
There are six failure-catch variants, mirroring Effect's own error operators so the mental model transfers directly:
|
|
27
27
|
|
|
28
|
-
| Variant
|
|
29
|
-
|
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `catchTag` / `catchTags`
|
|
33
|
-
| `
|
|
28
|
+
| Variant | Catches |
|
|
29
|
+
| ------------------------- | ------------------------------------------ |
|
|
30
|
+
| `catch` | every failure in `E` |
|
|
31
|
+
| `catchCause` | the full `Cause` (defects included) |
|
|
32
|
+
| `catchTag` / `catchTags` | one / several tagged errors by `_tag` |
|
|
33
|
+
| `catchFilter` / `catchIf` | a selected subset, by `Filter` / predicate |
|
|
34
34
|
|
|
35
|
-
The channel algebra is the whole reason they exist: `catchTag("Foo", …)` removes `Foo` from the children's `E` and adds whatever the fallback needs — so the type of the boundary node reflects exactly which failures are still live and which were handled. An unhandled failure re-raises to the **nearest enclosing** boundary; if none catches it at **mount time**, mounting fails. Boundaries nest, so an inner `catchTag` can handle a specific case while an outer `
|
|
35
|
+
The channel algebra is the whole reason they exist: `catchTag("Foo", …)` removes `Foo` from the children's `E` and adds whatever the fallback needs — so the type of the boundary node reflects exactly which failures are still live and which were handled. An unhandled failure re-raises to the **nearest enclosing** boundary; if none catches it at **mount time**, mounting fails. Boundaries nest, so an inner `catchTag` can handle a specific case while an outer `catch` sweeps the rest.
|
|
36
36
|
|
|
37
37
|
### Post-mount failures with no enclosing boundary
|
|
38
38
|
|
|
@@ -52,11 +52,11 @@ import { SubscriptionRef, Stream } from "effect";
|
|
|
52
52
|
|
|
53
53
|
const count = yield * SubscriptionRef.make(0);
|
|
54
54
|
|
|
55
|
-
//
|
|
56
|
-
h.span([
|
|
55
|
+
// SubscriptionRef.changes(count) is a Stream<number> — each new value updates the text node
|
|
56
|
+
h.span([SubscriptionRef.changes(count)]);
|
|
57
57
|
|
|
58
58
|
// Stream as a prop — each emission sets the attribute
|
|
59
|
-
const isDisabled = Stream.map(
|
|
59
|
+
const isDisabled = Stream.map(SubscriptionRef.changes(count), (n) => n >= 10);
|
|
60
60
|
h.button({ disabled: isDisabled }, "Submit");
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -65,22 +65,22 @@ Streams can also supply entire child arrays. Each emission replaces the previous
|
|
|
65
65
|
```typescript
|
|
66
66
|
const todos = yield * SubscriptionRef.make<string[]>([]);
|
|
67
67
|
|
|
68
|
-
h.ul([Stream.map(
|
|
68
|
+
h.ul([Stream.map(SubscriptionRef.changes(todos), (list) => list.map((item) => h.li(item)))]);
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
## Derived streams
|
|
72
72
|
|
|
73
|
-
Because
|
|
73
|
+
Because `SubscriptionRef.changes(ref)` returns a plain `Stream`, the full Stream API applies:
|
|
74
74
|
|
|
75
75
|
```typescript
|
|
76
76
|
const count = yield * SubscriptionRef.make(0);
|
|
77
77
|
|
|
78
|
-
const doubled = Stream.map(
|
|
79
|
-
const formatted = Stream.map(
|
|
80
|
-
const isHigh = Stream.map(
|
|
78
|
+
const doubled = Stream.map(SubscriptionRef.changes(count), (n) => n * 2);
|
|
79
|
+
const formatted = Stream.map(SubscriptionRef.changes(count), (n) => `Count: ${n}`);
|
|
80
|
+
const isHigh = Stream.map(SubscriptionRef.changes(count), (n) => n > 10);
|
|
81
81
|
|
|
82
82
|
h.div([
|
|
83
|
-
h.p([
|
|
83
|
+
h.p([SubscriptionRef.changes(count)]),
|
|
84
84
|
h.p([doubled]),
|
|
85
85
|
h.p([formatted]),
|
|
86
86
|
h.p({ style: { color: Stream.map(isHigh, (b) => (b ? "red" : "black")) } }, "Status"),
|
|
@@ -93,8 +93,10 @@ Multiple refs can be combined with `Stream.zipLatestWith`, `Stream.merge`, or ot
|
|
|
93
93
|
const firstName = yield * SubscriptionRef.make("");
|
|
94
94
|
const lastName = yield * SubscriptionRef.make("");
|
|
95
95
|
|
|
96
|
-
const fullName = Stream.zipLatestWith(
|
|
97
|
-
|
|
96
|
+
const fullName = Stream.zipLatestWith(
|
|
97
|
+
SubscriptionRef.changes(firstName),
|
|
98
|
+
SubscriptionRef.changes(lastName),
|
|
99
|
+
(first, last) => `${first} ${last}`.trim(),
|
|
98
100
|
);
|
|
99
101
|
```
|
|
100
102
|
|
|
@@ -20,7 +20,7 @@ type Node<E = never, R = never> = Effect.Effect<ElementDescriptor, E, R>;
|
|
|
20
20
|
Every element in a Weft tree **is an Effect**. `h.div(...)`, a component's return value, a boundary — each is an `Effect` that, when run, produces an element descriptor. Two consequences follow immediately, and they shape everything else:
|
|
21
21
|
|
|
22
22
|
1. **The error (`E`) and requirement (`R`) channels accumulate through the tree.** A child that reads a service, or a prop backed by a failible stream, contributes its `R` and `E` to its parent, which contributes to _its_ parent, up to the mount boundary. The type of your app node is the exact union of everything it needs and everything it can fail with — visible to the type checker, satisfiable exactly once, at `mount`/`hydrate`. See [The Combinator API](https://weftui.dev/docs/explanation/combinator-api) for how the accumulation works mechanically.
|
|
23
|
-
2. **Every Effect combinator applies to a node directly.** `Effect.provide`, `Effect.flatMap`, `Effect.gen`, `Effect.
|
|
23
|
+
2. **Every Effect combinator applies to a node directly.** `Effect.provide`, `Effect.flatMap`, `Effect.gen`, `Effect.catch` — none of them are special-cased for UI. A node is an ordinary Effect, so the entire Effect ecosystem composes with your view for free.
|
|
24
24
|
|
|
25
25
|
JSX collapses every component to an opaque `JSX.Element`, erasing both channels. Weft keeps them, and that is the point of the whole design. (There is [no JSX](https://weftui.dev/docs/explanation/combinator-api) here — components are plain functions you _call_.)
|
|
26
26
|
|
|
@@ -39,7 +39,7 @@ The `ThemeServiceLive` example above works because `mount`'s effect and the serv
|
|
|
39
39
|
Effect.runPromise(mount(App(), root).pipe(Effect.provide(SomeScopedLayer)));
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
This is exactly what happened with
|
|
42
|
+
This is exactly what happened with the atom registry layer (`AtomRegistry.layer`, from `effect/unstable/reactivity`) in the [`effect-atom` example](https://github.com/stefvw93/weft/tree/main/examples/effect-atom) (issue #122): every atom-driven region rendered empty, with no error, because the registry the streams read from had already been disposed.
|
|
43
43
|
|
|
44
44
|
The fix is to give the scoped layer a lifetime that matches the app, not the initial render: provide it **outside** a scoped region that stays open for as long as the app should run, and mount inside that region with `mountScoped` (which ties `unmount` to the region's scope instead of to the resolution of the mount effect). An `Effect.never` (or `Deferred.await` on a shutdown signal) keeps the region — and therefore the layer — alive until something explicitly closes it. See [Provide Services](https://weftui.dev/docs/how-to/provide-services) for the recipe, including the `ManagedRuntime` alternative when a scoped region isn't a good fit.
|
|
45
45
|
|
|
@@ -50,7 +50,7 @@ A plain `mount`/`hydrate` discharges `R` at the call site. But under `@weftui/ro
|
|
|
50
50
|
So the router exposes an explicit **`context` seam** — a `Layer` threaded to the document shell and every route, layout, and leaf:
|
|
51
51
|
|
|
52
52
|
```typescript
|
|
53
|
-
class Greeting extends Context.
|
|
53
|
+
class Greeting extends Context.Service<Greeting, { text: string }>()("Greeting") {}
|
|
54
54
|
|
|
55
55
|
// server entry
|
|
56
56
|
RouterServer.render(App, { document, url, context: Layer.succeed(Greeting, { text: "hi" }) });
|
|
@@ -63,7 +63,7 @@ The seam is **symmetric** (same shape on both sides) and **type-tracked**: the d
|
|
|
63
63
|
|
|
64
64
|
## Server-only services: `ServerTag`
|
|
65
65
|
|
|
66
|
-
Some services must _never_ run in the browser — a database handle, a private credential, an rpc handler's backing store. Declare those with [`ServerTag`](https://weftui.dev/docs/reference/core#servertag) instead of `Context.
|
|
66
|
+
Some services must _never_ run in the browser — a database handle, a private credential, an rpc handler's backing store. Declare those with [`ServerTag`](https://weftui.dev/docs/reference/core#servertag) instead of `Context.Service`. It behaves exactly like `Context.Service`, but its identifier carries a **server-only brand**.
|
|
67
67
|
|
|
68
68
|
The brand's job is to turn a leak into a **compile error at the `hydrate` call site**. A `Boundary.rpc` handler legitimately reads server-only services on the server, but they must not survive into client code: since `render` only ever touches the _decoded result_ (never the service), a correctly-written boundary keeps its output `R` free of the brand. If a branded tag ever leaks into `render` and reaches the client requirement channel, `hydrate`'s `AssertNoServerOnly` resolves `R` to a compile-error sentinel — you learn at build time, not from a runtime defect.
|
|
69
69
|
|
|
@@ -165,7 +165,7 @@ Router.route("users/:id", {
|
|
|
165
165
|
path: idParam,
|
|
166
166
|
component: Component.gen(function* () {
|
|
167
167
|
const { id } = yield* Router.params(idParam);
|
|
168
|
-
if (id < 0) return yield* notFound();
|
|
168
|
+
if (!Number.isFinite(id) || id < 0) return yield* notFound();
|
|
169
169
|
return yield* h.div(`User ${id}`);
|
|
170
170
|
}),
|
|
171
171
|
});
|
|
@@ -173,6 +173,8 @@ Router.route("users/:id", {
|
|
|
173
173
|
|
|
174
174
|
`RouterNotFound` is exported, so a `Boundary.catchTag("RouterNotFound", …)` placed inside a subtree overrides the app-level fallback for that subtree (the router's internal boundary is outermost, so a nearer user boundary wins).
|
|
175
175
|
|
|
176
|
+
> **`Schema.NumberFromString` gotcha.** Decoding no longer fails on a non-numeric segment — `/users/abc` decodes `id` to `NaN` instead of missing the route. A leaf that guards a numeric param must check `Number.isFinite(id)` itself (as above); relying on the schema alone to 404 non-numeric input no longer works.
|
|
177
|
+
|
|
176
178
|
## Client setup
|
|
177
179
|
|
|
178
180
|
On the client, provide the `Router` via `RouterLive(def)` and render `RouterApp(def)`. `RouterLive` is a **scoped layer** — it owns the `popstate` listener and the same-origin link-click interceptor — so it must outlive the mount. Provide it through a long-lived `ManagedRuntime` rather than `Effect.provide` at the node level:
|
|
@@ -231,6 +233,16 @@ yield * patchQuery({ sort: "old" }); // merges into the current query
|
|
|
231
233
|
- **`navigate(ref, args)`** builds the URL via [`href`](#type-safe-links-with-href) (so it round-trips with the matcher) and pushes — or, with `{ replace: true }`, replaces — the History entry. `args` follows the same requiredness rules as `href`.
|
|
232
234
|
- **`setQuery` / `patchQuery`** keep the path, so the active leaf is never remounted — pair them with `Router.queryStream` for in-place reactive updates. They are a no-op when no route is matched.
|
|
233
235
|
|
|
236
|
+
### Scroll position on navigation
|
|
237
|
+
|
|
238
|
+
A client navigation whose **path** changes resets the window scroll to the top at commit — matching what a full page load would do, which a raw History `pushState`/`replaceState` otherwise doesn't. This applies uniformly to `Router.navigate`, clicking a link the [interceptor](#link-interception) handles, and the `push` / `replace` helpers.
|
|
239
|
+
|
|
240
|
+
- **Query-only navigations preserve scroll.** `setQuery` / `patchQuery` (and any navigation that keeps the same path) don't reset — the leaf stays mounted, so there's nothing to scroll away from.
|
|
241
|
+
- **Back/forward is untouched.** The router never resets scroll on `popstate`; the browser's native `history.scrollRestoration: "auto"` restores the offset the entry had when the user left it.
|
|
242
|
+
- **Hash navigation (`#section`) is unaffected** — it's browser-native, and the link interceptor already lets same-document/hash-only clicks fall through.
|
|
243
|
+
|
|
244
|
+
There's no opt-out; the behavior is hardwired.
|
|
245
|
+
|
|
234
246
|
## Server setup
|
|
235
247
|
|
|
236
248
|
On the server, `RouterServer` matches a request URL, builds a fixed-match `Router`, renders `RouterApp` to hydratable HTML inside a **document shell**, and reports a status (404 when no route matches or a page raises `RouterNotFound`).
|
|
@@ -266,9 +278,9 @@ export const handler = RouterServer.toWebHandler(App, { document: documentShell
|
|
|
266
278
|
|
|
267
279
|
`render` provides both `Router.Outlet` (the app, per request) and `Router` (so the shell may read params), and renders through `renderToStringHydratable` so the client can `hydrate` in place.
|
|
268
280
|
|
|
269
|
-
###
|
|
281
|
+
### `effect/unstable/httpapi` is the spine
|
|
270
282
|
|
|
271
|
-
The tree is the authoring surface, but
|
|
283
|
+
The tree is the authoring surface, but `effect/unstable/httpapi`'s `HttpApi` is the **single source of truth** for paths and schemas. Sealing the tree with `Router.router(...)` builds it once (`buildHttpApi`) and stamps it onto `def.httpApi`: a single `"pages"` group with one GET endpoint per leaf at its full path pattern, carrying `setPath(pathSchema)`, `setUrlParams(querySchema)`, and a `RouterNotFound → 404` error. Both sides read that one definition, so they always agree:
|
|
272
284
|
|
|
273
285
|
- **Server** — `RouterServer` dispatches through `HttpApiBuilder` (platform owns request→leaf matching, path/query decode, and the 404 status).
|
|
274
286
|
- **Client** — `RouterLive` derives a real `HttpApiClient` from the same `def.httpApi` (exposed as `Router.httpApiClient`) for network work. SPA URL→leaf resolution stays **local** (there is no public client-side "match this URL against my `HttpApi`" utility in platform), fed from the same endpoint definitions so it never drifts from the server.
|
|
@@ -280,7 +292,7 @@ The tree is the authoring surface, but `@effect/platform`'s `HttpApi` is the **s
|
|
|
280
292
|
| `RouterNotFound` | `notFound()`, or no route matched | `Boundary.catchTag("RouterNotFound", …)` (or the app-level `notFound` page) |
|
|
281
293
|
| `RouterParamsError` | `Router.params` / `Router.query` on a missing/invalid key or no match | `Boundary.catchTag("RouterParamsError", …)` |
|
|
282
294
|
|
|
283
|
-
Both are modeled as `Schema.
|
|
295
|
+
Both are modeled as `Schema.TaggedErrorClass`, so they encode/decode across the wire the same way `Boundary.rpc` replays typed failures.
|
|
284
296
|
|
|
285
297
|
## `Boundary.rpc` interplay
|
|
286
298
|
|
|
@@ -289,7 +301,7 @@ Initial SSR navigation works end to end: the server resolves the rpc and inlines
|
|
|
289
301
|
## See also
|
|
290
302
|
|
|
291
303
|
- [`@weftui/router` API reference](https://weftui.dev/docs/reference/router)
|
|
292
|
-
- [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr) — a runnable SSR + hydration app with nested layouts, persistent layout state, type-safe `href`s, handler-arg props, and programmatic navigation over the
|
|
304
|
+
- [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr) — a runnable SSR + hydration app with nested layouts, persistent layout state, type-safe `href`s, handler-arg props, and programmatic navigation over the `effect/unstable/httpapi` spine
|
|
293
305
|
- [Component Authoring](https://weftui.dev/docs/how-to/author-components) — `Component.make` / `Component.gen`, the idiomatic way to write route components
|
|
294
306
|
- [Server-Side Rendering](https://weftui.dev/docs/how-to/render-on-the-server) — `renderToStringHydratable`, `hydrate`, and `Boundary.rpc`
|
|
295
307
|
- [RPC Data Boundaries](https://weftui.dev/docs/how-to/load-data-with-rpc) — `Boundary.rpc`, the `Resource` handle, and the four lifecycles
|
|
@@ -43,7 +43,7 @@ const Counter = () =>
|
|
|
43
43
|
const count = yield* SubscriptionRef.make(0);
|
|
44
44
|
|
|
45
45
|
return yield* h.div([
|
|
46
|
-
h.span([
|
|
46
|
+
h.span([SubscriptionRef.changes(count)]),
|
|
47
47
|
h.button({ onclick: () => SubscriptionRef.update(count, (n) => n + 1) }, "+"),
|
|
48
48
|
]);
|
|
49
49
|
});
|
|
@@ -63,10 +63,10 @@ so it is already in context when you need it.
|
|
|
63
63
|
This matters the moment a component starts **background work** — a subscription, an
|
|
64
64
|
observer of a `ref`, a polling timer, anything you `fork`. The rule:
|
|
65
65
|
|
|
66
|
-
> Fork background work with **`Effect.forkScoped`**, never a bare `Effect.
|
|
66
|
+
> Fork background work with **`Effect.forkScoped`**, never a bare `Effect.forkChild`.
|
|
67
67
|
|
|
68
68
|
`Effect.forkScoped` attaches the fiber to the instance scope, so it keeps running for
|
|
69
|
-
the component's lifetime and is interrupted on unmount. A bare `Effect.
|
|
69
|
+
the component's lifetime and is interrupted on unmount. A bare `Effect.forkChild` instead
|
|
70
70
|
attaches the fiber to the component-body fiber — the one that runs your `Effect.gen` to
|
|
71
71
|
produce the tree. That fiber completes the instant the gen returns its node, so the
|
|
72
72
|
forked work is cancelled almost immediately.
|
|
@@ -82,12 +82,12 @@ const AutoFocusInput = () =>
|
|
|
82
82
|
const inputRef = yield* SubscriptionRef.make<Option.Option<HTMLInputElement>>(Option.none());
|
|
83
83
|
|
|
84
84
|
yield* pipe(
|
|
85
|
-
|
|
85
|
+
SubscriptionRef.changes(inputRef),
|
|
86
86
|
Stream.filter(Option.isSome),
|
|
87
87
|
Stream.take(1),
|
|
88
88
|
Stream.runForEach((el) => Effect.sync(() => el.value.focus())),
|
|
89
89
|
Effect.forkScoped, // ✅ tied to the instance scope — survives until unmount
|
|
90
|
-
// Effect.
|
|
90
|
+
// Effect.forkChild, // ❌ tied to the body fiber — interrupted when the gen returns
|
|
91
91
|
);
|
|
92
92
|
|
|
93
93
|
return yield* h.input({ ref: inputRef, type: "text" });
|
|
@@ -9,15 +9,15 @@ description: Build a controlled form with SubscriptionRef field state, reactive
|
|
|
9
9
|
|
|
10
10
|
**Goal:** a controlled form whose inputs drive `SubscriptionRef` state, whose errors update reactively as the user types, and whose submit runs an Effect.
|
|
11
11
|
|
|
12
|
-
Each field is a `SubscriptionRef`. Bind it with `oninput`, derive validation from its
|
|
12
|
+
Each field is a `SubscriptionRef`. Bind it with `oninput`, derive validation from its `SubscriptionRef.changes(ref)` stream, and return an `Effect` from `onsubmit` (after `preventDefault`).
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
15
|
import { h } from "@weftui/core";
|
|
16
|
-
import { Effect,
|
|
16
|
+
import { Effect, Result, Schema, Stream, SubscriptionRef } from "effect";
|
|
17
17
|
|
|
18
18
|
const Email = Schema.String.pipe(
|
|
19
|
-
Schema.
|
|
20
|
-
Schema.
|
|
19
|
+
Schema.check(Schema.makeFilter((s) => (s.includes("@") ? undefined : "Must contain @"))),
|
|
20
|
+
Schema.check(Schema.makeFilter((s) => (s.includes(".") ? undefined : "Must contain a domain"))),
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
const LoginForm = () =>
|
|
@@ -26,11 +26,11 @@ const LoginForm = () =>
|
|
|
26
26
|
const status = yield* SubscriptionRef.make<string | null>(null);
|
|
27
27
|
|
|
28
28
|
// Validation is a stream derived from the field — it re-runs as the user types.
|
|
29
|
-
const error = Stream.map(
|
|
29
|
+
const error = Stream.map(SubscriptionRef.changes(email), (value) => {
|
|
30
30
|
if (value.length === 0) return null; // don't nag an empty field
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
return Result.match(Schema.decodeUnknownResult(Email)(value), {
|
|
32
|
+
onFailure: (e) => e.message.split(":").pop()?.trim() ?? "Invalid",
|
|
33
|
+
onSuccess: () => null,
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -52,7 +52,7 @@ const LoginForm = () =>
|
|
|
52
52
|
}),
|
|
53
53
|
Stream.map(error, (err) => (err ? h.span({ class: "error-text" }, err) : null)),
|
|
54
54
|
h.button({ type: "submit" }, "Login"),
|
|
55
|
-
h.div([Stream.map(
|
|
55
|
+
h.div([Stream.map(SubscriptionRef.changes(status), (s) => (s ? h.span(s) : null))]),
|
|
56
56
|
],
|
|
57
57
|
);
|
|
58
58
|
});
|
|
@@ -61,7 +61,7 @@ const LoginForm = () =>
|
|
|
61
61
|
## How it works
|
|
62
62
|
|
|
63
63
|
- **Field state** is a `SubscriptionRef.make("")`; `oninput` writes the current value with `SubscriptionRef.set`. Because the input is driven by the ref, it is a controlled input.
|
|
64
|
-
- **Validation is reactive**, not on-blur or on-submit only: `Stream.map(
|
|
64
|
+
- **Validation is reactive**, not on-blur or on-submit only: `Stream.map(SubscriptionRef.changes(email), …)` produces an error string (or `null`) on every keystroke. Use [`Schema`](https://effect.website/docs/schema/introduction) to decode — `Schema.decodeUnknownResult(schema)(value)` returns a `Result`, and `Result.match` turns it into UI. A node or `null` in a child slot renders the error or nothing.
|
|
65
65
|
- **Submit returns an Effect.** `onsubmit` calls `e.preventDefault()` and then **returns** an `Effect` (it is not `yield*`-ed inline) — the renderer runs it in a detached fiber, so it can `SubscriptionRef.set`, `Effect.sleep`, read fields with `SubscriptionRef.get`, or call a service.
|
|
66
66
|
|
|
67
67
|
## Variations
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
title: Load Async Data
|
|
3
3
|
order: 9
|
|
4
4
|
section: how-to
|
|
5
|
-
description: Show a loading state then resolved content with Stream.concat, and turn a failed fetch into a fallback node with Effect.
|
|
5
|
+
description: Show a loading state then resolved content with Stream.concat, and turn a failed fetch into a fallback node with Effect.catch — all client-side.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Load Async Data
|
|
9
9
|
|
|
10
10
|
**Goal:** render a loading placeholder, then the fetched content, and a fallback if the fetch fails — for data that loads **on the client**.
|
|
11
11
|
|
|
12
|
-
Return a `Stream<Node>` that emits the loading node first and the resolved node second, sequenced with `Stream.concat`. Handle failure inside the effect with `Effect.
|
|
12
|
+
Return a `Stream<Node>` that emits the loading node first and the resolved node second, sequenced with `Stream.concat`. Handle failure inside the effect with `Effect.catch`, which maps the error to a fallback node.
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
15
|
import { h } from "@weftui/core";
|
|
@@ -34,7 +34,7 @@ const UserCard = ({ id }: { id: number }) =>
|
|
|
34
34
|
Stream.fromEffect(
|
|
35
35
|
fetchUser(id).pipe(
|
|
36
36
|
Effect.flatMap((user) => h.div({ class: "user-card" }, [h.h3(user.name), h.p(user.email)])),
|
|
37
|
-
Effect.
|
|
37
|
+
Effect.catch((error) => h.div({ class: "error" }, `Error: ${error.message}`)),
|
|
38
38
|
),
|
|
39
39
|
),
|
|
40
40
|
);
|
|
@@ -44,7 +44,7 @@ const UserCard = ({ id }: { id: number }) =>
|
|
|
44
44
|
|
|
45
45
|
- **`Stream.concat`** sequences two streams: `Stream.make(loadingNode)` emits once immediately, then `Stream.fromEffect(effect)` emits the resolved node when the effect completes. The renderer swaps the DOM in place on the second emission.
|
|
46
46
|
- **`Effect.flatMap((data) => h.div(...))`** builds the content node from the data — `h.*` returns a `Node`, which is an `Effect`, so it composes directly in the pipeline.
|
|
47
|
-
- **`Effect.
|
|
47
|
+
- **`Effect.catch((error) => node)`** converts the error channel into a fallback node, so the stream always yields something renderable. The failure never escapes to the mount.
|
|
48
48
|
- **Parallel loading is automatic:** place several async components as siblings and their fetches run concurrently — no orchestration needed.
|
|
49
49
|
|
|
50
50
|
## When to reach for a boundary instead
|
|
@@ -39,7 +39,7 @@ The rpc **contract** (pure Schema) is shared with the client. The rpc **handler*
|
|
|
39
39
|
|
|
40
40
|
```typescript
|
|
41
41
|
// data/inventory.ts
|
|
42
|
-
import { Rpc, RpcGroup } from "
|
|
42
|
+
import { Rpc, RpcGroup } from "effect/unstable/rpc";
|
|
43
43
|
import { Context, Effect, Layer, Schema } from "effect";
|
|
44
44
|
|
|
45
45
|
// --- Contract (shareable with the client) ---
|
|
@@ -53,10 +53,10 @@ export const GetStock = Rpc.make("GetStock", { payload: StockKey, success: Stock
|
|
|
53
53
|
export const StockRpcs = RpcGroup.make(GetStock);
|
|
54
54
|
|
|
55
55
|
// --- Handler (server-only; the client never imports this) ---
|
|
56
|
-
class Inventory extends Context.
|
|
56
|
+
class Inventory extends Context.Service<
|
|
57
57
|
Inventory,
|
|
58
58
|
{ readonly stockFor: (id: number) => Effect.Effect<typeof Stock.Type> }
|
|
59
|
-
>() {}
|
|
59
|
+
>()("Inventory") {}
|
|
60
60
|
|
|
61
61
|
const InventoryLive = Layer.succeed(Inventory, {
|
|
62
62
|
stockFor: (id) => Effect.succeed({ units: 7 + (id % 5) }),
|
|
@@ -68,7 +68,7 @@ export const StockLive = StockRpcs.toLayer({
|
|
|
68
68
|
}).pipe(Layer.provide(InventoryLive));
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
Declare server-only services with [`ServerTag`](https://weftui.dev/docs/reference/core#servertag) (not `Context.
|
|
71
|
+
Declare server-only services with [`ServerTag`](https://weftui.dev/docs/reference/core#servertag) (not `Context.Service`) when they might be referenced from universal code: the brand makes a leak into `render` a compile error at the `hydrate` call site rather than a runtime surprise.
|
|
72
72
|
|
|
73
73
|
## Wiring the router
|
|
74
74
|
|
|
@@ -157,7 +157,7 @@ Boundary.catchTag({ tag: "OutOfStock", fallback: (e) => h.p({ class: "error" },
|
|
|
157
157
|
]);
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
A transport **defect** (no `Cause.
|
|
160
|
+
A transport **defect** (no `Cause.findErrorOption`), or an rpc with no `error` schema, is **not** replayed; it propagates — a server-side fallback and a client mismatch.
|
|
161
161
|
|
|
162
162
|
## When to use
|
|
163
163
|
|
|
@@ -121,4 +121,4 @@ In both cases the tell is the same: nothing in the composition keeps a scope ope
|
|
|
121
121
|
- [Layer lifetime at the mount](https://weftui.dev/docs/explanation/services-and-context#layer-lifetime-at-the-mount) — why the mount effect resolving early matters for scoped layers
|
|
122
122
|
- [Services and Context](https://weftui.dev/docs/explanation/services-and-context) — how `R` accumulates and discharges at the mount
|
|
123
123
|
- [`mountScoped` / `hydrateScoped` reference](https://weftui.dev/docs/reference/dom#mountscoped) — signatures and error unions
|
|
124
|
-
- [examples/effect-atom](https://github.com/stefvw93/weft/tree/main/examples/effect-atom) — a real scoped layer (`
|
|
124
|
+
- [examples/effect-atom](https://github.com/stefvw93/weft/tree/main/examples/effect-atom) — a real scoped layer (`AtomRegistry.layer` from `effect/unstable/reactivity`) mounted with this composition
|
|
@@ -9,7 +9,7 @@ description: Capture a DOM element with the ref prop into a SubscriptionRef<Opti
|
|
|
9
9
|
|
|
10
10
|
**Goal:** get a handle to a real DOM element — to focus it, measure it, or call an imperative browser API on it.
|
|
11
11
|
|
|
12
|
-
Declare a `SubscriptionRef<Option<HTMLElement>>`, attach it with the `ref` prop, and either **react** to the element appearing (a scoped observer on
|
|
12
|
+
Declare a `SubscriptionRef<Option<HTMLElement>>`, attach it with the `ref` prop, and either **react** to the element appearing (a scoped observer on `SubscriptionRef.changes(ref)`) or **read** it later inside a handler.
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
15
|
import { h } from "@weftui/core";
|
|
@@ -21,7 +21,7 @@ const AutoFocusInput = () =>
|
|
|
21
21
|
|
|
22
22
|
// Observe the element becoming available, once, and focus it.
|
|
23
23
|
yield* pipe(
|
|
24
|
-
|
|
24
|
+
SubscriptionRef.changes(inputRef),
|
|
25
25
|
Stream.filter(Option.isSome),
|
|
26
26
|
Stream.take(1),
|
|
27
27
|
Stream.runForEach((el) => Effect.sync(() => el.value.focus())),
|
|
@@ -35,8 +35,8 @@ const AutoFocusInput = () =>
|
|
|
35
35
|
## How it works
|
|
36
36
|
|
|
37
37
|
- **The `ref` prop** takes a `SubscriptionRef<Option<T>>`. The renderer sets it to `Option.some(element)` **once**, when the element is created — so the ref is an `Option`: `None` until mount, `Some(el)` after.
|
|
38
|
-
- **React to mount** by observing `
|
|
39
|
-
- **Use `Effect.forkScoped`, not `Effect.
|
|
38
|
+
- **React to mount** by observing `SubscriptionRef.changes(ref)`: `Stream.filter(Option.isSome)` waits for the element, `Stream.take(1)` takes just the first appearance, and `Stream.runForEach` does the imperative work. This is the equivalent of a mount effect.
|
|
39
|
+
- **Use `Effect.forkScoped`, not `Effect.forkChild`.** `forkScoped` ties the observer fiber to the component's **instance scope** (the ambient `Scope` the renderer provides), so it lives as long as the component is mounted. A bare `Effect.forkChild` binds to the transient component-body fiber and is interrupted the instant the generator returns — the observer would never fire.
|
|
40
40
|
|
|
41
41
|
## Read a ref imperatively
|
|
42
42
|
|
|
@@ -58,6 +58,6 @@ const scroll = () =>
|
|
|
58
58
|
|
|
59
59
|
## See also
|
|
60
60
|
|
|
61
|
-
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) — `SubscriptionRef` and
|
|
61
|
+
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) — `SubscriptionRef` and `SubscriptionRef.changes`
|
|
62
62
|
- [Author Components](https://weftui.dev/docs/how-to/author-components) — instance scope and `Effect.forkScoped`
|
|
63
63
|
- [examples/element-ref](https://github.com/stefvw93/weft/tree/main/examples/element-ref) — auto-focus, element measurement, and imperative scroll via refs
|
package/docs/reference/core.md
CHANGED
|
@@ -144,7 +144,7 @@ Boundary.suspend(
|
|
|
144
144
|
|
|
145
145
|
#### `Boundary.rpc`
|
|
146
146
|
|
|
147
|
-
A universal server/client render boundary backed by one `Rpc` from the app's merged `RpcGroup` ([
|
|
147
|
+
A universal server/client render boundary backed by one `Rpc` from the app's merged `RpcGroup` ([`effect/unstable/rpc`](https://github.com/Effect-TS/effect)). The rpc **`_tag`** is the boundary's stable identity and its **payload schema** the typed input; the handler lives in the server-only rpc Layer (`group.toLayer(...)`), which the client never imports — tree-shaking does the client/server split structurally. Unlike the catch variants it takes a `render` function — not a children array — and that `render` receives a reactive [`Resource`](#resourcea), not a bare value.
|
|
148
148
|
|
|
149
149
|
```typescript
|
|
150
150
|
Boundary.rpc<R extends Rpc.Any, C extends Node<any, any>>(
|
|
@@ -201,22 +201,21 @@ The descriptor `type` every `Boundary.rpc` carries (`{ type: SERVER_BOUNDARY, pr
|
|
|
201
201
|
interface AppRpcClient {
|
|
202
202
|
readonly call: (tag: string, payload: unknown) => Effect.Effect<unknown, unknown>;
|
|
203
203
|
}
|
|
204
|
-
class AppRpcClientTag extends Context.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
>() {}
|
|
204
|
+
class AppRpcClientTag extends Context.Service<AppRpcClientTag, AppRpcClient>()(
|
|
205
|
+
"@weftui/core/AppRpcClient",
|
|
206
|
+
) {}
|
|
208
207
|
```
|
|
209
208
|
|
|
210
|
-
The ambient, package-neutral seam the renderer resolves a `Boundary.rpc` through — a **flat, untyped** caller `(tag, payload) => Effect<success>`. It lets `@weftui/dom` resolve a boundary without importing
|
|
209
|
+
The ambient, package-neutral seam the renderer resolves a `Boundary.rpc` through — a **flat, untyped** caller `(tag, payload) => Effect<success>`. It lets `@weftui/dom` resolve a boundary without importing `effect/unstable/rpc` or `@weftui/router`. `@weftui/router` provides it: a **network** `RpcClient` (POST `/_eui/rpc`) in the browser, an **in-process** client over the handler Layer on the server. `call` returns the already-decoded success; the renderer owns `successSchema`/`errorSchema` decoding of the inline SSR payload only. Both `AppRpcClientTag` and the `AppRpcClient` type are re-exported from `@weftui/core`. Absent in a router-less mount, where a `Boundary.rpc` resolves to a descriptive "needs router/rpc" error (not a defect).
|
|
211
210
|
|
|
212
211
|
See the [rpc data boundaries guide](https://weftui.dev/docs/how-to/load-data-with-rpc) and [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr).
|
|
213
212
|
|
|
214
|
-
#### `Boundary.
|
|
213
|
+
#### `Boundary.catch`
|
|
215
214
|
|
|
216
|
-
Catches all typed failures (`Cause.fail`). Defects (`Cause.die`) are not caught and re-raise.
|
|
215
|
+
Catches all typed failures (`Cause.fail`). Defects (`Cause.die`) are not caught and re-raise. Mirrors Effect 4's `Effect.catch` (renamed from `catchAll` in v3).
|
|
217
216
|
|
|
218
217
|
```typescript
|
|
219
|
-
Boundary.
|
|
218
|
+
Boundary.catch<C, FE, FR>(
|
|
220
219
|
props: { fallback: (e: ChildrenE<C>) => Node<FE, FR> },
|
|
221
220
|
children: C,
|
|
222
221
|
): Node<FE, ChildrenR<C> | FR>
|
|
@@ -224,12 +223,12 @@ Boundary.catchAll<C, FE, FR>(
|
|
|
224
223
|
|
|
225
224
|
The children's `E` is fully consumed. The output `E` is only the fallback's own error channel.
|
|
226
225
|
|
|
227
|
-
#### `Boundary.
|
|
226
|
+
#### `Boundary.catchCause`
|
|
228
227
|
|
|
229
|
-
Catches every `Cause` including defects and interruptions.
|
|
228
|
+
Catches every `Cause` including defects and interruptions. Mirrors Effect 4's `Effect.catchCause` (renamed from `catchAllCause` in v3).
|
|
230
229
|
|
|
231
230
|
```typescript
|
|
232
|
-
Boundary.
|
|
231
|
+
Boundary.catchCause<C, FE, FR>(
|
|
233
232
|
props: { fallback: (cause: Cause.Cause<ChildrenE<C>>) => Node<FE, FR> },
|
|
234
233
|
children: C,
|
|
235
234
|
): Node<FE, ChildrenR<C> | FR>
|
|
@@ -266,18 +265,30 @@ Boundary.catchTags<C, Handlers>(
|
|
|
266
265
|
): Node<UnhandledE | HandlersE, ChildrenR<C> | HandlersR>
|
|
267
266
|
```
|
|
268
267
|
|
|
269
|
-
#### `Boundary.
|
|
268
|
+
#### `Boundary.catchFilter`
|
|
270
269
|
|
|
271
|
-
|
|
270
|
+
Conditionally catches using a `Filter`, run on each typed failure: a `Result.succeed` (pass) recovers via `fallback`, receiving the possibly-narrowed pass value; a `Result.fail` re-raises the error (its `Fail` channel `X` is preserved in the output `E`, since the boundary may not handle any given error). Takes the `Filter` and `fallback` as **positional** arguments — no wrapping props object. Mirrors Effect 4's `Effect.catchFilter` (renamed from `catchSome`, which took an `Option`-returning function in v3).
|
|
272
271
|
|
|
273
272
|
```typescript
|
|
274
|
-
Boundary.
|
|
275
|
-
|
|
273
|
+
Boundary.catchFilter<C, EB, X, FE, FR>(
|
|
274
|
+
filter: Filter.Filter<ChildrenE<C>, EB, X>,
|
|
275
|
+
fallback: (matched: EB) => Node<FE, FR>,
|
|
276
276
|
children: C,
|
|
277
|
-
): Node<
|
|
277
|
+
): Node<X | FE, ChildrenR<C> | FR>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { Boundary } from "@weftui/core";
|
|
282
|
+
import { Filter, Result } from "effect";
|
|
283
|
+
|
|
284
|
+
Boundary.catchFilter(
|
|
285
|
+
Filter.make((e: AppError) => (e._tag === "Net" ? Result.succeed(e) : Result.fail(e))),
|
|
286
|
+
(matched) => h.div({}, matched.message),
|
|
287
|
+
[Widget()],
|
|
288
|
+
);
|
|
278
289
|
```
|
|
279
290
|
|
|
280
|
-
The children's `E` is
|
|
291
|
+
The children's `E` is narrowed to the filter's `Fail` channel `X` in the output, because the boundary may or may not handle any given error.
|
|
281
292
|
|
|
282
293
|
#### `Boundary.catchIf`
|
|
283
294
|
|
|
@@ -301,7 +312,7 @@ Inner boundaries shadow outer ones for their subtree — the innermost boundary
|
|
|
301
312
|
|
|
302
313
|
```typescript
|
|
303
314
|
// Inner catches FooError; BarError propagates to outer
|
|
304
|
-
Boundary.
|
|
315
|
+
Boundary.catch({ fallback: (e) => h.div(`Outer: ${e.message}`) }, [
|
|
305
316
|
Boundary.catchTag({ tag: "Foo", fallback: (e) => h.span(`Foo: ${e.msg}`) }, [
|
|
306
317
|
ChildWithFooOrBarError(),
|
|
307
318
|
]),
|
|
@@ -312,7 +323,7 @@ Boundary.catchAll({ fallback: (e) => h.div(`Outer: ${e.message}`) }, [
|
|
|
312
323
|
|
|
313
324
|
## ServerTag
|
|
314
325
|
|
|
315
|
-
A `Context.
|
|
326
|
+
A `Context.Service` key whose identifier is branded server-only. Use it exactly like `Context.Service` for services that must only ever be provided on the server — e.g. a database handle read inside an rpc handler Layer. The brand also guards [`Boundary.rpc`](#boundaryrpc): a server-only tag accidentally referenced in `render` stays in the requirement channel, where `hydrate`'s `AssertNoServerOnly` rejects it at compile time.
|
|
316
327
|
|
|
317
328
|
```typescript
|
|
318
329
|
import { ServerTag } from "@weftui/core";
|
package/docs/reference/router.md
CHANGED
|
@@ -19,7 +19,7 @@ Three entry points mirror `@weftui/dom`:
|
|
|
19
19
|
|
|
20
20
|
## `Router`
|
|
21
21
|
|
|
22
|
-
`Router` is both an Effect `Context.
|
|
22
|
+
`Router` is both an Effect `Context.Service` key and the authoring namespace; the two roles merge by declaration. `yield* Router` reads the per-render service; `Router.route(…)` authors a tree.
|
|
23
23
|
|
|
24
24
|
The service value carries:
|
|
25
25
|
|
|
@@ -96,7 +96,7 @@ See the [Split Routes Lazily](https://weftui.dev/docs/how-to/split-routes-lazily
|
|
|
96
96
|
|
|
97
97
|
### `Router.Outlet`
|
|
98
98
|
|
|
99
|
-
A `Context.
|
|
99
|
+
A `Context.Service` key whose value is the node to splice for the next level down. A layout (or the server document shell) reads it with `const outlet = yield* Router.Outlet`. Typed **opaque** as `Node<never, never>`, and discharged by the router at render time, so it never appears in a reader's aggregate requirement channel.
|
|
100
100
|
|
|
101
101
|
### `Router.params` / `Router.query`
|
|
102
102
|
|
|
@@ -224,7 +224,7 @@ RouterLive(
|
|
|
224
224
|
): Layer.Layer<Router | AppRpcClientTag>;
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
-
The client `Router` layer, backed by the History API. Seeds a `SubscriptionRef` from `window.location`, listens for `popstate`, installs the same-origin link-click interceptor, and derives the `HttpApiClient` exposed as `Router.httpApiClient` (over `FetchHttpClient`; `baseUrl` defaults to same-origin). Alongside `Router` it also provides the core [`AppRpcClientTag`](https://weftui.dev/docs/reference/core#apprpcclienttag) seam — a **network** flat rpc client over the app's merged `RpcGroup` (`RpcClient.make` → `POST /_eui/rpc`) — so `@weftui/dom` can resolve a [`Boundary.rpc`](https://weftui.dev/docs/reference/core#boundaryrpc) (hydrated refetch and client-first SPA mount) without depending on this package or
|
|
227
|
+
The client `Router` layer, backed by the History API. Seeds a `SubscriptionRef` from `window.location`, listens for `popstate`, installs the same-origin link-click interceptor, and derives the `HttpApiClient` exposed as `Router.httpApiClient` (over `FetchHttpClient`; `baseUrl` defaults to same-origin). Alongside `Router` it also provides the core [`AppRpcClientTag`](https://weftui.dev/docs/reference/core#apprpcclienttag) seam — a **network** flat rpc client over the app's merged `RpcGroup` (`RpcClient.make` → `POST /_eui/rpc`) — so `@weftui/dom` can resolve a [`Boundary.rpc`](https://weftui.dev/docs/reference/core#boundaryrpc) (hydrated refetch and client-first SPA mount) without depending on this package or `effect/unstable/rpc`. Pass the same merged `group` the server wires into [`RouterServer`](#routerserver). **Scoped** — it must outlive the mount, so provide it through a `ManagedRuntime`:
|
|
228
228
|
|
|
229
229
|
```typescript
|
|
230
230
|
const runtime = ManagedRuntime.make(RouterLive(App, { rpc: { group: StockRpcs } }));
|
|
@@ -258,6 +258,10 @@ yield * push("/users/1/posts?sort=new");
|
|
|
258
258
|
yield * patchQuery({ sort: "old" }); // keeps the current path + other query fields
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
+
### Scroll reset
|
|
262
|
+
|
|
263
|
+
Every client-committed navigation whose path differs from the previously committed path — `navigate`, `push`, `replace`, and the link interceptor — resets `window.scrollTo(0, 0)` synchronously at commit. Query-only navigations (`setQuery` / `patchQuery`, or any push/replace to the same path) preserve scroll. `popstate` (`back` / `forward`) never resets; the browser's own `history.scrollRestoration: "auto"` restores the prior offset for those entries. Server rendering is unaffected. Not configurable — there is no `NavigateOptions` field to opt out.
|
|
264
|
+
|
|
261
265
|
### `installLinkInterceptor`
|
|
262
266
|
|
|
263
267
|
```typescript
|
|
@@ -290,11 +294,11 @@ Dispatch runs through the `def.httpApi` spine via `HttpApiBuilder`: platform own
|
|
|
290
294
|
|
|
291
295
|
### `RouterNotFound`
|
|
292
296
|
|
|
293
|
-
`Schema.
|
|
297
|
+
`Schema.TaggedErrorClass` with an optional `path: string`. Raised by [`notFound`](#notfound) or when no route matches. Caught by the router's internal not-found boundary; export it to place your own `Boundary.catchTag("RouterNotFound", …)` (a nearer user boundary wins).
|
|
294
298
|
|
|
295
299
|
### `RouterParamsError`
|
|
296
300
|
|
|
297
|
-
`Schema.
|
|
301
|
+
`Schema.TaggedErrorClass` with `source: "path" | "query"` and `keys: readonly string[]`. Raised by `Router.params` / `Router.query` when the live match doesn't satisfy the requested fields. Bubbles into the tree's aggregate error channel.
|
|
298
302
|
|
|
299
303
|
### `notFound`
|
|
300
304
|
|
|
@@ -11,7 +11,7 @@ description: Add component-local state with SubscriptionRef and weave its stream
|
|
|
11
11
|
|
|
12
12
|
## Local state with `SubscriptionRef`
|
|
13
13
|
|
|
14
|
-
Use Effect's `SubscriptionRef` for component-local state.
|
|
14
|
+
Use Effect's `SubscriptionRef` for component-local state. `SubscriptionRef.changes(ref)` returns a `Stream` that emits the current value and then every update. Pass that stream as a child or prop and the DOM at that spot becomes live:
|
|
15
15
|
|
|
16
16
|
```typescript
|
|
17
17
|
import { h } from "@weftui/core";
|
|
@@ -23,7 +23,7 @@ const Counter = () =>
|
|
|
23
23
|
const count = yield* SubscriptionRef.make(0);
|
|
24
24
|
|
|
25
25
|
return yield* h.div([
|
|
26
|
-
h.span([
|
|
26
|
+
h.span([SubscriptionRef.changes(count)]),
|
|
27
27
|
h.button({ onclick: () => SubscriptionRef.update(count, (n) => n + 1) }, "+"),
|
|
28
28
|
h.button({ onclick: () => SubscriptionRef.update(count, (n) => n - 1) }, "-"),
|
|
29
29
|
]);
|
|
@@ -36,18 +36,18 @@ void Effect.runPromise(mount(Counter(), document.getElementById("root")!));
|
|
|
36
36
|
|
|
37
37
|
## The key idea: the body runs once
|
|
38
38
|
|
|
39
|
-
The `Counter` function runs **exactly once**. It creates the ref, builds the tree, and returns. After that, nothing re-invokes it — the only thing that changes the DOM is the `
|
|
39
|
+
The `Counter` function runs **exactly once**. It creates the ref, builds the tree, and returns. After that, nothing re-invokes it — the only thing that changes the DOM is the `SubscriptionRef.changes(count)` stream woven into the `h.span`. When you click `+`, `SubscriptionRef.update` pushes a new value, the stream emits, and the renderer patches _just that span's text_ in place. No diff, no re-render, no sibling touched.
|
|
40
40
|
|
|
41
41
|
This is what "streams are the weft" means in practice: reactivity is local to exactly where you thread a stream. Everything else is static. The full model is [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model); the vocabulary of stream-shaped values is [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives).
|
|
42
42
|
|
|
43
|
-
> **Note.** `[
|
|
43
|
+
> **Note.** `[SubscriptionRef.changes(count)]` — the stream is passed as a child array. Static values (`"Hello"`, `5`) work in the same position and simply never change. The rule is uniform: a plain value is static, a stream-shaped value is reactive.
|
|
44
44
|
|
|
45
45
|
## Deriving values
|
|
46
46
|
|
|
47
|
-
Because
|
|
47
|
+
Because `SubscriptionRef.changes(count)` returns a `Stream`, you shape reactive text with ordinary stream operators:
|
|
48
48
|
|
|
49
49
|
```typescript
|
|
50
|
-
h.span([Stream.map(
|
|
50
|
+
h.span([Stream.map(SubscriptionRef.changes(count), (n) => `Count: ${n}`)]);
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Anywhere you would compute a derived value, map the stream instead — the derivation stays reactive.
|
|
@@ -18,10 +18,9 @@ import { h } from "@weftui/core";
|
|
|
18
18
|
import { mount } from "@weftui/dom/client";
|
|
19
19
|
import { Context, Effect, Layer, pipe } from "effect";
|
|
20
20
|
|
|
21
|
-
class Logger extends Context.
|
|
22
|
-
Logger,
|
|
23
|
-
|
|
24
|
-
>() {}
|
|
21
|
+
class Logger extends Context.Service<Logger, { log: (message: string) => Effect.Effect<void> }>()(
|
|
22
|
+
"Logger",
|
|
23
|
+
) {}
|
|
25
24
|
|
|
26
25
|
const LoggerLive = Layer.succeed(Logger, {
|
|
27
26
|
log: (message) => Effect.sync(() => console.log(message)),
|
|
@@ -20,12 +20,12 @@ import { Data, Effect } from "effect";
|
|
|
20
20
|
class ApiError extends Data.TaggedError("ApiError")<{ status: number }> {}
|
|
21
21
|
|
|
22
22
|
const SafeWidget = () =>
|
|
23
|
-
Boundary.
|
|
23
|
+
Boundary.catch({ fallback: (e) => h.div({ class: "error" }, `Request failed: ${e.status}`) }, [
|
|
24
24
|
Effect.fail(new ApiError({ status: 503 })),
|
|
25
25
|
]);
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
There are six failure-catch variants — `
|
|
28
|
+
There are six failure-catch variants — `catch`, `catchCause`, `catchTag`, `catchTags`, `catchFilter`, `catchIf` — mirroring Effect's own error operators. A failure that a boundary does not match re-raises to the **nearest enclosing** boundary; if none catches it, the mount fails. The conceptual model (and why the boundary's type reflects exactly which failures are handled) is [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense).
|
|
29
29
|
|
|
30
30
|
## Render on the server
|
|
31
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weftui/dom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "DOM renderer for Weft — mount and hydrate in the browser, render to string or stream on the server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dom",
|
|
@@ -44,13 +44,12 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@weftui/core": "0.
|
|
47
|
+
"@weftui/core": "0.27.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@effect/rpc": "^0.75.1",
|
|
51
50
|
"@types/jsdom": "^28.0.3",
|
|
52
51
|
"@types/node": "^25.9.2",
|
|
53
|
-
"effect": "
|
|
52
|
+
"effect": "4.0.0-beta.93",
|
|
54
53
|
"jsdom": "^29.1.1",
|
|
55
54
|
"tsx": "^4.22.4",
|
|
56
55
|
"typescript": "^6.0.3",
|
|
@@ -58,6 +57,6 @@
|
|
|
58
57
|
"vite-plus": "0.2.2"
|
|
59
58
|
},
|
|
60
59
|
"peerDependencies": {
|
|
61
|
-
"effect": "
|
|
60
|
+
"effect": "4.0.0-beta.93"
|
|
62
61
|
}
|
|
63
62
|
}
|