@gonvex/client 0.1.25 → 0.1.26

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/dist/index.d.ts CHANGED
@@ -2,6 +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
6
  export * from "./cache.js";
6
7
  export * from "./cache-coordinator.js";
7
8
  export * from "./browser-cache.js";
@@ -12,8 +13,21 @@ export * from "./persistent-cache.js";
12
13
  export * from "./query-cache.js";
13
14
  export * from "./sync-store.js";
14
15
  export * from "./error-reporter.js";
16
+ export * from "./optimistic.js";
17
+ export * from "./outbox.js";
18
+ export * from "./signals.js";
15
19
  export type { QueryCacheDirective } from "@gonvex/protocol";
16
20
  type SubscriptionHandler = (message: ServerMessage) => void;
21
+ export type SyncReadyMessage = Extract<ServerMessage, {
22
+ type: "sync.ready";
23
+ }> & {
24
+ /** True when the server cut this collection at its row or byte budget. */
25
+ truncated?: boolean;
26
+ };
27
+ export type SyncMessage = Extract<ServerMessage, {
28
+ type: "sync.snapshot" | "sync.delta" | "sync.needHashes" | "sync.syncing" | "sync.reset" | "sync.error";
29
+ }> | SyncReadyMessage;
30
+ export type SyncSubscriptionHandler = (message: SyncMessage) => void;
17
31
  type WatchUpdateHandler = () => void;
18
32
  type TelemetryHandler = (event: GonvexTelemetryEvent) => void;
19
33
  type ConnectionStateHandler = (state: ConnectionState) => void;
@@ -31,7 +45,7 @@ export type GonvexClientErrorCode = "server" | "timeout" | "disconnected" | "clo
31
45
  * - `timeout`: no response arrived within the operation timeout. For
32
46
  * mutations/actions the write may or may not have been applied.
33
47
  * - `disconnected`: the socket dropped while the operation was pending.
34
- * Mutations/actions fail closed and are never replayed automatically.
48
+ * Mutations/actions fail closed unless a mutation opted into the outbox.
35
49
  * - `closed`: the client was explicitly closed.
36
50
  * - `auth`: authentication was rejected.
37
51
  */
@@ -69,12 +83,37 @@ export declare const DEFAULT_ACTION_TIMEOUT_MS = 60000;
69
83
  export type CallOptions = {
70
84
  /** Per-call override of the operation timeout. `0` disables. */
71
85
  timeoutMs?: number;
86
+ /** Ordered row changes to expose until the mutation settles. */
87
+ optimistic?: OptimisticPatch[];
88
+ /** Queue transport failures durably instead of rejecting. Default `reject`. */
89
+ offline?: "queue" | "reject";
90
+ };
91
+ /** Returned when an offline mutation has been accepted by the local outbox. */
92
+ export type QueuedMutationOutcome = {
93
+ status: "queued";
94
+ mutationId: string;
72
95
  };
96
+ export type GonvexAuthTokenFetcher = (args: {
97
+ /** True when the server just rejected the current token — bypass any cache. */
98
+ forceRefreshToken: boolean;
99
+ }) => Promise<string | null | undefined>;
73
100
  export type GonvexClientAuth = {
74
101
  project?: string;
75
102
  token?: string;
76
103
  tenant?: string;
77
104
  telemetry?: boolean;
105
+ /**
106
+ * Async source of the auth token, mirroring Convex's `fetchToken` contract.
107
+ * When installed, the client re-fetches before every auth send — on first
108
+ * connect, on every reconnect, and once more with `forceRefreshToken: true`
109
+ * when the server rejects the current token — so a socket that outlives a
110
+ * short-lived JWT (e.g. an ~1h Firebase ID token) reauthenticates with a
111
+ * live credential instead of replaying the expired one. A `token` passed in
112
+ * the same `setAuth` call is trusted and sent as-is; resolving `null` signs
113
+ * the session out; a rejected fetch keeps the currently installed token so
114
+ * an offline start is not signed out.
115
+ */
116
+ fetchToken?: GonvexAuthTokenFetcher;
78
117
  /**
79
118
  * Non-secret identity hint ({@link https://datatracker.ietf.org/doc/html/rfc7519 JWT}
80
119
  * `sub` and `iss` claims) that stands in for a token when deriving the local
@@ -103,6 +142,14 @@ export type GonvexClientOptions = GonvexClientAuth & {
103
142
  */
104
143
  syncSubscriptionRetentionMs?: number;
105
144
  sync?: false | SyncStoreOptions;
145
+ /**
146
+ * Durable mutation queue settings. Every replay keeps its original
147
+ * idempotency key, making an accidental cross-tab double-send server-safe.
148
+ */
149
+ outbox?: {
150
+ databaseName?: string;
151
+ enabled?: boolean;
152
+ };
106
153
  errorReporting?: false | Omit<ErrorReporterOptions, "endpoint" | "project" | "tenant">;
107
154
  timeouts?: GonvexTimeoutOptions;
108
155
  };
