@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.
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.9",
89
+ version: "0.1.11",
87
90
  description: "Official TypeScript/JavaScript client library for Aouda",
88
91
  type: "module",
89
92
  main: "./dist/index.cjs",
@@ -354,6 +357,7 @@ var AuthHandler = class {
354
357
  this._tokenExpiry = null;
355
358
  this._authenticated = false;
356
359
  this._refreshPromise = null;
360
+ this._onAccessTokenRefreshed = null;
357
361
  this._serverUrl = serverUrl.replace(/\/+$/, "");
358
362
  this._timeout = timeout;
359
363
  this._authBasePath = config.basePath;
@@ -603,10 +607,16 @@ var AuthHandler = class {
603
607
  const thresholdSeconds = this._refreshThresholdMs / 1e3;
604
608
  return this._tokenExpiry <= nowSeconds + thresholdSeconds;
605
609
  }
610
+ setOnAccessTokenRefreshed(handler) {
611
+ this._onAccessTokenRefreshed = handler;
612
+ }
606
613
  async _doRefresh() {
607
614
  try {
608
615
  const result = await this.callRefreshEndpoint();
609
616
  this.handleSignInResult(result);
617
+ if (result.accessToken) {
618
+ this._onAccessTokenRefreshed?.(result.accessToken);
619
+ }
610
620
  return true;
611
621
  } catch {
612
622
  return false;
@@ -1207,7 +1217,15 @@ var ERROR_CODE_MAP = {
1207
1217
  AUTH_TOKEN_INVALID: AoudaAuthenticationError,
1208
1218
  AUTH_TOKEN_REVOKED: AoudaAuthenticationError,
1209
1219
  AUTH_API_KEY_INVALID: AoudaAuthenticationError,
1210
- AUTH_REQUIRED: AoudaAuthenticationError
1220
+ AUTH_REQUIRED: AoudaAuthenticationError,
1221
+ NAMED_QUERY_NOT_FOUND: AoudaNotFoundError,
1222
+ NAMED_MUTATION_NOT_FOUND: AoudaNotFoundError,
1223
+ NAMED_QUERY_BATCH_EMPTY: AoudaValidationError,
1224
+ NAMED_QUERY_BATCH_TOO_LARGE: AoudaValidationError,
1225
+ NAMED_QUERY_BATCH_MUTATION: AoudaValidationError,
1226
+ NAMED_QUERY_BIND_FAILED: AoudaValidationError,
1227
+ NAMED_QUERY_PARAM_REQUIRED: AoudaValidationError,
1228
+ NAMED_MUTATION_BIND_FAILED: AoudaValidationError
1211
1229
  };
1212
1230
  function createComposedAbortController(...signals) {
1213
1231
  const controller = new AbortController();
@@ -2100,6 +2118,8 @@ var TableSubscription = class {
2100
2118
  this._started = false;
2101
2119
  this._startPromise = null;
2102
2120
  this._lastVersion = 0;
2121
+ this._pendingSnapshotRows = [];
2122
+ this._forceFreshSubscribe = false;
2103
2123
  this.id = createStreamingId("sub");
2104
2124
  this._transport = transport;
2105
2125
  this._tableName = tableName;
@@ -2107,6 +2127,7 @@ var TableSubscription = class {
2107
2127
  this._onSnapshot = options.onSnapshot;
2108
2128
  this._onChange = options.onChange;
2109
2129
  this._onError = options.onError;
2130
+ this._conflate = options.conflate;
2110
2131
  this._reconnectHandlerKey = `${this.id}::reconnect`;
2111
2132
  }
2112
2133
  get lastVersion() {
@@ -2156,7 +2177,9 @@ var TableSubscription = class {
2156
2177
  return;
2157
2178
  }
2158
2179
  try {
2159
- const resumeFrom = this._lastVersion > 0 ? this._lastVersion : void 0;
2180
+ this._pendingSnapshotRows = [];
2181
+ const resumeFrom = this._forceFreshSubscribe || this._lastVersion <= 0 ? void 0 : this._lastVersion;
2182
+ this._forceFreshSubscribe = false;
2160
2183
  await this._sendSubscribe(resumeFrom);
2161
2184
  } catch (error) {
2162
2185
  const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription reconnect failed");
@@ -2176,6 +2199,9 @@ var TableSubscription = class {
2176
2199
  if (resumeFrom !== void 0) {
2177
2200
  message.resume_from = resumeFrom;
2178
2201
  }
2202
+ if (this._conflate !== void 0) {
2203
+ message.conflate = this._conflate;
2204
+ }
2179
2205
  await this._transport.send(message);
2180
2206
  }
2181
2207
  _handleMessage(message) {
@@ -2184,7 +2210,13 @@ var TableSubscription = class {
2184
2210
  }
2185
2211
  switch (message.type) {
2186
2212
  case "snapshot":
2187
- this._handleSnapshot(message);
2213
+ this._handleSnapshotPage(message);
2214
+ return;
2215
+ case "snapshot_complete":
2216
+ this._handleSnapshotComplete(message);
2217
+ return;
2218
+ case "gap":
2219
+ void this._handleGap(message);
2188
2220
  return;
2189
2221
  case "change":
2190
2222
  this._handleChange(message);
@@ -2196,9 +2228,13 @@ var TableSubscription = class {
2196
2228
  return;
2197
2229
  }
2198
2230
  }
2199
- _handleSnapshot(message) {
2231
+ _handleSnapshotPage(message) {
2232
+ this._pendingSnapshotRows.push(...message.rows);
2233
+ }
2234
+ _handleSnapshotComplete(message) {
2200
2235
  this._lastVersion = message.version;
2201
- const rows = message.rows;
2236
+ const rows = this._pendingSnapshotRows;
2237
+ this._pendingSnapshotRows = [];
2202
2238
  this._onSnapshot?.(rows, message.version);
2203
2239
  this._queue.push({
2204
2240
  type: "snapshot",
@@ -2206,6 +2242,16 @@ var TableSubscription = class {
2206
2242
  version: message.version
2207
2243
  });
2208
2244
  }
2245
+ async _handleGap(message) {
2246
+ this._pendingSnapshotRows = [];
2247
+ this._lastVersion = message.last_seq;
2248
+ try {
2249
+ await this._sendSubscribe(message.last_seq);
2250
+ } catch (error) {
2251
+ const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription gap resume failed");
2252
+ this._notifyError(wrapped);
2253
+ }
2254
+ }
2209
2255
  _handleChange(message) {
2210
2256
  this._lastVersion = message.version;
2211
2257
  const event = {
@@ -2216,10 +2262,20 @@ var TableSubscription = class {
2216
2262
  key: message.key,
2217
2263
  version: message.version
2218
2264
  };
2265
+ if (message.values_skipped !== void 0) {
2266
+ event.values_skipped = message.values_skipped;
2267
+ }
2219
2268
  this._onChange?.(event);
2220
2269
  this._queue.push(event);
2221
2270
  }
2222
2271
  _handleServerError(message) {
2272
+ if (message.code === "SLOW_CONSUMER") {
2273
+ this._pendingSnapshotRows = [];
2274
+ this._lastVersion = 0;
2275
+ this._forceFreshSubscribe = true;
2276
+ return;
2277
+ }
2278
+ this._pendingSnapshotRows = [];
2223
2279
  const error = new AoudaConnectionError(
2224
2280
  `Subscription error (${message.code}): ${message.message}`
2225
2281
  );
@@ -2932,7 +2988,8 @@ var TableQuery = class _TableQuery {
2932
2988
  {
2933
2989
  onSnapshot: options.onSnapshot,
2934
2990
  onChange: options.onChange,
2935
- onError: options.onError
2991
+ onError: options.onError,
2992
+ conflate: options.conflate
2936
2993
  },
2937
2994
  mergedFilter
2938
2995
  );
@@ -4658,6 +4715,147 @@ var MaterializedQueriesApi = class {
4658
4715
  }
4659
4716
  };
4660
4717
 
4718
+ // src/named-queries.ts
4719
+ var MAX_NAMED_QUERY_BATCH_SIZE = 32;
4720
+ function raiseDeprecationWarnings(sink, warnings) {
4721
+ if (warnings == null) return;
4722
+ for (const warning of warnings) {
4723
+ if (warning.code !== "NAMED_QUERY_DEPRECATED" && warning.code !== "NAMED_MUTATION_DEPRECATED") {
4724
+ continue;
4725
+ }
4726
+ const sunset = warning.sunsetAt != null ? ` sunsetAt=${warning.sunsetAt}` : "";
4727
+ const hash = warning.hash != null && warning.hash.length > 0 ? ` hash=${warning.hash}` : "";
4728
+ sink({
4729
+ code: warning.code,
4730
+ hash: warning.hash,
4731
+ sunsetAt: warning.sunsetAt,
4732
+ message: `${warning.code}:${hash}${sunset}`.trim()
4733
+ });
4734
+ }
4735
+ }
4736
+ function emptyStats() {
4737
+ return {
4738
+ rowsScanned: 0,
4739
+ rowsReturned: 0,
4740
+ segmentsAccessed: 0,
4741
+ executionMs: 0
4742
+ };
4743
+ }
4744
+ var NamedQueriesApi = class {
4745
+ constructor(transport, database, onWarning) {
4746
+ this.transport = transport;
4747
+ this.database = database;
4748
+ this.onWarning = onWarning;
4749
+ }
4750
+ async execute(hash, args, options) {
4751
+ if (typeof hash !== "string" || hash.trim().length === 0) {
4752
+ throw new Error("Named query hash must be a non-empty string");
4753
+ }
4754
+ const prefix = databasePath2(this.database);
4755
+ const path = `${prefix}/named-queries/${encodeURIComponent(hash)}/query?format=columnar`;
4756
+ const response = await this.transport.post(
4757
+ path,
4758
+ { args: args ?? {} },
4759
+ { signal: options?.signal }
4760
+ );
4761
+ const rows = columnarToRows(response);
4762
+ raiseDeprecationWarnings(this.onWarning, response.warnings);
4763
+ return {
4764
+ rows,
4765
+ stats: response.stats,
4766
+ warnings: response.warnings
4767
+ };
4768
+ }
4769
+ async batch(items, options) {
4770
+ if (items.length === 0) {
4771
+ throw new AoudaValidationError(
4772
+ "Named query batch requires a non-empty queries array.",
4773
+ "NAMED_QUERY_BATCH_EMPTY",
4774
+ 400
4775
+ );
4776
+ }
4777
+ if (items.length > MAX_NAMED_QUERY_BATCH_SIZE) {
4778
+ throw new AoudaValidationError(
4779
+ `Named query batch exceeds ${MAX_NAMED_QUERY_BATCH_SIZE} elements.`,
4780
+ "NAMED_QUERY_BATCH_TOO_LARGE",
4781
+ 400
4782
+ );
4783
+ }
4784
+ const prefix = databasePath2(this.database);
4785
+ const path = `${prefix}/named-queries/batch?format=columnar`;
4786
+ const envelope = await this.transport.post(
4787
+ path,
4788
+ { queries: items },
4789
+ { signal: options?.signal }
4790
+ );
4791
+ return (envelope.results ?? []).map((slot) => {
4792
+ if (slot.code != null && slot.code.length > 0) {
4793
+ return {
4794
+ isError: true,
4795
+ code: slot.code,
4796
+ error: slot.error
4797
+ };
4798
+ }
4799
+ const columnar = {
4800
+ columns: slot.columns ?? [],
4801
+ types: slot.types ?? [],
4802
+ data: slot.data ?? [],
4803
+ rowCount: slot.rowCount ?? 0,
4804
+ stats: slot.stats ?? emptyStats(),
4805
+ warnings: slot.warnings
4806
+ };
4807
+ const result = {
4808
+ rows: columnarToRows(columnar),
4809
+ stats: columnar.stats,
4810
+ warnings: slot.warnings
4811
+ };
4812
+ raiseDeprecationWarnings(this.onWarning, slot.warnings);
4813
+ return { isError: false, result };
4814
+ });
4815
+ }
4816
+ };
4817
+ var NamedMutationsApi = class {
4818
+ constructor(transport, database, onWarning) {
4819
+ this.transport = transport;
4820
+ this.database = database;
4821
+ this.onWarning = onWarning;
4822
+ }
4823
+ async execute(hash, args) {
4824
+ if (typeof hash !== "string" || hash.trim().length === 0) {
4825
+ throw new Error("Named mutation hash must be a non-empty string");
4826
+ }
4827
+ const prefix = databasePath2(this.database);
4828
+ const path = `${prefix}/named-mutations/${encodeURIComponent(hash)}/execute`;
4829
+ const wire = await this.transport.post(path, {
4830
+ args: args ?? {}
4831
+ });
4832
+ let op = "unknown";
4833
+ let rowsAffected = 0;
4834
+ if (wire.rowsInserted != null) {
4835
+ op = "insert";
4836
+ rowsAffected = wire.rowsInserted;
4837
+ } else if (wire.rowsUpdated != null) {
4838
+ op = "update";
4839
+ rowsAffected = wire.rowsUpdated;
4840
+ } else if (wire.rowsDeleted != null) {
4841
+ op = "delete";
4842
+ rowsAffected = wire.rowsDeleted;
4843
+ }
4844
+ raiseDeprecationWarnings(this.onWarning, wire.warnings);
4845
+ const returning = wire.rows == null ? void 0 : {
4846
+ rows: columnarToRows(wire.rows),
4847
+ stats: wire.rows.stats,
4848
+ warnings: wire.rows.warnings
4849
+ };
4850
+ return {
4851
+ op,
4852
+ rowsAffected,
4853
+ returning,
4854
+ warnings: wire.warnings
4855
+ };
4856
+ }
4857
+ };
4858
+
4661
4859
  // src/streaming/websocket-transport.ts
4662
4860
  var import_msgpack = require("@msgpack/msgpack");
4663
4861
  var DEFAULT_PING_INTERVAL_MS = 2e4;
@@ -4682,6 +4880,7 @@ var WebSocketTransport = class {
4682
4880
  // Resolved/rejected when the auth handshake completes.
4683
4881
  this._authResolve = null;
4684
4882
  this._authReject = null;
4883
+ this._handshakeComplete = false;
4685
4884
  this._serverUrl = serverUrl;
4686
4885
  this._database = database;
4687
4886
  this._authHandler = options.authHandler;
@@ -4691,6 +4890,11 @@ var WebSocketTransport = class {
4691
4890
  this._maxMissedHeartbeats = options.maxMissedHeartbeats ?? DEFAULT_MAX_MISSED_HEARTBEATS;
4692
4891
  this._enableCompression = options.enableCompression ?? true;
4693
4892
  this._wireMode = options.wireMode ?? "json";
4893
+ this._authHandler?.setOnAccessTokenRefreshed((token) => {
4894
+ if (this._handshakeComplete && this._ws?.readyState === WS_READY_STATE_OPEN) {
4895
+ void this.send({ type: "re_auth", token });
4896
+ }
4897
+ });
4694
4898
  }
4695
4899
  // ─── Public API ────────────────────────────────────────────────────────────
4696
4900
  /** Latest version number from the most recent server `heartbeat` message. */
@@ -4863,6 +5067,7 @@ var WebSocketTransport = class {
4863
5067
  const resolve = this._authResolve;
4864
5068
  this._authResolve = null;
4865
5069
  this._authReject = null;
5070
+ this._handshakeComplete = true;
4866
5071
  if (resolve) resolve();
4867
5072
  break;
4868
5073
  }
@@ -4889,10 +5094,15 @@ var WebSocketTransport = class {
4889
5094
  break;
4890
5095
  }
4891
5096
  const id = "id" in msg ? msg.id : void 0;
4892
- if (id !== void 0 && id !== null) {
5097
+ if (id !== void 0 && id !== null && id !== "") {
4893
5098
  this._handlers.get(id)?.(msg);
4894
5099
  } else {
4895
5100
  this._handlers.get("__global__")?.(msg);
5101
+ for (const [key, handler] of this._handlers) {
5102
+ if (key !== "__global__") {
5103
+ handler(msg);
5104
+ }
5105
+ }
4896
5106
  }
4897
5107
  }
4898
5108
  // ─── Internal: reconnect ──────────────────────────────────────────────────
@@ -5420,6 +5630,19 @@ var AoudaClient = class {
5420
5630
  this.transport,
5421
5631
  this.database
5422
5632
  );
5633
+ const onNamedArtifactWarning = options.onNamedArtifactWarning ?? ((warning) => {
5634
+ console.warn(warning.message);
5635
+ });
5636
+ this._namedQueries = new NamedQueriesApi(
5637
+ this.transport,
5638
+ this.database,
5639
+ onNamedArtifactWarning
5640
+ );
5641
+ this._namedMutations = new NamedMutationsApi(
5642
+ this.transport,
5643
+ this.database,
5644
+ onNamedArtifactWarning
5645
+ );
5423
5646
  }
5424
5647
  /**
5425
5648
  * Connects to the Aouda server.
@@ -5562,6 +5785,18 @@ var AoudaClient = class {
5562
5785
  get materializedQueries() {
5563
5786
  return this._materializedQueries;
5564
5787
  }
5788
+ /**
5789
+ * Hash-only named-query execute and batch. Names are codegen aliases (D-5).
5790
+ */
5791
+ get namedQueries() {
5792
+ return this._namedQueries;
5793
+ }
5794
+ /**
5795
+ * Hash-only named-mutation execute. No batch.
5796
+ */
5797
+ get namedMutations() {
5798
+ return this._namedMutations;
5799
+ }
5565
5800
  /**
5566
5801
  * Access auth operations (signUp, signIn, signOut, refresh, me, changePassword).
5567
5802
  * @returns The auth API.
@@ -5983,6 +6218,8 @@ var version = package_default.version;
5983
6218
  MaterializedQueryState,
5984
6219
  MaterializedQueryType,
5985
6220
  MetricsAdminApi,
6221
+ NamedMutationsApi,
6222
+ NamedQueriesApi,
5986
6223
  NodeAdminApi,
5987
6224
  ReplicationAdminApi,
5988
6225
  RetryPolicy,
@@ -5993,6 +6230,7 @@ var version = package_default.version;
5993
6230
  WhereGroupBuilder,
5994
6231
  applyLocalNetworkAccess,
5995
6232
  coerceColumnarValue,
6233
+ columnarToRows,
5996
6234
  createAoudaClient,
5997
6235
  createAoudaClusterMcpToolSet,
5998
6236
  installLocalNetworkFetch,