@aouda/client 0.1.7 → 0.1.9

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.
@@ -421,7 +421,7 @@ var import_node_url = require("url");
421
421
  // package.json
422
422
  var package_default = {
423
423
  name: "@aouda/client",
424
- version: "0.1.7",
424
+ version: "0.1.9",
425
425
  description: "Official TypeScript/JavaScript client library for Aouda",
426
426
  type: "module",
427
427
  main: "./dist/index.cjs",
@@ -536,12 +536,13 @@ var AoudaResponseError = class extends AoudaError {
536
536
  }
537
537
  };
538
538
  var AoudaApiError = class extends AoudaError {
539
- constructor(message, code, statusCode, details, requestId) {
539
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
540
540
  super(message);
541
541
  this.code = code;
542
542
  this.statusCode = statusCode;
543
543
  this.details = details;
544
544
  this.requestId = requestId;
545
+ this.retryAfterSeconds = retryAfterSeconds;
545
546
  this.name = "AoudaApiError";
546
547
  }
547
548
  };
@@ -564,8 +565,8 @@ var AoudaValidationError = class extends AoudaApiError {
564
565
  }
565
566
  };
566
567
  var AoudaServerError = class extends AoudaApiError {
567
- constructor(message, code, statusCode, details, requestId) {
568
- super(message, code, statusCode, details, requestId);
568
+ constructor(message, code, statusCode, details, requestId, retryAfterSeconds) {
569
+ super(message, code, statusCode, details, requestId, retryAfterSeconds);
569
570
  this.name = "AoudaServerError";
570
571
  }
571
572
  };
@@ -1504,6 +1505,8 @@ var ERROR_CODE_MAP = {
1504
1505
  SERVICE_UNAVAILABLE: AoudaServerError,
1505
1506
  TIMEOUT: AoudaServerError,
1506
1507
  OVERLOADED: AoudaServerError,
1508
+ MEMORY_BUDGET_EXCEEDED: AoudaServerError,
1509
+ WAL_CAPACITY_EXCEEDED: AoudaServerError,
1507
1510
  AUTH_TOKEN_MISSING: AoudaAuthenticationError,
1508
1511
  AUTH_TOKEN_EXPIRED: AoudaAuthenticationError,
1509
1512
  AUTH_TOKEN_INVALID: AoudaAuthenticationError,
@@ -1531,13 +1534,22 @@ function createComposedAbortController(...signals) {
1531
1534
  }
1532
1535
  return controller;
1533
1536
  }
1534
- function createApiError(statusCode, statusText, body) {
1537
+ function parseRetryAfterSeconds(header) {
1538
+ if (header == null || header.trim() === "") return void 0;
1539
+ const trimmed = header.trim();
1540
+ if (!/^\d+$/.test(trimmed)) return void 0;
1541
+ const n = Number.parseInt(trimmed, 10);
1542
+ if (!Number.isFinite(n) || n < 0) return void 0;
1543
+ return n;
1544
+ }
1545
+ function createApiError(statusCode, statusText, body, retryAfterHeader) {
1535
1546
  const message = body.error ?? `${statusCode} ${statusText}`;
1536
1547
  const code = body.code ?? "UNKNOWN";
1537
1548
  const details = body.details;
1538
1549
  const requestId = body.requestId;
1550
+ const retryAfterSeconds = parseRetryAfterSeconds(retryAfterHeader ?? null);
1539
1551
  const Ctor = ERROR_CODE_MAP[code] ?? AoudaApiError;
1540
- return new Ctor(message, code, statusCode, details, requestId);
1552
+ return new Ctor(message, code, statusCode, details, requestId, retryAfterSeconds);
1541
1553
  }
1542
1554
  var HttpTransport = class {
1543
1555
  constructor(options) {
@@ -1630,7 +1642,8 @@ var HttpTransport = class {
1630
1642
  throw createApiError(
1631
1643
  response.status,
1632
1644
  response.statusText,
1633
- errorBody
1645
+ errorBody,
1646
+ response.headers.get("Retry-After")
1634
1647
  );
1635
1648
  }
1636
1649
  throw new AoudaResponseError(
@@ -1740,7 +1753,8 @@ var HttpTransport = class {
1740
1753
  throw createApiError(
1741
1754
  response.status,
1742
1755
  response.statusText,
1743
- errorBody
1756
+ errorBody,
1757
+ response.headers.get("Retry-After")
1744
1758
  );
1745
1759
  }
1746
1760
  throw new AoudaResponseError(
@@ -2086,6 +2100,17 @@ var CIRCUIT_BREAKER_POLICY_DISABLED = {
2086
2100
 
2087
2101
  // src/resilience/resilient-transport.ts
2088
2102
  var REQUEST_ID_HEADER3 = "X-Request-Id";
2103
+ function isCapacityBackPressure(error) {
2104
+ return error instanceof AoudaApiError && (error.code === "MEMORY_BUDGET_EXCEEDED" || error.code === "WAL_CAPACITY_EXCEEDED");
2105
+ }
2106
+ function retryDelayMs(retryPolicy, attempt, error) {
2107
+ const backoff = calculateDelay(retryPolicy, attempt);
2108
+ if (!isCapacityBackPressure(error) || !(error instanceof AoudaApiError)) {
2109
+ return backoff;
2110
+ }
2111
+ const headerMs = error.retryAfterSeconds != null ? error.retryAfterSeconds * 1e3 : 0;
2112
+ return Math.max(backoff, headerMs);
2113
+ }
2089
2114
  function delayMs(ms, signal) {
2090
2115
  if (ms <= 0) return Promise.resolve();
2091
2116
  return new Promise((resolve4, reject) => {
@@ -2140,7 +2165,7 @@ var ResilientTransport = class {
2140
2165
  } catch (error) {
2141
2166
  lastError = error;
2142
2167
  const retryable = isRetryable(error);
2143
- if (retryable) {
2168
+ if (retryable && !isCapacityBackPressure(error)) {
2144
2169
  this.circuitBreaker.recordFailure(error);
2145
2170
  }
2146
2171
  if (!retryable) {
@@ -2150,7 +2175,7 @@ var ResilientTransport = class {
2150
2175
  if (attempt > this.retryPolicy.maxRetries) {
2151
2176
  throw error;
2152
2177
  }
2153
- const delay = calculateDelay(this.retryPolicy, attempt);
2178
+ const delay = retryDelayMs(this.retryPolicy, attempt, error);
2154
2179
  try {
2155
2180
  await delayMs(delay, config.signal);
2156
2181
  } catch {
@@ -2186,7 +2211,7 @@ var ResilientTransport = class {
2186
2211
  } catch (error) {
2187
2212
  lastError = error;
2188
2213
  const retryable = isRetryable(error);
2189
- if (retryable) {
2214
+ if (retryable && !isCapacityBackPressure(error)) {
2190
2215
  this.circuitBreaker.recordFailure(error);
2191
2216
  }
2192
2217
  if (!retryable) {
@@ -2196,7 +2221,7 @@ var ResilientTransport = class {
2196
2221
  if (attempt > this.retryPolicy.maxRetries) {
2197
2222
  throw error;
2198
2223
  }
2199
- const delay = calculateDelay(this.retryPolicy, attempt);
2224
+ const delay = retryDelayMs(this.retryPolicy, attempt, error);
2200
2225
  try {
2201
2226
  await delayMs(delay, config.signal);
2202
2227
  } catch {
@@ -3953,23 +3978,68 @@ var TablesApi = class {
3953
3978
  );
3954
3979
  }
3955
3980
  /**
3956
- * Renames a column.
3981
+ * Alters a column (type, nullable, encoder, autoIncrement, references, and/or rename).
3982
+ * PATCH /api/databases/{db}/tables/{t}/columns/{c}.
3983
+ * Omit a property to leave it unchanged. For `references`, omit unchanged; `""` or `null` clears.
3957
3984
  * @param tableName - Table name.
3958
3985
  * @param columnName - Current column name.
3959
- * @param body - Request body (database, newName).
3960
- * @returns 200 response body (column detail).
3961
- * @throws AoudaNotFoundError if table or column does not exist; AoudaApiError for WRITE_NOT_ALLOWED, etc.
3986
+ * @param body - Fields to change. `database` is injected from the client scope when omitted.
3987
+ * @returns Updated column detail.
3962
3988
  */
3963
- async renameColumn(tableName, columnName, body) {
3989
+ async alterColumn(tableName, columnName, body) {
3964
3990
  validateNonEmptyString(tableName, "Table name");
3965
3991
  validateNonEmptyString(columnName, "Column name");
3966
- validateNonEmptyString(body.newName, "New column name");
3992
+ const hasField = body.newName !== void 0 || body.type !== void 0 || body.nullable !== void 0 || body.encoder !== void 0 || body.autoIncrement !== void 0 || body.references !== void 0;
3993
+ if (!hasField) {
3994
+ throw new Error(
3995
+ "AlterColumnRequest must set at least one of: newName, type, nullable, encoder, autoIncrement, references"
3996
+ );
3997
+ }
3967
3998
  const prefix = databasePath2(this.database);
3968
3999
  const encodedTable = encodeURIComponent(tableName);
3969
4000
  const encodedColumn = encodeURIComponent(columnName);
4001
+ const requestBody = {
4002
+ database: this.database,
4003
+ ...body
4004
+ };
3970
4005
  return this.transport.patch(
3971
4006
  `${prefix}/tables/${encodedTable}/columns/${encodedColumn}`,
3972
- body
4007
+ requestBody
4008
+ );
4009
+ }
4010
+ /**
4011
+ * Renames a column (convenience wrapper over {@link alterColumn}).
4012
+ * @param tableName - Table name.
4013
+ * @param columnName - Current column name.
4014
+ * @param body - Request body with newName (database optional; injected when omitted).
4015
+ * @returns 200 response body (column detail).
4016
+ * @throws AoudaNotFoundError if table or column does not exist; AoudaApiError for WRITE_NOT_ALLOWED, etc.
4017
+ */
4018
+ async renameColumn(tableName, columnName, body) {
4019
+ validateNonEmptyString(body.newName ?? "", "New column name");
4020
+ return this.alterColumn(tableName, columnName, { newName: body.newName });
4021
+ }
4022
+ /**
4023
+ * Reorders columns in a table.
4024
+ * PUT /api/databases/{db}/tables/{t}/columns:order (204 No Content).
4025
+ * @param tableName - Table name.
4026
+ * @param columnsOrBody - Ordered column names, or a request body with `columns`.
4027
+ */
4028
+ async reorderColumns(tableName, columnsOrBody) {
4029
+ validateNonEmptyString(tableName, "Table name");
4030
+ const columns = Array.isArray(columnsOrBody) ? columnsOrBody : columnsOrBody.columns;
4031
+ if (!columns?.length) {
4032
+ throw new Error("columns array is required and must be non-empty");
4033
+ }
4034
+ const prefix = databasePath2(this.database);
4035
+ const encodedTable = encodeURIComponent(tableName);
4036
+ const requestBody = {
4037
+ database: this.database,
4038
+ columns
4039
+ };
4040
+ await this.transport.put(
4041
+ `${prefix}/tables/${encodedTable}/columns:order`,
4042
+ requestBody
3973
4043
  );
3974
4044
  }
3975
4045
  /**
@@ -4209,6 +4279,37 @@ var BranchesApi = class {
4209
4279
  }
4210
4280
  };
4211
4281
 
4282
+ // src/jobs.ts
4283
+ function validateNonEmptyString2(value, name) {
4284
+ if (typeof value !== "string" || value.trim().length === 0) {
4285
+ throw new Error(`${name} must be a non-empty string`);
4286
+ }
4287
+ }
4288
+ var JobsApi = class {
4289
+ constructor(transport, database) {
4290
+ this.transport = transport;
4291
+ this.database = database;
4292
+ }
4293
+ get prefix() {
4294
+ return `${databasePath2(this.database)}/jobs`;
4295
+ }
4296
+ /**
4297
+ * Lists jobs whose params target this database.
4298
+ */
4299
+ async list() {
4300
+ return this.transport.get(this.prefix);
4301
+ }
4302
+ /**
4303
+ * Gets a single job by id when it belongs to this database.
4304
+ * @param jobId - Job GUID string.
4305
+ */
4306
+ async get(jobId) {
4307
+ validateNonEmptyString2(jobId, "Job id");
4308
+ const encoded = encodeURIComponent(jobId);
4309
+ return this.transport.get(`${this.prefix}/${encoded}`);
4310
+ }
4311
+ };
4312
+
4212
4313
  // src/admin/server.ts
4213
4314
  var ServerAdminApi = class {
4214
4315
  constructor(transport) {
@@ -5486,7 +5587,7 @@ var DEFAULT_TIMEOUT_MS = 3e4;
5486
5587
  function normalizeBaseUrl(url) {
5487
5588
  return url.replace(/\/+$/, "");
5488
5589
  }
5489
- function validateNonEmptyString2(value, name) {
5590
+ function validateNonEmptyString3(value, name) {
5490
5591
  if (typeof value !== "string" || value.trim().length === 0) {
5491
5592
  throw new Error(`${name} must be a non-empty string`);
5492
5593
  }
@@ -5501,8 +5602,8 @@ var AoudaClient = class {
5501
5602
  constructor(options) {
5502
5603
  this.connected = false;
5503
5604
  this._wsTransport = null;
5504
- validateNonEmptyString2(options.serverUrl, "serverUrl");
5505
- validateNonEmptyString2(options.database, "database");
5605
+ validateNonEmptyString3(options.serverUrl, "serverUrl");
5606
+ validateNonEmptyString3(options.database, "database");
5506
5607
  this.baseUrl = normalizeBaseUrl(options.serverUrl);
5507
5608
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
5508
5609
  this.database = options.database.trim();
@@ -5584,6 +5685,7 @@ var AoudaClient = class {
5584
5685
  this._authHandler = null;
5585
5686
  }
5586
5687
  this._tables = new TablesApi(this.transport, this.database);
5688
+ this._jobs = new JobsApi(this.transport, this.database);
5587
5689
  this._databases = new DatabasesApi(this.transport);
5588
5690
  this._schema = new SchemaApi(this.transport, this.database);
5589
5691
  this._branches = new BranchesApi(this.transport, this.database);
@@ -5677,7 +5779,7 @@ var AoudaClient = class {
5677
5779
  * ```
5678
5780
  */
5679
5781
  table(name) {
5680
- validateNonEmptyString2(name, "Table name");
5782
+ validateNonEmptyString3(name, "Table name");
5681
5783
  return new TableQuery(
5682
5784
  this.transport,
5683
5785
  name,
@@ -5693,6 +5795,13 @@ var AoudaClient = class {
5693
5795
  get tables() {
5694
5796
  return this._tables;
5695
5797
  }
5798
+ /**
5799
+ * Access pending/background jobs for the current database (e.g. ColumnRewrite).
5800
+ * @returns The jobs API.
5801
+ */
5802
+ get jobs() {
5803
+ return this._jobs;
5804
+ }
5696
5805
  /**
5697
5806
  * Access server-level database operations (list, create, get, drop).
5698
5807
  * @returns The databases API.
@@ -5789,7 +5898,7 @@ var AoudaClient = class {
5789
5898
  * ```
5790
5899
  */
5791
5900
  bulkLoad(tableName, rows, options) {
5792
- validateNonEmptyString2(tableName, "tableName");
5901
+ validateNonEmptyString3(tableName, "tableName");
5793
5902
  return new BulkLoadCoordinator(this.transport, this.database).run(
5794
5903
  [tableName],
5795
5904
  rows,