@gonvex/client 0.1.29 → 0.1.31
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 +62 -7
- package/dist/index.d.ts +20 -5
- package/dist/index.js +289 -46
- package/dist/index.js.map +1 -1
- package/dist/optimistic.d.ts +46 -14
- package/dist/optimistic.js +216 -27
- package/dist/optimistic.js.map +1 -1
- package/dist/outbox.d.ts +18 -8
- package/dist/outbox.js +110 -29
- package/dist/outbox.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -99,8 +99,54 @@ const memoryOnly = new GonvexClient(url, { sync: false });
|
|
|
99
99
|
The default global IndexedDB budget is 100 MiB. Server-declared per-collection
|
|
100
100
|
row/byte budgets still apply, and least-recently-used collections are evicted
|
|
101
101
|
first. Storage is isolated by runtime, project, tenant, authenticated identity,
|
|
102
|
-
and permissions.
|
|
103
|
-
|
|
102
|
+
and permissions.
|
|
103
|
+
|
|
104
|
+
## Entity-level optimistic mutations
|
|
105
|
+
|
|
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:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
await client.mutation(api.tasks.update, {
|
|
127
|
+
taskId,
|
|
128
|
+
updates: { priority_id: priorityId },
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
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.
|
|
139
|
+
|
|
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
|
|
142
|
+
rows are isolated by project, tenant, and authenticated identity; an account
|
|
143
|
+
switch removes the previous identity's overlay and can never replay its writes
|
|
144
|
+
under the new session. Unscoped rows from the pre-isolation schema are removed
|
|
145
|
+
during migration because their owner cannot be proven. If an opaque credential
|
|
146
|
+
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.
|
|
104
150
|
|
|
105
151
|
## Lightweight Error Tracking
|
|
106
152
|
|
|
@@ -162,12 +208,19 @@ Rejected operations throw `GonvexClientError` with `code`:
|
|
|
162
208
|
- `closed` — client was explicitly closed
|
|
163
209
|
- `auth` — authentication rejected
|
|
164
210
|
|
|
165
|
-
### Mutation / action
|
|
211
|
+
### Mutation / action disconnect policy
|
|
212
|
+
|
|
213
|
+
Actions and mutations without `{ offline: "queue" }` fail closed after a
|
|
214
|
+
disconnect. They reject with `code: "disconnected"` (or `timeout` / `closed`).
|
|
215
|
+
Optimistic mutations are persisted before transport even in fail-closed mode,
|
|
216
|
+
so a process reload cannot expose an older cached row while an accepted write
|
|
217
|
+
is still waiting for its authoritative subscription update.
|
|
166
218
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
219
|
+
Pass `{ offline: "queue" }` to a mutation to durably accept a transport failure
|
|
220
|
+
and replay the same idempotency key after reconnect, whether or not that
|
|
221
|
+
mutation also declares optimistic UI metadata. Actions are never queued.
|
|
222
|
+
Deterministic server errors are never queued and always roll an optimistic
|
|
223
|
+
entity overlay back when one exists.
|
|
171
224
|
|
|
172
225
|
Live queries keep last-good data at the React layer (`useQueryResult`) and
|
|
173
226
|
resubscribe after reconnect. Call `client.retryQuery(ref, args)` to force a
|
|
@@ -181,6 +234,8 @@ The package exports:
|
|
|
181
234
|
- `ConvexReactClient` compatibility alias
|
|
182
235
|
- `GonvexClientError`, `ConnectionState`, timeout defaults
|
|
183
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
|
|
184
239
|
- `subscribeSync`, `watchSync`, and normalized persistent sync storage
|
|
185
240
|
- browser capability and telemetry helpers
|
|
186
241
|
- `GonvexErrorReporter` and automatic operation error reporting
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { BrowserTelemetryInfo, JsonValue, MessageTrace, ServerCapabilities,
|
|
|
2
2
|
import { type QueryCacheOptions, type QueryCacheStatus } from "./query-cache.js";
|
|
3
3
|
import { type SyncStoreOptions } from "./sync-store.js";
|
|
4
4
|
import { type ErrorReporterOptions } from "./error-reporter.js";
|
|
5
|
-
import { OptimisticOverlay, type OptimisticPatch } from "./optimistic.js";
|
|
5
|
+
import { OptimisticOverlay, type OptimisticMutationDefinition, type OptimisticPatch, type OptimisticProjection } from "./optimistic.js";
|
|
6
6
|
export * from "./cache.js";
|
|
7
7
|
export * from "./cache-coordinator.js";
|
|
8
8
|
export * from "./browser-cache.js";
|
|
@@ -34,6 +34,10 @@ type ConnectionStateHandler = (state: ConnectionState) => void;
|
|
|
34
34
|
export type FunctionReference = {
|
|
35
35
|
kind: string;
|
|
36
36
|
path: string;
|
|
37
|
+
optimistic?: {
|
|
38
|
+
projection?: OptimisticProjection;
|
|
39
|
+
mutation?: OptimisticMutationDefinition;
|
|
40
|
+
};
|
|
37
41
|
};
|
|
38
42
|
export type GonvexClientErrorCode = "server" | "timeout" | "disconnected" | "closed" | "auth";
|
|
39
43
|
/**
|
|
@@ -197,7 +201,11 @@ export declare class GonvexClient {
|
|
|
197
201
|
private readonly mutationOutbox;
|
|
198
202
|
private readonly overlay;
|
|
199
203
|
private readonly optimisticMutationIds;
|
|
200
|
-
private readonly
|
|
204
|
+
private readonly optimisticOutboxEntryIds;
|
|
205
|
+
private outboxReady;
|
|
206
|
+
private outboxScope;
|
|
207
|
+
private outboxScopeGeneration;
|
|
208
|
+
private readonly outboxEphemeralScope;
|
|
201
209
|
private readonly unsubscribeOutbox;
|
|
202
210
|
private readonly unsubscribeOverlay;
|
|
203
211
|
private drainingOutbox;
|
|
@@ -269,7 +277,10 @@ export declare class GonvexClient {
|
|
|
269
277
|
private scheduleSyncWatermarkPersistence;
|
|
270
278
|
private emitSyncMessage;
|
|
271
279
|
private materializeSyncMessage;
|
|
272
|
-
private
|
|
280
|
+
private materializeQueryMessage;
|
|
281
|
+
private emitOptimisticEntity;
|
|
282
|
+
private acknowledgeOptimisticSource;
|
|
283
|
+
private acknowledgeOptimisticQuerySnapshot;
|
|
273
284
|
private markSyncSubscriptionsOutOfDate;
|
|
274
285
|
private startSync;
|
|
275
286
|
private sendSyncOpen;
|
|
@@ -278,16 +289,19 @@ export declare class GonvexClient {
|
|
|
278
289
|
private unsubscribeSyncListener;
|
|
279
290
|
private persistSyncSnapshot;
|
|
280
291
|
private persistSyncDelta;
|
|
292
|
+
private activateOutboxScope;
|
|
281
293
|
private restoreOutbox;
|
|
282
294
|
private addOptimisticMutation;
|
|
283
295
|
private settleOptimisticMutation;
|
|
284
296
|
private rejectOptimisticMutation;
|
|
297
|
+
private ackOptimisticMutation;
|
|
285
298
|
private drainOutbox;
|
|
286
299
|
private scheduleOutboxDrain;
|
|
287
300
|
mutation<T = JsonValue>(ref: FunctionReference, args: JsonValue, options: CallOptions & {
|
|
288
301
|
offline: "queue";
|
|
289
302
|
}): Promise<T | QueuedMutationOutcome>;
|
|
290
303
|
mutation<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
|
|
304
|
+
private runOptimisticMutation;
|
|
291
305
|
action<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
|
|
292
306
|
query<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
|
|
293
307
|
/**
|
|
@@ -300,8 +314,9 @@ export declare class GonvexClient {
|
|
|
300
314
|
* Flush a queue of mutations in one `mutation.callMany` frame (queue order,
|
|
301
315
|
* one websocket round trip). Each entry settles independently — a failed
|
|
302
316
|
* call does not reject the batch — so offline queues can apply per-row
|
|
303
|
-
* outcomes. Falls back to
|
|
304
|
-
*
|
|
317
|
+
* outcomes. Falls back to the standard per-mutation path when the runtime
|
|
318
|
+
* lacks batching or when a call needs generated/explicit optimism or durable
|
|
319
|
+
* offline queuing, so there is never a second mutation-state implementation.
|
|
305
320
|
*/
|
|
306
321
|
mutationMany<T = JsonValue>(calls: Array<{
|
|
307
322
|
ref: FunctionReference;
|