@weftui/core 0.28.0 → 0.30.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 +8 -8
- package/dist/{index-Bsh2WLtx.d.ts → index-CX9uEejU.d.ts} +70 -51
- package/dist/index.d.ts +68 -119
- package/dist/index.js +1 -1
- package/dist/types/index.d.ts +1 -1
- package/docs/explanation/boundaries-and-suspense.md +39 -20
- package/docs/explanation/combinator-api.md +14 -12
- package/docs/explanation/reactive-primitives.md +14 -14
- package/docs/explanation/rendering-model.md +20 -18
- package/docs/explanation/services-and-context.md +35 -21
- package/docs/how-to/add-routing.md +361 -60
- package/docs/how-to/author-components.md +42 -30
- 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 -32
- package/docs/how-to/provide-services.md +20 -18
- package/docs/how-to/render-keyed-lists.md +14 -12
- package/docs/how-to/render-on-the-server.md +18 -14
- package/docs/how-to/show-navigation-progress.md +12 -10
- 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 +46 -42
- package/docs/reference/dom.md +274 -58
- package/docs/reference/router.md +69 -47
- package/docs/tutorial/01-your-first-app.md +7 -9
- package/docs/tutorial/02-reactivity.md +8 -6
- package/docs/tutorial/03-services-and-async.md +10 -6
- package/docs/tutorial/04-errors-and-server.md +14 -5
- package/package.json +2 -2
|
@@ -7,14 +7,14 @@ description: The Source<A, E, R> vocabulary; Stream, Effect, and Subscribable as
|
|
|
7
7
|
|
|
8
8
|
# Reactive Primitives
|
|
9
9
|
|
|
10
|
-
The unified `Source` vocabulary
|
|
10
|
+
The unified `Source` vocabulary lets static values, Effects, Streams, and Subscribables be used interchangeably. Props, children, and style values all accept the same type.
|
|
11
11
|
|
|
12
12
|
Weft accepts a `Source` for prop values and children. Any of these is valid wherever reactivity is supported:
|
|
13
13
|
|
|
14
14
|
- A plain static value (`string`, `number`, `boolean`, ...)
|
|
15
|
-
- An `Effect.Effect<A, E, R
|
|
16
|
-
- A `Stream.Stream<A, E, R
|
|
17
|
-
- A `Subscribable<A, E, R
|
|
15
|
+
- An `Effect.Effect<A, E, R>`: runs once and resolves to a value
|
|
16
|
+
- A `Stream.Stream<A, E, R>`: each emission replaces the previous value
|
|
17
|
+
- A `Subscribable<A, E, R>`: like a hot stream; already has a "current value"
|
|
18
18
|
|
|
19
19
|
The `Source<A, E, R>` type captures this union:
|
|
20
20
|
|
|
@@ -24,7 +24,7 @@ type Source<A, E, R> = A | Effect.Effect<A, E, R> | Stream.Stream<A, E, R> | Sub
|
|
|
24
24
|
|
|
25
25
|
## Static values
|
|
26
26
|
|
|
27
|
-
Static props
|
|
27
|
+
Static props are set once and never updated:
|
|
28
28
|
|
|
29
29
|
```typescript
|
|
30
30
|
h.div({ class: "container", id: "root" }, "Hello");
|
|
@@ -45,17 +45,17 @@ The `E` and `R` channels of the Effect flow into the node's own channels.
|
|
|
45
45
|
|
|
46
46
|
## Stream props and children
|
|
47
47
|
|
|
48
|
-
Streams are the primary reactive primitive. Each emission replaces the previous value in the DOM
|
|
48
|
+
Streams are the primary reactive primitive. Each emission replaces the previous value in the DOM (no diffing, direct DOM update):
|
|
49
49
|
|
|
50
50
|
```typescript
|
|
51
51
|
import { SubscriptionRef, Stream } from "effect";
|
|
52
52
|
|
|
53
53
|
const count = yield * SubscriptionRef.make(0);
|
|
54
54
|
|
|
55
|
-
// SubscriptionRef.changes(count) is a Stream<number
|
|
55
|
+
// SubscriptionRef.changes(count) is a Stream<number>: each new value updates the text node
|
|
56
56
|
h.span([SubscriptionRef.changes(count)]);
|
|
57
57
|
|
|
58
|
-
// Stream as a prop
|
|
58
|
+
// Stream as a prop: each emission sets the attribute
|
|
59
59
|
const isDisabled = Stream.map(SubscriptionRef.changes(count), (n) => n >= 10);
|
|
60
60
|
h.button({ disabled: isDisabled }, "Submit");
|
|
61
61
|
```
|
|
@@ -119,7 +119,7 @@ h.div({ style: styleObjectStream });
|
|
|
119
119
|
|
|
120
120
|
// Combine a whole-object stream with a static property.
|
|
121
121
|
// A whole-object stream replaces every property on each emit, so fold the
|
|
122
|
-
// static value into each emitted object with Stream.map
|
|
122
|
+
// static value into each emitted object with Stream.map. You cannot spread the
|
|
123
123
|
// Stream itself into a style object (that copies the Stream's internals, not
|
|
124
124
|
// its emitted style keys).
|
|
125
125
|
h.div({
|
|
@@ -154,11 +154,11 @@ pipe(
|
|
|
154
154
|
);
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
-
In practice you only encounter `NoPropValue`
|
|
157
|
+
In practice you only encounter `NoPropValue` when a finite `Stream` prop ends before emitting (e.g., `Stream.empty` or `Stream.take(0, stream)`). Most usage with `SubscriptionRef.changes` or infinite streams never raises it.
|
|
158
158
|
|
|
159
159
|
## See also
|
|
160
160
|
|
|
161
|
-
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model)
|
|
162
|
-
- [The Combinator API](https://weftui.dev/docs/explanation/combinator-api)
|
|
163
|
-
- [Style Reactively](https://weftui.dev/docs/how-to/style-reactively) and [Render Keyed Lists](https://weftui.dev/docs/how-to/render-keyed-lists)
|
|
164
|
-
- [`Source` reference](https://weftui.dev/docs/reference/core#source-namespace)
|
|
161
|
+
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model): streams as the weft woven through a static tree
|
|
162
|
+
- [The Combinator API](https://weftui.dev/docs/explanation/combinator-api): how reactive props and children contribute `E`/`R`
|
|
163
|
+
- [Style Reactively](https://weftui.dev/docs/how-to/style-reactively) and [Render Keyed Lists](https://weftui.dev/docs/how-to/render-keyed-lists): reactive props and collections in practice
|
|
164
|
+
- [`Source` reference](https://weftui.dev/docs/reference/core#source-namespace): the `Source` type and `Source.toSubscribable`
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
title: The Rendering Model
|
|
3
3
|
order: 1
|
|
4
4
|
section: explanation
|
|
5
|
-
description: Why Weft has no virtual DOM
|
|
5
|
+
description: Why Weft has no virtual DOM. Nodes are Effects, streams are the live thread woven through a static tree, and hydration adopts server DOM in place.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# The Rendering Model
|
|
9
9
|
|
|
10
|
-
Weft renders UI by **weaving streams through a static tree**. There is no virtual DOM, no diff, no reconciler comparing two trees each frame.
|
|
10
|
+
Weft renders UI by **weaving streams through a static tree**. There is no virtual DOM, no diff, no reconciler comparing two trees each frame.
|
|
11
11
|
|
|
12
12
|
## Nodes are Effects
|
|
13
13
|
|
|
@@ -17,38 +17,40 @@ The whole library rests on a single equation:
|
|
|
17
17
|
type Node<E = never, R = never> = Effect.Effect<ElementDescriptor, E, R>;
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Every element in a Weft tree **is an Effect**. `h.div(...)`, a component's return value, a boundary
|
|
20
|
+
Every element in a Weft tree **is an Effect**. `h.div(...)`, a component's return value, a boundary: each is an `Effect` that, when run, produces an element descriptor. Two consequences follow:
|
|
21
21
|
|
|
22
|
-
1. **The error (`E`) and requirement (`R`) channels accumulate through the tree.** A child that reads a service, or a prop backed by a failible stream, contributes its `R` and `E` to its parent
|
|
23
|
-
2. **Every Effect combinator applies to a node directly.** `Effect.provide`, `Effect.flatMap`, `Effect.gen`, `Effect.catch
|
|
22
|
+
1. **The error (`E`) and requirement (`R`) channels accumulate through the tree.** A child that reads a service, or a prop backed by a failible stream, contributes its `R` and `E` to its parent. That parent contributes to _its_ parent, up to the mount boundary. The type of your app node is the exact union of everything it needs and everything it can fail with. It is visible to the type checker, satisfiable exactly once, at `mount`/`hydrate`. See [The Combinator API](https://weftui.dev/docs/explanation/combinator-api) for how the accumulation works mechanically.
|
|
23
|
+
2. **Every Effect combinator applies to a node directly.** `Effect.provide`, `Effect.flatMap`, `Effect.gen`, `Effect.catch`: none of them are special-cased for UI. A node is an ordinary Effect, so the entire Effect ecosystem composes with your view for free.
|
|
24
24
|
|
|
25
|
-
JSX collapses every component to an opaque `JSX.Element`, erasing both channels. Weft keeps them, and that is the point of the whole design. (There is [no JSX](https://weftui.dev/docs/explanation/combinator-api) here
|
|
25
|
+
JSX collapses every component to an opaque `JSX.Element`, erasing both channels. Weft keeps them, and that is the point of the whole design. (There is [no JSX](https://weftui.dev/docs/explanation/combinator-api) here; components are plain functions you _call_.)
|
|
26
26
|
|
|
27
27
|
## Warp and weft
|
|
28
28
|
|
|
29
29
|
The name is the metaphor. On a loom, the **warp** is the set of fixed threads held under tension; the **weft** is the live thread drawn back and forth across them to form the cloth.
|
|
30
30
|
|
|
31
|
-
- Your **component tree is the warp
|
|
32
|
-
- **Streams are the weft
|
|
31
|
+
- Your **component tree is the warp**: the structure, fixed for the lifetime of a mounted region.
|
|
32
|
+
- **Streams are the weft**: the live values drawn across that structure. A `Stream`, `Effect`, or `Subscribable` used as a prop value or child is a thread woven through a specific point in the tree.
|
|
33
33
|
|
|
34
|
-
When a stream emits, only the DOM at _that_ point updates. Nothing above it re-runs; no sibling is touched
|
|
34
|
+
When a stream emits, only the DOM at _that_ point updates. Nothing above it re-runs; no sibling is touched. There is no tree to diff because the structure never changed; only a value threaded through one hole in it did. This is why Weft needs no virtual DOM: **the reactivity is local by construction.** The vocabulary of stream-shaped values (and how their channels flow) is [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives).
|
|
35
35
|
|
|
36
|
-
> **Note.** "Only that point updates" is the default, not a manual optimization. You do not memoize regions or declare dependencies
|
|
36
|
+
> **Note.** "Only that point updates" is the default, not a manual optimization. You do not memoize regions or declare dependencies. A value is reactive exactly where you thread a stream, and static everywhere else.
|
|
37
37
|
|
|
38
38
|
## Streams drive all updates
|
|
39
39
|
|
|
40
|
-
There is no `setState`, no render-triggering scheduler, no "re-render this component." A region of the DOM is live **if and only if** a stream is woven into it. To make something update, you thread a stream through it; to keep something static, you pass a plain value.
|
|
40
|
+
There is no `setState`, no render-triggering scheduler, no "re-render this component." A region of the DOM is live **if and only if** a stream is woven into it. To make something update, you thread a stream through it; to keep something static, you pass a plain value.
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
The renderer subscribes to each woven stream and patches its target in place on every emission. It reuses the existing DOM node and patches text and attributes rather than recreating elements, so identity, focus, and typed input survive an update.
|
|
43
|
+
|
|
44
|
+
This also fixes the update _shape_. Because the structure is fixed, an update is always "new value into a known hole," never "reconcile these two trees." Even list rendering, where the number of children genuinely varies, is expressed as a keyed region ([`List.each`](https://weftui.dev/docs/how-to/render-keyed-lists)). It reconciles by key rather than by structural diff.
|
|
43
45
|
|
|
44
46
|
## One tree, two sides, hydrate in place
|
|
45
47
|
|
|
46
48
|
The same component tree renders on the server and the client:
|
|
47
49
|
|
|
48
50
|
- On the **server**, the tree renders to an HTML string (or a streaming response) via `@weftui/dom/server`. The _hydratable_ renderers additionally emit the inline data each reactive region needs to resume.
|
|
49
|
-
- On the **client**, `WeftApp.hydrate` walks that server-rendered DOM and **adopts it in place
|
|
51
|
+
- On the **client**, `WeftApp.hydrate` walks that server-rendered DOM and **adopts it in place**: it wires up reactivity and event handlers on the existing nodes rather than re-rendering. The first client production matches the adopted DOM exactly, so nothing is mutated and there is no flash.
|
|
50
52
|
|
|
51
|
-
Because the same `Node<E, R>` describes both passes, there is nothing to keep in sync
|
|
53
|
+
Because the same `Node<E, R>` describes both passes, there is nothing to keep in sync. The server output and the client's first render are the _same tree_ run in two environments. Services flow from the app layer (or the router's render-time context) through the tree to wherever a component reads them, on both sides. The mechanics of the two-sided render live in [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server); the service flow is [Services and Context](https://weftui.dev/docs/explanation/services-and-context).
|
|
52
54
|
|
|
53
55
|
## Why this matters
|
|
54
56
|
|
|
@@ -59,7 +61,7 @@ Because the same `Node<E, R>` describes both passes, there is nothing to keep in
|
|
|
59
61
|
|
|
60
62
|
## See also
|
|
61
63
|
|
|
62
|
-
- [The Combinator API](https://weftui.dev/docs/explanation/combinator-api)
|
|
63
|
-
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives)
|
|
64
|
-
- [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense)
|
|
65
|
-
- [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server)
|
|
64
|
+
- [The Combinator API](https://weftui.dev/docs/explanation/combinator-api): how `E`/`R` accumulate; why `Node` is an `Effect`; `h` and components
|
|
65
|
+
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives): the stream-shaped values you weave through the tree
|
|
66
|
+
- [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense): how failure and async are modeled as nodes in the same tree
|
|
67
|
+
- [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server): the server/client split and `hydrate`
|
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
title: Services and Context
|
|
3
3
|
order: 5
|
|
4
4
|
section: explanation
|
|
5
|
-
description: How Effect services reach components
|
|
5
|
+
description: "How Effect services reach components: the requirement channel, discharging R at the mount, the router's render-time context seam, and ServerTag server-only brands."
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Services and Context
|
|
9
9
|
|
|
10
|
-
Weft has no separate dependency-injection system. It uses Effect's
|
|
10
|
+
Weft has no separate dependency-injection system. It uses Effect's. A component that needs a service reads it with `yield* Service`. Because [a node is an Effect](https://weftui.dev/docs/explanation/rendering-model), that requirement rides the node's `R` channel up the tree to a single point where you provide it.
|
|
11
|
+
|
|
12
|
+
This page explains how a service travels from provider to reader, and the two seams that make that work across the server/client boundary.
|
|
11
13
|
|
|
12
14
|
## R accumulates, then discharges once
|
|
13
15
|
|
|
14
|
-
When a component does `yield* ThemeService`, `ThemeService` enters that node's requirement channel. It accumulates through every parent
|
|
16
|
+
When a component does `yield* ThemeService`, `ThemeService` enters that node's requirement channel. It accumulates through every parent: a boundary, a layout, the app node. The whole tree's `R` becomes the union of everything any component needs. You satisfy it in **one** place, at the edge:
|
|
15
17
|
|
|
16
18
|
```typescript
|
|
17
19
|
import { Effect } from "effect";
|
|
@@ -21,29 +23,37 @@ const app = WeftApp.make(ThemeServiceLive);
|
|
|
21
23
|
const handle = WeftApp.mount(app, App(), document.getElementById("root")!);
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
Provide too little and it is a compile error at `WeftApp.make
|
|
26
|
+
Provide too little and it is a compile error at `WeftApp.make`: the type of `App()` names exactly which service is missing. This is the same discipline as any Effect program. `R` is a promise the type checker holds you to, discharged at the program's boundary, not sprinkled through the tree.
|
|
25
27
|
|
|
26
|
-
Services flow **down** from the app's layer to every reader, including across reactive boundaries
|
|
28
|
+
Services flow **down** from the app's layer to every reader, including across reactive boundaries. A stream woven into a prop carries its own `R`, and a handler that reads a service resolves it from the same context. There is no prop-drilling and no context-provider component: the requirement channel _is_ the wiring.
|
|
27
29
|
|
|
28
30
|
## Layer lifetime and the app runtime
|
|
29
31
|
|
|
30
|
-
Under the old `mount`/`hydrate` model this was a real footgun. Each call created its own implicit `ManagedRuntime
|
|
32
|
+
Under the old `mount`/`hydrate` model this was a real footgun. Each call created its own implicit `ManagedRuntime`. That runtime's effect resolved right after the tree's **initial render**, not when the app stopped running. Streams, event handlers, and forked work kept running on it long after.
|
|
33
|
+
|
|
34
|
+
`Effect.provide(scopedLayer)` is `acquireUseRelease` sugar: acquire, run the wrapped effect, then release **when that effect completes**. Wrapped directly around `mount`, the release ran at mount-resolve, while the mounted tree was still reading from the now-disposed service:
|
|
31
35
|
|
|
32
36
|
```typescript
|
|
33
37
|
// ❌ (old API) the layer's finalizers ran the instant runPromise settled, while the
|
|
34
|
-
// mounted tree kept running
|
|
38
|
+
// mounted tree kept running: every subscription then read a disposed service
|
|
35
39
|
Effect.runPromise(mount(App(), root).pipe(Effect.provide(SomeScopedLayer)));
|
|
36
40
|
```
|
|
37
41
|
|
|
38
|
-
This is exactly what happened with the atom registry layer (`AtomRegistry.layer`, from `effect/unstable/reactivity`) in the [`effect-atom` example](https://github.com/stefvw93/weft/tree/main/examples/effect-atom) (issue #122)
|
|
42
|
+
This is exactly what happened with the atom registry layer (`AtomRegistry.layer`, from `effect/unstable/reactivity`) in the [`effect-atom` example](https://github.com/stefvw93/weft/tree/main/examples/effect-atom) (issue #122). Every atom-driven region rendered empty, with no error, because the registry the streams read from had already been disposed.
|
|
43
|
+
|
|
44
|
+
`WeftApp` closes this gap structurally instead of by convention. An app owns exactly **one** lazy `ManagedRuntime`. `WeftApp.make(layer)` builds the layer on the first `mount`/`hydrate`. It releases only at `WeftApp.dispose(app)`, never when any individual mount's render effect resolves.
|
|
45
|
+
|
|
46
|
+
A scoped layer (`AtomRegistry.layer`, `RouterLive`) therefore just works passed straight to `WeftApp.make`. There is no `mountScoped`, no `Effect.never`, and no manual `ManagedRuntime` composition to reach for.
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
See [Provide Services](https://weftui.dev/docs/how-to/provide-services) for the recipes. They cover `memoMap` sharing across apps and the `Effect.acquireRelease(make, dispose)` pattern for binding an app's own lifetime to an external scope. There is deliberately no `makeScoped`.
|
|
41
49
|
|
|
42
50
|
## The router's render-time context seam
|
|
43
51
|
|
|
44
|
-
A plain `WeftApp.mount`/`WeftApp.hydrate` discharges `R` once, at `WeftApp.make`. But under `@weftui/router`, the tree does not render in the context of the effect that called `render
|
|
52
|
+
A plain `WeftApp.mount`/`WeftApp.hydrate` discharges `R` once, at `WeftApp.make`. But under `@weftui/router`, the tree does not render in the context of the effect that called `render`.
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
Each request dispatches through platform's HTTP layer in its own managed context. The reactive outlet drains in the top render context, not in any intermediate node's. Providing a service _ambiently_ around the render would be lost before it reached a route component.
|
|
55
|
+
|
|
56
|
+
So the router exposes an explicit **`context` seam**, a `Layer` threaded to the document shell and every route, layout, and leaf:
|
|
47
57
|
|
|
48
58
|
```typescript
|
|
49
59
|
class Greeting extends Context.Service<Greeting, { text: string }>()("Greeting") {}
|
|
@@ -51,17 +61,21 @@ class Greeting extends Context.Service<Greeting, { text: string }>()("Greeting")
|
|
|
51
61
|
// server entry
|
|
52
62
|
RouterServer.render(App, { document, url, context: Layer.succeed(Greeting, { text: "hi" }) });
|
|
53
63
|
|
|
54
|
-
// client entry
|
|
64
|
+
// client entry: same seam, so the hydrated tree reads the same services
|
|
55
65
|
RouterLive(App, { context: DocsLive });
|
|
56
66
|
```
|
|
57
67
|
|
|
58
|
-
The seam is **symmetric** (same shape on both sides) and **type-tracked
|
|
68
|
+
The seam is **symmetric** (same shape on both sides) and **type-tracked**. The def's aggregate residual `R` is discharged here, so a missing provide is a compile error rather than a runtime 500. The residual is `AppServices<R>`: the def's `R` minus what the router itself threads (`Router`, `Router.Outlet`, `AppRpcClientTag`). An app with no app-services needs no `context`; a loosely-typed `RouterDef<any, any>` may omit it.
|
|
69
|
+
|
|
70
|
+
This is how the website provides its `Docs` service to every page. See [Add Routing](https://weftui.dev/docs/how-to/add-routing).
|
|
59
71
|
|
|
60
72
|
## Server-only services: `ServerTag`
|
|
61
73
|
|
|
62
|
-
Some services must _never_ run in the browser
|
|
74
|
+
Some services must _never_ run in the browser: a database handle, a private credential, an rpc handler's backing store. Declare those with [`ServerTag`](https://weftui.dev/docs/reference/core#servertag) instead of `Context.Service`. It behaves exactly like `Context.Service`, but its identifier carries a **server-only brand**.
|
|
75
|
+
|
|
76
|
+
The brand's job is to turn a leak into a **compile error at the `hydrate` call site**. A `Boundary.rpc` handler legitimately reads server-only services on the server, but they must not survive into client code. Since `render` only ever touches the _decoded result_ (never the service), a correctly-written boundary keeps its output `R` free of the brand.
|
|
63
77
|
|
|
64
|
-
|
|
78
|
+
If a branded tag leaks into `render` and reaches the client requirement channel, `hydrate`'s `AssertNoServerOnly` resolves `R` to a compile-error sentinel. You learn at build time, not from a runtime defect.
|
|
65
79
|
|
|
66
80
|
```typescript
|
|
67
81
|
import { ServerTag } from "@weftui/core";
|
|
@@ -73,15 +87,15 @@ class Db extends ServerTag("Db")<Db, { query: (sql: string) => Effect.Effect<Row
|
|
|
73
87
|
## The whole picture
|
|
74
88
|
|
|
75
89
|
- A component reads a service with `yield* Service`; the requirement enters `R`.
|
|
76
|
-
- `R` accumulates through the tree and is discharged **once
|
|
90
|
+
- `R` accumulates through the tree and is discharged **once**: at `WeftApp.make`, or through the router's `context` seam.
|
|
77
91
|
- The same services flow to the same components on the server and the client, because it is the same tree.
|
|
78
92
|
- `ServerTag` brands the services that must stay server-side, enforced at the `hydrate` boundary.
|
|
79
93
|
|
|
80
94
|
## See also
|
|
81
95
|
|
|
82
|
-
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model)
|
|
83
|
-
- [The Combinator API](https://weftui.dev/docs/explanation/combinator-api)
|
|
84
|
-
- [Provide Services](https://weftui.dev/docs/how-to/provide-services)
|
|
85
|
-
- [Add Routing](https://weftui.dev/docs/how-to/add-routing)
|
|
86
|
-
- [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc)
|
|
96
|
+
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model): why services flow through the tree at all
|
|
97
|
+
- [The Combinator API](https://weftui.dev/docs/explanation/combinator-api): how `R` accumulates from children and reactive props
|
|
98
|
+
- [Provide Services](https://weftui.dev/docs/how-to/provide-services): recipes for app layers, scoped layers, and binding an app's lifetime to an external scope
|
|
99
|
+
- [Add Routing](https://weftui.dev/docs/how-to/add-routing): providing app services through the router `context` seam
|
|
100
|
+
- [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc): where `ServerTag` and the rpc handler Layer meet
|
|
87
101
|
- [`ServerTag` API reference](https://weftui.dev/docs/reference/core#servertag)
|