@weftui/dom 0.26.0 → 0.26.2
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 +66 -0
- package/dist/client/index.js +1 -1
- package/docs/explanation/boundaries-and-suspense.md +91 -0
- package/docs/explanation/combinator-api.md +164 -0
- package/docs/explanation/reactive-primitives.md +162 -0
- package/docs/explanation/rendering-model.md +65 -0
- package/docs/explanation/services-and-context.md +91 -0
- package/docs/how-to/add-routing.md +296 -0
- package/docs/how-to/author-components.md +264 -0
- package/docs/how-to/handle-forms.md +76 -0
- package/docs/how-to/load-async-data.md +70 -0
- package/docs/how-to/load-data-with-rpc.md +172 -0
- package/docs/how-to/provide-services.md +124 -0
- package/docs/how-to/render-keyed-lists.md +51 -0
- package/docs/how-to/render-on-the-server.md +86 -0
- package/docs/how-to/show-navigation-progress.md +65 -0
- package/docs/how-to/split-routes-lazily.md +62 -0
- package/docs/how-to/style-reactively.md +63 -0
- package/docs/how-to/use-element-refs.md +63 -0
- package/docs/index.md +59 -0
- package/docs/reference/core.md +496 -0
- package/docs/reference/dom.md +142 -0
- package/docs/reference/router.md +348 -0
- package/docs/tutorial/01-your-first-app.md +48 -0
- package/docs/tutorial/02-reactivity.md +57 -0
- package/docs/tutorial/03-services-and-async.md +77 -0
- package/docs/tutorial/04-errors-and-server.md +61 -0
- package/package.json +19 -5
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @weftui/dom
|
|
2
|
+
|
|
3
|
+
> The DOM renderer for [Weft](https://weftui.dev) — mount and hydrate in the browser, render to string or stream on the server.
|
|
4
|
+
|
|
5
|
+
Takes a [`@weftui/core`](https://weftui.dev/docs/reference/core) node tree and renders it to real DOM on the client or to HTML on the server. There is no virtual DOM and no diffing — streams patch the tree in place. The same tree renders to HTML with `renderToStringHydratable` and `hydrate()`s flash-free on the client.
|
|
6
|
+
|
|
7
|
+
Two entry points: `@weftui/dom/client` for the browser, `@weftui/dom/server` for Node.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @weftui/core @weftui/dom effect
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`effect` is a peer dependency; `@weftui/core` is required to author the tree.
|
|
16
|
+
|
|
17
|
+
## Key exports
|
|
18
|
+
|
|
19
|
+
### `@weftui/dom/client`
|
|
20
|
+
|
|
21
|
+
| Export | What it does |
|
|
22
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
23
|
+
| `mount(node, target)` | Renders a node into `target` for a fresh (non-SSR) page and starts all streams. |
|
|
24
|
+
| `hydrate(node, target)` | Adopts server-rendered DOM **in place** and resumes reactivity — the flash-free path. |
|
|
25
|
+
| `mountScoped` / `hydrateScoped` | Scope-aware variants that register teardown as a finalizer on an ambient `Scope`. |
|
|
26
|
+
| `MountHandle` | Handle returned by mount/hydrate; `unmount()` tears the reactive tree down (idempotent). |
|
|
27
|
+
|
|
28
|
+
### `@weftui/dom/server`
|
|
29
|
+
|
|
30
|
+
| Export | What it does |
|
|
31
|
+
| -------------------------------- | -------------------------------------------------------------------------------- |
|
|
32
|
+
| `renderToString` | Renders a node to a complete HTML string (static, non-hydrated). |
|
|
33
|
+
| `renderToStringHydratable` | Same, plus the hydration markers `hydrate` needs. Pair with `hydrate`. |
|
|
34
|
+
| `renderToStream` / `…Hydratable` | Streaming variants — emit HTML chunks as the tree resolves, for progressive SSR. |
|
|
35
|
+
| `renderToHydratableShell` | Produces the document scaffold for servers that assemble the shell separately. |
|
|
36
|
+
|
|
37
|
+
The package root re-exports the renderer error types: `HydrationMismatchError`, `UnsupportedNodeTypeError`, `RenderError`, `StreamSubscriptionError`.
|
|
38
|
+
|
|
39
|
+
## Example
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { h } from "@weftui/core";
|
|
43
|
+
import { mount } from "@weftui/dom/client";
|
|
44
|
+
import { Effect, SubscriptionRef } from "effect";
|
|
45
|
+
|
|
46
|
+
const Counter = () =>
|
|
47
|
+
Effect.gen(function* () {
|
|
48
|
+
const count = yield* SubscriptionRef.make(0);
|
|
49
|
+
return yield* h.button({ onclick: () => SubscriptionRef.update(count, (n) => n + 1) }, [
|
|
50
|
+
count.changes,
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
void Effect.runPromise(mount(Counter(), document.getElementById("root")!));
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Documentation
|
|
58
|
+
|
|
59
|
+
- Full docs: **https://weftui.dev**
|
|
60
|
+
- `@weftui/dom` API reference: **https://weftui.dev/docs/reference/dom**
|
|
61
|
+
- Server-side rendering guide: **https://weftui.dev/docs/how-to/render-on-the-server**
|
|
62
|
+
- Bundled with this package: see the [`./docs`](./docs) directory in `node_modules/@weftui/dom/docs` — the complete tutorial, how-to, explanation, and reference tree ships on disk for offline and agent use.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT © Stef van Wijchen
|
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 w,HashMap as T,HashSet as E,Layer as ee,ManagedRuntime as te,Option as D,Ref as O,Schema as ne,Scope as k,Stream as A,SubscriptionRef as j,pipe as M}from"effect";import{AppRpcClientTag as re,FAILURE_BOUNDARY as ie,FRAGMENT as ae,LIST as oe,SERVER_BOUNDARY as se,SUSPENSE_BOUNDARY as ce,Source as le,getElementDescriptor as ue,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 I=()=>++fe;function pe(e){if(e.length<=2||!e.startsWith(`on`))return!1;let t=e[2];return t!==void 0&&t>=`a`&&t<=`z`}function L(e,t){return x.gen(function*(){for(let[n,r]of Object.entries(t))if(n!==`children`){if(pe(n)){yield*be(e,n,r);continue}if(n===`ref`&&typeof r==`object`&&O.RefTypeId in r){yield*O.set(r,D.some(e));continue}if(n===`style`){yield*ve(e,r);continue}me(e,n)?yield*he(e,n,r):yield*ge(e,n,r)}})}function me(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 he(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 ge(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=_e(n);r!==void 0&&(typeof n==`boolean`?n?e.setAttribute(t,``):e.removeAttribute(t):e.setAttribute(t,r))}},`attribute:${t}`);else{let r=_e(n);r!==void 0&&(typeof n==`boolean`?n?e.setAttribute(t,``):e.removeAttribute(t):e.setAttribute(t,r))}})}function _e(e){if(e!=null)return String(e)}function ve(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(R(n),String(r))}},`style`);return}if(typeof t==`string`){e.setAttribute(`style`,t);return}typeof t==`object`&&t&&(yield*ye(e,t))})}function ye(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(R(n),String(t))},`style.${n}`):r!=null&&e.style.setProperty(R(n),String(r))})}function R(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function z(e,t,n){return x.gen(function*(){let n=yield*r,i=yield*x.serviceOption(a),o=A.runForEach(e,e=>x.sync(()=>void t(e))),s=yield*x.forkIn(o,n.scope);yield*M(w.await(s),x.flatMap(e=>C.isFailure(e)&&D.isSome(i)?i.value.reportError(e.cause):x.void),x.forkIn(n.scope))})}function be(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(M(r,x.catchAll(e=>process.env.NODE_ENV===`development`?x.logError(`Event handler error: ${t}`,{error:e}):x.void)))},e.addEventListener(a,o))};yield*k.addFinalizer(i.scope,x.sync(s)),N(n)||x.isEffect(n)?yield*z(P(n),e=>c(e),`event:${t}`):c(n)})}function xe(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*k.close(n,C.void),s===null)return D.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 Se(e){return x.gen(function*(){let t=yield*r,n=yield*x.serviceOption(a),i=I(),s=document.createComment(o(i)),c=document.createComment(u(i)),l=yield*k.fork(t.scope,S.sequential),d={...t,scope:l},f=yield*b.make(),p=yield*M(V(e.children),x.provideService(a,{reportError:e=>b.fail(f,e).pipe(x.asVoid)}),x.provideService(r,d),x.provideService(k.Scope,l),x.catchAllCause(t=>{let n=e.match(t);return n===null?x.failCause(t):M(k.close(l,C.void),x.flatMap(()=>B(n)),x.map(e=>e===null?[]:Array.isArray(e)?e:[e]))}));return yield*x.forkIn(xe(e,f,l,n,s,c),t.scope),[s,...p,c]})}function Ce(e){return x.gen(function*(){let t=yield*r,i=yield*O.make(1),a=yield*b.make(),o=M(O.updateAndGet(i,e=>e-1),x.flatMap(e=>e<=0?x.asVoid(b.succeed(a,void 0)):x.void)),s={register:O.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)||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(D.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 we(t){return x.gen(function*(){let n=yield*r,i=yield*x.serviceOption(re);if(D.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=I(),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*Xe(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(N(e)||x.isEffect(e)){let t=ue(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(P(e))}if(typeof e==`object`&&Symbol.iterator in e&&!(`type`in e))return yield*V(Te(e));if(typeof e==`object`&&`type`in e&&!(Symbol.iterator in e)){let{type:t,props:n}=e;return t===ae?yield*Ee(n):t===ce?yield*Ce(n):t===se?yield*we(n):t===ie?yield*Se(n):t===oe?yield*Ne(n):typeof t==`string`?yield*De(t,n):typeof t==`function`?yield*Oe(t,n):yield*x.fail(new i({type:t,message:`Invalid Renderable type: expected string, FRAGMENT, or function, got ${typeof t}`}))}return null})}function Te(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(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 Ee(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 De(e,t){return x.gen(function*(){let n=document.createElement(e);yield*L(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(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 Oe(e,t){return x.gen(function*(){let i=e(t);if(N(i)||x.isEffect(i)){let e=yield*r,t=yield*k.fork(e.scope,S.sequential),a={...e,scope:t},o=yield*x.serviceOption(n),s=P(i);return D.isSome(o)&&(yield*o.value.register,s=M(s,A.zipWithIndex,A.flatMap(([e,t])=>t===0?A.fromEffect(x.as(o.value.settle,e)):A.make(e)))),yield*H(s).pipe(x.provideService(r,a),x.provideService(k.Scope,t))}return yield*B(i)})}function H(e){return x.gen(function*(){let t=yield*r,[n,i]=ke(yield*F()),o=null,s=A.runForEach(e,e=>x.gen(function*(){o!==null&&(yield*k.close(o,C.void)),o=yield*k.fork(t.scope,S.sequential);let a={...t,scope:o};yield*U(n,i,e).pipe(x.provideService(r,a),x.provideService(k.Scope,o))})),c=yield*x.forkIn(s,t.scope),l=yield*x.serviceOption(a);return yield*M(w.await(c),x.flatMap(e=>C.isFailure(e)&&D.isSome(l)?l.value.reportError(e.cause):x.void),x.forkIn(t.scope)),[n,i]})}function ke(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===rt&&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*Ae(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=ue(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 Ae(e,t){return x.gen(function*(){yield*L(e,t.props);let n=t.props.children,r=n===void 0?[]:Array.isArray(n)?n:[n];if(!(yield*je(e,r))){for(;e.firstChild!==null;)e.firstChild.remove();yield*Me(e,r)}})}function je(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!==rt)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*Ae(i,G(r))}return!0})}function Me(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 Ne(e){return x.gen(function*(){let t=yield*r,{of:n,by:i,render:o}=e,[s,c]=ke(yield*F()),l=yield*k.fork(t.scope,S.sequential),u=(yield*le.toSubscribable(n).pipe(x.provideService(k.Scope,l))).changes,d={records:T.empty(),order:[]},f=A.runForEach(u,e=>x.gen(function*(){d=yield*q(Array.from(e),i,o,d,l,c,t)})),p=yield*x.forkIn(f,l),m=yield*x.serviceOption(a);return yield*M(w.await(p),x.flatMap(e=>C.isFailure(e)&&D.isSome(m)?m.value.reportError(e.cause):x.void),x.forkIn(t.scope)),[s,c]})}function Pe(t,n){return x.gen(function*(){let r=[],i=E.empty();for(let a=0;a<t.length;a++){let o=n===void 0?t[a]:n(t[a],a);if(E.has(i,o))return yield*x.fail(new e({cause:o,message:`List.each: duplicate key ${Re(o)} in a single emission; keys must be unique (set a stable \`by\`).`}));i=E.add(i,o),r.push(o)}return r})}function q(e,t,n,r,i,a,o){return x.gen(function*(){let s=yield*Pe(e,t),c=T.empty();r.order.forEach((e,t)=>{c=T.set(c,e,t)});let l=[],u=[];for(let t=0;t<e.length;t++){let a=s[t],d=T.get(r.records,a);if(D.isSome(d))l.push(d.value),u.push(D.getOrElse(T.get(c,a),()=>-1));else{let r=yield*Fe(a,e[t],t,n,i,o);l.push(r),u.push(-1)}}let d=E.empty();for(let e of s)d=E.add(d,e);for(let e of r.order)if(!E.has(d,e)){let t=T.get(r.records,e);D.isSome(t)&&(yield*k.close(t.value.scope,C.void),Le(t.value.startMarker,t.value.endMarker))}let f=ze(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]:Ie(t.startMarker,t.endMarker);for(let e of r)p.insertBefore(e,n)}let m=T.empty();for(let e of l)m=T.set(m,e.key,e);return{records:m,order:s}})}function Fe(e,t,n,i,a,o){return x.gen(function*(){let s=yield*k.fork(a,S.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(k.Scope,s));return{key:e,scope:s,startMarker:u,endMarker:d,nodes:f===null?[]:Array.isArray(f)?f:[f]}})}function Ie(e,t){let n=[],r=e;for(;r!==null&&(n.push(r),r!==t);)r=r.nextSibling;return n}function Le(e,t){let n=e;for(;n!==null;){let e=n.nextSibling;if(n.remove(),n===t)break;n=e}}function Re(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 ze(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 Be(e,t){return x.gen(function*(){let n=yield*x.context(),i=yield*x.serviceOption(k.Scope),a=yield*k.make(),o=te.make(ee.succeedContext(y.add(n,k.Scope,a))),s={runtime:o,scope:a,streamIdCounter:{current:0}},c=x.zipRight(k.close(a,C.void),x.promise(()=>o.dispose()));t.innerHTML=``;let l=yield*B(e).pipe(x.provideService(r,s),x.provideService(k.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*k.close(a,C.void),yield*x.promise(()=>o.dispose()))})};return D.isSome(i)&&(yield*k.addFinalizer(i.value,d.unmount())),d})}function Ve(e,t){return x.gen(function*(){let n=yield*x.context(),i=yield*x.serviceOption(k.Scope),a=yield*k.make(),o=te.make(ee.succeedContext(y.add(n,k.Scope,a))),s=yield*Ue(),c={runtime:o,scope:a,streamIdCounter:{current:0},hydrationReady:s};it(t,c.streamIdCounter);let l=x.zipRight(k.close(a,C.void),x.promise(()=>o.dispose()));yield*J(e,t.firstChild,`root`).pipe(x.provideService(r,c),x.provideService(k.Scope,a),x.tapError(()=>l)),yield*s.settle,yield*s.awaitReady;let u=!1,d={unmount:()=>x.gen(function*(){u||(u=!0,yield*k.close(a,C.void),yield*x.promise(()=>o.dispose()))})};return D.isSome(i)&&(yield*k.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*He(String(e),t,n);if(typeof e==`boolean`||e==null)return t;if(N(e)||x.isEffect(e)){let r=ue(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*Ge(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===ae)return yield*Y(a,t,n);if(r===ce){if(t!==null&&t.nodeType===Z){let e=_(t);if(e!==null&&e.kind===`start`)return yield*qe(t,n)}return yield*Y(a,t,n)}return r===ie?yield*Ye(a,t,n):r===se?yield*Ze(a,t,n):r===oe?yield*$e(a,t,n):typeof r==`string`?yield*Qe(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 He(e,t,n){return x.gen(function*(){if(e.length===0)return t;if(t===null||t.nodeType!==rt)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 Ue(){return x.gen(function*(){let e=yield*O.make(1),t=yield*b.make(),n=M(O.updateAndGet(e,e=>e-1),x.flatMap(e=>e<=0?x.asVoid(b.succeed(t,void 0)):x.void));return{register:O.update(e,e=>e+1),settle:n,awaitReady:b.await(t)}})}function We(e){let t=!1;return x.suspend(()=>t||e===void 0?x.void:(t=!0,e.settle))}function Ge(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 o=t,s=d(o);if(s===null||s.kind!==`start`)return yield*$(`reactive region start marker`,Q(t),n);let c=at(o);if(c===null)return yield*$(`reactive region end marker`,`unterminated region starting at ${JSON.stringify(o.data)}`,n);let l=We(i.hydrationReady),u=!0,f=null,p=A.runForEach(e,e=>x.gen(function*(){f!==null&&(yield*k.close(f,C.void)),f=yield*k.fork(i.scope,S.sequential);let t={...i,scope:f};yield*x.gen(function*(){u?(u=!1,yield*Ke(e,o,c,n),yield*l):yield*U(o,c,e)}).pipe(x.provideService(r,t),x.provideService(k.Scope,f))})).pipe(x.ensuring(l));i.hydrationReady!==void 0&&(yield*i.hydrationReady.register);let m=yield*x.forkIn(p,i.scope),h=yield*x.serviceOption(a);return yield*M(w.await(m),x.flatMap(e=>C.isFailure(e)&&D.isSome(h)?h.value.reportError(e.cause):x.void),x.forkIn(i.scope)),c.nextSibling})}function Ke(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 qe(e,t){return x.gen(function*(){let n=Je(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 D.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 Je(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 Ye(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*k.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(k.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=I(),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(xe(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*ne.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 Xe(e,t,n,r){return x.gen(function*(){let i=yield*j.make(n),a=yield*j.make(!1),o=yield*j.make(D.none());return{value:i,refetch:D.match(r,{onNone:()=>x.void,onSome:n=>x.gen(function*(){(yield*j.get(a))||(yield*j.set(a,!0),yield*x.gen(function*(){let r=yield*x.exit(n.call(e,t()));C.isSuccess(r)?(yield*j.set(i,r.value),yield*j.set(o,D.none())):yield*j.set(o,D.some(v.squash(r.cause)))}).pipe(x.ensuring(j.set(a,!1))))})}),pending:a,error:o}})}function Ze(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=>ne.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(re),s=yield*Xe(e.tag,e.payload,a,o),c=yield*J(e.render(s),r.nextSibling,n);return r.remove(),c})}function Qe(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*L(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 $e(e,t,n){return x.gen(function*(){let i=yield*r,{of:o,by:s,render:c}=e;if(t===null||t.nodeType!==Z)return yield*$(`list region start marker`,Q(t),n);let l=t,u=d(l);if(u===null||u.kind!==`start`)return yield*$(`list region start marker`,Q(t),n);let f=at(l);if(f===null)return yield*$(`list region end marker`,`unterminated region starting at ${JSON.stringify(l.data)}`,n);let p=et(l,f),m=yield*k.fork(i.scope,S.sequential),h=(yield*le.toSubscribable(o).pipe(x.provideService(k.Scope,m))).changes,g={records:T.empty(),order:[]},_=!0,v=We(i.hydrationReady),y=A.runForEach(h,e=>x.gen(function*(){let t=Array.from(e);_?(_=!1,g=yield*tt(t,s,c,p,m,l,f,i,n),yield*v):g=yield*q(t,s,c,g,m,f,i)})).pipe(x.ensuring(v));i.hydrationReady!==void 0&&(yield*i.hydrationReady.register);let b=yield*x.forkIn(y,m),E=yield*x.serviceOption(a);return yield*M(w.await(b),x.flatMap(e=>C.isFailure(e)&&D.isSome(E)?E.value.reportError(e.cause):x.void),x.forkIn(i.scope)),f.nextSibling})}function et(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 tt(e,t,n,r,i,a,o,s,c){return x.gen(function*(){let l=yield*Pe(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:T.empty(),order:[]},i,o,s);let u=[];for(let t=0;t<e.length;t++){let a=yield*nt(l[t],e[t],t,n,r[t],i,s,c);u.push(a)}let d=T.empty();for(let e of u)d=T.set(d,e.key,e);return{records:d,order:l}})}function nt(e,t,n,i,a,o,s,c){return x.gen(function*(){let l=yield*k.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(k.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*k.close(l,C.void);let p=yield*k.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(k.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,rt=3,Z=8;function it(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 at(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 ot(e,t){return M(Be(e,t),x.tap(e=>x.addFinalizer(()=>e.unmount())))}function st(e,t){return M(Ve(e,t),x.tap(e=>x.addFinalizer(()=>e.unmount())))}export{Ve as hydrate,st as hydrateScoped,Be as mount,ot as mountScoped};
|
|
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};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Boundaries and Suspense
|
|
3
|
+
order: 4
|
|
4
|
+
section: explanation
|
|
5
|
+
description: How Weft models failure, async, and server data as boundary nodes in the same tree — failure-catch variants, Boundary.suspend, and Boundary.rpc, and how their E/R channels behave.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Boundaries and Suspense
|
|
9
|
+
|
|
10
|
+
A **boundary** is a node that intercepts something flowing through the tree — an error, a pending async child, or a server-resolved value — and decides what the DOM shows in its place. Because a boundary is itself a `Node<E, R>` ([nodes are Effects](https://weftui.dev/docs/explanation/rendering-model)), it composes exactly like any other element: you nest it, and its children's channels flow through it under a transformation the boundary defines.
|
|
11
|
+
|
|
12
|
+
The `Boundary` namespace has three kinds. This page is the conceptual map; the [core reference](https://weftui.dev/docs/reference/core#boundary-namespace) has the full signatures.
|
|
13
|
+
|
|
14
|
+
## Failure boundaries
|
|
15
|
+
|
|
16
|
+
A component's `E` channel accumulates up the tree. A **failure boundary** is where you _discharge_ some of that `E`: it wraps children and, if one of them fails, renders a fallback instead of letting the failure propagate to the mount.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { Boundary, h } from "@weftui/core";
|
|
20
|
+
|
|
21
|
+
Boundary.catchAll({ fallback: (e) => h.div({ class: "error" }, `Failed: ${e.message}`) }, [
|
|
22
|
+
RiskyWidget(),
|
|
23
|
+
]);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
There are six failure-catch variants, mirroring Effect's own error operators so the mental model transfers directly:
|
|
27
|
+
|
|
28
|
+
| Variant | Catches |
|
|
29
|
+
| ------------------------ | ------------------------------------------ |
|
|
30
|
+
| `catchAll` | every failure in `E` |
|
|
31
|
+
| `catchAllCause` | the full `Cause` (defects included) |
|
|
32
|
+
| `catchTag` / `catchTags` | one / several tagged errors by `_tag` |
|
|
33
|
+
| `catchSome` / `catchIf` | a selected subset, by `Option` / predicate |
|
|
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 `catchAll` sweeps the rest.
|
|
36
|
+
|
|
37
|
+
### Post-mount failures with no enclosing boundary
|
|
38
|
+
|
|
39
|
+
The routing above describes what happens while a node is being built. Once mounted, a reactive region — an attribute, child, or list stream, or a hydrated equivalent — keeps running for the lifetime of its scope, and it can still fail later: a `Stream` backing a `Boundary.rpc` resource might raise `RouterNotFound` after a client-side navigation, for instance. If a `BoundaryContext` encloses the region, the failure routes to it exactly as above, and the boundary's fallback swaps in.
|
|
40
|
+
|
|
41
|
+
If no boundary encloses it, there is nothing to swap to. Weft does not synthesize one: the region's DOM keeps its last rendered content, and the subscription fiber's failure exit is left **unobserved**. The Effect runtime itself then reports it — `"Fiber terminated with an unhandled error"` — because Weft raises that fiber's `FiberRef.unhandledErrorLogLevel` from the ambient default (`Debug`) to `LogLevel.Error` and annotates the log with `weft.region`, identifying the failing region by kind and identity (e.g. `attribute:class`, `child:stream-3`, `list:stream-2`, `hydrate:stream-1 (/products/42)`). This fires for typed failures and defects alike, in both dev and prod, exactly once per failing region. Interruption — the ordinary case of unmount tearing down the region's scope — is never reported; only genuine failures are.
|
|
42
|
+
|
|
43
|
+
This is deliberate: rather than a Weft-specific error-reporting config, visibility is controlled by the same knobs any Effect program uses — `Logger.withMinimumLogLevel` to filter it, `Effect.withUnhandledErrorLogLevel` to change how loudly (or quietly) unhandled fiber exits are reported elsewhere in your program. A stream that can fail and has no enclosing boundary is a stream whose failures you've chosen not to route into the UI — the log is what tells you that decision has consequences at runtime.
|
|
44
|
+
|
|
45
|
+
## Suspense boundaries
|
|
46
|
+
|
|
47
|
+
`Boundary.suspend` wraps async children and shows a `fallback` until **all** of them have emitted their first value, then swaps atomically — either everything is visible or nothing is. This prevents partial flicker when sibling async regions resolve at different times.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { Boundary, h } from "@weftui/core";
|
|
51
|
+
|
|
52
|
+
Boundary.suspend({ fallback: h.div({ class: "spinner" }, "Loading…") }, [
|
|
53
|
+
AsyncCard({ id: 1 }),
|
|
54
|
+
AsyncCard({ id: 2 }),
|
|
55
|
+
]);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A suspense boundary is transparent to the type channels: its node is `Node<ChildrenE, ChildrenR>` — the children's `E`/`R` pass straight through, exactly as they would for a plain `h.*` parent. It changes _timing_ (when the children become visible), not _types_.
|
|
59
|
+
|
|
60
|
+
On the server, `renderToStreamHydratable` emits the fallback inline and appends patch scripts as children resolve; on the client, `hydrate` sees through the boundary and adopts the already-resolved DOM directly.
|
|
61
|
+
|
|
62
|
+
> **Note.** There is no `Suspense` export — the API is `Boundary.suspend(props, children)`. Reach for it for async that loads **on the client**; for data that must resolve on the **server** and hydrate without a second request, use `Boundary.rpc` (below).
|
|
63
|
+
|
|
64
|
+
## The rpc boundary
|
|
65
|
+
|
|
66
|
+
`Boundary.rpc` is the server-data boundary: it resolves one `Rpc` on the server, serializes the result into the HTML, replays it on the client during `hydrate` (no second request, no flash), and then keeps the region live for `refetch`. Conceptually it is the same idea as the other boundaries — a node that decides what renders in a subtree — but the thing it intercepts is a **round-trip to a server handler**, and instead of a children array it takes a `render` function that receives a reactive [`Resource`](https://weftui.dev/docs/reference/core#resourcea).
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { Boundary, h } from "@weftui/core";
|
|
70
|
+
import { Stream } from "effect";
|
|
71
|
+
|
|
72
|
+
Boundary.rpc(
|
|
73
|
+
GetStock,
|
|
74
|
+
() => ({ id: productId }),
|
|
75
|
+
(resource) => h.span([Stream.map(resource.value.changes, (s) => String(s.units))]),
|
|
76
|
+
{ fallback: h.p("loading…") },
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Unlike the failure and suspense boundaries, `Boundary.rpc` is not self-contained: it resolves through the ambient [`AppRpcClientTag`](https://weftui.dev/docs/reference/core#apprpcclienttag) seam that `@weftui/router` provides on both sides. Its channel behavior is also distinct — the rpc's typed `error` schema joins the node's `E` (replayable through an enclosing failure boundary), while `render`'s `R` passes through untouched. The full model — the contract/handler split, the four lifecycles, typed-failure replay — is a **how-to**, not repeated here: [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc).
|
|
81
|
+
|
|
82
|
+
## One tree, three interceptors
|
|
83
|
+
|
|
84
|
+
The unifying idea: failure, async pending state, and server data are not three separate subsystems bolted onto the renderer. They are three **boundary nodes** in the one tree, each intercepting a different thing flowing through it, each with channel behavior you can read off its type. That is why they nest freely — a `Boundary.catchTag` can wrap a `Boundary.rpc` to catch its typed failure, and a `Boundary.suspend` can wrap async siblings that themselves contain rpc boundaries.
|
|
85
|
+
|
|
86
|
+
## See also
|
|
87
|
+
|
|
88
|
+
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model) — why a boundary is just a node in a static tree
|
|
89
|
+
- [`Boundary` API reference](https://weftui.dev/docs/reference/core#boundary-namespace) — every variant's signature and channel algebra
|
|
90
|
+
- [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc) — the full `Boundary.rpc` walkthrough and its four lifecycles
|
|
91
|
+
- [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server) — how suspense and rpc boundaries stream and hydrate
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: The Combinator API
|
|
3
|
+
order: 2
|
|
4
|
+
section: explanation
|
|
5
|
+
description: How h, h.fragment, and Component.gen / Component.make work; why Node is an Effect; how E and R accumulate through a tree.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# The Combinator API
|
|
9
|
+
|
|
10
|
+
Weft builds UI trees by calling builder functions. Because component return types stay as generic `Effect.Effect<ElementDescriptor, E, R>`, the error channel (`E`) and requirements channel (`R`) propagate through the entire tree — visible to the type checker, satisfiable at the mount boundary. JSX collapses every component's return type to an opaque `JSX.Element`, erasing both channels; the combinator API exists specifically to keep them intact.
|
|
11
|
+
|
|
12
|
+
## Nodes are Effects
|
|
13
|
+
|
|
14
|
+
`Node<E, R>` is defined as:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
type Node<E = never, R = never> = Effect.Effect<ElementDescriptor, E, R>;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Nodes are first-class Effects. Everything in the Effect ecosystem works on them directly:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { h } from "@weftui/core";
|
|
24
|
+
import { Effect } from "effect";
|
|
25
|
+
|
|
26
|
+
// yield* in Effect.gen — R propagates into the generator's context
|
|
27
|
+
const node = yield * h.div({ class: "container" }, "Hello");
|
|
28
|
+
|
|
29
|
+
// pipe — chain Effect operators directly
|
|
30
|
+
const provided = pipe(h.div(userStream), Effect.provide(UserServiceLive));
|
|
31
|
+
|
|
32
|
+
// Effect.flatMap — sequence node creation with async logic
|
|
33
|
+
const card = pipe(
|
|
34
|
+
fetchCard(id),
|
|
35
|
+
Effect.flatMap((data) => h.div({ class: "card" }, data.title)),
|
|
36
|
+
);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## The `h` namespace
|
|
40
|
+
|
|
41
|
+
`h` is a proxy object where every property is an element builder. Access any HTML or SVG tag name as `h.tagName`:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { h } from "@weftui/core";
|
|
45
|
+
|
|
46
|
+
h.div({ class: "container" }, [h.span("Hello"), h.p("World")]);
|
|
47
|
+
h.input({ type: "text", placeholder: "Search..." });
|
|
48
|
+
h.button({ type: "button", onclick: () => handleClick() }, "Submit");
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Each builder accepts these call signatures:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// props + children array
|
|
55
|
+
h.div(props, children: Node[])
|
|
56
|
+
|
|
57
|
+
// props + single string or number child
|
|
58
|
+
h.div(props, child: string | number)
|
|
59
|
+
|
|
60
|
+
// props only
|
|
61
|
+
h.div(props)
|
|
62
|
+
|
|
63
|
+
// children only (no props)
|
|
64
|
+
h.div(children: Node[])
|
|
65
|
+
|
|
66
|
+
// single string or number child
|
|
67
|
+
h.div("five")
|
|
68
|
+
h.div(5)
|
|
69
|
+
|
|
70
|
+
// no props, no children
|
|
71
|
+
h.div()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### How `E` and `R` accumulate
|
|
75
|
+
|
|
76
|
+
Reactive prop values (any `Stream`, `Effect`, or `Subscribable`) contribute their channels to the node:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
declare const colorStream: Stream.Stream<string, never, ThemeService>;
|
|
80
|
+
|
|
81
|
+
// Node<never, ThemeService> — R comes from the stream prop
|
|
82
|
+
const box = h.div({ style: { color: colorStream } }, "Hello");
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Children contribute their channels too, and siblings union their channels:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
declare const nodeA: Node<never, ServiceA>;
|
|
89
|
+
declare const nodeB: Node<never, ServiceB>;
|
|
90
|
+
|
|
91
|
+
// Node<never, ServiceA | ServiceB>
|
|
92
|
+
const parent = h.div([nodeA, nodeB]);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Static values (strings, numbers, plain functions) contribute `never` to both channels.
|
|
96
|
+
|
|
97
|
+
## `h.fragment`
|
|
98
|
+
|
|
99
|
+
`h.fragment` groups children without emitting a wrapper element. Use it when a component needs to return multiple sibling nodes:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { h } from "@weftui/core";
|
|
103
|
+
|
|
104
|
+
// Renders as three adjacent <td> elements with no wrapping element
|
|
105
|
+
const TableRow = ({ user }: { user: User }) =>
|
|
106
|
+
h.fragment([h.td(user.name), h.td(user.role), h.td(user.status)]);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Custom components with `Component.gen` / `Component.make`
|
|
110
|
+
|
|
111
|
+
Plain functions work fine for simple components, but the `Component` factories provide type-level wiring so the caller's reactive prop types contribute their `E`/`R` to the returned node. Pick `Component.make` for a plain-function body and `Component.gen` for a generator body (when you need `yield*` to set up local state or pull from services).
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { Component, h } from "@weftui/core";
|
|
115
|
+
import { Stream } from "effect";
|
|
116
|
+
|
|
117
|
+
interface ButtonProps {
|
|
118
|
+
label: string | Stream.Stream<string>;
|
|
119
|
+
onclick?: () => void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const Button = Component.make((props: ButtonProps) =>
|
|
123
|
+
h.button({ onclick: props.onclick }, [props.label]),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// When called with a stream prop, the stream's R flows into the node type:
|
|
127
|
+
declare const labelStream: Stream.Stream<string, never, I18nService>;
|
|
128
|
+
|
|
129
|
+
// Node<never, I18nService>
|
|
130
|
+
const btn = Button({ label: labelStream });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Components also accept an optional `children` argument, either as `readonly Renderable[]` or as a `(input) => readonly Renderable[]` function (render-prop pattern). `E`/`R` from children — including the array returned by a function-children call — accumulate on the resulting node.
|
|
134
|
+
|
|
135
|
+
Without `Component`, a plain function's return type is fixed at definition time and does not reflect the caller's reactive prop types.
|
|
136
|
+
|
|
137
|
+
See [component-authoring.md](https://weftui.dev/docs/how-to/author-components) for a full walkthrough.
|
|
138
|
+
|
|
139
|
+
## Suspense boundaries
|
|
140
|
+
|
|
141
|
+
`Boundary.suspend` wraps async children and shows a fallback until all of them have emitted their first value:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { Boundary, h } from "@weftui/core";
|
|
145
|
+
|
|
146
|
+
Boundary.suspend({ fallback: h.div({ class: "spinner" }, "Loading...") }, [
|
|
147
|
+
AsyncCard({ id: 1 }),
|
|
148
|
+
AsyncCard({ id: 2 }),
|
|
149
|
+
]);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The fallback is replaced atomically — either all children are visible or none are. This prevents partial flicker when multiple async siblings resolve at different times. The boundary's node type is `Node<ChildrenE, ChildrenR>`: the children's `E`/`R` channels accumulate onto it, exactly as they would for a plain `h.*` parent.
|
|
153
|
+
|
|
154
|
+
On the server, `renderToStreamHydratable` emits the fallback inline and appends patch scripts as children resolve. On the client, `hydrate` sees through `Boundary.suspend` boundaries and adopts the already-resolved DOM directly.
|
|
155
|
+
|
|
156
|
+
`Boundary.suspend` is one of the boundary combinators — see the [core reference](https://weftui.dev/docs/reference/core#boundarysuspend) for the full `Boundary.*` surface, including the failure-catch variants and `Boundary.rpc`.
|
|
157
|
+
|
|
158
|
+
## See also
|
|
159
|
+
|
|
160
|
+
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model) — why a `Node` is an `Effect` and how the tree renders
|
|
161
|
+
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) — the `Source` vocabulary that reactive props and children accept
|
|
162
|
+
- [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense) — the boundary combinators as tree nodes
|
|
163
|
+
- [Author Components](https://weftui.dev/docs/how-to/author-components) — `Component.gen` / `Component.make` in practice
|
|
164
|
+
- [`@weftui/core` reference](https://weftui.dev/docs/reference/core)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Reactive Primitives
|
|
3
|
+
order: 3
|
|
4
|
+
section: explanation
|
|
5
|
+
description: The Source<A, E, R> vocabulary; Stream, Effect, and Subscribable as prop values and children; derived streams, reactive styles, and NoPropValue.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Reactive Primitives
|
|
9
|
+
|
|
10
|
+
The unified `Source` vocabulary is what lets static values, Effects, Streams, and Subscribables be used interchangeably wherever reactivity is supported — props, children, and style values all accept the same type.
|
|
11
|
+
|
|
12
|
+
Weft accepts a `Source` for prop values and children. Any of these is valid wherever reactivity is supported:
|
|
13
|
+
|
|
14
|
+
- A plain static value (`string`, `number`, `boolean`, ...)
|
|
15
|
+
- An `Effect.Effect<A, E, R>` — runs once and resolves to a value
|
|
16
|
+
- A `Stream.Stream<A, E, R>` — each emission replaces the previous value
|
|
17
|
+
- A `Subscribable<A, E, R>` — like a hot stream; already has a "current value"
|
|
18
|
+
|
|
19
|
+
The `Source<A, E, R>` type captures this union:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
type Source<A, E, R> = A | Effect.Effect<A, E, R> | Stream.Stream<A, E, R> | Subscribable<A, E, R>;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Static values
|
|
26
|
+
|
|
27
|
+
Static props behave exactly as you'd expect — set once and never updated:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
h.div({ class: "container", id: "root" }, "Hello");
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Effect props
|
|
34
|
+
|
|
35
|
+
When a prop value is an `Effect`, it runs once and the resulting value is applied:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const username = Effect.map(fetchProfile(), (p) => p.name);
|
|
39
|
+
|
|
40
|
+
// Renders the username once it resolves
|
|
41
|
+
h.span([username]);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `E` and `R` channels of the Effect flow into the node's own channels.
|
|
45
|
+
|
|
46
|
+
## Stream props and children
|
|
47
|
+
|
|
48
|
+
Streams are the primary reactive primitive. Each emission replaces the previous value in the DOM — no diffing, direct DOM update:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { SubscriptionRef, Stream } from "effect";
|
|
52
|
+
|
|
53
|
+
const count = yield * SubscriptionRef.make(0);
|
|
54
|
+
|
|
55
|
+
// count.changes is a Stream<number> — each new value updates the text node
|
|
56
|
+
h.span([count.changes]);
|
|
57
|
+
|
|
58
|
+
// Stream as a prop — each emission sets the attribute
|
|
59
|
+
const isDisabled = Stream.map(count.changes, (n) => n >= 10);
|
|
60
|
+
h.button({ disabled: isDisabled }, "Submit");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Streams can also supply entire child arrays. Each emission replaces the previous set of children:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const todos = yield * SubscriptionRef.make<string[]>([]);
|
|
67
|
+
|
|
68
|
+
h.ul([Stream.map(todos.changes, (list) => list.map((item) => h.li(item)))]);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Derived streams
|
|
72
|
+
|
|
73
|
+
Because `.changes` is a plain `Stream`, the full Stream API applies:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const count = yield * SubscriptionRef.make(0);
|
|
77
|
+
|
|
78
|
+
const doubled = Stream.map(count.changes, (n) => n * 2);
|
|
79
|
+
const formatted = Stream.map(count.changes, (n) => `Count: ${n}`);
|
|
80
|
+
const isHigh = Stream.map(count.changes, (n) => n > 10);
|
|
81
|
+
|
|
82
|
+
h.div([
|
|
83
|
+
h.p([count.changes]),
|
|
84
|
+
h.p([doubled]),
|
|
85
|
+
h.p([formatted]),
|
|
86
|
+
h.p({ style: { color: Stream.map(isHigh, (b) => (b ? "red" : "black")) } }, "Status"),
|
|
87
|
+
]);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Multiple refs can be combined with `Stream.zipLatestWith`, `Stream.merge`, or other combinators:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const firstName = yield * SubscriptionRef.make("");
|
|
94
|
+
const lastName = yield * SubscriptionRef.make("");
|
|
95
|
+
|
|
96
|
+
const fullName = Stream.zipLatestWith(firstName.changes, lastName.changes, (first, last) =>
|
|
97
|
+
`${first} ${last}`.trim(),
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Reactive styles
|
|
102
|
+
|
|
103
|
+
The `style` prop accepts the same `Source` vocabulary at any level:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Individual property as a stream
|
|
107
|
+
h.div({
|
|
108
|
+
style: {
|
|
109
|
+
color: colorStream, // Stream<string>
|
|
110
|
+
opacity: opacityStream, // Stream<number>
|
|
111
|
+
fontWeight: "bold", // static
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Entire style object as a stream
|
|
116
|
+
h.div({ style: styleObjectStream });
|
|
117
|
+
|
|
118
|
+
// Combine a whole-object stream with a static property.
|
|
119
|
+
// A whole-object stream replaces every property on each emit, so fold the
|
|
120
|
+
// static value into each emitted object with Stream.map — you cannot spread the
|
|
121
|
+
// Stream itself into a style object (that copies the Stream's internals, not
|
|
122
|
+
// its emitted style keys).
|
|
123
|
+
h.div({
|
|
124
|
+
style: Stream.map(styleObjectStream, (s) => ({
|
|
125
|
+
...s, // reactive properties
|
|
126
|
+
transition: "all 0.3s", // static, applied on every emit
|
|
127
|
+
})),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// For a mix of static and per-property reactive values, use per-property
|
|
131
|
+
// streams alongside static siblings instead:
|
|
132
|
+
h.div({
|
|
133
|
+
style: {
|
|
134
|
+
transform: transformStream, // reactive, per-property
|
|
135
|
+
transition: "all 0.3s", // static
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## NoPropValue
|
|
141
|
+
|
|
142
|
+
When a `Stream` prop ends before emitting, the renderer raises a `NoPropValue` tagged error. This carries an optional `key` field identifying which prop triggered it:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Handle at the mount boundary if needed. `Effect.catchTag` matches the error
|
|
146
|
+
// by its string tag, so no `NoPropValue` import is required here.
|
|
147
|
+
pipe(
|
|
148
|
+
mount(App(), root),
|
|
149
|
+
Effect.catchTag("NoPropValue", (e) =>
|
|
150
|
+
Effect.logWarning(`Prop stream ended before emitting: ${e.key}`),
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
In practice you only encounter `NoPropValue` if you use a finite `Stream` as a prop and it ends before emitting — e.g., `Stream.empty` or `Stream.take(0, stream)`. Most usage with `SubscriptionRef.changes` or infinite streams never raises it.
|
|
156
|
+
|
|
157
|
+
## See also
|
|
158
|
+
|
|
159
|
+
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model) — streams as the weft woven through a static tree
|
|
160
|
+
- [The Combinator API](https://weftui.dev/docs/explanation/combinator-api) — how reactive props and children contribute `E`/`R`
|
|
161
|
+
- [Style Reactively](https://weftui.dev/docs/how-to/style-reactively) and [Render Keyed Lists](https://weftui.dev/docs/how-to/render-keyed-lists) — reactive props and collections in practice
|
|
162
|
+
- [`Source` reference](https://weftui.dev/docs/reference/core#source-namespace) — the `Source` type and `Source.toSubscribable`
|