@weftui/dom 0.27.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +32 -18
  2. package/dist/boundary-replay-BY4GyLot.js +1 -0
  3. package/dist/client/index.d.ts +159 -146
  4. package/dist/client/index.js +1 -1
  5. package/dist/{data-Bk7mnjdd.d.ts → data-C88W1AwU.d.ts} +4 -5
  6. package/dist/index.d.ts +196 -2
  7. package/dist/index.js +1 -1
  8. package/dist/rolldown-runtime-DK3Fl9T5.js +1 -0
  9. package/dist/server/index.d.ts +6 -7
  10. package/dist/server/index.js +1 -1
  11. package/dist/shared-Dz0KM9ku.js +1 -0
  12. package/docs/explanation/boundaries-and-suspense.md +37 -18
  13. package/docs/explanation/combinator-api.md +14 -12
  14. package/docs/explanation/reactive-primitives.md +15 -15
  15. package/docs/explanation/rendering-model.md +20 -18
  16. package/docs/explanation/services-and-context.md +39 -29
  17. package/docs/how-to/add-routing.md +76 -52
  18. package/docs/how-to/author-components.md +46 -32
  19. package/docs/how-to/compose-behavior-and-markup.md +144 -0
  20. package/docs/how-to/handle-forms.md +6 -6
  21. package/docs/how-to/load-async-data.md +15 -13
  22. package/docs/how-to/load-data-with-rpc.md +34 -30
  23. package/docs/how-to/provide-services.md +54 -74
  24. package/docs/how-to/render-keyed-lists.md +10 -8
  25. package/docs/how-to/render-on-the-server.md +19 -14
  26. package/docs/how-to/show-navigation-progress.md +10 -8
  27. package/docs/how-to/split-routes-lazily.md +16 -14
  28. package/docs/how-to/style-reactively.md +13 -13
  29. package/docs/how-to/use-element-refs.md +10 -8
  30. package/docs/index.md +20 -18
  31. package/docs/reference/core.md +44 -40
  32. package/docs/reference/dom.md +395 -58
  33. package/docs/reference/router.md +71 -49
  34. package/docs/tutorial/01-your-first-app.md +10 -11
  35. package/docs/tutorial/02-reactivity.md +11 -8
  36. package/docs/tutorial/03-services-and-async.md +19 -13
  37. package/docs/tutorial/04-errors-and-server.md +17 -7
  38. package/package.json +9 -8
  39. package/dist/boundary-replay-BR26_puM.js +0 -1
  40. package/dist/data-uLmMpQMV.js +0 -1
