@aouda/client 0.1.9 → 0.1.11

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-CxP9Vnc8.cjs';
1
+ import { A as AoudaClient } from '../client-CT3GdrvA.cjs';
2
2
 
3
3
  /**
4
4
  * CLI for @aouda/client.
@@ -1,4 +1,4 @@
1
- import { A as AoudaClient } from '../client-CxP9Vnc8.js';
1
+ import { A as AoudaClient } from '../client-CT3GdrvA.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.9",
395
+ version: "0.1.11",
396
396
  description: "Official TypeScript/JavaScript client library for Aouda",
397
397
  type: "module",
398
398
  main: "./dist/index.cjs",
@@ -630,6 +630,7 @@ var AuthHandler = class {
630
630
  this._tokenExpiry = null;
631
631
  this._authenticated = false;
632
632
  this._refreshPromise = null;
633
+ this._onAccessTokenRefreshed = null;
633
634
  this._serverUrl = serverUrl.replace(/\/+$/, "");
634
635
  this._timeout = timeout;
635
636
  this._authBasePath = config.basePath;
@@ -879,10 +880,16 @@ var AuthHandler = class {
879
880
  const thresholdSeconds = this._refreshThresholdMs / 1e3;
880
881
  return this._tokenExpiry <= nowSeconds + thresholdSeconds;
881
882
  }
883
+ setOnAccessTokenRefreshed(handler) {
884
+ this._onAccessTokenRefreshed = handler;
885
+ }
882
886
  async _doRefresh() {
883
887
  try {
884
888
  const result = await this.callRefreshEndpoint();
885
889
  this.handleSignInResult(result);
890
+ if (result.accessToken) {
891
+ this._onAccessTokenRefreshed?.(result.accessToken);
892
+ }
886
893
  return true;
887
894
  } catch {
888
895
  return false;
@@ -1483,7 +1490,15 @@ var ERROR_CODE_MAP = {
1483
1490
  AUTH_TOKEN_INVALID: AoudaAuthenticationError,
1484
1491
  AUTH_TOKEN_REVOKED: AoudaAuthenticationError,
1485
1492
  AUTH_API_KEY_INVALID: AoudaAuthenticationError,
1486
- AUTH_REQUIRED: AoudaAuthenticationError
1493
+ AUTH_REQUIRED: AoudaAuthenticationError,
1494
+ NAMED_QUERY_NOT_FOUND: AoudaNotFoundError,
1495
+ NAMED_MUTATION_NOT_FOUND: AoudaNotFoundError,
1496
+ NAMED_QUERY_BATCH_EMPTY: AoudaValidationError,
1497
+ NAMED_QUERY_BATCH_TOO_LARGE: AoudaValidationError,
1498
+ NAMED_QUERY_BATCH_MUTATION: AoudaValidationError,
1499
+ NAMED_QUERY_BIND_FAILED: AoudaValidationError,
1500
+ NAMED_QUERY_PARAM_REQUIRED: AoudaValidationError,
1501
+ NAMED_MUTATION_BIND_FAILED: AoudaValidationError
1487
1502
  };
1488
1503
  function createComposedAbortController(...signals) {
1489
1504
  const controller = new AbortController();
@@ -2376,6 +2391,8 @@ var TableSubscription = class {
2376
2391
  this._started = false;
2377
2392
  this._startPromise = null;
2378
2393
  this._lastVersion = 0;
2394
+ this._pendingSnapshotRows = [];
2395
+ this._forceFreshSubscribe = false;
2379
2396
  this.id = createStreamingId("sub");
2380
2397
  this._transport = transport;
2381
2398
  this._tableName = tableName;
@@ -2383,6 +2400,7 @@ var TableSubscription = class {
2383
2400
  this._onSnapshot = options.onSnapshot;
2384
2401
  this._onChange = options.onChange;
2385
2402
  this._onError = options.onError;
2403
+ this._conflate = options.conflate;
2386
2404
  this._reconnectHandlerKey = `${this.id}::reconnect`;
2387
2405
  }
2388
2406
  get lastVersion() {
@@ -2432,7 +2450,9 @@ var TableSubscription = class {
2432
2450
  return;
2433
2451
  }
2434
2452
  try {
2435
- const resumeFrom = this._lastVersion > 0 ? this._lastVersion : void 0;
2453
+ this._pendingSnapshotRows = [];
2454
+ const resumeFrom = this._forceFreshSubscribe || this._lastVersion <= 0 ? void 0 : this._lastVersion;
2455
+ this._forceFreshSubscribe = false;
2436
2456
  await this._sendSubscribe(resumeFrom);
2437
2457
  } catch (error) {
2438
2458
  const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription reconnect failed");
@@ -2452,6 +2472,9 @@ var TableSubscription = class {
2452
2472
  if (resumeFrom !== void 0) {
2453
2473
  message.resume_from = resumeFrom;
2454
2474
  }
2475
+ if (this._conflate !== void 0) {
2476
+ message.conflate = this._conflate;
2477
+ }
2455
2478
  await this._transport.send(message);
2456
2479
  }
2457
2480
  _handleMessage(message) {
@@ -2460,7 +2483,13 @@ var TableSubscription = class {
2460
2483
  }
2461
2484
  switch (message.type) {
2462
2485
  case "snapshot":
2463
- this._handleSnapshot(message);
2486
+ this._handleSnapshotPage(message);
2487
+ return;
2488
+ case "snapshot_complete":
2489
+ this._handleSnapshotComplete(message);
2490
+ return;
2491
+ case "gap":
2492
+ void this._handleGap(message);
2464
2493
  return;
2465
2494
  case "change":
2466
2495
  this._handleChange(message);
@@ -2472,9 +2501,13 @@ var TableSubscription = class {
2472
2501
  return;
2473
2502
  }
2474
2503
  }
2475
- _handleSnapshot(message) {
2504
+ _handleSnapshotPage(message) {
2505
+ this._pendingSnapshotRows.push(...message.rows);
2506
+ }
2507
+ _handleSnapshotComplete(message) {
2476
2508
  this._lastVersion = message.version;
2477
- const rows = message.rows;
2509
+ const rows = this._pendingSnapshotRows;
2510
+ this._pendingSnapshotRows = [];
2478
2511
  this._onSnapshot?.(rows, message.version);
2479
2512
  this._queue.push({
2480
2513
  type: "snapshot",
@@ -2482,6 +2515,16 @@ var TableSubscription = class {
2482
2515
  version: message.version
2483
2516
  });
2484
2517
  }
2518
+ async _handleGap(message) {
2519
+ this._pendingSnapshotRows = [];
2520
+ this._lastVersion = message.last_seq;
2521
+ try {
2522
+ await this._sendSubscribe(message.last_seq);
2523
+ } catch (error) {
2524
+ const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription gap resume failed");
2525
+ this._notifyError(wrapped);
2526
+ }
2527
+ }
2485
2528
  _handleChange(message) {
2486
2529
  this._lastVersion = message.version;
2487
2530
  const event = {
@@ -2492,10 +2535,20 @@ var TableSubscription = class {
2492
2535
  key: message.key,
2493
2536
  version: message.version
2494
2537
  };
2538
+ if (message.values_skipped !== void 0) {
2539
+ event.values_skipped = message.values_skipped;
2540
+ }
2495
2541
  this._onChange?.(event);
2496
2542
  this._queue.push(event);
2497
2543
  }
2498
2544
  _handleServerError(message) {
2545
+ if (message.code === "SLOW_CONSUMER") {
2546
+ this._pendingSnapshotRows = [];
2547
+ this._lastVersion = 0;
2548
+ this._forceFreshSubscribe = true;
2549
+ return;
2550
+ }
2551
+ this._pendingSnapshotRows = [];
2499
2552
  const error = new AoudaConnectionError(
2500
2553
  `Subscription error (${message.code}): ${message.message}`
2501
2554
  );
@@ -3208,7 +3261,8 @@ var TableQuery = class _TableQuery {
3208
3261
  {
3209
3262
  onSnapshot: options.onSnapshot,
3210
3263
  onChange: options.onChange,
3211
- onError: options.onError
3264
+ onError: options.onError,
3265
+ conflate: options.conflate
3212
3266
  },
3213
3267
  mergedFilter
3214
3268
  );
@@ -4903,6 +4957,147 @@ var MaterializedQueriesApi = class {
4903
4957
  }
4904
4958
  };
4905
4959
 
4960
+ // src/named-queries.ts
4961
+ var MAX_NAMED_QUERY_BATCH_SIZE = 32;
4962
+ function raiseDeprecationWarnings(sink, warnings) {
4963
+ if (warnings == null) return;
4964
+ for (const warning of warnings) {
4965
+ if (warning.code !== "NAMED_QUERY_DEPRECATED" && warning.code !== "NAMED_MUTATION_DEPRECATED") {
4966
+ continue;
4967
+ }
4968
+ const sunset = warning.sunsetAt != null ? ` sunsetAt=${warning.sunsetAt}` : "";
4969
+ const hash = warning.hash != null && warning.hash.length > 0 ? ` hash=${warning.hash}` : "";
4970
+ sink({
4971
+ code: warning.code,
4972
+ hash: warning.hash,
4973
+ sunsetAt: warning.sunsetAt,
4974
+ message: `${warning.code}:${hash}${sunset}`.trim()
4975
+ });
4976
+ }
4977
+ }
4978
+ function emptyStats() {
4979
+ return {
4980
+ rowsScanned: 0,
4981
+ rowsReturned: 0,
4982
+ segmentsAccessed: 0,
4983
+ executionMs: 0
4984
+ };
4985
+ }
4986
+ var NamedQueriesApi = class {
4987
+ constructor(transport, database, onWarning) {
4988
+ this.transport = transport;
4989
+ this.database = database;
4990
+ this.onWarning = onWarning;
4991
+ }
4992
+ async execute(hash, args, options) {
4993
+ if (typeof hash !== "string" || hash.trim().length === 0) {
4994
+ throw new Error("Named query hash must be a non-empty string");
4995
+ }
4996
+ const prefix = databasePath2(this.database);
4997
+ const path4 = `${prefix}/named-queries/${encodeURIComponent(hash)}/query?format=columnar`;
4998
+ const response = await this.transport.post(
4999
+ path4,
5000
+ { args: args ?? {} },
5001
+ { signal: options?.signal }
5002
+ );
5003
+ const rows = columnarToRows(response);
5004
+ raiseDeprecationWarnings(this.onWarning, response.warnings);
5005
+ return {
5006
+ rows,
5007
+ stats: response.stats,
5008
+ warnings: response.warnings
5009
+ };
5010
+ }
5011
+ async batch(items, options) {
5012
+ if (items.length === 0) {
5013
+ throw new AoudaValidationError(
5014
+ "Named query batch requires a non-empty queries array.",
5015
+ "NAMED_QUERY_BATCH_EMPTY",
5016
+ 400
5017
+ );
5018
+ }
5019
+ if (items.length > MAX_NAMED_QUERY_BATCH_SIZE) {
5020
+ throw new AoudaValidationError(
5021
+ `Named query batch exceeds ${MAX_NAMED_QUERY_BATCH_SIZE} elements.`,
5022
+ "NAMED_QUERY_BATCH_TOO_LARGE",
5023
+ 400
5024
+ );
5025
+ }
5026
+ const prefix = databasePath2(this.database);
5027
+ const path4 = `${prefix}/named-queries/batch?format=columnar`;
5028
+ const envelope = await this.transport.post(
5029
+ path4,
5030
+ { queries: items },
5031
+ { signal: options?.signal }
5032
+ );
5033
+ return (envelope.results ?? []).map((slot) => {
5034
+ if (slot.code != null && slot.code.length > 0) {
5035
+ return {
5036
+ isError: true,
5037
+ code: slot.code,
5038
+ error: slot.error
5039
+ };
5040
+ }
5041
+ const columnar = {
5042
+ columns: slot.columns ?? [],
5043
+ types: slot.types ?? [],
5044
+ data: slot.data ?? [],
5045
+ rowCount: slot.rowCount ?? 0,
5046
+ stats: slot.stats ?? emptyStats(),
5047
+ warnings: slot.warnings
5048
+ };
5049
+ const result = {
5050
+ rows: columnarToRows(columnar),
5051
+ stats: columnar.stats,
5052
+ warnings: slot.warnings
5053
+ };
5054
+ raiseDeprecationWarnings(this.onWarning, slot.warnings);
5055
+ return { isError: false, result };
5056
+ });
5057
+ }
5058
+ };
5059
+ var NamedMutationsApi = class {
5060
+ constructor(transport, database, onWarning) {
5061
+ this.transport = transport;
5062
+ this.database = database;
5063
+ this.onWarning = onWarning;
5064
+ }
5065
+ async execute(hash, args) {
5066
+ if (typeof hash !== "string" || hash.trim().length === 0) {
5067
+ throw new Error("Named mutation hash must be a non-empty string");
5068
+ }
5069
+ const prefix = databasePath2(this.database);
5070
+ const path4 = `${prefix}/named-mutations/${encodeURIComponent(hash)}/execute`;
5071
+ const wire = await this.transport.post(path4, {
5072
+ args: args ?? {}
5073
+ });
5074
+ let op = "unknown";
5075
+ let rowsAffected = 0;
5076
+ if (wire.rowsInserted != null) {
5077
+ op = "insert";
5078
+ rowsAffected = wire.rowsInserted;
5079
+ } else if (wire.rowsUpdated != null) {
5080
+ op = "update";
5081
+ rowsAffected = wire.rowsUpdated;
5082
+ } else if (wire.rowsDeleted != null) {
5083
+ op = "delete";
5084
+ rowsAffected = wire.rowsDeleted;
5085
+ }
5086
+ raiseDeprecationWarnings(this.onWarning, wire.warnings);
5087
+ const returning = wire.rows == null ? void 0 : {
5088
+ rows: columnarToRows(wire.rows),
5089
+ stats: wire.rows.stats,
5090
+ warnings: wire.rows.warnings
5091
+ };
5092
+ return {
5093
+ op,
5094
+ rowsAffected,
5095
+ returning,
5096
+ warnings: wire.warnings
5097
+ };
5098
+ }
5099
+ };
5100
+
4906
5101
  // src/streaming/websocket-transport.ts
4907
5102
  import { decode as decodeMessagePack, encode as encodeMessagePack } from "@msgpack/msgpack";
4908
5103
  var DEFAULT_PING_INTERVAL_MS = 2e4;
@@ -4927,6 +5122,7 @@ var WebSocketTransport = class {
4927
5122
  // Resolved/rejected when the auth handshake completes.
4928
5123
  this._authResolve = null;
4929
5124
  this._authReject = null;
5125
+ this._handshakeComplete = false;
4930
5126
  this._serverUrl = serverUrl;
4931
5127
  this._database = database;
4932
5128
  this._authHandler = options.authHandler;
@@ -4936,6 +5132,11 @@ var WebSocketTransport = class {
4936
5132
  this._maxMissedHeartbeats = options.maxMissedHeartbeats ?? DEFAULT_MAX_MISSED_HEARTBEATS;
4937
5133
  this._enableCompression = options.enableCompression ?? true;
4938
5134
  this._wireMode = options.wireMode ?? "json";
5135
+ this._authHandler?.setOnAccessTokenRefreshed((token) => {
5136
+ if (this._handshakeComplete && this._ws?.readyState === WS_READY_STATE_OPEN) {
5137
+ void this.send({ type: "re_auth", token });
5138
+ }
5139
+ });
4939
5140
  }
4940
5141
  // ─── Public API ────────────────────────────────────────────────────────────
4941
5142
  /** Latest version number from the most recent server `heartbeat` message. */
