@aouda/client 0.1.8 → 0.1.10

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.cjs CHANGED
@@ -61,6 +61,8 @@ __export(index_exports, {
61
61
  MaterializedQueryState: () => MaterializedQueryState,
62
62
  MaterializedQueryType: () => MaterializedQueryType,
63
63
  MetricsAdminApi: () => MetricsAdminApi,
64
+ NamedMutationsApi: () => NamedMutationsApi,
65
+ NamedQueriesApi: () => NamedQueriesApi,
64
66
  NodeAdminApi: () => NodeAdminApi,
65
67
  ReplicationAdminApi: () => ReplicationAdminApi,
66
68
  RetryPolicy: () => RetryPolicy,
@@ -71,6 +73,7 @@ __export(index_exports, {
71
73
  WhereGroupBuilder: () => WhereGroupBuilder,
72
74
  applyLocalNetworkAccess: () => applyLocalNetworkAccess,
73
75
  coerceColumnarValue: () => coerceColumnarValue,
76
+ columnarToRows: () => columnarToRows,
74
77
  createAoudaClient: () => createAoudaClient,
75
78
  createAoudaClusterMcpToolSet: () => createAoudaClusterMcpToolSet,
76
79
  installLocalNetworkFetch: () => installLocalNetworkFetch,
@@ -83,7 +86,7 @@ module.exports = __toCommonJS(index_exports);
83
86
  // package.json
84
87
  var package_default = {
85
88
  name: "@aouda/client",
86
- version: "0.1.8",
89
+ version: "0.1.10",
87
90
  description: "Official TypeScript/JavaScript client library for Aouda",
88
91
  type: "module",
89
92
  main: "./dist/index.cjs",
@@ -215,12 +218,13 @@ var AoudaResponseError = class extends AoudaError {
215
218
  }
216
219
  };
217
220
  var AoudaApiError = class extends AoudaError {
218
- constructor(message, code, statusCode, details, requestId) {
221
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
219
222
  super(message);
220
223
  this.code = code;
221
224
  this.statusCode = statusCode;
222
225
  this.details = details;
223
226
  this.requestId = requestId;
227
+ this.retryAfterSeconds = retryAfterSeconds;
224
228
  this.name = "AoudaApiError";
225
229
  }
226
230
  };
@@ -243,8 +247,8 @@ var AoudaValidationError = class extends AoudaApiError {
243
247
  }
244
248
  };
245
249
  var AoudaServerError = class extends AoudaApiError {
246
- constructor(message, code, statusCode, details, requestId) {
247
- super(message, code, statusCode, details, requestId);
250
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
251
+ super(message, code, statusCode, details, requestId, retryAfterSeconds);
248
252
  this.name = "AoudaServerError";
249
253
  }
250
254
  };
@@ -1199,12 +1203,22 @@ var ERROR_CODE_MAP = {
1199
1203
  SERVICE_UNAVAILABLE: AoudaServerError,
1200
1204
  TIMEOUT: AoudaServerError,
1201
1205
  OVERLOADED: AoudaServerError,
1206
+ MEMORY_BUDGET_EXCEEDED: AoudaServerError,
1207
+ WAL_CAPACITY_EXCEEDED: AoudaServerError,
1202
1208
  AUTH_TOKEN_MISSING: AoudaAuthenticationError,
1203
1209
  AUTH_TOKEN_EXPIRED: AoudaAuthenticationError,
1204
1210
  AUTH_TOKEN_INVALID: AoudaAuthenticationError,
1205
1211
  AUTH_TOKEN_REVOKED: AoudaAuthenticationError,
1206
1212
  AUTH_API_KEY_INVALID: AoudaAuthenticationError,
1207
- AUTH_REQUIRED: AoudaAuthenticationError
1213
+ AUTH_REQUIRED: AoudaAuthenticationError,
1214
+ NAMED_QUERY_NOT_FOUND: AoudaNotFoundError,
1215
+ NAMED_MUTATION_NOT_FOUND: AoudaNotFoundError,
1216
+ NAMED_QUERY_BATCH_EMPTY: AoudaValidationError,
1217
+ NAMED_QUERY_BATCH_TOO_LARGE: AoudaValidationError,
1218
+ NAMED_QUERY_BATCH_MUTATION: AoudaValidationError,
1219
+ NAMED_QUERY_BIND_FAILED: AoudaValidationError,
1220
+ NAMED_QUERY_PARAM_REQUIRED: AoudaValidationError,
1221
+ NAMED_MUTATION_BIND_FAILED: AoudaValidationError
1208
1222
  };
1209
1223
  function createComposedAbortController(...signals) {
1210
1224
  const controller = new AbortController();
@@ -1226,13 +1240,22 @@ function createComposedAbortController(...signals) {
1226
1240
  }
1227
1241
  return controller;
1228
1242
  }
1229
- function createApiError(statusCode, statusText, body) {
1243
+ function parseRetryAfterSeconds(header) {
1244
+ if (header == null || header.trim() === "") return void 0;
1245
+ const trimmed = header.trim();
1246
+ if (!/^\d+$/.test(trimmed)) return void 0;
1247
+ const n = Number.parseInt(trimmed, 10);
1248
+ if (!Number.isFinite(n) || n < 0) return void 0;
1249
+ return n;
1250
+ }
1251
+ function createApiError(statusCode, statusText, body, retryAfterHeader) {
1230
1252
  const message = body.error ?? `${statusCode} ${statusText}`;
1231
1253
  const code = body.code ?? "UNKNOWN";
1232
1254
  const details = body.details;
1233
1255
  const requestId = body.requestId;
1256
+ const retryAfterSeconds = parseRetryAfterSeconds(retryAfterHeader ?? null);
1234
1257
  const Ctor = ERROR_CODE_MAP[code] ?? AoudaApiError;
1235
- return new Ctor(message, code, statusCode, details, requestId);
1258
+ return new Ctor(message, code, statusCode, details, requestId, retryAfterSeconds);
1236
1259
  }
1237
1260
  var HttpTransport = class {
1238
1261
  constructor(options) {
@@ -1325,7 +1348,8 @@ var HttpTransport = class {
1325
1348
  throw createApiError(
1326
1349
  response.status,
1327
1350
  response.statusText,
1328
- errorBody
1351
+ errorBody,
1352
+ response.headers.get("Retry-After")
1329
1353
  );
1330
1354
  }
1331
1355
  throw new AoudaResponseError(
@@ -1435,7 +1459,8 @@ var HttpTransport = class {
1435
1459
  throw createApiError(
1436
1460
  response.status,
1437
1461
  response.statusText,
1438
- errorBody
1462
+ errorBody,
1463
+ response.headers.get("Retry-After")
1439
1464
  );
1440
1465
  }
1441
1466
  throw new AoudaResponseError(
@@ -1781,6 +1806,17 @@ var CIRCUIT_BREAKER_POLICY_DISABLED = {
1781
1806
 
1782
1807
  // src/resilience/resilient-transport.ts
1783
1808
  var REQUEST_ID_HEADER3 = "X-Request-Id";
1809
+ function isCapacityBackPressure(error) {
1810
+ return error instanceof AoudaApiError && (error.code === "MEMORY_BUDGET_EXCEEDED" || error.code === "WAL_CAPACITY_EXCEEDED");
1811
+ }
1812
+ function retryDelayMs(retryPolicy, attempt, error) {
1813
+ const backoff = calculateDelay(retryPolicy, attempt);
1814
+ if (!isCapacityBackPressure(error) || !(error instanceof AoudaApiError)) {
1815
+ return backoff;
1816
+ }
1817
+ const headerMs = error.retryAfterSeconds != null ? error.retryAfterSeconds * 1e3 : 0;
1818
+ return Math.max(backoff, headerMs);
1819
+ }
1784
1820
  function delayMs(ms, signal) {
1785
1821
  if (ms <= 0) return Promise.resolve();
1786
1822
  return new Promise((resolve, reject) => {
@@ -1835,7 +1871,7 @@ var ResilientTransport = class {
1835
1871
  } catch (error) {
1836
1872
  lastError = error;
1837
1873
  const retryable = isRetryable(error);
1838
- if (retryable) {
1874
+ if (retryable && !isCapacityBackPressure(error)) {
1839
1875
  this.circuitBreaker.recordFailure(error);
1840
1876
  }
1841
1877
  if (!retryable) {
@@ -1845,7 +1881,7 @@ var ResilientTransport = class {
1845
1881
  if (attempt > this.retryPolicy.maxRetries) {
1846
1882
  throw error;
1847
1883
  }
1848
- const delay = calculateDelay(this.retryPolicy, attempt);
1884
+ const delay = retryDelayMs(this.retryPolicy, attempt, error);
1849
1885
  try {
1850
1886
  await delayMs(delay, config.signal);
1851
1887
  } catch {
@@ -1881,7 +1917,7 @@ var ResilientTransport = class {
1881
1917
  } catch (error) {
1882
1918
  lastError = error;
1883
1919
  const retryable = isRetryable(error);
1884
- if (retryable) {
1920
+ if (retryable && !isCapacityBackPressure(error)) {
1885
1921
  this.circuitBreaker.recordFailure(error);
1886
1922
  }
1887
1923
  if (!retryable) {
@@ -1891,7 +1927,7 @@ var ResilientTransport = class {
1891
1927
  if (attempt > this.retryPolicy.maxRetries) {
1892
1928
  throw error;
1893
1929
  }
1894
- const delay = calculateDelay(this.retryPolicy, attempt);
1930
+ const delay = retryDelayMs(this.retryPolicy, attempt, error);
1895
1931
  try {
1896
1932
  await delayMs(delay, config.signal);
1897
1933
  } catch {
@@ -2075,6 +2111,7 @@ var TableSubscription = class {
2075
2111
  this._started = false;
2076
2112
  this._startPromise = null;
2077
2113
  this._lastVersion = 0;
2114
+ this._pendingSnapshotRows = [];
2078
2115
  this.id = createStreamingId("sub");
2079
2116
  this._transport = transport;
2080
2117
  this._tableName = tableName;
@@ -2131,6 +2168,7 @@ var TableSubscription = class {
2131
2168
  return;
2132
2169
  }
2133
2170
  try {
2171
+ this._pendingSnapshotRows = [];
2134
2172
  const resumeFrom = this._lastVersion > 0 ? this._lastVersion : void 0;
2135
2173
  await this._sendSubscribe(resumeFrom);
2136
2174
  } catch (error) {
@@ -2159,7 +2197,13 @@ var TableSubscription = class {
2159
2197
  }
2160
2198
  switch (message.type) {
2161
2199
  case "snapshot":
2162
- this._handleSnapshot(message);
2200
+ this._handleSnapshotPage(message);
2201
+ return;
2202
+ case "snapshot_complete":
2203
+ this._handleSnapshotComplete(message);
2204
+ return;
2205
+ case "gap":
2206
+ void this._handleGap(message);
2163
2207
  return;
2164
2208
  case "change":
2165
2209
  this._handleChange(message);
@@ -2171,9 +2215,13 @@ var TableSubscription = class {
2171
2215
  return;
2172
2216
  }
2173
2217
  }
2174
- _handleSnapshot(message) {
2218
+ _handleSnapshotPage(message) {
2219
+ this._pendingSnapshotRows.push(...message.rows);
2220
+ }
2221
+ _handleSnapshotComplete(message) {
2175
2222
  this._lastVersion = message.version;
2176
- const rows = message.rows;
2223
+ const rows = this._pendingSnapshotRows;
2224
+ this._pendingSnapshotRows = [];
2177
2225
  this._onSnapshot?.(rows, message.version);
2178
2226
  this._queue.push({
2179
2227
  type: "snapshot",
@@ -2181,6 +2229,16 @@ var TableSubscription = class {
2181
2229
  version: message.version
2182
2230
  });
2183
2231
  }
2232
+ async _handleGap(message) {
2233
+ this._pendingSnapshotRows = [];
2234
+ this._lastVersion = message.last_seq;
2235
+ try {
2236
+ await this._sendSubscribe(message.last_seq);
2237
+ } catch (error) {
2238
+ const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription gap resume failed");
2239
+ this._notifyError(wrapped);
2240
+ }
2241
+ }
2184
2242
  _handleChange(message) {
2185
2243
  this._lastVersion = message.version;
2186
2244
  const event = {
@@ -2195,6 +2253,7 @@ var TableSubscription = class {
2195
2253
  this._queue.push(event);
2196
2254
  }
2197
2255
  _handleServerError(message) {
2256
+ this._pendingSnapshotRows = [];
2198
2257
  const error = new AoudaConnectionError(
2199
2258
  `Subscription error (${message.code}): ${message.message}`
2200
2259
  );
@@ -4633,6 +4692,147 @@ var MaterializedQueriesApi = class {
4633
4692
  }
4634
4693
  };
4635
4694
 
4695
+ // src/named-queries.ts
4696
+ var MAX_NAMED_QUERY_BATCH_SIZE = 32;
4697
+ function raiseDeprecationWarnings(sink, warnings) {
4698
+ if (warnings == null) return;
4699
+ for (const warning of warnings) {
4700
+ if (warning.code !== "NAMED_QUERY_DEPRECATED" && warning.code !== "NAMED_MUTATION_DEPRECATED") {
4701
+ continue;
4702
+ }
4703
+ const sunset = warning.sunsetAt != null ? ` sunsetAt=${warning.sunsetAt}` : "";
4704
+ const hash = warning.hash != null && warning.hash.length > 0 ? ` hash=${warning.hash}` : "";
4705
+ sink({
4706
+ code: warning.code,
4707
+ hash: warning.hash,
4708
+ sunsetAt: warning.sunsetAt,
4709
+ message: `${warning.code}:${hash}${sunset}`.trim()
4710
+ });
4711
+ }
4712
+ }
4713
+ function emptyStats() {
4714
+ return {
4715
+ rowsScanned: 0,
4716
+ rowsReturned: 0,
4717
+ segmentsAccessed: 0,
4718
+ executionMs: 0
4719
+ };
4720
+ }
4721
+ var NamedQueriesApi = class {
4722
+ constructor(transport, database, onWarning) {
4723
+ this.transport = transport;
4724
+ this.database = database;
4725
+ this.onWarning = onWarning;
4726
+ }
4727
+ async execute(hash, args, options) {
4728
+ if (typeof hash !== "string" || hash.trim().length === 0) {
4729
+ throw new Error("Named query hash must be a non-empty string");
4730
+ }
4731
+ const prefix = databasePath2(this.database);
4732
+ const path = `${prefix}/named-queries/${encodeURIComponent(hash)}/query?format=columnar`;
4733
+ const response = await this.transport.post(
4734
+ path,
4735
+ { args: args ?? {} },
4736
+ { signal: options?.signal }
4737
+ );
4738
+ const rows = columnarToRows(response);
4739
+ raiseDeprecationWarnings(this.onWarning, response.warnings);
4740
+ return {
4741
+ rows,
4742
+ stats: response.stats,
4743
+ warnings: response.warnings
4744
+ };
4745
+ }
4746
+ async batch(items, options) {
4747
+ if (items.length === 0) {
4748
+ throw new AoudaValidationError(
4749
+ "Named query batch requires a non-empty queries array.",
4750
+ "NAMED_QUERY_BATCH_EMPTY",
4751
+ 400
4752
+ );
4753
+ }
4754
+ if (items.length > MAX_NAMED_QUERY_BATCH_SIZE) {
4755
+ throw new AoudaValidationError(
4756
+ `Named query batch exceeds ${MAX_NAMED_QUERY_BATCH_SIZE} elements.`,
4757
+ "NAMED_QUERY_BATCH_TOO_LARGE",
4758
+ 400
4759
+ );
4760
+ }
4761
+ const prefix = databasePath2(this.database);
4762
+ const path = `${prefix}/named-queries/batch?format=columnar`;
4763
+ const envelope = await this.transport.post(
4764
+ path,
4765
+ { queries: items },
4766
+ { signal: options?.signal }
4767
+ );
4768
+ return (envelope.results ?? []).map((slot) => {
4769
+ if (slot.code != null && slot.code.length > 0) {
4770
+ return {
4771
+ isError: true,
4772
+ code: slot.code,
4773
+ error: slot.error
4774
+ };
4775
+ }
4776
+ const columnar = {
4777
+ columns: slot.columns ?? [],
4778
+ types: slot.types ?? [],
4779
+ data: slot.data ?? [],
4780
+ rowCount: slot.rowCount ?? 0,
4781
+ stats: slot.stats ?? emptyStats(),
4782
+ warnings: slot.warnings
4783
+ };
4784
+ const result = {
4785
+ rows: columnarToRows(columnar),
4786
+ stats: columnar.stats,
4787
+ warnings: slot.warnings
4788
+ };
4789
+ raiseDeprecationWarnings(this.onWarning, slot.warnings);
4790
+ return { isError: false, result };
4791
+ });
4792
+ }
4793
+ };
4794
+ var NamedMutationsApi = class {
4795
+ constructor(transport, database, onWarning) {
4796
+ this.transport = transport;
4797
+ this.database = database;
4798
+ this.onWarning = onWarning;
4799
+ }
4800
+ async execute(hash, args) {
4801
+ if (typeof hash !== "string" || hash.trim().length === 0) {
4802
+ throw new Error("Named mutation hash must be a non-empty string");
4803
+ }
4804
+ const prefix = databasePath2(this.database);
4805
+ const path = `${prefix}/named-mutations/${encodeURIComponent(hash)}/execute`;
4806
+ const wire = await this.transport.post(path, {
4807
+ args: args ?? {}
4808
+ });
4809
+ let op = "unknown";
4810
+ let rowsAffected = 0;
4811
+ if (wire.rowsInserted != null) {
4812
+ op = "insert";
4813
+ rowsAffected = wire.rowsInserted;
4814
+ } else if (wire.rowsUpdated != null) {
4815
+ op = "update";
4816
+ rowsAffected = wire.rowsUpdated;
4817
+ } else if (wire.rowsDeleted != null) {
4818
+ op = "delete";
4819
+ rowsAffected = wire.rowsDeleted;
4820
+ }
4821
+ raiseDeprecationWarnings(this.onWarning, wire.warnings);
4822
+ const returning = wire.rows == null ? void 0 : {
4823
+ rows: columnarToRows(wire.rows),
4824
+ stats: wire.rows.stats,
4825
+ warnings: wire.rows.warnings
4826
+ };
4827
+ return {
4828
+ op,
4829
+ rowsAffected,
4830
+ returning,
4831
+ warnings: wire.warnings
4832
+ };
4833
+ }
4834
+ };
4835
+
4636
4836
  // src/streaming/websocket-transport.ts
4637
4837
  var import_msgpack = require("@msgpack/msgpack");
4638
4838
  var DEFAULT_PING_INTERVAL_MS = 2e4;
@@ -5395,6 +5595,19 @@ var AoudaClient = class {
5395
5595
  this.transport,
5396
5596
  this.database
5397
5597
  );
5598
+ const onNamedArtifactWarning = options.onNamedArtifactWarning ?? ((warning) => {
5599
+ console.warn(warning.message);
5600
+ });
5601
+ this._namedQueries = new NamedQueriesApi(
5602
+ this.transport,
5603
+ this.database,
5604
+ onNamedArtifactWarning
5605
+ );
5606
+ this._namedMutations = new NamedMutationsApi(
5607
+ this.transport,
5608
+ this.database,
5609
+ onNamedArtifactWarning
5610
+ );
5398
5611
  }
5399
5612
  /**
5400
5613
  * Connects to the Aouda server.
@@ -5537,6 +5750,18 @@ var AoudaClient = class {
5537
5750
  get materializedQueries() {
5538
5751
  return this._materializedQueries;
5539
5752
  }
5753
+ /**
5754
+ * Hash-only named-query execute and batch. Names are codegen aliases (D-5).
5755
+ */
5756
+ get namedQueries() {
5757
+ return this._namedQueries;
5758
+ }
5759
+ /**
5760
+ * Hash-only named-mutation execute. No batch.
5761
+ */
5762
+ get namedMutations() {
5763
+ return this._namedMutations;
5764
+ }
5540
5765
  /**
5541
5766
  * Access auth operations (signUp, signIn, signOut, refresh, me, changePassword).
5542
5767
  * @returns The auth API.
@@ -5958,6 +6183,8 @@ var version = package_default.version;
5958
6183
  MaterializedQueryState,
5959
6184
  MaterializedQueryType,
5960
6185
  MetricsAdminApi,
6186
+ NamedMutationsApi,
6187
+ NamedQueriesApi,
5961
6188
  NodeAdminApi,
5962
6189
  ReplicationAdminApi,
5963
6190
  RetryPolicy,
@@ -5968,6 +6195,7 @@ var version = package_default.version;
5968
6195
  WhereGroupBuilder,
5969
6196
  applyLocalNetworkAccess,
5970
6197
  coerceColumnarValue,
6198
+ columnarToRows,
5971
6199
  createAoudaClient,
5972
6200
  createAoudaClusterMcpToolSet,
5973
6201
  installLocalNetworkFetch,