@gonvex/client 0.1.30 → 0.1.32
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 +25 -5
- package/dist/index.js +302 -49
- package/dist/index.js.map +1 -1
- package/dist/kv-stores.d.ts +61 -0
- package/dist/kv-stores.js +447 -0
- package/dist/kv-stores.js.map +1 -0
- 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 +76 -8
- package/dist/outbox.js +290 -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,8 @@ 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
|
+
import { type OutboxStore } from "./outbox.js";
|
|
6
7
|
export * from "./cache.js";
|
|
7
8
|
export * from "./cache-coordinator.js";
|
|
8
9
|
export * from "./browser-cache.js";
|
|
@@ -15,6 +16,7 @@ export * from "./sync-store.js";
|
|
|
15
16
|
export * from "./error-reporter.js";
|
|
16
17
|
export * from "./optimistic.js";
|
|
17
18
|
export * from "./outbox.js";
|
|
19
|
+
export * from "./kv-stores.js";
|
|
18
20
|
export * from "./signals.js";
|
|
19
21
|
export type { QueryCacheDirective } from "@gonvex/protocol";
|
|
20
22
|
type SubscriptionHandler = (message: ServerMessage) => void;
|
|
@@ -34,6 +36,10 @@ type ConnectionStateHandler = (state: ConnectionState) => void;
|
|
|
34
36
|
export type FunctionReference = {
|
|
35
37
|
kind: string;
|
|
36
38
|
path: string;
|
|
39
|
+
optimistic?: {
|
|
40
|
+
projection?: OptimisticProjection;
|
|
41
|
+
mutation?: OptimisticMutationDefinition;
|
|
42
|
+
};
|
|
37
43
|
};
|
|
38
44
|
export type GonvexClientErrorCode = "server" | "timeout" | "disconnected" | "closed" | "auth";
|
|
39
45
|
/**
|
|
@@ -145,10 +151,13 @@ export type GonvexClientOptions = GonvexClientAuth & {
|
|
|
145
151
|
/**
|
|
146
152
|
* Durable mutation queue settings. Every replay keeps its original
|
|
147
153
|
* idempotency key, making an accidental cross-tab double-send server-safe.
|
|
154
|
+
* Runtimes without IndexedDB inject `store` to keep queued mutations
|
|
155
|
+
* durable; queue semantics always stay in the SDK.
|
|
148
156
|
*/
|
|
149
157
|
outbox?: {
|
|
150
158
|
databaseName?: string;
|
|
151
159
|
enabled?: boolean;
|
|
160
|
+
store?: OutboxStore;
|
|
152
161
|
};
|
|
153
162
|
errorReporting?: false | Omit<ErrorReporterOptions, "endpoint" | "project" | "tenant">;
|
|
154
163
|
timeouts?: GonvexTimeoutOptions;
|
|
@@ -197,7 +206,11 @@ export declare class GonvexClient {
|
|
|
197
206
|
private readonly mutationOutbox;
|
|
198
207
|
private readonly overlay;
|
|
199
208
|
private readonly optimisticMutationIds;
|
|
200
|
-
private readonly
|
|
209
|
+
private readonly optimisticOutboxEntryIds;
|
|
210
|
+
private outboxReady;
|
|
211
|
+
private outboxScope;
|
|
212
|
+
private outboxScopeGeneration;
|
|
213
|
+
private readonly outboxEphemeralScope;
|
|
201
214
|
private readonly unsubscribeOutbox;
|
|
202
215
|
private readonly unsubscribeOverlay;
|
|
203
216
|
private drainingOutbox;
|
|
@@ -269,7 +282,10 @@ export declare class GonvexClient {
|
|
|
269
282
|
private scheduleSyncWatermarkPersistence;
|
|
270
283
|
private emitSyncMessage;
|
|
271
284
|
private materializeSyncMessage;
|
|
272
|
-
private
|
|
285
|
+
private materializeQueryMessage;
|
|
286
|
+
private emitOptimisticEntity;
|
|
287
|
+
private acknowledgeOptimisticSource;
|
|
288
|
+
private acknowledgeOptimisticQuerySnapshot;
|
|
273
289
|
private markSyncSubscriptionsOutOfDate;
|
|
274
290
|
private startSync;
|
|
275
291
|
private sendSyncOpen;
|
|
@@ -278,16 +294,19 @@ export declare class GonvexClient {
|
|
|
278
294
|
private unsubscribeSyncListener;
|
|
279
295
|
private persistSyncSnapshot;
|
|
280
296
|
private persistSyncDelta;
|
|
297
|
+
private activateOutboxScope;
|
|
281
298
|
private restoreOutbox;
|
|
282
299
|
private addOptimisticMutation;
|
|
283
300
|
private settleOptimisticMutation;
|
|
284
301
|
private rejectOptimisticMutation;
|
|
302
|
+
private ackOptimisticMutation;
|
|
285
303
|
private drainOutbox;
|
|
286
304
|
private scheduleOutboxDrain;
|
|
287
305
|
mutation<T = JsonValue>(ref: FunctionReference, args: JsonValue, options: CallOptions & {
|
|
288
306
|
offline: "queue";
|
|
289
307
|
}): Promise<T | QueuedMutationOutcome>;
|
|
290
308
|
mutation<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
|
|
309
|
+
private runOptimisticMutation;
|
|
291
310
|
action<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
|
|
292
311
|
query<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
|
|
293
312
|
/**
|
|
@@ -300,8 +319,9 @@ export declare class GonvexClient {
|
|
|
300
319
|
* Flush a queue of mutations in one `mutation.callMany` frame (queue order,
|
|
301
320
|
* one websocket round trip). Each entry settles independently — a failed
|
|
302
321
|
* call does not reject the batch — so offline queues can apply per-row
|
|
303
|
-
* outcomes. Falls back to
|
|
304
|
-
*
|
|
322
|
+
* outcomes. Falls back to the standard per-mutation path when the runtime
|
|
323
|
+
* lacks batching or when a call needs generated/explicit optimism or durable
|
|
324
|
+
* offline queuing, so there is never a second mutation-state implementation.
|
|
305
325
|
*/
|
|
306
326
|
mutationMany<T = JsonValue>(calls: Array<{
|
|
307
327
|
ref: FunctionReference;
|