@pooflabs/core 0.0.32 → 0.0.34

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.mjs CHANGED
@@ -119,10 +119,14 @@ class WebSessionManager {
119
119
  const newObj = JSON.parse(newSession);
120
120
  return { address: newObj.address, session: newObj };
121
121
  }
122
+ // Refresh failed — clear stale session to prevent retry loops
123
+ this.clearSession();
122
124
  return null;
123
125
  }
124
126
  }
125
127
  catch (err) {
128
+ // Token decode or refresh failed — clear stale session to prevent retry loops
129
+ this.clearSession();
126
130
  return null;
127
131
  }
128
132
  return { address: sessionObj.address, session: sessionObj };
@@ -3046,15 +3050,30 @@ class ServerSessionManager {
3046
3050
  constructor() {
3047
3051
  /* Private cache (lives for the life of the process) */
3048
3052
  this.session = null;
3053
+ /* Coalesce concurrent getSession() calls into a single in-flight request */
3054
+ this.pendingSession = null;
3049
3055
  }
3050
3056
  /* ---------------------------------------------- *
3051
3057
  * GET (lazy-fetch)
3052
3058
  * ---------------------------------------------- */
3053
3059
  async getSession() {
3054
- if (this.session === null) {
3055
- this.session = await createSession();
3060
+ if (this.session !== null) {
3061
+ return this.session;
3062
+ }
3063
+ // If a session creation is already in-flight, reuse that promise
3064
+ // instead of firing a second concurrent request.
3065
+ if (this.pendingSession !== null) {
3066
+ return this.pendingSession;
3056
3067
  }
3057
- return this.session;
3068
+ this.pendingSession = createSession()
3069
+ .then((session) => {
3070
+ this.session = session;
3071
+ return session;
3072
+ })
3073
+ .finally(() => {
3074
+ this.pendingSession = null;
3075
+ });
3076
+ return this.pendingSession;
3058
3077
  }
3059
3078
  /* ---------------------------------------------- *
3060
3079
  * STORE (overwrites the cached value)
@@ -3067,6 +3086,7 @@ class ServerSessionManager {
3067
3086
  * ---------------------------------------------- */
3068
3087
  clearSession() {
3069
3088
  this.session = null;
3089
+ this.pendingSession = null;
3070
3090
  }
3071
3091
  /* ---------------------------------------------- *
3072
3092
  * QUICK helpers
@@ -3369,6 +3389,150 @@ function hashForKey$1(value) {
3369
3389
  }
3370
3390
  return h.toString(36);
3371
3391
  }
3392
+ /**
3393
+ * Validates that a field name is a safe identifier (alphanumeric, underscores, dots for nested paths).
3394
+ * Prevents prompt injection via crafted field names.
3395
+ */
3396
+ function validateFieldName(field) {
3397
+ if (!/^[a-zA-Z0-9_.]+$/.test(field)) {
3398
+ throw new Error(`Invalid field name "${field}". Field names must only contain letters, numbers, underscores, and dots.`);
3399
+ }
3400
+ }
3401
+ /**
3402
+ * Parses a raw aggregation result (e.g. [{ count: 42 }] or [{ _id: null, total: 100 }])
3403
+ * into a single numeric value.
3404
+ */
3405
+ function parseAggregateValue(result) {
3406
+ if (typeof result === 'number')
3407
+ return result;
3408
+ if (Array.isArray(result)) {
3409
+ if (result.length === 0)
3410
+ return 0;
3411
+ // Multiple elements — not a collapsed aggregate result
3412
+ if (result.length > 1) {
3413
+ throw new Error(`Unexpected aggregate result: got array with ${result.length} elements. The AI may have returned full documents instead of an aggregation.`);
3414
+ }
3415
+ const first = result[0];
3416
+ if (typeof first === 'number')
3417
+ return first;
3418
+ if (first && typeof first === 'object') {
3419
+ // $count stage returns { count: N }
3420
+ if (typeof first.count === 'number')
3421
+ return first.count;
3422
+ // $group stage returns { _id: null, result: N } — expect _id + one numeric field
3423
+ const numericEntries = Object.entries(first).filter(([key, val]) => key !== '_id' && typeof val === 'number');
3424
+ if (numericEntries.length === 1)
3425
+ return numericEntries[0][1];
3426
+ }
3427
+ // Avoid leaking document contents into error messages
3428
+ const shape = first && typeof first === 'object' ? `{${Object.keys(first).join(', ')}}` : String(first);
3429
+ throw new Error(`Unexpected aggregate result shape: ${shape}. Expected {count: N} or {_id: null, <field>: N}.`);
3430
+ }
3431
+ if (result && typeof result === 'object' && typeof result.count === 'number') {
3432
+ return result.count;
3433
+ }
3434
+ throw new Error(`Unexpected aggregate result type: ${typeof result}. Expected a number, array, or object with a count field.`);
3435
+ }
3436
+ /**
3437
+ * Count items in a collection path. Returns a numeric result.
3438
+ *
3439
+ * This uses the AI query engine with a count-specific prompt prefix,
3440
+ * so TaroBase will generate a $count aggregation pipeline and return
3441
+ * just the count rather than full documents.
3442
+ *
3443
+ * IMPORTANT: This only works for collections where the read policy is "true".
3444
+ * If the read policy requires per-document checks, the server will return
3445
+ * an error because aggregate counts cannot be performed without pulling all
3446
+ * documents for access control evaluation.
3447
+ *
3448
+ * @param path - Collection path (e.g., "posts", "users/abc/comments")
3449
+ * @param opts - Optional filter prompt and overrides
3450
+ * @returns AggregateResult with the count value
3451
+ */
3452
+ async function count(path, opts = {}) {
3453
+ const prefix = 'Return ONLY the total count of matching documents. Use the $count stage to produce a field named "count". Do NOT return the documents themselves.';
3454
+ const fullPrompt = opts.prompt
3455
+ ? `${prefix} Filter: ${opts.prompt}`
3456
+ : prefix;
3457
+ const result = await get(path, { prompt: fullPrompt, bypassCache: true, _overrides: opts._overrides });
3458
+ return { value: parseAggregateValue(result) };
3459
+ }
3460
+ /**
3461
+ * Run an aggregate operation on a collection path. Returns a numeric result.
3462
+ *
3463
+ * Supported operations:
3464
+ * - count: Total number of documents
3465
+ * - uniqueCount: Number of distinct values for a field
3466
+ * - sum: Sum of a numeric field
3467
+ * - avg: Average of a numeric field
3468
+ * - min: Minimum value of a numeric field
3469
+ * - max: Maximum value of a numeric field
3470
+ *
3471
+ * IMPORTANT: This only works for collections where the read policy is "true".
3472
+ * If the read policy requires per-document checks, the server will return
3473
+ * an error because aggregate operations cannot be performed without pulling
3474
+ * all documents for access control evaluation.
3475
+ *
3476
+ * @param path - Collection path (e.g., "posts", "users/abc/comments")
3477
+ * @param operation - The aggregate operation to perform
3478
+ * @param opts - Options including optional filter prompt and field name
3479
+ * @returns AggregateResult with the computed numeric value
3480
+ */
3481
+ async function aggregate(path, operation, opts = {}) {
3482
+ let prefix;
3483
+ switch (operation) {
3484
+ case 'count':
3485
+ prefix = 'Return ONLY the total count of matching documents. Use the $count stage to produce a field named "count". Do NOT return the documents themselves.';
3486
+ break;
3487
+ case 'uniqueCount':
3488
+ if (!opts.field)
3489
+ throw new Error('aggregate "uniqueCount" requires a field option');
3490
+ validateFieldName(opts.field);
3491
+ prefix = `Return ONLY the count of unique/distinct values of the "${opts.field}" field. Use $group with _id set to "$${opts.field}" then $count to produce a field named "count". Do NOT return the documents themselves.`;
3492
+ break;
3493
+ case 'sum':
3494
+ if (!opts.field)
3495
+ throw new Error('aggregate "sum" requires a field option');
3496
+ validateFieldName(opts.field);
3497
+ prefix = `Return ONLY the sum of the "${opts.field}" field across all matching documents. Use $group with _id: null and result: { $sum: "$${opts.field}" }. Do NOT return the documents themselves.`;
3498
+ break;
3499
+ case 'avg':
3500
+ if (!opts.field)
3501
+ throw new Error('aggregate "avg" requires a field option');
3502
+ validateFieldName(opts.field);
3503
+ prefix = `Return ONLY the average of the "${opts.field}" field across all matching documents. Use $group with _id: null and result: { $avg: "$${opts.field}" }. Do NOT return the documents themselves.`;
3504
+ break;
3505
+ case 'min':
3506
+ if (!opts.field)
3507
+ throw new Error('aggregate "min" requires a field option');
3508
+ validateFieldName(opts.field);
3509
+ prefix = `Return ONLY the minimum value of the "${opts.field}" field across all matching documents. Use $group with _id: null and result: { $min: "$${opts.field}" }. Do NOT return the documents themselves.`;
3510
+ break;
3511
+ case 'max':
3512
+ if (!opts.field)
3513
+ throw new Error('aggregate "max" requires a field option');
3514
+ validateFieldName(opts.field);
3515
+ prefix = `Return ONLY the maximum value of the "${opts.field}" field across all matching documents. Use $group with _id: null and result: { $max: "$${opts.field}" }. Do NOT return the documents themselves.`;
3516
+ break;
3517
+ default:
3518
+ throw new Error(`Unsupported aggregate operation: ${operation}`);
3519
+ }
3520
+ const fullPrompt = opts.prompt
3521
+ ? `${prefix} Filter: ${opts.prompt}`
3522
+ : prefix;
3523
+ const result = await get(path, { prompt: fullPrompt, bypassCache: true, _overrides: opts._overrides });
3524
+ // For uniqueCount, the AI may return $group results without the final $count
3525
+ // stage, producing [{_id: "val1"}, {_id: "val2"}, ...]. Verify elements look
3526
+ // like $group output (only _id key) before using array length as the count.
3527
+ if (operation === 'uniqueCount' && Array.isArray(result) && result.length > 1) {
3528
+ const looksLikeGroupOutput = result.every((el) => el && typeof el === 'object' && Object.keys(el).length === 1 && '_id' in el);
3529
+ if (looksLikeGroupOutput) {
3530
+ return { value: result.length };
3531
+ }
3532
+ throw new Error(`Unexpected uniqueCount result: got ${result.length} elements that don't match $group output shape.`);
3533
+ }
3534
+ return { value: parseAggregateValue(result) };
3535
+ }
3372
3536
  async function get(path, opts = {}) {
3373
3537
  try {
3374
3538
  let normalizedPath = path.startsWith("/") ? path.slice(1) : path;
@@ -3829,13 +3993,15 @@ async function syncItems(paths, options) {
3829
3993
  const connections = new Map();
3830
3994
  const responseCache = new Map();
3831
3995
  const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
3832
- const TOKEN_REFRESH_BUFFER = 5 * 60 * 1000; // Refresh token 5 minutes before expiry
3996
+ const TOKEN_REFRESH_BUFFER = 10 * 60 * 1000; // Refresh token 10 minutes before expiry
3997
+ const TOKEN_CHECK_INTERVAL = 5 * 60 * 1000; // Check token freshness every 5 minutes
3833
3998
  // ============ WebSocket Config ============
3834
3999
  const BASE_MIN_RECONNECT_DELAY_MS = 1000;
3835
4000
  const MIN_RECONNECT_DELAY_JITTER_MS = 1000;
3836
4001
  const MAX_RECONNECT_DELAY_MS = 300000;
3837
4002
  const RECONNECT_DELAY_GROW_FACTOR = 1.8;
3838
- const MIN_BROWSER_RECONNECT_INTERVAL_MS = 30000;
4003
+ const MIN_BROWSER_RECONNECT_INTERVAL_MS = 5000;
4004
+ const MAX_AUTH_REFRESH_RETRIES = 5;
3839
4005
  const WS_CONFIG = {
3840
4006
  // Keep retrying indefinitely so long outages recover without page refresh.
3841
4007
  maxRetries: Infinity,
@@ -3890,29 +4056,53 @@ function getTokenExpirationTime(token) {
3890
4056
  return null;
3891
4057
  }
3892
4058
  }
3893
- function scheduleTokenRefresh(connection, token) {
4059
+ function scheduleTokenRefresh(connection, isServer) {
3894
4060
  // Clear any existing timer
3895
4061
  if (connection.tokenRefreshTimer) {
3896
- clearTimeout(connection.tokenRefreshTimer);
4062
+ clearInterval(connection.tokenRefreshTimer);
3897
4063
  connection.tokenRefreshTimer = null;
3898
4064
  }
3899
- if (!token) {
3900
- return; // No token = unauthenticated, no refresh needed
3901
- }
3902
- const expirationTime = getTokenExpirationTime(token);
3903
- if (!expirationTime) {
3904
- return; // Can't parse expiration
3905
- }
3906
- const refreshTime = expirationTime - TOKEN_REFRESH_BUFFER;
3907
- const delay = refreshTime - Date.now();
3908
- if (delay <= 0) {
3909
- // Token already expired or about to expire, reconnect immediately
3910
- reconnectWithNewAuthV2();
3911
- return;
3912
- }
3913
- connection.tokenRefreshTimer = setTimeout(() => {
3914
- reconnectWithNewAuthV2();
3915
- }, delay);
4065
+ // Periodic check: every TOKEN_CHECK_INTERVAL, check if the token needs refreshing.
4066
+ // This replaces the old single setTimeout approach which was unreliable for long
4067
+ // delays (browsers throttle/suspend timers in background tabs).
4068
+ connection.tokenRefreshTimer = setInterval(async () => {
4069
+ try {
4070
+ const currentToken = await getIdToken(isServer);
4071
+ if (!currentToken)
4072
+ return; // Unauthenticated, nothing to refresh
4073
+ const expirationTime = getTokenExpirationTime(currentToken);
4074
+ if (!expirationTime)
4075
+ return;
4076
+ const timeUntilExpiry = expirationTime - Date.now();
4077
+ if (timeUntilExpiry <= TOKEN_REFRESH_BUFFER) {
4078
+ console.info('[WS v2] Token expiring soon, proactively refreshing and reconnecting');
4079
+ // Refresh the token directly rather than going through getFreshAuthToken(),
4080
+ // which only refreshes when the token is within 60s of expiry. We want to
4081
+ // refresh as soon as we enter the buffer window to avoid unnecessary reconnects.
4082
+ try {
4083
+ const currentRefreshToken = await getRefreshToken(isServer);
4084
+ if (!currentRefreshToken) {
4085
+ console.warn('[WS v2] No refresh token available for proactive refresh');
4086
+ return;
4087
+ }
4088
+ const refreshData = await refreshSession(currentRefreshToken);
4089
+ if (refreshData && refreshData.idToken && refreshData.accessToken) {
4090
+ await updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken, isServer);
4091
+ reconnectWithNewAuthV2();
4092
+ }
4093
+ else {
4094
+ console.warn('[WS v2] Proactive token refresh returned incomplete data');
4095
+ }
4096
+ }
4097
+ catch (refreshError) {
4098
+ console.warn('[WS v2] Proactive token refresh failed, will retry next interval:', refreshError);
4099
+ }
4100
+ }
4101
+ }
4102
+ catch (error) {
4103
+ console.error('[WS v2] Error in periodic token check:', error);
4104
+ }
4105
+ }, TOKEN_CHECK_INTERVAL);
3916
4106
  }
3917
4107
  async function getFreshAuthToken(isServer) {
3918
4108
  const currentToken = await getIdToken(isServer);
@@ -3922,10 +4112,12 @@ async function getFreshAuthToken(isServer) {
3922
4112
  if (!isTokenExpired(currentToken)) {
3923
4113
  return currentToken;
3924
4114
  }
4115
+ // Token is expired — attempt refresh
3925
4116
  try {
3926
4117
  const refreshToken = await getRefreshToken(isServer);
3927
4118
  if (!refreshToken) {
3928
- return currentToken;
4119
+ console.warn('[WS v2] Token expired but no refresh token available');
4120
+ return null;
3929
4121
  }
3930
4122
  const refreshData = await refreshSession(refreshToken);
3931
4123
  if (refreshData && refreshData.idToken && refreshData.accessToken) {
@@ -3936,7 +4128,11 @@ async function getFreshAuthToken(isServer) {
3936
4128
  catch (error) {
3937
4129
  console.error('[WS v2] Error refreshing token:', error);
3938
4130
  }
3939
- return currentToken;
4131
+ // Return null instead of the expired token to prevent infinite 401 reconnect storms.
4132
+ // The server accepts unauthenticated connections; auth-required subscriptions will
4133
+ // receive per-subscription errors via onError callbacks.
4134
+ console.warn('[WS v2] Token refresh failed, connecting without auth to prevent reconnect storm');
4135
+ return null;
3940
4136
  }
3941
4137
  function hasDisconnectedActiveConnections() {
3942
4138
  for (const connection of connections.values()) {
@@ -4000,6 +4196,9 @@ async function getOrCreateConnection(appId, isServer) {
4000
4196
  isConnected: false,
4001
4197
  appId,
4002
4198
  tokenRefreshTimer: null,
4199
+ lastMessageAt: Date.now(),
4200
+ keepaliveTimer: null,
4201
+ consecutiveAuthFailures: 0,
4003
4202
  };
4004
4203
  connections.set(appId, connection);
4005
4204
  // URL provider for reconnection with fresh tokens
@@ -4025,6 +4224,22 @@ async function getOrCreateConnection(appId, isServer) {
4025
4224
  const authToken = await getFreshAuthToken(isServer);
4026
4225
  if (authToken) {
4027
4226
  wsUrl.searchParams.append('authorization', authToken);
4227
+ // Successful token acquisition — reset failure counter
4228
+ connection.consecutiveAuthFailures = 0;
4229
+ }
4230
+ else {
4231
+ // Check if user WAS authenticated (had a token that expired).
4232
+ // If so, retry with exponential backoff before falling back to unauthenticated.
4233
+ const expiredToken = await getIdToken(isServer);
4234
+ if (expiredToken && isTokenExpired(expiredToken)) {
4235
+ connection.consecutiveAuthFailures++;
4236
+ if (connection.consecutiveAuthFailures <= MAX_AUTH_REFRESH_RETRIES) {
4237
+ console.warn(`[WS v2] Auth refresh failed (attempt ${connection.consecutiveAuthFailures}/${MAX_AUTH_REFRESH_RETRIES}), retrying with backoff`);
4238
+ throw new Error('Auth token refresh failed, retrying with backoff');
4239
+ }
4240
+ console.warn('[WS v2] Auth refresh retries exhausted, falling back to unauthenticated connection');
4241
+ }
4242
+ // No token at all (never authenticated) or retries exhausted — connect without auth
4028
4243
  }
4029
4244
  return wsUrl.toString();
4030
4245
  };
@@ -4036,18 +4251,34 @@ async function getOrCreateConnection(appId, isServer) {
4036
4251
  ws.addEventListener('open', () => {
4037
4252
  connection.isConnecting = false;
4038
4253
  connection.isConnected = true;
4039
- // Schedule token refresh before expiry
4040
- (async () => {
4041
- const token = await getIdToken(isServer);
4042
- scheduleTokenRefresh(connection, token);
4043
- })();
4254
+ connection.lastMessageAt = Date.now();
4255
+ connection.consecutiveAuthFailures = 0;
4256
+ // Schedule periodic token freshness checks
4257
+ scheduleTokenRefresh(connection, isServer);
4044
4258
  // Re-subscribe to all existing subscriptions after reconnect
4045
4259
  for (const sub of connection.subscriptions.values()) {
4260
+ sub.lastData = undefined;
4046
4261
  sendSubscribe(connection, sub);
4047
4262
  }
4263
+ // Start keepalive detection — if no messages for 90s, force reconnect
4264
+ if (connection.keepaliveTimer) {
4265
+ clearInterval(connection.keepaliveTimer);
4266
+ }
4267
+ connection.keepaliveTimer = setInterval(() => {
4268
+ var _a;
4269
+ if (Date.now() - connection.lastMessageAt > 90000) {
4270
+ console.warn('[WS v2] No messages received for 90s, forcing reconnect');
4271
+ if (connection.keepaliveTimer) {
4272
+ clearInterval(connection.keepaliveTimer);
4273
+ connection.keepaliveTimer = null;
4274
+ }
4275
+ (_a = connection.ws) === null || _a === void 0 ? void 0 : _a.reconnect();
4276
+ }
4277
+ }, 30000);
4048
4278
  });
4049
4279
  // Handle incoming messages
4050
4280
  ws.addEventListener('message', (event) => {
4281
+ connection.lastMessageAt = Date.now();
4051
4282
  try {
4052
4283
  const message = JSON.parse(event.data);
4053
4284
  handleServerMessage(connection, message);
@@ -4059,20 +4290,22 @@ async function getOrCreateConnection(appId, isServer) {
4059
4290
  // Handle errors
4060
4291
  ws.addEventListener('error', (event) => {
4061
4292
  console.error('[WS v2] WebSocket error:', event);
4062
- // Reject all pending subscriptions
4063
- for (const [id, pending] of connection.pendingSubscriptions) {
4293
+ for (const [, pending] of connection.pendingSubscriptions) {
4064
4294
  pending.reject(new Error('WebSocket error'));
4065
- connection.pendingSubscriptions.delete(id);
4066
4295
  }
4296
+ connection.pendingSubscriptions.clear();
4067
4297
  });
4068
4298
  // Handle close
4069
4299
  ws.addEventListener('close', () => {
4070
4300
  connection.isConnected = false;
4071
- // Clear token refresh timer
4072
4301
  if (connection.tokenRefreshTimer) {
4073
- clearTimeout(connection.tokenRefreshTimer);
4302
+ clearInterval(connection.tokenRefreshTimer);
4074
4303
  connection.tokenRefreshTimer = null;
4075
4304
  }
4305
+ if (connection.keepaliveTimer) {
4306
+ clearInterval(connection.keepaliveTimer);
4307
+ connection.keepaliveTimer = null;
4308
+ }
4076
4309
  });
4077
4310
  return connection;
4078
4311
  }
@@ -4272,9 +4505,7 @@ async function subscribeV2(path, subscriptionOptions) {
4272
4505
  await subscriptionPromise;
4273
4506
  }
4274
4507
  catch (error) {
4275
- // Remove subscription on error
4276
- connection.subscriptions.delete(subscriptionId);
4277
- throw error;
4508
+ console.warn('[WS v2] Subscription confirmation failed, keeping for reconnect recovery:', error);
4278
4509
  }
4279
4510
  }
4280
4511
  // Return unsubscribe function
@@ -4314,7 +4545,7 @@ async function removeCallbackFromSubscription(connection, subscriptionId, callba
4314
4545
  if (connection.subscriptions.size === 0 && connection.ws) {
4315
4546
  // Clear token refresh timer
4316
4547
  if (connection.tokenRefreshTimer) {
4317
- clearTimeout(connection.tokenRefreshTimer);
4548
+ clearInterval(connection.tokenRefreshTimer);
4318
4549
  connection.tokenRefreshTimer = null;
4319
4550
  }
4320
4551
  connection.ws.close();
@@ -4330,7 +4561,7 @@ async function closeAllSubscriptionsV2() {
4330
4561
  for (const [appId, connection] of connections) {
4331
4562
  // Clear token refresh timer
4332
4563
  if (connection.tokenRefreshTimer) {
4333
- clearTimeout(connection.tokenRefreshTimer);
4564
+ clearInterval(connection.tokenRefreshTimer);
4334
4565
  connection.tokenRefreshTimer = null;
4335
4566
  }
4336
4567
  if (connection.ws) {
@@ -4390,8 +4621,6 @@ async function reconnectWithNewAuthV2() {
4390
4621
  if (!connection.ws) {
4391
4622
  continue;
4392
4623
  }
4393
- // Reject any pending subscriptions - they'll need to be retried after reconnect
4394
- // This prevents hanging promises during auth transitions
4395
4624
  for (const [, pending] of connection.pendingSubscriptions) {
4396
4625
  pending.reject(new Error('Connection reconnecting due to auth change'));
4397
4626
  }
@@ -4401,6 +4630,8 @@ async function reconnectWithNewAuthV2() {
4401
4630
  pending.resolve();
4402
4631
  }
4403
4632
  connection.pendingUnsubscriptions.clear();
4633
+ // Reset auth failure counter — this is a proactive reconnect (login, token refresh)
4634
+ connection.consecutiveAuthFailures = 0;
4404
4635
  // Close the WebSocket (this triggers reconnection in ReconnectingWebSocket)
4405
4636
  // We use reconnect() which will close and re-open with fresh URL (including new token)
4406
4637
  try {
@@ -4483,5 +4714,5 @@ async function reconnectWithNewAuth() {
4483
4714
  return reconnectWithNewAuthV2();
4484
4715
  }
4485
4716
 
4486
- export { ServerSessionManager, WebSessionManager, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, createSessionWithPrivy, createSessionWithSignature, genAuthNonce, genSolanaMessage, get, getCachedData, getConfig, getFiles, getIdToken, init, reconnectWithNewAuth, refreshSession, runExpression, runExpressionMany, runQuery, runQueryMany, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe };
4717
+ export { ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithPrivy, createSessionWithSignature, genAuthNonce, genSolanaMessage, get, getCachedData, getConfig, getFiles, getIdToken, init, reconnectWithNewAuth, refreshSession, runExpression, runExpressionMany, runQuery, runQueryMany, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe };
4487
4718
  //# sourceMappingURL=index.mjs.map