@pooflabs/core 0.0.27 → 0.0.29
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/client/operations.d.ts +13 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +184 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -36
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/utils/utils.d.ts +1 -1
- package/package.json +1 -1
|
@@ -5,13 +5,24 @@ export type SetOptions = {
|
|
|
5
5
|
headers?: Record<string, string>;
|
|
6
6
|
};
|
|
7
7
|
};
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Options for the get function.
|
|
10
|
+
*/
|
|
11
|
+
export type GetOptions = {
|
|
12
|
+
/** Natural language prompt for AI-powered queries (collections only) */
|
|
9
13
|
prompt?: string | undefined;
|
|
14
|
+
/** Bypass the local cache and fetch fresh data */
|
|
10
15
|
bypassCache?: boolean;
|
|
16
|
+
/** Include documents from sub-paths (nested collections) */
|
|
17
|
+
includeSubPaths?: boolean;
|
|
18
|
+
/** Shape object for relationship resolution - specifies which related documents to include */
|
|
19
|
+
shape?: Record<string, any>;
|
|
20
|
+
/** Internal overrides for headers */
|
|
11
21
|
_overrides?: {
|
|
12
22
|
headers?: Record<string, string>;
|
|
13
23
|
};
|
|
14
|
-
}
|
|
24
|
+
};
|
|
25
|
+
export declare function get(path: string, opts?: GetOptions): Promise<any>;
|
|
15
26
|
export type RunExpressionOptions = {
|
|
16
27
|
returnType?: 'Bool' | 'String' | 'Int' | 'UInt';
|
|
17
28
|
_overrides?: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { init } from './client/config';
|
|
2
2
|
export { getConfig, ClientConfig } from './client/config';
|
|
3
|
-
export { get, set, setMany, setFile, getFiles, runQuery, runQueryMany, runExpression, runExpressionMany, signMessage, signTransaction, signAndSubmitTransaction, SetOptions, RunExpressionOptions, RunExpressionResult } from './client/operations';
|
|
3
|
+
export { get, set, setMany, setFile, getFiles, runQuery, runQueryMany, runExpression, runExpressionMany, signMessage, signTransaction, signAndSubmitTransaction, SetOptions, GetOptions, RunExpressionOptions, RunExpressionResult } from './client/operations';
|
|
4
4
|
export { subscribe, closeAllSubscriptions, clearCache, getCachedData, reconnectWithNewAuth } from './client/subscription';
|
|
5
5
|
export * from './types';
|
|
6
6
|
export { getIdToken } from './utils/utils';
|
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
|
-
|
|
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
|
-
|
|
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
3211
|
var _a, _b, _c, _d, _e, _f;
|
|
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,19 +3248,20 @@ 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
|
|
3180
|
-
if (!
|
|
3181
|
-
throw new Error("
|
|
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 (
|
|
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
|
}
|
|
@@ -3279,8 +3352,10 @@ async function get(path, opts = {}) {
|
|
|
3279
3352
|
if (!normalizedPath || normalizedPath.length === 0) {
|
|
3280
3353
|
return new Error("Invalid path provided.");
|
|
3281
3354
|
}
|
|
3282
|
-
// Create cache key combining path and
|
|
3283
|
-
const
|
|
3355
|
+
// Create cache key combining path, prompt, includeSubPaths, and shape
|
|
3356
|
+
const shapeKey = opts.shape ? JSON.stringify(opts.shape) : '';
|
|
3357
|
+
const includeSubPathsKey = opts.includeSubPaths ? ':subpaths' : '';
|
|
3358
|
+
const cacheKey = `${normalizedPath}:${opts.prompt || ''}${includeSubPathsKey}:${shapeKey}`;
|
|
3284
3359
|
const now = Date.now();
|
|
3285
3360
|
// Check for valid cache entry if not bypassing cache
|
|
3286
3361
|
if (!opts.bypassCache && getCache[cacheKey] && now < getCache[cacheKey].expiresAt) {
|
|
@@ -3301,15 +3376,20 @@ async function get(path, opts = {}) {
|
|
|
3301
3376
|
// Cache miss or bypass - proceed with API request
|
|
3302
3377
|
const pathIsDocument = normalizedPath.split("/").length % 2 === 0;
|
|
3303
3378
|
let response;
|
|
3379
|
+
// Build common query params
|
|
3380
|
+
const includeSubPathsParam = opts.includeSubPaths ? '&includeSubPaths=true' : '';
|
|
3381
|
+
const shapeParam = opts.shape ? `&shape=${encodeURIComponent(JSON.stringify(opts.shape))}` : '';
|
|
3304
3382
|
if (pathIsDocument) {
|
|
3305
3383
|
const itemId = encodeURIComponent(normalizedPath);
|
|
3306
|
-
|
|
3384
|
+
// For documents, query params go after the path
|
|
3385
|
+
const queryParams = [includeSubPathsParam, shapeParam].filter(p => p).join('');
|
|
3386
|
+
const apiPath = queryParams ? `items/${itemId}?${queryParams.substring(1)}` : `items/${itemId}`;
|
|
3307
3387
|
response = await makeApiRequest('GET', apiPath, null, opts._overrides);
|
|
3308
3388
|
}
|
|
3309
3389
|
else {
|
|
3310
3390
|
const path = encodeURIComponent(normalizedPath);
|
|
3311
3391
|
const promptQueryParam = (opts === null || opts === void 0 ? void 0 : opts.prompt) ? `&prompt=${btoa(opts.prompt)}` : "";
|
|
3312
|
-
const apiPath = `items?path=${path}${promptQueryParam}`;
|
|
3392
|
+
const apiPath = `items?path=${path}${promptQueryParam}${includeSubPathsParam}${shapeParam}`;
|
|
3313
3393
|
response = await makeApiRequest('GET', apiPath, null, opts._overrides);
|
|
3314
3394
|
}
|
|
3315
3395
|
// Cache the response (unless bypassing cache)
|
|
@@ -3713,21 +3793,34 @@ const responseCache = new Map();
|
|
|
3713
3793
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
3714
3794
|
const TOKEN_REFRESH_BUFFER = 5 * 60 * 1000; // Refresh token 5 minutes before expiry
|
|
3715
3795
|
// ============ WebSocket Config ============
|
|
3796
|
+
const BASE_MIN_RECONNECT_DELAY_MS = 1000;
|
|
3797
|
+
const MIN_RECONNECT_DELAY_JITTER_MS = 1000;
|
|
3798
|
+
const MAX_RECONNECT_DELAY_MS = 300000;
|
|
3799
|
+
const RECONNECT_DELAY_GROW_FACTOR = 1.8;
|
|
3800
|
+
const MIN_BROWSER_RECONNECT_INTERVAL_MS = 30000;
|
|
3716
3801
|
const WS_CONFIG = {
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3802
|
+
// Keep retrying indefinitely so long outages recover without page refresh.
|
|
3803
|
+
maxRetries: Infinity,
|
|
3804
|
+
// Conservative starting delay with jitter spreads reconnects across clients.
|
|
3805
|
+
minReconnectionDelay: BASE_MIN_RECONNECT_DELAY_MS + Math.floor(Math.random() * MIN_RECONNECT_DELAY_JITTER_MS),
|
|
3806
|
+
// Stretch backoff to 5 minutes during prolonged outages to protect backend pools.
|
|
3807
|
+
maxReconnectionDelay: MAX_RECONNECT_DELAY_MS,
|
|
3808
|
+
// Moderate growth to avoid rapid retry bursts while still converging to max delay.
|
|
3809
|
+
reconnectionDelayGrowFactor: RECONNECT_DELAY_GROW_FACTOR,
|
|
3810
|
+
// Give handshakes more time before considering the attempt failed.
|
|
3811
|
+
connectionTimeout: 10000,
|
|
3722
3812
|
};
|
|
3723
3813
|
const WS_V2_PATH = '/ws/v2';
|
|
3814
|
+
let browserReconnectHooksAttached = false;
|
|
3815
|
+
let lastBrowserTriggeredReconnectAt = 0;
|
|
3724
3816
|
// ============ Helper Functions ============
|
|
3725
3817
|
function generateSubscriptionId() {
|
|
3726
3818
|
return `sub_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
3727
3819
|
}
|
|
3728
|
-
function getCacheKey(path, prompt) {
|
|
3820
|
+
function getCacheKey(path, prompt, shape) {
|
|
3729
3821
|
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
3730
|
-
|
|
3822
|
+
const shapeKey = shape && Object.keys(shape).length > 0 ? JSON.stringify(shape) : '';
|
|
3823
|
+
return `${normalizedPath}:${prompt || 'default'}:${shapeKey}`;
|
|
3731
3824
|
}
|
|
3732
3825
|
function isTokenExpired(token) {
|
|
3733
3826
|
try {
|
|
@@ -3789,7 +3882,7 @@ async function getFreshAuthToken(isServer) {
|
|
|
3789
3882
|
}
|
|
3790
3883
|
const refreshData = await refreshSession(refreshToken);
|
|
3791
3884
|
if (refreshData && refreshData.idToken && refreshData.accessToken) {
|
|
3792
|
-
updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken);
|
|
3885
|
+
await updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken, isServer);
|
|
3793
3886
|
return refreshData.idToken;
|
|
3794
3887
|
}
|
|
3795
3888
|
}
|
|
@@ -3798,8 +3891,54 @@ async function getFreshAuthToken(isServer) {
|
|
|
3798
3891
|
}
|
|
3799
3892
|
return currentToken;
|
|
3800
3893
|
}
|
|
3894
|
+
function hasDisconnectedActiveConnections() {
|
|
3895
|
+
for (const connection of connections.values()) {
|
|
3896
|
+
if (!connection.ws) {
|
|
3897
|
+
continue;
|
|
3898
|
+
}
|
|
3899
|
+
// Only nudge reconnection when socket is fully closed.
|
|
3900
|
+
// If ReconnectingWebSocket is already in CONNECTING/backoff state, don't interfere.
|
|
3901
|
+
if (connection.ws.readyState === WS_READY_STATE_CLOSED) {
|
|
3902
|
+
return true;
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
return false;
|
|
3906
|
+
}
|
|
3907
|
+
function maybeReconnectFromBrowserSignal() {
|
|
3908
|
+
const now = Date.now();
|
|
3909
|
+
if (now - lastBrowserTriggeredReconnectAt < MIN_BROWSER_RECONNECT_INTERVAL_MS) {
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
if (!hasDisconnectedActiveConnections()) {
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
lastBrowserTriggeredReconnectAt = now;
|
|
3916
|
+
reconnectWithNewAuthV2().catch((error) => {
|
|
3917
|
+
console.error('[WS v2] Browser-triggered reconnect failed:', error);
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
function attachBrowserReconnectHooksOnce() {
|
|
3921
|
+
if (browserReconnectHooksAttached) {
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
if (typeof window === 'undefined') {
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3927
|
+
browserReconnectHooksAttached = true;
|
|
3928
|
+
window.addEventListener('online', () => {
|
|
3929
|
+
maybeReconnectFromBrowserSignal();
|
|
3930
|
+
});
|
|
3931
|
+
if (typeof document !== 'undefined') {
|
|
3932
|
+
document.addEventListener('visibilitychange', () => {
|
|
3933
|
+
if (document.visibilityState === 'visible') {
|
|
3934
|
+
maybeReconnectFromBrowserSignal();
|
|
3935
|
+
}
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3801
3939
|
// ============ Connection Management ============
|
|
3802
3940
|
async function getOrCreateConnection(appId, isServer) {
|
|
3941
|
+
attachBrowserReconnectHooksOnce();
|
|
3803
3942
|
let connection = connections.get(appId);
|
|
3804
3943
|
if (connection && connection.ws) {
|
|
3805
3944
|
return connection;
|
|
@@ -3892,13 +4031,15 @@ function handleServerMessage(connection, message) {
|
|
|
3892
4031
|
case 'subscribed': {
|
|
3893
4032
|
const subscription = connection.subscriptions.get(message.subscriptionId);
|
|
3894
4033
|
if (subscription) {
|
|
3895
|
-
//
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
4034
|
+
// A data update can race ahead of subscribed during reconnect.
|
|
4035
|
+
// If we already received data for this subscription, treat subscribed
|
|
4036
|
+
// as an ack only and avoid regressing to an older snapshot.
|
|
4037
|
+
if (subscription.lastData === undefined) {
|
|
4038
|
+
const cacheKey = getCacheKey(subscription.path, subscription.prompt, subscription.shape);
|
|
4039
|
+
responseCache.set(cacheKey, { data: message.data, timestamp: Date.now() });
|
|
4040
|
+
subscription.lastData = message.data;
|
|
4041
|
+
notifyCallbacks(subscription, message.data);
|
|
4042
|
+
}
|
|
3902
4043
|
}
|
|
3903
4044
|
// Resolve pending subscription promise
|
|
3904
4045
|
const pending = connection.pendingSubscriptions.get(message.subscriptionId);
|
|
@@ -3921,7 +4062,7 @@ function handleServerMessage(connection, message) {
|
|
|
3921
4062
|
const subscription = connection.subscriptions.get(message.subscriptionId);
|
|
3922
4063
|
if (subscription) {
|
|
3923
4064
|
// Update cache
|
|
3924
|
-
const cacheKey = getCacheKey(subscription.path, subscription.prompt);
|
|
4065
|
+
const cacheKey = getCacheKey(subscription.path, subscription.prompt, subscription.shape);
|
|
3925
4066
|
responseCache.set(cacheKey, { data: message.data, timestamp: Date.now() });
|
|
3926
4067
|
// Store last data
|
|
3927
4068
|
subscription.lastData = message.data;
|
|
@@ -3964,6 +4105,7 @@ function notifyCallbacks(subscription, data) {
|
|
|
3964
4105
|
}
|
|
3965
4106
|
// WebSocket readyState constants
|
|
3966
4107
|
const WS_READY_STATE_OPEN = 1;
|
|
4108
|
+
const WS_READY_STATE_CLOSED = 3;
|
|
3967
4109
|
function sendSubscribe(connection, subscription) {
|
|
3968
4110
|
if (!connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN) {
|
|
3969
4111
|
return;
|
|
@@ -3974,6 +4116,9 @@ function sendSubscribe(connection, subscription) {
|
|
|
3974
4116
|
path: subscription.path,
|
|
3975
4117
|
prompt: subscription.prompt ? btoa(subscription.prompt) : undefined,
|
|
3976
4118
|
includeSubPaths: subscription.includeSubPaths,
|
|
4119
|
+
shape: subscription.shape && Object.keys(subscription.shape).length > 0
|
|
4120
|
+
? subscription.shape
|
|
4121
|
+
: undefined,
|
|
3977
4122
|
};
|
|
3978
4123
|
try {
|
|
3979
4124
|
connection.ws.send(JSON.stringify(message));
|
|
@@ -4005,7 +4150,7 @@ function sendUnsubscribe(connection, subscriptionId) {
|
|
|
4005
4150
|
async function subscribeV2(path, subscriptionOptions) {
|
|
4006
4151
|
const config = await getConfig();
|
|
4007
4152
|
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
4008
|
-
const cacheKey = getCacheKey(normalizedPath, subscriptionOptions.prompt);
|
|
4153
|
+
const cacheKey = getCacheKey(normalizedPath, subscriptionOptions.prompt, subscriptionOptions.shape);
|
|
4009
4154
|
// Deliver cached data immediately if available
|
|
4010
4155
|
const cachedEntry = responseCache.get(cacheKey);
|
|
4011
4156
|
if (cachedEntry && Date.now() - cachedEntry.timestamp < CACHE_TTL && subscriptionOptions.onData) {
|
|
@@ -4016,10 +4161,12 @@ async function subscribeV2(path, subscriptionOptions) {
|
|
|
4016
4161
|
}
|
|
4017
4162
|
// Get or create connection for this appId
|
|
4018
4163
|
const connection = await getOrCreateConnection(config.appId, config.isServer);
|
|
4019
|
-
// Check if we already have a subscription for this path+prompt
|
|
4164
|
+
// Check if we already have a subscription for this path+prompt+shape
|
|
4165
|
+
const shapeKey = subscriptionOptions.shape ? JSON.stringify(subscriptionOptions.shape) : '';
|
|
4020
4166
|
let existingSubscription;
|
|
4021
4167
|
for (const sub of connection.subscriptions.values()) {
|
|
4022
|
-
|
|
4168
|
+
const subShapeKey = sub.shape ? JSON.stringify(sub.shape) : '';
|
|
4169
|
+
if (sub.path === normalizedPath && sub.prompt === subscriptionOptions.prompt && subShapeKey === shapeKey) {
|
|
4023
4170
|
existingSubscription = sub;
|
|
4024
4171
|
break;
|
|
4025
4172
|
}
|
|
@@ -4044,6 +4191,7 @@ async function subscribeV2(path, subscriptionOptions) {
|
|
|
4044
4191
|
subscriptionId,
|
|
4045
4192
|
path: normalizedPath,
|
|
4046
4193
|
prompt: subscriptionOptions.prompt,
|
|
4194
|
+
shape: subscriptionOptions.shape,
|
|
4047
4195
|
includeSubPaths: false,
|
|
4048
4196
|
callbacks: [subscriptionOptions],
|
|
4049
4197
|
lastData: undefined,
|