@@ -137,6 +184,9 @@ export declare class GonvexClient {
137
184
  private auth;
138
185
  private authInFlight;
139
186
  private authWatchdogTimer;
187
+ private authFetchGeneration;
188
+ private authRetriedAfterError;
189
+ private readonly authErrorHandlers;
140
190
  private telemetryEnabled;
141
191
  private readonly queryCache;
142
192
  private readonly queryCacheWaitForScope;
@@ -144,6 +194,14 @@ export declare class GonvexClient {
144
194
  private readonly querySubscriptionRetentionMs;
145
195
  private readonly syncSubscriptionRetentionMs;
146
196
  private readonly syncStore;
197
+ private readonly mutationOutbox;
198
+ private readonly overlay;
199
+ private readonly optimisticMutationIds;
200
+ private readonly outboxReady;
201
+ private readonly unsubscribeOutbox;
202
+ private readonly unsubscribeOverlay;
203
+ private drainingOutbox;
204
+ private outboxDrainTimer;
147
205
  private queryCacheDirective;
148
206
  private queryCacheGeneration;
149
207
  private syncScopeGeneration;
@@ -162,12 +220,24 @@ export declare class GonvexClient {
162
220
  private connectionCount;
163
221
  private readonly timeouts;
164
222
  constructor(url: string, options?: GonvexClientOptions);
223
+ /** The client's materialized optimistic state for pending-row indicators. */
224
+ get optimisticOverlay(): OptimisticOverlay;
225
+ /** Number of mutations waiting for a definitive server result. */
226
+ outboxCount(): Promise<number>;
165
227
  connectionState(): ConnectionState;
166
228
  /** Metadata advertised by the runtime in its latest session.ready frame. */
167
229
  serverInfo(): Readonly<ServerCapabilities>;
168
230
  subscribeToConnectionState(handler: ConnectionStateHandler): () => void;
169
231
  private notifyConnectionState;
170
232
  setAuth(auth: GonvexClientAuth): void;
233
+ /**
234
+ * Subscribe to unrecoverable auth rejections: the server refused the
235
+ * credentials and, when a token fetcher is installed, a force-refreshed
236
+ * token did not fix it. Lets apps route to sign-in instead of silently
237
+ * degrading to an unauthenticated session.
238
+ */
239
+ onAuthError(handler: (error: string) => void): () => void;
240
+ private applyAuth;
171
241
  connect(): void;
172
242
  close(): void;
173
243
  private rejectPendingCalls;
@@ -184,7 +254,7 @@ export declare class GonvexClient {
184
254
  localQueryResult(): T | undefined;
185
255
  onUpdate(handler: WatchUpdateHandler): () => void;
186
256
  };
187
- subscribeSync(ref: FunctionReference, args: JsonValue | undefined, onMessage: SubscriptionHandler): () => void;
257
+ subscribeSync(ref: FunctionReference, args: JsonValue | undefined, onMessage: SyncSubscriptionHandler): () => void;
188
258
  watchSync<T extends JsonValue = JsonValue>(ref: FunctionReference, args?: JsonValue): {
189
259
  localSyncResult(): T[] | undefined;
190
260
  status(): {
@@ -196,6 +266,8 @@ export declare class GonvexClient {
196
266
  private handleSyncMessage;
197
267
  private acceptSyncReady;
198
268
  private emitSyncMessage;
269
+ private materializeSyncMessage;
270
+ private emitOptimisticCollection;
199
271
  private markSyncSubscriptionsOutOfDate;
200
272
  private startSync;
201
273
  private sendSyncOpen;
@@ -204,6 +276,15 @@ export declare class GonvexClient {
204
276
  private unsubscribeSyncListener;
205
277
  private persistSyncSnapshot;
206
278
  private persistSyncDelta;
279
+ private restoreOutbox;
280
+ private addOptimisticMutation;
281
+ private settleOptimisticMutation;
282
+ private rejectOptimisticMutation;
283
+ private drainOutbox;
284
+ private scheduleOutboxDrain;
285
+ mutation<T = JsonValue>(ref: FunctionReference, args: JsonValue, options: CallOptions & {
286
+ offline: "queue";
287
+ }): Promise<T | QueuedMutationOutcome>;
207
288
  mutation<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
208
289
  action<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
209
290
  query<T = JsonValue>(ref: FunctionReference, args?: JsonValue, options?: CallOptions): Promise<T>;
@@ -256,6 +337,10 @@ export declare class GonvexClient {
256
337
  private emitTelemetry;
257
338
  private reportTelemetry;
258
339
  private sendAuth;
340
+ private sendAuthFrame;
341
+ private fetchAndSendAuth;
342
+ private refreshRejectedAuth;
343
+ private notifyAuthError;
259
344
  private armAuthWatchdog;
260
345
  private send;
261
346
  private sendNow;