@weftui/core 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.
- package/README.md +11 -10
- package/dist/{index-DMNuAQXj.d.ts → index-4cTlhojA.d.ts} +47 -35
- package/dist/index.d.ts +77 -135
- package/dist/types/index.d.ts +1 -1
- package/docs/explanation/boundaries-and-suspense.md +37 -18
- package/docs/explanation/combinator-api.md +14 -12
- package/docs/explanation/reactive-primitives.md +15 -15
- package/docs/explanation/rendering-model.md +20 -18
- package/docs/explanation/services-and-context.md +39 -29
- package/docs/how-to/add-routing.md +76 -52
- package/docs/how-to/author-components.md +46 -32
- package/docs/how-to/compose-behavior-and-markup.md +144 -0
- package/docs/how-to/handle-forms.md +6 -6
- package/docs/how-to/load-async-data.md +15 -13
- package/docs/how-to/load-data-with-rpc.md +34 -30
- package/docs/how-to/provide-services.md +54 -74
- package/docs/how-to/render-keyed-lists.md +10 -8
- package/docs/how-to/render-on-the-server.md +19 -14
- package/docs/how-to/show-navigation-progress.md +10 -8
- package/docs/how-to/split-routes-lazily.md +16 -14
- package/docs/how-to/style-reactively.md +13 -13
- package/docs/how-to/use-element-refs.md +10 -8
- package/docs/index.md +20 -18
- package/docs/reference/core.md +44 -40
- package/docs/reference/dom.md +395 -58
- package/docs/reference/router.md +71 -49
- package/docs/tutorial/01-your-first-app.md +10 -11
- package/docs/tutorial/02-reactivity.md +11 -8
- package/docs/tutorial/03-services-and-async.md +19 -13
- package/docs/tutorial/04-errors-and-server.md +17 -7
- package/package.json +8 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { D as Subscribable, E as Source, O as index_d_exports, T as NoPropValue, a as SVGElements, r as Renderable, s as HTMLElements, t as ElementDescriptor } from "./index-
|
|
1
|
+
import { D as Subscribable, E as Source, O as index_d_exports, T as NoPropValue, a as SVGElements, r as Renderable, s as HTMLElements, t as ElementDescriptor } from "./index-4cTlhojA.js";
|
|
2
2
|
import { Cause, Context, Effect, Filter, Option, Stream } from "effect";
|
|
3
3
|
import { Rpc } from "effect/unstable/rpc";
|
|
4
|
-
|
|
5
4
|
//#region src/combinator/types.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Widens a single prop value type so that Stream/Effect/Subscribable variants
|
|
8
|
-
* accept any E and R
|
|
7
|
+
* accept any E and R, not just `never`. Static values (string, number, etc.)
|
|
9
8
|
* are left unchanged. TypeScript distributes this over union types.
|
|
10
9
|
*/
|
|
11
10
|
type OpenPropSource<T> = T extends Stream.Stream<infer A, any, any> ? Stream.Stream<A, any, any> : T extends Effect.Effect<infer A, any, any> ? Effect.Effect<A, any, any> : T extends Subscribable<infer A, any, any> ? Subscribable<A, any, any> : T;
|
|
12
11
|
/**
|
|
13
12
|
* A node in the combinator tree.
|
|
14
|
-
* IS an Effect
|
|
13
|
+
* IS an Effect: `yield*`, `Effect.gen`, and `pipe` all work natively. Resolves
|
|
15
14
|
* to an {@link ElementDescriptor}.
|
|
16
15
|
*
|
|
17
16
|
* Declared as an interface (rather than a bare alias) so it can merge with the
|
|
@@ -33,19 +32,19 @@ declare namespace Node {
|
|
|
33
32
|
/** Extract the requirement channel `R` from a {@link Node} (a static node ⇒ `never`). */
|
|
34
33
|
type Context<N> = [N] extends [Effect.Effect<any, any, infer R>] ? R : never;
|
|
35
34
|
}
|
|
36
|
-
/** Extract E from a props object
|
|
37
|
-
type PropsE<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, infer E, any> ? E : P[K] extends Effect.Effect<any, infer E, any> ? E : P[K] extends Subscribable<any, infer E, any> ? E : P[K] extends ((...args: any[]) => infer Ret) ? Ret extends Effect.Effect<any, infer E, any> ? E : never : never }[keyof P];
|
|
38
|
-
/** Extract R from a props object
|
|
39
|
-
type PropsR<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, any, infer R> ? R : P[K] extends Effect.Effect<any, any, infer R> ? R : P[K] extends Subscribable<any, any, infer R> ? R : P[K] extends ((...args: any[]) => infer Ret) ? Ret extends Effect.Effect<any, any, infer R> ? R : never : never }[keyof P];
|
|
40
|
-
/** Extract E from a children array
|
|
41
|
-
type ChildrenE<T extends readonly Renderable[]> = [T[number]] extends [never] ? never : { [K in keyof T]: T[K] extends Effect.Effect<any, infer E, any> ? E : T[K] extends Stream.Stream<any, infer E, any> ? E : never }[number];
|
|
42
|
-
/** Extract R from a children array
|
|
43
|
-
type ChildrenR<T extends readonly Renderable[]> = [T[number]] extends [never] ? never : { [K in keyof T]: T[K] extends Effect.Effect<any, any, infer R> ? R : T[K] extends Stream.Stream<any, any, infer R> ? R : never }[number];
|
|
35
|
+
/** Extract E from a props object: Stream/Effect/Subscribable prop values and Effect-returning event handlers contribute their E channel. */
|
|
36
|
+
type PropsE<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, infer E, any> ? E : P[K] extends Effect.Effect<any, infer E, any> ? E : P[K] extends Subscribable<any, infer E, any> ? E : P[K] extends ((...args: any[]) => infer Ret) ? Ret extends Effect.Effect<any, infer E, any> ? E : never : never; }[keyof P];
|
|
37
|
+
/** Extract R from a props object: Stream/Effect/Subscribable prop values and Effect-returning event handlers contribute their R channel. */
|
|
38
|
+
type PropsR<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, any, infer R> ? R : P[K] extends Effect.Effect<any, any, infer R> ? R : P[K] extends Subscribable<any, any, infer R> ? R : P[K] extends ((...args: any[]) => infer Ret) ? Ret extends Effect.Effect<any, any, infer R> ? R : never : never; }[keyof P];
|
|
39
|
+
/** Extract E from a children array: Node (Effect) and Stream children contribute their E. */
|
|
40
|
+
type ChildrenE<T extends readonly Renderable[]> = [T[number]] extends [never] ? never : { [K in keyof T]: T[K] extends Effect.Effect<any, infer E, any> ? E : T[K] extends Stream.Stream<any, infer E, any> ? E : never; }[number];
|
|
41
|
+
/** Extract R from a children array: Node (Effect) and Stream children contribute their R. */
|
|
42
|
+
type ChildrenR<T extends readonly Renderable[]> = [T[number]] extends [never] ? never : { [K in keyof T]: T[K] extends Effect.Effect<any, any, infer R> ? R : T[K] extends Stream.Stream<any, any, infer R> ? R : never; }[number];
|
|
44
43
|
/**
|
|
45
44
|
* Strip `children` from HTML prop types and widen all Source prop types to
|
|
46
|
-
* allow any E/R
|
|
45
|
+
* allow any E/R, so callers can pass `Stream<T, E, R>` with real requirements.
|
|
47
46
|
*/
|
|
48
|
-
type CombinatorialProps<P> = { [K in keyof Omit<P, "children">]: OpenPropSource<Omit<P, "children">[K]
|
|
47
|
+
type CombinatorialProps<P> = { [K in keyof Omit<P, "children">]: OpenPropSource<Omit<P, "children">[K]>; };
|
|
49
48
|
declare namespace boundary_impl_d_exports {
|
|
50
49
|
export { CatchTagE, CatchTagsE, FAILURE_BOUNDARY, FailureProps, Resource, RpcOptions, SERVER_BOUNDARY, SUSPENSE_BOUNDARY, SuspenseProps, catch_ as catch, catchCause, catchFilter, catchIf, catchTag, catchTags, rpc, suspend };
|
|
51
50
|
}
|
|
@@ -77,8 +76,8 @@ type CatchTagsE<C extends readonly Renderable[], Tags extends string> = Exclude<
|
|
|
77
76
|
* variant wraps a subtree and shows a fallback in response to an event.
|
|
78
77
|
*
|
|
79
78
|
* - **Failure boundaries** (`catch`, `catchCause`, `catchTag`,
|
|
80
|
-
* `catchTags`, `catchFilter`, `catchIf`) intercept rendering-path errors
|
|
81
|
-
* construction-time errors and post-mount stream failures
|
|
79
|
+
* `catchTags`, `catchFilter`, `catchIf`) intercept rendering-path errors
|
|
80
|
+
* (construction-time errors and post-mount stream failures), mirroring
|
|
82
81
|
* Effect's `catch*` combinators.
|
|
83
82
|
* - **Suspense boundary** (`suspend`) shows a fallback while async children are
|
|
84
83
|
* pending, then swaps to the resolved children once all have settled.
|
|
@@ -91,7 +90,7 @@ type CatchTagsE<C extends readonly Renderable[], Tags extends string> = Exclude<
|
|
|
91
90
|
* ```ts
|
|
92
91
|
* import { Boundary, h } from "@weftui/core";
|
|
93
92
|
*
|
|
94
|
-
* // Failure boundary wrapping a suspense boundary
|
|
93
|
+
* // Failure boundary wrapping a suspense boundary, the common pairing:
|
|
95
94
|
* Boundary.catch({ fallback: (e) => h.div({}, e.message) }, [
|
|
96
95
|
* Boundary.suspend({ fallback: h.div({}, "Loading…") }, [AsyncCard()]),
|
|
97
96
|
* ])
|
|
@@ -110,7 +109,7 @@ interface FailureProps {
|
|
|
110
109
|
readonly match: (cause: Cause.Cause<unknown>) => Node<unknown, unknown> | null;
|
|
111
110
|
}
|
|
112
111
|
/**
|
|
113
|
-
* Props for the {@link suspend} boundary
|
|
112
|
+
* Props for the {@link suspend} boundary, read by renderers to access
|
|
114
113
|
* `fallback` and `children` from the node descriptor.
|
|
115
114
|
*/
|
|
116
115
|
interface SuspenseProps {
|
|
@@ -142,9 +141,9 @@ declare function catchCause<C extends readonly Renderable[], FE = never, FR = ne
|
|
|
142
141
|
* Catch errors whose `_tag` matches `props.tag`. The matched tag is removed
|
|
143
142
|
* from the output `E`; unmatched errors are re-raised.
|
|
144
143
|
*/
|
|
145
|
-
declare function catchTag<C extends readonly Renderable[], Tag extends
|
|
144
|
+
declare function catchTag<C extends readonly Renderable[], Tag extends ChildrenE<C> extends {
|
|
146
145
|
_tag: string;
|
|
147
|
-
} ? ChildrenE<C>["_tag"] : string
|
|
146
|
+
} ? ChildrenE<C>["_tag"] : string, FE = never, FR = never>(props: {
|
|
148
147
|
readonly tag: Tag;
|
|
149
148
|
readonly fallback: (e: Extract<ChildrenE<C>, {
|
|
150
149
|
_tag: Tag;
|
|
@@ -159,7 +158,7 @@ declare function catchTags<C extends readonly Renderable[], Handlers extends { r
|
|
|
159
158
|
_tag: string;
|
|
160
159
|
} ? ChildrenE<C>["_tag"] : never]?: (e: Extract<ChildrenE<C>, {
|
|
161
160
|
_tag: Tag;
|
|
162
|
-
}>) => Node<any, any
|
|
161
|
+
}>) => Node<any, any>; }>(handlers: Handlers, children: C): Node<CatchTagsE<C, keyof Handlers & string> | { [K in keyof Handlers]: Handlers[K] extends ((e: any) => Node<infer E, any>) ? E : never; }[keyof Handlers], ChildrenR<C> | { [K in keyof Handlers]: Handlers[K] extends ((e: any) => Node<any, infer R>) ? R : never; }[keyof Handlers]>;
|
|
163
162
|
/**
|
|
164
163
|
* Conditionally catch using a `Filter`. The `filter` runs on each typed
|
|
165
164
|
* failure: a `Result.succeed` (pass) recovers via `fallback`, receiving the
|
|
@@ -186,7 +185,7 @@ declare function catchTags<C extends readonly Renderable[], Handlers extends { r
|
|
|
186
185
|
*/
|
|
187
186
|
declare function catchFilter<C extends readonly Renderable[], EB, X, FE = never, FR = never>(filter: Filter.Filter<ChildrenE<C>, EB, X>, fallback: (matched: EB) => Node<FE, FR>, children: C): Node<X | FE, ChildrenR<C> | FR>;
|
|
188
187
|
/**
|
|
189
|
-
* Conditionally catch
|
|
188
|
+
* Conditionally catch: a predicate gates the fallback. If the predicate
|
|
190
189
|
* returns `false`, the error is re-raised. The children's `E` is preserved
|
|
191
190
|
* in the output since the boundary may not handle any given error.
|
|
192
191
|
*/
|
|
@@ -197,19 +196,10 @@ declare function catchIf<C extends readonly Renderable[], FE = never, FR = never
|
|
|
197
196
|
/**
|
|
198
197
|
* Creates a suspense boundary node.
|
|
199
198
|
*
|
|
200
|
-
* Shows `fallback` while async children are pending (have not yet emitted
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* The renderer (`@weftui/dom`) identifies the boundary via its
|
|
205
|
-
* {@link SUSPENSE_BOUNDARY} type tag.
|
|
206
|
-
*
|
|
207
|
-
* @example
|
|
208
|
-
* ```ts
|
|
209
|
-
* import { Boundary, h } from "@weftui/core";
|
|
210
|
-
*
|
|
211
|
-
* Boundary.suspend({ fallback: h.div({}, "Loading…") }, [AsyncCard(), AsyncSidebar()])
|
|
212
|
-
* ```
|
|
199
|
+
* Shows `fallback` while async children are pending (have not yet emitted their
|
|
200
|
+
* first value), then atomically swaps to the resolved children once **all**
|
|
201
|
+
* pending children have settled. The renderer (`@weftui/dom`) identifies the
|
|
202
|
+
* boundary via its {@link SUSPENSE_BOUNDARY} type tag.
|
|
213
203
|
*/
|
|
214
204
|
declare function suspend<C extends readonly Renderable[]>(props: SuspenseProps, children: C): Node<ChildrenE<C>, ChildrenR<C>>;
|
|
215
205
|
/**
|
|
@@ -221,7 +211,7 @@ declare function suspend<C extends readonly Renderable[]>(props: SuspenseProps,
|
|
|
221
211
|
*
|
|
222
212
|
* On the **server** and on the **first client paint after hydrate** `value`
|
|
223
213
|
* emits the SSR `data` first (await-first), so SSR HTML and the adopted DOM are
|
|
224
|
-
* byte-identical
|
|
214
|
+
* byte-identical, with no fallback flash.
|
|
225
215
|
*
|
|
226
216
|
* @typeParam A - The loaded data shape.
|
|
227
217
|
*/
|
|
@@ -241,7 +231,7 @@ interface Resource<A> {
|
|
|
241
231
|
readonly pending: Subscribable<boolean>;
|
|
242
232
|
/**
|
|
243
233
|
* `Some` with the last refetch error, else `None`. A failed refetch leaves
|
|
244
|
-
* the previous `value` intact (stale-on-error)
|
|
234
|
+
* the previous `value` intact (stale-on-error). It does **not** unmount the
|
|
245
235
|
* subtree or raise into an enclosing failure `Boundary`.
|
|
246
236
|
*/
|
|
247
237
|
readonly error: Subscribable<Option.Option<unknown>>;
|
|
@@ -263,7 +253,7 @@ interface RpcOptions {
|
|
|
263
253
|
*
|
|
264
254
|
* The boundary is a thin consumer of one `Rpc` from the application's merged
|
|
265
255
|
* `RpcGroup`. Its data source is the ambient {@link AppRpcClient}: there is no
|
|
266
|
-
* co-located `load`, no `provide`, no per-boundary `id`/registry
|
|
256
|
+
* co-located `load`, no `provide`, no per-boundary `id`/registry. The rpc
|
|
267
257
|
* **tag** is the stable identity and the rpc **payload schema** is the typed
|
|
268
258
|
* input. The handler lives in the server-only rpc Layer, so nothing here needs a
|
|
269
259
|
* bundler prune.
|
|
@@ -273,7 +263,7 @@ interface RpcOptions {
|
|
|
273
263
|
* the rpc's `successSchema` inline as a `<script type="application/json">`
|
|
274
264
|
* payload, and renders `render(seededResource)` to HTML in place.
|
|
275
265
|
* - **Hydrate**: reads the inline payload at the cursor, decodes via
|
|
276
|
-
* `successSchema`, seeds the {@link Resource}, and adopts the DOM
|
|
266
|
+
* `successSchema`, seeds the {@link Resource}, and adopts the DOM, replaying
|
|
277
267
|
* the server result, never re-calling the rpc.
|
|
278
268
|
* - **Refetch**: `Resource.refetch` calls `AppRpcClient.call(tag, payload())`
|
|
279
269
|
* over the network and patches the subtree in place (stale-on-error).
|
|
@@ -282,7 +272,7 @@ interface RpcOptions {
|
|
|
282
272
|
* `render(resource)` once it resolves.
|
|
283
273
|
*
|
|
284
274
|
* `payload` is a thunk so a fresh payload is produced per call (SSR, refetch,
|
|
285
|
-
* mount)
|
|
275
|
+
* mount). Its return type is the rpc's decoded payload. The renderer identifies
|
|
286
276
|
* the boundary via its {@link SERVER_BOUNDARY} type tag.
|
|
287
277
|
*
|
|
288
278
|
* @example
|
|
@@ -309,9 +299,9 @@ declare function rpc<R extends Rpc.Any, C extends Node<any, any>>(rpc: R, payloa
|
|
|
309
299
|
/**
|
|
310
300
|
* Ambient, package-neutral seam for resolving a {@link Boundary.rpc} boundary's
|
|
311
301
|
* data through the application's merged `RpcGroup`. The DOM renderer
|
|
312
|
-
* (`@weftui/dom`) must resolve a boundary
|
|
313
|
-
*
|
|
314
|
-
*
|
|
302
|
+
* (`@weftui/dom`) must resolve a boundary **without** importing
|
|
303
|
+
* `effect/unstable/rpc` or `@weftui/router`, on the server (SSR), during a
|
|
304
|
+
* client refetch, and on a client-first SPA mount. So the rpc caller is injected as a
|
|
315
305
|
* service: `@weftui/router` provides it (a network `RpcClient` on the browser,
|
|
316
306
|
* an in-process client over the handler layer on the server), and the renderer
|
|
317
307
|
* reads it from ambient context, treating `Option.none` (no router/rpc present)
|
|
@@ -335,8 +325,8 @@ interface AppRpcClient {
|
|
|
335
325
|
}
|
|
336
326
|
declare const AppRpcClientTag_base: Context.ServiceClass<AppRpcClientTag, "@weftui/core/AppRpcClient", AppRpcClient>;
|
|
337
327
|
/**
|
|
338
|
-
* Context tag for the {@link AppRpcClient} seam. Provided by `@weftui/router
|
|
339
|
-
*
|
|
328
|
+
* Context tag for the {@link AppRpcClient} seam. Provided by `@weftui/router`:
|
|
329
|
+
* a network client (`RouterLive`, POST `/_eui/rpc`) on the client, an
|
|
340
330
|
* in-process client over the handler layer (`RouterServer`) on the server. Absent
|
|
341
331
|
* in a router-less mount, where a {@link Boundary.rpc} resolves to a descriptive
|
|
342
332
|
* "needs router/rpc" error.
|
|
@@ -346,7 +336,7 @@ declare class AppRpcClientTag extends AppRpcClientTag_base {}
|
|
|
346
336
|
//#region src/server/brand.d.ts
|
|
347
337
|
/**
|
|
348
338
|
* Type-level marker stamped onto the identifier of a {@link ServerTag}. It never
|
|
349
|
-
* exists at runtime
|
|
339
|
+
* exists at runtime (only its `typeof` is referenced by {@link ServerOnly}), so
|
|
350
340
|
* the brand is purely a compile-time discriminator for {@link AssertNoServerOnly}.
|
|
351
341
|
*/
|
|
352
342
|
declare const ServerOnlyTypeId: unique symbol;
|
|
@@ -399,10 +389,9 @@ type AssertNoServerOnly<R> = [Extract<R, ServerOnly>] extends [never] ? R : Serv
|
|
|
399
389
|
*/
|
|
400
390
|
declare function isStream(value: unknown): value is Stream.Stream<unknown, any, any>;
|
|
401
391
|
/**
|
|
402
|
-
* Normalizes a static value, `Effect`, or `Stream` into a `Stream
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* unchanged.
|
|
392
|
+
* Normalizes a static value, `Effect`, or `Stream` into a `Stream`, the weft
|
|
393
|
+
* equivalent of Vue's `unref`. Static values become a single-element stream,
|
|
394
|
+
* Effects become a one-shot stream, and existing Streams pass through unchanged.
|
|
406
395
|
*/
|
|
407
396
|
declare function toStream<A>(value: A | Effect.Effect<A> | Stream.Stream<A>): Stream.Stream<A>;
|
|
408
397
|
//#endregion
|
|
@@ -410,16 +399,15 @@ declare function toStream<A>(value: A | Effect.Effect<A> | Stream.Stream<A>): St
|
|
|
410
399
|
/** Augmentable interface for user-defined custom element tags and props. */
|
|
411
400
|
interface CustomElements {}
|
|
412
401
|
/**
|
|
413
|
-
* Callable type for an element builder (one per tag
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
* `E`/`R` channels on the returned {@link Node}:
|
|
402
|
+
* Callable type for an element builder (one per tag: `h.div`, `h.span`, …).
|
|
403
|
+
* Every call shape preserves the caller's prop and child `E`/`R` channels on the
|
|
404
|
+
* returned {@link Node}:
|
|
417
405
|
*
|
|
418
|
-
* - `el(props, children)
|
|
419
|
-
* - `el(props, child)
|
|
420
|
-
* - `el(props)
|
|
421
|
-
* - `el(children)
|
|
422
|
-
* - `el()
|
|
406
|
+
* - `el(props, children)`: props plus an array of children.
|
|
407
|
+
* - `el(props, child)`: props plus a single `string | number` child.
|
|
408
|
+
* - `el(props)`: props only, no children.
|
|
409
|
+
* - `el(children)`: children only, no props.
|
|
410
|
+
* - `el()`: no arguments, yielding a `Node<never, never>`.
|
|
423
411
|
*/
|
|
424
412
|
interface ElementFn<Props> {
|
|
425
413
|
<P extends Props, const C extends readonly Renderable[]>(props: P, children: C): Node<PropsE<P> | ChildrenE<C>, PropsR<P> | ChildrenR<C>>;
|
|
@@ -434,31 +422,20 @@ type DataAttributes = {
|
|
|
434
422
|
};
|
|
435
423
|
type H = {
|
|
436
424
|
/**
|
|
437
|
-
* Builds a fragment node
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* @example
|
|
442
|
-
* ```ts
|
|
443
|
-
* h.fragment([h.span({}, "left"), h.span({}, "right")]);
|
|
444
|
-
* ```
|
|
425
|
+
* Builds a fragment node whose children render inline, with no wrapping
|
|
426
|
+
* element. Equivalent to `<>…</>` in JSX. `E`/`R` from the children accumulate
|
|
427
|
+
* on the returned {@link Node}.
|
|
445
428
|
*/
|
|
446
429
|
fragment<const C extends readonly Renderable[]>(children: C): Node<ChildrenE<C>, ChildrenR<C>>;
|
|
447
|
-
} & { [K in keyof HTMLElements]: ElementFn<CombinatorialProps<HTMLElements[K] & DataAttributes
|
|
448
|
-
/**
|
|
449
|
-
* Builds an `h` proxy backed by the given cache. Each tag access lazily creates
|
|
450
|
-
* an `ElementFn` and memoizes it in the cache, so repeat accesses return the
|
|
451
|
-
* same function reference. Exposed primarily to allow tests to observe an
|
|
452
|
-
* isolated cache; production code should use the module-level `h`.
|
|
453
|
-
*/
|
|
430
|
+
} & { [K in keyof HTMLElements]: ElementFn<CombinatorialProps<HTMLElements[K] & DataAttributes>>; } & { [K in keyof SVGElements]: ElementFn<CombinatorialProps<SVGElements[K] & DataAttributes>>; } & { [K in keyof CustomElements]: ElementFn<CustomElements[K] & DataAttributes>; };
|
|
454
431
|
declare const h: H;
|
|
455
432
|
//#endregion
|
|
456
433
|
//#region src/combinator/descriptor.d.ts
|
|
457
434
|
/**
|
|
458
435
|
* Builds a static-markup {@link Node} for a known {@link ElementDescriptor}.
|
|
459
436
|
*
|
|
460
|
-
* The result is a normal `Effect.succeed(descriptor)`
|
|
461
|
-
* inside `Component.gen` and indistinguishable to Effect
|
|
437
|
+
* The result is a normal `Effect.succeed(descriptor)` (fully `yield*`-able
|
|
438
|
+
* inside `Component.gen` and indistinguishable to Effect), but additionally
|
|
462
439
|
* carries the descriptor on a non-enumerable property. Renderers detect this via
|
|
463
440
|
* {@link getElementDescriptor} and render the descriptor directly, with no
|
|
464
441
|
* `runSync` probe (the reason `h.*`, `h.fragment`, and `Boundary.*` never need to
|
|
@@ -470,8 +447,8 @@ declare function elementNode<E = never, R = never>(descriptor: ElementDescriptor
|
|
|
470
447
|
* built with {@link elementNode}, or `undefined` if `node` is anything else
|
|
471
448
|
* (a primitive, an iterable, a `Stream`, or a reactive `Effect`).
|
|
472
449
|
*
|
|
473
|
-
* Renderers call this to take the static fast path
|
|
474
|
-
* directly
|
|
450
|
+
* Renderers call this to take the static fast path (rendering the descriptor
|
|
451
|
+
* directly) before falling back to running genuinely reactive Effects.
|
|
475
452
|
*/
|
|
476
453
|
declare function getElementDescriptor(node: unknown): ElementDescriptor | undefined;
|
|
477
454
|
//#endregion
|
|
@@ -486,7 +463,7 @@ declare const FRAGMENT: unique symbol;
|
|
|
486
463
|
* `FRAGMENT` and the `Boundary` symbols are detected.
|
|
487
464
|
*/
|
|
488
465
|
declare const LIST: unique symbol;
|
|
489
|
-
/** Element type carried by a list source
|
|
466
|
+
/** Element type carried by a list source: the element type of the emitted `Iterable`. */
|
|
490
467
|
type ItemOf<S> = Source.Success<S> extends Iterable<infer T> ? T : never;
|
|
491
468
|
/**
|
|
492
469
|
* Keyed-list combinator namespace. The opt-in alternative to wholesale child
|
|
@@ -514,30 +491,20 @@ declare namespace List {
|
|
|
514
491
|
readonly by?: (item: ItemOf<S>, index: number) => K;
|
|
515
492
|
}
|
|
516
493
|
/**
|
|
517
|
-
* Declares a keyed reactive list region.
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
* returned node's `E`/`R` are the union of the source channels and the
|
|
522
|
-
* channels of the node `render` returns.
|
|
523
|
-
*
|
|
524
|
-
* @example
|
|
525
|
-
* ```ts
|
|
526
|
-
* List.each(
|
|
527
|
-
* { of: peopleStream, by: (p) => p.id },
|
|
528
|
-
* (person) => h.li({}, person.name),
|
|
529
|
-
* );
|
|
530
|
-
* ```
|
|
494
|
+
* Declares a keyed reactive list region. `render` runs **once per key**; a
|
|
495
|
+
* persisted key keeps its DOM nodes and its running subscription fibers across
|
|
496
|
+
* re-emits (it is never re-invoked). The returned node's `E`/`R` are the union
|
|
497
|
+
* of the source channels and the channels of the node `render` returns.
|
|
531
498
|
*/
|
|
532
499
|
function each<S extends Source.Source<Iterable<any>, any, any>, CE = never, CR = never, K = ItemOf<S>>(options: Options<S, K>, render: (item: ItemOf<S>, index: number) => Node<CE, CR>): Node<Source.Error<S> | CE, Source.Context<S> | CR>;
|
|
533
500
|
/**
|
|
534
|
-
* Extract the error channel `E` from a list {@link Node}
|
|
501
|
+
* Extract the error channel `E` from a list {@link Node}, re-exported from
|
|
535
502
|
* {@link Node.Error}, the canonical accessor (a `Node`'s success channel is
|
|
536
503
|
* fixed to `ElementDescriptor`, so only `Error`/`Context` are exposed here).
|
|
537
504
|
*/
|
|
538
505
|
type Error<N> = Node.Error<N>;
|
|
539
506
|
/**
|
|
540
|
-
* Extract the requirement channel `R` from a list {@link Node}
|
|
507
|
+
* Extract the requirement channel `R` from a list {@link Node}, re-exported
|
|
541
508
|
* from {@link Node.Context}, the canonical accessor.
|
|
542
509
|
*/
|
|
543
510
|
type Context<N> = Node.Context<N>;
|
|
@@ -547,45 +514,39 @@ declare namespace List {
|
|
|
547
514
|
/**
|
|
548
515
|
* Factories for building custom components whose returned `Node`s carry the
|
|
549
516
|
* caller's prop `E`/`R` channels and the component's own internal `E`/`R`.
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
* - `Component.make` — body is a plain function returning any `Effect`.
|
|
555
|
-
*
|
|
556
|
-
* Both accept an optional second `children` argument which may be either an
|
|
557
|
-
* array of {@link Renderable} or a function `(input) => readonly Renderable[]` (the
|
|
558
|
-
* render-prop / function-children pattern).
|
|
517
|
+
* `Component.gen` takes a generator body (use `yield*` like `Effect.gen`);
|
|
518
|
+
* `Component.make` takes a plain function returning any `Effect`. Both accept an
|
|
519
|
+
* optional second `children` argument: an array of {@link Renderable}, or a
|
|
520
|
+
* function `(input) => readonly Renderable[]` (the render-prop pattern).
|
|
559
521
|
*/
|
|
560
522
|
declare namespace Component {
|
|
561
523
|
/**
|
|
562
524
|
* Shape of the optional `children` argument to a {@link Component}.
|
|
563
525
|
*
|
|
564
|
-
* - `readonly Renderable[]
|
|
565
|
-
* - `(input: Input) => readonly Renderable[]
|
|
526
|
+
* - `readonly Renderable[]`: a flat list of children, the common case.
|
|
527
|
+
* - `(input: Input) => readonly Renderable[]`: function-children. The component
|
|
566
528
|
* supplies `input` (some scoped value) and the caller returns the children
|
|
567
529
|
* array. Useful for render-prop / slot patterns.
|
|
568
530
|
*/
|
|
569
531
|
type Children<Input = never> = readonly Renderable[] | ((input: Input) => readonly Renderable[]);
|
|
570
532
|
/**
|
|
571
|
-
* Extract the error channel `E` from the {@link Node} a component produces
|
|
533
|
+
* Extract the error channel `E` from the {@link Node} a component produces,
|
|
572
534
|
* re-exported from {@link Node.Error}, the canonical accessor. Apply to a
|
|
573
535
|
* component's return type, e.g. `Component.Error<ReturnType<typeof MyComponent>>`.
|
|
574
536
|
*/
|
|
575
537
|
type Error<N> = Node.Error<N>;
|
|
576
538
|
/**
|
|
577
539
|
* Extract the requirement channel `R` from the {@link Node} a component
|
|
578
|
-
* produces
|
|
540
|
+
* produces, re-exported from {@link Node.Context}, the canonical accessor.
|
|
579
541
|
*/
|
|
580
542
|
type Context<N> = Node.Context<N>;
|
|
581
543
|
/**
|
|
582
544
|
* The callable shape returned by {@link gen} and {@link make}. Generic over
|
|
583
545
|
* the caller's specific `GenP`/`GenC` so reactive prop values and reactive
|
|
584
546
|
* children contribute their `E`/`R` to the resulting `Node` at the call site.
|
|
585
|
-
*
|
|
586
547
|
* For function-children, `ChildrenE`/`ChildrenR` are extracted from the
|
|
587
|
-
* function's `ReturnType`
|
|
588
|
-
*
|
|
548
|
+
* function's `ReturnType` (the array the caller would produce), not from the
|
|
549
|
+
* function itself.
|
|
589
550
|
*/
|
|
590
551
|
type Component<P, C extends Children, E, R> = <GenP extends P, GenC extends C>(props: GenP, children?: GenC) => Node<PropsE<GenP> | ChildrenE<GenC extends ((...args: any[]) => any) ? ReturnType<GenC> : GenC> | E, PropsR<GenP> | ChildrenR<GenC extends ((...args: any[]) => any) ? ReturnType<GenC> : GenC> | R>;
|
|
591
552
|
/**
|
|
@@ -619,30 +580,11 @@ declare namespace Component {
|
|
|
619
580
|
function gen<Eff extends Effect.Effect<any, any, any>, BaseProps = Record<string, never>, C extends Children = readonly Renderable[]>(f: (props: BaseProps, children: C) => Generator<Eff, ElementDescriptor, never>): Component.Component<BaseProps, C, Eff extends Effect.Effect<any, infer E, any> ? E : never, Eff extends Effect.Effect<any, any, infer R> ? R : never>;
|
|
620
581
|
/**
|
|
621
582
|
* Defines a component whose body is a plain function returning any `Effect`
|
|
622
|
-
* (typically a {@link Node}). Use when the implementation is a one-liner or
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
* call site, and function-children are supported via the optional second
|
|
628
|
-
* argument.
|
|
629
|
-
*
|
|
630
|
-
* @example
|
|
631
|
-
* ```ts
|
|
632
|
-
* const Avatar = Component.make((props: { src: string }) =>
|
|
633
|
-
* h.img({ src: props.src, alt: "" }),
|
|
634
|
-
* );
|
|
635
|
-
* ```
|
|
636
|
-
*
|
|
637
|
-
* @example With function-children
|
|
638
|
-
* ```ts
|
|
639
|
-
* const List = Component.make(
|
|
640
|
-
* (
|
|
641
|
-
* props: { items: readonly string[] },
|
|
642
|
-
* children: (item: string) => readonly Renderable[],
|
|
643
|
-
* ) => h.ul({}, props.items.flatMap(children)),
|
|
644
|
-
* );
|
|
645
|
-
* ```
|
|
583
|
+
* (typically a {@link Node}). Use when the implementation is a one-liner or a
|
|
584
|
+
* pipe composition, with no generator overhead. Same `E`/`R` propagation
|
|
585
|
+
* semantics as {@link gen}: internal `E`/`R` come from the returned effect,
|
|
586
|
+
* caller props and children contribute at the call site, and function-children
|
|
587
|
+
* are supported via the optional second argument.
|
|
646
588
|
*/
|
|
647
589
|
function make<Eff extends Effect.Effect<any, any, any>, BaseProps = Record<string, never>, C extends Children = readonly Renderable[]>(f: (props: BaseProps, children: C) => Eff): Component<BaseProps, C, Eff extends Effect.Effect<any, infer E, any> ? E : never, Eff extends Effect.Effect<any, any, infer R> ? R : never>;
|
|
648
590
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as StyleAttributeValue, S as HTMLAttributeSource, _ as HTMLFormMethod, a as SVGElements, b as HTMLReferrerPolicy, c as HTMLRole, d as EventHandlerFn, f as HTMLAutocapitalize, g as HTMLFormEncType, h as HTMLDir, i as SVGAttributes, l as DOMAttributes, m as HTMLCrossorigin, n as ElementType, o as HTMLAttributes, p as HTMLAutocomplete, r as Renderable, s as HTMLElements, t as ElementDescriptor, u as EventHandler, v as HTMLIframeSandbox, w as StyleProperties, x as AriaAttributes, y as HTMLLinkAs } from "../index-
|
|
1
|
+
import { C as StyleAttributeValue, S as HTMLAttributeSource, _ as HTMLFormMethod, a as SVGElements, b as HTMLReferrerPolicy, c as HTMLRole, d as EventHandlerFn, f as HTMLAutocapitalize, g as HTMLFormEncType, h as HTMLDir, i as SVGAttributes, l as DOMAttributes, m as HTMLCrossorigin, n as ElementType, o as HTMLAttributes, p as HTMLAutocomplete, r as Renderable, s as HTMLElements, t as ElementDescriptor, u as EventHandler, v as HTMLIframeSandbox, w as StyleProperties, x as AriaAttributes, y as HTMLLinkAs } from "../index-4cTlhojA.js";
|
|
2
2
|
export { AriaAttributes, DOMAttributes, ElementDescriptor, ElementType, EventHandler, EventHandlerFn, HTMLAttributeSource, HTMLAttributes, HTMLAutocapitalize, HTMLAutocomplete, HTMLCrossorigin, HTMLDir, HTMLElements, HTMLFormEncType, HTMLFormMethod, HTMLIframeSandbox, HTMLLinkAs, HTMLReferrerPolicy, HTMLRole, Renderable, SVGAttributes, SVGElements, StyleAttributeValue, StyleProperties };
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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)
|
|
89
|
-
- [`Boundary` API reference](https://weftui.dev/docs/reference/core#boundary-namespace)
|
|
90
|
-
- [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc)
|
|
91
|
-
- [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server)
|
|
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
|