@weftui/core 0.28.0 → 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.
@@ -2,7 +2,7 @@
2
2
  title: "@weftui/core"
3
3
  order: 1
4
4
  section: reference
5
- description: Full API surface for @weftui/core element builders, components, sources, streams, and boundaries.
5
+ description: "Full API surface for @weftui/core: element builders, components, sources, streams, and boundaries."
6
6
  ---
7
7
 
8
8
  # @weftui/core API Reference
@@ -52,7 +52,7 @@ Use when a component needs to return multiple sibling elements.
52
52
 
53
53
  ### `Component`
54
54
 
55
- Namespace exposing two factories for reusable components with caller-propagating reactive prop types: `Component.gen` (generator body) and `Component.make` (plain-function body). Both return a callable that is generic over the caller's specific `props`/`children`, so reactive prop values and reactive children contribute their `E`/`R` at the call site.
55
+ Namespace exposing two factories for reusable components with caller-propagating reactive prop types: `Component.gen` (generator body) and `Component.make` (plain-function body). Both return a callable that is generic over the caller's specific `props`/`children`. Reactive prop values and reactive children therefore contribute their `E`/`R` at the call site.
56
56
 
57
57
  ```typescript
58
58
  import { Component } from "@weftui/core";
@@ -69,7 +69,7 @@ Component.make<BaseProps, C>(
69
69
  ): /* same call signature as above */;
70
70
  ```
71
71
 
72
- **`Children`** the optional `children` argument may be either form:
72
+ **`Children`**: the optional `children` argument may be either form:
73
73
 
74
74
  ```typescript
75
75
  type Component.Children<Input = never> =
@@ -79,7 +79,7 @@ type Component.Children<Input = never> =
79
79
 
80
80
  For function-children, `ChildrenE`/`ChildrenR` are extracted from the function's `ReturnType`, not from the function itself. The component's body invokes the function with whatever input it chooses.
81
81
 
82
- **Example `Component.gen`**:
82
+ **Example: `Component.gen`**
83
83
 
84
84
  ```typescript
