@aouda/client 0.1.14 → 0.1.16

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/cli/index.js CHANGED
@@ -392,7 +392,7 @@ import { fileURLToPath } from "url";
392
392
  // package.json
393
393
  var package_default = {
394
394
  name: "@aouda/client",
395
- version: "0.1.14",
395
+ version: "0.1.16",
396
396
  description: "Official TypeScript/JavaScript client library for Aouda",
397
397
  type: "module",
398
398
  main: "./dist/index.cjs",
@@ -485,9 +485,10 @@ var AoudaError = class extends Error {
485
485
  }
486
486
  };
487
487
  var AoudaConnectionError = class extends AoudaError {
488
- constructor(message, cause) {
488
+ constructor(message, cause, rowErrors) {
489
489
  super(message);
490
490
  this.cause = cause;
491
+ this.rowErrors = rowErrors;
491
492
  this.name = "AoudaConnectionError";
492
493
  }
493
494
  };
@@ -507,43 +508,45 @@ var AoudaResponseError = class extends AoudaError {
507
508
  }
508
509
  };
509
510
  var AoudaApiError = class extends AoudaError {
510
- constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
511
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors) {
511
512
  super(message);
512
513
  this.code = code;
513
514
  this.statusCode = statusCode;
514
515
  this.details = details;
515
516
  this.requestId = requestId;
516
517
  this.retryAfterSeconds = retryAfterSeconds;
518
+ this.token = token;
519
+ this.rowErrors = rowErrors;
517
520
  this.name = "AoudaApiError";
518
521
  }
519
522
  };
520
523
  var AoudaNotFoundError = class extends AoudaApiError {
521
- constructor(message, code, statusCode, details, requestId) {
522
- super(message, code, statusCode, details, requestId);
524
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors) {
525
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors);
523
526
  this.name = "AoudaNotFoundError";
524
527
  }
525
528
  };
526
529
  var AoudaConflictError = class extends AoudaApiError {
527
- constructor(message, code, statusCode, details, requestId) {
528
- super(message, code, statusCode, details, requestId);
530
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors) {
531
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors);
529
532
  this.name = "AoudaConflictError";
530
533
  }
531
534
  };
532
535
  var AoudaValidationError = class extends AoudaApiError {
533
- constructor(message, code, statusCode, details, requestId) {
534
- super(message, code, statusCode, details, requestId);
536
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors) {
537
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors);
535
538
  this.name = "AoudaValidationError";
536
539
  }
537
540
  };
538
541
  var AoudaServerError = class extends AoudaApiError {
539
- constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
540
- super(message, code, statusCode, details, requestId, retryAfterSeconds);
542
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors) {
543
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors);
541
544
  this.name = "AoudaServerError";
542
545
  }
543
546
  };
544
547
  var AoudaAuthenticationError = class extends AoudaApiError {
545
- constructor(message, code, statusCode, details, requestId) {
546
- super(message, code, statusCode, details, requestId);
548
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors) {
549
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token, rowErrors);
547
550
  this.name = "AoudaAuthenticationError";
548
551
  }
549
552
  };
@@ -1339,7 +1342,7 @@ function buildHandle(jobId, commitResp, wasResumed, transport, database, signal)
1339
1342
  rowsDurablyCommitted: commitResp.rowsLoaded,
1340
1343
  segmentsCreated: commitResp.segmentsCreated,
1341
1344
  committedAtUtc: commitResp.committedAtUtc,
1342
- walPosition: commitResp.walPosition,
1345
+ token: commitResp.token,
1343
1346
  writeConcernSatisfied: commitResp.writeConcernAchieved ?? "acknowledged",
1344
1347
  writeConcernTimedOut: commitResp.writeConcernTimedOut,
1345
1348
  wasResumed,
@@ -1465,6 +1468,7 @@ var PROTOCOL_VERSION_HEADER = "X-Aouda-Protocol-Version";
1465
1468
  var PROTOCOL_VERSION = "1";
1466
1469
  var CONTENT_TYPE_JSON2 = "application/json";
1467
1470
  var REQUEST_ID_HEADER2 = "X-Request-Id";
