@pooflabs/core 0.0.28 → 0.0.30

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.
@@ -22,6 +22,11 @@ export type GetOptions = {
22
22
  headers?: Record<string, string>;
23
23
  };
24
24
  };
25
+ export type RunQueryOptions = {
26
+ _overrides?: {
27
+ headers?: Record<string, string>;
28
+ };
29
+ };
25
30
  export declare function get(path: string, opts?: GetOptions): Promise<any>;
26
31
  export type RunExpressionOptions = {
27
32
  returnType?: 'Bool' | 'String' | 'Int' | 'UInt';
@@ -38,12 +43,12 @@ export type RunExpressionResult = {
38
43
  result?: any;
39
44
  }[];
40
45
  };
41
- export declare function runQuery(absolutePath: string, queryName: string, queryArgs: any): Promise<any>;
46
+ export declare function runQuery(absolutePath: string, queryName: string, queryArgs: any, opts?: RunQueryOptions): Promise<any>;
42
47
  export declare function runQueryMany(many: {
43
48
  absolutePath: string;
44
49
  queryName: string;
45
50
  queryArgs: any;
46
- }[]): Promise<any>;
51
+ }[], opts?: RunQueryOptions): Promise<any>;
47
52
  export declare function runExpression(expression: string, queryArgs: any, options?: RunExpressionOptions): Promise<RunExpressionResult>;