@@ -5108,6 +5309,7 @@ var WebSocketTransport = class {
5108
5309
  const resolve4 = this._authResolve;
5109
5310
  this._authResolve = null;
5110
5311
  this._authReject = null;
5312
+ this._handshakeComplete = true;
5111
5313
  if (resolve4) resolve4();
5112
5314
  break;
5113
5315
  }
@@ -5134,10 +5336,15 @@ var WebSocketTransport = class {
5134
5336
  break;
5135
5337
  }
5136
5338
  const id = "id" in msg ? msg.id : void 0;
5137
- if (id !== void 0 && id !== null) {
5339
+ if (id !== void 0 && id !== null && id !== "") {
5138
5340
  this._handlers.get(id)?.(msg);
5139
5341
  } else {
5140
5342
  this._handlers.get("__global__")?.(msg);
5343
+ for (const [key, handler] of this._handlers) {
5344
+ if (key !== "__global__") {
5345
+ handler(msg);
5346
+ }
5347
+ }
5141
5348
  }
5142
5349
  }
5143
5350
  // ─── Internal: reconnect ──────────────────────────────────────────────────
@@ -5665,6 +5872,19 @@ var AoudaClient = class {
5665
5872
  this.transport,
5666
5873
  this.database
5667
5874
  );
5875
+ const onNamedArtifactWarning = options.onNamedArtifactWarning ?? ((warning) => {
5876
+ console.warn(warning.message);
5877
+ });
5878
+ this._namedQueries = new NamedQueriesApi(
5879
+ this.transport,
5880
+ this.database,
5881
+ onNamedArtifactWarning
5882
+ );
5883
+ this._namedMutations = new NamedMutationsApi(
5884
+ this.transport,
5885
+ this.database,
5886
+ onNamedArtifactWarning
5887
+ );
5668
5888
  }
