@aouda/client 0.1.14 → 0.1.15

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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "@aouda/client",
4
- version: "0.1.14",
4
+ version: "0.1.15",
5
5
  description: "Official TypeScript/JavaScript client library for Aouda",
6
6
  type: "module",
7
7
  main: "./dist/index.cjs",
@@ -133,43 +133,44 @@ var AoudaResponseError = class extends AoudaError {
133
133
  }
134
134
  };
135
135
  var AoudaApiError = class extends AoudaError {
136
- constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
136
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token) {
137
137
  super(message);
138
138
  this.code = code;
139
139
  this.statusCode = statusCode;
140
140
  this.details = details;
141
141
  this.requestId = requestId;
142
142
  this.retryAfterSeconds = retryAfterSeconds;
143
+ this.token = token;
143
144
  this.name = "AoudaApiError";
144
145
  }
145
146
  };
146
147
  var AoudaNotFoundError = class extends AoudaApiError {
147
- constructor(message, code, statusCode, details, requestId) {
148
- super(message, code, statusCode, details, requestId);
148
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token) {
149
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token);
149
150
  this.name = "AoudaNotFoundError";
150
151
  }
151
152
  };
152
153
  var AoudaConflictError = class extends AoudaApiError {
153
- constructor(message, code, statusCode, details, requestId) {
154
- super(message, code, statusCode, details, requestId);
154
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token) {
155
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token);
155
156
  this.name = "AoudaConflictError";
156
157
  }
157
158
  };
158
159
  var AoudaValidationError = class extends AoudaApiError {
159
- constructor(message, code, statusCode, details, requestId) {
160
- super(message, code, statusCode, details, requestId);
160
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token) {
161
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token);
161
162
  this.name = "AoudaValidationError";
162
163
  }
163
164
  };
164
165
  var AoudaServerError = class extends AoudaApiError {
165
- constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
166
- super(message, code, statusCode, details, requestId, retryAfterSeconds);
166
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token) {
167
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token);
167
168
  this.name = "AoudaServerError";
168
169
  }
169
170
  };
170
171
  var AoudaAuthenticationError = class extends AoudaApiError {
171
- constructor(message, code, statusCode, details, requestId) {
172
- super(message, code, statusCode, details, requestId);
172
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds, token) {
173
+ super(message, code, statusCode, details, requestId, retryAfterSeconds, token);
173
174
  this.name = "AoudaAuthenticationError";
174
175
  }
175
176
  };
@@ -981,7 +982,7 @@ function buildHandle(jobId, commitResp, wasResumed, transport, database, signal)
981
982
  rowsDurablyCommitted: commitResp.rowsLoaded,
982
983
  segmentsCreated: commitResp.segmentsCreated,
983
984
  committedAtUtc: commitResp.committedAtUtc,
984
- walPosition: commitResp.walPosition,
985
+ token: commitResp.token,
985
986
  writeConcernSatisfied: commitResp.writeConcernAchieved ?? "acknowledged",
986
987
  writeConcernTimedOut: commitResp.writeConcernTimedOut,
987
988
  wasResumed,
@@ -1107,6 +1108,7 @@ var PROTOCOL_VERSION_HEADER = "X-Aouda-Protocol-Version";
1107
1108
  var PROTOCOL_VERSION = "1";
1108
1109
  var CONTENT_TYPE_JSON2 = "application/json";
1109
1110
  var REQUEST_ID_HEADER2 = "X-Request-Id";