85
85
  interface TextFieldProps {
@@ -96,7 +96,7 @@ const TextField = Component.gen(function* (props: TextFieldProps) {
96
96
  });
97
97
  ```
98
98
 
99
- **Example `Component.make` with function-children**:
99
+ **Example: `Component.make` with function-children**
100
100
 
101
101
  ```typescript
102
102
  const Labeled = Component.make(
@@ -108,17 +108,17 @@ Labeled({ label: "Name" }, (label) => [h.label(label), h.input()]);
108
108
  ```
109
109
 
110
110
  > For rendering a reactive collection, reach for the built-in [`List.each`](#listeach)
111
- > rather than mapping items by hand it reconciles by key across emissions instead of
111
+ > rather than mapping items by hand. It reconciles by key across emissions instead of
112
112
  > rebuilding the region.
113
113
 
114
114
  ### `Boundary` namespace
115
115
 
116
- Variants for intercepting rendering-path errors in a subtree, plus `Boundary.suspend` for async fallbacks and `Boundary.rpc` for rpc-backed server data. Each returns a descriptor that the renderer processes via the same `{ type, props }` branch. The catch variants share the same call shape props first, children array second.
116
+ Variants for intercepting rendering-path errors in a subtree, plus `Boundary.suspend` for async fallbacks and `Boundary.rpc` for rpc-backed server data. Each returns a descriptor that the renderer processes via the same `{ type, props }` branch. The catch variants share the same call shape: props first, children array second.
117
117
 
118
118
  **What is caught:**
119
119
 
120
- - Construction-time failures the Effect phase of building child nodes
121
- - Post-mount stream failures streams driving children or prop values that fail after mount
120
+ - Construction-time failures: the Effect phase of building child nodes
121
+ - Post-mount stream failures: streams driving children or prop values that fail after mount
122
122
 
123
123
  **What is NOT caught:** event handler errors (they run in detached fibers outside the render path).
124
124
 
@@ -144,7 +144,9 @@ Boundary.suspend(
144
144
 
145
145
  #### `Boundary.rpc`
146
146
 
147
- A universal server/client render boundary backed by one `Rpc` from the app's merged `RpcGroup` ([`effect/unstable/rpc`](https://github.com/Effect-TS/effect)). The rpc **`_tag`** is the boundary's stable identity and its **payload schema** the typed input; the handler lives in the server-only rpc Layer (`group.toLayer(...)`), which the client never imports tree-shaking does the client/server split structurally. Unlike the catch variants it takes a `render` function — not a children array — and that `render` receives a reactive [`Resource`](#resourcea), not a bare value.
147
+ A universal server/client render boundary backed by one `Rpc` from the app's merged `RpcGroup` ([`effect/unstable/rpc`](https://github.com/Effect-TS/effect)). The rpc **`_tag`** is the boundary's stable identity and its **payload schema** the typed input. The handler lives in the server-only rpc Layer (`group.toLayer(...)`), which the client never imports: tree-shaking does the client/server split structurally.
148
+
149
+ Unlike the catch variants it takes a `render` function, not a children array. That `render` receives a reactive [`Resource`](#resourcea), not a bare value.
148
150
 
149
151
  ```typescript
150
152
  Boundary.rpc<R extends Rpc.Any, C extends Node<any, any>>(
@@ -157,27 +159,27 @@ Boundary.rpc<R extends Rpc.Any, C extends Node<any, any>>(
157
159
 
158
160
  The boundary resolves the rpc through the ambient [`AppRpcClientTag`](#apprpcclienttag) seam, provided by `@weftui/router` (`RouterServer` on the server, `RouterLive` on the client). It has four lifecycles:
159
161
 
160
- - **SSR:** the server resolves the rpc in-process (over the handler Layer), `successSchema`-encodes the result inline as `<script type="application/json">` at the region cursor, then renders `render(seededResource)` to HTML in place.
161
- - **Hydrate:** `hydrate` reads the inline payload positionally, `successSchema`-decodes it, seeds the `Resource`, and adopts the DOM it **never re-calls the rpc** (replay, not refetch).
162
- - **Refetch:** `resource.refetch` calls the rpc again over the network (`POST /_eui/rpc`) and patches the subtree in place (**stale-on-error** a failed refetch leaves the previous value intact).
162
+ - **SSR:** the server resolves the rpc in-process (over the handler Layer), `successSchema`-encodes the result inline as `<script type="application/json">` at the region cursor. It then renders `render(seededResource)` to HTML in place.
163
+ - **Hydrate:** `hydrate` reads the inline payload positionally, `successSchema`-decodes it, seeds the `Resource`, and adopts the DOM. It **never re-calls the rpc** (replay, not refetch).
164
+ - **Refetch:** `resource.refetch` calls the rpc again over the network (`POST /_eui/rpc`) and patches the subtree in place. **Stale-on-error**: a failed refetch leaves the previous value intact.
163
165
  - **Client-first mount:** SPA-navigating into a boundary with **no** SSR payload renders `options.fallback`, forks the rpc call, and swaps in `render(resource)` once it resolves.
164
166
 
165
- **Channel algebra:** the output `E` is `render`'s error union plus the rpc's typed `Rpc.Error<R>` (`never` for an rpc with no `error` schema). The output `R` is **exactly** `render`'s `R`, untouched there is no `provide`/`RServer` to discharge (the handler lives in the rpc Layer) and **no `Exclude`** is applied. A server-only tag accidentally referenced in `render` therefore stays in `R`, where `hydrate`'s `AssertNoServerOnly` rejects it. Brand such services with [`ServerTag`](#servertag).
167
+ **Channel algebra:** the output `E` is `render`'s error union plus the rpc's typed `Rpc.Error<R>` (`never` for an rpc with no `error` schema). The output `R` is **exactly** `render`'s `R`, untouched. There is no `provide`/`RServer` to discharge (the handler lives in the rpc Layer) and **no `Exclude`** is applied. A server-only tag accidentally referenced in `render` therefore stays in `R`, where `hydrate`'s `AssertNoServerOnly` rejects it. Brand such services with [`ServerTag`](#servertag).
166
168
 
167
- **Typed-failure replay:** a resolved rpc **error** on the SSR pass is `errorSchema`-encoded and relocated to the nearest enclosing failure `Boundary`, then replayed on the client (decoded and re-raised, reproducing the same fallback DOM never retried). A transport **defect**, or an rpc with no `error` schema, is not replayed; it propagates.
169
+ **Typed-failure replay:** a resolved rpc **error** on the SSR pass is `errorSchema`-encoded and relocated to the nearest enclosing failure `Boundary`. It is then replayed on the client: decoded and re-raised, reproducing the same fallback DOM (never retried). A transport **defect**, or an rpc with no `error` schema, is not replayed; it propagates.
168
170
 
169
171
  > **Not yet covered:** streamed success (`Rpc.make(..., { stream: true })`) and mutations are on the roadmap, not this pass.
170
172
 
171
173
  ##### `Resource<A>`
172
174
 
173
- The reactive handle `render` receives (`A = Rpc.Success<R>`). After hydrate the region is live: `value` is seeded with the SSR payload and the client can `refetch` the same data on demand, patching the rendered subtree in place.
175
+ The reactive handle `render` receives (`A = Rpc.Success<R>`). After hydrate the region is live: `value` is seeded with the SSR payload. The client can `refetch` the same data on demand, patching the rendered subtree in place.
174
176
 
175
- | Field | Type | Meaning |
176
- | --------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
- | `value` | `Subscribable.Subscribable<A>` | Current data. Seeded with the SSR `data` (await-first, emits immediately, so SSR HTML and adopted DOM are byte-identical no fallback flash). A successful refetch pushes the new value. |
178
- | `refetch` | `Effect.Effect<void>` | Re-resolves the rpc over the network with a fresh `payload()` and sets `value`. Client only a no-op on the server. |
179
- | `pending` | `Subscribable.Subscribable<boolean>` | `true` while a refetch is in flight (`false` on the server / before any refetch). |
180
- | `error` | `Subscribable.Subscribable<Option<unknown>>` | `Some` with the last refetch error, else `None`. A failed refetch is stale-on-error it does **not** unmount or raise into a failure `Boundary`. |
177
+ | Field | Type | Meaning |
178
+ | --------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
179
+ | `value` | `Subscribable.Subscribable<A>` | Current data. Seeded with the SSR `data` (await-first, emits immediately, so SSR HTML and adopted DOM are byte-identical, no fallback flash). A successful refetch pushes the new value. |
180
+ | `refetch` | `Effect.Effect<void>` | Re-resolves the rpc over the network with a fresh `payload()` and sets `value`. Client only, a no-op on the server. |
181
+ | `pending` | `Subscribable.Subscribable<boolean>` | `true` while a refetch is in flight (`false` on the server / before any refetch). |
182
+ | `error` | `Subscribable.Subscribable<Option<unknown>>` | `Some` with the last refetch error, else `None`. A failed refetch is stale-on-error: it does **not** unmount or raise into a failure `Boundary`. |
181
183
 
182
184
  ##### `RpcOptions`
183
185
 
@@ -206,7 +208,9 @@ class AppRpcClientTag extends Context.Service<AppRpcClientTag, AppRpcClient>()(
206
208
  ) {}
207
209
  ```
208
210
 
209
- The ambient, package-neutral seam the renderer resolves a `Boundary.rpc` through a **flat, untyped** caller `(tag, payload) => Effect<success>`. It lets `@weftui/dom` resolve a boundary without importing `effect/unstable/rpc` or `@weftui/router`. `@weftui/router` provides it: a **network** `RpcClient` (POST `/_eui/rpc`) in the browser, an **in-process** client over the handler Layer on the server. `call` returns the already-decoded success; the renderer owns `successSchema`/`errorSchema` decoding of the inline SSR payload only. Both `AppRpcClientTag` and the `AppRpcClient` type are re-exported from `@weftui/core`. Absent in a router-less mount, where a `Boundary.rpc` resolves to a descriptive "needs router/rpc" error (not a defect).
211
+ The ambient, package-neutral seam the renderer resolves a `Boundary.rpc` through: a **flat, untyped** caller `(tag, payload) => Effect<success>`. It lets `@weftui/dom` resolve a boundary without importing `effect/unstable/rpc` or `@weftui/router`. `@weftui/router` provides it: a **network** `RpcClient` (POST `/_eui/rpc`) in the browser, an **in-process** client over the handler Layer on the server.
212
+
213
+ `call` returns the already-decoded success; the renderer owns `successSchema`/`errorSchema` decoding of the inline SSR payload only. Both `AppRpcClientTag` and the `AppRpcClient` type are re-exported from `@weftui/core`. Absent in a router-less mount, where a `Boundary.rpc` resolves to a descriptive "needs router/rpc" error (not a defect).
210
214
 
211
215
  See the [rpc data boundaries guide](https://weftui.dev/docs/how-to/load-data-with-rpc) and [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr).
212
216
 
@@ -267,7 +271,9 @@ Boundary.catchTags<C, Handlers>(
267
271
 
268
272
  #### `Boundary.catchFilter`
269
273
 
270
- Conditionally catches using a `Filter`, run on each typed failure: a `Result.succeed` (pass) recovers via `fallback`, receiving the possibly-narrowed pass value; a `Result.fail` re-raises the error (its `Fail` channel `X` is preserved in the output `E`, since the boundary may not handle any given error). Takes the `Filter` and `fallback` as **positional** arguments — no wrapping props object. Mirrors Effect 4's `Effect.catchFilter` (renamed from `catchSome`, which took an `Option`-returning function in v3).
274
+ Conditionally catches using a `Filter`, run on each typed failure. A `Result.succeed` (pass) recovers via `fallback`, receiving the possibly-narrowed pass value. A `Result.fail` re-raises the error; its `Fail` channel `X` is preserved in the output `E`, since the boundary may not handle any given error.
275
+
276
+ Takes the `Filter` and `fallback` as **positional** arguments (no wrapping props object). Mirrors Effect 4's `Effect.catchFilter` (renamed from `catchSome`, which took an `Option`-returning function in v3).
271
277
 
272
278
  ```typescript
273
279
  Boundary.catchFilter<C, EB, X, FE, FR>(
@@ -288,8 +294,6 @@ Boundary.catchFilter(
288
294
  );
289
295
  ```
290
296
 
291
- The children's `E` is narrowed to the filter's `Fail` channel `X` in the output, because the boundary may or may not handle any given error.
292
-
293
297
  #### `Boundary.catchIf`
294
298
 
295
299
  A predicate gates the fallback. `false` re-raises.
@@ -308,7 +312,7 @@ Boundary.catchIf<C, FE, FR>(
308
312
 
309
313
  When a boundary's `match` returns `null` (unmatched error), the error propagates to the nearest **parent** `Boundary` via `BoundaryContext`. If there is no parent boundary, the error fails the enclosing mount.
310
314
 
311
- Inner boundaries shadow outer ones for their subtree the innermost boundary is always tried first.
315
+ Inner boundaries shadow outer ones for their subtree: the innermost boundary is always tried first.
312
316
 
313
317
  ```typescript
314
318
  // Inner catches FooError; BarError propagates to outer
@@ -323,7 +327,7 @@ Boundary.catch({ fallback: (e) => h.div(`Outer: ${e.message}`) }, [
323
327
 
324
328
  ## ServerTag
325
329
 
326
- A `Context.Service` key whose identifier is branded server-only. Use it exactly like `Context.Service` for services that must only ever be provided on the server e.g. a database handle read inside an rpc handler Layer. The brand also guards [`Boundary.rpc`](#boundaryrpc): a server-only tag accidentally referenced in `render` stays in the requirement channel, where `hydrate`'s `AssertNoServerOnly` rejects it at compile time.
330
+ A `Context.Service` key whose identifier is branded server-only. Use it exactly like `Context.Service` for services that must only ever be provided on the server (e.g. a database handle read inside an rpc handler Layer). The brand also guards [`Boundary.rpc`](#boundaryrpc): a server-only tag accidentally referenced in `render` stays in the requirement channel, where `hydrate`'s `AssertNoServerOnly` rejects it at compile time.
327
331
 
328
332
  ```typescript
329
333
  import { ServerTag } from "@weftui/core";
@@ -336,8 +340,8 @@ class Database extends ServerTag("Database")<
336
340
  ```
337
341
 
338
342
  - The server-only brand rides along in the requirement channel `R` of any effect that uses the tag.
339
- - An rpc handler Layer (`group.toLayer(...)`) discharges it on the server, where it is provided; it never enters a [`Boundary.rpc`](#boundaryrpc)'s output `R`, since `render` only reads the decoded result.
340
- - If a branded tag ever reaches client code referenced in `render` and surviving into `hydrate`'s requirement channel `AssertNoServerOnly` resolves `R` to a compile-error sentinel (`ServerOnlyLeak`) at the `hydrate` call site, rather than failing silently at runtime.
343
+ - An rpc handler Layer (`group.toLayer(...)`) discharges it on the server, where it is provided. It never enters a [`Boundary.rpc`](#boundaryrpc)'s output `R`, since `render` only reads the decoded result.
344
+ - If a branded tag ever reaches client code (referenced in `render` and surviving into `hydrate`'s requirement channel), `AssertNoServerOnly` resolves `R` to a compile-error sentinel (`ServerOnlyLeak`). The failure surfaces at the `hydrate` call site, not silently at runtime.
341
345
 
342
346
  `ServerOnly`, `ServerOnlyLeak`, and `AssertNoServerOnly<R>` are exported alongside `ServerTag` for advanced typing; most code only needs `ServerTag` itself.
343
347
 
@@ -347,10 +351,10 @@ class Database extends ServerTag("Database")<
347
351
 
348
352
  ### `List` namespace
349
353
 
350
- The keyed-list combinator. It is the opt-in alternative to wholesale child rebuilds: items are rendered **once per key** and reconciled across emissions, so reordering, inserting, or removing items reuses and moves existing DOM rather than rebuilding the region.
354
+ The keyed-list combinator. It is the opt-in alternative to wholesale child rebuilds: items are rendered **once per key** and reconciled across emissions. Reordering, inserting, or removing items therefore reuses and moves existing DOM rather than rebuilding the region.
351
355
 
352
356
  > **Note:** This exported `List` namespace is the built-in, key-reconciling way to
353
- > render collections prefer it over hand-rolling a component that maps items into
357
+ > render collections. Prefer it over hand-rolling a component that maps items into
354
358
  > elements.
355
359
 
356
360
  ```typescript
@@ -372,7 +376,7 @@ List.each<S extends Source.Source<Iterable<any>, any, any>, CE, CR, K>(
372
376
 
373
377
  `render` runs **once per key**; a persisted key keeps its DOM nodes and its running subscription fibers across re-emits (it is never re-invoked). The returned node's `E`/`R` are the union of the source channels and the channels of the node `render` returns.
374
378
 
375
- > **⚠️ Render-once / index-key footgun:** because `render` runs exactly once per key, reconciliation never refreshes a kept row's content refresh a row by threading a `Stream` **inside** it, not by re-running `render`. Keying by index (`by: (_, i) => i`) reuses rows positionally and will show stale content after a reorder; prefer a stable identity key (`by: (item) => item.id`).
379
+ > **⚠️ Render-once / index-key footgun:** because `render` runs exactly once per key, reconciliation never refreshes a kept row's content. Refresh a row by threading a `Stream` **inside** it, not by re-running `render`. Keying by index (`by: (_, i) => i`) reuses rows positionally and will show stale content after a reorder. Prefer a stable identity key (`by: (item) => item.id`).
376
380
 
377
381
  **`List.Options<S, K>`**
378
382
 
@@ -383,8 +387,8 @@ interface List.Options<S, K> {
383
387
  }
384
388
  ```
385
389
 
386
- - **`of`** the list source. Each emission is materialized to an array to fix order, then reconciled by key.
387
- - **`by`** projects each item to its reconciliation key. Omitted ⇒ the item itself is the key (structural for `Data`, by reference otherwise).
390
+ - **`of`**: the list source. Each emission is materialized to an array to fix order, then reconciled by key.
391
+ - **`by`**: projects each item to its reconciliation key. Omitted ⇒ the item itself is the key (structural for `Data`, by reference otherwise).
388
392
 
389
393
  #### `List.Error<N>` and `List.Context<N>`
390
394
 
@@ -435,7 +439,7 @@ Normalization rules:
435
439
  - **`Effect`** → memoized via `Effect.cached`; `changes` emits the resolved value once
436
440
  - **`Stream`** → forks a scoped pump fiber that drains into a `SubscriptionRef`; `get` awaits the first emission
437
441
 
438
- The pump fiber is tied to the enclosing scope via `Effect.forkScoped` it terminates when the scope closes.
442
+ The pump fiber is tied to the enclosing scope via `Effect.forkScoped`. It terminates when the scope closes.
439
443
 
440
444
  ### `NoPropValue`
441
445
 
@@ -458,7 +462,7 @@ type PropsE<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, infer E, any>
458
462
  type PropsR<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, any, infer R> ? R : ... }[keyof P]
459
463
  ```
460
464
 
461
- These are used internally by `h` and `Component` to accumulate channels from props. You generally don't need to reference them directly unless building utilities over the combinator API.
465
+ These are used internally by `h` and `Component` to accumulate channels from props. Reference them directly only when building utilities over the combinator API.
462
466
 
463
467
  ---
464
468
 
@@ -466,7 +470,7 @@ These are used internally by `h` and `Component` to accumulate channels from pro
466
470
 
467
471
  ### `FRAGMENT`
468
472
 
469
- Internal brand used to mark fragment nodes. Not intended for direct use use `h.fragment` instead.
473
+ Internal brand used to mark fragment nodes. Not intended for direct use; use `h.fragment` instead.
470
474
 
471
475
  ---
472
476
 
@@ -502,6 +506,6 @@ isSubscribable(value: unknown): value is Subscribable<unknown, unknown, unknown>
502
506
 
503
507
  ## See also
504
508
 
505
- - [The Combinator API](https://weftui.dev/docs/explanation/combinator-api) · [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) · [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense) the concepts behind this surface
506
- - [Author Components](https://weftui.dev/docs/how-to/author-components) · [Render Keyed Lists](https://weftui.dev/docs/how-to/render-keyed-lists) task guides that use it
509
+ - [The Combinator API](https://weftui.dev/docs/explanation/combinator-api) · [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) · [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense): the concepts behind this surface
510
+ - [Author Components](https://weftui.dev/docs/how-to/author-components) · [Render Keyed Lists](https://weftui.dev/docs/how-to/render-keyed-lists): task guides that use it
507
511
  - [`@weftui/dom` reference](https://weftui.dev/docs/reference/dom) · [`@weftui/router` reference](https://weftui.dev/docs/reference/router)