5669
5889
  /**
5670
5890
  * Connects to the Aouda server.
@@ -5807,6 +6027,18 @@ var AoudaClient = class {
5807
6027
  get materializedQueries() {
5808
6028
  return this._materializedQueries;
5809
6029
  }
6030
+ /**
6031
+ * Hash-only named-query execute and batch. Names are codegen aliases (D-5).
6032
+ */
6033
+ get namedQueries() {
6034
+ return this._namedQueries;
6035
+ }
6036
+ /**
6037
+ * Hash-only named-mutation execute. No batch.
6038
+ */
6039
+ get namedMutations() {
6040
+ return this._namedMutations;
6041
+ }
5810
6042
  /**
5811
6043
  * Access auth operations (signUp, signIn, signOut, refresh, me, changePassword).
5812
6044
  * @returns The auth API.
@@ -5954,7 +6186,7 @@ Usage:
5954
6186
  npx @aouda/client schema <command> [options]
5955
6187
 
5956
6188
  Commands:
5957
- generate Fetch schema from Aouda server and output TypeScript types
6189
+ generate Fetch schema from Aouda server and output TypeScript types (tables + named query/mutation hashes)
5958
6190
  schema Schema management (diff, apply, export, validate, history, seed)
5959
6191
  diff Show migration plan (desired vs current)
5960
6192
  apply Apply schema (use --allow-destructive for drops; --dry-run to preview)