@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.
@@ -1,4 +1,4 @@
1
- import { A as AoudaClient } from '../client-TIOp5NUu.cjs';
1
+ import { A as AoudaClient } from '../client-Dgenr-cE.cjs';
2
2
 
3
3
  /**
4
4
  * CLI for @aouda/client.
@@ -1,4 +1,4 @@
1
- import { A as AoudaClient } from '../client-TIOp5NUu.js';
1
+ import { A as AoudaClient } from '../client-Dgenr-cE.js';
2
2
 
3
3
  /**
4
4
  * CLI for @aouda/client.
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.8",
395
+ version: "0.1.10",
396
396
  description: "Official TypeScript/JavaScript client library for Aouda",
397
397
  type: "module",
398
398
  main: "./dist/index.cjs",
@@ -507,12 +507,13 @@ var AoudaResponseError = class extends AoudaError {
507
507
  }
508
508
  };
509
509
  var AoudaApiError = class extends AoudaError {
510
- constructor(message, code, statusCode, details, requestId) {
510
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
511
511
  super(message);
512
512
  this.code = code;
513
513
  this.statusCode = statusCode;
514
514
  this.details = details;
515
515
  this.requestId = requestId;
516
+ this.retryAfterSeconds = retryAfterSeconds;
516
517
  this.name = "AoudaApiError";
517
518
  }
518
519
  };
@@ -535,8 +536,8 @@ var AoudaValidationError = class extends AoudaApiError {
535
536
  }
536
537
  };
537
538
  var AoudaServerError = class extends AoudaApiError {
538
- constructor(message, code, statusCode, details, requestId) {
539
- super(message, code, statusCode, details, requestId);
539
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
540
+ super(message, code, statusCode, details, requestId, retryAfterSeconds);
540
541
  this.name = "AoudaServerError";
541
542
  }
542
543
  };
@@ -1475,12 +1476,22 @@ var ERROR_CODE_MAP = {
1475
1476
  SERVICE_UNAVAILABLE: AoudaServerError,
1476
1477
  TIMEOUT: AoudaServerError,
1477
1478
  OVERLOADED: AoudaServerError,
1479
+ MEMORY_BUDGET_EXCEEDED: AoudaServerError,
1480
+ WAL_CAPACITY_EXCEEDED: AoudaServerError,
1478
1481
  AUTH_TOKEN_MISSING: AoudaAuthenticationError,
1479
1482
  AUTH_TOKEN_EXPIRED: AoudaAuthenticationError,
1480
1483
  AUTH_TOKEN_INVALID: AoudaAuthenticationError,
1481
1484
  AUTH_TOKEN_REVOKED: AoudaAuthenticationError,
1482
1485
  AUTH_API_KEY_INVALID: AoudaAuthenticationError,
1483
- AUTH_REQUIRED: AoudaAuthenticationError
1486
+ AUTH_REQUIRED: AoudaAuthenticationError,
1487
+ NAMED_QUERY_NOT_FOUND: AoudaNotFoundError,
1488
+ NAMED_MUTATION_NOT_FOUND: AoudaNotFoundError,
1489
+ NAMED_QUERY_BATCH_EMPTY: AoudaValidationError,
1490
+ NAMED_QUERY_BATCH_TOO_LARGE: AoudaValidationError,
1491
+ NAMED_QUERY_BATCH_MUTATION: AoudaValidationError,
1492
+ NAMED_QUERY_BIND_FAILED: AoudaValidationError,
1493
+ NAMED_QUERY_PARAM_REQUIRED: AoudaValidationError,
1494
+ NAMED_MUTATION_BIND_FAILED: AoudaValidationError
1484
1495
  };
1485
1496
  function createComposedAbortController(...signals) {
1486
1497
  const controller = new AbortController();
@@ -1502,13 +1513,22 @@ function createComposedAbortController(...signals) {
1502
1513
  }
1503
1514
  return controller;
1504
1515
  }
1505
- function createApiError(statusCode, statusText, body) {
1516
+ function parseRetryAfterSeconds(header) {
1517
+ if (header == null || header.trim() === "") return void 0;
1518
+ const trimmed = header.trim();
1519
+ if (!/^\d+$/.test(trimmed)) return void 0;
1520
+ const n = Number.parseInt(trimmed, 10);
1521
+ if (!Number.isFinite(n) || n < 0) return void 0;
1522
+ return n;
1523
+ }
1524
+ function createApiError(statusCode, statusText, body, retryAfterHeader) {
1506
1525
  const message = body.error ?? `${statusCode} ${statusText}`;
1507
1526
  const code = body.code ?? "UNKNOWN";
1508
1527
  const details = body.details;
1509
1528
  const requestId = body.requestId;
1529
+ const retryAfterSeconds = parseRetryAfterSeconds(retryAfterHeader ?? null);
1510
1530
  const Ctor = ERROR_CODE_MAP[code] ?? AoudaApiError;
1511
- return new Ctor(message, code, statusCode, details, requestId);
1531
+ return new Ctor(message, code, statusCode, details, requestId, retryAfterSeconds);
1512
1532
  }
1513
1533
  var HttpTransport = class {
1514
1534
  constructor(options) {
@@ -1601,7 +1621,8 @@ var HttpTransport = class {
1601
1621
  throw createApiError(
1602
1622
  response.status,
1603
1623
  response.statusText,
1604
- errorBody
1624
+ errorBody,
1625
+ response.headers.get("Retry-After")
1605
1626
  );
1606
1627
  }
1607
1628
  throw new AoudaResponseError(
@@ -1711,7 +1732,8 @@ var HttpTransport = class {
1711
1732
  throw createApiError(
1712
1733
  response.status,
1713
1734
  response.statusText,
1714
- errorBody
1735
+ errorBody,
1736
+ response.headers.get("Retry-After")
1715
1737
  );
1716
1738
  }
1717
1739
  throw new AoudaResponseError(
@@ -2057,6 +2079,17 @@ var CIRCUIT_BREAKER_POLICY_DISABLED = {
2057
2079
 
2058
2080
  // src/resilience/resilient-transport.ts
2059
2081
  var REQUEST_ID_HEADER3 = "X-Request-Id";
2082
+ function isCapacityBackPressure(error) {
2083
+ return error instanceof AoudaApiError && (error.code === "MEMORY_BUDGET_EXCEEDED" || error.code === "WAL_CAPACITY_EXCEEDED");
2084
+ }
2085
+ function retryDelayMs(retryPolicy, attempt, error) {
2086
+ const backoff = calculateDelay(retryPolicy, attempt);
2087
+ if (!isCapacityBackPressure(error) || !(error instanceof AoudaApiError)) {
2088
+ return backoff;
2089
+ }
2090
+ const headerMs = error.retryAfterSeconds != null ? error.retryAfterSeconds * 1e3 : 0;
2091
+ return Math.max(backoff, headerMs);
2092
+ }
2060
2093
  function delayMs(ms, signal) {
2061
2094
  if (ms <= 0) return Promise.resolve();
2062
2095
  return new Promise((resolve4, reject) => {
@@ -2111,7 +2144,7 @@ var ResilientTransport = class {
2111
2144
  } catch (error) {
2112
2145
  lastError = error;
2113
2146
  const retryable = isRetryable(error);
2114
- if (retryable) {
2147
+ if (retryable && !isCapacityBackPressure(error)) {
2115
2148
  this.circuitBreaker.recordFailure(error);
2116
2149
  }
2117
2150
  if (!retryable) {
@@ -2121,7 +2154,7 @@ var ResilientTransport = class {
2121
2154
  if (attempt > this.retryPolicy.maxRetries) {
2122
2155
  throw error;
2123
2156
  }
2124
- const delay = calculateDelay(this.retryPolicy, attempt);
2157
+ const delay = retryDelayMs(this.retryPolicy, attempt, error);
2125
2158
  try {
2126
2159
  await delayMs(delay, config.signal);
2127
2160
  } catch {
@@ -2157,7 +2190,7 @@ var ResilientTransport = class {
2157
2190
  } catch (error) {
2158
2191
  lastError = error;
2159
2192
  const retryable = isRetryable(error);
2160
- if (retryable) {
2193
+ if (retryable && !isCapacityBackPressure(error)) {
2161
2194
  this.circuitBreaker.recordFailure(error);
2162
2195
  }
2163
2196
  if (!retryable) {
@@ -2167,7 +2200,7 @@ var ResilientTransport = class {
2167
2200
  if (attempt > this.retryPolicy.maxRetries) {
2168
2201
  throw error;
2169
2202
  }
2170
- const delay = calculateDelay(this.retryPolicy, attempt);
2203
+ const delay = retryDelayMs(this.retryPolicy, attempt, error);
2171
2204
  try {
2172
2205
  await delayMs(delay, config.signal);
2173
2206
  } catch {
@@ -2351,6 +2384,7 @@ var TableSubscription = class {
2351
2384
  this._started = false;
2352
2385
  this._startPromise = null;
2353
2386
  this._lastVersion = 0;
2387
+ this._pendingSnapshotRows = [];
2354
2388
  this.id = createStreamingId("sub");
2355
2389
  this._transport = transport;
2356
2390
  this._tableName = tableName;
@@ -2407,6 +2441,7 @@ var TableSubscription = class {
2407
2441
  return;
2408
2442
  }
2409
2443
  try {
2444
+ this._pendingSnapshotRows = [];
2410
2445
  const resumeFrom = this._lastVersion > 0 ? this._lastVersion : void 0;
2411
2446
  await this._sendSubscribe(resumeFrom);
2412
2447
  } catch (error) {
@@ -2435,7 +2470,13 @@ var TableSubscription = class {
2435
2470
  }
2436
2471
  switch (message.type) {
2437
2472
  case "snapshot":
2438
- this._handleSnapshot(message);
2473
+ this._handleSnapshotPage(message);
2474
+ return;
2475
+ case "snapshot_complete":
2476
+ this._handleSnapshotComplete(message);
2477
+ return;
2478
+ case "gap":
2479
+ void this._handleGap(message);
2439
2480
  return;
2440
2481
  case "change":
2441
2482
  this._handleChange(message);
@@ -2447,9 +2488,13 @@ var TableSubscription = class {
2447
2488
  return;
2448
2489
  }
2449
2490
  }
2450
- _handleSnapshot(message) {
2491
+ _handleSnapshotPage(message) {
2492
+ this._pendingSnapshotRows.push(...message.rows);
2493
+ }
2494
+ _handleSnapshotComplete(message) {
2451
2495
  this._lastVersion = message.version;
2452
- const rows = message.rows;
2496
+ const rows = this._pendingSnapshotRows;
2497
+ this._pendingSnapshotRows = [];
2453
2498
  this._onSnapshot?.(rows, message.version);
2454
2499
  this._queue.push({
2455
2500
  type: "snapshot",
@@ -2457,6 +2502,16 @@ var TableSubscription = class {
2457
2502
  version: message.version
2458
2503
  });
2459
2504
  }
2505
+ async _handleGap(message) {
2506
+ this._pendingSnapshotRows = [];
2507
+ this._lastVersion = message.last_seq;
2508
+ try {
2509
+ await this._sendSubscribe(message.last_seq);
2510
+ } catch (error) {
2511
+ const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription gap resume failed");
2512
+ this._notifyError(wrapped);
2513
+ }
2514
+ }
2460
2515
  _handleChange(message) {
2461
2516
  this._lastVersion = message.version;
2462
2517
  const event = {
@@ -2471,6 +2526,7 @@ var TableSubscription = class {
2471
2526
  this._queue.push(event);
2472
2527
  }
2473
2528
  _handleServerError(message) {
2529
+ this._pendingSnapshotRows = [];
2474
2530
  const error = new AoudaConnectionError(
2475
2531
  `Subscription error (${message.code}): ${message.message}`
2476
2532
  );
@@ -4878,6 +4934,147 @@ var MaterializedQueriesApi = class {
4878
4934
  }
4879
4935
  };
4880
4936
 
4937
+ // src/named-queries.ts
4938
+ var MAX_NAMED_QUERY_BATCH_SIZE = 32;
4939
+ function raiseDeprecationWarnings(sink, warnings) {
4940
+ if (warnings == null) return;
4941
+ for (const warning of warnings) {
4942
+ if (warning.code !== "NAMED_QUERY_DEPRECATED" && warning.code !== "NAMED_MUTATION_DEPRECATED") {
4943
+ continue;
4944
+ }
4945
+ const sunset = warning.sunsetAt != null ? ` sunsetAt=${warning.sunsetAt}` : "";
4946
+ const hash = warning.hash != null && warning.hash.length > 0 ? ` hash=${warning.hash}` : "";
4947
+ sink({
4948
+ code: warning.code,
4949
+ hash: warning.hash,
4950
+ sunsetAt: warning.sunsetAt,
4951
+ message: `${warning.code}:${hash}${sunset}`.trim()
4952
+ });
4953
+ }
4954
+ }
4955
+ function emptyStats() {
4956
+ return {
4957
+ rowsScanned: 0,
4958
+ rowsReturned: 0,
4959
+ segmentsAccessed: 0,
4960
+ executionMs: 0
4961
+ };
4962
+ }
4963
+ var NamedQueriesApi = class {
4964
+ constructor(transport, database, onWarning) {
4965
+ this.transport = transport;
4966
+ this.database = database;
4967
+ this.onWarning = onWarning;
4968
+ }
4969
+ async execute(hash, args, options) {
4970
+ if (typeof hash !== "string" || hash.trim().length === 0) {
4971
+ throw new Error("Named query hash must be a non-empty string");
4972
+ }
4973
+ const prefix = databasePath2(this.database);
4974
+ const path4 = `${prefix}/named-queries/${encodeURIComponent(hash)}/query?format=columnar`;
4975
+ const response = await this.transport.post(
4976
+ path4,
4977
+ { args: args ?? {} },
4978
+ { signal: options?.signal }
4979
+ );
4980
+ const rows = columnarToRows(response);
4981
+ raiseDeprecationWarnings(this.onWarning, response.warnings);
4982
+ return {
4983
+ rows,
4984
+ stats: response.stats,
4985
+ warnings: response.warnings
4986
+ };
4987
+ }
4988
+ async batch(items, options) {
4989
+ if (items.length === 0) {
4990
+ throw new AoudaValidationError(
4991
+ "Named query batch requires a non-empty queries array.",
4992
+ "NAMED_QUERY_BATCH_EMPTY",
4993
+ 400
4994
+ );
4995
+ }
4996
+ if (items.length > MAX_NAMED_QUERY_BATCH_SIZE) {
4997
+ throw new AoudaValidationError(
4998
+ `Named query batch exceeds ${MAX_NAMED_QUERY_BATCH_SIZE} elements.`,
4999
+ "NAMED_QUERY_BATCH_TOO_LARGE",
5000
+ 400
5001
+ );
5002
+ }
5003
+ const prefix = databasePath2(this.database);
5004
+ const path4 = `${prefix}/named-queries/batch?format=columnar`;
5005
+ const envelope = await this.transport.post(
5006
+ path4,
5007
+ { queries: items },
5008
+ { signal: options?.signal }
5009
+ );
5010
+ return (envelope.results ?? []).map((slot) => {
5011
+ if (slot.code != null && slot.code.length > 0) {
5012
+ return {
5013
+ isError: true,
5014
+ code: slot.code,
5015
+ error: slot.error
5016
+ };
5017
+ }
5018
+ const columnar = {
5019
+ columns: slot.columns ?? [],
5020
+ types: slot.types ?? [],
5021
+ data: slot.data ?? [],
5022
+ rowCount: slot.rowCount ?? 0,
5023
+ stats: slot.stats ?? emptyStats(),
5024
+ warnings: slot.warnings
5025
+ };
5026
+ const result = {
5027
+ rows: columnarToRows(columnar),
5028
+ stats: columnar.stats,
5029
+ warnings: slot.warnings
5030
+ };
5031
+ raiseDeprecationWarnings(this.onWarning, slot.warnings);
5032
+ return { isError: false, result };
5033
+ });
5034
+ }
5035
+ };
5036
+ var NamedMutationsApi = class {
5037
+ constructor(transport, database, onWarning) {
5038
+ this.transport = transport;
5039
+ this.database = database;
5040
+ this.onWarning = onWarning;
5041
+ }
5042
+ async execute(hash, args) {
5043
+ if (typeof hash !== "string" || hash.trim().length === 0) {
5044
+ throw new Error("Named mutation hash must be a non-empty string");
5045
+ }
5046
+ const prefix = databasePath2(this.database);
5047
+ const path4 = `${prefix}/named-mutations/${encodeURIComponent(hash)}/execute`;
5048
+ const wire = await this.transport.post(path4, {
5049
+ args: args ?? {}
5050
+ });
5051
+ let op = "unknown";
5052
+ let rowsAffected = 0;
5053
+ if (wire.rowsInserted != null) {
5054
+ op = "insert";
5055
+ rowsAffected = wire.rowsInserted;
5056
+ } else if (wire.rowsUpdated != null) {
5057
+ op = "update";
5058
+ rowsAffected = wire.rowsUpdated;
5059
+ } else if (wire.rowsDeleted != null) {
5060
+ op = "delete";
5061
+ rowsAffected = wire.rowsDeleted;
5062
+ }
5063
+ raiseDeprecationWarnings(this.onWarning, wire.warnings);
5064
+ const returning = wire.rows == null ? void 0 : {
5065
+ rows: columnarToRows(wire.rows),
5066
+ stats: wire.rows.stats,
5067
+ warnings: wire.rows.warnings
5068
+ };
5069
+ return {
5070
+ op,
5071
+ rowsAffected,
5072
+ returning,
5073
+ warnings: wire.warnings
5074
+ };
5075
+ }
5076
+ };
5077
+
4881
5078
  // src/streaming/websocket-transport.ts
4882
5079
  import { decode as decodeMessagePack, encode as encodeMessagePack } from "@msgpack/msgpack";
4883
5080
  var DEFAULT_PING_INTERVAL_MS = 2e4;
@@ -5640,6 +5837,19 @@ var AoudaClient = class {
5640
5837
  this.transport,
5641
5838
  this.database
5642
5839
  );
5840
+ const onNamedArtifactWarning = options.onNamedArtifactWarning ?? ((warning) => {
5841
+ console.warn(warning.message);
5842
+ });
5843
+ this._namedQueries = new NamedQueriesApi(
5844
+ this.transport,
5845
+ this.database,
5846
+ onNamedArtifactWarning
5847
+ );
5848
+ this._namedMutations = new NamedMutationsApi(
5849
+ this.transport,
5850
+ this.database,
5851
+ onNamedArtifactWarning
5852
+ );
5643
5853
  }
5644
5854
  /**
5645
5855
  * Connects to the Aouda server.
@@ -5782,6 +5992,18 @@ var AoudaClient = class {
5782
5992
  get materializedQueries() {
5783
5993
  return this._materializedQueries;
5784
5994
  }
5995
+ /**
5996
+ * Hash-only named-query execute and batch. Names are codegen aliases (D-5).
5997
+ */
5998
+ get namedQueries() {
5999
+ return this._namedQueries;
6000
+ }
6001
+ /**
6002
+ * Hash-only named-mutation execute. No batch.
6003
+ */
6004
+ get namedMutations() {
6005
+ return this._namedMutations;
6006
+ }
5785
6007
  /**
5786
6008
  * Access auth operations (signUp, signIn, signOut, refresh, me, changePassword).
5787
6009
  * @returns The auth API.
@@ -5929,7 +6151,7 @@ Usage:
5929
6151
  npx @aouda/client schema <command> [options]
5930
6152
 
5931
6153
  Commands:
5932
- generate Fetch schema from Aouda server and output TypeScript types
6154
+ generate Fetch schema from Aouda server and output TypeScript types (tables + named query/mutation hashes)
5933
6155
  schema Schema management (diff, apply, export, validate, history, seed)
5934
6156
  diff Show migration plan (desired vs current)
5935
6157
  apply Apply schema (use --allow-destructive for drops; --dry-run to preview)