@rindle/optimistic 0.4.3 → 0.5.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 CHANGED
@@ -1,70 +1,39 @@
1
1
  # @rindle/optimistic
2
2
 
3
- Optimistic writes over the local-first wasm engine (`OPTIMISTIC-WRITES-DESIGN.md`).
3
+ The browser store for a synced Rindle app: optimistic writes over the local-first wasm
4
+ engine. `createRindleClient` boots the wasm engine, opens the ws subscription to the
5
+ daemon, resolves query leases through your API server, and runs the mutation queue.
4
6
 
5
- The local IVM engine **is** the rebase function: the wasm `Db` keeps an authoritative
6
- `sync` fork per base table and reconciles every server batch by **rewinding** the live
7
- pipeline to the authoritative state and **re-invoking** the still-pending client
8
- mutators against it no inverse mutations, no second materialization. This package is
9
- the client glue around that engine cycle:
10
-
11
- - **Named client mutators** (`ClientRegistry`): `(tx, args) => void` against a
12
- `MutationTx` (`get`/`add`/`remove`/`edit`; reads see the current base + the
13
- transaction's own writes). Deterministic and replayable — a mutator is re-invoked on
14
- every rebase, so a read-dependent `incrementScore(+5)` computed on a stale `10`
15
- correctly becomes `105` after the server lands `100`.
16
- - **cv-buffering** (§8.5): per-query data frames are `cv`-tagged and buffer; a
17
- connection-level progress frame `{ cvMin, lmid, rejected }` releases everything
18
- `cv ≤ cvMin` as one coherent step — no torn multi-query reads, ever. Buffer
19
- overflow drops + re-hydrates (the one new client obligation).
20
- - **Pending stack** of `(mid, name, args)` — never effects; `lmid` confirms (drop),
21
- `rejected` snaps back (the engine cycle removes the phantom effects).
22
- - **`ResultType`** per query (§6/§7): the **server channel's** state only — `unknown`
23
- while not hydrated (the server hasn't answered yet), `complete` once it has. A pending
24
- mutation no longer moves it; `error` is reserved and currently unproduced.
25
- - **Pending axis** (§7.2): "is an unconfirmed prediction touching this query?" is its own
26
- reactive signal — `pending(qid)` / `onPending` / `pendingTables()` — so a "saving…"
27
- indicator is separate from loading.
28
- - **Local-only tables**: `table("draft", { local: true })` declares client-authoritative
29
- state that never syncs, rebases, or appears in the normalized server schema. Use
30
- `store.writeLocal(...)` for selections, drafts, and view prefs; replayable predicted
31
- mutators are intentionally barred from reading/writing local-only tables.
32
- - **Folded mutations** (FOLDED-MUTATIONS-DESIGN): `mutate.foo.folded({ key, debounceMs,
33
- maxWaitMs }, args)` collapses a run of high-frequency same-key writes (a slider drag) into
34
- **one** debounced, last-value-wins server write while the local view still updates on every
35
- call. Folded mutators must be **absorbing** (last-writer-wins); a read-dependent one is
36
- refused. `flushFolds()` (also auto-fired on `pagehide`/`beforeunload`) drains in-flight folds.
7
+ The local IVM engine **is** the rebase function: it keeps an authoritative fork per base
8
+ table and reconciles every server batch by **rewinding** to the authoritative state and
9
+ **re-invoking** the still-pending mutators against it — no inverse mutations, no rollback
10
+ code. A rejected write snaps back on its own.
37
11
 
38
12
  ```ts
39
- import { createOptimisticStore, type ClientRegistry } from "@rindle/optimistic";
40
- import { defineQuery, newQueryBuilder } from "@rindle/client";
41
- // `schema` is your @rindle/client schema; `source` is an `OptimisticSource`
42
- // (e.g. `createRemoteOptimisticSource` from @rindle/remote).
43
-
44
- const registry = {
45
- createIssue: (tx, args: { id: number; title: string }) =>
46
- tx.add("issue", [args.id, args.title]),
47
- } satisfies ClientRegistry;
48
-
49
- const q = newQueryBuilder(schema);
50
- const queries = {
51
- allIssues: defineQuery("allIssues", () => q.issue.orderBy("id", "asc")),
52
- };
53
-
54
- const { store, backend, mutate } = createOptimisticStore(schema, source, registry, {
55
- clientID: "client-1",
13
+ import { createRindleClient } from "@rindle/optimistic";
14
+ import { mutators, schema } from "../shared/app-def.ts"; // isomorphic mutators + generated schema
15
+
16
+ export const app = await createRindleClient({
17
+ schema,
18
+ mutators, // one generator body per mutator, run on both tiers
19
+ user: () => currentUser(), // the acting principal a mutator sees as ctx.user
20
+ api: { url: "" }, // your API server (named queries + mutations)
21
+ daemon: { wsUrl: "ws://127.0.0.1:7601" }, // the daemon's PUBLIC subscription port
56
22
  });
57
23
 
58
- const view = store.materialize(queries.allIssues()); // named remote footprint + local view
59
- mutate.createIssue({ id: 7, title: "instant" }); // applies NOW; reconciles when confirmed
60
- backend.resultType(/* queryId */ 1); // "unknown" (loading) → "complete"
61
- backend.pending(/* queryId */ 1); // is an optimistic write pending on this query?
24
+ app.mutate.createIssue({ id, title: "ship it", createdAt: Date.now() }); // applies NOW, rebases on confirm
62
25
  ```
63
26
 
64
- A `Store` over an `OptimisticBackend` is an ordinary `@rindle/client` store the
65
- `Backend` seam holds. Unstamped `store.query...materialize()` calls are local-only; use
66
- `defineQuery` + `store.materialize(query)` for remote sync. The `OptimisticSource` (the server side: cv-tagged normalized
67
- frames + progress frames + the mutation upstream) is pluggable; the ws transport wiring
68
- is the follow-up slice. The engine-side cycle and the server protocol are proven in
69
- Rust (`src/optimistic.rs`, `rindle-replica`'s mutation/envelope suites); this package's
70
- tests pin the TS wiring against a scripted source.
27
+ Also in the box: **folded mutations** (collapse a slider drag into one last-value-wins
28
+ server write), **local-only tables** (`{ local: true }` — client state that never syncs
29
+ or rebases, written with `store.writeLocal`), the per-query **`ResultType`** axis
30
+ (loading vs. server-answered) and the separate **pending** axis (is an unconfirmed write
31
+ touching this query?).
32
+
33
+ ## Docs
34
+
35
+ Full docs — wiring, optimistic writes & rebase, folded writes, local-only tables, and the
36
+ loading/pending signals: **[rindle.sh/docs/client](https://rindle.sh/docs/client)** ·
37
+ markdown mirror: [`client.md`](https://rindle.sh/docs/client.md) · the mutator contract:
38
+ [rindle.sh/docs/mutators](https://rindle.sh/docs/mutators) · for agents:
39
+ [llms.txt](https://rindle.sh/llms.txt)