@aouda/client 0.1.10 → 0.1.12

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.10",
424
+ version: "0.1.12",
425
425
  description: "Official TypeScript/JavaScript client library for Aouda",
426
426
  type: "module",
427
427
  main: "./dist/index.cjs",
@@ -659,6 +659,7 @@ var AuthHandler = class {
659
659
  this._tokenExpiry = null;
660
660
  this._authenticated = false;
661
661
  this._refreshPromise = null;
662
+ this._onAccessTokenRefreshed = null;
662
663
  this._serverUrl = serverUrl.replace(/\/+$/, "");
663
664
  this._timeout = timeout;
664
665
  this._authBasePath = config.basePath;
@@ -908,10 +909,16 @@ var AuthHandler = class {
908
909
  const thresholdSeconds = this._refreshThresholdMs / 1e3;
909
910
  return this._tokenExpiry <= nowSeconds + thresholdSeconds;
910
911
  }
912
+ setOnAccessTokenRefreshed(handler) {
913
+ this._onAccessTokenRefreshed = handler;
914
+ }
911
915
  async _doRefresh() {
912
916
  try {
913
917
  const result = await this.callRefreshEndpoint();
914
918
  this.handleSignInResult(result);
919
+ if (result.accessToken) {
920
+ this._onAccessTokenRefreshed?.(result.accessToken);
921
+ }
915
922
  return true;
916
923
  } catch {
917
924
  return false;
@@ -2407,20 +2414,22 @@ var AsyncEventQueue = class {
2407
2414
  }
2408
2415
  };
2409
2416
  var TableSubscription = class {
2410
- constructor(transport, tableName, options = {}, baseFilter) {
2417
+ constructor(transport, identity, options = {}, onWarnings) {
2411
2418
  this._queue = new AsyncEventQueue();
2412
2419
  this._active = true;
2413
2420
  this._started = false;
2414
2421
  this._startPromise = null;
2415
2422
  this._lastVersion = 0;
2416
2423
  this._pendingSnapshotRows = [];
2424
+ this._forceFreshSubscribe = false;
2417
2425
  this.id = createStreamingId("sub");
2418
2426
  this._transport = transport;
2419
- this._tableName = tableName;
2420
- this._baseFilter = baseFilter;
2427
+ this._identity = identity;
2421
2428
  this._onSnapshot = options.onSnapshot;
2422
2429
  this._onChange = options.onChange;
2423
2430
  this._onError = options.onError;
2431
+ this._onWarnings = onWarnings;
2432
+ this._conflate = options.conflate;
2424
2433
  this._reconnectHandlerKey = `${this.id}::reconnect`;
2425
2434
  }
2426
2435
  get lastVersion() {
@@ -2471,7 +2480,8 @@ var TableSubscription = class {
2471
2480
  }
2472
2481
  try {
2473
2482
  this._pendingSnapshotRows = [];
2474
- const resumeFrom = this._lastVersion > 0 ? this._lastVersion : void 0;
2483
+ const resumeFrom = this._forceFreshSubscribe || this._lastVersion <= 0 ? void 0 : this._lastVersion;
2484
+ this._forceFreshSubscribe = false;
2475
2485
  await this._sendSubscribe(resumeFrom);
2476
2486
  } catch (error) {
2477
2487
  const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription reconnect failed");
@@ -2481,16 +2491,25 @@ var TableSubscription = class {
2481
2491
  async _sendSubscribe(resumeFrom) {
2482
2492
  const message = {
2483
2493
  type: "subscribe",
2484
- id: this.id,
2485
- target: this._tableName
2494
+ id: this.id
2486
2495
  };
2487
- const filter = this._baseFilter;
2488
- if (filter !== void 0) {
2489
- message.filter = filter;
2496
+ if (this._identity.kind === "named") {
2497
+ message.hash = this._identity.hash;
2498
+ if (this._identity.args !== void 0) {
2499
+ message.args = this._identity.args;
2500
+ }
2501
+ } else {
2502
+ message.target = this._identity.target;
2503
+ if (this._identity.filter !== void 0) {
2504
+ message.filter = this._identity.filter;
2505
+ }
2490
2506
  }
2491
2507
  if (resumeFrom !== void 0) {
2492
2508
  message.resume_from = resumeFrom;
2493
2509
  }
2510
+ if (this._conflate !== void 0) {
2511
+ message.conflate = this._conflate;
2512
+ }
2494
2513
  await this._transport.send(message);
2495
2514
  }
2496
2515
  _handleMessage(message) {
@@ -2522,6 +2541,9 @@ var TableSubscription = class {
2522
2541
  }
2523
2542
  _handleSnapshotComplete(message) {
2524
2543
  this._lastVersion = message.version;
2544
+ if (message.warnings != null && message.warnings.length > 0) {
2545
+ this._onWarnings?.(message.warnings);
2546
+ }
2525
2547
  const rows = this._pendingSnapshotRows;
2526
2548
  this._pendingSnapshotRows = [];
2527
2549
  this._onSnapshot?.(rows, message.version);
@@ -2551,10 +2573,19 @@ var TableSubscription = class {
2551
2573
  key: message.key,
2552
2574
  version: message.version
2553
2575
  };
2576
+ if (message.values_skipped !== void 0) {
2577
+ event.values_skipped = message.values_skipped;
2578
+ }
2554
2579
  this._onChange?.(event);
2555
2580
  this._queue.push(event);
2556
2581
  }
2557
2582
  _handleServerError(message) {
2583
+ if (message.code === "SLOW_CONSUMER") {
2584
+ this._pendingSnapshotRows = [];
2585
+ this._lastVersion = 0;
2586
+ this._forceFreshSubscribe = true;
2587
+ return;
2588
+ }
2558
2589
  this._pendingSnapshotRows = [];
2559
2590
  const error = new AoudaConnectionError(
2560
2591
  `Subscription error (${message.code}): ${message.message}`
@@ -3262,16 +3293,16 @@ var TableQuery = class _TableQuery {
3262
3293
  const whereClause = this.buildWhereClause();
3263
3294
  const queryFilter = buildSubscriptionFilter(whereClause);
3264
3295
  const mergedFilter = mergeFilterObjects(queryFilter, options.filter);
3265
- const subscription = new TableSubscription(
3266
- wsTransport,
3267
- this.tableName,
3268
- {
3269
- onSnapshot: options.onSnapshot,
3270
- onChange: options.onChange,
3271
- onError: options.onError
3272
- },
3273
- mergedFilter
3274
- );
3296
+ const subscription = new TableSubscription(wsTransport, {
3297
+ kind: "table",
3298
+ target: this.tableName,
3299
+ filter: mergedFilter
3300
+ }, {
3301
+ onSnapshot: options.onSnapshot,
3302
+ onChange: options.onChange,
3303
+ onError: options.onError,
3304
+ conflate: options.conflate
3305
+ });
3275
3306
  subscription.start();
3276
3307
  return subscription;
3277
3308
  }
@@ -3857,10 +3888,84 @@ function serializeUpdateValue(col, val) {
3857
3888
  else: toNode(cond.else)
3858
3889
  };
3859
3890
  }
3891
+ if ("$upper" in val) {
3892
+ return { type: "call", fn: "upper", args: [unaryCallArg(col, val.$upper)] };
3893
+ }
3894
+ if ("$lower" in val) {
3895
+ return { type: "call", fn: "lower", args: [unaryCallArg(col, val.$lower)] };
3896
+ }
3897
+ if ("$trim" in val) {
3898
+ return { type: "call", fn: "trim", args: [unaryCallArg(col, val.$trim)] };
3899
+ }
3900
+ if ("$concat" in val) {
3901
+ const parts = val.$concat;
3902
+ return {
3903
+ type: "call",
3904
+ fn: "concat",
3905
+ args: parts.map((p) => valueToExprNode(p))
3906
+ };
3907
+ }
3908
+ if ("$substring" in val) {
3909
+ const spec = val.$substring;
3910
+ if (Array.isArray(spec)) {
3911
+ return {
3912
+ type: "call",
3913
+ fn: "substring",
3914
+ args: [{ type: "colRef", col }, ...spec.map((p) => valueToExprNode(p))]
3915
+ };
3916
+ }
3917
+ return {
3918
+ type: "call",
3919
+ fn: "substring",
3920
+ args: [{ type: "colRef", col }, { type: "literal", value: spec }]
3921
+ };
3922
+ }
3923
+ if ("$round" in val) {
3924
+ return {
3925
+ type: "call",
3926
+ fn: "round",
3927
+ args: [
3928
+ { type: "colRef", col },
3929
+ { type: "literal", value: val.$round }
3930
+ ]
3931
+ };
3932
+ }
3933
+ if ("$roundTo" in val) {
3934
+ return {
3935
+ type: "call",
3936
+ fn: "roundTo",
3937
+ args: [
3938
+ { type: "colRef", col },
3939
+ { type: "literal", value: val.$roundTo }
3940
+ ]
3941
+ };
3942
+ }
3943
+ if ("$cast" in val) {
3944
+ return {
3945
+ type: "call",
3946
+ fn: "cast",
3947
+ args: [
3948
+ { type: "colRef", col },
3949
+ { type: "literal", value: val.$cast }
3950
+ ]
3951
+ };
3952
+ }
3860
3953
  throw new Error(
3861
3954
  `Unknown expression operator in update() for column '${col}': ${JSON.stringify(val)}`
3862
3955
  );
3863
3956
  }
3957
+ function unaryCallArg(col, operand) {
3958
+ if (operand === true || operand === 1) {
3959
+ return { type: "colRef", col };
3960
+ }
3961
+ return valueToExprNode(operand);
3962
+ }
3963
+ function valueToExprNode(v) {
3964
+ if (typeof v === "string" && v.startsWith("$")) {
3965
+ return { type: "colRef", col: v.slice(1) };
3966
+ }
3967
+ return { type: "literal", value: v };
3968
+ }
3864
3969
 
3865
3970
  // src/tables.ts
3866
3971
  function validateNonEmptyString(value, name) {
@@ -4990,10 +5095,11 @@ function emptyStats() {
4990
5095
  };
4991
5096
  }
4992
5097
  var NamedQueriesApi = class {
4993
- constructor(transport, database, onWarning) {
5098
+ constructor(transport, database, onWarning, getStreamingTransport) {
4994
5099
  this.transport = transport;
4995
5100
  this.database = database;
4996
5101
  this.onWarning = onWarning;
5102
+ this.getStreamingTransport = getStreamingTransport;
4997
5103
  }
4998
5104
  async execute(hash, args, options) {
4999
5105
  if (typeof hash !== "string" || hash.trim().length === 0) {
@@ -5061,6 +5167,26 @@ var NamedQueriesApi = class {
5061
5167
  return { isError: false, result };
5062
5168
  });
5063
5169
  }
5170
+ subscribe(hash, args, options = {}) {
5171
+ if (typeof hash !== "string" || hash.trim().length === 0) {
5172
+ throw new Error("Named query hash must be a non-empty string");
5173
+ }
5174
+ const subscription = new TableSubscription(
5175
+ this.getStreamingTransport(),
5176
+ { kind: "named", hash, args },
5177
+ {
5178
+ onSnapshot: options.onSnapshot,
5179
+ onChange: options.onChange,
5180
+ onError: options.onError,
5181
+ conflate: options.conflate
5182
+ },
5183
+ (warnings) => {
5184
+ raiseDeprecationWarnings(this.onWarning, warnings);
5185
+ }
5186
+ );
5187
+ subscription.start();
5188
+ return subscription;
5189
+ }
5064
5190
  };
5065
5191
  var NamedMutationsApi = class {
5066
5192
  constructor(transport, database, onWarning) {
@@ -5128,6 +5254,7 @@ var WebSocketTransport = class {
5128
5254
  // Resolved/rejected when the auth handshake completes.
5129
5255
  this._authResolve = null;
5130
5256
  this._authReject = null;
5257
+ this._handshakeComplete = false;
5131
5258
  this._serverUrl = serverUrl;
5132
5259
  this._database = database;
5133
5260
  this._authHandler = options.authHandler;
@@ -5137,6 +5264,11 @@ var WebSocketTransport = class {
5137
5264
  this._maxMissedHeartbeats = options.maxMissedHeartbeats ?? DEFAULT_MAX_MISSED_HEARTBEATS;
5138
5265
  this._enableCompression = options.enableCompression ?? true;
5139
5266
  this._wireMode = options.wireMode ?? "json";
5267
+ this._authHandler?.setOnAccessTokenRefreshed((token) => {
5268
+ if (this._handshakeComplete && this._ws?.readyState === WS_READY_STATE_OPEN) {
5269
+ void this.send({ type: "re_auth", token });
5270
+ }
5271
+ });
5140
5272
  }
5141
5273
  // ─── Public API ────────────────────────────────────────────────────────────
5142
5274
  /** Latest version number from the most recent server `heartbeat` message. */
@@ -5309,6 +5441,7 @@ var WebSocketTransport = class {
5309
5441
  const resolve4 = this._authResolve;
5310
5442
  this._authResolve = null;
5311
5443
  this._authReject = null;
5444
+ this._handshakeComplete = true;
5312
5445
  if (resolve4) resolve4();
5313
5446
  break;
5314
5447
  }
@@ -5335,10 +5468,15 @@ var WebSocketTransport = class {
5335
5468
  break;
5336
5469
  }
5337
5470
  const id = "id" in msg ? msg.id : void 0;
5338
- if (id !== void 0 && id !== null) {
5471
+ if (id !== void 0 && id !== null && id !== "") {
5339
5472
  this._handlers.get(id)?.(msg);
5340
5473
  } else {
5341
5474
  this._handlers.get("__global__")?.(msg);
5475
+ for (const [key, handler] of this._handlers) {
5476
+ if (key !== "__global__") {
5477
+ handler(msg);
5478
+ }
5479
+ }
5342
5480
  }
5343
5481
  }
5344
5482
  // ─── Internal: reconnect ──────────────────────────────────────────────────
@@ -5872,7 +6010,8 @@ var AoudaClient = class {
5872
6010
  this._namedQueries = new NamedQueriesApi(
5873
6011
  this.transport,
5874
6012
  this.database,
5875
- onNamedArtifactWarning
6013
+ onNamedArtifactWarning,
6014
+ () => this._getOrCreateWebSocketTransport()
5876
6015
  );
5877
6016
  this._namedMutations = new NamedMutationsApi(
5878
6017
  this.transport,