@gonvex/client 0.1.32 → 0.3.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.
Files changed (59) hide show
  1. package/README.md +119 -95
  2. package/dist/control.d.ts +416 -0
  3. package/dist/control.js +210 -0
  4. package/dist/control.js.map +1 -0
  5. package/dist/error-reporter.d.ts +20 -6
  6. package/dist/error-reporter.js +55 -27
  7. package/dist/error-reporter.js.map +1 -1
  8. package/dist/index.d.ts +166 -133
  9. package/dist/index.js +1114 -1139
  10. package/dist/index.js.map +1 -1
  11. package/dist/indexeddb-replica.d.ts +20 -0
  12. package/dist/indexeddb-replica.js +193 -0
  13. package/dist/indexeddb-replica.js.map +1 -0
  14. package/dist/kv-stores.d.ts +2 -48
  15. package/dist/kv-stores.js +24 -411
  16. package/dist/kv-stores.js.map +1 -1
  17. package/dist/local-replica.d.ts +240 -0
  18. package/dist/local-replica.js +590 -0
  19. package/dist/local-replica.js.map +1 -0
  20. package/dist/optimistic.d.ts +34 -55
  21. package/dist/optimistic.js +70 -269
  22. package/dist/optimistic.js.map +1 -1
  23. package/dist/outbox.d.ts +25 -25
  24. package/dist/outbox.js +27 -27
  25. package/dist/outbox.js.map +1 -1
  26. package/dist/query-expression.d.ts +58 -0
  27. package/dist/query-expression.js +164 -0
  28. package/dist/query-expression.js.map +1 -0
  29. package/dist/replica-integrity.d.ts +5 -0
  30. package/dist/replica-integrity.js +58 -0
  31. package/dist/replica-integrity.js.map +1 -0
  32. package/package.json +4 -4
  33. package/dist/browser-cache-client.d.ts +0 -77
  34. package/dist/browser-cache-client.js +0 -156
  35. package/dist/browser-cache-client.js.map +0 -1
  36. package/dist/browser-cache-shared-worker.d.ts +0 -35
  37. package/dist/browser-cache-shared-worker.js +0 -118
  38. package/dist/browser-cache-shared-worker.js.map +0 -1
  39. package/dist/browser-cache.d.ts +0 -43
  40. package/dist/browser-cache.js +0 -67
  41. package/dist/browser-cache.js.map +0 -1
  42. package/dist/browser-capabilities.d.ts +0 -21
  43. package/dist/browser-capabilities.js +0 -31
  44. package/dist/browser-capabilities.js.map +0 -1
  45. package/dist/cache-coordinator.d.ts +0 -37
  46. package/dist/cache-coordinator.js +0 -109
  47. package/dist/cache-coordinator.js.map +0 -1
  48. package/dist/cache.d.ts +0 -74
  49. package/dist/cache.js +0 -120
  50. package/dist/cache.js.map +0 -1
  51. package/dist/persistent-cache.d.ts +0 -41
  52. package/dist/persistent-cache.js +0 -103
  53. package/dist/persistent-cache.js.map +0 -1
  54. package/dist/query-cache.d.ts +0 -88
  55. package/dist/query-cache.js +0 -346
  56. package/dist/query-cache.js.map +0 -1
  57. package/dist/sync-store.d.ts +0 -96
  58. package/dist/sync-store.js +0 -500
  59. package/dist/sync-store.js.map +0 -1
package/README.md CHANGED
@@ -1,11 +1,36 @@
1
1
  # @gonvex/client
2
2
 
3
- Browser client for Gonvex realtime queries, mutations, actions, telemetry, and
4
- local cache helpers.
3
+ Browser client for Gonvex Queries, Reducers, Actions, Live Queries, the
4
+ persistent Local Replica, and telemetry.
5
5
 
6
6
  Most React apps should use `@gonvex/react`, which wraps this package with hooks.
7
7
  Use `@gonvex/client` directly when you want lower-level control.
8
8
 
9
+ ## Control Plane
10
+
11
+ Host-owned account, tenant-directory, invitation, agent-auth, voice, and support
12
+ functions use the same persistent connection as tenant functions:
13
+
14
+ ```ts
15
+ import { control } from "@gonvex/client";
16
+
17
+ const account = await client.query(control.accounts.me, {});
18
+ const tenants = await client.query(control.tenants.mine, {});
19
+ await client.reducer(control.tenants.updateTimezone, { timezone: "America/Los_Angeles" });
20
+
21
+ const stop = client.watchControlQuery(control.support.sessions, {}, (result) => {
22
+ renderSessions(result);
23
+ });
24
+ ```
25
+
26
+ The references include argument/result schemas and their authorization class.
27
+ They never accept database URLs. Tenant-admin references always operate on the
28
+ active, authoritatively admitted tenant.
29
+
30
+ Control Plane live Queries resubscribe on reconnect and refresh after an
31
+ authorized Control Plane Reducer. Use `watchControlQuery` instead of refetching
32
+ after a write.
33
+
9
34
  ## Install
