@ekodb/ekodb-client 0.19.0 → 0.20.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
@@ -269,7 +269,6 @@ const joinResults = await client.find("users", multiQuery);
269
269
  - `.contains(field, value)` - String contains
270
270
  - `.startsWith(field, value)` - String starts with
271
271
  - `.endsWith(field, value)` - String ends with
272
- - `.regex(field, pattern)` - Regex match
273
272
  - `.sortAsc(field)` / `.sortDesc(field)` - Sorting
274
273
  - `.limit(n)` / `.skip(n)` - Pagination
275
274
  - `.join(joinConfig)` - Add join configuration
package/dist/client.d.ts CHANGED
@@ -374,6 +374,20 @@ export declare class EkoDBClient {
374
374
  * Sleep for a specified number of seconds
375
375
  */
376
376
  private sleep;
377
+ /**
378
+ * Parse a `Retry-After` header into a non-negative delay in seconds.
379
+ *
380
+ * Per RFC 9110 the value is either delay-seconds (an integer) or an
381
+ * HTTP-date. Anything that doesn't resolve to a finite, non-negative number
382
+ * (missing header, garbage, a past date) falls back to `defaultSecs`.
383
+ */
384
+ private parseRetryAfter;
385
+ /**
386
+ * Backoff delay (in seconds) for a 0-indexed retry attempt: a capped
387
+ * exponential schedule (0.2s → 5s) with full jitter, so concurrent clients
388
+ * don't retry in lockstep. Returns a value in [d/2, d].
389
+ */
390
+ private backoffSeconds;
377
391
  /**
378
392
  * Helper to determine if a path should use JSON
379
393
  * Only CRUD operations (insert/update/delete/batch) use MessagePack
@@ -1090,9 +1104,17 @@ export declare class EkoDBClient {
1090
1104
  filterValue?: string;
1091
1105
  }): EventStream<MutationNotification>;
1092
1106
  /**
1093
- * Create a WebSocket client
1107
+ * Create a WebSocket client.
1108
+ *
1109
+ * The token is supplied as a provider bound to this client's
1110
+ * {@link getToken}, so every (re)connect re-evaluates (and proactively
1111
+ * refreshes) the auth token instead of snapshotting it once. This means a
1112
+ * reconnect after a token rotation uses the current token.
1113
+ *
1114
+ * @param wsURL - The WebSocket URL (e.g. `wss://host`); `/api/ws` is appended if absent.
1115
+ * @param options - Optional reconnect/timeout tunables.
1094
1116
  */
1095
- websocket(wsURL: string): WebSocketClient;
1117
+ websocket(wsURL: string, options?: WebSocketClientOptions): WebSocketClient;
1096
1118
  /**
1097
1119
  * Generate embeddings for a single text
1098
1120
  *
@@ -1236,6 +1258,35 @@ export interface SubscribeOptions {
1236
1258
  filterField?: string;
1237
1259
  filterValue?: string;
1238
1260
  }
1261
+ /**
1262
+ * A token provider: either a static token string, or a (possibly async)
1263
+ * function that returns a fresh token. When a function is supplied it is
1264
+ * re-invoked on every (re)connect, so a rotated/refreshed token is always
1265
+ * used for the new socket instead of a stale snapshot captured once.
1266
+ */
1267
+ export type TokenProvider = string | (() => string | null | Promise<string | null>);
1268
+ /** Tunables for the WebSocket client's reconnect + request-timeout behavior. */
1269
+ export interface WebSocketClientOptions {
1270
+ /**
1271
+ * Auto-reconnect after an unexpected socket close/error (not an explicit
1272
+ * `close()`/unsubscribe). Defaults to true.
1273
+ */
1274
+ autoReconnect?: boolean;
1275
+ /** Initial backoff delay in ms before the first reconnect attempt. Default 200. */
1276
+ reconnectInitialDelayMs?: number;
1277
+ /** Maximum backoff delay in ms (the cap for exponential growth). Default 5000. */
1278
+ reconnectMaxDelayMs?: number;
1279
+ /**
1280
+ * Maximum number of consecutive reconnect attempts before giving up.
1281
+ * 0 or undefined means unlimited. Default unlimited.
1282
+ */
1283
+ reconnectMaxAttempts?: number;
1284
+ /**
1285
+ * Per-request timeout in ms for request/response WS calls. If no response
1286
+ * arrives in this window the pending promise rejects. Default 30000.
1287
+ */
1288
+ requestTimeoutMs?: number;
1289
+ }
1239
1290
  /** EventEmitter-like interface for subscriptions and chat streams. */
1240
1291
  export declare class EventStream<_T = unknown> {
1241
1292
  private listeners;
@@ -1280,24 +1331,67 @@ export declare class SchemaCache {
1280
1331
  export declare function extractRecordId(record: Record, extraCandidates?: string[]): string | undefined;
1281
1332
  export declare class WebSocketClient {
1282
1333
  private wsURL;
1283
- private token;
1334
+ private tokenProvider;
1284
1335
  private ws;
1285
1336
  private dispatcherRunning;
1286
1337
  private schemaCache;
1338
+ private autoReconnect;
1339
+ private reconnectInitialDelayMs;
1340
+ private reconnectMaxDelayMs;
1341
+ private reconnectMaxAttempts;
1342
+ private requestTimeoutMs;
1343
+ /** Set while close() is in progress so the close handler doesn't reconnect. */
1344
+ private closed;
1345
+ private reconnectAttempts;
1346
+ private reconnecting;
1347
+ private connectPromise;
1287
1348
  private pendingRequests;
1288
1349
  private subscriptions;
1350
+ /** Bookkeeping so subscriptions can be replayed on reconnect. */
1351
+ private subscriptionParams;
1289
1352
  private chatStreams;
1290
1353
  private registerToolsAck;
1291
- constructor(wsURL: string, token: string);
1354
+ /**
1355
+ * @param wsURL - WebSocket URL; `/api/ws` is appended if absent.
1356
+ * @param token - A static token string OR a {@link TokenProvider} function
1357
+ * re-evaluated on every (re)connect (so a refreshed token is used after a drop).
1358
+ * @param options - Optional reconnect/timeout tunables.
1359
+ */
1360
+ constructor(wsURL: string, token: TokenProvider, options?: WebSocketClientOptions);
1292
1361
  private messageCounter;
1293
1362
  private genMessageId;
1294
1363
  /**
1295
- * Connect and start the dispatcher.
1364
+ * Compute the capped exponential backoff (with jitter) for a reconnect
1365
+ * attempt. attempt 0 -> ~initial, growing x2 each time up to the max cap.
1366
+ * Jitter is +/-25% to avoid thundering-herd reconnect storms.
1367
+ * @internal exposed for testing
1368
+ */
1369
+ computeBackoff(attempt: number): number;
1370
+ /**
1371
+ * Connect and start the dispatcher. Re-evaluates the token provider so the
1372
+ * current/refreshed token is used for this socket.
1296
1373
  */
1297
1374
  private ensureConnected;
1375
+ private openSocket;
1298
1376
  private spawnDispatcher;
1377
+ /**
1378
+ * Reject in-flight requests and tear down the dead socket. If the close was
1379
+ * unexpected (not an explicit `close()`) and auto-reconnect is enabled,
1380
+ * schedule a reconnect that re-sends the active subscriptions.
1381
+ */
1382
+ private handleDisconnect;
1383
+ /**
1384
+ * Reconnect with capped exponential backoff + jitter, then re-send the
1385
+ * subscribe messages for every active subscription so the SAME EventStream
1386
+ * keeps delivering mutations after a transient drop.
1387
+ */
1388
+ private scheduleReconnect;
1389
+ /** Re-send Subscribe frames for every tracked subscription after a reconnect. */
1390
+ private resubscribeAll;
1299
1391
  private routeMessage;
1300
1392
  private sendRequest;
1393
+ /** Resolve/reject a pending request, clearing its timeout timer. */
1394
+ private settlePending;
1301
1395
  /**
1302
1396
  * Find all records in a collection via WebSocket.
1303
1397
  */
@@ -1307,6 +1401,11 @@ export declare class WebSocketClient {
1307
1401
  * Returns an EventStream that emits "mutation" events.
1308
1402
  */
1309
1403
  subscribe(collection: string, options?: SubscribeOptions): Promise<EventStream<MutationNotification>>;
1404
+ /**
1405
+ * Unsubscribe from a collection's mutation notifications. This is an
1406
+ * intentional teardown, so the subscription is NOT replayed on reconnect.
1407
+ */
1408
+ unsubscribe(collection: string): void;
1310
1409
  /**
1311
1410
  * Send a chat message and receive a streaming response.
1312
1411
  * Returns an EventStream that emits "event" with ChatStreamEvent objects.
@@ -1369,6 +1468,10 @@ export declare class WebSocketClient {
1369
1468
  deleteCollection(name: string): Promise<void>;
1370
1469
  /**
1371
1470
  * Close the WebSocket connection.
1471
+ *
1472
+ * This is an INTENTIONAL close: it disables auto-reconnect, rejects any
1473
+ * in-flight requests, and tears down all subscriptions/chat streams so
1474
+ * nothing is replayed afterward.
1372
1475
  */
1373
1476
  close(): void;
1374
1477
  }