1471
+ var TOKEN_HEADER = "X-Aouda-Token";
1468
1472
  var ERROR_CODE_MAP = {
1469
1473
  TABLE_NOT_FOUND: AoudaNotFoundError,
1470
1474
  COLUMN_NOT_FOUND: AoudaNotFoundError,
@@ -1479,6 +1483,10 @@ var ERROR_CODE_MAP = {
1479
1483
  INVALID_OPERATOR: AoudaValidationError,
1480
1484
  INVALID_COLUMN: AoudaValidationError,
1481
1485
  INVALID_VALUE: AoudaValidationError,
1486
+ CONSTRAINT_CHECK_VIOLATION: AoudaValidationError,
1487
+ TRANSFORM_DERIVED_READONLY: AoudaValidationError,
1488
+ TRANSFORM_ROUTE_UNMATCHED: AoudaValidationError,
1489
+ TRANSFORM_ROUTE_AMBIGUOUS: AoudaValidationError,
1482
1490
  UNSUPPORTED_VERSION: AoudaValidationError,
1483
1491
  MALFORMED_REQUEST: AoudaValidationError,
1484
1492
  INTERNAL_ERROR: AoudaServerError,
@@ -1504,7 +1512,12 @@ var ERROR_CODE_MAP = {
1504
1512
  AUTH_IDENTITY_INVALID: AoudaValidationError,
1505
1513
  AUTH_IDENTITY_NOT_FOUND: AoudaValidationError,
1506
1514
  BULK_LOAD_TRANSFORM_INTENT_REQUIRED: AoudaValidationError,
1507
- BULK_LOAD_TRANSFORM_INTENT_CONFLICT: AoudaValidationError
1515
+ BULK_LOAD_TRANSFORM_INTENT_CONFLICT: AoudaValidationError,
1516
+ TOKEN_MALFORMED: AoudaValidationError,
1517
+ TOKEN_FOREIGN_DATABASE: AoudaValidationError,
1518
+ TOKEN_EPOCH_SUPERSEDED: AoudaConflictError,
1519
+ TOKEN_UNSATISFIED: AoudaConflictError,
1520
+ TOKEN_FETCH_PRIMARY: AoudaApiError
1508
1521
  };
1509
1522
  function createComposedAbortController(...signals) {
1510
1523
  const controller = new AbortController();
@@ -1534,6 +1547,9 @@ function parseRetryAfterSeconds(header) {
1534
1547
  if (!Number.isFinite(n) || n < 0) return void 0;
1535
1548
  return n;
1536
1549
  }
1550
+ function nonEmptyRowErrors(rowErrors) {
1551
+ return Array.isArray(rowErrors) && rowErrors.length > 0 ? rowErrors : void 0;
1552
+ }
1537
1553
  function createApiError(statusCode, statusText, body, retryAfterHeader) {
1538
1554
  const message = body.error ?? `${statusCode} ${statusText}`;
1539
1555
  const code = body.code ?? "UNKNOWN";
@@ -1541,7 +1557,16 @@ function createApiError(statusCode, statusText, body, retryAfterHeader) {
1541
1557
  const requestId = body.requestId;
1542
1558
  const retryAfterSeconds = parseRetryAfterSeconds(retryAfterHeader ?? null);
1543
1559
  const Ctor = ERROR_CODE_MAP[code] ?? AoudaApiError;
1544
- return new Ctor(message, code, statusCode, details, requestId, retryAfterSeconds);
1560
+ return new Ctor(
1561
+ message,
1562
+ code,
1563
+ statusCode,
1564
+ details,
1565
+ requestId,
1566
+ retryAfterSeconds,
1567
+ body.token,
1568
+ nonEmptyRowErrors(body.rowErrors)
1569
+ );
1545
1570
  }
1546
1571
  var HttpTransport = class {
1547
1572
  constructor(options) {
@@ -1553,6 +1578,29 @@ var HttpTransport = class {
1553
1578
  ...options.defaultHeaders
1554
1579
  };
1555
1580
  this.abortController = new AbortController();
1581
+ this.database = options.database;
1582
+ this.store = options.consistencyTokenStore;
1583
+ }
1584
+ presentTokenHeaders(path4, headers) {
1585
+ if (this.store == null || this.database == null || isAuthPath(path4)) {
1586
+ return;
1587
+ }
1588
+ const token = this.store.get(this.database);
1589
+ if (token != null && token.length > 0) {
1590
+ headers[TOKEN_HEADER] = token;
1591
+ }
1592
+ }
1593
+ observeResponse(_path, response, bodyText, errorCode) {
1594
+ if (this.store == null || this.database == null) {
1595
+ return;
1596
+ }
1597
+ if (errorCode === "TOKEN_MALFORMED" || errorCode === "TOKEN_FOREIGN_DATABASE") {
1598
+ return;
1599
+ }
1600
+ const header = response.headers.get(TOKEN_HEADER);
1601
+ const bodyToken = readBodyToken(bodyText);
1602
+ const token = header != null && header.trim().length > 0 ? header : bodyToken;
1603
+ this.store.observe(this.database, token);
1556
1604
  }
1557
1605
  /**
1558
1606
  * Abort all in-flight and future requests (e.g. on client disconnect).
@@ -1590,6 +1638,7 @@ var HttpTransport = class {
1590
1638
  ...this.defaultHeaders,
1591
1639
  ...config.headers
1592
1640
  };
1641
+ this.presentTokenHeaders(config.path, headers);
1593
1642
  if (config.body !== void 0 && config.body !== null) {
1594
1643
  headers["Content-Type"] = CONTENT_TYPE_JSON2;
1595
1644
  }
@@ -1609,6 +1658,7 @@ var HttpTransport = class {
1609
1658
  const text = await response.text();
1610
1659
  if (config.allowStatuses?.includes(response.status) && text) {
1611
1660
  try {
1661
+ this.observeResponse(config.path, response, text);
1612
1662
  return JSON.parse(text);
1613
1663
  } catch {
1614
1664
  }
@@ -1622,15 +1672,20 @@ var HttpTransport = class {
1622
1672
  }
1623
1673
  if (response.status === 401) {
1624
1674
  const code = errorBody?.code ?? "AUTH_TOKEN_MISSING";
1675
+ this.observeResponse(config.path, response, text, code);
1625
1676
  throw new AoudaAuthenticationError(
1626
1677
  errorBody?.error ?? "Unauthorized",
1627
1678
  code,
1628
1679
  401,
1629
1680
  errorBody?.details,
1630
- errorBody?.requestId
1681
+ errorBody?.requestId,
1682
+ void 0,
1683
+ errorBody?.token,
1684
+ nonEmptyRowErrors(errorBody?.rowErrors)
1631
1685
  );
1632
1686
  }
1633
1687
  if (errorBody?.code != null) {
1688
+ this.observeResponse(config.path, response, text, errorBody.code);
1634
1689
  throw createApiError(
1635
1690
  response.status,
1636
1691
  response.statusText,
@@ -1638,6 +1693,7 @@ var HttpTransport = class {
1638
1693
  response.headers.get("Retry-After")
1639
1694
  );
1640
1695
  }
1696
+ this.observeResponse(config.path, response, text);
1641
1697
  throw new AoudaResponseError(
1642
1698
  errorBody?.error ?? `${response.status} ${response.statusText}`,
1643
1699
  response.status,
@@ -1645,6 +1701,7 @@ var HttpTransport = class {
1645
1701
  );
1646
1702
  }
1647
1703
  const responseText = await response.text();
1704
+ this.observeResponse(config.path, response, responseText);
1648
1705
  if (config.rawText) {
1649
1706
  return responseText;
1650
1707
  }
@@ -1705,6 +1762,7 @@ var HttpTransport = class {
1705
1762
  ...this.defaultHeaders,
1706
1763
  ...config.headers
1707
1764
  };
1765
+ this.presentTokenHeaders(config.path, headers);
1708
1766
  if (config.rawBodyStr === void 0 && config.body !== void 0 && config.body !== null) {
1709
1767
  headers["Content-Type"] = CONTENT_TYPE_JSON2;
1710
1768
  }
@@ -1721,6 +1779,7 @@ var HttpTransport = class {
1721
1779
  try {
1722
1780
  const response = await localNetworkFetch(url, init);
1723
1781
  if (response.ok || config.allowStatuses?.includes(response.status)) {
1782
+ this.observeResponse(config.path, response, void 0);
1724
1783
  return response;
1725
1784
  }
1726
1785
  const text = await response.text();
@@ -1733,15 +1792,20 @@ var HttpTransport = class {
1733
1792
  }
1734
1793
  if (response.status === 401) {
1735
1794
  const code = errorBody?.code ?? "AUTH_TOKEN_MISSING";
1795
+ this.observeResponse(config.path, response, text, code);
1736
1796
  throw new AoudaAuthenticationError(
1737
1797
  errorBody?.error ?? "Unauthorized",
1738
1798
  code,
1739
1799
  401,
1740
1800
  errorBody?.details,
1741
- errorBody?.requestId
1801
+ errorBody?.requestId,
1802
+ void 0,
1803
+ errorBody?.token,
1804
+ nonEmptyRowErrors(errorBody?.rowErrors)
1742
1805
  );
1743
1806
  }
1744
1807
  if (errorBody?.code != null) {
1808
+ this.observeResponse(config.path, response, text, errorBody.code);
1745
1809
  throw createApiError(
1746
1810
  response.status,
1747
1811
  response.statusText,
@@ -1749,6 +1813,7 @@ var HttpTransport = class {
1749
1813
  response.headers.get("Retry-After")
1750
1814
  );
1751
1815
  }
1816
+ this.observeResponse(config.path, response, text);
1752
1817
  throw new AoudaResponseError(
1753
1818
  errorBody?.error ?? `${response.status} ${response.statusText}`,
1754
1819
  response.status,
@@ -1903,6 +1968,47 @@ var HttpTransport = class {
1903
1968
  });
1904
1969
  }
1905
1970
  };
1971
+ function isAuthPath(path4) {
1972
+ let p = path4;
1973
+ const q = p.indexOf("?");
1974
+ if (q >= 0) {
1975
+ p = p.slice(0, q);
1976
+ }
1977
+ if (/^https?:\/\//i.test(p)) {
1978
+ try {
1979
+ p = new URL(p).pathname;
1980
+ } catch {
1981
+ }
1982
+ }
1983
+ const lower = p.toLowerCase();
1984
+ if (lower === "/api/auth" || lower.startsWith("/api/auth/")) {
1985
+ return true;
1986
+ }
1987
+ const prefix = "/api/databases/";
1988
+ if (!lower.startsWith(prefix)) {
1989
+ return false;
1990
+ }
1991
+ const rest = lower.slice(prefix.length);
1992
+ const slash = rest.indexOf("/");
1993
+ if (slash < 0) {
1994
+ return false;
1995
+ }
1996
+ const afterDb = rest.slice(slash + 1);
1997
+ return afterDb === "auth" || afterDb.startsWith("auth/");
1998
+ }
1999
+ function readBodyToken(bodyText) {
2000
+ if (bodyText == null || bodyText.trim().length === 0) {
2001
+ return void 0;
2002
+ }
2003
+ try {
2004
+ const parsed = JSON.parse(bodyText);
2005
+ if (parsed !== null && typeof parsed === "object" && "token" in parsed && typeof parsed.token === "string") {
2006
+ return parsed.token;
2007
+ }
2008
+ } catch {
2009
+ }
2010
+ return void 0;
2011
+ }
1906
2012
 
1907
2013
  // src/resilience/retry.ts
1908
2014
  var DEFAULT_MAX_RETRIES = 3;
@@ -2335,6 +2441,48 @@ function databasePath2(db) {
2335
2441
  return `/api/databases/${encodeURIComponent(db)}`;
2336
2442
  }
2337
2443
 
2444
+ // src/consistency-token-store.ts
2445
+ var MemoryConsistencyTokenStore = class {
2446
+ constructor() {
2447
+ this.tokens = /* @__PURE__ */ new Map();
2448
+ }
2449
+ get(database) {
2450
+ if (typeof database !== "string" || database.trim().length === 0) {
2451
+ throw new Error("database must be a non-empty string");
2452
+ }
2453
+ return this.tokens.get(normalizeKey(database));
2454
+ }
2455
+ observe(database, token) {
2456
+ if (typeof database !== "string" || database.trim().length === 0) {
2457
+ throw new Error("database must be a non-empty string");
2458
+ }
2459
+ if (token == null || token.trim().length === 0) {
2460
+ return;
2461
+ }
2462
+ const key = normalizeKey(database);
2463
+ const stored = this.tokens.get(key);
2464
+ if (stored === void 0 || compareOrdinal(token, stored) > 0) {
2465
+ this.tokens.set(key, token);
2466
+ }
2467
+ }
2468
+ };
2469
+ function compareOrdinal(left, right) {
2470
+ if (left === right) return 0;
2471
+ return left < right ? -1 : 1;
2472
+ }
2473
+ function maxToken(left, right) {
2474
+ if (left == null || left.length === 0) {
2475
+ return right == null || right.length === 0 ? void 0 : right;
2476
+ }
2477
+ if (right == null || right.length === 0) {
2478
+ return left;
2479
+ }
2480
+ return compareOrdinal(left, right) >= 0 ? left : right;
2481
+ }
2482
+ function normalizeKey(database) {
2483
+ return database.toLowerCase();
2484
+ }
2485
+
2338
2486
  // src/streaming/subscription.ts
2339
2487
  var AsyncEventQueue = class {
2340
2488
  constructor() {
@@ -2391,7 +2539,7 @@ var AsyncEventQueue = class {
2391
2539
  }
2392
2540
  };
2393
2541
  var TableSubscription = class {
2394
- constructor(transport, identity, options = {}, onWarnings) {
2542
+ constructor(transport, identity, options = {}, onWarnings, store, database) {
2395
2543
  this._queue = new AsyncEventQueue();
2396
2544
  this._active = true;
2397
2545
  this._started = false;
@@ -2407,6 +2555,11 @@ var TableSubscription = class {
2407
2555
  this._onError = options.onError;
2408
2556
  this._onWarnings = onWarnings;
2409
2557
  this._conflate = options.conflate;
2558
+ this._atLeast = options.atLeast;
2559
+ this._waitMs = options.waitMs;
2560
+ this._onExceeded = options.onExceeded;
2561
+ this._store = store;
2562
+ this._database = database;
2410
2563
  this._reconnectHandlerKey = `${this.id}::reconnect`;
2411
2564
  }
2412
2565
  get lastVersion() {
@@ -2471,7 +2624,7 @@ var TableSubscription = class {
2471
2624
  id: this.id
2472
2625
  };
2473
2626
  if (this._identity.kind === "named") {
2474
- message.hash = this._identity.hash;
2627
+ message.name = this._identity.name;
2475
2628
  if (this._identity.args !== void 0) {
2476
2629
  message.args = this._identity.args;
2477
2630
  }
@@ -2490,8 +2643,30 @@ var TableSubscription = class {
2490
2643
  if (this._conflate !== void 0) {
2491
2644
  message.conflate = this._conflate;
2492
2645
  }
2646
+ const pin = this._resolvePin();
2647
+ if (pin !== void 0) {
2648
+ message.at_least = pin;
2649
+ }
2650
+ if (this._waitMs !== void 0) {
2651
+ message.wait_ms = this._waitMs;
2652
+ }
2653
+ if (this._onExceeded !== void 0) {
2654
+ message.on_exceeded = this._onExceeded;
2655
+ }
2493
2656
  await this._transport.send(message);
2494
2657
  }
2658
+ _resolvePin() {
2659
+ if (this._atLeast !== void 0 && this._store != null && this._database != null) {
2660
+ this._store.observe(this._database, this._atLeast);
2661
+ }
2662
+ const stored = this._store != null && this._database != null ? this._store.get(this._database) : void 0;
2663
+ return maxToken(this._atLeast, stored);
2664
+ }
2665
+ _observeToken(token) {
2666
+ if (token !== void 0 && this._store != null && this._database != null) {
2667
+ this._store.observe(this._database, token);
2668
+ }
2669
+ }
2495
2670
  _handleMessage(message) {
2496
2671
  if (!this._active) {
2497
2672
  return;
@@ -2518,6 +2693,7 @@ var TableSubscription = class {
2518
2693
  }
2519
2694
  _handleSnapshotPage(message) {
2520
2695
  this._pendingSnapshotRows.push(...message.rows);
2696
+ this._observeToken(message.token);
2521
2697
  }
2522
2698
  _handleSnapshotComplete(message) {
2523
2699
  this._lastVersion = message.version;
@@ -2535,6 +2711,10 @@ var TableSubscription = class {
2535
2711
  if (message.total_matches !== void 0) {
2536
2712
  snapshot.totalMatches = message.total_matches;
2537
2713
  }
2714
+ if (message.token !== void 0) {
2715
+ snapshot.token = message.token;
2716
+ this._observeToken(message.token);
2717
+ }
2538
2718
  this._queue.push(snapshot);
2539
2719
  }
2540
2720
  async _handleGap(message) {
@@ -2560,6 +2740,10 @@ var TableSubscription = class {
2560
2740
  if (message.values_skipped !== void 0) {
2561
2741
  event.values_skipped = message.values_skipped;
2562
2742
  }
2743
+ if (message.token !== void 0) {
2744
+ event.token = message.token;
2745
+ this._observeToken(message.token);
2746
+ }
2563
2747
  this._onChange?.(event);
2564
2748
  this._queue.push(event);
2565
2749
  }
@@ -2707,8 +2891,11 @@ var TableWriteStream = class {
2707
2891
  resolve4?.();
2708
2892
  }
2709
2893
  _handleServerError(message) {
2894
+ const rowErrors = Array.isArray(message.errors) && message.errors.length > 0 ? message.errors : void 0;
2710
2895
  const error = new AoudaConnectionError(
2711
- `Write stream error (${message.code}): ${message.message}`
2896
+ `Write stream error (${message.code}): ${message.message}`,
2897
+ void 0,
2898
+ rowErrors
2712
2899
  );
2713
2900
  const openReject = this._openReject;
2714
2901
  this._openResolve = null;
@@ -2904,11 +3091,12 @@ var TableQuery = class _TableQuery {
2904
3091
  * @param state - Optional initial state (used for immutable chaining).
2905
3092
  * @internal Use `client.table()` to create queries.
2906
3093
  */
2907
- constructor(transport, tableName, database, state, getWebSocketTransport) {
3094
+ constructor(transport, tableName, database, state, getWebSocketTransport, store) {
2908
3095
  this.transport = transport;
2909
3096
  this.tableName = tableName;
2910
3097
  this.database = database;
2911
3098
  this.getWebSocketTransport = getWebSocketTransport;
3099
+ this.store = store;
2912
3100
  this.state = state ?? {
2913
3101
  predicates: [],
2914
3102
  groupClauses: [],
@@ -2924,6 +3112,16 @@ var TableQuery = class _TableQuery {
2924
3112
  isDistinct: false
2925
3113
  };
2926
3114
  }
3115
+ withState(state) {
3116
+ return new _TableQuery(
3117
+ this.transport,
3118
+ this.tableName,
3119
+ this.database,
3120
+ state,
3121
+ this.getWebSocketTransport,
3122
+ this.store
3123
+ );
3124
+ }
2927
3125
  where(column, operator, value) {
2928
3126
  const newPredicates = buildWherePredicates(column, operator, value);
2929
3127
  return new _TableQuery(
@@ -2934,7 +3132,8 @@ var TableQuery = class _TableQuery {
2934
3132
  ...this.state,
2935
3133
  predicates: [...this.state.predicates, ...newPredicates]
2936
3134
  },
2937
- this.getWebSocketTransport
3135
+ this.getWebSocketTransport,
3136
+ this.store
2938
3137
  );
2939
3138
  }
2940
3139
  /**
@@ -2953,7 +3152,7 @@ var TableQuery = class _TableQuery {
2953
3152
  return new _TableQuery(this.transport, this.tableName, this.database, {
2954
3153
  ...this.state,
2955
3154
  groupClauses: [...this.state.groupClauses, sub]
2956
- }, this.getWebSocketTransport);
3155
+ }, this.getWebSocketTransport, this.store);
2957
3156
  }
2958
3157
  /**
2959
3158
  * Sets the primary sort column for the query.
@@ -2978,7 +3177,7 @@ var TableQuery = class _TableQuery {
2978
3177
  return new _TableQuery(this.transport, this.tableName, this.database, {
2979
3178
  ...this.state,
2980
3179
  orderByClauses: [orderByClause]
2981
- }, this.getWebSocketTransport);
3180
+ }, this.getWebSocketTransport, this.store);
2982
3181
  }
2983
3182
  /**
2984
3183
  * Sets the primary sort column to descending order.
@@ -3024,7 +3223,7 @@ var TableQuery = class _TableQuery {
3024
3223
  return new _TableQuery(this.transport, this.tableName, this.database, {
3025
3224
  ...this.state,
3026
3225
  orderByClauses: [...this.state.orderByClauses, orderByClause]
3027
- }, this.getWebSocketTransport);
3226
+ }, this.getWebSocketTransport, this.store);
3028
3227
  }
3029
3228
  /**
3030
3229
  * Sets the maximum number of rows to return.
@@ -3041,7 +3240,7 @@ var TableQuery = class _TableQuery {
3041
3240
  return new _TableQuery(this.transport, this.tableName, this.database, {
3042
3241
  ...this.state,
3043
3242
  limitValue: count
3044
- }, this.getWebSocketTransport);
3243
+ }, this.getWebSocketTransport, this.store);
3045
3244
  }
3046
3245
  /**
3047
3246
  * Sets the number of rows to skip.
@@ -3058,7 +3257,7 @@ var TableQuery = class _TableQuery {
3058
3257
  return new _TableQuery(this.transport, this.tableName, this.database, {
3059
3258
  ...this.state,
3060
3259
  offsetValue: count
3061
- }, this.getWebSocketTransport);
3260
+ }, this.getWebSocketTransport, this.store);
3062
3261
  }
3063
3262
  /**
3064
3263
  * Requests cross-partition access for this query.
@@ -3073,10 +3272,21 @@ var TableQuery = class _TableQuery {
3073
3272
  return new _TableQuery(this.transport, this.tableName, this.database, {
3074
3273
  ...this.state,
3075
3274
  crossPartitionAccess: true
3076
- }, this.getWebSocketTransport);
3275
+ }, this.getWebSocketTransport, this.store);
3077
3276
  }
3078
3277
  /**
3079
- * Restricts the columns returned in the result.
3278
+ * Pin this query at at least this C-1 token. Observes the token into the
3279
+ * client store (I3, sticky) and presents it on execute via `X-Aouda-Token`.
3280
+ */
3281
+ atLeast(token) {
3282
+ if (typeof token !== "string" || token.trim().length === 0) {
3283
+ throw new Error("atLeast() requires a non-empty token");
3284
+ }
3285
+ this.store?.observe(this.database, token);
3286
+ return this.withState({ ...this.state, atLeast: token });
3287
+ }
3288
+ /**
3289
+ * Selects specific columns to return.
3080
3290
  * If not called, all columns are returned.
3081
3291
  *
3082
3292
  * When T is a specific row type, only keys of T are accepted as column names.
@@ -3098,7 +3308,7 @@ var TableQuery = class _TableQuery {
3098
3308
  return new _TableQuery(this.transport, this.tableName, this.database, {
3099
3309
  ...this.state,
3100
3310
  selectColumns: columns.length > 0 ? columns : null
3101
- }, this.getWebSocketTransport);
3311
+ }, this.getWebSocketTransport, this.store);
3102
3312
  }
3103
3313
  /**
3104
3314
  * Return only distinct (de-duplicated) rows for the given columns — SQL `SELECT DISTINCT`.
@@ -3128,7 +3338,7 @@ var TableQuery = class _TableQuery {
3128
3338
  ...this.state,
3129
3339
  selectColumns: columns,
3130
3340
  isDistinct: true
3131
- }, this.getWebSocketTransport);
3341
+ }, this.getWebSocketTransport, this.store);
3132
3342
  }
3133
3343
  /**
3134
3344
  * Adds server-side computed columns to the query result.
@@ -3137,6 +3347,12 @@ var TableQuery = class _TableQuery {
3137
3347
  * evaluated per row on the server. Computed columns are appended after any physical-column
3138
3348
  * `select()` projection.
3139
3349
  *
3350
+ * Result types are inferred by the server where the expression permits
3351
+ * (e.g. Int32 → `number`). Uninferable expressions use wire `"Unknown"` /
3352
+ * codegen `unknown`. Computed columns are always nullable. Named-query
3353
+ * `*Row` properties pick this up when regenerated against a post-S08 server.
3354
+ * See aouda-docs/guides/browser-tier-read-limits.md#selectexpr-result-types
3355
+ *
3140
3356
  * @param projections - One or more `{ alias, expr }` pairs.
3141
3357
  * @returns A new TableQuery with computed columns set.
3142
3358
  *
@@ -3157,7 +3373,7 @@ var TableQuery = class _TableQuery {
3157
3373
  return new _TableQuery(this.transport, this.tableName, this.database, {
3158
3374
  ...this.state,
3159
3375
  selectExprs: projections
3160
- }, this.getWebSocketTransport);
3376
+ }, this.getWebSocketTransport, this.store);
3161
3377
  }
3162
3378
  join(rightTable, leftColumnOrColumns, rightColumnOrColumns) {
3163
3379
  return this.addJoinClause(
@@ -3215,7 +3431,8 @@ var TableQuery = class _TableQuery {
3215
3431
  }
3216
3432
  ]
3217
3433
  },
3218
- this.getWebSocketTransport
3434
+ this.getWebSocketTransport,
3435
+ this.store
3219
3436
  );
3220
3437
  }
3221
3438
  /**
@@ -3251,7 +3468,8 @@ var TableQuery = class _TableQuery {
3251
3468
  ...this.state,
3252
3469
  groupByColumns: [...columns]
3253
3470
  },
3254
- this.getWebSocketTransport
3471
+ this.getWebSocketTransport,
3472
+ this.store
3255
3473
  );
3256
3474
  }
3257
3475
  /**
@@ -3285,8 +3503,11 @@ var TableQuery = class _TableQuery {
3285
3503
  onSnapshot: options.onSnapshot,
3286
3504
  onChange: options.onChange,
3287
3505
  onError: options.onError,
3288
- conflate: options.conflate
3289
- });
3506
+ conflate: options.conflate,
3507
+ atLeast: options.atLeast ?? this.state.atLeast,
3508
+ waitMs: options.waitMs,
3509
+ onExceeded: options.onExceeded
3510
+ }, void 0, this.store, this.database);
3290
3511
  subscription.start();
3291
3512
  return subscription;
3292
3513
  }
@@ -3401,7 +3622,8 @@ var TableQuery = class _TableQuery {
3401
3622
  ...this.state,
3402
3623
  joinClauses: [...this.state.joinClauses, joinClause]
3403
3624
  },
3404
- this.getWebSocketTransport
3625
+ this.getWebSocketTransport,
3626
+ this.store
3405
3627
  );
3406
3628
  }
3407
3629
  addAggregate(op, column) {
@@ -3422,7 +3644,8 @@ var TableQuery = class _TableQuery {
3422
3644
  }
3423
3645
  ]
3424
3646
  },
3425
- this.getWebSocketTransport
3647
+ this.getWebSocketTransport,
3648
+ this.store
3426
3649
  );
3427
3650
  }
3428
3651
  requireWebSocketTransport() {
@@ -3452,17 +3675,23 @@ var TableQuery = class _TableQuery {
3452
3675
  * ```
3453
3676
  */
3454
3677
  async execute() {
3678
+ if (this.state.atLeast) {
3679
+ this.store?.observe(this.database, this.state.atLeast);
3680
+ }
3455
3681
  const request = this.buildRequest();
3456
3682
  const path4 = `${databasePath2(this.database)}/query`;
3457
3683
  const response = await this.transport.post(path4, request);
3458
3684
  const rows = columnarToRows(response);
3459
3685
  const stats = response.stats;
3460
- return { rows, stats };
3686
+ return { rows, stats, token: response.token };
3461
3687
  }
3462
3688
  /**
3463
3689
  * Executes the query and returns the raw columnar JSON payload (no row-object conversion).
3464
3690
  */
3465
3691
  async toColumnar() {
3692
+ if (this.state.atLeast) {
3693
+ this.store?.observe(this.database, this.state.atLeast);
3694
+ }
3466
3695
  const request = this.buildRequest();
3467
3696
  const path4 = `${databasePath2(this.database)}/query`;
3468
3697
  return this.transport.post(path4, request);
@@ -3492,8 +3721,12 @@ var TableQuery = class _TableQuery {
3492
3721
  limitValue: 0,
3493
3722
  selectColumns: []
3494
3723
  },
3495
- this.getWebSocketTransport
3724
+ this.getWebSocketTransport,
3725
+ this.store
3496
3726
  );
3727
+ if (this.state.atLeast) {
3728
+ this.store?.observe(this.database, this.state.atLeast);
3729
+ }
3497
3730
  const request = countQuery.buildRequest();
3498
3731
  const path4 = `${databasePath2(this.database)}/query`;
3499
3732
  const response = await this.transport.post(path4, request);
@@ -3540,7 +3773,8 @@ var TableQuery = class _TableQuery {
3540
3773
  const response = await this.transport.post(path4, body);
3541
3774
  const result = {
3542
3775
  rowsInserted: response.rowsInserted,
3543
- executionMs: response.executionMs
3776
+ executionMs: response.executionMs,
3777
+ token: response.token
3544
3778
  };
3545
3779
  if (response.generatedValues !== void 0) {
3546
3780
  result.generatedValues = response.generatedValues;
@@ -3590,7 +3824,8 @@ var TableQuery = class _TableQuery {
3590
3824
  const response = await this.transport.post(path4, body);
3591
3825
  const result = {
3592
3826
  rowsInserted: response.rowsInserted,
3593
- executionMs: response.executionMs
3827
+ executionMs: response.executionMs,
3828
+ token: response.token
3594
3829
  };
3595
3830
  if (response.generatedValues !== void 0) {
3596
3831
  result.generatedValues = response.generatedValues;
@@ -3642,6 +3877,7 @@ var TableQuery = class _TableQuery {
3642
3877
  return {
3643
3878
  rowsAffected: response.rowsUpdated,
3644
3879
  executionMs: response.executionMs,
3880
+ token: response.token,
3645
3881
  ...response.rows ? { rows: columnarToRows(response.rows) } : {},
3646
3882
  ...response.rowsTruncated ? { rowsTruncated: true } : {}
3647
3883
  };
@@ -3692,6 +3928,7 @@ var TableQuery = class _TableQuery {
3692
3928
  rowsAffected: response.rowsDeleted,
3693
3929
  executionMs: response.executionMs,
3694
3930
  hasMore: response.hasMore,
3931
+ token: response.token,
3695
3932
  ...response.rows ? { rows: columnarToRows(response.rows) } : {},
3696
3933
  ...response.rowsTruncated ? { rowsTruncated: true } : {}
3697
3934
  };
@@ -3711,7 +3948,8 @@ var TableQuery = class _TableQuery {
3711
3948
  return {
3712
3949
  rowsAffected: response.rowsDeleted,
3713
3950
  executionMs: response.executionMs,
3714
- hasMore: false
3951
+ hasMore: false,
3952
+ token: response.token
3715
3953
  };
3716
3954
  }
3717
3955
  /**
@@ -3724,7 +3962,14 @@ var TableQuery = class _TableQuery {
3724
3962
  throw new Error("batch() requires a non-empty operations array");
3725
3963
  }
3726
3964
  const wireOperations = operations.map((op, index) => {
3727
- const base = new _TableQuery(this.transport, this.tableName, this.database);
3965
+ const base = new _TableQuery(
3966
+ this.transport,
3967
+ this.tableName,
3968
+ this.database,
3969
+ void 0,
3970
+ this.getWebSocketTransport,
3971
+ this.store
3972
+ );
3728
3973
  const scoped = op.where(base);
3729
3974
  const where = scoped.buildWhereClause();
3730
3975
  if (!where) {
@@ -5061,12 +5306,12 @@ function raiseDeprecationWarnings(sink, warnings) {
5061
5306
  continue;
5062
5307
  }
5063
5308
  const sunset = warning.sunsetAt != null ? ` sunsetAt=${warning.sunsetAt}` : "";
5064
- const hash = warning.hash != null && warning.hash.length > 0 ? ` hash=${warning.hash}` : "";
5309
+ const name = warning.name != null && warning.name.length > 0 ? ` name=${warning.name}` : "";
5065
5310
  sink({
5066
5311
  code: warning.code,
5067
- hash: warning.hash,
5312
+ name: warning.name,
5068
5313
  sunsetAt: warning.sunsetAt,
5069
- message: `${warning.code}:${hash}${sunset}`.trim()
5314
+ message: `${warning.code}:${name}${sunset}`.trim()
5070
5315
  });
5071
5316
  }
5072
5317
  }
@@ -5079,24 +5324,28 @@ function emptyStats() {
5079
5324
  };
5080
5325
  }
5081
5326
  var NamedQueriesApi = class {
5082
- constructor(transport, database, onWarning, getStreamingTransport) {
5327
+ constructor(transport, database, onWarning, getStreamingTransport, store) {
5083
5328
  this.transport = transport;
5084
5329
  this.database = database;
5085
5330
  this.onWarning = onWarning;
5086
5331
  this.getStreamingTransport = getStreamingTransport;
5332
+ this.store = store;
5087
5333
  }
5088
- async execute(hash, args, options) {
5089
- if (typeof hash !== "string" || hash.trim().length === 0) {
5090
- throw new Error("Named query hash must be a non-empty string");
5334
+ async execute(name, args, options) {
5335
+ if (typeof name !== "string" || name.trim().length === 0) {
5336
+ throw new Error("Named query name must be a non-empty string");
5091
5337
  }
5092
5338
  const prefix = databasePath2(this.database);
5093
- const path4 = `${prefix}/named-queries/${encodeURIComponent(hash)}/query?format=columnar`;
5339
+ const path4 = `${prefix}/named-queries/${encodeURIComponent(name)}/query?format=columnar`;
5094
5340
  const body = {
5095
5341
  args: args ?? {}
5096
5342
  };
5097
5343
  if (options?.orderByIndex !== void 0) {
5098
5344
  body.orderByIndex = options.orderByIndex;
5099
5345
  }
5346
+ if (options?.atLeast !== void 0) {
5347
+ this.store?.observe(this.database, options.atLeast);
5348
+ }
5100
5349
  const response = await this.transport.post(
5101
5350
  path4,
5102
5351
  body,
@@ -5108,7 +5357,8 @@ var NamedQueriesApi = class {
5108
5357
  rows,
5109
5358
  stats: response.stats,
5110
5359
  warnings: response.warnings,
5111
- totalMatches: response.totalMatches
5360
+ totalMatches: response.totalMatches,
5361
+ token: response.token
5112
5362
  };
5113
5363
  }
5114
5364
  async batch(items, options) {
@@ -5126,6 +5376,9 @@ var NamedQueriesApi = class {
5126
5376
  400
5127
5377
  );
5128
5378
  }
5379
+ if (options?.atLeast !== void 0) {
5380
+ this.store?.observe(this.database, options.atLeast);
5381
+ }
5129
5382
  const prefix = databasePath2(this.database);
5130
5383
  const path4 = `${prefix}/named-queries/batch?format=columnar`;
5131
5384
  const envelope = await this.transport.post(
@@ -5148,34 +5401,41 @@ var NamedQueriesApi = class {
5148
5401
  rowCount: slot.rowCount ?? 0,
5149
5402
  stats: slot.stats ?? emptyStats(),
5150
5403
  warnings: slot.warnings,
5151
- totalMatches: slot.totalMatches
5404
+ totalMatches: slot.totalMatches,
5405
+ token: slot.token
5152
5406
  };
5153
5407
  const result = {
5154
5408
  rows: columnarToRows(columnar),
5155
5409
  stats: columnar.stats,
5156
5410
  warnings: slot.warnings,
5157
- totalMatches: slot.totalMatches
5411
+ totalMatches: slot.totalMatches,
5412
+ token: slot.token
5158
5413
  };
5159
5414
  raiseDeprecationWarnings(this.onWarning, slot.warnings);
5160
5415
  return { isError: false, result };
5161
5416
  });
5162
5417
  }
5163
- subscribe(hash, args, options = {}) {
5164
- if (typeof hash !== "string" || hash.trim().length === 0) {
5165
- throw new Error("Named query hash must be a non-empty string");
5418
+ subscribe(name, args, options = {}) {
5419
+ if (typeof name !== "string" || name.trim().length === 0) {
5420
+ throw new Error("Named query name must be a non-empty string");
5166
5421
  }
5167
5422
  const subscription = new TableSubscription(
5168
5423
  this.getStreamingTransport(),
5169
- { kind: "named", hash, args, orderByIndex: options.orderByIndex },
5424
+ { kind: "named", name, args, orderByIndex: options.orderByIndex },
5170
5425
  {
5171
5426
  onSnapshot: options.onSnapshot,
5172
5427
  onChange: options.onChange,
5173
5428
  onError: options.onError,
5174
- conflate: options.conflate
5429
+ conflate: options.conflate,
5430
+ atLeast: options.atLeast,
5431
+ waitMs: options.waitMs,
5432
+ onExceeded: options.onExceeded
5175
5433
  },
5176
5434
  (warnings) => {
5177
5435
  raiseDeprecationWarnings(this.onWarning, warnings);
5178
- }
5436
+ },
5437
+ this.store,
5438
+ this.database
5179
5439
  );
5180
5440
  subscription.start();
5181
5441
  return subscription;
@@ -5187,12 +5447,12 @@ var NamedMutationsApi = class {
5187
5447
  this.database = database;
5188
5448
  this.onWarning = onWarning;
5189
5449
  }
5190
- async execute(hash, args) {
5191
- if (typeof hash !== "string" || hash.trim().length === 0) {
5192
- throw new Error("Named mutation hash must be a non-empty string");
5450
+ async execute(name, args) {
5451
+ if (typeof name !== "string" || name.trim().length === 0) {
5452
+ throw new Error("Named mutation name must be a non-empty string");
5193
5453
  }
5194
5454
  const prefix = databasePath2(this.database);
5195
- const path4 = `${prefix}/named-mutations/${encodeURIComponent(hash)}/execute`;
5455
+ const path4 = `${prefix}/named-mutations/${encodeURIComponent(name)}/execute`;
5196
5456
  const wire = await this.transport.post(path4, {
5197
5457
  args: args ?? {}
5198
5458
  });
@@ -5253,6 +5513,7 @@ var WebSocketTransport = class {
5253
5513
  // Serialized-send queue: each send appends to this tail.
5254
5514
  this._sendTail = Promise.resolve();
5255
5515
  this._lastVersion = 0;
5516
+ this._lastToken = null;
5256
5517
  this._reconnectAttempt = 0;
5257
5518
  this._disposed = false;
5258
5519
  this._missedPings = 0;
@@ -5270,6 +5531,7 @@ var WebSocketTransport = class {
5270
5531
  this._pingIntervalMs = options.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;
5271
5532
  this._maxMissedHeartbeats = options.maxMissedHeartbeats ?? DEFAULT_MAX_MISSED_HEARTBEATS;
5272
5533
  this._enableCompression = options.enableCompression ?? true;
5534
+ this._store = options.consistencyTokenStore;
5273
5535
  this._wireMode = options.wireMode ?? "json";
5274
5536
  this._authHandler?.setOnAccessTokenRefreshed((token) => {
5275
5537
  if (this._handshakeComplete && this._ws?.readyState === WS_READY_STATE_OPEN) {
@@ -5278,10 +5540,14 @@ var WebSocketTransport = class {
5278
5540
  });
5279
5541
  }
5280
5542
  // ─── Public API ────────────────────────────────────────────────────────────
5281
- /** Latest version number from the most recent server `heartbeat` message. */
5543
+ /** Latest change-event sequence from the most recent server `heartbeat` message. */
5282
5544
  get lastVersion() {
5283
5545
  return this._lastVersion;
5284
5546
  }
5547
+ /** Consistency token from the most recent server `heartbeat` (ADR 0042 D-13). */
5548
+ get lastToken() {
5549
+ return this._lastToken;
5550
+ }
5285
5551
  /**
5286
5552
  * Registers a handler for incoming server messages with the given channel id.
5287
5553
  * Use `"__global__"` to receive broadcast messages that carry no `id`.
@@ -5469,6 +5735,10 @@ var WebSocketTransport = class {
5469
5735
  }
5470
5736
  case "heartbeat":
5471
5737
  this._lastVersion = msg.version;
5738
+ this._lastToken = msg.token ?? null;
5739
+ if (this._lastToken != null && this._lastToken.length > 0) {
5740
+ this._store?.observe(this._database, this._lastToken);
5741
+ }
5472
5742
  break;
5473
5743
  case "pong":
5474
5744
  this._missedPings = 0;
@@ -5634,16 +5904,21 @@ var LongPollTransport = class {
5634
5904
  this._connected = false;
5635
5905
  this._pollTask = null;
5636
5906
  this._lastVersion = 0;
5907
+ this._lastToken = null;
5637
5908
  this._serverUrl = serverUrl;
5638
5909
  this._database = database;
5639
5910
  this._authHandler = options.authHandler;
5640
5911
  this._onReconnected = options.onReconnected;
5641
5912
  this._waitMs = Math.max(1, options.waitMs ?? DEFAULT_WAIT_MS);
5642
5913
  this._fetch = options.fetchImpl ?? localNetworkFetch;
5914
+ this._store = options.consistencyTokenStore;
5643
5915
  }
5644
5916
  get lastVersion() {
5645
5917
  return this._lastVersion;
5646
5918
  }
5919
+ get lastToken() {
5920
+ return this._lastToken;
5921
+ }
5647
5922
  registerHandler(id, handler) {
5648
5923
  this._handlers.set(id, handler);
5649
5924
  }
@@ -5766,6 +6041,10 @@ var LongPollTransport = class {
5766
6041
  for (const msg of payload.messages) {
5767
6042
  if (msg.type === "heartbeat") {
5768
6043
  this._lastVersion = msg.version;
6044
+ this._lastToken = msg.token ?? null;
6045
+ if (this._lastToken != null && this._lastToken.length > 0) {
6046
+ this._store?.observe(this._database, this._lastToken);
6047
+ }
5769
6048
  }
5770
6049
  const id = "id" in msg ? msg.id : void 0;
5771
6050
  if (id !== void 0 && id !== null) {
@@ -5845,6 +6124,9 @@ var FallbackStreamingTransport = class {
5845
6124
  get lastVersion() {
5846
6125
  return this._active.lastVersion;
5847
6126
  }
6127
+ get lastToken() {
6128
+ return this._active.lastToken;
6129
+ }
5848
6130
  registerHandler(id, handler) {
5849
6131
  this._handlers.set(id, handler);
5850
6132
  this._active.registerHandler(id, handler);
@@ -5924,6 +6206,7 @@ var AoudaClient = class {
5924
6206
  this.baseUrl = normalizeBaseUrl(options.serverUrl);
5925
6207
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
5926
6208
  this.database = options.database.trim();
6209
+ this._store = options.consistencyTokenStore ?? new MemoryConsistencyTokenStore();
5927
6210
  if (!Number.isFinite(this.timeout) || this.timeout <= 0) {
5928
6211
  throw new Error("timeout must be a finite positive number");
5929
6212
  }
@@ -5958,7 +6241,9 @@ var AoudaClient = class {
5958
6241
  }
5959
6242
  const httpTransport = new HttpTransport({
5960
6243
  baseUrl: this.baseUrl,
5961
- timeout: this.timeout
6244
+ timeout: this.timeout,
6245
+ database: this.database,
6246
+ consistencyTokenStore: this._store
5962
6247
  });
5963
6248
  const activeAuth = options.serverAuth ?? options.appAuth;
5964
6249
  if (activeAuth) {
@@ -6018,7 +6303,8 @@ var AoudaClient = class {
6018
6303
  this.transport,
6019
6304
  this.database,
6020
6305
  onNamedArtifactWarning,
6021
- () => this._getOrCreateWebSocketTransport()
6306
+ () => this._getOrCreateWebSocketTransport(),
6307
+ this._store
6022
6308
  );
6023
6309
  this._namedMutations = new NamedMutationsApi(
6024
6310
  this.transport,
@@ -6117,7 +6403,8 @@ var AoudaClient = class {
6117
6403
  name,
6118
6404
  this.database,
6119
6405
  void 0,
6120
- () => this._getOrCreateWebSocketTransport()
6406
+ () => this._getOrCreateWebSocketTransport(),
6407
+ this._store
6121
6408
  );
6122
6409
  }
6123
6410
  /**
@@ -6169,13 +6456,26 @@ var AoudaClient = class {
6169
6456
  return this._materializedQueries;
6170
6457
  }
6171
6458
  /**
6172
- * Hash-only named-query execute and batch. Names are codegen aliases (D-5).
6459
+ * Named-query execute, read-only batch, and subscribe by unique schema name.
6173
6460
  */
6174
6461
  get namedQueries() {
6175
6462
  return this._namedQueries;
6176
6463
  }
6464
+ observeConsistencyToken(token) {
6465
+ this._store.observe(this.database, token);
6466
+ }
6467
+ getObservedConsistencyToken() {
6468
+ return this._store.get(this.database);
6469
+ }
6470
+ async getConsistencyToken() {
6471
+ const body = await this.transport.get(
6472
+ `/api/databases/${encodeURIComponent(this.database)}/token`
6473
+ );
6474
+ this._store.observe(this.database, body.token);
6475
+ return this._store.get(this.database) ?? body.token;
6476
+ }
6177
6477
  /**
6178
- * Hash-only named-mutation execute. No batch.
6478
+ * Named-mutation execute by unique schema name. No batch.
6179
6479
  */
6180
6480
  get namedMutations() {
6181
6481
  return this._namedMutations;
@@ -6302,13 +6602,15 @@ var AoudaClient = class {
6302
6602
  const primary = new WebSocketTransport(this.baseUrl, this.database, {
6303
6603
  authHandler: this._authHandler,
6304
6604
  enableCompression: this._streamingEnableCompression,
6305
- wireMode: this._streamingWireMode
6605
+ wireMode: this._streamingWireMode,
6606
+ consistencyTokenStore: this._store
6306
6607
  });
6307
6608
  if (this._streamingEnableLongPollFallback) {
6308
6609
  this._wsTransport = new FallbackStreamingTransport(primary, () => {
6309
6610
  return new LongPollTransport(this.baseUrl, this.database, {
6310
6611
  authHandler: this._authHandler,
6311
- waitMs: this._streamingLongPollWaitMs
6612
+ waitMs: this._streamingLongPollWaitMs,
6613
+ consistencyTokenStore: this._store
6312
6614
  });
6313
6615
  });
6314
6616
  } else {
@@ -6333,7 +6635,7 @@ Usage:
6333
6635
  npx @aouda/client schema <command> [options]
6334
6636
 
6335
6637
  Commands:
6336
- generate Fetch schema from Aouda server and output TypeScript types (tables + named query/mutation hashes)
6638
+ generate Fetch schema from Aouda server and output TypeScript types (tables + optional named query/mutation Args/Row)
6337
6639
  schema Schema management (diff, apply, export, validate, history, seed)
6338
6640
  diff Show migration plan (desired vs current)
6339
6641
  apply Apply schema (use --allow-destructive for drops; --dry-run to preview)