package/dist/index.d.ts CHANGED
@@ -1,2 +1,196 @@
1
- import { i as UnsupportedNodeTypeError, n as RenderError, r as StreamSubscriptionError, t as HydrationMismatchError } from "./data-Bk7mnjdd.js";
2
- export { HydrationMismatchError, RenderError, StreamSubscriptionError, UnsupportedNodeTypeError };
1
+ import { i as UnsupportedNodeTypeError, n as RenderError, r as StreamSubscriptionError, t as HydrationMismatchError } from "./data-C88W1AwU.js";
2
+ import { Effect, Option, Stream, SubscriptionRef } from "effect";
3
+ import { NoPropValue, Source, Subscribable } from "@weftui/core";
4
+ declare namespace props_d_exports {
5
+ export { CxInput, CxRecord, CxResult, DomProps, Merged, cx, merge };
6
+ }
7
+ /**
8
+ * The prop-bag argument shape accepted by `h.*` element builders.
9
+ * Loosely constrained because {@link merge} dispatches on the key
10
+ * (`class`, `style`, `ref`, `on*`), not on the bag's declared type.
11
+ */
12
+ type DomProps = object;
13
+ /** Any reactive prop value: the non-static arms of `Source.Source`. */
14
+ type ReactiveValue = Stream.Stream<any, any, any> | Effect.Effect<any, any, any> | Subscribable.Subscribable<any, any, any>;
15
+ /** Any function value, used to detect plain event-handler functions. */
16
+ type AnyFunction = (...args: ReadonlyArray<any>) => any;
17
+ /** The lowercase third character the renderer's `isEventHandler` check requires. */
18
+ type LowercaseLetter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z";
19
+ /**
20
+ * Type-level form of the renderer's `isEventHandler` check. Mirrors it exactly,
21
+ * including the lowercase third character: Weft's DOM handler props are all
22
+ * lowercase (`onclick`), so a camelCase `onClick` is not a handler key and must
23
+ * type as last-wins, matching what the runtime does with it.
24
+ */
25
+ type EventHandlerKey = `on${LowercaseLetter}${string}`;
26
+ /**
27
+ * Error channel a handler side contributes. Covers a plain handler returning an
28
+ * Effect and the reactive `Stream`/`Effect`-of-handler forms core's
29
+ * `EventHandler` allows, so a reactive side keeps its channel even though the
30
+ * runtime treats it as last-wins.
31
+ */
32
+ type HandlerError<H> = H extends ((...args: ReadonlyArray<any>) => infer Ret) ? Ret extends Effect.Effect<any, infer E, any> ? E : never : H extends Stream.Stream<any, infer E, any> ? E : H extends Effect.Effect<any, infer E, any> ? E : never;
33
+ /** Context channel a handler side contributes; mirrors {@link HandlerError}. */
34
+ type HandlerContext<H> = H extends ((...args: ReadonlyArray<any>) => infer Ret) ? Ret extends Effect.Effect<any, any, infer R> ? R : never : H extends Stream.Stream<any, any, infer R> ? R : H extends Effect.Effect<any, any, infer R> ? R : never;
35
+ /**
36
+ * A handler side with its "no handler" arms removed. Core declares handler
37
+ * props as `null | false | EventHandler<...>`, and leaving those in place makes
38
+ * the event-parameter match below fail, degrading the merged handler's event to
39
+ * the base `Event` (so `ev.clientX` stops compiling). Kept separate from
40
+ * {@link Present}, which must not strip `false` from ordinary boolean props.
41
+ */
42
+ type HandlerSide<T> = Exclude<T, null | false | undefined>;
43
+ /** Event parameter of a chained handler: intersection of both sides' events. */
44
+ type MergedHandlerEvent<L, R> = [HandlerSide<L>] extends [(event: infer EL) => any] ? [HandlerSide<R>] extends [(event: infer ER) => any] ? EL & ER : EL : [HandlerSide<R>] extends [(event: infer ER2) => any] ? ER2 : Event;
45
+ /**
46
+ * Handler cell rule. If either side can carry a handler the merged value is a
47
+ * handler function whose channels union both sides'; if neither can, the cell
48
+ * is the nullish "no handler" type.
49
+ *
50
+ * The callable shape is deliberately coarse: core declares handler props as
51
+ * `null | false | EventHandler<...>`, so a rule that only matched two bare
52
+ * functions would miss every realistically-typed bag and drop the left side's
53
+ * `E`/`R` from `PropsE`/`PropsR`. Channel accuracy is the part that must not
54
+ * degrade, so it is computed from whatever shape each side has.
55
+ */
56
+ type MergedHandlerValue<L, R> = [HandlerSide<L>, HandlerSide<R>] extends [never, never] // Neither side can carry a handler, so neither can the result.
57
+ ? null | false | undefined : (event: MergedHandlerEvent<L, R>) => Effect.Effect<void, HandlerError<HandlerSide<L>> | HandlerError<HandlerSide<R>>, HandlerContext<HandlerSide<L>> | HandlerContext<HandlerSide<R>>>;
58
+ /**
59
+ * Class cell rule. A side that cannot carry a reactive value joins statically,
60
+ * so two such sides stay a plain `string` and the descriptor remains
61
+ * analyzable (AC8). If either side might be reactive the cell is exactly a
62
+ * `Stream`, never a union: `PropsE`/`PropsR` extract channels by matching
63
+ * `P[K] extends Stream<...>`, and a union would fail that match and silently
64
+ * drop the class's `E`/`R`. Erring toward `Stream` is therefore the safe
65
+ * direction, at the cost of over-reporting for a wide `Source<string>` side.
66
+ */
67
+ type MergedClassValue<L, R> = true extends IsReactiveCxInput<L> | IsReactiveCxInput<R> ? Stream.Stream<string, CxInputError<L> | CxInputError<R> | NoPropValue, CxInputContext<L> | CxInputContext<R>> : string;
68
+ /**
69
+ * True for a per-property style object. Mirrors the runtime
70
+ * `isPlainStyleObject`, including its array exclusion, so the two cannot
71
+ * disagree about which shape takes the object-merge branch.
72
+ */
73
+ type IsStyleObject<V> = V extends object ? V extends ReactiveValue | AnyFunction | ReadonlyArray<any> ? false : true : false;
74
+ /**
75
+ * Style cell rule: object plus object takes the key union with the right side
76
+ * winning per key, each surviving value passed through by reference.
77
+ * Any other shape on either side is last-wins.
78
+ */
79
+ type MergedStyleValue<L, R> = [IsStyleObject<L>, IsStyleObject<R>] extends [true, true] ? { readonly [K in keyof L | keyof R]: K extends keyof R ? R[K] : K extends keyof L ? L[K] : never; } : R;
80
+ /**
81
+ * Ref cell rule: both sides concatenate into one readonly fan-out array.
82
+ * Typed against the same permissive element as the core `ref` array arm so the
83
+ * result stays assignable to any element builder. `SubscriptionRef` is
84
+ * invariant, so a union of the two sides' exact ref types would reject the
85
+ * heterogeneous fan-out this rule exists to enable (AC14a).
86
+ */
87
+ type MergedRefValue = ReadonlyArray<SubscriptionRef.SubscriptionRef<Option.Option<any>>>;
88
+ /**
89
+ * The value a side actually carries when its key is present. An optional prop
90
+ * indexes to `T | undefined`; the cell rules must dispatch on `T`, or every
91
+ * pattern match below silently falls through to last-wins.
92
+ */
93
+ type Present<T> = Exclude<T, undefined>;
94
+ /** Per-key dispatch for a key present on both sides. */
95
+ type MergedValue<K extends PropertyKey, L, R> = K extends "class" ? MergedClassValue<Present<L>, Present<R>> : K extends "style" ? MergedStyleValue<Present<L>, Present<R>> : K extends "ref" ? MergedRefValue : K extends EventHandlerKey ? MergedHandlerValue<Present<L>, Present<R>> : Present<R>;
96
+ /**
97
+ * The cell for a key both bags declare.
98
+ *
99
+ * When one side's key is optional it may be absent at runtime, in which case
100
+ * the other side's value survives unmerged. That outcome is deliberately not
101
+ * unioned in: each cell rule above is already coarse enough to accept it at a
102
+ * prop slot, and unioning the bare side back in would reintroduce the invariant
103
+ * `SubscriptionRef` that makes the AC14a fan-out fail to compile.
104
+ */
105
+ type MergedSharedValue<K extends keyof L & keyof R, L, R> = MergedValue<K, L[K], R[K]>;
106
+ /** Flattens an intersection into one object type, preserving optional modifiers. */
107
+ type Simplify<T> = { [K in keyof T]: T[K]; };
108
+ /** Keys only the left bag has. Homomorphic over `L`, so `?` modifiers survive. */
109
+ type MergedLeftOnly<L, R> = { readonly [K in keyof L as K extends keyof R ? never : K]: L[K]; };
110
+ /** Keys only the right bag has. Homomorphic over `R`, so `?` modifiers survive. */
111
+ type MergedRightOnly<L, R> = { readonly [K in keyof R as K extends keyof L ? never : K]: R[K]; };
112
+ /**
113
+ * Keys both bags declare. Emitted as required (`-?`) with an undefined-free
114
+ * value, because `PropsE`/`PropsR` extract channels by matching `P[K]` against
115
+ * a function or `Stream` shape, and a `T | undefined` union fails that match
116
+ * and silently drops the key's `E`/`R`. Channel accuracy outranks presence
117
+ * precision here, so a shared key that is optional on both sides and absent at
118
+ * runtime is still typed as present.
119
+ */
120
+ type MergedShared<L, R> = { readonly [K in keyof L & keyof R]-?: MergedSharedValue<K, L, R>; };
121
+ /**
122
+ * Binary merge of two bags: shared keys via {@link MergedValue}, rest pass
123
+ * through. Built from two homomorphic mapped types so optional keys stay
124
+ * optional. A single `[K in keyof L | keyof R]` map would mark every key
125
+ * required, claiming keys are present that the runtime never copied.
126
+ */
127
+ type MergedPair<L, R> = Simplify<MergedLeftOnly<L, R> & MergedRightOnly<L, R> & MergedShared<L, R>>;
128
+ /** The empty bag: identity of the merge monoid and result of `merge()`. */
129
+ type EmptyBag = Readonly<Record<never, never>>;
130
+ /**
131
+ * Result of {@link merge}: a left-to-right fold of {@link MergedPair} over the
132
+ * bag tuple. Value types stay coarse but `E`/`R` channels stay precise, so
133
+ * `PropsE`/`PropsR` accumulate the full union through `h.*`.
134
+ */
135
+ type Merged<Bags extends ReadonlyArray<DomProps>> = Bags extends readonly [] ? EmptyBag : Bags extends readonly [infer Only extends DomProps] ? Only : Bags extends readonly [infer L extends DomProps, infer R extends DomProps, ...infer Rest extends ReadonlyArray<DomProps>] ? Merged<[MergedPair<L, R>, ...Rest]> : MergedPair<Bags[number], Bags[number]>;
136
+ /**
137
+ * Merge DOM prop bags left to right. Pure: the result is a plain prop bag of
138
+ * ordinary Weft prop values, so `h.*` accepts it and `PropsE`/`PropsR` carry
139
+ * the error and context channels through. `{}` is the identity, and the fold is
140
+ * associative except for `style` when a non-object form takes part (see below).
141
+ *
142
+ * Rules for a key present on both sides:
143
+ *
144
+ * - `on*`: chained left to right, both always run, failures isolated and causes aggregated.
145
+ * - `class`: space-concatenated. Static stays a `string`, reactive derives a `Stream<string>`.
146
+ * - `style`: two per-property objects merge per key (right wins). Other forms are
147
+ * last-wins, which discards a side and so is not associative (v1 limitation).
148
+ * - `ref`: fan-out. Refs concatenate into an array and every ref is set.
149
+ * - anything else: last-wins.
150
+ *
151
+ * Keys on only one side pass through untouched.
152
+ *
153
+ * @param bags - Lowest precedence first.
154
+ */
155
+ declare function merge<const Bags extends ReadonlyArray<DomProps>>(...bags: Bags): Merged<Bags>;
156
+ /** A reactive string value accepted by {@link cx} in value position. */
157
+ type CxReactiveValue = Stream.Stream<string, any, any> | Effect.Effect<string, any, any> | Subscribable.Subscribable<string, any, any>;
158
+ /** A condition in a {@link cx} record: a static or reactive boolean. */
159
+ type CxCondition = boolean | Stream.Stream<boolean, any, any> | Effect.Effect<boolean, any, any> | Subscribable.Subscribable<boolean, any, any>;
160
+ /**
161
+ * Record form of a {@link cx} input: each key is a class name included while
162
+ * its condition is truthy. Reactive conditions are the difference from clsx.
163
+ */
164
+ interface CxRecord {
165
+ readonly [className: string]: CxCondition;
166
+ }
167
+ /**
168
+ * One input to {@link cx}: a string, a falsy value (skipped), a reactive
169
+ * string, a condition record, or a nested array of inputs.
170
+ */
171
+ type CxInput = string | false | null | undefined | CxReactiveValue | CxRecord | ReadonlyArray<CxInput>;
172
+ /** True when a {@link cx} input contains a reactive value at any depth. */
173
+ type IsReactiveCxInput<I> = I extends ReactiveValue ? true : I extends ReadonlyArray<infer Item> ? IsReactiveCxInput<Item> : I extends CxRecord ? I[keyof I] extends boolean ? false : true : false;
174
+ /** Union of the error channels contributed by a {@link cx} input. */
175
+ type CxInputError<I> = I extends ReactiveValue ? Source.Error<I> : I extends ReadonlyArray<infer Item> ? CxInputError<Item> : I extends CxRecord ? Source.Error<I[keyof I]> : never;
176
+ /** Union of the context channels contributed by a {@link cx} input. */
177
+ type CxInputContext<I> = I extends ReactiveValue ? Source.Context<I> : I extends ReadonlyArray<infer Item> ? CxInputContext<Item> : I extends CxRecord ? Source.Context<I[keyof I]> : never;
178
+ /**
179
+ * Result of {@link cx}: a plain `string` when every input is static, otherwise
180
+ * a `Stream<string>` unioning all reactive inputs' channels plus `NoPropValue`.
181
+ */
182
+ type CxResult<Inputs extends ReadonlyArray<CxInput>> = true extends IsReactiveCxInput<Inputs[number]> ? Stream.Stream<string, CxInputError<Inputs[number]> | NoPropValue, CxInputContext<Inputs[number]>> : string;
183
+ /**
184
+ * Reactive clsx. Builds a class string from strings, falsy values (skipped),
185
+ * nested arrays, and `{ className: condition }` records, where any value or
186
+ * condition may be a `Stream`, `Effect`, or `Subscribable`.
187
+ *
188
+ * All-static inputs join into a plain string, so the descriptor stays
189
+ * analyzable. Any reactive input derives a pure `Stream<string>` description,
190
+ * the same engine {@link merge} uses for its `class` rule.
191
+ *
192
+ * @param inputs - Joined left to right, no dedupe.
193
+ */
194
+ declare function cx<const Inputs extends ReadonlyArray<CxInput>>(...inputs: Inputs): CxResult<Inputs>;
195
+ //#endregion
196
+ export { HydrationMismatchError, props_d_exports as Props, RenderError, StreamSubscriptionError, UnsupportedNodeTypeError };
package/dist/index.js CHANGED
@@ -1 +1 @@
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};
1
+ import{t as e}from"./rolldown-runtime-DK3Fl9T5.js";import{_ as t,b as n,h as r,i,v as a}from"./shared-Dz0KM9ku.js";import{Cause as o,Effect as s,Exit as c,Stream as l,pipe as u}from"effect";import{NoPropValue as d,Subscribable as f,isStream as p}from"@weftui/core";var m=e({cx:()=>C,merge:()=>h});function h(...e){return e.reduce((e,t)=>g(e,t),{})}function g(e,t){let n={...e};for(let[r,i]of Object.entries(t))n[r]=Object.hasOwn(e,r)?y(r,e[r],i):i;return n}function _(e){return p(e)||s.isEffect(e)||f.isSubscribable(e)}function v(e){return typeof e==`object`&&!!e&&!Array.isArray(e)&&!_(e)&&w(e)}function y(e,t,n){return e===`ref`?[...b(t),...b(n)]:e===`class`?k([t,n],`class`):e===`style`?v(t)&&v(n)?{...t,...n}:n:i(e)?typeof t==`function`&&typeof n==`function`?S(t,n):n??t:n}function b(e){return(Array.isArray(e)?e:[e]).filter(e=>e!=null)}function x(e,t){try{let n=e(t);return s.isEffect(n)?n:s.void}catch(e){return s.die(e)}}function S(e,t){return n=>{let r=x(e,n),i=x(t,n);return s.gen(function*(){let e=yield*s.exit(r),t=yield*s.exit(i);if(c.isFailure(e)&&c.isFailure(t))return yield*s.failCause(o.fromReasons([...e.cause.reasons,...t.cause.reasons]));if(c.isFailure(e))return yield*s.failCause(e.cause);if(c.isFailure(t))return yield*s.failCause(t.cause)})}}function C(...e){return k(e)}function w(e){let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function T(e,t){for(let n of e)if(!(n===!1||n==null)){if(typeof n==`string`){n!==``&&t.push({kind:`static`,text:n});continue}if(Array.isArray(n)){T(n,t);continue}if(_(n)){t.push({kind:`value`,source:n});continue}if(w(n))for(let[e,r]of Object.entries(n))_(r)?t.push({kind:`toggle`,name:e,source:r}):r&&t.push({kind:`static`,text:e})}}function E(e,t){let n=[],r=0;for(let i of e){if(i.kind===`static`){n.push(i.text);continue}let e=t[r];r+=1,i.kind===`value`?typeof e==`string`&&e!==``&&n.push(e):e&&n.push(i.name)}return n.join(` `)}function D(e,t){return l.suspend(()=>{let n=!1;return u(e,l.tap(()=>s.sync(()=>{n=!0})),l.concat(l.suspend(()=>n?l.empty:l.fail(new d({key:t})))))})}function O(e,t){if(p(e))return D(e,t);if(s.isEffect(e))return l.fromEffect(e);let n=e;return l.concat(l.fromEffect(n.get),n.changes)}function k(e,t){let n=[];T(e,n);let r=n.filter(e=>e.kind!==`static`);if(r.length===0)return E(n,[]);let i=r.map(e=>O(e.source,t));return u(l.zipLatestAll(...i),l.map(e=>E(n,e)))}export{r as HydrationMismatchError,m as Props,t as RenderError,a as StreamSubscriptionError,n as UnsupportedNodeTypeError};
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty,t=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],enumerable:!0});return n||e(r,Symbol.toStringTag,{value:`Module`}),r};export{t};
@@ -1,7 +1,6 @@
1
1
  import { Cause, Context, Effect, Option, Scope, Stream } from "effect";