48
53
  export declare function runExpressionMany(many: {
49
54
  expression: string;
package/dist/index.js CHANGED
@@ -204,6 +204,11 @@ class WebSessionManager {
204
204
  }
205
205
  WebSessionManager.TAROBASE_SESSION_STORAGE_KEY = "tarobase_session_storage";
206
206
 
207
+ var webSessionManager = /*#__PURE__*/Object.freeze({
208
+ __proto__: null,
209
+ WebSessionManager: WebSessionManager
210
+ });
211
+
207
212
  function getDefaultExportFromCjs (x) {
208
213
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
209
214
  }
@@ -3101,11 +3106,19 @@ class ServerSessionManager {
3101
3106
  /* The single, shared instance */
3102
3107
  ServerSessionManager.instance = new ServerSessionManager();
3103
3108
 
3109
+ var serverSessionManager = /*#__PURE__*/Object.freeze({
3110
+ __proto__: null,
3111
+ ServerSessionManager: ServerSessionManager
3112
+ });
3113
+
3104
3114
  async function createBearerToken(isServer) {
3105
3115
  if (isServer) {
3106
3116
  const sessionMgr = ServerSessionManager.instance;
3107
3117
  const session = await sessionMgr.getSession();
3108
- return `Bearer ${session === null || session === void 0 ? void 0 : session.idToken}`;
3118
+ if (!(session === null || session === void 0 ? void 0 : session.idToken)) {
3119
+ return null;
3120
+ }
3121
+ return `Bearer ${session.idToken}`;
3109
3122
  }
3110
3123
  const idToken = WebSessionManager.getIdToken();
3111
3124
  if (!idToken) {
@@ -3138,13 +3151,72 @@ async function getRefreshToken(isServer) {
3138
3151
  }
3139
3152
  return WebSessionManager.getRefreshToken();
3140
3153
  }
3141
- function updateIdTokenAndAccessToken(idToken, accessToken) {
3142
- WebSessionManager.updateIdTokenAndAccessToken(idToken, accessToken);
3154
+ async function updateIdTokenAndAccessToken(idToken, accessToken, isServer = false) {
3155
+ if (isServer) {
3156
+ const sessionMgr = ServerSessionManager.instance;
3157
+ // Ensure we have a session to update; if missing, let lazy creation happen on next request.
3158
+ let session = null;
3159
+ try {
3160
+ session = await sessionMgr.getSession();
3161
+ }
3162
+ catch (_a) {
3163
+ return;
3164
+ }
3165
+ if (!session) {
3166
+ return;
3167
+ }
3168
+ sessionMgr.setSession(Object.assign(Object.assign({}, session), { idToken,
3169
+ accessToken }));
3170
+ return;
3171
+ }
3172
+ await WebSessionManager.updateIdTokenAndAccessToken(idToken, accessToken);
3143
3173
  }
3144
3174
 
3175
+ const refreshInFlight = new Map();
3176
+ async function refreshAuthSessionOnce(appId, isServer) {
3177
+ const key = `${isServer ? "server" : "web"}:${appId}`;
3178
+ const existing = refreshInFlight.get(key);
3179
+ if (existing) {
3180
+ return existing;
3181
+ }
3182
+ const refreshPromise = (async () => {
3183
+ var _a, _b;
3184
+ try {
3185
+ const refreshToken = await getRefreshToken(isServer);
3186
+ if (!refreshToken) {
3187
+ return false;
3188
+ }
3189
+ const refreshData = await refreshSession(refreshToken);
3190
+ if (!(refreshData === null || refreshData === void 0 ? void 0 : refreshData.idToken) || !(refreshData === null || refreshData === void 0 ? void 0 : refreshData.accessToken)) {
3191
+ return false;
3192
+ }
3193
+ await updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken, isServer);
3194
+ return true;
3195
+ }
3196
+ catch (error) {
3197
+ if (!isServer && (((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === 401 || ((_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status) === 403)) {
3198
+ const { WebSessionManager } = await Promise.resolve().then(function () { return webSessionManager; });
3199
+ WebSessionManager.clearSession();
3200
+ }
3201
+ return false;
3202
+ }
3203
+ finally {
3204
+ refreshInFlight.delete(key);
3205
+ }
3206
+ })();
3207
+ refreshInFlight.set(key, refreshPromise);
3208
+ return refreshPromise;
3209
+ }
3145
3210
  async function makeApiRequest(method, urlPath, data, _overrides) {
3146
- var _a, _b, _c, _d, _e, _f;
3211
+ var _a, _b, _c, _d;
3147
3212
  const config = await getConfig();
3213
+ let hasRetriedAfterServerSessionReset = false;
3214
+ const clearServerSession = async () => {
3215
+ if (!config.isServer)
3216
+ return;
3217
+ const { ServerSessionManager } = await Promise.resolve().then(function () { return serverSessionManager; });
3218
+ ServerSessionManager.instance.clearSession();
3219
+ };
3148
3220
  async function executeRequest() {
3149
3221
  const authHeader = await createAuthHeader(config.isServer);
3150
3222
  const headers = Object.assign({ "Content-Type": "application/json", "X-Public-App-Id": config.appId, "X-App-Id": config.appId }, authHeader);
@@ -3176,27 +3248,34 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
3176
3248
  catch (error) {
3177
3249
  if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
3178
3250
  try {
3179
- const refreshToken = await getRefreshToken(config.isServer);
3180
- if (!refreshToken) {
3181
- throw new Error("No refresh token found");
3182
- }
3183
- const refreshData = await refreshSession(refreshToken);
3184
- if (refreshData &&
3185
- refreshData.idToken &&
3186
- refreshData.accessToken) {
3187
- updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken);
3251
+ const refreshed = await refreshAuthSessionOnce(config.appId, config.isServer);
3252
+ if (!refreshed) {
3253
+ throw new Error("Unable to refresh auth session");
3188
3254
  }
3189
3255
  return await executeRequest();
3190
3256
  }
3191
- catch (refreshError) {
3257
+ catch (_refreshError) {
3258
+ // Server-side fallback: clear cached session and retry once to force
3259
+ // createSession() with a fresh Cognito session.
3260
+ if (config.isServer && !hasRetriedAfterServerSessionReset) {
3261
+ hasRetriedAfterServerSessionReset = true;
3262
+ await clearServerSession();
3263
+ return await executeRequest();
3264
+ }
3192
3265
  throw error;
3193
3266
  }
3194
3267
  }
3195
3268
  if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) == 404) {
3196
3269
  return { data: null, status: 404 };
3197
3270
  }
3198
- if ((_d = (_c = error.response) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.message) {
3199
- throw new Error((_f = (_e = error.response) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f.message);
3271
+ if ((_c = error.response) === null || _c === void 0 ? void 0 : _c.data) {
3272
+ const responseData = typeof error.response.data === 'object'
3273
+ ? error.response.data
3274
+ : { message: String(error.response.data) };
3275
+ const apiError = new Error(responseData.message || error.message || 'Request failed');
3276
+ Object.assign(apiError, responseData);
3277
+ apiError.statusCode = (_d = error.response) === null || _d === void 0 ? void 0 : _d.status;
3278
+ throw apiError;
3200
3279
  }
3201
3280
  throw error;
3202
3281
  }
@@ -3261,6 +3340,40 @@ async function getConfig() {
3261
3340
  return clientConfig;
3262
3341
  }
3263
3342
 
3343
+ /******************************************************************************
3344
+ Copyright (c) Microsoft Corporation.
3345
+
3346
+ Permission to use, copy, modify, and/or distribute this software for any
3347
+ purpose with or without fee is hereby granted.
3348
+
3349
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
3350
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
3351
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
3352
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
3353
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3354
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3355
+ PERFORMANCE OF THIS SOFTWARE.
3356
+ ***************************************************************************** */
3357
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
3358
+
3359
+
3360
+ function __rest(s, e) {
3361
+ var t = {};
3362
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
3363
+ t[p] = s[p];
3364
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
3365
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
3366
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
3367
+ t[p[i]] = s[p[i]];
3368
+ }
3369
+ return t;
3370
+ }
3371
+
3372
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
3373
+ var e = new Error(message);
3374
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3375
+ };
3376
+
3264
3377
  // Cache for get responses
3265
3378
  const getCache = {};
3266
3379
  // Track in-flight requests to coalesce multiple identical requests
@@ -3358,11 +3471,11 @@ function cleanupExpiredCache() {
3358
3471
  });
3359
3472
  lastCacheCleanup = now;
3360
3473
  }
3361
- async function runQuery(absolutePath, queryName, queryArgs) {
3362
- const result = await runQueryMany([{ absolutePath, queryName, queryArgs }]);
3474
+ async function runQuery(absolutePath, queryName, queryArgs, opts) {
3475
+ const result = await runQueryMany([{ absolutePath, queryName, queryArgs }], opts);
3363
3476
  return result[0];
3364
3477
  }
3365
- async function runQueryMany(many) {
3478
+ async function runQueryMany(many, opts) {
3366
3479
  try {
3367
3480
  let queries = [];
3368
3481
  for (const { absolutePath, queryName, queryArgs } of many) {
@@ -3373,7 +3486,7 @@ async function runQueryMany(many) {
3373
3486
  }
3374
3487
  queries.push({ path: normalizedPath, queryName, queryArgs });
3375
3488
  }
3376
- const response = await makeApiRequest('POST', 'queries', { queries }, undefined);
3489
+ const response = await makeApiRequest('POST', 'queries', { queries }, opts === null || opts === void 0 ? void 0 : opts._overrides);
3377
3490
  return response.data.queries.map((result) => result.result);
3378
3491
  }
3379
3492
  catch (error) {
@@ -3490,6 +3603,12 @@ async function setMany(many, options) {
3490
3603
  if (setResponse.data === true) {
3491
3604
  return Object.assign(Object.assign({}, documents.map(d => d.document)), { transactionId: null });
3492
3605
  }
3606
+ else if (setResponse.data &&
3607
+ typeof setResponse.data === 'object' &&
3608
+ setResponse.data.success === true) {
3609
+ const _a = setResponse.data, { success: _success } = _a, rest = __rest(_a, ["success"]);
3610
+ return Object.assign(Object.assign(Object.assign({}, documents.map(d => d.document)), rest), { transactionId: null });
3611
+ }
3493
3612
  else {
3494
3613
  return Object.assign(Object.assign({}, setResponse.data), { transactionId: null });
3495
3614
  }
@@ -3720,14 +3839,26 @@ const responseCache = new Map();
3720
3839
  const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
3721
3840
  const TOKEN_REFRESH_BUFFER = 5 * 60 * 1000; // Refresh token 5 minutes before expiry
3722
3841
  // ============ WebSocket Config ============
3842
+ const BASE_MIN_RECONNECT_DELAY_MS = 1000;
3843
+ const MIN_RECONNECT_DELAY_JITTER_MS = 1000;
3844
+ const MAX_RECONNECT_DELAY_MS = 300000;
3845
+ const RECONNECT_DELAY_GROW_FACTOR = 1.8;
3846
+ const MIN_BROWSER_RECONNECT_INTERVAL_MS = 30000;
3723
3847
  const WS_CONFIG = {
3724
- maxRetries: 10,
3725
- minReconnectionDelay: 1000,
3726
- maxReconnectionDelay: 30000,
3727
- reconnectionDelayGrowFactor: 1.3,
3728
- connectionTimeout: 4000,
3848
+ // Keep retrying indefinitely so long outages recover without page refresh.
3849
+ maxRetries: Infinity,
3850
+ // Conservative starting delay with jitter spreads reconnects across clients.
3851
+ minReconnectionDelay: BASE_MIN_RECONNECT_DELAY_MS + Math.floor(Math.random() * MIN_RECONNECT_DELAY_JITTER_MS),
3852
+ // Stretch backoff to 5 minutes during prolonged outages to protect backend pools.
3853
+ maxReconnectionDelay: MAX_RECONNECT_DELAY_MS,
3854
+ // Moderate growth to avoid rapid retry bursts while still converging to max delay.
3855
+ reconnectionDelayGrowFactor: RECONNECT_DELAY_GROW_FACTOR,
3856
+ // Give handshakes more time before considering the attempt failed.
3857
+ connectionTimeout: 10000,
3729
3858
  };
3730
3859
  const WS_V2_PATH = '/ws/v2';
3860
+ let browserReconnectHooksAttached = false;
3861
+ let lastBrowserTriggeredReconnectAt = 0;
3731
3862
  // ============ Helper Functions ============
3732
3863
  function generateSubscriptionId() {
3733
3864
  return `sub_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
@@ -3797,7 +3928,7 @@ async function getFreshAuthToken(isServer) {
3797
3928
  }
3798
3929
  const refreshData = await refreshSession(refreshToken);
3799
3930
  if (refreshData && refreshData.idToken && refreshData.accessToken) {
3800
- updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken);
3931
+ await updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken, isServer);
3801
3932
  return refreshData.idToken;
3802
3933
  }
3803
3934
  }
@@ -3806,8 +3937,54 @@ async function getFreshAuthToken(isServer) {
3806
3937
  }
3807
3938
  return currentToken;
3808
3939
  }
3940
+ function hasDisconnectedActiveConnections() {
3941
+ for (const connection of connections.values()) {
3942
+ if (!connection.ws) {
3943
+ continue;
3944
+ }
3945
+ // Only nudge reconnection when socket is fully closed.
3946
+ // If ReconnectingWebSocket is already in CONNECTING/backoff state, don't interfere.
3947
+ if (connection.ws.readyState === WS_READY_STATE_CLOSED) {
3948
+ return true;
3949
+ }
3950
+ }
3951
+ return false;
3952
+ }
3953
+ function maybeReconnectFromBrowserSignal() {
3954
+ const now = Date.now();
3955
+ if (now - lastBrowserTriggeredReconnectAt < MIN_BROWSER_RECONNECT_INTERVAL_MS) {
3956
+ return;
3957
+ }
3958
+ if (!hasDisconnectedActiveConnections()) {
3959
+ return;
3960
+ }
3961
+ lastBrowserTriggeredReconnectAt = now;
3962
+ reconnectWithNewAuthV2().catch((error) => {
3963
+ console.error('[WS v2] Browser-triggered reconnect failed:', error);
3964
+ });
3965
+ }
3966
+ function attachBrowserReconnectHooksOnce() {
3967
+ if (browserReconnectHooksAttached) {
3968
+ return;
3969
+ }
3970
+ if (typeof window === 'undefined') {
3971
+ return;
3972
+ }
3973
+ browserReconnectHooksAttached = true;
3974
+ window.addEventListener('online', () => {
3975
+ maybeReconnectFromBrowserSignal();
3976
+ });
3977
+ if (typeof document !== 'undefined') {
3978
+ document.addEventListener('visibilitychange', () => {
3979
+ if (document.visibilityState === 'visible') {
3980
+ maybeReconnectFromBrowserSignal();
3981
+ }
3982
+ });
3983
+ }
3984
+ }
3809
3985
  // ============ Connection Management ============
3810
3986
  async function getOrCreateConnection(appId, isServer) {
3987
+ attachBrowserReconnectHooksOnce();
3811
3988
  let connection = connections.get(appId);
3812
3989
  if (connection && connection.ws) {
3813
3990
  return connection;
@@ -3900,13 +4077,15 @@ function handleServerMessage(connection, message) {
3900
4077
  case 'subscribed': {
3901
4078
  const subscription = connection.subscriptions.get(message.subscriptionId);
3902
4079
  if (subscription) {
3903
- // Update cache
3904
- const cacheKey = getCacheKey(subscription.path, subscription.prompt, subscription.shape);
3905
- responseCache.set(cacheKey, { data: message.data, timestamp: Date.now() });
3906
- // Store last data
3907
- subscription.lastData = message.data;
3908
- // Notify callbacks
3909
- notifyCallbacks(subscription, message.data);
4080
+ // A data update can race ahead of subscribed during reconnect.
4081
+ // If we already received data for this subscription, treat subscribed
4082
+ // as an ack only and avoid regressing to an older snapshot.
4083
+ if (subscription.lastData === undefined) {
4084
+ const cacheKey = getCacheKey(subscription.path, subscription.prompt, subscription.shape);
4085
+ responseCache.set(cacheKey, { data: message.data, timestamp: Date.now() });
4086
+ subscription.lastData = message.data;
4087
+ notifyCallbacks(subscription, message.data);
4088
+ }
3910
4089
  }
3911
4090
  // Resolve pending subscription promise
3912
4091
  const pending = connection.pendingSubscriptions.get(message.subscriptionId);
@@ -3972,6 +4151,7 @@ function notifyCallbacks(subscription, data) {
3972
4151
  }
3973
4152
  // WebSocket readyState constants
3974
4153
  const WS_READY_STATE_OPEN = 1;
4154
+ const WS_READY_STATE_CLOSED = 3;
3975
4155
  function sendSubscribe(connection, subscription) {
3976
4156
  if (!connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN) {
3977
4157
  return;