10
35
 
11
36
  ```bash
@@ -16,6 +41,7 @@ npm install @gonvex/client
16
41
 
17
42
  ```ts
18
43
  import { GonvexClient } from "@gonvex/client";
44
+ import { api } from "./gonvex/_generated/api";
19
45
 
20
46
  const client = new GonvexClient("ws://localhost:8080/ws", {
21
47
  project: "my-project",
@@ -24,8 +50,8 @@ const client = new GonvexClient("ws://localhost:8080/ws", {
24
50
 
25
51
  client.connect();
26
52
 
27
- const unsubscribe = client.subscribeQuery(
28
- { kind: "query", path: "tasks.list" },
53
+ const unsubscribe = client.subscribeLiveQuery(
54
+ api.tasks.list,
29
55
  { status: "open" },
30
56
  (message) => {
31
57
  if (message.type === "query.result") {
@@ -34,8 +60,8 @@ const unsubscribe = client.subscribeQuery(
34
60
  },
35
61
  );
36
62
 
37
- await client.mutation(
38
- { kind: "mutation", path: "tasks.create" },
63
+ await client.reducer(
64
+ { kind: "reducer", path: "tasks.create" },
39
65
  { title: "Ship Gonvex" },
40
66
  );
41
67
 
@@ -43,110 +69,94 @@ unsubscribe();
43
69
  client.close();
44
70
  ```
45
71
 
46
- ## Transparent Browser Cache
72
+ ## Local Replica
47
73
 
48
- Live query results are persisted automatically in supported browsers when the
49
- runtime advertises a safe cache scope. A warm `subscribeQuery`, `watchQuery`, or
50
- React `useQuery` can replay its last result while the normal server subscription
51
- runs in parallel. The server result always wins and refreshes the snapshot,
52
- including results produced by realtime invalidation.
74
+ The Local Replica is the only client-side server-state store. It owns normalized
75
+ entities, live-query/replica windows, revision metadata, and optimistic command
76
+ patches. One-shot Query results are transient and are never persisted.
53
77
 
54
- Caching is isolated by runtime deployment, project, tenant, user, and current
55
- permissions. New clients connected to older runtimes stay server-only.
56
-
57
- No setup is required. To opt out or clear the disposable cache:
78
+ Inject a durable adapter when desired; omit it for memory-only operation:
58
79
 
59
80
  ```ts
60
- const client = new GonvexClient(url, { queryCache: false });
61
-
62
- await client.clearQueryCache();
63
- await client.clearQueryCache({ allScopes: true });
81
+ const client = new GonvexClient(url, {
82
+ localReplica: { storage: indexedDbReplicaStorage },
83
+ });
64
84
  ```
65
85
 
66
- Dexie is loaded asynchronously only after a cache-capable session is confirmed,
67
- so IndexedDB setup does not delay the WebSocket query path.
86
+ All window updates are applied atomically before listeners are notified. Live
87
+ Query windows and Replica Collections reference the same normalized entities,
88
+ so every view of an entity converges together.
68
89
 
69
- ## Durable Sync Collections
90
+ ## Replica Collections
70
91
 
71
- Sync functions materialize bounded, authorized single-table collections in a
72
- normalized IndexedDB store and resume them from a durable Postgres cursor:
92
+ Replica Collections materialize bounded, authorized entity sets in normalized
93
+ IndexedDB storage and resume them from a durable Postgres revision:
73
94
 
74
95
  ```ts
75
- const watch = client.watchSync<Task>(
76
- { kind: "sync", path: "tasks.recent" },
96
+ const watch = client.watchReplica<Task>(
97
+ api.tasks.recent,
77
98
  { workspaceId: "workspace-a" },
78
99
  );
79
100
 
80
101
  const stop = watch.onUpdate(() => {
81
- render(watch.localSyncResult() ?? []);
102
+ const state = watch.localReplicaState();
103
+ render(state?.rows ?? []);
104
+ console.log(state?.completeness, state?.truncated, state?.computedRevision);
82
105
  console.log(watch.status()); // { isLoading, isUpToDate }
83
106
  });
84
107
  ```
85
108
 
86
- Configure or disable the sync store when constructing the client:
109
+ Configure Local Replica persistence when constructing the client:
87
110
 
88
111
  ```ts
89
112
  const client = new GonvexClient(url, {
90
- sync: {
91
- databaseName: "my-product-sync",
92
- maxBytes: 150 * 1024 * 1024,
93
- },
113
+ localReplica: { storage: indexedDbReplicaStorage },
94
114
  });
95
-
96
- const memoryOnly = new GonvexClient(url, { sync: false });
97
115
  ```
98
116
 
99
- The default global IndexedDB budget is 100 MiB. Server-declared per-collection
100
- row/byte budgets still apply, and least-recently-used collections are evicted
101
- first. Storage is isolated by runtime, project, tenant, authenticated identity,
102
- and permissions.
117
+ ## Optimistic Reducers
103
118
 
104
- ## Entity-level optimistic mutations
119
+ Every public interactive Reducer declares its optimistic transaction. The
120
+ authoritative transaction and optimistic patches are reconciled in LocalReplica.
105
121
 
106
- Declare how a mutation maps its arguments to an entity row and how each live
107
- read projects that entity:
108
-
109
- ```go
110
- app.Mutation(
111
- "tasks.update",
112
- updateTask,
113
- gonvex.OptimisticMutation("tasks").RowIDArg("taskId").FieldsArg("updates"),
114
- )
115
-
116
- app.Query(
117
- "tasks.byWorkspace",
118
- tasksByWorkspace,
119
- gonvex.OptimisticProjection("tasks").Key("_id").ResultPath("page"),
120
- )
121
- ```
122
-
123
- Generated references carry this metadata, so a normal mutation call is enough:
122
+ Generated references carry this metadata, so a normal Reducer call is enough:
124
123
 
125
124
  ```ts
126
- await client.mutation(api.tasks.update, {
125
+ await client.reducer(api.tasks.update, {
127
126
  taskId,
128
127
  updates: { priority_id: priorityId },
129
128
  });
130
129
  ```
131
130
 
132
- The client persists the pending mutation before exposing it, layers it over
133
- every matching sync and query subscription, and notifies watchers immediately.
134
- An RPC result marks the write accepted but does not remove it; the overlay is
135
- retired only when every source that displayed it reports the mutation id (or a
136
- restored committed snapshot already matches). Deterministic server errors
137
- remove the overlay and repaint the authoritative rows. This keeps stale query
138
- or sync frames from briefly reverting the UI.
131
+ The client persists the pending command, applies it through LocalReplica, and
132
+ notifies watchers immediately. Reducer success includes an
133
+ `originCommandId` and committed revision. The overlay is removed only after the
134
+ corresponding authoritative transaction has been applied locally, preventing an
135
+ empty or stale frame between optimistic and committed state.
139
136
 
140
- The authoritative sync/query cache stays immutable. Durable pending state lives
141
- in the mutation outbox and is re-applied to cached rows after reload. Outbox
137
+ Authoritative entity state and optimistic entities are materialized by one
138
+ LocalReplica graph. Durable pending state lives in the command outbox and is
139
+ re-applied after reload. Outbox
142
140
  rows are isolated by project, tenant, and authenticated identity; an account
143
141
  switch removes the previous identity's overlay and can never replay its writes
144
142
  under the new session. Unscoped rows from the pre-isolation schema are removed
145
143
  during migration because their owner cannot be proven. If an opaque credential
146
144
  does not expose a stable identity (and no `identity` hint is supplied), its
147
- outbox is deliberately session-only rather than risking cross-user replay. For
148
- a complex projection that cannot be derived from one nested fields argument,
149
- callers can still provide explicit `optimistic` entity patches.
145
+ outbox is deliberately session-only rather than risking cross-user replay.
146
+
147
+ ## Offline Live Queries
148
+
149
+ Generated Live Query references include the same structured plan Gonvex
150
+ compiles to PostgreSQL. While offline, the client executes that plan over the
151
+ normalized cached corpus:
152
+
153
+ ```ts
154
+ const result = client.offlineLiveQuery(api.tasks.grid, args);
155
+ // { rows, completeness: "complete" | "partial", supported }
156
+ ```
157
+
158
+ `supported: false` means the plan is explicitly server-only. A partial result
159
+ must be labeled as cached data rather than presented as the full database.
150
160
 
151
161
  ## Lightweight Error Tracking
152
162
 
@@ -165,9 +175,23 @@ const client = new GonvexClient(url, {
165
175
  });
166
176
  ```
167
177
 
168
- Use `GonvexErrorReporter` directly when integrating an existing application
169
- logger. See the Error Tracking guide in the full documentation for privacy,
170
- grouping, persistence, and dashboard details.
178
+ Error registration and envelopes use native persistent-protocol frames. No
179
+ browser HTTP ingestion endpoint is needed.
180
+
181
+ Applications can report an explicit event through the public API:
182
+
183
+ ```ts
184
+ await client.reportError("envelope", {
185
+ events: [{
186
+ message: "Task preview failed",
187
+ level: "error",
188
+ context: { component: "TaskPreview" },
189
+ }],
190
+ });
191
+ ```
192
+
193
+ `new GonvexErrorReporter({ client })` adds global browser error capture and
194
+ re-registers its telemetry session after reconnect.
171
195
 
172
196
  ## Connection reliability
173
197
 
@@ -180,7 +204,7 @@ reconnect.
180
204
  client.connectionState();
181
205
  // {
182
206
  // isWebSocketConnected, hasEverConnected, connectionCount, connectionRetries,
183
- // hasInflightRequests, inflightMutations, inflightActions, inflightOneShotQueries
207
+ // hasInflightRequests, inflightReducers, inflightActions, inflightOneShotQueries
184
208
  // }
185
209
 
186
210
  const stop = client.subscribeToConnectionState((state) => {
@@ -193,7 +217,7 @@ const stop = client.subscribeToConnectionState((state) => {
193
217
  | Operation | Default |
194
218
  | --- | --- |
195
219
  | One-shot `query()` | 20s |
196
- | `mutation()` | 20s |
220
+ | `reducer()` | 20s |
197
221
  | `action()` | 60s |
198
222
 
199
223
  Override per client (`timeouts` option) or per call (`{ timeoutMs }`). Use `0` to disable.
@@ -202,43 +226,43 @@ Override per client (`timeouts` option) or per call (`{ timeoutMs }`). Use `0` t
202
226
 
203
227
  Rejected operations throw `GonvexClientError` with `code`:
204
228
 
205
- - `server` runtime executed the function and returned an error
206
- - `timeout` no response within the timeout
207
- - `disconnected` socket dropped while the operation was pending
208
- - `closed` client was explicitly closed
209
- - `auth` authentication rejected
229
+ - `server`: runtime executed the function and returned an error
230
+ - `timeout`: no response within the timeout
231
+ - `disconnected`: socket dropped while the operation was pending
232
+ - `closed`: client was explicitly closed
233
+ - `auth`: authentication rejected
210
234
 
211
- ### Mutation / action disconnect policy
235
+ ### Reducer / Action disconnect policy
212
236
 
213
- Actions and mutations without `{ offline: "queue" }` fail closed after a
237
+ Actions and Reducers without `{ offline: "queue" }` fail closed after a
214
238
  disconnect. They reject with `code: "disconnected"` (or `timeout` / `closed`).
215
- Optimistic mutations are persisted before transport even in fail-closed mode,
239
+ Optimistic Reducers are persisted before transport even in fail-closed mode,
216
240
  so a process reload cannot expose an older cached row while an accepted write
217
241
  is still waiting for its authoritative subscription update.
218
242
 
219
- Pass `{ offline: "queue" }` to a mutation to durably accept a transport failure
243
+ Pass `{ offline: "queue" }` to a Reducer to durably accept a transport failure
220
244
  and replay the same idempotency key after reconnect, whether or not that
221
- mutation also declares optimistic UI metadata. Actions are never queued.
245
+ Reducer also declares optimistic UI metadata. Actions are never queued.
222
246
  Deterministic server errors are never queued and always roll an optimistic
223
247
  entity overlay back when one exists.
224
248
 
225
- Live queries keep last-good data at the React layer (`useQueryResult`) and
226
- resubscribe after reconnect. Call `client.retryQuery(ref, args)` to force a
227
- re-request after a server error or soft timeout.
249
+ Live Queries persist their last verified window in the Local Replica and
250
+ resubscribe after reconnect. Call `client.retryLiveQuery(ref, args)` to force a
251
+ re-request after a server error. `useQueryResult` is for one-shot Queries.
228
252
 
229
253
  ## Exports
230
254
 
231
255
  The package exports:
232
256
 
233
257
  - `GonvexClient`
234
- - `ConvexReactClient` compatibility alias
235
258
  - `GonvexClientError`, `ConnectionState`, timeout defaults
236
- - transparent persistent query caching and lower-level experimental cache helpers
237
- - durable entity-level optimistic overlays for query and sync projections
238
- - opt-in mutation outbox replay with stable idempotency keys
239
- - `subscribeSync`, `watchSync`, and normalized persistent sync storage
259
+ - normalized Local Replica entities and Live Query windows
260
+ - durable optimistic Reducer overlays reconciled by command ID and revision
261
+ - opt-in command outbox replay with stable idempotency keys
262
+ - `subscribeReplica`, `watchReplica`, and persistent Replica storage
263
+ - `watchControlQuery` for authorized Control Plane live Queries
240
264
  - browser capability and telemetry helpers
241
- - `GonvexErrorReporter` and automatic operation error reporting
265
+ - `reportError`, `GonvexErrorReporter`, and automatic operation error reporting
242
266
 
243
267
  ## Related Packages
244
268