2
2
  import { AppRpcClientTag, Renderable } from "@weftui/core";
3
3
  import { Renderable as Renderable$1 } from "@weftui/core/types";
4
-
5
4
  //#region src/server/render-to-stream.d.ts
6
5
  /**
7
6
  * Progressively serializes an Effect-infused JSX tree (`Renderable`) into a stream
@@ -28,7 +27,7 @@ declare const renderToStreamHydratable: (node: Renderable) => Stream.Stream<stri
28
27
  * The server-side counterpart to the client DOM renderer, intended to produce
29
28
  * output isomorphic with what the client renderer creates in the browser.
30
29
  *
31
- * Suspense boundaries render their fallback directly no comment markers
30
+ * Suspense boundaries render their fallback directly: no comment markers
32
31
  * and no `<template>`/`<script>` patches. For streaming Suspense support use
33
32
  * {@link renderToStreamHydratable} / {@link renderToStream} instead.
34
33
  *
@@ -54,7 +53,7 @@ declare const renderToStringHydratable: (node: Renderable$1) => Effect.Effect<st
54
53
  */
55
54
  interface HydratableShell {
56
55
  /**
57
- * The fully buffered main walk byte-identical to the `mainStream` portion
56
+ * The fully buffered main walk, byte-identical to the `mainStream` portion
58
57
  * of `renderToStreamHydratable` for the same tree: reactive-region markers,
59
58
  * Suspense fallbacks inline with their markers, resolved (blocking)
60
59
  * `Boundary.rpc` regions, and failure-boundary fallbacks with payloads.
@@ -62,7 +61,7 @@ interface HydratableShell {
62
61
  readonly shell: string;
63
62
  /**
64
63
  * The Suspense patch queue as a stream. Never fails (resolution fibers
65
- * handle their own errors see {@link SuspenseFailureHandlerTag}); completes
64
+ * handle their own errors, see {@link SuspenseFailureHandlerTag}); completes
66
65
  * once all pending boundaries have resolved, or immediately if the tree has
67
66
  * no `Boundary.suspend`.
68
67
  */
@@ -74,7 +73,7 @@ interface HydratableShell {
74
73
  * an HTTP consumer can decide status/headers before flushing any bytes, then
75
74
  * streams Suspense patches separately.
76
75
  *
77
- * Errors raised during the main walk fail this Effect nothing has been
76
+ * Errors raised during the main walk fail this Effect. Nothing has been
78
77
  * handed to the consumer yet, so the caller may respond with a different
79
78
  * document and a real status (AC-SH2).
80
79
  *
@@ -96,7 +95,7 @@ interface SuspenseFailureSubstitute {
96
95
  * When `true`, the patch script also injects
97
96
  * `<meta name="robots" content="noindex">` into `document.head` before
98
97
  * performing the swap (the head has long been flushed; DOM injection is the
99
- * only route Googlebot's soft-404 pattern).
98
+ * only route: Googlebot's soft-404 pattern).
100
99
  */
101
100
  readonly markNoindex: boolean;
102
101
  /**
@@ -115,7 +114,7 @@ interface SuspenseFailureSubstitute {
115
114
  * cause escapes the suspended children unhandled (no failure `Boundary`
116
115
  * inside the children matched it). Returning `Option.some` substitutes the
117
116
  * patch content for that boundary; `Option.none` (or an absent service)
118
- * keeps the default behaviour the failure is swallowed, no patch is
117
+ * keeps the default behaviour: the failure is swallowed, no patch is
119
118
  * emitted, and the fallback persists. Spec: `streaming-shell.specs.md`
120
119
  * (AC-FH1 … AC-FH6).
121
120
  */
@@ -1 +1 @@
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={'"':`&quot;`,"&":`&amp;`,"'":`&#x27;`,"<":`&lt;`,">":`&gt;`};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};
1
+ import{a as e,b as t,d as n,f as r,i,o as a,p as o,t as s,u as c}from"../shared-Dz0KM9ku.js";import{n as l,t as u}from"../boundary-replay-BY4GyLot.js";import{Cause as d,Context as f,Effect as p,Exit as m,Option as h,Queue as g,Ref as _,Schema as v,Scope as y,Stream as b}from"effect";import{AppRpcClientTag as ee,FAILURE_BOUNDARY as x,FRAGMENT as S,LIST as C,SERVER_BOUNDARY as w,SUSPENSE_BOUNDARY as T,Source as te,Subscribable as E,getElementDescriptor as D,isStream as O,toStream as k}from"@weftui/core";const A=new Set([`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`param`,`source`,`track`,`wbr`]),j={'"':`&quot;`,"&":`&amp;`,"'":`&#x27;`,"<":`&lt;`,">":`&gt;`};function M(e){return e.replace(/["'&<>]/g,e=>j[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 F(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function I(e){return p.gen(function*(){let t=``;for(let[n,r]of Object.entries(e))if(!(n===`children`||n===`ref`||i(n))){if(n===`style`){t+=yield*R(r);continue}t+=yield*ne(n,r)}return t})}function L(e){return O(e)||p.isEffect(e)?k(e).pipe(b.runHead,p.map(h.getOrElse(()=>void 0))):p.succeed(e)}function ne(e,t){return p.gen(function*(){let n=yield*L(t);return n==null?``:typeof n==`boolean`?n?` ${e}=""`:``:` ${e}="${M(String(n))}"`})}function R(e){return p.gen(function*(){let t=yield*L(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*L(r);t!=null&&e.push(`${F(n)}: ${String(t)}`)}return e.length===0?``:` style="${M(e.join(`; `))}"`}return``})}var z=class extends f.Service()(`@weftui/dom/SuspenseFailureHandler`){};function B(e,t,n=!1,i){let a=o(e),c=r(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 b.unwrap(p.gen(function*(){let i=++t.idCounter.current;yield*_.update(t.pendingCount,e=>e+1);let a=e.children===void 0?null:(Array.isArray(e.children),e.children),s=p.gen(function*(){let e=yield*b.mkString(n(a));yield*g.offer(t.patchQueue,h.some(B(i,e)))}).pipe(p.catchCause(e=>d.hasInterruptsOnly(e)?p.failCause(e):p.gen(function*(){let r=yield*p.serviceOption(z);if(h.isNone(r))return;let a=r.value.handle(e);if(h.isNone(a))return;let o=yield*b.mkString(n(a.value.content));yield*g.offer(t.patchQueue,h.some(B(i,o,a.value.markNoindex,a.value.failureReplay)))})),p.ensuring(_.updateAndGet(t.pendingCount,e=>e-1).pipe(p.flatMap(e=>e<=0?g.offer(t.patchQueue,h.none()).pipe(p.asVoid):p.void))),p.ignore);yield*p.forkIn(s,t.scope);let c=`<!--${o(i)}-->`,l=`<!--${r(i)}-->`,u=e.fallback??null;return b.make(c).pipe(b.concat(n(u)),b.concat(b.make(l)))}))}function H(e,t,n){let r=e.children.length===0?null:e.children.length===1?e.children[0]:e.children;return b.unwrap(p.gen(function*(){let i=yield*b.mkString(n(r)).pipe(p.catchCause(r=>p.gen(function*(){let i=e.match(r);if(i===null)return yield*p.failCause(r);let a=yield*b.mkString(n(i));if(t===null)return a;let o=yield*_.getAndSet(t,h.none());return h.isNone(o)?a:`<script type="application/json" ${u}>${P({index:l(e.children).indexOf(o.value.owner),error:o.value.encoded})}<\/script>`+a})));return b.make(i)}))}function re(e,t,n){if(t===null)return p.failCause(n);let r=d.findErrorOption(n);return h.isNone(r)?p.failCause(n):v.encodeEffect(e.errorSchema)(r.value).pipe(p.flatMap(n=>_.set(t,h.some({owner:e,encoded:n}))),p.matchCauseEffect({onFailure:()=>p.failCause(n),onSuccess:()=>p.failCause(n)}))}function ie(e){return{value:E.make({get:p.succeed(e),changes:b.make(e)}),refetch:p.void,pending:E.make({get:p.succeed(!1),changes:b.make(!1)}),error:E.make({get:p.succeed(h.none()),changes:b.make(h.none())})}}function U(e,t,n,r){return b.unwrap(p.gen(function*(){let i=yield*(yield*ee).call(e.tag,e.payload()).pipe(p.catchCause(t=>re(e,n,t))),a=r(e.render(ie(i)));if(!t)return a;let o=`<script type="application/json">${P(yield*v.encodeEffect(e.successSchema)(i))}<\/script>`;return b.make(o).pipe(b.concat(a))}))}function W(e){return p.scoped(te.toSubscribable(e).pipe(p.flatMap(e=>e.get),p.map(e=>Array.from(e)),p.catchTag(`NoPropValue`,()=>p.succeed([]))))}function G(e,n){if(e==null||typeof e==`boolean`)return b.empty;if(typeof e==`string`||typeof e==`number`||typeof e==`bigint`)return b.make(M(String(e)));if(O(e)||p.isEffect(e)){let t=D(e);if(t!==void 0)return G(t,n);if(p.isEffect(e)){let t=p.runSyncExit(e);if(m.isSuccess(t))return G(t.value,n)}return k(e).pipe(b.runHead,p.map(h.match({onNone:()=>b.empty,onSome:e=>G(e,n)})),b.unwrap)}if(typeof e==`object`&&Symbol.iterator in e&&!(`type`in e))return b.flatMap(b.fromIterable(e),e=>G(e,n));if(typeof e==`object`&&`type`in e&&!(Symbol.iterator in e)){let{type:t,props:r}=e;if(t===S)return q(r,n);if(t===T){let e=r;return n===null?G(e.fallback??null,null):V(e,n,e=>G(e,n))}if(t===x)return H(r,null,e=>G(e,n));if(t===w)return U(r,!1,null,e=>G(e,n));if(t===C)return K(r,n);if(typeof t==`string`){let e=b.fromEffect(I(r).pipe(p.map(e=>`<${t}${e}>`)));return A.has(t)?e:e.pipe(b.concat(q(r,n)),b.concat(b.make(`</${t}>`)))}if(typeof t==`function`)return G(t(r),n)}return b.fail(new t({type:e.type,message:`Invalid Renderable type: expected string, FRAGMENT, or function, got ${typeof e.type}`}))}function K(e,t){return b.unwrap(p.gen(function*(){let n=yield*W(e.of),r=b.empty;return n.forEach((n,i)=>{r=r.pipe(b.concat(G(e.render(n,i),t)))}),r}))}function q(e,t){let n=`children`in e?e.children:void 0;if(n==null)return b.empty;let r=Array.isArray(n)?n:[n];return b.flatMap(b.fromIterable(r),e=>G(e,t))}function J(e,r,i){if(e==null||typeof e==`boolean`)return b.empty;if(typeof e==`string`||typeof e==`number`||typeof e==`bigint`)return b.make(M(String(e)));if(O(e)||p.isEffect(e)){let t=D(e);if(t!==void 0)return J(t,r,i);if(p.isEffect(e)){let t=p.runSyncExit(e);if(m.isSuccess(t))return J(t.value,r,i)}return k(e).pipe(b.runHead,p.map(e=>{let t=++r.current,a=h.match(e,{onNone:()=>b.empty,onSome:e=>J(e,r,i)});return b.make(`<!--${n(t)}-->`).pipe(b.concat(a),b.concat(b.make(`<!--${c(t)}-->`)))}),b.unwrap)}if(typeof e==`object`&&Symbol.iterator in e&&!(`type`in e))return b.flatMap(b.fromIterable(e),e=>J(e,r,i));if(typeof e==`object`&&`type`in e&&!(Symbol.iterator in e)){let{type:t,props:n}=e;if(t===S)return Y(n,r,i);if(t===T){let e=n;return i===null?J(e.fallback??null,r,null):V(e,i,e=>J(e,r,i))}if(t===x)return H(n,i?.failureCollector??null,e=>J(e,r,i));if(t===w)return U(n,!0,i?.failureCollector??null,e=>J(e,r,i));if(t===C)return ae(n,r,i);if(typeof t==`string`){let e=b.fromEffect(I(n).pipe(p.map(e=>`<${t}${e}>`)));return A.has(t)?e:e.pipe(b.concat(Y(n,r,i)),b.concat(b.make(`</${t}>`)))}if(typeof t==`function`)return J(t(n),r,i)}return b.fail(new t({type:e.type,message:`Invalid Renderable type: expected string, FRAGMENT, or function, got ${typeof e.type}`}))}function ae(t,r,i){return b.unwrap(p.gen(function*(){let o=++r.current,s=yield*W(t.of),l=b.empty;return s.forEach((n,o)=>{let s=++r.current;l=l.pipe(b.concat(b.make(`<!--${a(s)}-->`)),b.concat(J(t.render(n,o),r,i)),b.concat(b.make(`<!--${e(s)}-->`)))}),b.make(`<!--${n(o)}-->`).pipe(b.concat(l),b.concat(b.make(`<!--${c(o)}-->`)))}))}function Y(e,t,n){let r=`children`in e?e.children:void 0;if(r==null)return b.empty;let i=Array.isArray(r)?r:[r];return b.flatMap(b.fromIterable(i),e=>J(e,t,n))}const X=e=>G(e,null),oe=e=>b.unwrap(p.gen(function*(){let t=yield*g.unbounded(),n=yield*_.make(0),r=G(e,{patchQueue:t,pendingCount:n,idCounter:{current:0},scope:yield*p.scope,failureCollector:null}).pipe(b.ensuring(_.get(n).pipe(p.flatMap(e=>e===0?g.offer(t,h.none()).pipe(p.asVoid):p.void))));return b.concat(r,Z(t))})),Z=e=>b.fromQueue(e).pipe(b.takeWhile(h.isSome),b.map(e=>e.value)),Q=(e,t)=>p.gen(function*(){let n=yield*g.unbounded();yield*y.addFinalizer(t,p.asVoid(g.offer(n,h.none())));let r=yield*_.make(0);return{mainStream:J(e,{current:0},{patchQueue:n,pendingCount:r,idCounter:{current:0},scope:t,failureCollector:yield*_.make(h.none())}).pipe(b.ensuring(_.get(r).pipe(p.flatMap(e=>e===0?g.offer(n,h.none()).pipe(p.asVoid):p.void)))),patches:Z(n)}}),$=e=>b.unwrap(p.gen(function*(){let t=yield*p.scope,{mainStream:n,patches:r}=yield*Q(e,t);return b.concat(n,r)})),se=e=>X(e).pipe(b.mkString),ce=e=>$(e).pipe(b.mkString),le=e=>p.gen(function*(){let t=yield*p.scope,n=yield*y.fork(t,`sequential`),{mainStream:r,patches:i}=yield*Q(e,n);return{shell:yield*b.mkString(r).pipe(p.onError(e=>y.close(n,m.failCause(e)))),patches:i}});export{z as SuspenseFailureHandlerTag,le as renderToHydratableShell,oe as renderToStream,$ as renderToStreamHydratable,se as renderToString,ce as renderToStringHydratable};
@@ -0,0 +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.Service()(`BoundaryContext`){},s=class extends e.Service()(`SuspenseContext`){},c=class extends e.Service()(`RenderContext`){};function l(e){return` stream-start-${e} `}function u(e){return` stream-end-${e} `}function d(e){return` suspense-start-${e} `}function f(e){return` suspense-end-${e} `}const p=`data-weft-suspense-failure`,m=/^ suspense-(start|end)-(\d+) $/;function h(e){let t=m.exec(e.data);return t===null?null:{kind:t[1],id:Number.parseInt(t[2],10)}}function g(e){return` boundary-start-${e} `}function _(e){return` boundary-end-${e} `}const v=/^ stream-(start|end)-(\d+) $/;function y(e){let t=v.exec(e.data);return t===null?null:{kind:t[1],id:Number.parseInt(t[2],10)}}function b(e){return` list-item-start-${e} `}function x(e){return` list-item-end-${e} `}const S=/^ list-item-(start|end)-(\d+) $/;function C(e){let t=S.exec(e.data);return t===null?null:{kind:t[1],id:Number.parseInt(t[2],10)}}function w(e){if(e.length<=2||!e.startsWith(`on`))return!1;let t=e[2];return t!==void 0&&t>=`a`&&t<=`z`}export{i as _,x as a,n as b,y as c,l as d,f,c as g,a as h,w as i,h as l,o as m,_ as n,b as o,d as p,g as r,C as s,p as t,u,r as v,s as y};
@@ -2,18 +2,20 @@
2
2
  title: Boundaries and Suspense
3
3
  order: 4
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.
5
+ description: How Weft models failure, async, and server data as boundary nodes in the same tree. Covers failure-catch variants, Boundary.suspend, and Boundary.rpc, and how their E/R channels behave.
6
6
  ---
7
7
 
8
8
  # Boundaries and Suspense
9
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.
10
+ A **boundary** is a node that intercepts something flowing through the tree: an error, a pending async child, or a server-resolved value. It decides what the DOM shows in its place.
11
+
12
+ A boundary is itself a `Node<E, R>` ([nodes are Effects](https://weftui.dev/docs/explanation/rendering-model)), so 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
13
 
12
14
  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
15
 
14
16
  ## Failure boundaries
15
17
 
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.
18
+ 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
19
 
18
20
  ```typescript
19
21
  import { Boundary, h } from "@weftui/core";
@@ -32,19 +34,25 @@ There are six failure-catch variants, mirroring Effect's own error operators so
32
34
  | `catchTag` / `catchTags` | one / several tagged errors by `_tag` |
33
35
  | `catchFilter` / `catchIf` | a selected subset, by `Filter` / predicate |
34
36
 
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.
37
+ The channel algebra is the whole reason they exist. `catchTag("Foo", …)` removes `Foo` from the children's `E` and adds whatever the fallback needs. The type of the boundary node therefore reflects exactly which failures are still live and which were handled.
38
+
39
+ 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
40
 
37
41
  ### Post-mount failures with no enclosing boundary
38
42
 
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.
43
+ 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. It can still fail later: a `Stream` backing a `Boundary.rpc` resource might raise `RouterNotFound` after a client-side navigation. If a `BoundaryContext` encloses the region, the failure routes to it exactly as above, and the boundary's fallback swaps in.
44
+
45
+ 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 a watcher fiber (forked into the same scope alongside the subscription itself) observes its exit directly.
46
+
47
+ When that exit is a failure whose cause is not interruption-only, Weft reports it explicitly via `Effect.logError(exit.cause)`. The log is annotated with `weft.region` to identify 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, at the `"Error"` level. Interruption (the ordinary case of unmount tearing down the region's scope) is never reported; only genuine failures are.
40
48
 
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 a watcher fiber forked into the same scope alongside the subscription itself — observes its exit directly. When that exit is a failure whose cause is not interruption-only, Weft reports it explicitly via `Effect.logError(exit.cause)`, annotated with `weft.region` to identify 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, at the `"Error"` level. Interruption the ordinary case of unmount tearing down the region's scope — is never reported; only genuine failures are.
49
+ This is deliberate. Rather than leave the failure to whatever the Effect runtime would do with an unobserved fiber exit, Weft observes and logs it itself. Visibility is therefore controlled by the same knobs any Effect program uses: `References.MinimumLogLevel` (provided via `Effect.provideService`) to filter it, or a custom `Logger` to route it elsewhere.
42
50
 
43
- This is deliberate: rather than leaving the failure to whatever the Effect runtime would otherwise do with an unobserved fiber exit, Weft observes and logs it itself, so visibility is controlled by the same knobs any Effect program uses — `References.MinimumLogLevel` (provided via `Effect.provideService`) to filter it, or a custom `Logger` to route it elsewhere. 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.
51
+ 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
52
 
45
53
  ## Suspense boundaries
46
54
 
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.
55
+ `Boundary.suspend` wraps async children and shows a `fallback` until **all** of them have emitted their first value. Then it swaps atomically: either everything is visible or nothing is. This prevents partial flicker when sibling async regions resolve at different times.
48
56
 
49
57
  ```typescript
50
58
  import { Boundary, h } from "@weftui/core";
@@ -55,15 +63,22 @@ Boundary.suspend({ fallback: h.div({ class: "spinner" }, "Loading…") }, [
55
63
  ]);
56
64
  ```
57
65
 
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_.
66
+ 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
67
 
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.
68
+ 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
69
 
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).
70
+ > **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
71
 
64
72
  ## The rpc boundary
65
73
 
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).
74
+ `Boundary.rpc` is the server-data boundary. It:
75
+
76
+ - resolves one `Rpc` on the server
77
+ - serializes the result into the HTML
78
+ - replays it on the client during `hydrate` (no second request, no flash)
79
+ - keeps the region live for `refetch`
80
+
81
+ 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**. Instead of a children array, it takes a `render` function that receives a reactive [`Resource`](https://weftui.dev/docs/reference/core#resourcea).
67
82
 
68
83
  ```typescript
69
84
  import { Boundary, h } from "@weftui/core";
@@ -77,15 +92,19 @@ Boundary.rpc(
77
92
  );
78
93
  ```
79
94
 
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).
95
+ 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.
96
+
97
+ 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
98
 
82
99
  ## One tree, three interceptors
83
100
 
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.
101
+ 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 has channel behavior you can read off its type.
102
+
103
+ That is why they nest freely. A `Boundary.catchTag` can wrap a `Boundary.rpc` to catch its typed failure. A `Boundary.suspend` can wrap async siblings that themselves contain rpc boundaries.
85
104
 
86
105
  ## See also
87
106
 
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
107
+ - [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model): why a boundary is just a node in a static tree
108
+ - [`Boundary` API reference](https://weftui.dev/docs/reference/core#boundary-namespace): every variant's signature and channel algebra
109
+ - [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc): the full `Boundary.rpc` walkthrough and its four lifecycles
110
+ - [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server): how suspense and rpc boundaries stream and hydrate
@@ -7,7 +7,9 @@ description: How h, h.fragment, and Component.gen / Component.make work; why Nod
7
7
 
8
8
  # The Combinator API
9
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.
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.
11
+
12
+ 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
13
 
12
14
  ## Nodes are Effects
13
15
 
@@ -23,13 +25,13 @@ Nodes are first-class Effects. Everything in the Effect ecosystem works on them
23
25
  import { h } from "@weftui/core";
24
26
  import { Effect } from "effect";
25
27
 
26
- // yield* in Effect.gen R propagates into the generator's context
28
+ // yield* in Effect.gen: R propagates into the generator's context
27
29
  const node = yield * h.div({ class: "container" }, "Hello");
28
30
 
29
- // pipe chain Effect operators directly
31
+ // pipe: chain Effect operators directly
30
32
  const provided = pipe(h.div(userStream), Effect.provide(UserServiceLive));
31
33
 
32
- // Effect.flatMap sequence node creation with async logic
34
+ // Effect.flatMap: sequence node creation with async logic
33
35
  const card = pipe(
34
36
  fetchCard(id),
35
37
  Effect.flatMap((data) => h.div({ class: "card" }, data.title)),
@@ -78,7 +80,7 @@ Reactive prop values (any `Stream`, `Effect`, or `Subscribable`) contribute thei
78
80
  ```typescript
79
81
  declare const colorStream: Stream.Stream<string, never, ThemeService>;
80
82
 
81
- // Node<never, ThemeService> R comes from the stream prop
83
+ // Node<never, ThemeService>: R comes from the stream prop
82
84
  const box = h.div({ style: { color: colorStream } }, "Hello");
83
85
  ```
84
86
 
@@ -130,7 +132,7 @@ declare const labelStream: Stream.Stream<string, never, I18nService>;
130
132
  const btn = Button({ label: labelStream });
131
133
  ```
132
134
 
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.
135
+ 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
136
 
135
137
  Without `Component`, a plain function's return type is fixed at definition time and does not reflect the caller's reactive prop types.
136
138
 
@@ -149,16 +151,16 @@ Boundary.suspend({ fallback: h.div({ class: "spinner" }, "Loading...") }, [
149
151
  ]);
150
152
  ```
151
153
 
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.
154
+ 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
155
 
154
156
  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
157
 
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`.
158
+ `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
159
 
158
160
  ## See also
159
161
 
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
162
+ - [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model): why a `Node` is an `Effect` and how the tree renders
163
+ - [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives): the `Source` vocabulary that reactive props and children accept
164
+ - [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense): the boundary combinators as tree nodes
165
+ - [Author Components](https://weftui.dev/docs/how-to/author-components): `Component.gen` / `Component.make` in practice
164
166
  - [`@weftui/core` reference](https://weftui.dev/docs/reference/core)