1111
+ var TOKEN_HEADER = "X-Aouda-Token";
1110
1112
  var ERROR_CODE_MAP = {
1111
1113
  TABLE_NOT_FOUND: AoudaNotFoundError,
1112
1114
  COLUMN_NOT_FOUND: AoudaNotFoundError,
@@ -1146,7 +1148,12 @@ var ERROR_CODE_MAP = {
1146
1148
  AUTH_IDENTITY_INVALID: AoudaValidationError,
1147
1149
  AUTH_IDENTITY_NOT_FOUND: AoudaValidationError,
1148
1150
  BULK_LOAD_TRANSFORM_INTENT_REQUIRED: AoudaValidationError,
1149
- BULK_LOAD_TRANSFORM_INTENT_CONFLICT: AoudaValidationError
1151
+ BULK_LOAD_TRANSFORM_INTENT_CONFLICT: AoudaValidationError,
1152
+ TOKEN_MALFORMED: AoudaValidationError,
1153
+ TOKEN_FOREIGN_DATABASE: AoudaValidationError,
1154
+ TOKEN_EPOCH_SUPERSEDED: AoudaConflictError,
1155
+ TOKEN_UNSATISFIED: AoudaConflictError,
1156
+ TOKEN_FETCH_PRIMARY: AoudaApiError
1150
1157
  };
1151
1158
  function createComposedAbortController(...signals) {
1152
1159
  const controller = new AbortController();
@@ -1183,7 +1190,7 @@ function createApiError(statusCode, statusText, body, retryAfterHeader) {
1183
1190
  const requestId = body.requestId;
1184
1191
  const retryAfterSeconds = parseRetryAfterSeconds(retryAfterHeader ?? null);
1185
1192
  const Ctor = ERROR_CODE_MAP[code] ?? AoudaApiError;
1186
- return new Ctor(message, code, statusCode, details, requestId, retryAfterSeconds);
1193
+ return new Ctor(message, code, statusCode, details, requestId, retryAfterSeconds, body.token);
1187
1194
  }
1188
1195
  var HttpTransport = class {
1189
1196
  constructor(options) {
@@ -1195,6 +1202,29 @@ var HttpTransport = class {
1195
1202
  ...options.defaultHeaders
1196
1203
  };
1197
1204
  this.abortController = new AbortController();
1205
+ this.database = options.database;
1206
+ this.store = options.consistencyTokenStore;
1207
+ }
1208
+ presentTokenHeaders(path, headers) {
1209
+ if (this.store == null || this.database == null || isAuthPath(path)) {
1210
+ return;
1211
+ }
1212
+ const token = this.store.get(this.database);
1213
+ if (token != null && token.length > 0) {
1214
+ headers[TOKEN_HEADER] = token;
1215
+ }
1216
+ }
1217
+ observeResponse(_path, response, bodyText, errorCode) {
1218
+ if (this.store == null || this.database == null) {
1219
+ return;
1220
+ }
1221
+ if (errorCode === "TOKEN_MALFORMED" || errorCode === "TOKEN_FOREIGN_DATABASE") {
1222
+ return;
1223
+ }
1224
+ const header = response.headers.get(TOKEN_HEADER);
1225
+ const bodyToken = readBodyToken(bodyText);
1226
+ const token = header != null && header.trim().length > 0 ? header : bodyToken;
1227
+ this.store.observe(this.database, token);
1198
1228
  }
1199
1229
  /**
1200
1230
  * Abort all in-flight and future requests (e.g. on client disconnect).
@@ -1232,6 +1262,7 @@ var HttpTransport = class {
1232
1262
  ...this.defaultHeaders,
1233
1263
  ...config.headers
1234
1264
  };
1265
+ this.presentTokenHeaders(config.path, headers);
1235
1266
  if (config.body !== void 0 && config.body !== null) {
1236
1267
  headers["Content-Type"] = CONTENT_TYPE_JSON2;
1237
1268
  }
@@ -1251,6 +1282,7 @@ var HttpTransport = class {
1251
1282
  const text = await response.text();
1252
1283
  if (config.allowStatuses?.includes(response.status) && text) {
1253
1284
  try {
1285
+ this.observeResponse(config.path, response, text);
1254
1286
  return JSON.parse(text);
1255
1287
  } catch {
1256
1288
  }
@@ -1264,15 +1296,19 @@ var HttpTransport = class {
1264
1296
  }
1265
1297
  if (response.status === 401) {
1266
1298
  const code = errorBody?.code ?? "AUTH_TOKEN_MISSING";
1299
+ this.observeResponse(config.path, response, text, code);
1267
1300
  throw new AoudaAuthenticationError(
1268
1301
  errorBody?.error ?? "Unauthorized",
1269
1302
  code,
1270
1303
  401,
1271
1304
  errorBody?.details,
1272
- errorBody?.requestId
1305
+ errorBody?.requestId,
1306
+ void 0,
1307
+ errorBody?.token
1273
1308
  );
1274
1309
  }
1275
1310
  if (errorBody?.code != null) {
1311
+ this.observeResponse(config.path, response, text, errorBody.code);
1276
1312
  throw createApiError(
1277
1313
  response.status,
1278
1314
  response.statusText,
@@ -1280,6 +1316,7 @@ var HttpTransport = class {
1280
1316
  response.headers.get("Retry-After")
1281
1317
  );
1282
1318
  }
1319
+ this.observeResponse(config.path, response, text);
1283
1320
  throw new AoudaResponseError(
1284
1321
  errorBody?.error ?? `${response.status} ${response.statusText}`,
1285
1322
  response.status,
@@ -1287,6 +1324,7 @@ var HttpTransport = class {
1287
1324
  );
1288
1325
  }
1289
1326
  const responseText = await response.text();
1327
+ this.observeResponse(config.path, response, responseText);
1290
1328
  if (config.rawText) {
1291
1329
  return responseText;
1292
1330
  }
@@ -1347,6 +1385,7 @@ var HttpTransport = class {
1347
1385
  ...this.defaultHeaders,
1348
1386
  ...config.headers
1349
1387
  };
1388
+ this.presentTokenHeaders(config.path, headers);
1350
1389
  if (config.rawBodyStr === void 0 && config.body !== void 0 && config.body !== null) {
1351
1390
  headers["Content-Type"] = CONTENT_TYPE_JSON2;
1352
1391
  }
@@ -1363,6 +1402,7 @@ var HttpTransport = class {
1363
1402
  try {
1364
1403
  const response = await localNetworkFetch(url, init);
1365
1404
  if (response.ok || config.allowStatuses?.includes(response.status)) {
1405
+ this.observeResponse(config.path, response, void 0);
1366
1406
  return response;
1367
1407
  }
1368
1408
  const text = await response.text();
@@ -1375,15 +1415,19 @@ var HttpTransport = class {
1375
1415
  }
1376
1416
  if (response.status === 401) {
1377
1417
  const code = errorBody?.code ?? "AUTH_TOKEN_MISSING";
1418
+ this.observeResponse(config.path, response, text, code);
1378
1419
  throw new AoudaAuthenticationError(
1379
1420
  errorBody?.error ?? "Unauthorized",
1380
1421
  code,
1381
1422
  401,
1382
1423
  errorBody?.details,
1383
- errorBody?.requestId
1424
+ errorBody?.requestId,
1425
+ void 0,
1426
+ errorBody?.token
1384
1427
  );
1385
1428
  }
1386
1429
  if (errorBody?.code != null) {
1430
+ this.observeResponse(config.path, response, text, errorBody.code);
1387
1431
  throw createApiError(
1388
1432
  response.status,
1389
1433
  response.statusText,
@@ -1391,6 +1435,7 @@ var HttpTransport = class {
1391
1435
  response.headers.get("Retry-After")
1392
1436
  );
1393
1437
  }
1438
+ this.observeResponse(config.path, response, text);
1394
1439
  throw new AoudaResponseError(
1395
1440
  errorBody?.error ?? `${response.status} ${response.statusText}`,
1396
1441
  response.status,
@@ -1545,6 +1590,47 @@ var HttpTransport = class {
1545
1590
  });
1546
1591
  }
1547
1592
  };
1593
+ function isAuthPath(path) {
1594
+ let p = path;
1595
+ const q = p.indexOf("?");
1596
+ if (q >= 0) {
1597
+ p = p.slice(0, q);
1598
+ }
1599
+ if (/^https?:\/\//i.test(p)) {
1600
+ try {
1601
+ p = new URL(p).pathname;
1602
+ } catch {
1603
+ }
1604
+ }
1605
+ const lower = p.toLowerCase();
1606
+ if (lower === "/api/auth" || lower.startsWith("/api/auth/")) {
1607
+ return true;
1608
+ }
1609
+ const prefix = "/api/databases/";
1610
+ if (!lower.startsWith(prefix)) {
1611
+ return false;
1612
+ }
1613
+ const rest = lower.slice(prefix.length);
1614
+ const slash = rest.indexOf("/");
1615
+ if (slash < 0) {
1616
+ return false;
1617
+ }
1618
+ const afterDb = rest.slice(slash + 1);
1619
+ return afterDb === "auth" || afterDb.startsWith("auth/");
1620
+ }
1621
+ function readBodyToken(bodyText) {
1622
+ if (bodyText == null || bodyText.trim().length === 0) {
1623
+ return void 0;
1624
+ }
1625
+ try {
1626
+ const parsed = JSON.parse(bodyText);
1627
+ if (parsed !== null && typeof parsed === "object" && "token" in parsed && typeof parsed.token === "string") {
1628
+ return parsed.token;
1629
+ }
1630
+ } catch {
1631
+ }
1632
+ return void 0;
1633
+ }
1548
1634
 
1549
1635
  // src/resilience/retry.ts
1550
1636
  var DEFAULT_MAX_RETRIES = 3;
@@ -1977,6 +2063,48 @@ function databasePath2(db) {
1977
2063
  return `/api/databases/${encodeURIComponent(db)}`;
1978
2064
  }
1979
2065
 
2066
+ // src/consistency-token-store.ts
2067
+ var MemoryConsistencyTokenStore = class {
2068
+ constructor() {
2069
+ this.tokens = /* @__PURE__ */ new Map();
2070
+ }
2071
+ get(database) {
2072
+ if (typeof database !== "string" || database.trim().length === 0) {
2073
+ throw new Error("database must be a non-empty string");
2074
+ }
2075
+ return this.tokens.get(normalizeKey(database));
2076
+ }
2077
+ observe(database, token) {
2078
+ if (typeof database !== "string" || database.trim().length === 0) {
2079
+ throw new Error("database must be a non-empty string");
2080
+ }
2081
+ if (token == null || token.trim().length === 0) {
2082
+ return;
2083
+ }
2084
+ const key = normalizeKey(database);
2085
+ const stored = this.tokens.get(key);
2086
+ if (stored === void 0 || compareOrdinal(token, stored) > 0) {
2087
+ this.tokens.set(key, token);
2088
+ }
2089
+ }
2090
+ };
2091
+ function compareOrdinal(left, right) {
2092
+ if (left === right) return 0;
2093
+ return left < right ? -1 : 1;
2094
+ }
2095
+ function maxToken(left, right) {
2096
+ if (left == null || left.length === 0) {
2097
+ return right == null || right.length === 0 ? void 0 : right;
2098
+ }
2099
+ if (right == null || right.length === 0) {
2100
+ return left;
2101
+ }
2102
+ return compareOrdinal(left, right) >= 0 ? left : right;
2103
+ }
2104
+ function normalizeKey(database) {
2105
+ return database.toLowerCase();
2106
+ }
2107
+
1980
2108
  // src/streaming/subscription.ts
1981
2109
  var AsyncEventQueue = class {
1982
2110
  constructor() {
@@ -2033,7 +2161,7 @@ var AsyncEventQueue = class {
2033
2161
  }
2034
2162
  };
2035
2163
  var TableSubscription = class {
2036
- constructor(transport, identity, options = {}, onWarnings) {
2164
+ constructor(transport, identity, options = {}, onWarnings, store, database) {
2037
2165
  this._queue = new AsyncEventQueue();
2038
2166
  this._active = true;
2039
2167
  this._started = false;
@@ -2049,6 +2177,11 @@ var TableSubscription = class {
2049
2177
  this._onError = options.onError;
2050
2178
  this._onWarnings = onWarnings;
2051
2179
  this._conflate = options.conflate;
2180
+ this._atLeast = options.atLeast;
2181
+ this._waitMs = options.waitMs;
2182
+ this._onExceeded = options.onExceeded;
2183
+ this._store = store;
2184
+ this._database = database;
2052
2185
  this._reconnectHandlerKey = `${this.id}::reconnect`;
2053
2186
  }
2054
2187
  get lastVersion() {
@@ -2113,7 +2246,7 @@ var TableSubscription = class {
2113
2246
  id: this.id
2114
2247
  };
2115
2248
  if (this._identity.kind === "named") {
2116
- message.hash = this._identity.hash;
2249
+ message.name = this._identity.name;
2117
2250
  if (this._identity.args !== void 0) {
2118
2251
  message.args = this._identity.args;
2119
2252
  }
@@ -2132,8 +2265,30 @@ var TableSubscription = class {
2132
2265
  if (this._conflate !== void 0) {
2133
2266
  message.conflate = this._conflate;
2134
2267
  }
2268
+ const pin = this._resolvePin();
2269
+ if (pin !== void 0) {
2270
+ message.at_least = pin;
2271
+ }
2272
+ if (this._waitMs !== void 0) {
2273
+ message.wait_ms = this._waitMs;
2274
+ }
2275
+ if (this._onExceeded !== void 0) {
2276
+ message.on_exceeded = this._onExceeded;
2277
+ }
2135
2278
  await this._transport.send(message);
2136
2279
  }
2280
+ _resolvePin() {
2281
+ if (this._atLeast !== void 0 && this._store != null && this._database != null) {
2282
+ this._store.observe(this._database, this._atLeast);
2283
+ }
2284
+ const stored = this._store != null && this._database != null ? this._store.get(this._database) : void 0;
2285
+ return maxToken(this._atLeast, stored);
2286
+ }
2287
+ _observeToken(token) {
2288
+ if (token !== void 0 && this._store != null && this._database != null) {
2289
+ this._store.observe(this._database, token);
2290
+ }
2291
+ }
2137
2292
  _handleMessage(message) {
2138
2293
  if (!this._active) {
2139
2294
  return;
@@ -2160,6 +2315,7 @@ var TableSubscription = class {
2160
2315
  }
2161
2316
  _handleSnapshotPage(message) {
2162
2317
  this._pendingSnapshotRows.push(...message.rows);
2318
+ this._observeToken(message.token);
2163
2319
  }
2164
2320
  _handleSnapshotComplete(message) {
2165
2321
  this._lastVersion = message.version;
@@ -2177,6 +2333,10 @@ var TableSubscription = class {
2177
2333
  if (message.total_matches !== void 0) {
2178
2334
  snapshot.totalMatches = message.total_matches;
2179
2335
  }
2336
+ if (message.token !== void 0) {
2337
+ snapshot.token = message.token;
2338
+ this._observeToken(message.token);
2339
+ }
2180
2340
  this._queue.push(snapshot);
2181
2341
  }
2182
2342
  async _handleGap(message) {
@@ -2202,6 +2362,10 @@ var TableSubscription = class {
2202
2362
  if (message.values_skipped !== void 0) {
2203
2363
  event.values_skipped = message.values_skipped;
2204
2364
  }
2365
+ if (message.token !== void 0) {
2366
+ event.token = message.token;
2367
+ this._observeToken(message.token);
2368
+ }
2205
2369
  this._onChange?.(event);
2206
2370
  this._queue.push(event);
2207
2371
  }
@@ -2546,11 +2710,12 @@ var TableQuery = class _TableQuery {
2546
2710
  * @param state - Optional initial state (used for immutable chaining).
2547
2711
  * @internal Use `client.table()` to create queries.
2548
2712
  */
2549
- constructor(transport, tableName, database, state, getWebSocketTransport) {
2713
+ constructor(transport, tableName, database, state, getWebSocketTransport, store) {
2550
2714
  this.transport = transport;
2551
2715
  this.tableName = tableName;
2552
2716
  this.database = database;
2553
2717
  this.getWebSocketTransport = getWebSocketTransport;
2718
+ this.store = store;
2554
2719
  this.state = state ?? {
2555
2720
  predicates: [],
2556
2721
  groupClauses: [],
@@ -2566,6 +2731,16 @@ var TableQuery = class _TableQuery {
2566
2731
  isDistinct: false
2567
2732
  };
2568
2733
  }
2734
+ withState(state) {
2735
+ return new _TableQuery(
2736
+ this.transport,
2737
+ this.tableName,
2738
+ this.database,
2739
+ state,
2740
+ this.getWebSocketTransport,
2741
+ this.store
2742
+ );
2743
+ }
2569
2744
  where(column, operator, value) {
2570
2745
  const newPredicates = buildWherePredicates(column, operator, value);
2571
2746
  return new _TableQuery(
@@ -2576,7 +2751,8 @@ var TableQuery = class _TableQuery {
2576
2751
  ...this.state,
2577
2752
  predicates: [...this.state.predicates, ...newPredicates]
2578
2753
  },
2579
- this.getWebSocketTransport
2754
+ this.getWebSocketTransport,
2755
+ this.store
2580
2756
  );
2581
2757
  }
2582
2758
  /**
@@ -2595,7 +2771,7 @@ var TableQuery = class _TableQuery {
2595
2771
  return new _TableQuery(this.transport, this.tableName, this.database, {
2596
2772
  ...this.state,
2597
2773
  groupClauses: [...this.state.groupClauses, sub]
2598
- }, this.getWebSocketTransport);
2774
+ }, this.getWebSocketTransport, this.store);
2599
2775
  }
2600
2776
  /**
2601
2777
  * Sets the primary sort column for the query.
@@ -2620,7 +2796,7 @@ var TableQuery = class _TableQuery {
2620
2796
  return new _TableQuery(this.transport, this.tableName, this.database, {
2621
2797
  ...this.state,
2622
2798
  orderByClauses: [orderByClause]
2623
- }, this.getWebSocketTransport);
2799
+ }, this.getWebSocketTransport, this.store);
2624
2800
  }
2625
2801
  /**
2626
2802
  * Sets the primary sort column to descending order.
@@ -2666,7 +2842,7 @@ var TableQuery = class _TableQuery {
2666
2842
  return new _TableQuery(this.transport, this.tableName, this.database, {
2667
2843
  ...this.state,
2668
2844
  orderByClauses: [...this.state.orderByClauses, orderByClause]
2669
- }, this.getWebSocketTransport);
2845
+ }, this.getWebSocketTransport, this.store);
2670
2846
  }
2671
2847
  /**
2672
2848
  * Sets the maximum number of rows to return.
@@ -2683,7 +2859,7 @@ var TableQuery = class _TableQuery {
2683
2859
  return new _TableQuery(this.transport, this.tableName, this.database, {
2684
2860
  ...this.state,
2685
2861
  limitValue: count
2686
- }, this.getWebSocketTransport);
2862
+ }, this.getWebSocketTransport, this.store);
2687
2863
  }
2688
2864
  /**
2689
2865
  * Sets the number of rows to skip.
@@ -2700,7 +2876,7 @@ var TableQuery = class _TableQuery {
2700
2876
  return new _TableQuery(this.transport, this.tableName, this.database, {
2701
2877
  ...this.state,
2702
2878
  offsetValue: count
2703
- }, this.getWebSocketTransport);
2879
+ }, this.getWebSocketTransport, this.store);
2704
2880
  }
2705
2881
  /**
2706
2882
  * Requests cross-partition access for this query.
@@ -2715,10 +2891,21 @@ var TableQuery = class _TableQuery {
2715
2891
  return new _TableQuery(this.transport, this.tableName, this.database, {
2716
2892
  ...this.state,
2717
2893
  crossPartitionAccess: true
2718
- }, this.getWebSocketTransport);
2894
+ }, this.getWebSocketTransport, this.store);
2719
2895
  }
2720
2896
  /**
2721
- * Restricts the columns returned in the result.
2897
+ * Pin this query at at least this C-1 token. Observes the token into the
2898
+ * client store (I3, sticky) and presents it on execute via `X-Aouda-Token`.
2899
+ */
2900
+ atLeast(token) {
2901
+ if (typeof token !== "string" || token.trim().length === 0) {
2902
+ throw new Error("atLeast() requires a non-empty token");
2903
+ }
2904
+ this.store?.observe(this.database, token);
2905
+ return this.withState({ ...this.state, atLeast: token });
2906
+ }
2907
+ /**
2908
+ * Selects specific columns to return.
2722
2909
  * If not called, all columns are returned.
2723
2910
  *
2724
2911
  * When T is a specific row type, only keys of T are accepted as column names.
@@ -2740,7 +2927,7 @@ var TableQuery = class _TableQuery {
2740
2927
  return new _TableQuery(this.transport, this.tableName, this.database, {
2741
2928
  ...this.state,
2742
2929
  selectColumns: columns.length > 0 ? columns : null
2743
- }, this.getWebSocketTransport);
2930
+ }, this.getWebSocketTransport, this.store);
2744
2931
  }
2745
2932
  /**
2746
2933
  * Return only distinct (de-duplicated) rows for the given columns — SQL `SELECT DISTINCT`.
@@ -2770,7 +2957,7 @@ var TableQuery = class _TableQuery {
2770
2957
  ...this.state,
2771
2958
  selectColumns: columns,
2772
2959
  isDistinct: true
2773
- }, this.getWebSocketTransport);
2960
+ }, this.getWebSocketTransport, this.store);
2774
2961
  }
2775
2962
  /**
2776
2963
  * Adds server-side computed columns to the query result.
@@ -2799,7 +2986,7 @@ var TableQuery = class _TableQuery {
2799
2986
  return new _TableQuery(this.transport, this.tableName, this.database, {
2800
2987
  ...this.state,
2801
2988
  selectExprs: projections
2802
- }, this.getWebSocketTransport);
2989
+ }, this.getWebSocketTransport, this.store);
2803
2990
  }
2804
2991
  join(rightTable, leftColumnOrColumns, rightColumnOrColumns) {
2805
2992
  return this.addJoinClause(
@@ -2857,7 +3044,8 @@ var TableQuery = class _TableQuery {
2857
3044
  }
2858
3045
  ]
2859
3046
  },
2860
- this.getWebSocketTransport
3047
+ this.getWebSocketTransport,
3048
+ this.store
2861
3049
  );
2862
3050
  }
2863
3051
  /**
@@ -2893,7 +3081,8 @@ var TableQuery = class _TableQuery {
2893
3081
  ...this.state,
2894
3082
  groupByColumns: [...columns]
2895
3083
  },
2896
- this.getWebSocketTransport
3084
+ this.getWebSocketTransport,
3085
+ this.store
2897
3086
  );
2898
3087
  }
2899
3088
  /**
@@ -2927,8 +3116,11 @@ var TableQuery = class _TableQuery {
2927
3116
  onSnapshot: options.onSnapshot,
2928
3117
  onChange: options.onChange,
2929
3118
  onError: options.onError,
2930
- conflate: options.conflate
2931
- });
3119
+ conflate: options.conflate,
3120
+ atLeast: options.atLeast ?? this.state.atLeast,
3121
+ waitMs: options.waitMs,
3122
+ onExceeded: options.onExceeded
3123
+ }, void 0, this.store, this.database);
2932
3124
  subscription.start();
2933
3125
  return subscription;
2934
3126
  }
@@ -3043,7 +3235,8 @@ var TableQuery = class _TableQuery {
3043
3235
  ...this.state,
3044
3236
  joinClauses: [...this.state.joinClauses, joinClause]
3045
3237
  },
3046
- this.getWebSocketTransport
3238
+ this.getWebSocketTransport,
3239
+ this.store
3047
3240
  );
3048
3241
  }
3049
3242
  addAggregate(op, column) {
@@ -3064,7 +3257,8 @@ var TableQuery = class _TableQuery {
3064
3257
  }
3065
3258
  ]
3066
3259
  },
3067
- this.getWebSocketTransport
3260
+ this.getWebSocketTransport,
3261
+ this.store
3068
3262
  );
3069
3263
  }
3070
3264
  requireWebSocketTransport() {
@@ -3094,17 +3288,23 @@ var TableQuery = class _TableQuery {
3094
3288
  * ```
3095
3289
  */
3096
3290
  async execute() {
3291
+ if (this.state.atLeast) {
3292
+ this.store?.observe(this.database, this.state.atLeast);
3293
+ }
3097
3294
  const request = this.buildRequest();
3098
3295
  const path = `${databasePath2(this.database)}/query`;
3099
3296
  const response = await this.transport.post(path, request);
3100
3297
  const rows = columnarToRows(response);
3101
3298
  const stats = response.stats;
3102
- return { rows, stats };
3299
+ return { rows, stats, token: response.token };
3103
3300
  }
3104
3301
  /**
3105
3302
  * Executes the query and returns the raw columnar JSON payload (no row-object conversion).
3106
3303
  */
3107
3304
  async toColumnar() {
3305
+ if (this.state.atLeast) {
3306
+ this.store?.observe(this.database, this.state.atLeast);
3307
+ }
3108
3308
  const request = this.buildRequest();
3109
3309
  const path = `${databasePath2(this.database)}/query`;
3110
3310
  return this.transport.post(path, request);
@@ -3134,8 +3334,12 @@ var TableQuery = class _TableQuery {
3134
3334
  limitValue: 0,
3135
3335
  selectColumns: []
3136
3336
  },
3137
- this.getWebSocketTransport
3337
+ this.getWebSocketTransport,
3338
+ this.store
3138
3339
  );
3340
+ if (this.state.atLeast) {
3341
+ this.store?.observe(this.database, this.state.atLeast);
3342
+ }
3139
3343
  const request = countQuery.buildRequest();
3140
3344
  const path = `${databasePath2(this.database)}/query`;
3141
3345
  const response = await this.transport.post(path, request);
@@ -3182,7 +3386,8 @@ var TableQuery = class _TableQuery {
3182
3386
  const response = await this.transport.post(path, body);
3183
3387
  const result = {
3184
3388
  rowsInserted: response.rowsInserted,
3185
- executionMs: response.executionMs
3389
+ executionMs: response.executionMs,
3390
+ token: response.token
3186
3391
  };
3187
3392
  if (response.generatedValues !== void 0) {
3188
3393
  result.generatedValues = response.generatedValues;
@@ -3232,7 +3437,8 @@ var TableQuery = class _TableQuery {
3232
3437
  const response = await this.transport.post(path, body);
3233
3438
  const result = {
3234
3439
  rowsInserted: response.rowsInserted,
3235
- executionMs: response.executionMs
3440
+ executionMs: response.executionMs,
3441
+ token: response.token
3236
3442
  };
3237
3443
  if (response.generatedValues !== void 0) {
3238
3444
  result.generatedValues = response.generatedValues;
@@ -3284,6 +3490,7 @@ var TableQuery = class _TableQuery {
3284
3490
  return {
3285
3491
  rowsAffected: response.rowsUpdated,
3286
3492
  executionMs: response.executionMs,
3493
+ token: response.token,
3287
3494
  ...response.rows ? { rows: columnarToRows(response.rows) } : {},
3288
3495
  ...response.rowsTruncated ? { rowsTruncated: true } : {}
3289
3496
  };
@@ -3334,6 +3541,7 @@ var TableQuery = class _TableQuery {
3334
3541
  rowsAffected: response.rowsDeleted,
3335
3542
  executionMs: response.executionMs,
3336
3543
  hasMore: response.hasMore,
3544
+ token: response.token,
3337
3545
  ...response.rows ? { rows: columnarToRows(response.rows) } : {},
3338
3546
  ...response.rowsTruncated ? { rowsTruncated: true } : {}
3339
3547
  };
@@ -3353,7 +3561,8 @@ var TableQuery = class _TableQuery {
3353
3561
  return {
3354
3562
  rowsAffected: response.rowsDeleted,
3355
3563
  executionMs: response.executionMs,
3356
- hasMore: false
3564
+ hasMore: false,
3565
+ token: response.token
3357
3566
  };
3358
3567
  }
3359
3568
  /**
@@ -3366,7 +3575,14 @@ var TableQuery = class _TableQuery {
3366
3575
  throw new Error("batch() requires a non-empty operations array");
3367
3576
  }
3368
3577
  const wireOperations = operations.map((op, index) => {
3369
- const base = new _TableQuery(this.transport, this.tableName, this.database);
3578
+ const base = new _TableQuery(
3579
+ this.transport,
3580
+ this.tableName,
3581
+ this.database,
3582
+ void 0,
3583
+ this.getWebSocketTransport,
3584
+ this.store
3585
+ );
3370
3586
  const scoped = op.where(base);
3371
3587
  const where = scoped.buildWhereClause();
3372
3588
  if (!where) {
@@ -4734,12 +4950,12 @@ function raiseDeprecationWarnings(sink, warnings) {
4734
4950
  continue;
4735
4951
  }
4736
4952
  const sunset = warning.sunsetAt != null ? ` sunsetAt=${warning.sunsetAt}` : "";
4737
- const hash = warning.hash != null && warning.hash.length > 0 ? ` hash=${warning.hash}` : "";
4953
+ const name = warning.name != null && warning.name.length > 0 ? ` name=${warning.name}` : "";
4738
4954
  sink({
4739
4955
  code: warning.code,
4740
- hash: warning.hash,
4956
+ name: warning.name,
4741
4957
  sunsetAt: warning.sunsetAt,
4742
- message: `${warning.code}:${hash}${sunset}`.trim()
4958
+ message: `${warning.code}:${name}${sunset}`.trim()
4743
4959
  });
4744
4960
  }
4745
4961
  }
@@ -4752,24 +4968,28 @@ function emptyStats() {
4752
4968
  };
4753
4969
  }
4754
4970
  var NamedQueriesApi = class {
4755
- constructor(transport, database, onWarning, getStreamingTransport) {
4971
+ constructor(transport, database, onWarning, getStreamingTransport, store) {
4756
4972
  this.transport = transport;
4757
4973
  this.database = database;
4758
4974
  this.onWarning = onWarning;
4759
4975
  this.getStreamingTransport = getStreamingTransport;
4976
+ this.store = store;
4760
4977
  }
4761
- async execute(hash, args, options) {
4762
- if (typeof hash !== "string" || hash.trim().length === 0) {
4763
- throw new Error("Named query hash must be a non-empty string");
4978
+ async execute(name, args, options) {
4979
+ if (typeof name !== "string" || name.trim().length === 0) {
4980
+ throw new Error("Named query name must be a non-empty string");
4764
4981
  }
4765
4982
  const prefix = databasePath2(this.database);
4766
- const path = `${prefix}/named-queries/${encodeURIComponent(hash)}/query?format=columnar`;
4983
+ const path = `${prefix}/named-queries/${encodeURIComponent(name)}/query?format=columnar`;
4767
4984
  const body = {
4768
4985
  args: args ?? {}
4769
4986
  };
4770
4987
  if (options?.orderByIndex !== void 0) {
4771
4988
  body.orderByIndex = options.orderByIndex;
4772
4989
  }
4990
+ if (options?.atLeast !== void 0) {
4991
+ this.store?.observe(this.database, options.atLeast);
4992
+ }
4773
4993
  const response = await this.transport.post(
4774
4994
  path,
4775
4995
  body,
@@ -4781,7 +5001,8 @@ var NamedQueriesApi = class {
4781
5001
  rows,
4782
5002
  stats: response.stats,
4783
5003
  warnings: response.warnings,
4784
- totalMatches: response.totalMatches
5004
+ totalMatches: response.totalMatches,
5005
+ token: response.token
4785
5006
  };
4786
5007
  }
4787
5008
  async batch(items, options) {
@@ -4799,6 +5020,9 @@ var NamedQueriesApi = class {
4799
5020
  400
4800
5021
  );
4801
5022
  }
5023
+ if (options?.atLeast !== void 0) {
5024
+ this.store?.observe(this.database, options.atLeast);
5025
+ }
4802
5026
  const prefix = databasePath2(this.database);
4803
5027
  const path = `${prefix}/named-queries/batch?format=columnar`;
4804
5028
  const envelope = await this.transport.post(
@@ -4821,34 +5045,41 @@ var NamedQueriesApi = class {
4821
5045
  rowCount: slot.rowCount ?? 0,
4822
5046
  stats: slot.stats ?? emptyStats(),
4823
5047
  warnings: slot.warnings,
4824
- totalMatches: slot.totalMatches
5048
+ totalMatches: slot.totalMatches,
5049
+ token: slot.token
4825
5050
  };
4826
5051
  const result = {
4827
5052
  rows: columnarToRows(columnar),
4828
5053
  stats: columnar.stats,
4829
5054
  warnings: slot.warnings,
4830
- totalMatches: slot.totalMatches
5055
+ totalMatches: slot.totalMatches,
5056
+ token: slot.token
4831
5057
  };
4832
5058
  raiseDeprecationWarnings(this.onWarning, slot.warnings);
4833
5059
  return { isError: false, result };
4834
5060
  });
4835
5061
  }
4836
- subscribe(hash, args, options = {}) {
4837
- if (typeof hash !== "string" || hash.trim().length === 0) {
4838
- throw new Error("Named query hash must be a non-empty string");
5062
+ subscribe(name, args, options = {}) {
5063
+ if (typeof name !== "string" || name.trim().length === 0) {
5064
+ throw new Error("Named query name must be a non-empty string");
4839
5065
  }
4840
5066
  const subscription = new TableSubscription(
4841
5067
  this.getStreamingTransport(),
4842
- { kind: "named", hash, args, orderByIndex: options.orderByIndex },
5068
+ { kind: "named", name, args, orderByIndex: options.orderByIndex },
4843
5069
  {
4844
5070
  onSnapshot: options.onSnapshot,
4845
5071
  onChange: options.onChange,
4846
5072
  onError: options.onError,
4847
- conflate: options.conflate
5073
+ conflate: options.conflate,
5074
+ atLeast: options.atLeast,
5075
+ waitMs: options.waitMs,
5076
+ onExceeded: options.onExceeded
4848
5077
  },
4849
5078
  (warnings) => {
4850
5079
  raiseDeprecationWarnings(this.onWarning, warnings);
4851
- }
5080
+ },
5081
+ this.store,
5082
+ this.database
4852
5083
  );
4853
5084
  subscription.start();
4854
5085
  return subscription;
@@ -4860,12 +5091,12 @@ var NamedMutationsApi = class {
4860
5091
  this.database = database;
4861
5092
  this.onWarning = onWarning;
4862
5093
  }
4863
- async execute(hash, args) {
4864
- if (typeof hash !== "string" || hash.trim().length === 0) {
4865
- throw new Error("Named mutation hash must be a non-empty string");
5094
+ async execute(name, args) {
5095
+ if (typeof name !== "string" || name.trim().length === 0) {
5096
+ throw new Error("Named mutation name must be a non-empty string");
4866
5097
  }
4867
5098
  const prefix = databasePath2(this.database);
4868
- const path = `${prefix}/named-mutations/${encodeURIComponent(hash)}/execute`;
5099
+ const path = `${prefix}/named-mutations/${encodeURIComponent(name)}/execute`;
4869
5100
  const wire = await this.transport.post(path, {
4870
5101
  args: args ?? {}
4871
5102
  });
@@ -4926,6 +5157,7 @@ var WebSocketTransport = class {
4926
5157
  // Serialized-send queue: each send appends to this tail.
4927
5158
  this._sendTail = Promise.resolve();
4928
5159
  this._lastVersion = 0;
5160
+ this._lastToken = null;
4929
5161
  this._reconnectAttempt = 0;
4930
5162
  this._disposed = false;
4931
5163
  this._missedPings = 0;
@@ -4943,6 +5175,7 @@ var WebSocketTransport = class {
4943
5175
  this._pingIntervalMs = options.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;
4944
5176
  this._maxMissedHeartbeats = options.maxMissedHeartbeats ?? DEFAULT_MAX_MISSED_HEARTBEATS;
4945
5177
  this._enableCompression = options.enableCompression ?? true;
5178
+ this._store = options.consistencyTokenStore;
4946
5179
  this._wireMode = options.wireMode ?? "json";
4947
5180
  this._authHandler?.setOnAccessTokenRefreshed((token) => {
4948
5181
  if (this._handshakeComplete && this._ws?.readyState === WS_READY_STATE_OPEN) {
@@ -4951,10 +5184,14 @@ var WebSocketTransport = class {
4951
5184
  });
4952
5185
  }
4953
5186
  // ─── Public API ────────────────────────────────────────────────────────────
4954
- /** Latest version number from the most recent server `heartbeat` message. */
5187
+ /** Latest change-event sequence from the most recent server `heartbeat` message. */
4955
5188
  get lastVersion() {
4956
5189
  return this._lastVersion;
4957
5190
  }
5191
+ /** Consistency token from the most recent server `heartbeat` (ADR 0042 D-13). */
5192
+ get lastToken() {
5193
+ return this._lastToken;
5194
+ }
4958
5195
  /**
4959
5196
  * Registers a handler for incoming server messages with the given channel id.
4960
5197
  * Use `"__global__"` to receive broadcast messages that carry no `id`.
@@ -5142,6 +5379,10 @@ var WebSocketTransport = class {
5142
5379
  }
5143
5380
  case "heartbeat":
5144
5381
  this._lastVersion = msg.version;
5382
+ this._lastToken = msg.token ?? null;
5383
+ if (this._lastToken != null && this._lastToken.length > 0) {
5384
+ this._store?.observe(this._database, this._lastToken);
5385
+ }
5145
5386
  break;
5146
5387
  case "pong":
5147
5388
  this._missedPings = 0;
@@ -5307,16 +5548,21 @@ var LongPollTransport = class {
5307
5548
  this._connected = false;
5308
5549
  this._pollTask = null;
5309
5550
  this._lastVersion = 0;
5551
+ this._lastToken = null;
5310
5552
  this._serverUrl = serverUrl;
5311
5553
  this._database = database;
5312
5554
  this._authHandler = options.authHandler;
5313
5555
  this._onReconnected = options.onReconnected;
5314
5556
  this._waitMs = Math.max(1, options.waitMs ?? DEFAULT_WAIT_MS);
5315
5557
  this._fetch = options.fetchImpl ?? localNetworkFetch;
5558
+ this._store = options.consistencyTokenStore;
5316
5559
  }
5317
5560
  get lastVersion() {
5318
5561
  return this._lastVersion;
5319
5562
  }
5563
+ get lastToken() {
5564
+ return this._lastToken;
5565
+ }
5320
5566
  registerHandler(id, handler) {
5321
5567
  this._handlers.set(id, handler);
5322
5568
  }
@@ -5439,6 +5685,10 @@ var LongPollTransport = class {
5439
5685
  for (const msg of payload.messages) {
5440
5686
  if (msg.type === "heartbeat") {
5441
5687
  this._lastVersion = msg.version;
5688
+ this._lastToken = msg.token ?? null;
5689
+ if (this._lastToken != null && this._lastToken.length > 0) {
5690
+ this._store?.observe(this._database, this._lastToken);
5691
+ }
5442
5692
  }
5443
5693
  const id = "id" in msg ? msg.id : void 0;
5444
5694
  if (id !== void 0 && id !== null) {
@@ -5518,6 +5768,9 @@ var FallbackStreamingTransport = class {
5518
5768
  get lastVersion() {
5519
5769
  return this._active.lastVersion;
5520
5770
  }
5771
+ get lastToken() {
5772
+ return this._active.lastToken;
5773
+ }
5521
5774
  registerHandler(id, handler) {
5522
5775
  this._handlers.set(id, handler);
5523
5776
  this._active.registerHandler(id, handler);
@@ -5597,6 +5850,7 @@ var AoudaClient = class {
5597
5850
  this.baseUrl = normalizeBaseUrl(options.serverUrl);
5598
5851
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
5599
5852
  this.database = options.database.trim();
5853
+ this._store = options.consistencyTokenStore ?? new MemoryConsistencyTokenStore();
5600
5854
  if (!Number.isFinite(this.timeout) || this.timeout <= 0) {
5601
5855
  throw new Error("timeout must be a finite positive number");
5602
5856
  }
@@ -5631,7 +5885,9 @@ var AoudaClient = class {
5631
5885
  }
5632
5886
  const httpTransport = new HttpTransport({
5633
5887
  baseUrl: this.baseUrl,
5634
- timeout: this.timeout
5888
+ timeout: this.timeout,
5889
+ database: this.database,
5890
+ consistencyTokenStore: this._store
5635
5891
  });
5636
5892
  const activeAuth = options.serverAuth ?? options.appAuth;
5637
5893
  if (activeAuth) {
@@ -5691,7 +5947,8 @@ var AoudaClient = class {
5691
5947
  this.transport,
5692
5948
  this.database,
5693
5949
  onNamedArtifactWarning,
5694
- () => this._getOrCreateWebSocketTransport()
5950
+ () => this._getOrCreateWebSocketTransport(),
5951
+ this._store
5695
5952
  );
5696
5953
  this._namedMutations = new NamedMutationsApi(
5697
5954
  this.transport,
@@ -5790,7 +6047,8 @@ var AoudaClient = class {
5790
6047
  name,
5791
6048
  this.database,
5792
6049
  void 0,
5793
- () => this._getOrCreateWebSocketTransport()
6050
+ () => this._getOrCreateWebSocketTransport(),
6051
+ this._store
5794
6052
  );
5795
6053
  }
5796
6054
  /**
@@ -5842,13 +6100,26 @@ var AoudaClient = class {
5842
6100
  return this._materializedQueries;
5843
6101
  }
5844
6102
  /**
5845
- * Hash-only named-query execute and batch. Names are codegen aliases (D-5).
6103
+ * Named-query execute, read-only batch, and subscribe by unique schema name.
5846
6104
  */
5847
6105
  get namedQueries() {
5848
6106
  return this._namedQueries;
5849
6107
  }
6108
+ observeConsistencyToken(token) {
6109
+ this._store.observe(this.database, token);
6110
+ }
6111
+ getObservedConsistencyToken() {
6112
+ return this._store.get(this.database);
6113
+ }
6114
+ async getConsistencyToken() {
6115
+ const body = await this.transport.get(
6116
+ `/api/databases/${encodeURIComponent(this.database)}/token`
6117
+ );
6118
+ this._store.observe(this.database, body.token);
6119
+ return this._store.get(this.database) ?? body.token;
6120
+ }
5850
6121
  /**
5851
- * Hash-only named-mutation execute. No batch.
6122
+ * Named-mutation execute by unique schema name. No batch.
5852
6123
  */
5853
6124
  get namedMutations() {
5854
6125
  return this._namedMutations;
@@ -5975,13 +6246,15 @@ var AoudaClient = class {
5975
6246
  const primary = new WebSocketTransport(this.baseUrl, this.database, {
5976
6247
  authHandler: this._authHandler,
5977
6248
  enableCompression: this._streamingEnableCompression,
5978
- wireMode: this._streamingWireMode
6249
+ wireMode: this._streamingWireMode,
6250
+ consistencyTokenStore: this._store
5979
6251
  });
5980
6252
  if (this._streamingEnableLongPollFallback) {
5981
6253
  this._wsTransport = new FallbackStreamingTransport(primary, () => {
5982
6254
  return new LongPollTransport(this.baseUrl, this.database, {
5983
6255
  authHandler: this._authHandler,
5984
- waitMs: this._streamingLongPollWaitMs
6256
+ waitMs: this._streamingLongPollWaitMs,
6257
+ consistencyTokenStore: this._store
5985
6258
  });
5986
6259
  });
5987
6260
  } else {
@@ -6278,6 +6551,7 @@ export {
6278
6551
  MaterializedQueriesApi,
6279
6552
  MaterializedQueryState,
6280
6553
  MaterializedQueryType,
6554
+ MemoryConsistencyTokenStore,
6281
6555
  MetricsAdminApi,
6282
6556
  NamedMutationsApi,
6283
6557
  NamedQueriesApi,
@@ -6293,10 +6567,12 @@ export {
6293
6567
  applyLocalNetworkAccess,
6294
6568
  coerceColumnarValue,
6295
6569
  columnarToRows,
6570
+ compareOrdinal,
6296
6571
  createAoudaClient,
6297
6572
  createAoudaClusterMcpToolSet,
6298
6573
  installLocalNetworkFetch,
6299
6574
  localNetworkFetch,
6575
+ maxToken,
6300
6576
  resolveTargetAddressSpace,
6301
6577
  version
6302
6578
  };