@thomasfosterau/legation-svelte 0.0.0-stage → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,644 @@
1
+ # @thomasfosterau/legation-svelte
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e9c5cac: Async Svelte support and a type-safe runtime context. Every live rune hook's
8
+ result gains an awaitable `promise` alongside `current`: pending until the
9
+ first data arrives (suspending a `<svelte:boundary>`'s `pending` snippet),
10
+ then an already-resolved promise replaced per emission and on input change —
11
+ awaiting expressions re-run with fresh rows while Svelte keeps the previous
12
+ UI on screen; stream failures reject into the boundary's `failed` snippet.
13
+ The runtime context is built on Svelte's `createContext`, which raises the
14
+ package's Svelte peer requirement to `>=5.40`.
15
+ - 4709f41: One client surface over engine and mirror (ADR-0021): core gains
16
+ `ClientView` — the narrow surface component bindings consume (query/observe,
17
+ subscribe/unsubscribe, mutate/mutateWithAck with awaited-outcome semantics) —
18
+ with `fromEngine` and a `layer` deriving it from the in-context engine.
19
+ `TabMirror` satisfies it structurally, aggregates derive over any view's
20
+ `observe` (`Aggregate.observe`/`observeGrouped`), and `Related`'s read side
21
+ takes a `ClientView.Reader`, so joins and aggregates work over mirrors
22
+ unchanged.
23
+
24
+ For `@thomasfosterau/legation-svelte`, the seam is what lets the same components run over a
25
+ main-thread engine and a service-worker-hosted mirror with zero component
26
+ changes — the Client the hooks read from context is built over either.
27
+
28
+ - 41815c6: `useConnection` — connection-style paging over a read's root window (ADR-0024). `loadMore` widens the one window a page at a time rather than fetching detached pages, so each page's rows accumulate, the previous ones stay on screen through the widen (stale-while-revalidate), and the engine only ever syncs the current window — never the whole collection. The hook returns the live rows (`current`/`promise`) plus `loadMore()`, reactive `hasMore` and `loading`, and `reset()`, and seeds from SSR like the other `use*` hooks.
29
+
30
+ Per-parent paging falls out of instantiating the hook per parent: scope the read's `where` to a parent id and give each parent its own `useConnection` (one per `{#each}` row), and each parent's list pages independently. The passed read's own `first`/`after` are ignored — the hook owns the window — and the read thunk stays reactive, re-basing to the first page when its base changes.
31
+
32
+ - 5496ddf: Bump the pinned Effect version from `4.0.0-beta.84` to `4.0.0-beta.107` across
33
+ all packages (catalog and exact `peerDependencies`), and move
34
+ `@thomasfosterau/legation-jsonapi` and the commerce example from
35
+ `@thomasfosterau/effect-jsonapi@^0.6.0` to `^0.12.0` (#152).
36
+
37
+ **Why beta.107.** effect-jsonapi 0.12 requires `effect >= 4.0.0-beta.104` and
38
+ develops against `4.0.0-beta.107` exactly, so that is the version its typings
39
+ are known to line up with. The later `4.0.0-rc.*` releases were not attempted
40
+ in this change: the exact-pin policy (ADR-0003) prefers the version the
41
+ adopted library is built against, and every `effect/unstable/*` module this
42
+ repo uses (`httpapi`, `http`, `rpc`, `reactivity`, `persistence`, `sql`)
43
+ compiles and tests clean on beta.107.
44
+
45
+ **Effect API drift absorbed (no behavioural change):**
46
+
47
+ - `Schedule.either` / `Schedule.both` are gone; the default retry policy is now
48
+ `Schedule.max([Schedule.min([exponential, spaced]), recurs(5)])`, which is
49
+ the same policy (`min` recurs while any schedule recurs at the shortest
50
+ delay, `max` recurs while all recur at the longest delay).
51
+ - `Schema.TaggedErrorClass` is now `Schema.TaggedError` (the wire
52
+ `SyncRpcError` is unchanged on the wire).
53
+ - `SchemaRepresentation.fromASTs` is now `SchemaRepresentation.toRepresentations`
54
+ (spec export output is unchanged).
55
+ - `HttpApiGroup.Any` is gone and `HttpApi`'s group parameter is now
56
+ invariant, so `JsonApiSync.layer`, `JsonApiSync.inspect` and
57
+ `Convention.reflectResources` are generic over the concrete `HttpApi`
58
+ instead of accepting a widened alias. The exported `JsonApiSync.JsonApi`
59
+ alias is now `HttpApi.Constraint` (identifier and groups erased); pass your
60
+ concrete api to the functions rather than annotating it with the alias.
61
+
62
+ **effect-jsonapi 0.6 → 0.12 at the ADR-0016 boundary.** `Document`, `Query`,
63
+ `Resource`, `Relationship` and `Endpoint` lost no exports; `Query` gained
64
+ `IncludeOption`/`IncludePathsOf`/`OffsetPageStruct`/`bracketPageKeys` and
65
+ `Resource` gained `IncludeDepth`/`IncludePathsTo`. `schema.ts`, `types.ts` and
66
+ `CollectionLoader.ts` compile unchanged. One typing regression was noticed in
67
+ 0.12: endpoints with `:id` params (`Endpoint.get`/`delete`/`update`) declare
68
+ their params as `Schema.Struct<Schema.Struct.Fields & { id }>`, which leaks
69
+ `unknown` into the requirements of the corresponding `HttpApiClient` calls.
70
+ The commerce example narrows this with a small `run` helper in
71
+ `server/client.ts`; nothing in the published packages is affected.
72
+
73
+ The bump is `minor` rather than `patch` because the exact `effect` peer pin
74
+ means every consumer must move `effect` in the same change, and the
75
+ `@thomasfosterau/legation-jsonapi` type signatures above changed shape.
76
+
77
+ - fed4634: Peer the Effect v4 release-candidate line through an open floor rather than
78
+ an exact pin (#222), so an app can install `@thomasfosterau/legation-*` beside
79
+ `@thomasfosterau/effect-auth` and `@thomasfosterau/effect-jsonapi` and resolve
80
+ a single copy of `effect`. The published floor is `>=4.0.0-rc.118` (see
81
+ `effect-rc118-floor.md`), on `effect` and on the `@effect/platform-browser` /
82
+ `@effect/platform-node` peers.
83
+
84
+ **`@thomasfosterau/legation-jsonapi`**: a repeated shorthand filter key
85
+ (`?filter[name]=a&filter[name]=b`) is read as the comma list it spells
86
+ (`filter[name]=a,b`, an `In` predicate) rather than rejected with a 400,
87
+ because `@thomasfosterau/effect-jsonapi` accepts the repeated-key spelling at
88
+ every list-valued position.
89
+
90
+ The packages run in browsers and workers and deliberately declare no `engines`
91
+ (ADR-0003).
92
+
93
+ - fcc52eb: Target Effect `4.0.0-rc.118`: every package peers `effect >=4.0.0-rc.118`
94
+ (#261), and the `@effect/platform-browser` / `@effect/platform-node` peers use
95
+ the same floor. Install `effect` and every `@effect/*` companion at
96
+ `4.0.0-rc.118` or later together.
97
+
98
+ The packages import Effect's modules at their rc.118 paths (`effect/rpc`,
99
+ `effect/http`, `effect/http-api`, …); rc.118 removed the `effect/unstable/*`
100
+ export paths, so the packages don't resolve beside an earlier `effect`.
101
+
102
+ **`@thomasfosterau/legation-jsonapi`** depends on `@thomasfosterau/effect-jsonapi@^0.16.1`,
103
+ the first release that peers `effect >=4.0.0-rc.118`.
104
+
105
+ **`@thomasfosterau/legation-svelte`** depends on `@thomasfosterau/effect-svelte@^0.4.0`, for
106
+ the same reason.
107
+
108
+ - a1b95d4: The rune hooks speak Query over the Client (ADR-0022 step 8). `useQuery`/`useAggregate`/`useAggregateGrouped` take declarative `Query.Read` thunks — `include`d edges resolve onto each row's `related` (windowed per the edge's declared order), so `useRelated` is removed; `useMutation`/`useMutationWithAck` run over the context Client. The hooks read a `ClientRuntime` (`ManagedRuntime<Client.Client>`) provided via `setClientRuntime`. `@thomasfosterau/legation` adds the `Client` service tag and exposes `Client.nodes` (the registry the Client was built from).
109
+ - c0ab703: Keyed reactive mutation-state surface (#116) — TanStack Query's
110
+ `useMutationState` analogue. `ClientEngine.mutationStates` maintains a map
111
+ keyed by `mutationId`, seeded from the journal at boot (a reload shows
112
+ pending intents) and folded forward as mutations are journaled, acknowledged,
113
+ or permanently rejected — regardless of whether they went through `mutate`,
114
+ `mutateEncoded`, or `mutateWithAck` (previously only `mutateWithAck`-registered
115
+ mutations ever settled on `mutationEvents`/`engineEvents`; this fixes that
116
+ gap so the whole journal is observable). Terminal (acknowledged/rejected)
117
+ entries are retained only up to a configurable `mutationStateRetention.maxTerminal`
118
+ bound (default 200), evicted oldest-settled-first, so a mutation storm cannot
119
+ grow the map without limit — the journal stays the durable source of truth
120
+ for pending mutations.
121
+
122
+ Exposed at every layer:
123
+
124
+ - `ClientEngine.mutationStates: Stream<MutationStateMap>` and `MutationState.ts`'s
125
+ pure fold (`seed`/`journaled`/`acknowledged`/`rejected`).
126
+ - `ClientView`/`Client` gain `mutationStates`, so it works identically over an
127
+ owned engine and a connected mirror (ADR-0021) — a minimal mirror-protocol
128
+ extension (`SubscribeMutationStates`/`UnsubscribeMutationStates` mirror→host,
129
+ `MutationStatesUpdate` host→mirrors) broadcasts the whole map, refcounted
130
+ like a subset subscription.
131
+ - `EngineAtoms.mutationStates` (the whole map) and `EngineAtoms.mutationsFor(mutator)`
132
+ (a derived per-mutator selector).
133
+ - React `useMutationState(mutator?)` — always a plain array, oldest-journaled
134
+ first, no `AsyncResult` handling needed.
135
+ - Svelte `useMutationState(mutator?)` rune (`@thomasfosterau/legation-svelte`), reactive
136
+ to a `mutator` thunk like `useQuery`'s `read`.
137
+
138
+ The todo example's journal panel is written over this surface,
139
+ replacing its bespoke `Introspection`-based wiring, and demonstrates bounded
140
+ terminal retention.
141
+
142
+ - 8c65627: Form actions run named Mutations (ADR-0030, amendment #215).
143
+
144
+ `@thomasfosterau/legation-svelte/kit` adds:
145
+
146
+ - `mintFormClient` for the page that renders a form (a fresh form client id
147
+ with mutation id `1`, rendered in the `FORM_CLIENT_FIELD` and
148
+ `FORM_MUTATION_FIELD` hidden fields);
149
+ - `formMutationTarget`, which reads the client group from its cookie (never
150
+ minted there) and the form client from the posted fields;
151
+ - `runFormMutation`, which hands the form layer's decoded args to
152
+ `server.mutate` and answers the receipt the action returns under
153
+ `RECEIPT_KEY`;
154
+ - `enhanceFormMutation`, the enhanced submit for `use:enhance`. Each submit
155
+ mints a fresh form client, overlays the Mutation's effect, and settles on
156
+ the receipt. SvelteKit's post-action `load` re-run is off by default.
157
+
158
+ An action without the client-group cookie stays an out-of-band writer. The
159
+ client-group cookie's default name is now exported as `CLIENT_GROUP_COOKIE`.
160
+
161
+ `@thomasfosterau/legation` adds the enhanced submit's client half:
162
+
163
+ - `ClientEngine.beginFormMutation` and `Client.beginFormMutation` apply a
164
+ Mutation's local handler to the overlay under a form client's id without
165
+ journaling it, and return a submission to settle on the action's receipt.
166
+ - An `acknowledged` receipt moves the entry to the unpulled tail. The pull
167
+ whose `lastMutationIds` covers the form client releases it, as it does a
168
+ pushed mutation.
169
+ - A `rejected` receipt drops the entry, and `abandon` drops it when the action
170
+ did not answer. Either way a pull is signalled.
171
+
172
+ The entry holds a place in the tail's bound from the start, and only in
173
+ memory. Over a connected host (a mirror) `Client.beginFormMutation` fails with
174
+ a `TransportError`.
175
+
176
+ - 3538a81: Depend on the engine core under its new name, `@thomasfosterau/legation` (formerly `@thomasfosterau/legation-effect`), and its engine seams at `@thomasfosterau/legation/internal`. Install `@thomasfosterau/legation` alongside these packages and import the core from it.
177
+ - c47786c: `@legation/sveltekit` is merged into `@thomasfosterau/legation-svelte` as the
178
+ `@thomasfosterau/legation-svelte/kit` subpath, and `Hydration` moves to the public
179
+ `@thomasfosterau/legation` surface.
180
+
181
+ The split was never a dependency boundary: the SvelteKit package contained no
182
+ Svelte code, and its whole `@sveltejs/kit` coupling was two `import type
183
+ { Cookies }` statements — type-only, erased at build. `@sveltejs/kit` is now an
184
+ **optional** peer dependency of `@thomasfosterau/legation-svelte`, so a non-Kit Svelte app
185
+ pulls nothing extra, and only `@thomasfosterau/legation-svelte/kit` needs it.
186
+
187
+ The bindings shrink to one surface, on one entry point. The `Readable`-store
188
+ tier (`Query.query` / `namedQuery` / `aggregate` / `related`,
189
+ `Mutation.getMutate`, `undoControls`, and `Runtime.setEngineRuntime`) is
190
+ removed: it duplicated the rune hooks, and the package's own `svelte >= 5.40`
191
+ peer already ruled out the Svelte 4 audience it existed for. The hooks move
192
+ from the `/Runes` subpath to the package root — they are the API, so they are
193
+ not something to reach for separately — and gain **`useUndo(manager)`**, the
194
+ rune equivalent of the store-based `undoControls`, so nothing is lost. The
195
+ root is now a `.svelte.js` module with a `svelte` export condition; the
196
+ namespaces are gone, so `setClientRuntime` and the hooks are flat named
197
+ exports.
198
+
199
+ Hydration is now a core concept rather than a SvelteKit one. `Hydration` is
200
+ exported from `@thomasfosterau/legation` (it was behind `/internal`) and speaks
201
+ `Query.Read` instead of `Subset`: `snapshot({ nodes, reads })` and
202
+ `rowsFor(snapshot, nodes, read)`. `@thomasfosterau/legation-svelte/kit` keeps only the
203
+ framework's share — the client-group cookie, a `load`-shaped wrapper, and
204
+ `clientRuntime`, which replaces `engineRuntime` and returns the `Client`
205
+ runtime `setClientRuntime` expects (built on `Client.make`, so it starts the
206
+ sync loop itself).
207
+
208
+ Migration: `@thomasfosterau/legation-svelte/Runes` → `@thomasfosterau/legation-svelte`;
209
+ `Runtime.setClientRuntime` → `setClientRuntime`;
210
+ `@legation/sveltekit` → `@thomasfosterau/legation-svelte/kit`;
211
+ `engineRuntime({ engine: {...} })` → `clientRuntime({ nodes, mutations, ... })`;
212
+ `setEngineRuntime` → `setClientRuntime`; `query(subset)` → `useQuery(() => read)`;
213
+ `getMutate()` → `useMutation()`; `undoControls(m)` → `useUndo(m)`;
214
+ `Hydration.rowsFor(snapshot, subset)` → `Hydration.rowsFor(snapshot, nodes, read)`.
215
+
216
+ - 7f1efac: Named queries and auto-subscribing relational reads cross the mirror
217
+ protocol (ADR-0021 follow-up). `ClientView` gains
218
+ `subscribeQuery`/`unsubscribeQuery`/`queryNamed`/`observeNamed` and
219
+ `related` — both hosts satisfy the widened seam (`ClientViewError` now
220
+ includes `QueryError`). The tab↔host protocol adds
221
+ `SubscribeNamed`/`UnsubscribeNamed` (the host engine owns the server
222
+ round-trip) and `SubscribeRelated`/`UnsubscribeRelated`, where the host runs
223
+ the reconcile loop AND the join and the tab receives resolved rows
224
+ (`RelatedUpdate`) — data work stays off the main thread. `EngineHost` takes
225
+ an optional `registry` for relational resolution and refcounts all
226
+ registration kinds per tab. A new `HostReady` announcement makes
227
+ registrations survive host restarts: a starting host broadcasts it and
228
+ every mirror replays the registrations it holds — covering killed service
229
+ workers and tabs that raced the worker's boot. On a mirror, observing IS
230
+ subscribing: `TabMirror.observe`/`observeNamed`/`related` auto-register
231
+ their subject with the host for the stream's lifetime through a per-key
232
+ interest ledger (a mirror only carries keys some tab registered, and the
233
+ host refcounts per tab, so the ledger posts `Subscribe` on first interest
234
+ and `Unsubscribe` only when the last observer ends and no explicit
235
+ `subscribe` pins the key). In `@thomasfosterau/legation-svelte`, `namedQuery`,
236
+ `useNamedQuery`, and `useRelated({ subscribe: true })` now work over
237
+ mirror-only runtimes (the internal engine-lookup fallback is removed).
238
+ - f54f568: Per-mutation outcome surface (#36). Callers can now await a mutation's
239
+ authoritative backend outcome — applied, or permanently rejected with a reason
240
+ (#35) — rather than only its journal ack.
241
+
242
+ - `ClientEngine.mutateWithAck` returns a `MutationHandle` whose `acknowledged`
243
+ effect resolves with the `MutationOutcome` once the backend settles the
244
+ mutation via a push or pull acknowledgement. `ClientEngine.mutationEvents`
245
+ streams the lifecycle (`journaled`, then `acknowledged` or `rejected`). Plain
246
+ `mutate`/`mutateEncoded` are unchanged and register no deferred; pending
247
+ acknowledgements are interrupted on engine scope close, so they never hang.
248
+ - Svelte gains `useMutationWithAck` (over an additive
249
+ `EngineAtoms.mutationWithAck` atom), returning a promise of the outcome.
250
+ - The tab mirror forwards the outcome: `MirrorProtocol` gains an additive
251
+ `MutateSettled` host→tab message, `EngineHost` awaits the backend verdict on a
252
+ scoped fibre and posts it, and `TabMirror.mutateWithAck` awaits it (the
253
+ existing journal-ack `mutate` is unchanged).
254
+
255
+ - bc76e11: Every rune hook that takes a read thunk (`useQuery`, `useConnection`, `useNamedQuery`, `useAggregate`, `useAggregateGrouped`) lets the thunk return `undefined` to opt out — "no read right now," for a read keyed on a selection that starts empty. No window is registered and no request is issued, and `current` stays at its initial state rather than becoming a `Success` over zero rows, so an opted-out read stays distinguishable from one that legitimately matched nothing. The thunk stays reactive either way: flipping the condition subscribes/unsubscribes with the same stale-while-revalidate behaviour as any other input change.
256
+
257
+ This replaces the sentinel-argument workaround (a read keyed on `id ?? ""` to make it match nothing) — the Northwind orders screen and the Conduit editor example both drop theirs in favor of returning `undefined`.
258
+
259
+ - 9d2f21b: Rename the project to **Legation**. Every package moves to the `@legation`
260
+ scope: the engine is now `@legation/effect` (formerly
261
+ `@thomasfosterau/effect-sync-core`), and the adapters/bindings are
262
+ `@legation/browser`, `@legation/node`, `@legation/jsonapi`, `@legation/sql`,
263
+ and `@legation/svelte` (formerly `@thomasfosterau/effect-sync-*`).
264
+
265
+ Runtime identifiers follow the brand, which is **breaking for persisted local
266
+ state and dashboards**:
267
+
268
+ - `Context.Service` identifiers: `@thomasfosterau/effect-sync-core/*` →
269
+ `@legation/effect/*` (and per-package equivalents)
270
+ - Client-store key prefix: `effect-sync/` → `legation/`; CVR store prefix:
271
+ `effect-sync/cvr/` → `legation/cvr/`. Existing IndexedDB/KV state under the
272
+ old prefix is orphaned — clients rehydrate via a full pull; export any
273
+ pending journal before upgrading offline clients.
274
+ - Metrics: `effect_sync_*` → `legation_*`; spans: `effect-sync.*` →
275
+ `legation.*`
276
+ - Default BroadcastChannel name: `effect-sync` → `legation`; Redis hint
277
+ stream: `effect-sync:hints` → `legation:hints`; cache metadata header:
278
+ `x-effect-sync-cache-meta` → `x-legation-cache-meta`
279
+
280
+ - 16889f0: Rename every package from the `@legation/*` scope to
281
+ `@thomasfosterau/legation-*` (#303): `@legation/effect` is now
282
+ `@thomasfosterau/legation`, and likewise for `browser`, `cloudflare`,
283
+ `devtools`, `jsonapi`, `node`, `sql` and `svelte`. Subpath entries keep their
284
+ paths (`@thomasfosterau/legation/internal`,
285
+ `@thomasfosterau/legation-svelte/kit`,
286
+ `@thomasfosterau/legation-devtools/svelte`). Update imports and dependency
287
+ names accordingly; no `@legation/*` name was ever published to npm.
288
+
289
+ `Context.Service` identifiers and `Symbol.for` keys follow the package name
290
+ (`@legation/effect/Client` → `@thomasfosterau/legation/Client`, and
291
+ per-package equivalents). The reserved identity sentinels
292
+ (`@legation/anonymous`, `@legation/unstamped`), the `legation/` client-store
293
+ prefixes, metric and span names are unchanged.
294
+
295
+ - f441941: SSR snapshots over a `ClientTransport`, and a Cloudflare service-binding
296
+ carrier to take them (#211).
297
+
298
+ `Hydration.snapshot` no longer requires an in-process `ServerHandler`: pull is
299
+ the correctness path, so subscribe → pull → dehydrate works identically
300
+ against a remote Server. Pass `transport` and the render needs nothing from
301
+ context; omit it and the in-process path is unchanged — a loop-back transport
302
+ over the in-context handler. `loadSnapshot` in `@thomasfosterau/legation-svelte/kit` takes
303
+ the same option, so an Effect-based `load` can render against a backend in
304
+ another Worker.
305
+
306
+ `@thomasfosterau/legation-alchemy-cloudflare` provides `BindingTransport`: the sync protocol over a
307
+ service binding (`env.API.fetch`), for the ordinary two-Worker shape where the
308
+ Worker rendering the page is not the Worker holding the database. It forwards
309
+ the inbound cookie and origin so the bound Worker resolves the same caller —
310
+ and therefore the same room — the browser will reach on its own first pull.
311
+ Getting that wrong is silent: the snapshot still carries the right rows while
312
+ its cookie belongs to another room's CVR, degrading every first pull to the
313
+ reset path. The package's tests pin both sides of it.
314
+
315
+ - 9af9947: New package `@thomasfosterau/legation-svelte`: idiomatic Svelte bindings. `setClientRuntime`
316
+ supplies the `ManagedRuntime` carrying the `Client`; the rune hooks
317
+ (`useQuery` / `useConnection` / `useNamedQuery` / `useAggregate` /
318
+ `useAggregateGrouped` / `useMutation` / `useMutationWithAck` /
319
+ `useMutationState` / `useUndo`) are the whole read/write surface, each taking
320
+ its inputs as a thunk so a read re-subscribes when the reactive state it
321
+ touches changes. Extends the framework-bindings coverage (#25) to Svelte.
322
+ - bd0fd8b: Boot the bindings without an Effect runtime (#147). `createClient(options)` takes `Client.make`'s options and returns a `ClientHandle` with its own `dispose`; `setClient(handle)` puts it in context and `getClient()` reads it back. `setClient(undefined)`, as on the server, renders every hook from its `initial` seed, and a hook that needs the Client fails with `ClientUnavailable`, so no stand-in runtime is needed during SSR. A runtime the app builds itself still works: `fromRuntime(runtime)`, or `setClientRuntime(runtime)`. The error for a hook used with nothing in context now names `setClient(createClient(…))`.
323
+ - a69a914: Add a `related` store: a live join over a subset's declared relationships
324
+ (`Related.observe` bridged to a Svelte `Readable`), replacing the hand-rolled
325
+ `relatedStore` pattern in the Svelte examples. The base and foreign
326
+ collections must already be subscribed — the store reads the mirror.
327
+ - 5ced720: Add a rune-based API for Svelte 5 at `@thomasfosterau/legation-svelte/runes`: `useQuery`,
328
+ `useNamedQuery`, `useAggregate`, `useAggregateGrouped`, `useRelated` (with an
329
+ auto-subscribing `subscribe: true` variant) and `useMutation` /
330
+ `useMutationWithAck`. Hooks take their inputs as thunks tracked by `$effect`,
331
+ so a query re-subscribes when its reactive inputs change (e.g. a `$state`
332
+ pagination cursor) — something the store API cannot express — and expose a
333
+ reactive `current` `AsyncResult` with stale-while-revalidate `waiting`
334
+ semantics, matching the engine's reactivity atoms and the React binding.
335
+ Every live hook accepts `{ initial }` to seed a synchronous `Success` before
336
+ the first emission — what SSR renders, and what makes the first client render
337
+ match the server markup. The store API on the package root is unchanged and
338
+ remains Svelte-4 compatible.
339
+ - 4b05d55: SvelteKit integration (ADR-0020), on the `@thomasfosterau/legation-svelte/kit` subpath.
340
+ `clientGroupId` reads-or-mints the browser's client-group cookie;
341
+ `loadSnapshot` resolves it and produces the page's SSR snapshot over a
342
+ request-scoped engine inside any Effect-based `load`; `clientRuntime` boots the
343
+ Client over a snapshot-hydrated durable store (`Hydration.hydrateIfEmpty`) and
344
+ yields the runtime `setClientRuntime` consumes.
345
+
346
+ `@sveltejs/kit` is an optional peer dependency and appears only as a `Cookies`
347
+ type, so a non-Kit Svelte app pulls nothing.
348
+
349
+ - 0470e1c: Add a typed query surface over the existing untyped `Subset`/`LocalRow` (purely
350
+ additive — the protocol-level and dynamic surface is unchanged).
351
+
352
+ - `Subset.for(collection).make({ where, orderBy, limit, cursor })` builds a
353
+ `TypedSubset<A>`: field names and literal types in `where` (via a
354
+ field-checked `TypedPredicate<A>`) and `orderBy` are checked against the
355
+ collection's row type. It erases to a plain `Subset` at runtime.
356
+ - `ClientEngine.query`/`observe`, `EngineAtoms.liveQuery`, the React `useQuery`
357
+ and the Svelte `query` store now refine their row type to `A` when handed a
358
+ `TypedSubset<A>` (a trust-cast — the read path still never decodes, per
359
+ ADR-0014), so the row type flows through without `row.value as T` casts. Plain
360
+ `Subset` callers are unaffected.
361
+ - `ClientEngine.observe(subset, { select, equals? })` returns a projected
362
+ stream that re-emits only when the projected value changes (reference
363
+ equality by default), so a change to a field the projection ignores no longer
364
+ wakes the observer.
365
+ - Named-query cache/subscription keys (`ClientEngine`'s named subscriptions and
366
+ `EngineAtoms.namedQuery`) now key on `Hash.canonicalJson` rather than
367
+ `JSON.stringify`, so two structurally-equal args objects with different key
368
+ order resolve to the same subscription entry and the same Atom.
369
+
370
+ - 439f449: Add `Undo`: an optional, additive undo/redo module layered over anything that
371
+ can `mutate(name, args)` — no engine surgery. It follows the **paired-intents**
372
+ design (the `@rocicorp/undo` shape): the caller supplies both directions of a
373
+ change — an `execute` intent and its reversing `undo` intent — and
374
+ `Undo.make(handle)` returns an `UndoManager` whose `run` / `undo` / `redo`
375
+ replay through the handle's ordinary `mutate` pipeline. A `ClientEngine` and a
376
+ `Client` both satisfy the accepted `MutateHandle` shape structurally; because
377
+ their `mutate` methods don't share an error channel, both `MutateHandle` and
378
+ `UndoManager` are generic in that error type, so a failed mutation is still
379
+ observable as whatever the supplied handle fails with. Because undo and redo
380
+ are just mutations, they are optimistic, journaled, rebased, and offline-safe
381
+ like any write; the module only owns the history.
382
+
383
+ History is **in-memory and per-client** (one manager per engine, a pair of
384
+ stacks in a `SubscriptionRef`) — it does not survive a reload, though the
385
+ journal still carries any pending undo/redo mutations to the backend. `run`
386
+ clears the redo stack (a new action invalidates redo); `undo` and `redo` are
387
+ no-ops (never errors) when their stack is empty. `group(effect)` coalesces every
388
+ `run` made while `effect` runs into a single step: one `undo` replays each
389
+ member's `undo` in reverse order, one `redo` replays each member's `execute` in
390
+ order. `canUndo` / `canRedo` are one-shot effects; `canUndoChanges` /
391
+ `canRedoChanges` are `SubscriptionRef.changes`-backed streams for bindings.
392
+
393
+ The **delete-then-undo** criterion is met by the paired-intents pattern: capture
394
+ the row's pre-delete value from the local view at run time and pass it as the
395
+ undo's re-create args, so an undo restores the row exactly.
396
+
397
+ `@thomasfosterau/legation-svelte` gains `useUndo(manager)` — `canUndo` / `canRedo` as runes
398
+ over the manager's change streams, plus `undo` / `redo` handlers. The `todo`
399
+ example wires ⌘Z / Ctrl+Z (Shift for redo) through the manager and documents
400
+ the recipe.
401
+
402
+ ### Patch Changes
403
+
404
+ - d76b04d: Docs: add the "Coming from TanStack Query / SwiftData / SWR" capability guide
405
+ (`docs/comparisons/coming-from-tanstack-query.md`) and link it from each package README.
406
+ - c8d9afc: Breaking: `Client.make` now returns `{ client, engine, store }` (`Client.Made`) instead of the Client alone (#142). The engine and store are the ones the Client runs over, so an app can attach devtools (`Devtools.make(engine, store, …)`) without building a `ClientEngine` by hand. Migrate with `const { client } = yield* Client.make(…)`. `Client.connect` is unchanged: a Client joined to a hosted engine owns neither. The package is 0.x, so this is a minor.
407
+ - 8995115: Engine-level reactive window retargeting (#133 item 3): `ClientEngine.
408
+ observeWindow(subset)` returns a `WindowObservation` (`rows` + `retarget`)
409
+ whose `retarget({ limit?, cursor? })` widens or advances the SAME open
410
+ observation's window in place — reusing `LiveView`'s maintained order
411
+ (`LiveView.retarget`, #112) instead of tearing the subscription down and
412
+ rebuilding it from scratch — and keeps the engine's tracked subscription in
413
+ step, so the next `pull` fetches the wider window from the server too.
414
+ `ClientView.observeWindow` exposes it over the `ClientView` seam for an
415
+ engine-backed view (present only there — a `Mirror` has no maintained
416
+ window of its own to retarget). `Subscriptions.acquireWindow` composes the
417
+ usual refcounted subscribe/release with it for callers that want lifecycle
418
+ management alongside retargeting.
419
+
420
+ `@thomasfosterau/legation-svelte`'s `useConnection` (`hooks.svelte.ts`) now retargets
421
+ through this seam for the common case (no `include`d edges, an engine-backed
422
+ view): `loadMore`/`reset` widen/shrink the same open observation instead of
423
+ letting `pages` force a resubscribe. A joined read, or a mirror-backed
424
+ connection, falls back to the original resubscribe-per-page behavior,
425
+ unchanged. No public API changes — `useConnection`'s signature and observed
426
+ behavior (growing window, `hasMore`, `reset`) are the same; only the
427
+ underlying re-registration is gone.
428
+
429
+ - ae18cb3: Add a one-sentence doc comment to every exported declaration that had none, so editor hovers on the published `.d.ts` say what each export is for. This covers the root `@thomasfosterau/legation` surface (`Backend`, `Server`, `ClientStore`, the `Predicate` and `Aggregate` constructors, …), its `/internal` seams, and the options and config interfaces of the other packages. The `@thomasfosterau/legation` module header now counts seven concepts (Hydration joined ADR-0022's six). Documentation only; no API or behaviour changes.
430
+ - d1b01a4: Tested against `effect` 4.0.0 (and the matching `@effect/*` companions); the
431
+ peer floor is unchanged at `effect >=4.0.0-rc.118`.
432
+
433
+ `@thomasfosterau/legation` adapts to 4.0.0's stricter, type-only `Schema.brand`:
434
+
435
+ - `Node.Id` keeps its signature and runtime behaviour; its internal brand
436
+ identifier is cast to satisfy the new concrete-identifier check.
437
+ - `Node.AnyNode`'s `Id` property is now
438
+ `Schema.Codec<string & Brand.Brand<string>, string>` instead of `Id<string>`.
439
+ 4.0.0's `brand<String, B>` is type-only and no longer assignable to a wider
440
+ ``brand<String, `${string}/id`>``, so no concrete `Node<"todos", …>` was
441
+ assignable to `AnyNode`. Every concrete node is assignable again, and `Id`
442
+ is still a string-encoded branded id schema; only code that reads `.Id` off
443
+ an `AnyNode`-typed value sees the wider brand.
444
+
445
+ Checked the 4.0.0 behavioural changes against the packages and found no impact:
446
+ no use of `partition`/`separate`/`Option.partitionMap`, `Queue.State`,
447
+ `MessageStorage` or `RpcMessage.ExitEncoded`. The RPC client's missed-pong
448
+ handling now fails in-flight WebSocket calls rather than reporting a transient
449
+ error; `RpcTransport.fromOrigin` already treats a failed WebSocket call as a
450
+ `TransportError` and falls back to HTTP. The RPC server's WebSocket route now
451
+ closes with 1000/1001/1011, which nothing in the packages inspects.
452
+
453
+ - c5d3484: README: the `effect` floor (`4.0.0-rc.118`) is now tested on every pull
454
+ request, where CI runs the test suite against it, instead of in a weekly
455
+ canary. The peer range (`effect >=4.0.0-rc.118`) and the tested pin (`4.0.0`)
456
+ are unchanged.
457
+ - e9363c0: Peer `effect ^4.0.0` (was `>=4.0.0-rc.118`), and `@effect/platform-node` /
458
+ `@effect/platform-browser` `^4.0.0` likewise, per the peer-range policy: the
459
+ range is raised only when a dependency requires it. The range form changes
460
+ (the caret no longer admits the 4.0 release candidates or Effect 5); every
461
+ package's tests pass at `effect` 4.0.0.
462
+
463
+ `@thomasfosterau/legation-svelte` peers `effect ^4.0.2`: its dependency
464
+ `@thomasfosterau/effect-svelte` 0.6.0 peers `effect >=4.0.2`.
465
+
466
+ - c3353dc: Depend on `@thomasfosterau/effect-svelte` `^0.6.0` (was `^0.5.0`).
467
+ - 63bc424: Require `@thomasfosterau/effect-svelte` >= 0.3.2, which ships a `svelte` export condition. Consumers no longer need the manual dual-svelte workarounds — vite-plugin-svelte now auto-detects effect-svelte as a Svelte library and shares the app's single copy of svelte. The commerce example drops its `optimizeDeps.exclude` / `ssr.noExternal` for effect-svelte accordingly (verified in a real dev-server browser run: no `lifecycle_outside_component`). Vitest still needs its `server.deps.inline` — that's a vitest node-module externalization concern, independent of the export condition.
468
+ - ebcc6aa: Carry the enhanced form submit across the mirror protocol (#536). `Client.beginFormMutation` (and `/kit`'s `enhanceFormMutation`) now works over `Client.connect`: the mirror asks its host to begin the form mutation, and settles or abandons it on the receipt. The host's engine holds the in-flight entry, so its overlay and unpulled tail serve every mirror, and the effect is still released by the pull that covers the form client, not by the action's response. A mirror that disconnects or is swept abandons the entries it held.
469
+
470
+ `ClientEngine` gains `beginFormMutationEncoded`, the form submit's counterpart of `mutateEncoded`: the host decodes the args the mirror encoded rather than encoding them again, so a Mutation whose args schema transforms (such as `Schema.NumberFromString`) works over a mirror. When the host's store fails to park an acknowledged entry, the engine now still rebuilds its overlay and pulls, and the failure reaches the caller's `settle` as a `StoreError`.
471
+
472
+ Wire format: the begin travels as a `Mutate` naming the reserved mutator `MirrorProtocol.FORM_MUTATION_MUTATOR`, and the new `EndFormMutation` (mirror → host) and `FormMutationBegun` / `FormMutationFailed` / `FormMutationEnded` (host → mirror) messages carry the rest. The form replies name the mirror they answer, since host messages reach every mirror and each numbers its requests from zero. A host that predates this answers the begin as an unknown mutator, and the mirror reports that as a `TransportError` saying the host must be upgraded, instead of waiting for an answer that never comes. No app mutator may use the reserved name.
473
+
474
+ - d24594e: The rune hooks and the engine runtime context are now built on `@thomasfosterau/effect-svelte` (^0.3.1): `useLiveStream` replaces the hand-rolled `$effect`/fiber plumbing behind every `use*` hook, and `createRuntimeContext` provides the typed context behind `setClientRuntime`/`getClientRuntime`. Hook APIs are unchanged. One semantics refinement: an `initial` seed now starts as a **non-waiting** `Success` (no spinner over server-rendered content) until the first live emission replaces it; re-subscription stale-while-revalidate behavior is unchanged.
475
+ - 507ccb1: Every tagged error is now a branded `Schema.TaggedError` rather than a
476
+ `Data.TaggedError`: `StoreError`, `TransportError`, `QueryError` and the rest
477
+ of the engine's errors, the JSON:API adapter's `SyncError` / `NetworkError` /
478
+ `ParseError` / `ApiError` / `ConflictError` / `ResourceBridgeError`, and
479
+ `HostNotReady`. Construction, `_tag`, `message` and `Effect.catchTag` are
480
+ unchanged; each class now carries a schema and a nominal brand, so a
481
+ structurally similar object no longer type-checks as one.
482
+ - a01f43b: The public surface is the six concepts (ADR-0022 step 10b). `@thomasfosterau/legation`'s root index shrinks from 43 namespaces to the resource-graph API — `Node`, `Edge`, `Collection`, `Query`, `Aggregate`, `Mutation` (with `Mutator` as a deprecated alias), `Client`, `ClientStore`, `Transport`, `Server`, `Backend`, `MemoryBackend`, `Authorization`, `Predicate`, and the error classes. Everything else — the engine, the mirror seam, plan vocabulary (`Subset`/`Related`), protocol codecs, reactivity plumbing, and server assembly parts — moves to the new `@thomasfosterau/legation/internal` subpath for adapter authors, which the sibling `@thomasfosterau/legation-*` packages now compose from.
483
+ - b1be47b: Support SvelteKit 3: the optional `@sveltejs/kit` peer widens from `^2.0.0` to
484
+ `^2.0.0 || ^3.0.0`. `@thomasfosterau/legation-svelte/kit` uses Kit only as the `Cookies` type
485
+ and always sets the client-group cookie's `path` explicitly (default `"/"`), so
486
+ it behaves the same on both majors; none of the APIs Kit 3 removes or moves
487
+ (`$app/paths` `base`/`assets`/`resolveRoute`, `$app/stores`, `$service-worker`,
488
+ `goto` options, the navigation and form type moves) are used. Tested against
489
+ Kit 3.0.1 (the package's dev pin, and the example apps) and Kit 2.69.1 (the
490
+ package suite run with its dev pin moved back to Kit 2).
491
+ - 97cc5f9: Build with `vp pack` (tsdown) instead of `tsc --build`. Each entry point is now one ESM bundle with one declaration file (the two `@thomasfosterau/legation` entries share a chunk, so a value reached through `.` and `./internal` is still one instance). The `exports` map, entry file names and published dependency and peer ranges are unchanged, and nothing from npm is bundled. The source also moved to the Effect lint presets' idioms (namespace imports from `effect/*` submodules, `effect/Predicate` guards, tag-exact error recovery); behaviour is unchanged.
492
+ - Updated dependencies [2a1f63c]
493
+ - Updated dependencies [ef59fa7]
494
+ - Updated dependencies [56a2e51]
495
+ - Updated dependencies [d09dc65]
496
+ - Updated dependencies [db2366a]
497
+ - Updated dependencies [97db489]
498
+ - Updated dependencies [cc67999]
499
+ - Updated dependencies [a94df19]
500
+ - Updated dependencies [d76b04d]
501
+ - Updated dependencies [6f53fb1]
502
+ - Updated dependencies [d675d2e]
503
+ - Updated dependencies [1743c2b]
504
+ - Updated dependencies [1c56914]
505
+ - Updated dependencies [615e792]
506
+ - Updated dependencies [c8d9afc]
507
+ - Updated dependencies [4709f41]
508
+ - Updated dependencies [f0348b4]
509
+ - Updated dependencies [3e29e1b]
510
+ - Updated dependencies [a053736]
511
+ - Updated dependencies [8a8adfc]
512
+ - Updated dependencies [8995115]
513
+ - Updated dependencies [8995115]
514
+ - Updated dependencies [87fa7a5]
515
+ - Updated dependencies [822001c]
516
+ - Updated dependencies [4c59860]
517
+ - Updated dependencies [961e604]
518
+ - Updated dependencies [5937efe]
519
+ - Updated dependencies [08ac9cd]
520
+ - Updated dependencies [1fc3725]
521
+ - Updated dependencies [8995115]
522
+ - Updated dependencies [8a8adfc]
523
+ - Updated dependencies [025fabb]
524
+ - Updated dependencies [0b8fccb]
525
+ - Updated dependencies [e7369e1]
526
+ - Updated dependencies [8995115]
527
+ - Updated dependencies [c856d66]
528
+ - Updated dependencies [01a8c64]
529
+ - Updated dependencies [3362c37]
530
+ - Updated dependencies [62dcb1c]
531
+ - Updated dependencies [be4be06]
532
+ - Updated dependencies [ae18cb3]
533
+ - Updated dependencies [be0a44d]
534
+ - Updated dependencies [cc2f5b7]
535
+ - Updated dependencies [e818dc4]
536
+ - Updated dependencies [d1b01a4]
537
+ - Updated dependencies [5496ddf]
538
+ - Updated dependencies [19eb4b0]
539
+ - Updated dependencies [a874acb]
540
+ - Updated dependencies [c5d3484]
541
+ - Updated dependencies [38c3147]
542
+ - Updated dependencies [e9363c0]
543
+ - Updated dependencies [fed4634]
544
+ - Updated dependencies [fcc52eb]
545
+ - Updated dependencies [454e75c]
546
+ - Updated dependencies [2003cd6]
547
+ - Updated dependencies [52c614f]
548
+ - Updated dependencies [94d6327]
549
+ - Updated dependencies [91f07db]
550
+ - Updated dependencies [6e9e119]
551
+ - Updated dependencies [6c2c802]
552
+ - Updated dependencies [ebcc6aa]
553
+ - Updated dependencies [a1b95d4]
554
+ - Updated dependencies [c105fbf]
555
+ - Updated dependencies [e1a028a]
556
+ - Updated dependencies [dfb8759]
557
+ - Updated dependencies [9260efb]
558
+ - Updated dependencies [0644af5]
559
+ - Updated dependencies [4b05d55]
560
+ - Updated dependencies
561
+ - Updated dependencies [82911cc]
562
+ - Updated dependencies [9c9f2b0]
563
+ - Updated dependencies [7b21d11]
564
+ - Updated dependencies [6f54de8]
565
+ - Updated dependencies [ab1fb4e]
566
+ - Updated dependencies [792d8f5]
567
+ - Updated dependencies [a3b2ee4]
568
+ - Updated dependencies [eb9555a]
569
+ - Updated dependencies [c0ab703]
570
+ - Updated dependencies [8c65627]
571
+ - Updated dependencies [26ad3f3]
572
+ - Updated dependencies [d7cafd2]
573
+ - Updated dependencies [cb0a33d]
574
+ - Updated dependencies [912d95d]
575
+ - Updated dependencies [7290d2b]
576
+ - Updated dependencies [41c33e3]
577
+ - Updated dependencies [a77261c]
578
+ - Updated dependencies [c47786c]
579
+ - Updated dependencies [501b12c]
580
+ - Updated dependencies [7f1efac]
581
+ - Updated dependencies [a04918b]
582
+ - Updated dependencies [cf0b26c]
583
+ - Updated dependencies [ddb1fda]
584
+ - Updated dependencies [f54f568]
585
+ - Updated dependencies [fc880dc]
586
+ - Updated dependencies [9cbd160]
587
+ - Updated dependencies [0bebedf]
588
+ - Updated dependencies [bc4c599]
589
+ - Updated dependencies [20a6e18]
590
+ - Updated dependencies [b4d2b80]
591
+ - Updated dependencies [2201b4c]
592
+ - Updated dependencies [0cdefe7]
593
+ - Updated dependencies [66f3b5b]
594
+ - Updated dependencies [7e11e61]
595
+ - Updated dependencies [6175e38]
596
+ - Updated dependencies [caf4761]
597
+ - Updated dependencies [821b10d]
598
+ - Updated dependencies [9e74a83]
599
+ - Updated dependencies [49fd066]
600
+ - Updated dependencies [06405ed]
601
+ - Updated dependencies [e7f4871]
602
+ - Updated dependencies [916d91b]
603
+ - Updated dependencies [47f4e15]
604
+ - Updated dependencies [f829146]
605
+ - Updated dependencies [13658a7]
606
+ - Updated dependencies [31cfb2c]
607
+ - Updated dependencies [a1cea1c]
608
+ - Updated dependencies [7e94abc]
609
+ - Updated dependencies [4fbd0b7]
610
+ - Updated dependencies [9d2f21b]
611
+ - Updated dependencies [3016170]
612
+ - Updated dependencies [f4a1a60]
613
+ - Updated dependencies [507ccb1]
614
+ - Updated dependencies [c671ccf]
615
+ - Updated dependencies [16889f0]
616
+ - Updated dependencies [fb2a992]
617
+ - Updated dependencies [22f320d]
618
+ - Updated dependencies [652ae7b]
619
+ - Updated dependencies [83bcf76]
620
+ - Updated dependencies [20e57d3]
621
+ - Updated dependencies [f010749]
622
+ - Updated dependencies [8d02113]
623
+ - Updated dependencies [bf9054d]
624
+ - Updated dependencies [e40c73a]
625
+ - Updated dependencies [ca38874]
626
+ - Updated dependencies [27e1040]
627
+ - Updated dependencies [e68eb3e]
628
+ - Updated dependencies [985c4d5]
629
+ - Updated dependencies [3be5abc]
630
+ - Updated dependencies [97db489]
631
+ - Updated dependencies [4a23603]
632
+ - Updated dependencies [f441941]
633
+ - Updated dependencies [a01f43b]
634
+ - Updated dependencies [9a6426b]
635
+ - Updated dependencies [97cc5f9]
636
+ - Updated dependencies [fc880dc]
637
+ - Updated dependencies [e34bc24]
638
+ - Updated dependencies [0470e1c]
639
+ - Updated dependencies [439f449]
640
+ - Updated dependencies [3a88118]
641
+ - Updated dependencies [1b8b120]
642
+ - Updated dependencies [cf48723]
643
+ - Updated dependencies [b4489c5]
644
+ - @thomasfosterau/legation@0.1.0