@rpcbase/client 0.464.0 → 0.466.0
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/errorReporting-BaPe0tII.js +18 -0
- package/dist/errorReporting-BaPe0tII.js.map +1 -0
- package/dist/getServerApiClient-Dtfqua8g.js +165 -0
- package/dist/getServerApiClient-Dtfqua8g.js.map +1 -0
- package/dist/index.js +2999 -3258
- package/dist/index.js.map +1 -1
- package/dist/instrument.js +32 -40
- package/dist/instrument.js.map +1 -1
- package/dist/rts/index.js +2 -317
- package/dist/rts-C9lvbdZX.js +3161 -0
- package/dist/rts-C9lvbdZX.js.map +1 -0
- package/dist/spatial-nav/index.js +4988 -4327
- package/dist/spatial-nav/index.js.map +1 -1
- package/dist/throttle-BFr_Yc5g.js +620 -0
- package/dist/throttle-BFr_Yc5g.js.map +1 -0
- package/package.json +1 -1
- package/dist/errorReporting-CVoUUKxW.js +0 -18
- package/dist/errorReporting-CVoUUKxW.js.map +0 -1
- package/dist/getServerApiClient-DnG8591C.js +0 -192
- package/dist/getServerApiClient-DnG8591C.js.map +0 -1
- package/dist/rts/index.js.map +0 -1
- package/dist/throttle-CXOc9Dto.js +0 -396
- package/dist/throttle-CXOc9Dto.js.map +0 -1
- package/dist/useQuery-DZqYIJog.js +0 -3474
- package/dist/useQuery-DZqYIJog.js.map +0 -1
|
@@ -1,3474 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, useState, useRef, useEffect } from "react";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { c } from "react/compiler-runtime";
|
|
4
|
-
const STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY = "__staticRpcbaseRtsHydrationData";
|
|
5
|
-
const RtsSsrRuntimeContext = createContext(null);
|
|
6
|
-
const hydrationDataStore = /* @__PURE__ */ new Map();
|
|
7
|
-
const hydrationPageInfoStore = /* @__PURE__ */ new Map();
|
|
8
|
-
const hydrationTotalCountStore = /* @__PURE__ */ new Map();
|
|
9
|
-
const hydrationCountStore = /* @__PURE__ */ new Map();
|
|
10
|
-
const makeStoreKey = (modelName, queryKey) => `${modelName}.${queryKey}`;
|
|
11
|
-
const normalizeStringOrNull = (value) => {
|
|
12
|
-
if (typeof value !== "string") return null;
|
|
13
|
-
const normalized = value.trim();
|
|
14
|
-
return normalized ? normalized : null;
|
|
15
|
-
};
|
|
16
|
-
const normalizePageInfo$2 = (value) => {
|
|
17
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
18
|
-
const raw = value;
|
|
19
|
-
if (typeof raw.hasNextPage !== "boolean" || typeof raw.hasPrevPage !== "boolean") return void 0;
|
|
20
|
-
const nextCursor = typeof raw.nextCursor === "string" && raw.nextCursor ? raw.nextCursor : void 0;
|
|
21
|
-
const prevCursor = typeof raw.prevCursor === "string" && raw.prevCursor ? raw.prevCursor : void 0;
|
|
22
|
-
return {
|
|
23
|
-
hasNextPage: raw.hasNextPage,
|
|
24
|
-
hasPrevPage: raw.hasPrevPage,
|
|
25
|
-
...nextCursor ? {
|
|
26
|
-
nextCursor
|
|
27
|
-
} : {},
|
|
28
|
-
...prevCursor ? {
|
|
29
|
-
prevCursor
|
|
30
|
-
} : {}
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
const normalizeTotalCount$2 = (value) => {
|
|
34
|
-
if (typeof value !== "number") return void 0;
|
|
35
|
-
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
36
|
-
return Math.floor(value);
|
|
37
|
-
};
|
|
38
|
-
const parseHydrationData = (value) => {
|
|
39
|
-
if (!value || typeof value !== "object") return null;
|
|
40
|
-
const raw = value;
|
|
41
|
-
if (raw.v !== 1) return null;
|
|
42
|
-
if (!Array.isArray(raw.queries)) return null;
|
|
43
|
-
const rawCounts = Array.isArray(raw.counts) ? raw.counts : [];
|
|
44
|
-
const queries = [];
|
|
45
|
-
for (const entry of raw.queries) {
|
|
46
|
-
if (!entry || typeof entry !== "object") continue;
|
|
47
|
-
const query = entry;
|
|
48
|
-
const modelName = normalizeStringOrNull(query.modelName);
|
|
49
|
-
const queryKey = normalizeStringOrNull(query.queryKey);
|
|
50
|
-
if (!modelName || !queryKey) continue;
|
|
51
|
-
if (!Array.isArray(query.data)) continue;
|
|
52
|
-
const pageInfo = normalizePageInfo$2(query.pageInfo);
|
|
53
|
-
const totalCount = normalizeTotalCount$2(query.totalCount);
|
|
54
|
-
queries.push({
|
|
55
|
-
modelName,
|
|
56
|
-
queryKey,
|
|
57
|
-
data: query.data,
|
|
58
|
-
...pageInfo ? {
|
|
59
|
-
pageInfo
|
|
60
|
-
} : {},
|
|
61
|
-
...totalCount !== void 0 ? {
|
|
62
|
-
totalCount
|
|
63
|
-
} : {}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
const counts = [];
|
|
67
|
-
for (const entry of rawCounts) {
|
|
68
|
-
if (!entry || typeof entry !== "object") continue;
|
|
69
|
-
const countEntry = entry;
|
|
70
|
-
const modelName = normalizeStringOrNull(countEntry.modelName);
|
|
71
|
-
const queryKey = normalizeStringOrNull(countEntry.queryKey);
|
|
72
|
-
const count = normalizeTotalCount$2(countEntry.count);
|
|
73
|
-
if (!modelName || !queryKey || count === void 0) continue;
|
|
74
|
-
counts.push({
|
|
75
|
-
modelName,
|
|
76
|
-
queryKey,
|
|
77
|
-
count
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
v: 1,
|
|
82
|
-
tenantId: normalizeStringOrNull(raw.tenantId),
|
|
83
|
-
uid: normalizeStringOrNull(raw.uid),
|
|
84
|
-
queries,
|
|
85
|
-
counts
|
|
86
|
-
};
|
|
87
|
-
};
|
|
88
|
-
const hydrateRtsFromWindow = () => {
|
|
89
|
-
if (typeof window === "undefined") return;
|
|
90
|
-
const browserWindow = window;
|
|
91
|
-
const raw = browserWindow[STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY];
|
|
92
|
-
delete browserWindow[STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY];
|
|
93
|
-
const parsed = parseHydrationData(raw);
|
|
94
|
-
if (!parsed) return;
|
|
95
|
-
hydrationDataStore.clear();
|
|
96
|
-
hydrationPageInfoStore.clear();
|
|
97
|
-
hydrationTotalCountStore.clear();
|
|
98
|
-
hydrationCountStore.clear();
|
|
99
|
-
for (const query of parsed.queries) {
|
|
100
|
-
hydrationDataStore.set(makeStoreKey(query.modelName, query.queryKey), query.data);
|
|
101
|
-
hydrationPageInfoStore.set(makeStoreKey(query.modelName, query.queryKey), query.pageInfo);
|
|
102
|
-
hydrationTotalCountStore.set(makeStoreKey(query.modelName, query.queryKey), query.totalCount);
|
|
103
|
-
}
|
|
104
|
-
for (const count of parsed.counts) {
|
|
105
|
-
hydrationCountStore.set(makeStoreKey(count.modelName, count.queryKey), count.count);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
const peekHydratedRtsQueryData = (modelName, queryKey) => {
|
|
109
|
-
return hydrationDataStore.get(makeStoreKey(modelName, queryKey));
|
|
110
|
-
};
|
|
111
|
-
const peekHydratedRtsQueryPageInfo = (modelName, queryKey) => {
|
|
112
|
-
return hydrationPageInfoStore.get(makeStoreKey(modelName, queryKey));
|
|
113
|
-
};
|
|
114
|
-
const peekHydratedRtsQueryTotalCount = (modelName, queryKey) => {
|
|
115
|
-
return hydrationTotalCountStore.get(makeStoreKey(modelName, queryKey));
|
|
116
|
-
};
|
|
117
|
-
const peekHydratedRtsCount = (modelName, queryKey) => {
|
|
118
|
-
return hydrationCountStore.get(makeStoreKey(modelName, queryKey));
|
|
119
|
-
};
|
|
120
|
-
const consumeHydratedRtsQueryData = (modelName, queryKey) => {
|
|
121
|
-
const key = makeStoreKey(modelName, queryKey);
|
|
122
|
-
hydrationDataStore.delete(key);
|
|
123
|
-
hydrationPageInfoStore.delete(key);
|
|
124
|
-
hydrationTotalCountStore.delete(key);
|
|
125
|
-
};
|
|
126
|
-
const consumeHydratedRtsCount = (modelName, queryKey) => {
|
|
127
|
-
hydrationCountStore.delete(makeStoreKey(modelName, queryKey));
|
|
128
|
-
};
|
|
129
|
-
const clearHydratedRtsQueryData = () => {
|
|
130
|
-
hydrationDataStore.clear();
|
|
131
|
-
hydrationPageInfoStore.clear();
|
|
132
|
-
hydrationTotalCountStore.clear();
|
|
133
|
-
hydrationCountStore.clear();
|
|
134
|
-
};
|
|
135
|
-
const RtsSsrRuntimeProvider = (t0) => {
|
|
136
|
-
const $ = c(3);
|
|
137
|
-
const {
|
|
138
|
-
value,
|
|
139
|
-
children
|
|
140
|
-
} = t0;
|
|
141
|
-
let t1;
|
|
142
|
-
if ($[0] !== children || $[1] !== value) {
|
|
143
|
-
t1 = /* @__PURE__ */ jsx(RtsSsrRuntimeContext.Provider, { value, children });
|
|
144
|
-
$[0] = children;
|
|
145
|
-
$[1] = value;
|
|
146
|
-
$[2] = t1;
|
|
147
|
-
} else {
|
|
148
|
-
t1 = $[2];
|
|
149
|
-
}
|
|
150
|
-
return t1;
|
|
151
|
-
};
|
|
152
|
-
const useRtsSsrRuntime = () => {
|
|
153
|
-
return useContext(RtsSsrRuntimeContext);
|
|
154
|
-
};
|
|
155
|
-
const RTS_QUERY_WINDOW_MAX_COUNT = 4096;
|
|
156
|
-
const memoryStore = /* @__PURE__ */ new Map();
|
|
157
|
-
let reactNativeStorage = null;
|
|
158
|
-
const MMKV_STORAGE_ID = "rpcbase-rts";
|
|
159
|
-
const memoryStorage = {
|
|
160
|
-
getItem: (key) => memoryStore.get(key) ?? null,
|
|
161
|
-
setItem: (key, value) => {
|
|
162
|
-
memoryStore.set(key, String(value));
|
|
163
|
-
},
|
|
164
|
-
removeItem: (key) => {
|
|
165
|
-
memoryStore.delete(key);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
const asRuntimeStorage = (value) => {
|
|
169
|
-
if (!value || typeof value !== "object") return null;
|
|
170
|
-
const candidate = value;
|
|
171
|
-
if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function" || typeof candidate.removeItem !== "function") {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
getItem: candidate.getItem.bind(value),
|
|
176
|
-
setItem: candidate.setItem.bind(value),
|
|
177
|
-
removeItem: candidate.removeItem.bind(value)
|
|
178
|
-
};
|
|
179
|
-
};
|
|
180
|
-
const isReactNativeRuntime = () => {
|
|
181
|
-
if (typeof navigator === "undefined") return false;
|
|
182
|
-
return navigator.product === "ReactNative";
|
|
183
|
-
};
|
|
184
|
-
const getRuntimeRequire = () => {
|
|
185
|
-
const globalRequire = globalThis.require;
|
|
186
|
-
if (typeof globalRequire === "function") {
|
|
187
|
-
return globalRequire;
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
return (0, eval)("require");
|
|
191
|
-
} catch {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
const getReactNativeStorage = () => {
|
|
196
|
-
if (reactNativeStorage) return reactNativeStorage;
|
|
197
|
-
const runtimeRequire = getRuntimeRequire();
|
|
198
|
-
if (!runtimeRequire) {
|
|
199
|
-
throw new Error("RTS storage: react-native-mmkv is required in React Native runtime");
|
|
200
|
-
}
|
|
201
|
-
let mmkvModule;
|
|
202
|
-
try {
|
|
203
|
-
mmkvModule = runtimeRequire("react-native-mmkv");
|
|
204
|
-
} catch (error) {
|
|
205
|
-
const runtimeError = error instanceof Error ? ` ${error.message}` : "";
|
|
206
|
-
throw new Error(`RTS storage: react-native-mmkv is required in React Native runtime.${runtimeError}`);
|
|
207
|
-
}
|
|
208
|
-
const MMKV = mmkvModule.MMKV;
|
|
209
|
-
if (typeof MMKV !== "function") {
|
|
210
|
-
throw new Error("RTS storage: invalid react-native-mmkv module shape");
|
|
211
|
-
}
|
|
212
|
-
const mmkv = new MMKV({
|
|
213
|
-
id: MMKV_STORAGE_ID
|
|
214
|
-
});
|
|
215
|
-
reactNativeStorage = {
|
|
216
|
-
getItem: (key) => {
|
|
217
|
-
const value = mmkv.getString(key);
|
|
218
|
-
return typeof value === "string" ? value : null;
|
|
219
|
-
},
|
|
220
|
-
setItem: (key, value) => {
|
|
221
|
-
mmkv.set(key, String(value));
|
|
222
|
-
},
|
|
223
|
-
removeItem: (key) => {
|
|
224
|
-
mmkv.delete(key);
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
return reactNativeStorage;
|
|
228
|
-
};
|
|
229
|
-
const getRuntimeStorage = () => {
|
|
230
|
-
if (isReactNativeRuntime()) {
|
|
231
|
-
return getReactNativeStorage();
|
|
232
|
-
}
|
|
233
|
-
const direct = asRuntimeStorage(globalThis.localStorage);
|
|
234
|
-
if (direct) return direct;
|
|
235
|
-
const windowStorage = asRuntimeStorage(globalThis.window?.localStorage);
|
|
236
|
-
if (windowStorage) return windowStorage;
|
|
237
|
-
return memoryStorage;
|
|
238
|
-
};
|
|
239
|
-
const UNDERSCORE_PREFIX = "$_";
|
|
240
|
-
const DEFAULT_FIND_LIMIT = 4096;
|
|
241
|
-
const INDEXED_DB_ADAPTER = "indexeddb";
|
|
242
|
-
const REACT_NATIVE_SQLITE_ADAPTER = "react-native-sqlite";
|
|
243
|
-
const QUERY_WINDOW_COLLECTION = "$query-windows-v1";
|
|
244
|
-
const QUERY_WINDOW_DOC_TYPE = "rts-query-window";
|
|
245
|
-
const QUERY_WINDOW_SCHEMA_VERSION = 1;
|
|
246
|
-
const QUERY_WINDOW_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
247
|
-
const QUERY_WINDOW_WRITE_ATTEMPTS = 4;
|
|
248
|
-
let storeConfig = null;
|
|
249
|
-
let pouchDbPromise = null;
|
|
250
|
-
let lastAppliedPrefix = null;
|
|
251
|
-
let activePouchAdapter = INDEXED_DB_ADAPTER;
|
|
252
|
-
const collections = /* @__PURE__ */ new Map();
|
|
253
|
-
const dbNamesByPrefix = /* @__PURE__ */ new Map();
|
|
254
|
-
const unwrapDefault = (mod) => {
|
|
255
|
-
if (!mod || typeof mod !== "object") return mod;
|
|
256
|
-
const maybe = mod;
|
|
257
|
-
return maybe.default ?? mod;
|
|
258
|
-
};
|
|
259
|
-
const computeBasePrefix = ({
|
|
260
|
-
tenantId,
|
|
261
|
-
appName
|
|
262
|
-
}) => {
|
|
263
|
-
let prefix = "rb/";
|
|
264
|
-
if (appName) prefix += `${appName}/`;
|
|
265
|
-
prefix += `${tenantId}/`;
|
|
266
|
-
return prefix;
|
|
267
|
-
};
|
|
268
|
-
const getDbNamesKey = (prefix) => `rb:rts:pouchDbs:${prefix}`;
|
|
269
|
-
const getPrefixOverrideKey = ({
|
|
270
|
-
tenantId,
|
|
271
|
-
appName
|
|
272
|
-
}) => `rb:rts:pouchPrefix:${appName ?? ""}:${tenantId}`;
|
|
273
|
-
const readPrefixOverride = ({
|
|
274
|
-
tenantId,
|
|
275
|
-
appName
|
|
276
|
-
}) => {
|
|
277
|
-
const storage = getRuntimeStorage();
|
|
278
|
-
try {
|
|
279
|
-
const value = storage.getItem(getPrefixOverrideKey({
|
|
280
|
-
tenantId,
|
|
281
|
-
appName
|
|
282
|
-
}));
|
|
283
|
-
if (!value) return null;
|
|
284
|
-
if (!value.endsWith("/")) return `${value}/`;
|
|
285
|
-
return value;
|
|
286
|
-
} catch {
|
|
287
|
-
return null;
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
const getPrefix = () => {
|
|
291
|
-
if (!storeConfig) {
|
|
292
|
-
throw new Error("RTS PouchDB store is not configured");
|
|
293
|
-
}
|
|
294
|
-
if (storeConfig.prefix) return storeConfig.prefix;
|
|
295
|
-
const basePrefix = computeBasePrefix({
|
|
296
|
-
tenantId: storeConfig.tenantId,
|
|
297
|
-
appName: storeConfig.appName
|
|
298
|
-
});
|
|
299
|
-
const override = readPrefixOverride({
|
|
300
|
-
tenantId: storeConfig.tenantId,
|
|
301
|
-
appName: storeConfig.appName
|
|
302
|
-
});
|
|
303
|
-
return override ?? basePrefix;
|
|
304
|
-
};
|
|
305
|
-
const loadDbNames = (prefix) => {
|
|
306
|
-
const existing = dbNamesByPrefix.get(prefix);
|
|
307
|
-
if (existing) return existing;
|
|
308
|
-
const names = /* @__PURE__ */ new Set();
|
|
309
|
-
const storage = getRuntimeStorage();
|
|
310
|
-
try {
|
|
311
|
-
const raw = storage.getItem(getDbNamesKey(prefix));
|
|
312
|
-
if (raw) {
|
|
313
|
-
const parsed = JSON.parse(raw);
|
|
314
|
-
if (Array.isArray(parsed)) {
|
|
315
|
-
for (const value of parsed) {
|
|
316
|
-
if (typeof value === "string" && value) names.add(value);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
} catch {
|
|
321
|
-
return names;
|
|
322
|
-
}
|
|
323
|
-
dbNamesByPrefix.set(prefix, names);
|
|
324
|
-
return names;
|
|
325
|
-
};
|
|
326
|
-
const persistDbNames = (prefix, names) => {
|
|
327
|
-
const storage = getRuntimeStorage();
|
|
328
|
-
try {
|
|
329
|
-
if (!names.size) {
|
|
330
|
-
storage.removeItem(getDbNamesKey(prefix));
|
|
331
|
-
dbNamesByPrefix.delete(prefix);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
storage.setItem(getDbNamesKey(prefix), JSON.stringify(Array.from(names)));
|
|
335
|
-
dbNamesByPrefix.set(prefix, names);
|
|
336
|
-
} catch {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
const registerDbName = (prefix, dbName) => {
|
|
341
|
-
if (!prefix || !dbName) return;
|
|
342
|
-
const names = loadDbNames(prefix);
|
|
343
|
-
if (names.has(dbName)) return;
|
|
344
|
-
names.add(dbName);
|
|
345
|
-
persistDbNames(prefix, names);
|
|
346
|
-
};
|
|
347
|
-
const unregisterDbName = (prefix, dbName) => {
|
|
348
|
-
if (!prefix || !dbName) return;
|
|
349
|
-
const names = loadDbNames(prefix);
|
|
350
|
-
if (!names.delete(dbName)) return;
|
|
351
|
-
persistDbNames(prefix, names);
|
|
352
|
-
};
|
|
353
|
-
const getPouchDb = async () => {
|
|
354
|
-
if (!pouchDbPromise) {
|
|
355
|
-
pouchDbPromise = (async () => {
|
|
356
|
-
const [core, findPlugin] = await Promise.all([import("pouchdb-core"), import("pouchdb-find")]);
|
|
357
|
-
const PouchDB = unwrapDefault(core);
|
|
358
|
-
if (isReactNativeRuntime()) {
|
|
359
|
-
const moduleName = "pouchdb-adapter-react-native-sqlite";
|
|
360
|
-
let sqliteAdapterModule;
|
|
361
|
-
try {
|
|
362
|
-
sqliteAdapterModule = await import(
|
|
363
|
-
/* @vite-ignore */
|
|
364
|
-
moduleName
|
|
365
|
-
);
|
|
366
|
-
} catch (error) {
|
|
367
|
-
const runtimeError = error instanceof Error ? ` ${error.message}` : "";
|
|
368
|
-
throw new Error(`RTS PouchDB: missing react-native sqlite adapter. Install \`pouchdb-adapter-react-native-sqlite\` in the app.${runtimeError}`);
|
|
369
|
-
}
|
|
370
|
-
PouchDB.plugin(unwrapDefault(sqliteAdapterModule));
|
|
371
|
-
activePouchAdapter = REACT_NATIVE_SQLITE_ADAPTER;
|
|
372
|
-
} else {
|
|
373
|
-
const indexedDbAdapter = await import("pouchdb-adapter-indexeddb");
|
|
374
|
-
PouchDB.plugin(unwrapDefault(indexedDbAdapter));
|
|
375
|
-
activePouchAdapter = INDEXED_DB_ADAPTER;
|
|
376
|
-
}
|
|
377
|
-
PouchDB.plugin(unwrapDefault(findPlugin));
|
|
378
|
-
return PouchDB;
|
|
379
|
-
})();
|
|
380
|
-
}
|
|
381
|
-
return pouchDbPromise;
|
|
382
|
-
};
|
|
383
|
-
const applyPrefix = (PouchDB, prefix = getPrefix()) => {
|
|
384
|
-
if (prefix === lastAppliedPrefix) return;
|
|
385
|
-
PouchDB.prefix = prefix;
|
|
386
|
-
lastAppliedPrefix = prefix;
|
|
387
|
-
};
|
|
388
|
-
const configureRtsPouchStore = (config) => {
|
|
389
|
-
storeConfig = config;
|
|
390
|
-
lastAppliedPrefix = null;
|
|
391
|
-
collections.clear();
|
|
392
|
-
};
|
|
393
|
-
const getCollection = async (modelName, options) => {
|
|
394
|
-
const prefix = getPrefix();
|
|
395
|
-
const PouchDB = await getPouchDb();
|
|
396
|
-
applyPrefix(PouchDB, prefix);
|
|
397
|
-
const dbName = `${options.uid}/${modelName}`;
|
|
398
|
-
const dbKey = `${prefix}${dbName}`;
|
|
399
|
-
const existing = collections.get(dbKey);
|
|
400
|
-
if (existing) return existing;
|
|
401
|
-
registerDbName(prefix, dbName);
|
|
402
|
-
const db = new PouchDB(dbName, {
|
|
403
|
-
adapter: activePouchAdapter,
|
|
404
|
-
revs_limit: 1
|
|
405
|
-
});
|
|
406
|
-
collections.set(dbKey, db);
|
|
407
|
-
return db;
|
|
408
|
-
};
|
|
409
|
-
const getQueryWindowScope = (uid) => {
|
|
410
|
-
if (!storeConfig) {
|
|
411
|
-
throw new Error("RTS PouchDB store is not configured");
|
|
412
|
-
}
|
|
413
|
-
return {
|
|
414
|
-
appName: storeConfig.appName ?? "",
|
|
415
|
-
tenantId: storeConfig.tenantId,
|
|
416
|
-
uid
|
|
417
|
-
};
|
|
418
|
-
};
|
|
419
|
-
const getQueryWindowFingerprint = (scope, modelName, queryKey) => JSON.stringify([QUERY_WINDOW_SCHEMA_VERSION, scope.appName, scope.tenantId, scope.uid, modelName, queryKey]);
|
|
420
|
-
const getQueryWindowDocumentId = (fingerprint) => `${QUERY_WINDOW_DOC_TYPE}:${encodeURIComponent(fingerprint)}`;
|
|
421
|
-
const getPouchErrorStatus = (error) => {
|
|
422
|
-
if (!error || typeof error !== "object") return void 0;
|
|
423
|
-
const status = error.status;
|
|
424
|
-
return typeof status === "number" && Number.isFinite(status) ? status : void 0;
|
|
425
|
-
};
|
|
426
|
-
const getPouchDocument = async (collection, id) => {
|
|
427
|
-
try {
|
|
428
|
-
return await collection.get(id);
|
|
429
|
-
} catch (error) {
|
|
430
|
-
if (getPouchErrorStatus(error) === 404) return null;
|
|
431
|
-
throw error;
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
const normalizeQueryWindowPageInfo = (value) => {
|
|
435
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
436
|
-
const raw = value;
|
|
437
|
-
if (typeof raw.hasNextPage !== "boolean" || typeof raw.hasPrevPage !== "boolean") return null;
|
|
438
|
-
if (raw.nextCursor !== void 0 && typeof raw.nextCursor !== "string") return null;
|
|
439
|
-
if (raw.prevCursor !== void 0 && typeof raw.prevCursor !== "string") return null;
|
|
440
|
-
const nextCursor = typeof raw.nextCursor === "string" && raw.nextCursor ? raw.nextCursor : void 0;
|
|
441
|
-
const prevCursor = typeof raw.prevCursor === "string" && raw.prevCursor ? raw.prevCursor : void 0;
|
|
442
|
-
return {
|
|
443
|
-
hasNextPage: raw.hasNextPage,
|
|
444
|
-
hasPrevPage: raw.hasPrevPage,
|
|
445
|
-
...nextCursor ? {
|
|
446
|
-
nextCursor
|
|
447
|
-
} : {},
|
|
448
|
-
...prevCursor ? {
|
|
449
|
-
prevCursor
|
|
450
|
-
} : {}
|
|
451
|
-
};
|
|
452
|
-
};
|
|
453
|
-
const isValidQueryWindowServerVersion = (value) => {
|
|
454
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
455
|
-
};
|
|
456
|
-
const isQueryWindowData = (value) => Array.isArray(value) && value.every((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
457
|
-
const parseQueryWindowSnapshot = (doc, expected) => {
|
|
458
|
-
if (doc.type !== QUERY_WINDOW_DOC_TYPE || doc.schemaVersion !== QUERY_WINDOW_SCHEMA_VERSION) return null;
|
|
459
|
-
if (doc.fingerprint !== expected.fingerprint) return null;
|
|
460
|
-
if (doc.modelName !== expected.modelName || doc.queryKey !== expected.queryKey) return null;
|
|
461
|
-
const scope = doc.scope;
|
|
462
|
-
if (!scope || typeof scope !== "object" || Array.isArray(scope)) return null;
|
|
463
|
-
const rawScope = scope;
|
|
464
|
-
if (rawScope.appName !== expected.scope.appName || rawScope.tenantId !== expected.scope.tenantId || rawScope.uid !== expected.scope.uid) {
|
|
465
|
-
return null;
|
|
466
|
-
}
|
|
467
|
-
if (!isQueryWindowData(doc.data)) return null;
|
|
468
|
-
const pageInfo = normalizeQueryWindowPageInfo(doc.pageInfo);
|
|
469
|
-
if (!pageInfo) return null;
|
|
470
|
-
const requestedCount = doc.requestedCount;
|
|
471
|
-
if (!Number.isSafeInteger(requestedCount) || requestedCount <= 0 || requestedCount > RTS_QUERY_WINDOW_MAX_COUNT || doc.data.length > requestedCount) {
|
|
472
|
-
return null;
|
|
473
|
-
}
|
|
474
|
-
const storedAt = doc.storedAt;
|
|
475
|
-
if (typeof storedAt !== "number" || !Number.isFinite(storedAt) || storedAt < 0) return null;
|
|
476
|
-
const totalCount = doc.totalCount;
|
|
477
|
-
if (totalCount !== void 0 && (!Number.isSafeInteger(totalCount) || totalCount < 0)) {
|
|
478
|
-
return null;
|
|
479
|
-
}
|
|
480
|
-
const serverVersion = doc.serverVersion;
|
|
481
|
-
if (serverVersion !== void 0 && !isValidQueryWindowServerVersion(serverVersion)) return null;
|
|
482
|
-
const serverEpoch = doc.serverEpoch;
|
|
483
|
-
if (serverEpoch !== void 0 && (typeof serverEpoch !== "string" || !serverEpoch)) return null;
|
|
484
|
-
if (serverEpoch === void 0 !== (serverVersion === void 0)) return null;
|
|
485
|
-
return {
|
|
486
|
-
data: doc.data,
|
|
487
|
-
pageInfo,
|
|
488
|
-
...typeof totalCount === "number" ? {
|
|
489
|
-
totalCount
|
|
490
|
-
} : {},
|
|
491
|
-
requestedCount,
|
|
492
|
-
...serverEpoch !== void 0 ? {
|
|
493
|
-
serverEpoch
|
|
494
|
-
} : {},
|
|
495
|
-
...serverVersion !== void 0 ? {
|
|
496
|
-
serverVersion
|
|
497
|
-
} : {},
|
|
498
|
-
storedAt
|
|
499
|
-
};
|
|
500
|
-
};
|
|
501
|
-
const deleteQueryWindowDocument = async (collection, doc) => {
|
|
502
|
-
const id = typeof doc._id === "string" ? doc._id : "";
|
|
503
|
-
const rev = typeof doc._rev === "string" ? doc._rev : "";
|
|
504
|
-
if (!id || !rev) return;
|
|
505
|
-
await collection.put({
|
|
506
|
-
_id: id,
|
|
507
|
-
_rev: rev,
|
|
508
|
-
_deleted: true
|
|
509
|
-
}).catch(() => void 0);
|
|
510
|
-
};
|
|
511
|
-
const readQueryWindowSnapshot = async ({
|
|
512
|
-
modelName,
|
|
513
|
-
queryKey,
|
|
514
|
-
uid,
|
|
515
|
-
now = Date.now()
|
|
516
|
-
}) => {
|
|
517
|
-
const scope = getQueryWindowScope(uid);
|
|
518
|
-
const fingerprint = getQueryWindowFingerprint(scope, modelName, queryKey);
|
|
519
|
-
const id = getQueryWindowDocumentId(fingerprint);
|
|
520
|
-
const collection = await getCollection(QUERY_WINDOW_COLLECTION, {
|
|
521
|
-
uid
|
|
522
|
-
});
|
|
523
|
-
const doc = await getPouchDocument(collection, id);
|
|
524
|
-
if (!doc) return {
|
|
525
|
-
hit: false
|
|
526
|
-
};
|
|
527
|
-
const snapshot = parseQueryWindowSnapshot(doc, {
|
|
528
|
-
fingerprint,
|
|
529
|
-
scope,
|
|
530
|
-
modelName,
|
|
531
|
-
queryKey
|
|
532
|
-
});
|
|
533
|
-
if (!snapshot || now - snapshot.storedAt >= QUERY_WINDOW_TTL_MS) {
|
|
534
|
-
await deleteQueryWindowDocument(collection, doc);
|
|
535
|
-
return {
|
|
536
|
-
hit: false
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
return {
|
|
540
|
-
hit: true,
|
|
541
|
-
snapshot
|
|
542
|
-
};
|
|
543
|
-
};
|
|
544
|
-
const writeQueryWindowSnapshot = async ({
|
|
545
|
-
modelName,
|
|
546
|
-
queryKey,
|
|
547
|
-
uid,
|
|
548
|
-
data,
|
|
549
|
-
pageInfo: rawPageInfo,
|
|
550
|
-
totalCount,
|
|
551
|
-
requestedCount,
|
|
552
|
-
serverEpoch,
|
|
553
|
-
serverVersion,
|
|
554
|
-
storedAt: rawStoredAt
|
|
555
|
-
}) => {
|
|
556
|
-
if (!modelName.trim()) throw new Error("writeQueryWindowSnapshot: modelName must be a non-empty string");
|
|
557
|
-
if (!queryKey) throw new Error("writeQueryWindowSnapshot: queryKey must be a non-empty string");
|
|
558
|
-
if (!uid.trim()) throw new Error("writeQueryWindowSnapshot: uid must be a non-empty string");
|
|
559
|
-
if (!isQueryWindowData(data)) throw new Error("writeQueryWindowSnapshot: data must contain objects");
|
|
560
|
-
const pageInfo = normalizeQueryWindowPageInfo(rawPageInfo);
|
|
561
|
-
if (!pageInfo) throw new Error("writeQueryWindowSnapshot: invalid pageInfo");
|
|
562
|
-
if (!Number.isSafeInteger(requestedCount) || requestedCount <= 0 || requestedCount > RTS_QUERY_WINDOW_MAX_COUNT || data.length > requestedCount) {
|
|
563
|
-
throw new Error("writeQueryWindowSnapshot: requestedCount must cover data and be between 1 and 4096");
|
|
564
|
-
}
|
|
565
|
-
if (totalCount !== void 0 && (!Number.isSafeInteger(totalCount) || totalCount < 0)) {
|
|
566
|
-
throw new Error("writeQueryWindowSnapshot: totalCount must be a non-negative integer");
|
|
567
|
-
}
|
|
568
|
-
if (serverVersion !== void 0 && !isValidQueryWindowServerVersion(serverVersion)) {
|
|
569
|
-
throw new Error("writeQueryWindowSnapshot: invalid serverVersion");
|
|
570
|
-
}
|
|
571
|
-
if (serverEpoch !== void 0 && (typeof serverEpoch !== "string" || !serverEpoch)) {
|
|
572
|
-
throw new Error("writeQueryWindowSnapshot: invalid serverEpoch");
|
|
573
|
-
}
|
|
574
|
-
if (serverEpoch === void 0 !== (serverVersion === void 0)) {
|
|
575
|
-
throw new Error("writeQueryWindowSnapshot: serverEpoch and serverVersion must be provided together");
|
|
576
|
-
}
|
|
577
|
-
const storedAt = rawStoredAt ?? Date.now();
|
|
578
|
-
if (!Number.isFinite(storedAt) || storedAt < 0) {
|
|
579
|
-
throw new Error("writeQueryWindowSnapshot: storedAt must be a non-negative finite number");
|
|
580
|
-
}
|
|
581
|
-
const scope = getQueryWindowScope(uid);
|
|
582
|
-
const fingerprint = getQueryWindowFingerprint(scope, modelName, queryKey);
|
|
583
|
-
const id = getQueryWindowDocumentId(fingerprint);
|
|
584
|
-
const collection = await getCollection(QUERY_WINDOW_COLLECTION, {
|
|
585
|
-
uid
|
|
586
|
-
});
|
|
587
|
-
const snapshot = {
|
|
588
|
-
data,
|
|
589
|
-
pageInfo,
|
|
590
|
-
...totalCount !== void 0 ? {
|
|
591
|
-
totalCount
|
|
592
|
-
} : {},
|
|
593
|
-
requestedCount,
|
|
594
|
-
...serverEpoch !== void 0 ? {
|
|
595
|
-
serverEpoch
|
|
596
|
-
} : {},
|
|
597
|
-
...serverVersion !== void 0 ? {
|
|
598
|
-
serverVersion
|
|
599
|
-
} : {},
|
|
600
|
-
storedAt
|
|
601
|
-
};
|
|
602
|
-
let lastConflict;
|
|
603
|
-
for (let attempt = 0; attempt < QUERY_WINDOW_WRITE_ATTEMPTS; attempt += 1) {
|
|
604
|
-
const current = await getPouchDocument(collection, id);
|
|
605
|
-
const currentSnapshot = current ? parseQueryWindowSnapshot(current, {
|
|
606
|
-
fingerprint,
|
|
607
|
-
scope,
|
|
608
|
-
modelName,
|
|
609
|
-
queryKey
|
|
610
|
-
}) : null;
|
|
611
|
-
if (currentSnapshot && (currentSnapshot.serverEpoch === serverEpoch && currentSnapshot.serverVersion !== void 0 && serverVersion !== void 0 && currentSnapshot.serverVersion >= serverVersion || (!serverEpoch || currentSnapshot.serverEpoch !== serverEpoch) && currentSnapshot.storedAt > storedAt)) {
|
|
612
|
-
return currentSnapshot;
|
|
613
|
-
}
|
|
614
|
-
const rev = typeof current?._rev === "string" ? current._rev : void 0;
|
|
615
|
-
const doc = {
|
|
616
|
-
_id: id,
|
|
617
|
-
...rev ? {
|
|
618
|
-
_rev: rev
|
|
619
|
-
} : {},
|
|
620
|
-
type: QUERY_WINDOW_DOC_TYPE,
|
|
621
|
-
schemaVersion: QUERY_WINDOW_SCHEMA_VERSION,
|
|
622
|
-
fingerprint,
|
|
623
|
-
scope,
|
|
624
|
-
modelName,
|
|
625
|
-
queryKey,
|
|
626
|
-
data,
|
|
627
|
-
pageInfo,
|
|
628
|
-
...totalCount !== void 0 ? {
|
|
629
|
-
totalCount
|
|
630
|
-
} : {},
|
|
631
|
-
requestedCount,
|
|
632
|
-
...serverEpoch !== void 0 ? {
|
|
633
|
-
serverEpoch
|
|
634
|
-
} : {},
|
|
635
|
-
...serverVersion !== void 0 ? {
|
|
636
|
-
serverVersion
|
|
637
|
-
} : {},
|
|
638
|
-
storedAt
|
|
639
|
-
};
|
|
640
|
-
try {
|
|
641
|
-
await collection.put(doc);
|
|
642
|
-
return snapshot;
|
|
643
|
-
} catch (error) {
|
|
644
|
-
if (getPouchErrorStatus(error) !== 409) throw error;
|
|
645
|
-
lastConflict = error;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
throw lastConflict ?? new Error("writeQueryWindowSnapshot: failed to persist snapshot");
|
|
649
|
-
};
|
|
650
|
-
const invalidateQueryWindowSnapshots = async ({
|
|
651
|
-
uid,
|
|
652
|
-
modelName
|
|
653
|
-
}) => {
|
|
654
|
-
const collection = await getCollection(QUERY_WINDOW_COLLECTION, {
|
|
655
|
-
uid
|
|
656
|
-
});
|
|
657
|
-
const selector = {
|
|
658
|
-
type: QUERY_WINDOW_DOC_TYPE,
|
|
659
|
-
...modelName ? {
|
|
660
|
-
modelName
|
|
661
|
-
} : {}
|
|
662
|
-
};
|
|
663
|
-
for (let batch = 0; batch < 32; batch += 1) {
|
|
664
|
-
const {
|
|
665
|
-
docs
|
|
666
|
-
} = await collection.find({
|
|
667
|
-
selector,
|
|
668
|
-
fields: ["_id", "_rev"],
|
|
669
|
-
limit: DEFAULT_FIND_LIMIT
|
|
670
|
-
});
|
|
671
|
-
const deletions = docs.map((doc) => ({
|
|
672
|
-
_id: typeof doc._id === "string" ? doc._id : "",
|
|
673
|
-
_rev: typeof doc._rev === "string" ? doc._rev : "",
|
|
674
|
-
_deleted: true
|
|
675
|
-
})).filter((doc) => doc._id && doc._rev);
|
|
676
|
-
if (!deletions.length) return;
|
|
677
|
-
const results = await collection.bulkDocs(deletions);
|
|
678
|
-
const hasConflict = Array.isArray(results) && results.some((result) => {
|
|
679
|
-
if (!result || typeof result !== "object") return false;
|
|
680
|
-
const record = result;
|
|
681
|
-
return record.error === true || record.status === 409;
|
|
682
|
-
});
|
|
683
|
-
if (hasConflict) continue;
|
|
684
|
-
if (docs.length < DEFAULT_FIND_LIMIT) return;
|
|
685
|
-
}
|
|
686
|
-
};
|
|
687
|
-
const replaceQueryKeys = (value, replaceKey) => {
|
|
688
|
-
if (typeof value !== "object" || value === null) {
|
|
689
|
-
return value;
|
|
690
|
-
}
|
|
691
|
-
if (Array.isArray(value)) {
|
|
692
|
-
return value.map((item) => replaceQueryKeys(item, replaceKey));
|
|
693
|
-
}
|
|
694
|
-
const obj = value;
|
|
695
|
-
const next = Object.create(Object.getPrototypeOf(obj));
|
|
696
|
-
for (const key of Object.keys(obj)) {
|
|
697
|
-
if (/^\$/.test(key) || /\.\d+$/.test(key)) {
|
|
698
|
-
throw new Error(`replaceQueryKeys: Unexpected key format: ${key}`);
|
|
699
|
-
}
|
|
700
|
-
const newKey = replaceKey(key);
|
|
701
|
-
next[newKey] = replaceQueryKeys(obj[key], replaceKey);
|
|
702
|
-
}
|
|
703
|
-
return next;
|
|
704
|
-
};
|
|
705
|
-
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
706
|
-
const splitPath = (path) => path.split(".").map((part) => part.trim()).filter(Boolean);
|
|
707
|
-
const getKeys = (obj, parentKey = "") => {
|
|
708
|
-
const keys = [];
|
|
709
|
-
for (const key of Object.keys(obj)) {
|
|
710
|
-
const nextKey = parentKey ? `${parentKey}.${key}` : key;
|
|
711
|
-
const value = obj[key];
|
|
712
|
-
if (Array.isArray(value)) {
|
|
713
|
-
const arrayKeys = /* @__PURE__ */ new Set();
|
|
714
|
-
for (const item of value) {
|
|
715
|
-
if (isRecord(item)) {
|
|
716
|
-
for (const nestedKey of getKeys(item, nextKey)) {
|
|
717
|
-
arrayKeys.add(nestedKey);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
if (arrayKeys.size > 0) {
|
|
722
|
-
keys.push(...arrayKeys);
|
|
723
|
-
} else {
|
|
724
|
-
keys.push(nextKey);
|
|
725
|
-
}
|
|
726
|
-
} else if (isRecord(value)) {
|
|
727
|
-
keys.push(...getKeys(value, nextKey));
|
|
728
|
-
} else {
|
|
729
|
-
keys.push(nextKey);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
return keys;
|
|
733
|
-
};
|
|
734
|
-
const satisfiesProjection = (doc, projection) => {
|
|
735
|
-
const docKeys = new Set(getKeys(doc));
|
|
736
|
-
const projectionKeys = new Set(Object.keys(projection).filter((key) => projection[key] === 1));
|
|
737
|
-
if (!projectionKeys.has("_id")) {
|
|
738
|
-
docKeys.delete("_id");
|
|
739
|
-
}
|
|
740
|
-
if (projectionKeys.size > docKeys.size) return false;
|
|
741
|
-
for (const key of projectionKeys) {
|
|
742
|
-
if (!docKeys.has(key)) return false;
|
|
743
|
-
}
|
|
744
|
-
return true;
|
|
745
|
-
};
|
|
746
|
-
const collectValuesAtPath = (value, parts, index) => {
|
|
747
|
-
if (index >= parts.length) return [value];
|
|
748
|
-
if (Array.isArray(value)) {
|
|
749
|
-
return value.flatMap((item) => collectValuesAtPath(item, parts, index));
|
|
750
|
-
}
|
|
751
|
-
if (!isRecord(value)) return [void 0];
|
|
752
|
-
return collectValuesAtPath(value[parts[index]], parts, index + 1);
|
|
753
|
-
};
|
|
754
|
-
const getValueAtPath = (doc, path) => {
|
|
755
|
-
const parts = splitPath(path);
|
|
756
|
-
if (!parts.length) return void 0;
|
|
757
|
-
const values = collectValuesAtPath(doc, parts, 0);
|
|
758
|
-
if (values.length === 0) return void 0;
|
|
759
|
-
if (values.length === 1) return values[0];
|
|
760
|
-
return values;
|
|
761
|
-
};
|
|
762
|
-
const pathCrossesArrayBeforeLeaf = (value, parts, index = 0) => {
|
|
763
|
-
if (index >= parts.length - 1) return false;
|
|
764
|
-
if (!isRecord(value)) return false;
|
|
765
|
-
const next = value[parts[index]];
|
|
766
|
-
if (Array.isArray(next)) return true;
|
|
767
|
-
return pathCrossesArrayBeforeLeaf(next, parts, index + 1);
|
|
768
|
-
};
|
|
769
|
-
const assignValueAtPath = (current, parts, index, value, valuesQueue) => {
|
|
770
|
-
const key = parts[index];
|
|
771
|
-
if (index === parts.length - 1) {
|
|
772
|
-
current[key] = valuesQueue ? valuesQueue.shift() : value;
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
const next = current[key];
|
|
776
|
-
if (Array.isArray(next)) {
|
|
777
|
-
for (const item of next) {
|
|
778
|
-
if (isRecord(item)) {
|
|
779
|
-
assignValueAtPath(item, parts, index + 1, value, valuesQueue);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
if (!isRecord(next)) {
|
|
785
|
-
current[key] = {};
|
|
786
|
-
}
|
|
787
|
-
assignValueAtPath(current[key], parts, index + 1, value, valuesQueue);
|
|
788
|
-
};
|
|
789
|
-
const setValueAtPath = (doc, path, value) => {
|
|
790
|
-
const parts = splitPath(path);
|
|
791
|
-
if (!parts.length) return;
|
|
792
|
-
const valuesQueue = pathCrossesArrayBeforeLeaf(doc, parts) && Array.isArray(value) ? [...value] : void 0;
|
|
793
|
-
assignValueAtPath(doc, parts, 0, value, valuesQueue);
|
|
794
|
-
};
|
|
795
|
-
const unsetValueAtPathParts = (current, parts, index) => {
|
|
796
|
-
if (Array.isArray(current)) {
|
|
797
|
-
for (const item of current) {
|
|
798
|
-
unsetValueAtPathParts(item, parts, index);
|
|
799
|
-
}
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
if (!isRecord(current)) return;
|
|
803
|
-
const key = parts[index];
|
|
804
|
-
if (index === parts.length - 1) {
|
|
805
|
-
delete current[key];
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
unsetValueAtPathParts(current[key], parts, index + 1);
|
|
809
|
-
};
|
|
810
|
-
const unsetValueAtPath = (doc, path) => {
|
|
811
|
-
const parts = splitPath(path);
|
|
812
|
-
if (!parts.length) return;
|
|
813
|
-
unsetValueAtPathParts(doc, parts, 0);
|
|
814
|
-
};
|
|
815
|
-
const cloneDoc = (doc) => {
|
|
816
|
-
try {
|
|
817
|
-
return structuredClone(doc);
|
|
818
|
-
} catch {
|
|
819
|
-
return JSON.parse(JSON.stringify(doc));
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
const toProjectionSpec = (projection) => {
|
|
823
|
-
const spec = {};
|
|
824
|
-
for (const [key, value] of Object.entries(projection)) {
|
|
825
|
-
const path = key.trim();
|
|
826
|
-
if (!path) continue;
|
|
827
|
-
if (value === 1 || value === 0) {
|
|
828
|
-
spec[path] = value;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
return spec;
|
|
832
|
-
};
|
|
833
|
-
const copyValueAtPath = (source, target, parts, index = 0) => {
|
|
834
|
-
if (!isRecord(source)) return false;
|
|
835
|
-
const key = parts[index];
|
|
836
|
-
const sourceValue = source[key];
|
|
837
|
-
if (sourceValue === void 0) return false;
|
|
838
|
-
if (index === parts.length - 1) {
|
|
839
|
-
target[key] = cloneDoc(sourceValue);
|
|
840
|
-
return true;
|
|
841
|
-
}
|
|
842
|
-
if (Array.isArray(sourceValue)) {
|
|
843
|
-
const existing = Array.isArray(target[key]) ? target[key] : [];
|
|
844
|
-
let copiedAny = false;
|
|
845
|
-
const nextArray = sourceValue.map((item, itemIndex) => {
|
|
846
|
-
const currentTarget = isRecord(existing[itemIndex]) ? existing[itemIndex] : {};
|
|
847
|
-
if (copyValueAtPath(item, currentTarget, parts, index + 1)) {
|
|
848
|
-
copiedAny = true;
|
|
849
|
-
}
|
|
850
|
-
return currentTarget;
|
|
851
|
-
});
|
|
852
|
-
if (copiedAny || sourceValue.length === 0) {
|
|
853
|
-
target[key] = nextArray;
|
|
854
|
-
return true;
|
|
855
|
-
}
|
|
856
|
-
return false;
|
|
857
|
-
}
|
|
858
|
-
const nextTarget = isRecord(target[key]) ? target[key] : {};
|
|
859
|
-
if (!copyValueAtPath(sourceValue, nextTarget, parts, index + 1)) return false;
|
|
860
|
-
target[key] = nextTarget;
|
|
861
|
-
return true;
|
|
862
|
-
};
|
|
863
|
-
const applyProjection = (doc, projection) => {
|
|
864
|
-
const spec = toProjectionSpec(projection);
|
|
865
|
-
const includeKeys = Object.keys(spec).filter((key) => spec[key] === 1);
|
|
866
|
-
const excludeKeys = Object.keys(spec).filter((key) => spec[key] === 0);
|
|
867
|
-
if (includeKeys.length > 0) {
|
|
868
|
-
const projected2 = {};
|
|
869
|
-
for (const key of includeKeys) {
|
|
870
|
-
const parts = splitPath(key);
|
|
871
|
-
if (parts.length > 0) copyValueAtPath(doc, projected2, parts);
|
|
872
|
-
}
|
|
873
|
-
if (spec._id !== 0 && Object.hasOwn(doc, "_id")) {
|
|
874
|
-
projected2._id = doc._id;
|
|
875
|
-
}
|
|
876
|
-
return projected2;
|
|
877
|
-
}
|
|
878
|
-
const projected = cloneDoc(doc);
|
|
879
|
-
for (const key of excludeKeys) {
|
|
880
|
-
unsetValueAtPath(projected, key);
|
|
881
|
-
}
|
|
882
|
-
return projected;
|
|
883
|
-
};
|
|
884
|
-
const compareSort = (a, b, sort) => {
|
|
885
|
-
for (const key of Object.keys(sort)) {
|
|
886
|
-
const dir = sort[key];
|
|
887
|
-
const aVal = getValueAtPath(a, key);
|
|
888
|
-
const bVal = getValueAtPath(b, key);
|
|
889
|
-
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
890
|
-
if (aVal < bVal) return -1 * dir;
|
|
891
|
-
if (aVal > bVal) return 1 * dir;
|
|
892
|
-
continue;
|
|
893
|
-
}
|
|
894
|
-
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
895
|
-
if (aVal < bVal) return -1 * dir;
|
|
896
|
-
if (aVal > bVal) return 1 * dir;
|
|
897
|
-
continue;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
return 0;
|
|
901
|
-
};
|
|
902
|
-
const extractDocId = (value) => {
|
|
903
|
-
if (typeof value === "string") return value;
|
|
904
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return "";
|
|
905
|
-
const id = value._id;
|
|
906
|
-
return typeof id === "string" ? id : "";
|
|
907
|
-
};
|
|
908
|
-
const matchValue = (docValue, matchValueRaw) => {
|
|
909
|
-
if (Array.isArray(matchValueRaw)) {
|
|
910
|
-
if (!Array.isArray(docValue)) return false;
|
|
911
|
-
if (docValue.length !== matchValueRaw.length) return false;
|
|
912
|
-
for (let i = 0; i < matchValueRaw.length; i += 1) {
|
|
913
|
-
const matched = matchValue(docValue[i], matchValueRaw[i]);
|
|
914
|
-
if (matched === null) return null;
|
|
915
|
-
if (!matched) return false;
|
|
916
|
-
}
|
|
917
|
-
return true;
|
|
918
|
-
}
|
|
919
|
-
if (matchValueRaw && typeof matchValueRaw === "object") {
|
|
920
|
-
const matchObj = matchValueRaw;
|
|
921
|
-
if (Object.keys(matchObj).some((key) => key.startsWith("$"))) return null;
|
|
922
|
-
if (!docValue || typeof docValue !== "object" || Array.isArray(docValue)) return false;
|
|
923
|
-
const docObj = docValue;
|
|
924
|
-
for (const key of Object.keys(matchObj)) {
|
|
925
|
-
const matched = matchValue(docObj[key], matchObj[key]);
|
|
926
|
-
if (matched === null) return null;
|
|
927
|
-
if (!matched) return false;
|
|
928
|
-
}
|
|
929
|
-
return true;
|
|
930
|
-
}
|
|
931
|
-
return Object.is(docValue, matchValueRaw);
|
|
932
|
-
};
|
|
933
|
-
const matchesSimpleQuery = (doc, query) => {
|
|
934
|
-
for (const [key, expected] of Object.entries(query)) {
|
|
935
|
-
if (key.startsWith("$")) return null;
|
|
936
|
-
const actual = getValueAtPath(doc, key);
|
|
937
|
-
const matched = matchValue(actual, expected);
|
|
938
|
-
if (matched === null) return null;
|
|
939
|
-
if (!matched) return false;
|
|
940
|
-
}
|
|
941
|
-
return true;
|
|
942
|
-
};
|
|
943
|
-
const remapDocFromStorage = (doc) => {
|
|
944
|
-
const next = {};
|
|
945
|
-
for (const [key, value] of Object.entries(doc)) {
|
|
946
|
-
const newKey = key.startsWith(UNDERSCORE_PREFIX) ? key.replace(/^\$_/, "") : key;
|
|
947
|
-
next[newKey] = value;
|
|
948
|
-
}
|
|
949
|
-
return next;
|
|
950
|
-
};
|
|
951
|
-
const runQueryInternal = async ({
|
|
952
|
-
modelName,
|
|
953
|
-
query = {},
|
|
954
|
-
options,
|
|
955
|
-
strictProjection = false
|
|
956
|
-
}) => {
|
|
957
|
-
const collection = await getCollection(modelName, {
|
|
958
|
-
uid: options.uid
|
|
959
|
-
});
|
|
960
|
-
const replacedQuery = replaceQueryKeys(query, (key) => key.startsWith("_") && key !== "_id" ? `${UNDERSCORE_PREFIX}${key}` : key);
|
|
961
|
-
const limit = typeof options.limit === "number" ? Math.abs(options.limit) : DEFAULT_FIND_LIMIT;
|
|
962
|
-
const {
|
|
963
|
-
docs
|
|
964
|
-
} = await collection.find({
|
|
965
|
-
selector: replacedQuery,
|
|
966
|
-
limit
|
|
967
|
-
});
|
|
968
|
-
const mappedDocs = docs.map(({
|
|
969
|
-
_rev: _revIgnored,
|
|
970
|
-
...rest
|
|
971
|
-
}) => remapDocFromStorage(rest));
|
|
972
|
-
let filteredDocs = mappedDocs;
|
|
973
|
-
if (options.projection) {
|
|
974
|
-
if (strictProjection && mappedDocs.some((entry) => !satisfiesProjection(entry, options.projection))) {
|
|
975
|
-
return {
|
|
976
|
-
data: [],
|
|
977
|
-
context: {
|
|
978
|
-
source: "cache"
|
|
979
|
-
},
|
|
980
|
-
projectionMismatch: true
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
filteredDocs = filteredDocs.filter((entry) => satisfiesProjection(entry, options.projection));
|
|
984
|
-
}
|
|
985
|
-
let result = filteredDocs;
|
|
986
|
-
if (options.sort) {
|
|
987
|
-
result = result.sort((a, b) => compareSort(a, b, options.sort));
|
|
988
|
-
}
|
|
989
|
-
return {
|
|
990
|
-
data: result,
|
|
991
|
-
context: {
|
|
992
|
-
source: "cache"
|
|
993
|
-
},
|
|
994
|
-
projectionMismatch: false
|
|
995
|
-
};
|
|
996
|
-
};
|
|
997
|
-
const runQuery = async ({
|
|
998
|
-
modelName,
|
|
999
|
-
query = {},
|
|
1000
|
-
options
|
|
1001
|
-
}) => {
|
|
1002
|
-
const result = await runQueryInternal({
|
|
1003
|
-
modelName,
|
|
1004
|
-
query,
|
|
1005
|
-
options
|
|
1006
|
-
});
|
|
1007
|
-
return {
|
|
1008
|
-
data: result.data,
|
|
1009
|
-
context: result.context
|
|
1010
|
-
};
|
|
1011
|
-
};
|
|
1012
|
-
const addWriteDoc = (writes, modelName, doc) => {
|
|
1013
|
-
const docsById = writes.get(modelName) ?? /* @__PURE__ */ new Map();
|
|
1014
|
-
docsById.set(doc._id, doc);
|
|
1015
|
-
writes.set(modelName, docsById);
|
|
1016
|
-
};
|
|
1017
|
-
const sanitizePopulatedDoc = (doc, populate, writes) => {
|
|
1018
|
-
const next = cloneDoc(doc);
|
|
1019
|
-
for (const entry of populate) {
|
|
1020
|
-
const pathParts = splitPath(entry.path);
|
|
1021
|
-
const crossesArray = pathCrossesArrayBeforeLeaf(next, pathParts);
|
|
1022
|
-
const value = getValueAtPath(next, entry.path);
|
|
1023
|
-
if (value === void 0) continue;
|
|
1024
|
-
if (Array.isArray(value)) {
|
|
1025
|
-
const ids = [];
|
|
1026
|
-
const nextValues = [];
|
|
1027
|
-
for (const candidate of value) {
|
|
1028
|
-
const id2 = extractDocId(candidate);
|
|
1029
|
-
if (id2) ids.push(id2);
|
|
1030
|
-
if (crossesArray) nextValues.push(id2 || null);
|
|
1031
|
-
if (!entry.model) continue;
|
|
1032
|
-
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
|
|
1033
|
-
const candidateDoc = candidate;
|
|
1034
|
-
const nested2 = entry.populate?.length ? sanitizePopulatedDoc(candidateDoc, entry.populate, writes) : cloneDoc(candidateDoc);
|
|
1035
|
-
addWriteDoc(writes, entry.model, nested2);
|
|
1036
|
-
}
|
|
1037
|
-
setValueAtPath(next, entry.path, crossesArray ? nextValues : ids);
|
|
1038
|
-
continue;
|
|
1039
|
-
}
|
|
1040
|
-
const id = extractDocId(value);
|
|
1041
|
-
setValueAtPath(next, entry.path, id || null);
|
|
1042
|
-
if (!id) continue;
|
|
1043
|
-
if (!entry.model) continue;
|
|
1044
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
1045
|
-
const valueDoc = value;
|
|
1046
|
-
const nested = entry.populate?.length ? sanitizePopulatedDoc(valueDoc, entry.populate, writes) : cloneDoc(valueDoc);
|
|
1047
|
-
addWriteDoc(writes, entry.model, nested);
|
|
1048
|
-
}
|
|
1049
|
-
return next;
|
|
1050
|
-
};
|
|
1051
|
-
const updatePopulatedDocs = async ({
|
|
1052
|
-
modelName,
|
|
1053
|
-
data,
|
|
1054
|
-
uid,
|
|
1055
|
-
populate
|
|
1056
|
-
}) => {
|
|
1057
|
-
if (!data.length) return;
|
|
1058
|
-
const writes = /* @__PURE__ */ new Map();
|
|
1059
|
-
const roots = data.map((doc) => sanitizePopulatedDoc(doc, populate, writes));
|
|
1060
|
-
await updateDocs(modelName, roots, uid);
|
|
1061
|
-
for (const [targetModelName, docsById] of writes.entries()) {
|
|
1062
|
-
if (!docsById.size) continue;
|
|
1063
|
-
await updateDocs(targetModelName, Array.from(docsById.values()), uid);
|
|
1064
|
-
}
|
|
1065
|
-
};
|
|
1066
|
-
const loadProjectedDocsByIds = async ({
|
|
1067
|
-
modelName,
|
|
1068
|
-
ids,
|
|
1069
|
-
uid,
|
|
1070
|
-
projection
|
|
1071
|
-
}) => {
|
|
1072
|
-
const uniqueIds = Array.from(new Set(ids.filter(Boolean)));
|
|
1073
|
-
if (!uniqueIds.length) {
|
|
1074
|
-
return {
|
|
1075
|
-
rawDocsById: /* @__PURE__ */ new Map(),
|
|
1076
|
-
projectedDocsById: /* @__PURE__ */ new Map(),
|
|
1077
|
-
projectionMismatch: false
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
const collection = await getCollection(modelName, {
|
|
1081
|
-
uid
|
|
1082
|
-
});
|
|
1083
|
-
const {
|
|
1084
|
-
docs
|
|
1085
|
-
} = await collection.find({
|
|
1086
|
-
selector: {
|
|
1087
|
-
_id: {
|
|
1088
|
-
$in: uniqueIds
|
|
1089
|
-
}
|
|
1090
|
-
},
|
|
1091
|
-
limit: uniqueIds.length
|
|
1092
|
-
});
|
|
1093
|
-
const rawDocsById = /* @__PURE__ */ new Map();
|
|
1094
|
-
const projectedDocsById = /* @__PURE__ */ new Map();
|
|
1095
|
-
let projectionMismatch = false;
|
|
1096
|
-
for (const rawDoc of docs) {
|
|
1097
|
-
const id = typeof rawDoc._id === "string" ? rawDoc._id : "";
|
|
1098
|
-
if (!id) continue;
|
|
1099
|
-
const {
|
|
1100
|
-
_rev: _revIgnored,
|
|
1101
|
-
...rest
|
|
1102
|
-
} = rawDoc;
|
|
1103
|
-
const remapped = remapDocFromStorage(rest);
|
|
1104
|
-
if (!satisfiesProjection(remapped, projection)) {
|
|
1105
|
-
projectionMismatch = true;
|
|
1106
|
-
continue;
|
|
1107
|
-
}
|
|
1108
|
-
rawDocsById.set(id, remapped);
|
|
1109
|
-
projectedDocsById.set(id, applyProjection(remapped, projection));
|
|
1110
|
-
}
|
|
1111
|
-
if (projectedDocsById.size < uniqueIds.length) {
|
|
1112
|
-
projectionMismatch = true;
|
|
1113
|
-
}
|
|
1114
|
-
return {
|
|
1115
|
-
rawDocsById,
|
|
1116
|
-
projectedDocsById,
|
|
1117
|
-
projectionMismatch
|
|
1118
|
-
};
|
|
1119
|
-
};
|
|
1120
|
-
const hydratePopulateEntries = async ({
|
|
1121
|
-
docs,
|
|
1122
|
-
populate,
|
|
1123
|
-
uid
|
|
1124
|
-
}) => {
|
|
1125
|
-
const currentDocs = docs;
|
|
1126
|
-
for (const entry of populate) {
|
|
1127
|
-
if (!entry.model) {
|
|
1128
|
-
return {
|
|
1129
|
-
hit: false,
|
|
1130
|
-
data: []
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
const descriptors = currentDocs.map((doc) => {
|
|
1134
|
-
const pathParts = splitPath(entry.path);
|
|
1135
|
-
const crossesArray = pathCrossesArrayBeforeLeaf(doc, pathParts);
|
|
1136
|
-
const rawValue = getValueAtPath(doc, entry.path);
|
|
1137
|
-
const isArray = Array.isArray(rawValue);
|
|
1138
|
-
const values = isArray ? rawValue : [rawValue];
|
|
1139
|
-
const ids = values.map((candidate) => extractDocId(candidate)).filter(Boolean);
|
|
1140
|
-
return {
|
|
1141
|
-
doc,
|
|
1142
|
-
hasValue: rawValue !== void 0,
|
|
1143
|
-
isArray,
|
|
1144
|
-
crossesArray,
|
|
1145
|
-
values,
|
|
1146
|
-
ids
|
|
1147
|
-
};
|
|
1148
|
-
});
|
|
1149
|
-
const allIds = descriptors.flatMap((descriptor) => descriptor.ids);
|
|
1150
|
-
const loaded = await loadProjectedDocsByIds({
|
|
1151
|
-
modelName: entry.model,
|
|
1152
|
-
ids: allIds,
|
|
1153
|
-
uid,
|
|
1154
|
-
projection: entry.select
|
|
1155
|
-
});
|
|
1156
|
-
if (loaded.projectionMismatch) {
|
|
1157
|
-
return {
|
|
1158
|
-
hit: false,
|
|
1159
|
-
data: []
|
|
1160
|
-
};
|
|
1161
|
-
}
|
|
1162
|
-
let populatedDocsById = loaded.projectedDocsById;
|
|
1163
|
-
if (entry.match) {
|
|
1164
|
-
const filtered = /* @__PURE__ */ new Map();
|
|
1165
|
-
for (const [id, candidate] of loaded.rawDocsById.entries()) {
|
|
1166
|
-
const matched = matchesSimpleQuery(candidate, entry.match);
|
|
1167
|
-
if (matched === null) return {
|
|
1168
|
-
hit: false,
|
|
1169
|
-
data: []
|
|
1170
|
-
};
|
|
1171
|
-
if (!matched) continue;
|
|
1172
|
-
const projected = populatedDocsById.get(id);
|
|
1173
|
-
if (projected) filtered.set(id, projected);
|
|
1174
|
-
}
|
|
1175
|
-
populatedDocsById = filtered;
|
|
1176
|
-
}
|
|
1177
|
-
if (entry.populate?.length) {
|
|
1178
|
-
const nestedResult = await hydratePopulateEntries({
|
|
1179
|
-
docs: Array.from(populatedDocsById.values()).map((candidate) => cloneDoc(candidate)),
|
|
1180
|
-
populate: entry.populate,
|
|
1181
|
-
uid
|
|
1182
|
-
});
|
|
1183
|
-
if (!nestedResult.hit) {
|
|
1184
|
-
return {
|
|
1185
|
-
hit: false,
|
|
1186
|
-
data: []
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
populatedDocsById = nestedResult.data.reduce((acc, candidate) => {
|
|
1190
|
-
const id = extractDocId(candidate);
|
|
1191
|
-
if (id) acc.set(id, candidate);
|
|
1192
|
-
return acc;
|
|
1193
|
-
}, /* @__PURE__ */ new Map());
|
|
1194
|
-
}
|
|
1195
|
-
for (const descriptor of descriptors) {
|
|
1196
|
-
if (!descriptor.hasValue) continue;
|
|
1197
|
-
if (descriptor.isArray && descriptor.crossesArray) {
|
|
1198
|
-
const values = descriptor.values.map((candidate) => {
|
|
1199
|
-
const id = extractDocId(candidate);
|
|
1200
|
-
return id ? populatedDocsById.get(id) ?? null : null;
|
|
1201
|
-
});
|
|
1202
|
-
setValueAtPath(descriptor.doc, entry.path, values);
|
|
1203
|
-
continue;
|
|
1204
|
-
}
|
|
1205
|
-
if (!descriptor.ids.length) {
|
|
1206
|
-
setValueAtPath(descriptor.doc, entry.path, descriptor.isArray ? [] : null);
|
|
1207
|
-
continue;
|
|
1208
|
-
}
|
|
1209
|
-
if (descriptor.isArray) {
|
|
1210
|
-
let values = descriptor.ids.map((id) => populatedDocsById.get(id)).filter((candidate) => Boolean(candidate));
|
|
1211
|
-
if (entry.options?.sort) {
|
|
1212
|
-
values = values.sort((a, b) => compareSort(a, b, entry.options.sort));
|
|
1213
|
-
}
|
|
1214
|
-
if (typeof entry.options?.limit === "number" && Number.isFinite(entry.options.limit)) {
|
|
1215
|
-
values = values.slice(0, Math.max(0, Math.floor(Math.abs(entry.options.limit))));
|
|
1216
|
-
}
|
|
1217
|
-
setValueAtPath(descriptor.doc, entry.path, values);
|
|
1218
|
-
continue;
|
|
1219
|
-
}
|
|
1220
|
-
const value = populatedDocsById.get(descriptor.ids[0]);
|
|
1221
|
-
setValueAtPath(descriptor.doc, entry.path, value ?? null);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
return {
|
|
1225
|
-
hit: true,
|
|
1226
|
-
data: currentDocs
|
|
1227
|
-
};
|
|
1228
|
-
};
|
|
1229
|
-
const runPopulatedQuery = async ({
|
|
1230
|
-
modelName,
|
|
1231
|
-
query = {},
|
|
1232
|
-
options
|
|
1233
|
-
}) => {
|
|
1234
|
-
const rootResult = await runQueryInternal({
|
|
1235
|
-
modelName,
|
|
1236
|
-
query,
|
|
1237
|
-
options: {
|
|
1238
|
-
uid: options.uid,
|
|
1239
|
-
projection: options.projection,
|
|
1240
|
-
sort: options.sort,
|
|
1241
|
-
limit: options.limit
|
|
1242
|
-
},
|
|
1243
|
-
strictProjection: true
|
|
1244
|
-
});
|
|
1245
|
-
if (rootResult.projectionMismatch) {
|
|
1246
|
-
return {
|
|
1247
|
-
hit: false,
|
|
1248
|
-
data: [],
|
|
1249
|
-
context: rootResult.context
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
|
-
const projectedRoots = rootResult.data.map((doc) => applyProjection(doc, options.projection));
|
|
1253
|
-
const hydrated = await hydratePopulateEntries({
|
|
1254
|
-
docs: projectedRoots.map((doc) => cloneDoc(doc)),
|
|
1255
|
-
populate: options.populate,
|
|
1256
|
-
uid: options.uid
|
|
1257
|
-
});
|
|
1258
|
-
if (!hydrated.hit) {
|
|
1259
|
-
return {
|
|
1260
|
-
hit: false,
|
|
1261
|
-
data: [],
|
|
1262
|
-
context: rootResult.context
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
return {
|
|
1266
|
-
hit: true,
|
|
1267
|
-
data: hydrated.data,
|
|
1268
|
-
context: rootResult.context
|
|
1269
|
-
};
|
|
1270
|
-
};
|
|
1271
|
-
const updateDocs = async (modelName, data, uid) => {
|
|
1272
|
-
const collection = await getCollection(modelName, {
|
|
1273
|
-
uid
|
|
1274
|
-
});
|
|
1275
|
-
const allIds = data.map((doc) => doc._id).filter(Boolean);
|
|
1276
|
-
if (!allIds.length) return;
|
|
1277
|
-
const {
|
|
1278
|
-
docs: currentDocs
|
|
1279
|
-
} = await collection.find({
|
|
1280
|
-
selector: {
|
|
1281
|
-
_id: {
|
|
1282
|
-
$in: allIds
|
|
1283
|
-
}
|
|
1284
|
-
},
|
|
1285
|
-
fields: ["_id", "_rev"],
|
|
1286
|
-
limit: allIds.length
|
|
1287
|
-
});
|
|
1288
|
-
const currentDocsById = currentDocs.reduce((acc, doc) => {
|
|
1289
|
-
const id = String(doc._id ?? "");
|
|
1290
|
-
if (id) acc[id] = doc;
|
|
1291
|
-
return acc;
|
|
1292
|
-
}, {});
|
|
1293
|
-
const newDocs = data.map((mongoDoc) => {
|
|
1294
|
-
const currentDoc = currentDocsById[mongoDoc._id] ?? {
|
|
1295
|
-
_id: mongoDoc._id
|
|
1296
|
-
};
|
|
1297
|
-
const nextDoc = Object.entries(mongoDoc).reduce((acc, [key, value]) => {
|
|
1298
|
-
const newKey = key !== "_id" && key.startsWith("_") ? `${UNDERSCORE_PREFIX}${key}` : key;
|
|
1299
|
-
acc[newKey] = value;
|
|
1300
|
-
return acc;
|
|
1301
|
-
}, {
|
|
1302
|
-
...currentDoc
|
|
1303
|
-
});
|
|
1304
|
-
const rev = currentDoc._rev;
|
|
1305
|
-
if (typeof rev === "string" && rev) {
|
|
1306
|
-
nextDoc._rev = rev;
|
|
1307
|
-
} else {
|
|
1308
|
-
delete nextDoc._rev;
|
|
1309
|
-
}
|
|
1310
|
-
return nextDoc;
|
|
1311
|
-
});
|
|
1312
|
-
await collection.bulkDocs(newDocs);
|
|
1313
|
-
};
|
|
1314
|
-
const deleteDocs = async (modelName, ids, uid) => {
|
|
1315
|
-
const collection = await getCollection(modelName, {
|
|
1316
|
-
uid
|
|
1317
|
-
});
|
|
1318
|
-
const allIds = ids.map((id) => String(id ?? "")).filter(Boolean);
|
|
1319
|
-
if (!allIds.length) return;
|
|
1320
|
-
const {
|
|
1321
|
-
docs: currentDocs
|
|
1322
|
-
} = await collection.find({
|
|
1323
|
-
selector: {
|
|
1324
|
-
_id: {
|
|
1325
|
-
$in: allIds
|
|
1326
|
-
}
|
|
1327
|
-
},
|
|
1328
|
-
fields: ["_id", "_rev"],
|
|
1329
|
-
limit: allIds.length
|
|
1330
|
-
});
|
|
1331
|
-
const deletions = currentDocs.map((doc) => ({
|
|
1332
|
-
_id: String(doc?._id ?? ""),
|
|
1333
|
-
_rev: doc?._rev,
|
|
1334
|
-
_deleted: true
|
|
1335
|
-
})).filter((doc) => doc._id && typeof doc._rev === "string" && doc._rev);
|
|
1336
|
-
if (!deletions.length) return;
|
|
1337
|
-
await collection.bulkDocs(deletions);
|
|
1338
|
-
};
|
|
1339
|
-
const destroyCollection = async (modelName, uid) => {
|
|
1340
|
-
const collection = await getCollection(modelName, {
|
|
1341
|
-
uid
|
|
1342
|
-
});
|
|
1343
|
-
const prefix = getPrefix();
|
|
1344
|
-
const dbName = `${uid}/${modelName}`;
|
|
1345
|
-
collections.delete(`${prefix}${dbName}`);
|
|
1346
|
-
unregisterDbName(prefix, dbName);
|
|
1347
|
-
await collection.destroy();
|
|
1348
|
-
};
|
|
1349
|
-
const resetRtsPouchStore = ({
|
|
1350
|
-
tenantId,
|
|
1351
|
-
appName
|
|
1352
|
-
}) => {
|
|
1353
|
-
const basePrefix = computeBasePrefix({
|
|
1354
|
-
tenantId,
|
|
1355
|
-
appName
|
|
1356
|
-
});
|
|
1357
|
-
const oldPrefix = readPrefixOverride({
|
|
1358
|
-
tenantId,
|
|
1359
|
-
appName
|
|
1360
|
-
}) ?? basePrefix;
|
|
1361
|
-
const dbNames = Array.from(loadDbNames(oldPrefix));
|
|
1362
|
-
const openDbs = Array.from(collections.entries()).filter(([key]) => key.startsWith(oldPrefix)).map(([, db]) => db);
|
|
1363
|
-
void (async () => {
|
|
1364
|
-
const remaining = new Set(dbNames);
|
|
1365
|
-
await Promise.all(openDbs.map((db) => db.destroy().catch(() => {
|
|
1366
|
-
})));
|
|
1367
|
-
if (remaining.size) {
|
|
1368
|
-
const PouchDB = await getPouchDb();
|
|
1369
|
-
const PouchDBForPrefix = PouchDB.defaults?.({}) ?? PouchDB;
|
|
1370
|
-
PouchDBForPrefix.prefix = oldPrefix;
|
|
1371
|
-
await Promise.all(Array.from(remaining).map(async (name) => {
|
|
1372
|
-
const db = new PouchDBForPrefix(name, {
|
|
1373
|
-
adapter: activePouchAdapter,
|
|
1374
|
-
revs_limit: 1
|
|
1375
|
-
});
|
|
1376
|
-
await db.destroy().then(() => {
|
|
1377
|
-
remaining.delete(name);
|
|
1378
|
-
}).catch(() => {
|
|
1379
|
-
});
|
|
1380
|
-
}));
|
|
1381
|
-
}
|
|
1382
|
-
if (remaining.size) {
|
|
1383
|
-
persistDbNames(oldPrefix, remaining);
|
|
1384
|
-
} else {
|
|
1385
|
-
persistDbNames(oldPrefix, /* @__PURE__ */ new Set());
|
|
1386
|
-
}
|
|
1387
|
-
})();
|
|
1388
|
-
const newPrefix = `${basePrefix}reset-${Date.now().toString(16)}/`;
|
|
1389
|
-
const storage = getRuntimeStorage();
|
|
1390
|
-
try {
|
|
1391
|
-
storage.setItem(getPrefixOverrideKey({
|
|
1392
|
-
tenantId,
|
|
1393
|
-
appName
|
|
1394
|
-
}), newPrefix);
|
|
1395
|
-
} catch {
|
|
1396
|
-
return newPrefix;
|
|
1397
|
-
}
|
|
1398
|
-
lastAppliedPrefix = null;
|
|
1399
|
-
collections.clear();
|
|
1400
|
-
return newPrefix;
|
|
1401
|
-
};
|
|
1402
|
-
const destroyAllCollections = async () => {
|
|
1403
|
-
const dbs = Array.from(collections.values());
|
|
1404
|
-
await Promise.all(dbs.map((db) => db.destroy()));
|
|
1405
|
-
collections.clear();
|
|
1406
|
-
};
|
|
1407
|
-
const EXCLUDE_PROJECTION_ERROR = "must be include-only (value 1); exclusion projection is not supported";
|
|
1408
|
-
const sortProjectionSpec = (projection) => {
|
|
1409
|
-
const sorted = {};
|
|
1410
|
-
for (const key of Object.keys(projection).sort()) {
|
|
1411
|
-
sorted[key] = projection[key];
|
|
1412
|
-
}
|
|
1413
|
-
return sorted;
|
|
1414
|
-
};
|
|
1415
|
-
const normalizeProjectionSpec = (value, source, label) => {
|
|
1416
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1417
|
-
const raw = value;
|
|
1418
|
-
const normalized = {};
|
|
1419
|
-
let hasExclude = false;
|
|
1420
|
-
for (const [key, rawValue] of Object.entries(raw)) {
|
|
1421
|
-
const path = key.trim();
|
|
1422
|
-
if (!path) continue;
|
|
1423
|
-
if (rawValue === 1 || rawValue === true) {
|
|
1424
|
-
normalized[path] = 1;
|
|
1425
|
-
continue;
|
|
1426
|
-
}
|
|
1427
|
-
if (rawValue === 0 || rawValue === false) {
|
|
1428
|
-
hasExclude = true;
|
|
1429
|
-
continue;
|
|
1430
|
-
}
|
|
1431
|
-
if (typeof rawValue === "number" && Number.isFinite(rawValue)) {
|
|
1432
|
-
if (rawValue === 1) normalized[path] = 1;
|
|
1433
|
-
if (rawValue === 0) hasExclude = true;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
if (hasExclude) {
|
|
1437
|
-
throw new Error(`${source}: ${label} ${EXCLUDE_PROJECTION_ERROR}`);
|
|
1438
|
-
}
|
|
1439
|
-
return Object.keys(normalized).length > 0 ? sortProjectionSpec(normalized) : void 0;
|
|
1440
|
-
};
|
|
1441
|
-
const normalizeSelectString = (value, source) => {
|
|
1442
|
-
const tokens = value.split(/\s+/).map((token) => token.trim()).filter(Boolean);
|
|
1443
|
-
if (!tokens.length) return void 0;
|
|
1444
|
-
const normalized = {};
|
|
1445
|
-
for (const token of tokens) {
|
|
1446
|
-
let path = token;
|
|
1447
|
-
if (token.startsWith("-")) {
|
|
1448
|
-
throw new Error(`${source}: populate select ${EXCLUDE_PROJECTION_ERROR}`);
|
|
1449
|
-
} else if (token.startsWith("+")) {
|
|
1450
|
-
path = token.slice(1);
|
|
1451
|
-
}
|
|
1452
|
-
path = path.trim();
|
|
1453
|
-
if (!path) continue;
|
|
1454
|
-
normalized[path] = 1;
|
|
1455
|
-
}
|
|
1456
|
-
return Object.keys(normalized).length > 0 ? sortProjectionSpec(normalized) : void 0;
|
|
1457
|
-
};
|
|
1458
|
-
const normalizePopulateSelect = (value, source) => {
|
|
1459
|
-
if (typeof value === "string") {
|
|
1460
|
-
return normalizeSelectString(value, source);
|
|
1461
|
-
}
|
|
1462
|
-
return normalizeProjectionSpec(value, source, "populate select");
|
|
1463
|
-
};
|
|
1464
|
-
const normalizePopulateOptions = (value) => {
|
|
1465
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1466
|
-
const raw = value;
|
|
1467
|
-
const normalized = {};
|
|
1468
|
-
if (raw.sort && typeof raw.sort === "object" && !Array.isArray(raw.sort)) {
|
|
1469
|
-
const sortRaw = raw.sort;
|
|
1470
|
-
const sort = {};
|
|
1471
|
-
for (const [key, rawDirection] of Object.entries(sortRaw)) {
|
|
1472
|
-
const path = key.trim();
|
|
1473
|
-
if (!path) continue;
|
|
1474
|
-
if (rawDirection === 1 || rawDirection === "asc") {
|
|
1475
|
-
sort[path] = 1;
|
|
1476
|
-
continue;
|
|
1477
|
-
}
|
|
1478
|
-
if (rawDirection === -1 || rawDirection === "desc") {
|
|
1479
|
-
sort[path] = -1;
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
if (Object.keys(sort).length > 0) {
|
|
1483
|
-
normalized.sort = sort;
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
if (typeof raw.limit === "number" && Number.isFinite(raw.limit)) {
|
|
1487
|
-
normalized.limit = Math.max(0, Math.floor(Math.abs(raw.limit)));
|
|
1488
|
-
}
|
|
1489
|
-
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
1490
|
-
};
|
|
1491
|
-
const normalizeString = (value) => {
|
|
1492
|
-
if (typeof value !== "string") return void 0;
|
|
1493
|
-
const normalized = value.trim();
|
|
1494
|
-
return normalized || void 0;
|
|
1495
|
-
};
|
|
1496
|
-
const normalizeObject = (value) => {
|
|
1497
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1498
|
-
return value;
|
|
1499
|
-
};
|
|
1500
|
-
const normalizePopulateObject = (value, source) => {
|
|
1501
|
-
const path = normalizeString(value.path);
|
|
1502
|
-
if (!path) {
|
|
1503
|
-
throw new Error(`${source}: populate entries must define a non-empty path`);
|
|
1504
|
-
}
|
|
1505
|
-
const select = normalizePopulateSelect(value.select, source);
|
|
1506
|
-
if (!select) {
|
|
1507
|
-
throw new Error(`${source}: populate entries must define a select projection`);
|
|
1508
|
-
}
|
|
1509
|
-
const nested = value.populate !== void 0 ? normalizePopulateOption(value.populate, source) : void 0;
|
|
1510
|
-
return {
|
|
1511
|
-
path,
|
|
1512
|
-
select,
|
|
1513
|
-
...normalizeString(value.model) ? {
|
|
1514
|
-
model: normalizeString(value.model)
|
|
1515
|
-
} : {},
|
|
1516
|
-
...normalizeObject(value.match) ? {
|
|
1517
|
-
match: normalizeObject(value.match)
|
|
1518
|
-
} : {},
|
|
1519
|
-
...normalizePopulateOptions(value.options) ? {
|
|
1520
|
-
options: normalizePopulateOptions(value.options)
|
|
1521
|
-
} : {},
|
|
1522
|
-
...nested && nested.length > 0 ? {
|
|
1523
|
-
populate: nested
|
|
1524
|
-
} : {}
|
|
1525
|
-
};
|
|
1526
|
-
};
|
|
1527
|
-
const normalizePopulateOption = (value, source) => {
|
|
1528
|
-
if (typeof value === "string") {
|
|
1529
|
-
throw new Error(`${source}: populate string syntax is not supported; use object entries with select`);
|
|
1530
|
-
}
|
|
1531
|
-
if (Array.isArray(value)) {
|
|
1532
|
-
if (value.length === 0) {
|
|
1533
|
-
throw new Error(`${source}: populate must contain at least one entry`);
|
|
1534
|
-
}
|
|
1535
|
-
return value.map((entry) => {
|
|
1536
|
-
if (typeof entry === "string") {
|
|
1537
|
-
throw new Error(`${source}: populate string syntax is not supported; use object entries with select`);
|
|
1538
|
-
}
|
|
1539
|
-
return normalizePopulateObject(entry, source);
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
return [normalizePopulateObject(value, source)];
|
|
1543
|
-
};
|
|
1544
|
-
const preparePopulateCacheOptions = (options, source) => {
|
|
1545
|
-
if (!options.populate) return void 0;
|
|
1546
|
-
const rootProjection = normalizeProjectionSpec(options.projection, source, "projection");
|
|
1547
|
-
if (!rootProjection) {
|
|
1548
|
-
throw new Error(`${source}: projection is required when populate is used`);
|
|
1549
|
-
}
|
|
1550
|
-
const populate = normalizePopulateOption(options.populate, source);
|
|
1551
|
-
if (!populate.length) {
|
|
1552
|
-
throw new Error(`${source}: populate must contain at least one entry`);
|
|
1553
|
-
}
|
|
1554
|
-
return {
|
|
1555
|
-
rootProjection,
|
|
1556
|
-
populate
|
|
1557
|
-
};
|
|
1558
|
-
};
|
|
1559
|
-
const isPlainObject = (value) => {
|
|
1560
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1561
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1562
|
-
return prototype === Object.prototype || prototype === null;
|
|
1563
|
-
};
|
|
1564
|
-
const serializeRtsQueryValue = (value) => {
|
|
1565
|
-
return JSON.stringify(value, (_key, currentValue) => {
|
|
1566
|
-
if (!isPlainObject(currentValue)) return currentValue;
|
|
1567
|
-
return Object.keys(currentValue).sort().reduce((acc, key) => {
|
|
1568
|
-
acc[key] = currentValue[key];
|
|
1569
|
-
return acc;
|
|
1570
|
-
}, {});
|
|
1571
|
-
}) ?? "";
|
|
1572
|
-
};
|
|
1573
|
-
const computeRtsQueryKey = (query, options) => {
|
|
1574
|
-
return serializeRtsQueryValue({
|
|
1575
|
-
version: 2,
|
|
1576
|
-
key: options.key ?? null,
|
|
1577
|
-
query,
|
|
1578
|
-
projection: options.projection ?? null,
|
|
1579
|
-
sort: options.sort ?? null,
|
|
1580
|
-
limit: typeof options.limit === "number" ? options.limit : null,
|
|
1581
|
-
populate: options.populate ?? null,
|
|
1582
|
-
pagination: options.pagination ?? null
|
|
1583
|
-
});
|
|
1584
|
-
};
|
|
1585
|
-
const hasSnapshotError = (snapshot) => {
|
|
1586
|
-
return snapshot?.error !== null && snapshot?.error !== void 0;
|
|
1587
|
-
};
|
|
1588
|
-
const RTS_CHANGES_PATH = "/api/rb/rts/changes";
|
|
1589
|
-
const MAX_TXN_BUF = 2048;
|
|
1590
|
-
const SERVER_RECONNECT_DELAY_MIN_MS = 1e4;
|
|
1591
|
-
const SERVER_RECONNECT_DELAY_MAX_MS = 15e3;
|
|
1592
|
-
const RUN_NETWORK_QUERY_TIMEOUT_ERROR = "runNetworkQuery: request timed out";
|
|
1593
|
-
const RUN_NETWORK_COUNT_TIMEOUT_ERROR = "runNetworkCount: request timed out";
|
|
1594
|
-
let socket = null;
|
|
1595
|
-
let socketReadyForMessages = false;
|
|
1596
|
-
let connectPromise = null;
|
|
1597
|
-
let pendingConnectionAttempt = null;
|
|
1598
|
-
let explicitDisconnect = false;
|
|
1599
|
-
let currentTenantId = null;
|
|
1600
|
-
let currentUid = null;
|
|
1601
|
-
let connectOptions = {};
|
|
1602
|
-
let connectionGeneration = 0;
|
|
1603
|
-
let connectionEpoch = null;
|
|
1604
|
-
const localTxnBuf = [];
|
|
1605
|
-
const queryCallbacks = /* @__PURE__ */ new Map();
|
|
1606
|
-
const countCallbacks = /* @__PURE__ */ new Map();
|
|
1607
|
-
const subscriptions = /* @__PURE__ */ new Map();
|
|
1608
|
-
const countSubscriptions = /* @__PURE__ */ new Map();
|
|
1609
|
-
const messageCallbacks = /* @__PURE__ */ new Map();
|
|
1610
|
-
const rtsMessageCallbacks = /* @__PURE__ */ new Map();
|
|
1611
|
-
const pendingWindowRequests = /* @__PURE__ */ new Map();
|
|
1612
|
-
let reconnectTimer = null;
|
|
1613
|
-
let reconnectAttempts = 0;
|
|
1614
|
-
let hasEstablishedConnection = false;
|
|
1615
|
-
let forceInitialQueryOnNextConnection = false;
|
|
1616
|
-
let pendingServerReconnectJitter = false;
|
|
1617
|
-
let syncPromise = null;
|
|
1618
|
-
let syncKey = null;
|
|
1619
|
-
let connectionStatus = "idle";
|
|
1620
|
-
let connectionError = null;
|
|
1621
|
-
const connectionStatusCallbacks = /* @__PURE__ */ new Set();
|
|
1622
|
-
const ensureRealtimeRuntime = () => {
|
|
1623
|
-
if (typeof WebSocket !== "function") {
|
|
1624
|
-
throw new Error("RTS websocket client requires WebSocket support");
|
|
1625
|
-
}
|
|
1626
|
-
if (typeof globalThis.setTimeout !== "function" || typeof globalThis.clearTimeout !== "function") {
|
|
1627
|
-
throw new Error("RTS websocket client requires timer support");
|
|
1628
|
-
}
|
|
1629
|
-
};
|
|
1630
|
-
const ensureSyncRuntime = () => {
|
|
1631
|
-
if (typeof fetch !== "function") {
|
|
1632
|
-
throw new Error("syncRtsChanges requires fetch support");
|
|
1633
|
-
}
|
|
1634
|
-
};
|
|
1635
|
-
const getRuntimeLocationHref = () => {
|
|
1636
|
-
if (typeof window !== "undefined" && typeof window.location?.href === "string" && window.location.href) {
|
|
1637
|
-
return window.location.href;
|
|
1638
|
-
}
|
|
1639
|
-
const location = globalThis.location;
|
|
1640
|
-
if (typeof location?.href === "string" && location.href) {
|
|
1641
|
-
return location.href;
|
|
1642
|
-
}
|
|
1643
|
-
return null;
|
|
1644
|
-
};
|
|
1645
|
-
const setRuntimeTimeout = (handler, delayMs) => {
|
|
1646
|
-
return globalThis.setTimeout(handler, delayMs);
|
|
1647
|
-
};
|
|
1648
|
-
const clearRuntimeTimeout = (timer) => {
|
|
1649
|
-
globalThis.clearTimeout(timer);
|
|
1650
|
-
};
|
|
1651
|
-
const setConnectionStatus = (status, error = null) => {
|
|
1652
|
-
connectionStatus = status;
|
|
1653
|
-
connectionError = error;
|
|
1654
|
-
for (const callback of connectionStatusCallbacks) {
|
|
1655
|
-
callback(status, error);
|
|
1656
|
-
}
|
|
1657
|
-
};
|
|
1658
|
-
const resolveWebSocketUrlFromCandidate = (candidateUrl, options) => {
|
|
1659
|
-
const url = new URL(candidateUrl);
|
|
1660
|
-
if (url.protocol === "http:") {
|
|
1661
|
-
url.protocol = "ws:";
|
|
1662
|
-
} else if (url.protocol === "https:") {
|
|
1663
|
-
url.protocol = "wss:";
|
|
1664
|
-
}
|
|
1665
|
-
if (!url.pathname || url.pathname === "/") {
|
|
1666
|
-
url.pathname = options.path ?? "/rts";
|
|
1667
|
-
}
|
|
1668
|
-
return url;
|
|
1669
|
-
};
|
|
1670
|
-
const resolveApiOriginUrl = ({
|
|
1671
|
-
url
|
|
1672
|
-
}) => {
|
|
1673
|
-
if (url) {
|
|
1674
|
-
const base2 = new URL(url);
|
|
1675
|
-
if (base2.protocol === "ws:") {
|
|
1676
|
-
base2.protocol = "http:";
|
|
1677
|
-
} else if (base2.protocol === "wss:") {
|
|
1678
|
-
base2.protocol = "https:";
|
|
1679
|
-
}
|
|
1680
|
-
base2.pathname = "/";
|
|
1681
|
-
base2.search = "";
|
|
1682
|
-
base2.hash = "";
|
|
1683
|
-
return base2;
|
|
1684
|
-
}
|
|
1685
|
-
const locationHref = getRuntimeLocationHref();
|
|
1686
|
-
if (!locationHref) {
|
|
1687
|
-
throw new Error("syncRtsChanges: options.url is required when location.href is unavailable");
|
|
1688
|
-
}
|
|
1689
|
-
const base = new URL(locationHref);
|
|
1690
|
-
base.pathname = "/";
|
|
1691
|
-
base.search = "";
|
|
1692
|
-
base.hash = "";
|
|
1693
|
-
return base;
|
|
1694
|
-
};
|
|
1695
|
-
const buildSyncChangesUrl = (_tenantId, options) => {
|
|
1696
|
-
const base = resolveApiOriginUrl(options);
|
|
1697
|
-
const url = new URL(RTS_CHANGES_PATH, base);
|
|
1698
|
-
return url.toString();
|
|
1699
|
-
};
|
|
1700
|
-
const buildSocketUrl = (_tenantId, _uid, options) => {
|
|
1701
|
-
if (options.url) {
|
|
1702
|
-
const url = resolveWebSocketUrlFromCandidate(options.url, options);
|
|
1703
|
-
return url.toString();
|
|
1704
|
-
}
|
|
1705
|
-
const locationHref = getRuntimeLocationHref();
|
|
1706
|
-
if (!locationHref) {
|
|
1707
|
-
throw new Error("connect: options.url is required when location.href is unavailable");
|
|
1708
|
-
}
|
|
1709
|
-
const base = new URL(locationHref);
|
|
1710
|
-
base.protocol = base.protocol === "https:" ? "wss:" : "ws:";
|
|
1711
|
-
base.pathname = options.path ?? "/rts";
|
|
1712
|
-
base.search = "";
|
|
1713
|
-
base.hash = "";
|
|
1714
|
-
return base.toString();
|
|
1715
|
-
};
|
|
1716
|
-
const sendToServer = (message) => {
|
|
1717
|
-
if (!socket || !socketReadyForMessages) return;
|
|
1718
|
-
if (socket.readyState !== WebSocket.OPEN) return;
|
|
1719
|
-
socket.send(JSON.stringify(message));
|
|
1720
|
-
};
|
|
1721
|
-
const isSocketReady = () => Boolean(socket && socketReadyForMessages && socket.readyState === WebSocket.OPEN);
|
|
1722
|
-
const normalizeRequestedCount = (value) => {
|
|
1723
|
-
if (!Number.isSafeInteger(value)) return void 0;
|
|
1724
|
-
if (value < 1 || value > RTS_QUERY_WINDOW_MAX_COUNT) return void 0;
|
|
1725
|
-
return value;
|
|
1726
|
-
};
|
|
1727
|
-
const getSubscriptionServerOptions = (subscription) => {
|
|
1728
|
-
if (!subscription.options.pagination || !subscription.requestedCount) {
|
|
1729
|
-
return subscription.options;
|
|
1730
|
-
}
|
|
1731
|
-
const {
|
|
1732
|
-
cursor: _cursor,
|
|
1733
|
-
direction: _direction,
|
|
1734
|
-
...pagination
|
|
1735
|
-
} = subscription.options.pagination;
|
|
1736
|
-
return {
|
|
1737
|
-
...subscription.options,
|
|
1738
|
-
pagination: {
|
|
1739
|
-
...pagination,
|
|
1740
|
-
limit: subscription.requestedCount
|
|
1741
|
-
}
|
|
1742
|
-
};
|
|
1743
|
-
};
|
|
1744
|
-
const resubscribeAll = ({
|
|
1745
|
-
forceInitialQuery
|
|
1746
|
-
}) => {
|
|
1747
|
-
for (const sub of subscriptions.values()) {
|
|
1748
|
-
const runInitialQuery = forceInitialQuery || sub.runInitialNetworkQuery;
|
|
1749
|
-
sendToServer({
|
|
1750
|
-
type: "register-query",
|
|
1751
|
-
modelName: sub.modelName,
|
|
1752
|
-
queryKey: sub.queryKey,
|
|
1753
|
-
query: sub.query,
|
|
1754
|
-
options: getSubscriptionServerOptions(sub),
|
|
1755
|
-
runInitialQuery
|
|
1756
|
-
});
|
|
1757
|
-
}
|
|
1758
|
-
for (const sub of countSubscriptions.values()) {
|
|
1759
|
-
const runInitialQuery = forceInitialQuery || sub.runInitialNetworkQuery;
|
|
1760
|
-
sendToServer({
|
|
1761
|
-
type: "register-count",
|
|
1762
|
-
modelName: sub.modelName,
|
|
1763
|
-
queryKey: sub.queryKey,
|
|
1764
|
-
query: sub.query,
|
|
1765
|
-
options: sub.options,
|
|
1766
|
-
runInitialQuery
|
|
1767
|
-
});
|
|
1768
|
-
}
|
|
1769
|
-
};
|
|
1770
|
-
const clearReconnectTimer = () => {
|
|
1771
|
-
if (reconnectTimer === null) return;
|
|
1772
|
-
clearRuntimeTimeout(reconnectTimer);
|
|
1773
|
-
reconnectTimer = null;
|
|
1774
|
-
};
|
|
1775
|
-
const rejectPendingConnectionAttempt = () => {
|
|
1776
|
-
const attempt = pendingConnectionAttempt;
|
|
1777
|
-
if (!attempt) return;
|
|
1778
|
-
pendingConnectionAttempt = null;
|
|
1779
|
-
attempt.reject(new Error("RTS WebSocket connection attempt superseded"));
|
|
1780
|
-
};
|
|
1781
|
-
const scheduleReconnect = () => {
|
|
1782
|
-
clearReconnectTimer();
|
|
1783
|
-
if (explicitDisconnect) {
|
|
1784
|
-
pendingServerReconnectJitter = false;
|
|
1785
|
-
return;
|
|
1786
|
-
}
|
|
1787
|
-
if (!currentTenantId || !currentUid) return;
|
|
1788
|
-
const cfg = connectOptions.reconnect ?? {};
|
|
1789
|
-
const maxAttempts = cfg.attempts ?? 128;
|
|
1790
|
-
const delayMs = cfg.delayMs ?? 400;
|
|
1791
|
-
const delayMaxMs = cfg.delayMaxMs ?? 1e4;
|
|
1792
|
-
if (reconnectAttempts >= maxAttempts) return;
|
|
1793
|
-
let delay = Math.min(delayMaxMs, delayMs * Math.pow(2, reconnectAttempts));
|
|
1794
|
-
if (pendingServerReconnectJitter) {
|
|
1795
|
-
const span = SERVER_RECONNECT_DELAY_MAX_MS - SERVER_RECONNECT_DELAY_MIN_MS;
|
|
1796
|
-
delay = SERVER_RECONNECT_DELAY_MIN_MS + Math.floor(Math.random() * (span + 1));
|
|
1797
|
-
pendingServerReconnectJitter = false;
|
|
1798
|
-
}
|
|
1799
|
-
reconnectAttempts += 1;
|
|
1800
|
-
reconnectTimer = setRuntimeTimeout(() => {
|
|
1801
|
-
void connectInternal(currentTenantId, currentUid, connectOptions, {
|
|
1802
|
-
resetReconnectAttempts: false
|
|
1803
|
-
});
|
|
1804
|
-
}, delay);
|
|
1805
|
-
};
|
|
1806
|
-
const isDocWithId = (doc) => {
|
|
1807
|
-
if (!doc || typeof doc !== "object") return false;
|
|
1808
|
-
return typeof doc._id === "string";
|
|
1809
|
-
};
|
|
1810
|
-
const normalizePageInfo$1 = (value) => {
|
|
1811
|
-
if (!value || typeof value !== "object") return void 0;
|
|
1812
|
-
if (Array.isArray(value)) return void 0;
|
|
1813
|
-
const raw = value;
|
|
1814
|
-
if (typeof raw.hasNextPage !== "boolean" || typeof raw.hasPrevPage !== "boolean") return void 0;
|
|
1815
|
-
const nextCursor = typeof raw.nextCursor === "string" && raw.nextCursor ? raw.nextCursor : void 0;
|
|
1816
|
-
const prevCursor = typeof raw.prevCursor === "string" && raw.prevCursor ? raw.prevCursor : void 0;
|
|
1817
|
-
return {
|
|
1818
|
-
hasNextPage: raw.hasNextPage,
|
|
1819
|
-
hasPrevPage: raw.hasPrevPage,
|
|
1820
|
-
...nextCursor ? {
|
|
1821
|
-
nextCursor
|
|
1822
|
-
} : {},
|
|
1823
|
-
...prevCursor ? {
|
|
1824
|
-
prevCursor
|
|
1825
|
-
} : {}
|
|
1826
|
-
};
|
|
1827
|
-
};
|
|
1828
|
-
const normalizeTotalCount$1 = (value) => {
|
|
1829
|
-
if (typeof value !== "number") return void 0;
|
|
1830
|
-
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
1831
|
-
return Math.floor(value);
|
|
1832
|
-
};
|
|
1833
|
-
const normalizeQueryWindow = (value) => {
|
|
1834
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1835
|
-
const raw = value;
|
|
1836
|
-
const requestedCount = normalizeRequestedCount(raw.requestedCount);
|
|
1837
|
-
if (!requestedCount) return void 0;
|
|
1838
|
-
const version = Number.isSafeInteger(raw.version) && raw.version >= 0 ? raw.version : void 0;
|
|
1839
|
-
return {
|
|
1840
|
-
requestedCount,
|
|
1841
|
-
...version !== void 0 ? {
|
|
1842
|
-
version
|
|
1843
|
-
} : {}
|
|
1844
|
-
};
|
|
1845
|
-
};
|
|
1846
|
-
const settlePendingWindowRequests = (cbKey, requestedCount, success) => {
|
|
1847
|
-
const pending = pendingWindowRequests.get(cbKey);
|
|
1848
|
-
if (!pending?.size) return;
|
|
1849
|
-
for (const request of Array.from(pending)) {
|
|
1850
|
-
if (request.requestedCount !== requestedCount) continue;
|
|
1851
|
-
clearRuntimeTimeout(request.timeoutId);
|
|
1852
|
-
pending.delete(request);
|
|
1853
|
-
if (!success) {
|
|
1854
|
-
const subscription = subscriptions.get(cbKey);
|
|
1855
|
-
if (subscription?.requestedCount === request.requestedCount) {
|
|
1856
|
-
const snapshotRequestedCount = subscription.lastSnapshot && !hasSnapshotError(subscription.lastSnapshot) ? subscription.lastSnapshot.context.window?.requestedCount : void 0;
|
|
1857
|
-
const rollbackRequestedCount = snapshotRequestedCount ?? request.previousRequestedCount;
|
|
1858
|
-
subscription.windowIntentVersion += 1;
|
|
1859
|
-
subscription.requestedCount = rollbackRequestedCount;
|
|
1860
|
-
sendToServer({
|
|
1861
|
-
type: "set-query-window",
|
|
1862
|
-
modelName: subscription.modelName,
|
|
1863
|
-
queryKey: subscription.queryKey,
|
|
1864
|
-
requestedCount: rollbackRequestedCount
|
|
1865
|
-
});
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
request.resolve(success);
|
|
1869
|
-
}
|
|
1870
|
-
if (!pending.size) pendingWindowRequests.delete(cbKey);
|
|
1871
|
-
};
|
|
1872
|
-
const failPendingWindowRequests = (cbKey) => {
|
|
1873
|
-
const pending = pendingWindowRequests.get(cbKey);
|
|
1874
|
-
if (!pending?.size) return;
|
|
1875
|
-
for (const request of Array.from(pending)) {
|
|
1876
|
-
settlePendingWindowRequests(cbKey, request.requestedCount, false);
|
|
1877
|
-
}
|
|
1878
|
-
};
|
|
1879
|
-
const cancelPendingWindowRequests = (cbKey) => {
|
|
1880
|
-
const entries = cbKey ? [[cbKey, pendingWindowRequests.get(cbKey)]] : Array.from(pendingWindowRequests.entries());
|
|
1881
|
-
for (const [key, pending] of entries) {
|
|
1882
|
-
if (!pending) continue;
|
|
1883
|
-
for (const request of pending) {
|
|
1884
|
-
clearRuntimeTimeout(request.timeoutId);
|
|
1885
|
-
request.resolve(false);
|
|
1886
|
-
}
|
|
1887
|
-
pendingWindowRequests.delete(key);
|
|
1888
|
-
}
|
|
1889
|
-
};
|
|
1890
|
-
const updateQuerySubscriptionSnapshot = (subscription, snapshot) => {
|
|
1891
|
-
if (snapshot.context.source === "cache" && subscription.lastSnapshot?.context.source === "network") {
|
|
1892
|
-
const cachedCount = snapshot.context.window?.requestedCount;
|
|
1893
|
-
const networkCount = subscription.lastSnapshot.context.window?.requestedCount;
|
|
1894
|
-
if (!cachedCount || !networkCount || cachedCount <= networkCount) return false;
|
|
1895
|
-
}
|
|
1896
|
-
subscription.lastSnapshot = snapshot;
|
|
1897
|
-
return true;
|
|
1898
|
-
};
|
|
1899
|
-
const updateCountSubscriptionSnapshot = (subscription, snapshot) => {
|
|
1900
|
-
if (snapshot.context.source === "cache" && subscription.lastSnapshot?.context.source === "network") {
|
|
1901
|
-
return false;
|
|
1902
|
-
}
|
|
1903
|
-
subscription.lastSnapshot = snapshot;
|
|
1904
|
-
return true;
|
|
1905
|
-
};
|
|
1906
|
-
const dispatchQuerySnapshotToSubscription = (subscription, snapshot) => {
|
|
1907
|
-
for (const callback of subscription.callbacks) {
|
|
1908
|
-
callback(snapshot.error, snapshot.data, snapshot.context);
|
|
1909
|
-
}
|
|
1910
|
-
};
|
|
1911
|
-
const dispatchQuerySnapshotToOneshotCallbacks = (cbKey, snapshot) => {
|
|
1912
|
-
const callbacks = queryCallbacks.get(cbKey);
|
|
1913
|
-
if (!callbacks || !callbacks.size) return;
|
|
1914
|
-
for (const callback of callbacks) {
|
|
1915
|
-
callback(snapshot.error, snapshot.data, snapshot.context);
|
|
1916
|
-
}
|
|
1917
|
-
};
|
|
1918
|
-
const dispatchCountSnapshotToSubscription = (subscription, snapshot) => {
|
|
1919
|
-
for (const callback of subscription.callbacks) {
|
|
1920
|
-
callback(snapshot.error, snapshot.count, snapshot.context);
|
|
1921
|
-
}
|
|
1922
|
-
};
|
|
1923
|
-
const dispatchCountSnapshotToOneshotCallbacks = (cbKey, snapshot) => {
|
|
1924
|
-
const callbacks = countCallbacks.get(cbKey);
|
|
1925
|
-
if (!callbacks || !callbacks.size) return;
|
|
1926
|
-
for (const callback of callbacks) {
|
|
1927
|
-
callback(snapshot.error, snapshot.count, snapshot.context);
|
|
1928
|
-
}
|
|
1929
|
-
};
|
|
1930
|
-
const requestRegisteredQueryRefresh = (subscription) => {
|
|
1931
|
-
if (subscription.options.pagination && subscription.requestedCount) {
|
|
1932
|
-
sendToServer({
|
|
1933
|
-
type: "set-query-window",
|
|
1934
|
-
modelName: subscription.modelName,
|
|
1935
|
-
queryKey: subscription.queryKey,
|
|
1936
|
-
requestedCount: subscription.requestedCount
|
|
1937
|
-
});
|
|
1938
|
-
return;
|
|
1939
|
-
}
|
|
1940
|
-
sendToServer({
|
|
1941
|
-
type: "run-query",
|
|
1942
|
-
modelName: subscription.modelName,
|
|
1943
|
-
queryKey: subscription.queryKey,
|
|
1944
|
-
query: subscription.query,
|
|
1945
|
-
options: getSubscriptionServerOptions(subscription)
|
|
1946
|
-
});
|
|
1947
|
-
};
|
|
1948
|
-
const requestRegisteredCountRefresh = (subscription) => {
|
|
1949
|
-
sendToServer({
|
|
1950
|
-
type: "run-count",
|
|
1951
|
-
modelName: subscription.modelName,
|
|
1952
|
-
queryKey: subscription.queryKey,
|
|
1953
|
-
query: subscription.query,
|
|
1954
|
-
options: subscription.options
|
|
1955
|
-
});
|
|
1956
|
-
};
|
|
1957
|
-
const requestSubscriptionLocalQuery = (cbKey, subscription) => {
|
|
1958
|
-
if (!currentUid) return false;
|
|
1959
|
-
const uid = currentUid;
|
|
1960
|
-
const generation = connectionGeneration;
|
|
1961
|
-
if (subscription.options.pagination) {
|
|
1962
|
-
const requestedCountAtStart = subscription.requestedCount;
|
|
1963
|
-
const windowIntentVersionAtStart = subscription.windowIntentVersion;
|
|
1964
|
-
const localQueryPromise = readQueryWindowSnapshot({
|
|
1965
|
-
uid,
|
|
1966
|
-
modelName: subscription.modelName,
|
|
1967
|
-
queryKey: subscription.queryKey
|
|
1968
|
-
}).then((result) => {
|
|
1969
|
-
if (!result.hit || generation !== connectionGeneration) return;
|
|
1970
|
-
const currentSubscription = subscriptions.get(cbKey);
|
|
1971
|
-
if (!currentSubscription) return;
|
|
1972
|
-
const requestedCount = normalizeRequestedCount(result.snapshot.requestedCount);
|
|
1973
|
-
if (!requestedCount) return;
|
|
1974
|
-
if (currentSubscription.windowIntentVersion !== windowIntentVersionAtStart) return;
|
|
1975
|
-
if (currentSubscription.requestedCount !== requestedCountAtStart) return;
|
|
1976
|
-
const snapshot = {
|
|
1977
|
-
error: null,
|
|
1978
|
-
data: result.snapshot.data,
|
|
1979
|
-
context: {
|
|
1980
|
-
source: "cache",
|
|
1981
|
-
pageInfo: result.snapshot.pageInfo,
|
|
1982
|
-
...result.snapshot.totalCount !== void 0 ? {
|
|
1983
|
-
totalCount: result.snapshot.totalCount
|
|
1984
|
-
} : {},
|
|
1985
|
-
window: {
|
|
1986
|
-
requestedCount
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
};
|
|
1990
|
-
if (!updateQuerySubscriptionSnapshot(currentSubscription, snapshot)) return;
|
|
1991
|
-
currentSubscription.requestedCount = requestedCount;
|
|
1992
|
-
currentSubscription.windowIntentVersion += 1;
|
|
1993
|
-
if (requestedCount !== requestedCountAtStart) {
|
|
1994
|
-
sendToServer({
|
|
1995
|
-
type: "set-query-window",
|
|
1996
|
-
modelName: currentSubscription.modelName,
|
|
1997
|
-
queryKey: currentSubscription.queryKey,
|
|
1998
|
-
requestedCount
|
|
1999
|
-
});
|
|
2000
|
-
}
|
|
2001
|
-
dispatchQuerySnapshotToSubscription(currentSubscription, snapshot);
|
|
2002
|
-
}).catch(() => {
|
|
2003
|
-
});
|
|
2004
|
-
subscription.localQueryPromise = localQueryPromise;
|
|
2005
|
-
void localQueryPromise.finally(() => {
|
|
2006
|
-
const currentSubscription = subscriptions.get(cbKey);
|
|
2007
|
-
if (currentSubscription?.localQueryPromise === localQueryPromise) {
|
|
2008
|
-
delete currentSubscription.localQueryPromise;
|
|
2009
|
-
}
|
|
2010
|
-
});
|
|
2011
|
-
return true;
|
|
2012
|
-
}
|
|
2013
|
-
const populateCache = subscription.populateCache;
|
|
2014
|
-
const hasPopulate = Boolean(populateCache);
|
|
2015
|
-
if (hasPopulate && populateCache) {
|
|
2016
|
-
void runPopulatedQuery({
|
|
2017
|
-
modelName: subscription.modelName,
|
|
2018
|
-
query: subscription.query,
|
|
2019
|
-
options: {
|
|
2020
|
-
uid,
|
|
2021
|
-
projection: populateCache.rootProjection,
|
|
2022
|
-
sort: subscription.options.sort,
|
|
2023
|
-
limit: subscription.options.limit,
|
|
2024
|
-
populate: populateCache.populate
|
|
2025
|
-
}
|
|
2026
|
-
}).then(({
|
|
2027
|
-
hit,
|
|
2028
|
-
data,
|
|
2029
|
-
context
|
|
2030
|
-
}) => {
|
|
2031
|
-
if (!hit) return;
|
|
2032
|
-
if (generation !== connectionGeneration) return;
|
|
2033
|
-
const currentSubscription = subscriptions.get(cbKey);
|
|
2034
|
-
if (!currentSubscription) return;
|
|
2035
|
-
const snapshot = {
|
|
2036
|
-
error: null,
|
|
2037
|
-
data,
|
|
2038
|
-
context
|
|
2039
|
-
};
|
|
2040
|
-
if (!updateQuerySubscriptionSnapshot(currentSubscription, snapshot)) return;
|
|
2041
|
-
dispatchQuerySnapshotToSubscription(currentSubscription, snapshot);
|
|
2042
|
-
}).catch(() => {
|
|
2043
|
-
});
|
|
2044
|
-
return true;
|
|
2045
|
-
}
|
|
2046
|
-
void runQuery({
|
|
2047
|
-
modelName: subscription.modelName,
|
|
2048
|
-
query: subscription.query,
|
|
2049
|
-
options: {
|
|
2050
|
-
uid,
|
|
2051
|
-
projection: subscription.options.projection,
|
|
2052
|
-
sort: subscription.options.sort,
|
|
2053
|
-
limit: subscription.options.limit
|
|
2054
|
-
}
|
|
2055
|
-
}).then(({
|
|
2056
|
-
data,
|
|
2057
|
-
context
|
|
2058
|
-
}) => {
|
|
2059
|
-
if (generation !== connectionGeneration) return;
|
|
2060
|
-
const currentSubscription = subscriptions.get(cbKey);
|
|
2061
|
-
if (!currentSubscription) return;
|
|
2062
|
-
const snapshot = {
|
|
2063
|
-
error: null,
|
|
2064
|
-
data,
|
|
2065
|
-
context
|
|
2066
|
-
};
|
|
2067
|
-
if (!updateQuerySubscriptionSnapshot(currentSubscription, snapshot)) return;
|
|
2068
|
-
dispatchQuerySnapshotToSubscription(currentSubscription, snapshot);
|
|
2069
|
-
}).catch(() => {
|
|
2070
|
-
});
|
|
2071
|
-
return true;
|
|
2072
|
-
};
|
|
2073
|
-
const handleQueryPayload = (payload) => {
|
|
2074
|
-
const {
|
|
2075
|
-
modelName,
|
|
2076
|
-
queryKey,
|
|
2077
|
-
data,
|
|
2078
|
-
error,
|
|
2079
|
-
txnId
|
|
2080
|
-
} = payload;
|
|
2081
|
-
const cbKey = `${modelName}.${queryKey}`;
|
|
2082
|
-
const subscription = subscriptions.get(cbKey);
|
|
2083
|
-
const hasSubscriptionCallbacks = Boolean(subscription?.callbacks.size);
|
|
2084
|
-
const hasOneshotCallbacks = Boolean(queryCallbacks.get(cbKey)?.size);
|
|
2085
|
-
if (!hasSubscriptionCallbacks && !hasOneshotCallbacks) return;
|
|
2086
|
-
const pageInfo = normalizePageInfo$1(payload.pageInfo);
|
|
2087
|
-
const totalCount = normalizeTotalCount$1(payload.totalCount);
|
|
2088
|
-
const queryWindow = normalizeQueryWindow(payload.window);
|
|
2089
|
-
const populateCache = subscription?.populateCache;
|
|
2090
|
-
const hasPopulate = Boolean(populateCache);
|
|
2091
|
-
const hasPagination = Boolean(subscription?.options?.pagination || pageInfo || totalCount !== void 0);
|
|
2092
|
-
if (subscription?.options.pagination && queryWindow) {
|
|
2093
|
-
if (queryWindow.requestedCount !== subscription.requestedCount) return;
|
|
2094
|
-
if (queryWindow.version !== void 0 && queryWindow.version <= subscription.lastNetworkVersion) return;
|
|
2095
|
-
if (queryWindow.version !== void 0) {
|
|
2096
|
-
subscription.lastNetworkVersion = queryWindow.version;
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
const isLocal = !!(txnId && localTxnBuf.includes(txnId));
|
|
2100
|
-
const context = {
|
|
2101
|
-
source: "network",
|
|
2102
|
-
isLocal,
|
|
2103
|
-
txnId,
|
|
2104
|
-
...pageInfo ? {
|
|
2105
|
-
pageInfo
|
|
2106
|
-
} : {},
|
|
2107
|
-
...totalCount !== void 0 ? {
|
|
2108
|
-
totalCount
|
|
2109
|
-
} : {},
|
|
2110
|
-
...queryWindow ? {
|
|
2111
|
-
window: queryWindow
|
|
2112
|
-
} : {}
|
|
2113
|
-
};
|
|
2114
|
-
const snapshot = {
|
|
2115
|
-
error: error ?? null,
|
|
2116
|
-
data,
|
|
2117
|
-
context
|
|
2118
|
-
};
|
|
2119
|
-
if (error) {
|
|
2120
|
-
if (subscription && updateQuerySubscriptionSnapshot(subscription, snapshot)) {
|
|
2121
|
-
dispatchQuerySnapshotToSubscription(subscription, snapshot);
|
|
2122
|
-
}
|
|
2123
|
-
dispatchQuerySnapshotToOneshotCallbacks(cbKey, snapshot);
|
|
2124
|
-
if (queryWindow) {
|
|
2125
|
-
settlePendingWindowRequests(cbKey, queryWindow.requestedCount, false);
|
|
2126
|
-
} else if (subscription?.options.pagination) {
|
|
2127
|
-
failPendingWindowRequests(cbKey);
|
|
2128
|
-
}
|
|
2129
|
-
return;
|
|
2130
|
-
}
|
|
2131
|
-
if (subscription && updateQuerySubscriptionSnapshot(subscription, snapshot)) {
|
|
2132
|
-
dispatchQuerySnapshotToSubscription(subscription, snapshot);
|
|
2133
|
-
}
|
|
2134
|
-
dispatchQuerySnapshotToOneshotCallbacks(cbKey, snapshot);
|
|
2135
|
-
if (queryWindow) {
|
|
2136
|
-
settlePendingWindowRequests(cbKey, queryWindow.requestedCount, true);
|
|
2137
|
-
}
|
|
2138
|
-
if (!currentUid) return;
|
|
2139
|
-
if (subscription?.options.pagination && queryWindow && pageInfo && Array.isArray(data) && connectionEpoch) {
|
|
2140
|
-
const uid = currentUid;
|
|
2141
|
-
const generation = connectionGeneration;
|
|
2142
|
-
const serverEpoch = connectionEpoch;
|
|
2143
|
-
const localQueryPromise = subscription.localQueryPromise;
|
|
2144
|
-
void Promise.resolve().then(async () => {
|
|
2145
|
-
await localQueryPromise;
|
|
2146
|
-
if (generation !== connectionGeneration || serverEpoch !== connectionEpoch) return;
|
|
2147
|
-
const currentSubscription = subscriptions.get(cbKey);
|
|
2148
|
-
if (!currentSubscription || currentSubscription.requestedCount !== queryWindow.requestedCount) return;
|
|
2149
|
-
await writeQueryWindowSnapshot({
|
|
2150
|
-
uid,
|
|
2151
|
-
modelName,
|
|
2152
|
-
queryKey,
|
|
2153
|
-
data,
|
|
2154
|
-
pageInfo,
|
|
2155
|
-
...totalCount !== void 0 ? {
|
|
2156
|
-
totalCount
|
|
2157
|
-
} : {},
|
|
2158
|
-
requestedCount: queryWindow.requestedCount,
|
|
2159
|
-
...queryWindow.version !== void 0 ? {
|
|
2160
|
-
serverEpoch,
|
|
2161
|
-
serverVersion: queryWindow.version
|
|
2162
|
-
} : {}
|
|
2163
|
-
});
|
|
2164
|
-
}).catch(() => {
|
|
2165
|
-
});
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
const docs = Array.isArray(data) ? data.filter(isDocWithId) : [];
|
|
2169
|
-
if (hasPagination) return;
|
|
2170
|
-
if (!docs.length) return;
|
|
2171
|
-
if (hasPopulate && populateCache) {
|
|
2172
|
-
void updatePopulatedDocs({
|
|
2173
|
-
modelName,
|
|
2174
|
-
data: docs,
|
|
2175
|
-
uid: currentUid,
|
|
2176
|
-
populate: populateCache.populate
|
|
2177
|
-
}).catch(() => {
|
|
2178
|
-
});
|
|
2179
|
-
return;
|
|
2180
|
-
}
|
|
2181
|
-
void updateDocs(modelName, docs, currentUid).catch(() => {
|
|
2182
|
-
});
|
|
2183
|
-
};
|
|
2184
|
-
const handleCountPayload = (payload) => {
|
|
2185
|
-
const {
|
|
2186
|
-
modelName,
|
|
2187
|
-
queryKey,
|
|
2188
|
-
count,
|
|
2189
|
-
error,
|
|
2190
|
-
txnId
|
|
2191
|
-
} = payload;
|
|
2192
|
-
const cbKey = `${modelName}.${queryKey}`;
|
|
2193
|
-
const subscription = countSubscriptions.get(cbKey);
|
|
2194
|
-
const hasSubscriptionCallbacks = Boolean(subscription?.callbacks.size);
|
|
2195
|
-
const hasOneshotCallbacks = Boolean(countCallbacks.get(cbKey)?.size);
|
|
2196
|
-
if (!hasSubscriptionCallbacks && !hasOneshotCallbacks) return;
|
|
2197
|
-
const isLocal = !!(txnId && localTxnBuf.includes(txnId));
|
|
2198
|
-
const context = {
|
|
2199
|
-
source: "network",
|
|
2200
|
-
isLocal,
|
|
2201
|
-
txnId
|
|
2202
|
-
};
|
|
2203
|
-
const normalizedCount = normalizeTotalCount$1(count);
|
|
2204
|
-
const snapshot = {
|
|
2205
|
-
error: error ?? null,
|
|
2206
|
-
count: normalizedCount,
|
|
2207
|
-
context
|
|
2208
|
-
};
|
|
2209
|
-
if (error) {
|
|
2210
|
-
if (subscription && updateCountSubscriptionSnapshot(subscription, snapshot)) {
|
|
2211
|
-
dispatchCountSnapshotToSubscription(subscription, snapshot);
|
|
2212
|
-
}
|
|
2213
|
-
dispatchCountSnapshotToOneshotCallbacks(cbKey, snapshot);
|
|
2214
|
-
return;
|
|
2215
|
-
}
|
|
2216
|
-
if (subscription && updateCountSubscriptionSnapshot(subscription, snapshot)) {
|
|
2217
|
-
dispatchCountSnapshotToSubscription(subscription, snapshot);
|
|
2218
|
-
}
|
|
2219
|
-
dispatchCountSnapshotToOneshotCallbacks(cbKey, snapshot);
|
|
2220
|
-
};
|
|
2221
|
-
const handleEvent = (payload) => {
|
|
2222
|
-
const callbacks = messageCallbacks.get(payload.event);
|
|
2223
|
-
if (!callbacks || !callbacks.size) return;
|
|
2224
|
-
for (const cb of callbacks) cb(payload.payload);
|
|
2225
|
-
};
|
|
2226
|
-
const handleRtsMessage = (payload) => {
|
|
2227
|
-
const callbacks = rtsMessageCallbacks.get(payload.channel);
|
|
2228
|
-
if (!callbacks || !callbacks.size) return;
|
|
2229
|
-
for (const cb of callbacks) cb(payload.payload);
|
|
2230
|
-
};
|
|
2231
|
-
const handleMessage = (event) => {
|
|
2232
|
-
let parsed;
|
|
2233
|
-
try {
|
|
2234
|
-
parsed = JSON.parse(typeof event.data === "string" ? event.data : String(event.data));
|
|
2235
|
-
} catch {
|
|
2236
|
-
return;
|
|
2237
|
-
}
|
|
2238
|
-
if (!parsed || typeof parsed !== "object") return;
|
|
2239
|
-
const message = parsed;
|
|
2240
|
-
if (message.type === "query-payload") {
|
|
2241
|
-
handleQueryPayload(message);
|
|
2242
|
-
return;
|
|
2243
|
-
}
|
|
2244
|
-
if (message.type === "count-payload") {
|
|
2245
|
-
handleCountPayload(message);
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
if (message.type === "event") {
|
|
2249
|
-
handleEvent(message);
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
if (message.type === "rts-message") {
|
|
2253
|
-
handleRtsMessage(message);
|
|
2254
|
-
}
|
|
2255
|
-
};
|
|
2256
|
-
const addLocalTxn = (txnId) => {
|
|
2257
|
-
if (!txnId) return;
|
|
2258
|
-
localTxnBuf.push(txnId);
|
|
2259
|
-
if (localTxnBuf.length > MAX_TXN_BUF) {
|
|
2260
|
-
localTxnBuf.shift();
|
|
2261
|
-
}
|
|
2262
|
-
};
|
|
2263
|
-
const getSyncStorageKey = ({
|
|
2264
|
-
tenantId,
|
|
2265
|
-
uid,
|
|
2266
|
-
appName
|
|
2267
|
-
}) => `rb:rts:changesSeq:${appName ?? ""}:${tenantId}:${uid}`;
|
|
2268
|
-
const readStoredSeq = (key) => {
|
|
2269
|
-
const storage = getRuntimeStorage();
|
|
2270
|
-
try {
|
|
2271
|
-
const raw = storage.getItem(key);
|
|
2272
|
-
const num = raw ? Number(raw) : 0;
|
|
2273
|
-
return Number.isFinite(num) && num >= 0 ? Math.floor(num) : 0;
|
|
2274
|
-
} catch {
|
|
2275
|
-
return 0;
|
|
2276
|
-
}
|
|
2277
|
-
};
|
|
2278
|
-
const writeStoredSeq = (key, value) => {
|
|
2279
|
-
const storage = getRuntimeStorage();
|
|
2280
|
-
try {
|
|
2281
|
-
storage.setItem(key, String(Math.max(0, Math.floor(value))));
|
|
2282
|
-
} catch {
|
|
2283
|
-
return;
|
|
2284
|
-
}
|
|
2285
|
-
};
|
|
2286
|
-
const applyChangeBatch = async (changes, uid, isCurrent) => {
|
|
2287
|
-
const resetModels = /* @__PURE__ */ new Set();
|
|
2288
|
-
const deletesByModel = /* @__PURE__ */ new Map();
|
|
2289
|
-
for (const change of changes) {
|
|
2290
|
-
const modelName = typeof change.modelName === "string" ? change.modelName : "";
|
|
2291
|
-
if (!modelName) continue;
|
|
2292
|
-
if (change.op === "reset_model") {
|
|
2293
|
-
resetModels.add(modelName);
|
|
2294
|
-
continue;
|
|
2295
|
-
}
|
|
2296
|
-
if (change.op === "delete") {
|
|
2297
|
-
const docId = typeof change.docId === "string" ? change.docId : "";
|
|
2298
|
-
if (!docId) continue;
|
|
2299
|
-
const existing = deletesByModel.get(modelName) ?? [];
|
|
2300
|
-
existing.push(docId);
|
|
2301
|
-
deletesByModel.set(modelName, existing);
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
for (const modelName of resetModels) {
|
|
2305
|
-
if (!isCurrent()) return false;
|
|
2306
|
-
await destroyCollection(modelName, uid).catch(() => {
|
|
2307
|
-
});
|
|
2308
|
-
}
|
|
2309
|
-
for (const [modelName, ids] of deletesByModel.entries()) {
|
|
2310
|
-
if (resetModels.has(modelName)) continue;
|
|
2311
|
-
if (!isCurrent()) return false;
|
|
2312
|
-
await deleteDocs(modelName, ids, uid).catch(() => {
|
|
2313
|
-
});
|
|
2314
|
-
}
|
|
2315
|
-
if (resetModels.size || deletesByModel.size) {
|
|
2316
|
-
if (!isCurrent()) return false;
|
|
2317
|
-
await invalidateQueryWindowSnapshots({
|
|
2318
|
-
uid
|
|
2319
|
-
}).catch(() => {
|
|
2320
|
-
});
|
|
2321
|
-
}
|
|
2322
|
-
return true;
|
|
2323
|
-
};
|
|
2324
|
-
const syncRtsChangesWithResult = async (tenantId, uid, options = {}, isCurrent = () => true) => {
|
|
2325
|
-
ensureSyncRuntime();
|
|
2326
|
-
if (!tenantId || !uid) return false;
|
|
2327
|
-
const storageKey = getSyncStorageKey({
|
|
2328
|
-
tenantId,
|
|
2329
|
-
uid,
|
|
2330
|
-
appName: options.appName
|
|
2331
|
-
});
|
|
2332
|
-
let sinceSeq = readStoredSeq(storageKey);
|
|
2333
|
-
let cacheInvalidated = false;
|
|
2334
|
-
const syncUrl = buildSyncChangesUrl(tenantId, {
|
|
2335
|
-
url: options.url
|
|
2336
|
-
});
|
|
2337
|
-
for (let i = 0; i < 32; i += 1) {
|
|
2338
|
-
const response = await fetch(syncUrl, {
|
|
2339
|
-
method: "POST",
|
|
2340
|
-
credentials: "include",
|
|
2341
|
-
headers: {
|
|
2342
|
-
"Content-Type": "application/json"
|
|
2343
|
-
},
|
|
2344
|
-
body: JSON.stringify({
|
|
2345
|
-
sinceSeq,
|
|
2346
|
-
limit: 2e3
|
|
2347
|
-
})
|
|
2348
|
-
});
|
|
2349
|
-
if (!isCurrent()) return false;
|
|
2350
|
-
if (!response.ok) return cacheInvalidated;
|
|
2351
|
-
const payload = await response.json().catch(() => null);
|
|
2352
|
-
if (!payload || typeof payload !== "object") return cacheInvalidated;
|
|
2353
|
-
const payloadObj = payload;
|
|
2354
|
-
const ok = payloadObj.ok;
|
|
2355
|
-
if (ok !== true) return cacheInvalidated;
|
|
2356
|
-
const latestSeq = Number(payloadObj.latestSeq ?? 0);
|
|
2357
|
-
const needsFullResync = Boolean(payloadObj.needsFullResync);
|
|
2358
|
-
if (needsFullResync) {
|
|
2359
|
-
if (!isCurrent()) return false;
|
|
2360
|
-
resetRtsPouchStore({
|
|
2361
|
-
tenantId,
|
|
2362
|
-
appName: options.appName
|
|
2363
|
-
});
|
|
2364
|
-
writeStoredSeq(storageKey, latestSeq);
|
|
2365
|
-
return true;
|
|
2366
|
-
}
|
|
2367
|
-
const changesRaw = payloadObj.changes;
|
|
2368
|
-
const changes = Array.isArray(changesRaw) ? changesRaw : [];
|
|
2369
|
-
const normalized = changes.map((c2) => {
|
|
2370
|
-
if (!c2 || typeof c2 !== "object") return null;
|
|
2371
|
-
const obj = c2;
|
|
2372
|
-
const seq = Number(obj.seq ?? 0);
|
|
2373
|
-
const modelName = typeof obj.modelName === "string" ? obj.modelName : String(obj.modelName ?? "");
|
|
2374
|
-
const op = obj.op === "reset_model" ? "reset_model" : "delete";
|
|
2375
|
-
const docId = typeof obj.docId === "string" && obj.docId ? obj.docId : obj.docId ? String(obj.docId) : void 0;
|
|
2376
|
-
return {
|
|
2377
|
-
seq,
|
|
2378
|
-
modelName,
|
|
2379
|
-
op,
|
|
2380
|
-
...docId ? {
|
|
2381
|
-
docId
|
|
2382
|
-
} : {}
|
|
2383
|
-
};
|
|
2384
|
-
}).filter((c2) => c2 !== null).filter((c2) => Number.isFinite(c2.seq) && c2.seq > 0 && c2.modelName && (c2.op === "reset_model" || !!c2.docId));
|
|
2385
|
-
if (!normalized.length) {
|
|
2386
|
-
writeStoredSeq(storageKey, latestSeq);
|
|
2387
|
-
return cacheInvalidated;
|
|
2388
|
-
}
|
|
2389
|
-
const applied = await applyChangeBatch(normalized, uid, isCurrent);
|
|
2390
|
-
if (!applied) return false;
|
|
2391
|
-
cacheInvalidated = true;
|
|
2392
|
-
const lastSeq = normalized.reduce((max, c2) => c2.seq > max ? c2.seq : max, sinceSeq);
|
|
2393
|
-
sinceSeq = lastSeq;
|
|
2394
|
-
writeStoredSeq(storageKey, sinceSeq);
|
|
2395
|
-
if (latestSeq > 0 && sinceSeq >= latestSeq) {
|
|
2396
|
-
return cacheInvalidated;
|
|
2397
|
-
}
|
|
2398
|
-
}
|
|
2399
|
-
return cacheInvalidated;
|
|
2400
|
-
};
|
|
2401
|
-
const syncRtsChanges = async (tenantId, uid, options = {}) => {
|
|
2402
|
-
await syncRtsChangesWithResult(tenantId, uid, options);
|
|
2403
|
-
};
|
|
2404
|
-
const ensureSynced = (tenantId, uid, options) => {
|
|
2405
|
-
if (options.syncChanges === false) return Promise.resolve(false);
|
|
2406
|
-
const key = `${options.appName ?? ""}:${tenantId}:${uid}`;
|
|
2407
|
-
if (syncPromise && syncKey === key) return syncPromise;
|
|
2408
|
-
syncKey = key;
|
|
2409
|
-
syncPromise = syncRtsChangesWithResult(tenantId, uid, {
|
|
2410
|
-
appName: options.appName,
|
|
2411
|
-
url: options.url
|
|
2412
|
-
}, () => currentTenantId === tenantId && currentUid === uid && (connectOptions.appName ?? "") === (options.appName ?? "")).catch(() => false).finally(() => {
|
|
2413
|
-
if (syncKey === key) {
|
|
2414
|
-
syncPromise = null;
|
|
2415
|
-
}
|
|
2416
|
-
});
|
|
2417
|
-
return syncPromise;
|
|
2418
|
-
};
|
|
2419
|
-
const connectInternal = (tenantId, uid, options, {
|
|
2420
|
-
resetReconnectAttempts
|
|
2421
|
-
}) => {
|
|
2422
|
-
ensureRealtimeRuntime();
|
|
2423
|
-
if (!tenantId) return Promise.resolve();
|
|
2424
|
-
if (!uid) throw new Error("Missing uid");
|
|
2425
|
-
currentTenantId = tenantId;
|
|
2426
|
-
currentUid = uid;
|
|
2427
|
-
connectOptions = options;
|
|
2428
|
-
if (options.configureStore !== false) {
|
|
2429
|
-
configureRtsPouchStore({
|
|
2430
|
-
tenantId,
|
|
2431
|
-
appName: options.appName
|
|
2432
|
-
});
|
|
2433
|
-
}
|
|
2434
|
-
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
|
|
2435
|
-
return connectPromise ?? Promise.resolve();
|
|
2436
|
-
}
|
|
2437
|
-
const synchronization = ensureSynced(tenantId, uid, options);
|
|
2438
|
-
explicitDisconnect = false;
|
|
2439
|
-
clearReconnectTimer();
|
|
2440
|
-
socketReadyForMessages = false;
|
|
2441
|
-
const url = buildSocketUrl(tenantId, uid, options);
|
|
2442
|
-
connectPromise = new Promise((resolve, reject) => {
|
|
2443
|
-
if (resetReconnectAttempts) reconnectAttempts = 0;
|
|
2444
|
-
let opened = false;
|
|
2445
|
-
let settled = false;
|
|
2446
|
-
let subscriptionsStarted = false;
|
|
2447
|
-
let cacheInvalidatedBeforeSubscriptions = false;
|
|
2448
|
-
const generation = ++connectionGeneration;
|
|
2449
|
-
const epoch = `${Date.now().toString(36)}.${generation.toString(36)}.${Math.random().toString(36).slice(2, 10)}`;
|
|
2450
|
-
connectionEpoch = epoch;
|
|
2451
|
-
pendingConnectionAttempt = {
|
|
2452
|
-
generation,
|
|
2453
|
-
reject: (error) => {
|
|
2454
|
-
if (settled) return;
|
|
2455
|
-
settled = true;
|
|
2456
|
-
reject(error);
|
|
2457
|
-
}
|
|
2458
|
-
};
|
|
2459
|
-
setConnectionStatus("connecting");
|
|
2460
|
-
const nextSocket = new WebSocket(url);
|
|
2461
|
-
socket = nextSocket;
|
|
2462
|
-
if (options.syncChanges !== false) {
|
|
2463
|
-
void synchronization.then((cacheInvalidated) => {
|
|
2464
|
-
if (!cacheInvalidated || generation !== connectionGeneration) return;
|
|
2465
|
-
if (!subscriptionsStarted) {
|
|
2466
|
-
cacheInvalidatedBeforeSubscriptions = true;
|
|
2467
|
-
return;
|
|
2468
|
-
}
|
|
2469
|
-
if (nextSocket.readyState !== WebSocket.OPEN) return;
|
|
2470
|
-
for (const subscription of subscriptions.values()) {
|
|
2471
|
-
if (!subscription.options.pagination) continue;
|
|
2472
|
-
requestRegisteredQueryRefresh(subscription);
|
|
2473
|
-
}
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
nextSocket.addEventListener("open", () => {
|
|
2477
|
-
if (generation !== connectionGeneration) return;
|
|
2478
|
-
opened = true;
|
|
2479
|
-
settled = true;
|
|
2480
|
-
if (pendingConnectionAttempt?.generation === generation) pendingConnectionAttempt = null;
|
|
2481
|
-
reconnectAttempts = 0;
|
|
2482
|
-
pendingServerReconnectJitter = false;
|
|
2483
|
-
socketReadyForMessages = true;
|
|
2484
|
-
setConnectionStatus("connected");
|
|
2485
|
-
for (const subscription of subscriptions.values()) {
|
|
2486
|
-
subscription.lastNetworkVersion = 0;
|
|
2487
|
-
if (subscription.runInitialLocalQuery && !subscription.hasRequestedInitialLocalQuery) {
|
|
2488
|
-
const cbKey = `${subscription.modelName}.${subscription.queryKey}`;
|
|
2489
|
-
subscription.hasRequestedInitialLocalQuery = requestSubscriptionLocalQuery(cbKey, subscription);
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
const forceInitialQuery = hasEstablishedConnection || forceInitialQueryOnNextConnection || cacheInvalidatedBeforeSubscriptions;
|
|
2493
|
-
resubscribeAll({
|
|
2494
|
-
forceInitialQuery
|
|
2495
|
-
});
|
|
2496
|
-
forceInitialQueryOnNextConnection = false;
|
|
2497
|
-
subscriptionsStarted = true;
|
|
2498
|
-
hasEstablishedConnection = true;
|
|
2499
|
-
resolve();
|
|
2500
|
-
});
|
|
2501
|
-
nextSocket.addEventListener("message", (event) => {
|
|
2502
|
-
if (generation !== connectionGeneration) return;
|
|
2503
|
-
handleMessage(event);
|
|
2504
|
-
});
|
|
2505
|
-
nextSocket.addEventListener("close", (event) => {
|
|
2506
|
-
if (generation !== connectionGeneration) return;
|
|
2507
|
-
socketReadyForMessages = false;
|
|
2508
|
-
if (!settled) {
|
|
2509
|
-
settled = true;
|
|
2510
|
-
if (pendingConnectionAttempt?.generation === generation) pendingConnectionAttempt = null;
|
|
2511
|
-
const error = new Error(`RTS WebSocket closed before becoming ready (code=${event.code})`);
|
|
2512
|
-
setConnectionStatus("error", error);
|
|
2513
|
-
reject(error);
|
|
2514
|
-
}
|
|
2515
|
-
if (!explicitDisconnect) {
|
|
2516
|
-
pendingServerReconnectJitter = opened;
|
|
2517
|
-
setConnectionStatus("connecting", connectionError);
|
|
2518
|
-
} else {
|
|
2519
|
-
setConnectionStatus("idle");
|
|
2520
|
-
}
|
|
2521
|
-
socket = null;
|
|
2522
|
-
connectPromise = null;
|
|
2523
|
-
scheduleReconnect();
|
|
2524
|
-
});
|
|
2525
|
-
nextSocket.addEventListener("error", (err) => {
|
|
2526
|
-
if (generation !== connectionGeneration) return;
|
|
2527
|
-
if (settled) return;
|
|
2528
|
-
settled = true;
|
|
2529
|
-
if (pendingConnectionAttempt?.generation === generation) pendingConnectionAttempt = null;
|
|
2530
|
-
socketReadyForMessages = false;
|
|
2531
|
-
const error = err instanceof Error ? err : new Error("RTS WebSocket error");
|
|
2532
|
-
setConnectionStatus("error", error);
|
|
2533
|
-
reject(error);
|
|
2534
|
-
});
|
|
2535
|
-
});
|
|
2536
|
-
return connectPromise;
|
|
2537
|
-
};
|
|
2538
|
-
const connect = (tenantId, uid, options = {}) => {
|
|
2539
|
-
const identityChanged = currentTenantId !== null && (currentTenantId !== tenantId || currentUid !== uid || (connectOptions.appName ?? "") !== (options.appName ?? ""));
|
|
2540
|
-
if (identityChanged) return reconnect(tenantId, uid, options);
|
|
2541
|
-
return connectInternal(tenantId, uid, options, {
|
|
2542
|
-
resetReconnectAttempts: true
|
|
2543
|
-
});
|
|
2544
|
-
};
|
|
2545
|
-
const disconnect = () => {
|
|
2546
|
-
explicitDisconnect = true;
|
|
2547
|
-
connectionGeneration += 1;
|
|
2548
|
-
rejectPendingConnectionAttempt();
|
|
2549
|
-
clearReconnectTimer();
|
|
2550
|
-
hasEstablishedConnection = false;
|
|
2551
|
-
forceInitialQueryOnNextConnection = false;
|
|
2552
|
-
pendingServerReconnectJitter = false;
|
|
2553
|
-
if (socket) {
|
|
2554
|
-
try {
|
|
2555
|
-
socket.close();
|
|
2556
|
-
} catch {
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
socket = null;
|
|
2560
|
-
socketReadyForMessages = false;
|
|
2561
|
-
connectPromise = null;
|
|
2562
|
-
connectionEpoch = null;
|
|
2563
|
-
cancelPendingWindowRequests();
|
|
2564
|
-
setConnectionStatus("idle");
|
|
2565
|
-
};
|
|
2566
|
-
const reconnect = (tenantId, uid, options = {}) => {
|
|
2567
|
-
const hasPreviousIdentity = currentTenantId !== null && currentUid !== null;
|
|
2568
|
-
const identityChanged = hasPreviousIdentity && (currentTenantId !== tenantId || currentUid !== uid || (connectOptions.appName ?? "") !== (options.appName ?? ""));
|
|
2569
|
-
if (identityChanged) {
|
|
2570
|
-
cancelPendingWindowRequests();
|
|
2571
|
-
for (const subscription of subscriptions.values()) {
|
|
2572
|
-
subscription.hasRequestedInitialLocalQuery = false;
|
|
2573
|
-
subscription.lastSnapshot = void 0;
|
|
2574
|
-
subscription.lastNetworkVersion = 0;
|
|
2575
|
-
subscription.windowIntentVersion += 1;
|
|
2576
|
-
subscription.requestedCount = subscription.initialWindowSize;
|
|
2577
|
-
delete subscription.localQueryPromise;
|
|
2578
|
-
dispatchQuerySnapshotToSubscription(subscription, {
|
|
2579
|
-
error: null,
|
|
2580
|
-
data: void 0,
|
|
2581
|
-
context: {
|
|
2582
|
-
source: "cache",
|
|
2583
|
-
reset: true,
|
|
2584
|
-
...subscription.initialWindowSize ? {
|
|
2585
|
-
window: {
|
|
2586
|
-
requestedCount: subscription.initialWindowSize
|
|
2587
|
-
}
|
|
2588
|
-
} : {}
|
|
2589
|
-
}
|
|
2590
|
-
});
|
|
2591
|
-
}
|
|
2592
|
-
for (const subscription of countSubscriptions.values()) {
|
|
2593
|
-
subscription.lastSnapshot = void 0;
|
|
2594
|
-
dispatchCountSnapshotToSubscription(subscription, {
|
|
2595
|
-
error: null,
|
|
2596
|
-
count: void 0,
|
|
2597
|
-
context: {
|
|
2598
|
-
source: "cache",
|
|
2599
|
-
reset: true
|
|
2600
|
-
}
|
|
2601
|
-
});
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
|
-
forceInitialQueryOnNextConnection = hasEstablishedConnection || identityChanged;
|
|
2605
|
-
explicitDisconnect = true;
|
|
2606
|
-
connectionGeneration += 1;
|
|
2607
|
-
rejectPendingConnectionAttempt();
|
|
2608
|
-
clearReconnectTimer();
|
|
2609
|
-
pendingServerReconnectJitter = false;
|
|
2610
|
-
if (socket) {
|
|
2611
|
-
try {
|
|
2612
|
-
socket.close();
|
|
2613
|
-
} catch {
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
socket = null;
|
|
2617
|
-
socketReadyForMessages = false;
|
|
2618
|
-
connectPromise = null;
|
|
2619
|
-
connectionEpoch = null;
|
|
2620
|
-
return connectInternal(tenantId, uid, options, {
|
|
2621
|
-
resetReconnectAttempts: true
|
|
2622
|
-
});
|
|
2623
|
-
};
|
|
2624
|
-
const getConnectionStatus = () => {
|
|
2625
|
-
return connectionStatus;
|
|
2626
|
-
};
|
|
2627
|
-
const getConnectionError = () => {
|
|
2628
|
-
return connectionError;
|
|
2629
|
-
};
|
|
2630
|
-
const onConnectionStatusChange = (callback) => {
|
|
2631
|
-
connectionStatusCallbacks.add(callback);
|
|
2632
|
-
return () => {
|
|
2633
|
-
connectionStatusCallbacks.delete(callback);
|
|
2634
|
-
};
|
|
2635
|
-
};
|
|
2636
|
-
const registerQuery = (modelName, query, optionsOrCallback, callbackMaybe, behavior) => {
|
|
2637
|
-
let options;
|
|
2638
|
-
let callback;
|
|
2639
|
-
if (typeof optionsOrCallback === "function") {
|
|
2640
|
-
options = {};
|
|
2641
|
-
callback = optionsOrCallback;
|
|
2642
|
-
} else {
|
|
2643
|
-
options = optionsOrCallback ?? {};
|
|
2644
|
-
callback = callbackMaybe;
|
|
2645
|
-
}
|
|
2646
|
-
if (!callback) return void 0;
|
|
2647
|
-
if (typeof modelName !== "string" || modelName.trim().length === 0) {
|
|
2648
|
-
throw new Error("registerQuery: modelName must be a non-empty string");
|
|
2649
|
-
}
|
|
2650
|
-
const queryKey = computeRtsQueryKey(query, options);
|
|
2651
|
-
const cbKey = `${modelName}.${queryKey}`;
|
|
2652
|
-
const runInitialNetworkQuery = behavior?.runInitialNetworkQuery !== false;
|
|
2653
|
-
const runInitialLocalQuery = behavior?.runInitialLocalQuery !== false;
|
|
2654
|
-
const forceRefreshOnMount = behavior?.forceRefreshOnMount === true;
|
|
2655
|
-
const populateCache = preparePopulateCacheOptions({
|
|
2656
|
-
projection: options.projection,
|
|
2657
|
-
populate: options.populate
|
|
2658
|
-
}, "registerQuery");
|
|
2659
|
-
const initialWindowSize = options.pagination ? normalizeRequestedCount(options.pagination.limit) : void 0;
|
|
2660
|
-
const existingSubscription = subscriptions.get(cbKey);
|
|
2661
|
-
const subscription = existingSubscription ?? {
|
|
2662
|
-
modelName,
|
|
2663
|
-
query,
|
|
2664
|
-
options,
|
|
2665
|
-
queryKey,
|
|
2666
|
-
callbacks: /* @__PURE__ */ new Set(),
|
|
2667
|
-
runInitialNetworkQuery,
|
|
2668
|
-
runInitialLocalQuery,
|
|
2669
|
-
hasRequestedInitialLocalQuery: false,
|
|
2670
|
-
lastNetworkVersion: 0,
|
|
2671
|
-
windowIntentVersion: 0,
|
|
2672
|
-
...initialWindowSize ? {
|
|
2673
|
-
initialWindowSize,
|
|
2674
|
-
requestedCount: initialWindowSize
|
|
2675
|
-
} : {},
|
|
2676
|
-
...populateCache ? {
|
|
2677
|
-
populateCache
|
|
2678
|
-
} : {}
|
|
2679
|
-
};
|
|
2680
|
-
const hadCallbacks = subscription.callbacks.size > 0;
|
|
2681
|
-
const hadInitialNetworkQuery = subscription.runInitialNetworkQuery;
|
|
2682
|
-
subscription.callbacks.add(callback);
|
|
2683
|
-
if (runInitialNetworkQuery) {
|
|
2684
|
-
subscription.runInitialNetworkQuery = true;
|
|
2685
|
-
}
|
|
2686
|
-
if (runInitialLocalQuery) {
|
|
2687
|
-
subscription.runInitialLocalQuery = true;
|
|
2688
|
-
}
|
|
2689
|
-
subscriptions.set(cbKey, subscription);
|
|
2690
|
-
let seeded = false;
|
|
2691
|
-
if (behavior?.seedSnapshot && updateQuerySubscriptionSnapshot(subscription, behavior.seedSnapshot)) {
|
|
2692
|
-
dispatchQuerySnapshotToSubscription(subscription, behavior.seedSnapshot);
|
|
2693
|
-
seeded = true;
|
|
2694
|
-
}
|
|
2695
|
-
if (!seeded && hadCallbacks && subscription.lastSnapshot && !hasSnapshotError(subscription.lastSnapshot)) {
|
|
2696
|
-
callback(subscription.lastSnapshot.error, subscription.lastSnapshot.data, subscription.lastSnapshot.context);
|
|
2697
|
-
}
|
|
2698
|
-
if (!hadCallbacks) {
|
|
2699
|
-
if (runInitialLocalQuery) {
|
|
2700
|
-
subscription.hasRequestedInitialLocalQuery = requestSubscriptionLocalQuery(cbKey, subscription);
|
|
2701
|
-
}
|
|
2702
|
-
sendToServer({
|
|
2703
|
-
type: "register-query",
|
|
2704
|
-
modelName,
|
|
2705
|
-
queryKey,
|
|
2706
|
-
query,
|
|
2707
|
-
options: getSubscriptionServerOptions(subscription),
|
|
2708
|
-
runInitialQuery: runInitialNetworkQuery
|
|
2709
|
-
});
|
|
2710
|
-
} else {
|
|
2711
|
-
if (runInitialLocalQuery && !subscription.hasRequestedInitialLocalQuery) {
|
|
2712
|
-
subscription.hasRequestedInitialLocalQuery = requestSubscriptionLocalQuery(cbKey, subscription);
|
|
2713
|
-
}
|
|
2714
|
-
if (forceRefreshOnMount || runInitialNetworkQuery && (!hadInitialNetworkQuery || hasSnapshotError(subscription.lastSnapshot))) {
|
|
2715
|
-
requestRegisteredQueryRefresh(subscription);
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
|
-
return () => {
|
|
2719
|
-
const currentSubscription = subscriptions.get(cbKey);
|
|
2720
|
-
currentSubscription?.callbacks.delete(callback);
|
|
2721
|
-
if (currentSubscription && currentSubscription.callbacks.size === 0) {
|
|
2722
|
-
subscriptions.delete(cbKey);
|
|
2723
|
-
cancelPendingWindowRequests(cbKey);
|
|
2724
|
-
sendToServer({
|
|
2725
|
-
type: "remove-query",
|
|
2726
|
-
modelName,
|
|
2727
|
-
queryKey
|
|
2728
|
-
});
|
|
2729
|
-
}
|
|
2730
|
-
};
|
|
2731
|
-
};
|
|
2732
|
-
const setQueryWindowSize = async ({
|
|
2733
|
-
modelName,
|
|
2734
|
-
query,
|
|
2735
|
-
options,
|
|
2736
|
-
requestedCount,
|
|
2737
|
-
timeoutMs = 1e4
|
|
2738
|
-
}) => {
|
|
2739
|
-
const normalizedRequestedCount = normalizeRequestedCount(requestedCount);
|
|
2740
|
-
if (!normalizedRequestedCount || !options.pagination) return false;
|
|
2741
|
-
const queryKey = computeRtsQueryKey(query, options);
|
|
2742
|
-
const cbKey = `${modelName}.${queryKey}`;
|
|
2743
|
-
const subscription = subscriptions.get(cbKey);
|
|
2744
|
-
if (!subscription?.options.pagination) return false;
|
|
2745
|
-
const currentNetworkWindow = subscription.lastSnapshot?.context.source === "network" ? subscription.lastSnapshot.context.window : void 0;
|
|
2746
|
-
if (subscription.requestedCount === normalizedRequestedCount && currentNetworkWindow?.requestedCount === normalizedRequestedCount) {
|
|
2747
|
-
return true;
|
|
2748
|
-
}
|
|
2749
|
-
const previousRequestedCount = subscription.requestedCount ?? subscription.initialWindowSize ?? normalizedRequestedCount;
|
|
2750
|
-
subscription.windowIntentVersion += 1;
|
|
2751
|
-
subscription.requestedCount = normalizedRequestedCount;
|
|
2752
|
-
let request;
|
|
2753
|
-
const response = new Promise((resolve) => {
|
|
2754
|
-
const timeoutId = setRuntimeTimeout(() => {
|
|
2755
|
-
settlePendingWindowRequests(cbKey, request.requestedCount, false);
|
|
2756
|
-
}, timeoutMs);
|
|
2757
|
-
request = {
|
|
2758
|
-
requestedCount: normalizedRequestedCount,
|
|
2759
|
-
previousRequestedCount,
|
|
2760
|
-
resolve,
|
|
2761
|
-
timeoutId
|
|
2762
|
-
};
|
|
2763
|
-
const pending = pendingWindowRequests.get(cbKey) ?? /* @__PURE__ */ new Set();
|
|
2764
|
-
pending.add(request);
|
|
2765
|
-
pendingWindowRequests.set(cbKey, pending);
|
|
2766
|
-
});
|
|
2767
|
-
try {
|
|
2768
|
-
if (!isSocketReady()) {
|
|
2769
|
-
if (!currentTenantId || !currentUid) {
|
|
2770
|
-
settlePendingWindowRequests(cbKey, normalizedRequestedCount, false);
|
|
2771
|
-
return false;
|
|
2772
|
-
}
|
|
2773
|
-
await connectInternal(currentTenantId, currentUid, connectOptions, {
|
|
2774
|
-
resetReconnectAttempts: false
|
|
2775
|
-
});
|
|
2776
|
-
}
|
|
2777
|
-
sendToServer({
|
|
2778
|
-
type: "set-query-window",
|
|
2779
|
-
modelName,
|
|
2780
|
-
queryKey,
|
|
2781
|
-
requestedCount: normalizedRequestedCount
|
|
2782
|
-
});
|
|
2783
|
-
} catch {
|
|
2784
|
-
settlePendingWindowRequests(cbKey, normalizedRequestedCount, false);
|
|
2785
|
-
return false;
|
|
2786
|
-
}
|
|
2787
|
-
return await response;
|
|
2788
|
-
};
|
|
2789
|
-
const registerCount = (modelName, query, optionsOrCallback, callbackMaybe, behavior) => {
|
|
2790
|
-
let options;
|
|
2791
|
-
let callback;
|
|
2792
|
-
if (typeof optionsOrCallback === "function") {
|
|
2793
|
-
options = {};
|
|
2794
|
-
callback = optionsOrCallback;
|
|
2795
|
-
} else {
|
|
2796
|
-
options = optionsOrCallback ?? {};
|
|
2797
|
-
callback = callbackMaybe;
|
|
2798
|
-
}
|
|
2799
|
-
if (!callback) return void 0;
|
|
2800
|
-
if (typeof modelName !== "string" || modelName.trim().length === 0) {
|
|
2801
|
-
throw new Error("registerCount: modelName must be a non-empty string");
|
|
2802
|
-
}
|
|
2803
|
-
const queryKey = computeRtsQueryKey(query, options);
|
|
2804
|
-
const cbKey = `${modelName}.${queryKey}`;
|
|
2805
|
-
const runInitialNetworkQuery = behavior?.runInitialNetworkQuery !== false;
|
|
2806
|
-
const forceRefreshOnMount = behavior?.forceRefreshOnMount === true;
|
|
2807
|
-
const existingSubscription = countSubscriptions.get(cbKey);
|
|
2808
|
-
const subscription = existingSubscription ?? {
|
|
2809
|
-
modelName,
|
|
2810
|
-
query,
|
|
2811
|
-
options,
|
|
2812
|
-
queryKey,
|
|
2813
|
-
callbacks: /* @__PURE__ */ new Set(),
|
|
2814
|
-
runInitialNetworkQuery
|
|
2815
|
-
};
|
|
2816
|
-
const hadCallbacks = subscription.callbacks.size > 0;
|
|
2817
|
-
const hadInitialNetworkQuery = subscription.runInitialNetworkQuery;
|
|
2818
|
-
subscription.callbacks.add(callback);
|
|
2819
|
-
if (runInitialNetworkQuery) {
|
|
2820
|
-
subscription.runInitialNetworkQuery = true;
|
|
2821
|
-
}
|
|
2822
|
-
countSubscriptions.set(cbKey, subscription);
|
|
2823
|
-
let seeded = false;
|
|
2824
|
-
if (behavior?.seedSnapshot && updateCountSubscriptionSnapshot(subscription, behavior.seedSnapshot)) {
|
|
2825
|
-
dispatchCountSnapshotToSubscription(subscription, behavior.seedSnapshot);
|
|
2826
|
-
seeded = true;
|
|
2827
|
-
}
|
|
2828
|
-
if (!seeded && hadCallbacks && subscription.lastSnapshot && !hasSnapshotError(subscription.lastSnapshot)) {
|
|
2829
|
-
callback(subscription.lastSnapshot.error, subscription.lastSnapshot.count, subscription.lastSnapshot.context);
|
|
2830
|
-
}
|
|
2831
|
-
if (!hadCallbacks) {
|
|
2832
|
-
sendToServer({
|
|
2833
|
-
type: "register-count",
|
|
2834
|
-
modelName,
|
|
2835
|
-
queryKey,
|
|
2836
|
-
query,
|
|
2837
|
-
options,
|
|
2838
|
-
runInitialQuery: runInitialNetworkQuery
|
|
2839
|
-
});
|
|
2840
|
-
} else if (forceRefreshOnMount || runInitialNetworkQuery && (!hadInitialNetworkQuery || hasSnapshotError(subscription.lastSnapshot))) {
|
|
2841
|
-
requestRegisteredCountRefresh(subscription);
|
|
2842
|
-
}
|
|
2843
|
-
return () => {
|
|
2844
|
-
const currentSubscription = countSubscriptions.get(cbKey);
|
|
2845
|
-
currentSubscription?.callbacks.delete(callback);
|
|
2846
|
-
if (currentSubscription && currentSubscription.callbacks.size === 0) {
|
|
2847
|
-
countSubscriptions.delete(cbKey);
|
|
2848
|
-
sendToServer({
|
|
2849
|
-
type: "remove-count",
|
|
2850
|
-
modelName,
|
|
2851
|
-
queryKey
|
|
2852
|
-
});
|
|
2853
|
-
}
|
|
2854
|
-
};
|
|
2855
|
-
};
|
|
2856
|
-
const makeRunQueryKey = () => `run-query.${Date.now().toString(36)}.${Math.random().toString(36).slice(2, 10)}`;
|
|
2857
|
-
const runNetworkQuery = async ({
|
|
2858
|
-
modelName,
|
|
2859
|
-
query,
|
|
2860
|
-
options = {},
|
|
2861
|
-
timeoutMs = 1e4
|
|
2862
|
-
}) => {
|
|
2863
|
-
if (typeof modelName !== "string" || modelName.trim().length === 0) {
|
|
2864
|
-
throw new Error("runNetworkQuery: modelName must be a non-empty string");
|
|
2865
|
-
}
|
|
2866
|
-
preparePopulateCacheOptions({
|
|
2867
|
-
projection: options.projection,
|
|
2868
|
-
populate: options.populate
|
|
2869
|
-
}, "runNetworkQuery");
|
|
2870
|
-
const hasTimeout = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0;
|
|
2871
|
-
const timeoutStartedAt = hasTimeout ? Date.now() : 0;
|
|
2872
|
-
if (!isSocketReady()) {
|
|
2873
|
-
if (currentTenantId && currentUid) {
|
|
2874
|
-
try {
|
|
2875
|
-
const connectAttempt = connectInternal(currentTenantId, currentUid, connectOptions, {
|
|
2876
|
-
resetReconnectAttempts: false
|
|
2877
|
-
});
|
|
2878
|
-
if (hasTimeout) {
|
|
2879
|
-
await new Promise((resolve, reject) => {
|
|
2880
|
-
const timeoutId = setRuntimeTimeout(() => {
|
|
2881
|
-
reject(new Error(RUN_NETWORK_QUERY_TIMEOUT_ERROR));
|
|
2882
|
-
}, timeoutMs);
|
|
2883
|
-
connectAttempt.then(() => {
|
|
2884
|
-
clearRuntimeTimeout(timeoutId);
|
|
2885
|
-
resolve();
|
|
2886
|
-
}, (error) => {
|
|
2887
|
-
clearRuntimeTimeout(timeoutId);
|
|
2888
|
-
reject(error);
|
|
2889
|
-
});
|
|
2890
|
-
});
|
|
2891
|
-
} else {
|
|
2892
|
-
await connectAttempt;
|
|
2893
|
-
}
|
|
2894
|
-
} catch {
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
}
|
|
2898
|
-
if (!isSocketReady()) {
|
|
2899
|
-
if (hasTimeout && Date.now() - timeoutStartedAt >= timeoutMs) {
|
|
2900
|
-
throw new Error(RUN_NETWORK_QUERY_TIMEOUT_ERROR);
|
|
2901
|
-
}
|
|
2902
|
-
throw new Error("runNetworkQuery: RTS socket is not connected");
|
|
2903
|
-
}
|
|
2904
|
-
const remainingTimeoutMs = hasTimeout ? Math.max(0, timeoutMs - (Date.now() - timeoutStartedAt)) : null;
|
|
2905
|
-
if (remainingTimeoutMs !== null && remainingTimeoutMs <= 0) {
|
|
2906
|
-
throw new Error(RUN_NETWORK_QUERY_TIMEOUT_ERROR);
|
|
2907
|
-
}
|
|
2908
|
-
const resolvedOptions = {
|
|
2909
|
-
...options,
|
|
2910
|
-
key: makeRunQueryKey()
|
|
2911
|
-
};
|
|
2912
|
-
const queryKey = computeRtsQueryKey(query, resolvedOptions);
|
|
2913
|
-
const cbKey = `${modelName}.${queryKey}`;
|
|
2914
|
-
return await new Promise((resolve, reject) => {
|
|
2915
|
-
let settled = false;
|
|
2916
|
-
let timeoutId = null;
|
|
2917
|
-
const cleanup = () => {
|
|
2918
|
-
const callbacks2 = queryCallbacks.get(cbKey);
|
|
2919
|
-
callbacks2?.delete(callback);
|
|
2920
|
-
if (callbacks2 && callbacks2.size === 0) queryCallbacks.delete(cbKey);
|
|
2921
|
-
if (timeoutId !== null) {
|
|
2922
|
-
clearRuntimeTimeout(timeoutId);
|
|
2923
|
-
}
|
|
2924
|
-
};
|
|
2925
|
-
const settle = (next) => {
|
|
2926
|
-
if (settled) return;
|
|
2927
|
-
settled = true;
|
|
2928
|
-
cleanup();
|
|
2929
|
-
next();
|
|
2930
|
-
};
|
|
2931
|
-
const callback = (error, data, context) => {
|
|
2932
|
-
if (error) {
|
|
2933
|
-
settle(() => reject(error));
|
|
2934
|
-
return;
|
|
2935
|
-
}
|
|
2936
|
-
settle(() => resolve({
|
|
2937
|
-
data,
|
|
2938
|
-
context
|
|
2939
|
-
}));
|
|
2940
|
-
};
|
|
2941
|
-
const callbacks = queryCallbacks.get(cbKey) ?? /* @__PURE__ */ new Set();
|
|
2942
|
-
callbacks.add(callback);
|
|
2943
|
-
queryCallbacks.set(cbKey, callbacks);
|
|
2944
|
-
if (remainingTimeoutMs !== null) {
|
|
2945
|
-
timeoutId = setRuntimeTimeout(() => {
|
|
2946
|
-
settle(() => reject(new Error(RUN_NETWORK_QUERY_TIMEOUT_ERROR)));
|
|
2947
|
-
}, remainingTimeoutMs);
|
|
2948
|
-
}
|
|
2949
|
-
sendToServer({
|
|
2950
|
-
type: "run-query",
|
|
2951
|
-
modelName,
|
|
2952
|
-
queryKey,
|
|
2953
|
-
query,
|
|
2954
|
-
options: resolvedOptions
|
|
2955
|
-
});
|
|
2956
|
-
});
|
|
2957
|
-
};
|
|
2958
|
-
const runNetworkCount = async ({
|
|
2959
|
-
modelName,
|
|
2960
|
-
query,
|
|
2961
|
-
options = {},
|
|
2962
|
-
timeoutMs = 1e4
|
|
2963
|
-
}) => {
|
|
2964
|
-
if (typeof modelName !== "string" || modelName.trim().length === 0) {
|
|
2965
|
-
throw new Error("runNetworkCount: modelName must be a non-empty string");
|
|
2966
|
-
}
|
|
2967
|
-
const hasTimeout = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0;
|
|
2968
|
-
const timeoutStartedAt = hasTimeout ? Date.now() : 0;
|
|
2969
|
-
if (!isSocketReady()) {
|
|
2970
|
-
if (currentTenantId && currentUid) {
|
|
2971
|
-
try {
|
|
2972
|
-
const connectAttempt = connectInternal(currentTenantId, currentUid, connectOptions, {
|
|
2973
|
-
resetReconnectAttempts: false
|
|
2974
|
-
});
|
|
2975
|
-
if (hasTimeout) {
|
|
2976
|
-
await new Promise((resolve, reject) => {
|
|
2977
|
-
const timeoutId = setRuntimeTimeout(() => {
|
|
2978
|
-
reject(new Error(RUN_NETWORK_COUNT_TIMEOUT_ERROR));
|
|
2979
|
-
}, timeoutMs);
|
|
2980
|
-
connectAttempt.then(() => {
|
|
2981
|
-
clearRuntimeTimeout(timeoutId);
|
|
2982
|
-
resolve();
|
|
2983
|
-
}, (error) => {
|
|
2984
|
-
clearRuntimeTimeout(timeoutId);
|
|
2985
|
-
reject(error);
|
|
2986
|
-
});
|
|
2987
|
-
});
|
|
2988
|
-
} else {
|
|
2989
|
-
await connectAttempt;
|
|
2990
|
-
}
|
|
2991
|
-
} catch {
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
if (!isSocketReady()) {
|
|
2996
|
-
if (hasTimeout && Date.now() - timeoutStartedAt >= timeoutMs) {
|
|
2997
|
-
throw new Error(RUN_NETWORK_COUNT_TIMEOUT_ERROR);
|
|
2998
|
-
}
|
|
2999
|
-
throw new Error("runNetworkCount: RTS socket is not connected");
|
|
3000
|
-
}
|
|
3001
|
-
const remainingTimeoutMs = hasTimeout ? Math.max(0, timeoutMs - (Date.now() - timeoutStartedAt)) : null;
|
|
3002
|
-
if (remainingTimeoutMs !== null && remainingTimeoutMs <= 0) {
|
|
3003
|
-
throw new Error(RUN_NETWORK_COUNT_TIMEOUT_ERROR);
|
|
3004
|
-
}
|
|
3005
|
-
const resolvedOptions = {
|
|
3006
|
-
...options,
|
|
3007
|
-
key: makeRunQueryKey()
|
|
3008
|
-
};
|
|
3009
|
-
const queryKey = computeRtsQueryKey(query, resolvedOptions);
|
|
3010
|
-
const cbKey = `${modelName}.${queryKey}`;
|
|
3011
|
-
return await new Promise((resolve, reject) => {
|
|
3012
|
-
let settled = false;
|
|
3013
|
-
let timeoutId = null;
|
|
3014
|
-
const cleanup = () => {
|
|
3015
|
-
const callbacks = countCallbacks.get(cbKey);
|
|
3016
|
-
callbacks?.delete(callback);
|
|
3017
|
-
if (callbacks && callbacks.size === 0) countCallbacks.delete(cbKey);
|
|
3018
|
-
if (timeoutId !== null) {
|
|
3019
|
-
clearRuntimeTimeout(timeoutId);
|
|
3020
|
-
}
|
|
3021
|
-
};
|
|
3022
|
-
const settle = (next) => {
|
|
3023
|
-
if (settled) return;
|
|
3024
|
-
settled = true;
|
|
3025
|
-
cleanup();
|
|
3026
|
-
next();
|
|
3027
|
-
};
|
|
3028
|
-
const callback = (error, count, context) => {
|
|
3029
|
-
if (error) {
|
|
3030
|
-
settle(() => reject(typeof error === "string" ? new Error(error) : error));
|
|
3031
|
-
return;
|
|
3032
|
-
}
|
|
3033
|
-
if (count === void 0) {
|
|
3034
|
-
settle(() => reject(new Error("runNetworkCount: invalid count payload")));
|
|
3035
|
-
return;
|
|
3036
|
-
}
|
|
3037
|
-
settle(() => resolve({
|
|
3038
|
-
count,
|
|
3039
|
-
context
|
|
3040
|
-
}));
|
|
3041
|
-
};
|
|
3042
|
-
const set = countCallbacks.get(cbKey) ?? /* @__PURE__ */ new Set();
|
|
3043
|
-
set.add(callback);
|
|
3044
|
-
countCallbacks.set(cbKey, set);
|
|
3045
|
-
if (remainingTimeoutMs !== null) {
|
|
3046
|
-
timeoutId = setRuntimeTimeout(() => {
|
|
3047
|
-
settle(() => reject(new Error(RUN_NETWORK_COUNT_TIMEOUT_ERROR)));
|
|
3048
|
-
}, remainingTimeoutMs);
|
|
3049
|
-
}
|
|
3050
|
-
sendToServer({
|
|
3051
|
-
type: "run-count",
|
|
3052
|
-
modelName,
|
|
3053
|
-
queryKey,
|
|
3054
|
-
query,
|
|
3055
|
-
options: resolvedOptions
|
|
3056
|
-
});
|
|
3057
|
-
});
|
|
3058
|
-
};
|
|
3059
|
-
const sendMessage = (event, payload) => {
|
|
3060
|
-
sendToServer({
|
|
3061
|
-
type: "event",
|
|
3062
|
-
event,
|
|
3063
|
-
payload
|
|
3064
|
-
});
|
|
3065
|
-
};
|
|
3066
|
-
const sendRtsMessage = (channel, payload) => {
|
|
3067
|
-
sendToServer({
|
|
3068
|
-
type: "rts-message",
|
|
3069
|
-
channel,
|
|
3070
|
-
payload
|
|
3071
|
-
});
|
|
3072
|
-
};
|
|
3073
|
-
const onMessage = (event, callback) => {
|
|
3074
|
-
const set = messageCallbacks.get(event) ?? /* @__PURE__ */ new Set();
|
|
3075
|
-
set.add(callback);
|
|
3076
|
-
messageCallbacks.set(event, set);
|
|
3077
|
-
return () => {
|
|
3078
|
-
const callbacks = messageCallbacks.get(event);
|
|
3079
|
-
callbacks?.delete(callback);
|
|
3080
|
-
if (callbacks && callbacks.size === 0) messageCallbacks.delete(event);
|
|
3081
|
-
};
|
|
3082
|
-
};
|
|
3083
|
-
const onRtsMessage = (channel, callback) => {
|
|
3084
|
-
const set = rtsMessageCallbacks.get(channel) ?? /* @__PURE__ */ new Set();
|
|
3085
|
-
set.add(callback);
|
|
3086
|
-
rtsMessageCallbacks.set(channel, set);
|
|
3087
|
-
return () => {
|
|
3088
|
-
const callbacks = rtsMessageCallbacks.get(channel);
|
|
3089
|
-
callbacks?.delete(callback);
|
|
3090
|
-
if (callbacks && callbacks.size === 0) rtsMessageCallbacks.delete(channel);
|
|
3091
|
-
};
|
|
3092
|
-
};
|
|
3093
|
-
const normalizePageInfo = (value) => {
|
|
3094
|
-
if (!value || typeof value !== "object") return void 0;
|
|
3095
|
-
if (Array.isArray(value)) return void 0;
|
|
3096
|
-
const raw = value;
|
|
3097
|
-
if (typeof raw.hasNextPage !== "boolean" || typeof raw.hasPrevPage !== "boolean") return void 0;
|
|
3098
|
-
const nextCursor = typeof raw.nextCursor === "string" && raw.nextCursor ? raw.nextCursor : void 0;
|
|
3099
|
-
const prevCursor = typeof raw.prevCursor === "string" && raw.prevCursor ? raw.prevCursor : void 0;
|
|
3100
|
-
return {
|
|
3101
|
-
hasNextPage: raw.hasNextPage,
|
|
3102
|
-
hasPrevPage: raw.hasPrevPage,
|
|
3103
|
-
...nextCursor ? {
|
|
3104
|
-
nextCursor
|
|
3105
|
-
} : {},
|
|
3106
|
-
...prevCursor ? {
|
|
3107
|
-
prevCursor
|
|
3108
|
-
} : {}
|
|
3109
|
-
};
|
|
3110
|
-
};
|
|
3111
|
-
const normalizeTotalCount = (value) => {
|
|
3112
|
-
if (typeof value !== "number") return void 0;
|
|
3113
|
-
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
3114
|
-
return Math.floor(value);
|
|
3115
|
-
};
|
|
3116
|
-
const normalizeWindowSize = (value) => {
|
|
3117
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 1;
|
|
3118
|
-
return Math.max(1, Math.min(RTS_QUERY_WINDOW_MAX_COUNT, Math.floor(value)));
|
|
3119
|
-
};
|
|
3120
|
-
const assertIncludeOnlyProjection = (projection) => {
|
|
3121
|
-
if (!projection) return;
|
|
3122
|
-
for (const [path, value] of Object.entries(projection)) {
|
|
3123
|
-
if (!path.trim()) continue;
|
|
3124
|
-
if (value !== 1) {
|
|
3125
|
-
throw new Error("useQuery: projection must be include-only (value 1); exclusion projection is not supported");
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3128
|
-
};
|
|
3129
|
-
const useQuery = (modelName, query = {}, options = {}) => {
|
|
3130
|
-
if (typeof modelName !== "string" || modelName.trim().length === 0) {
|
|
3131
|
-
throw new Error("useQuery: modelName must be a non-empty string");
|
|
3132
|
-
}
|
|
3133
|
-
assertIncludeOnlyProjection(options.projection);
|
|
3134
|
-
const enabled = options.enabled ?? true;
|
|
3135
|
-
const ssrEnabled = options.ssr !== false;
|
|
3136
|
-
const refreshOnMount = options.refreshOnMount === true;
|
|
3137
|
-
const key = options.key;
|
|
3138
|
-
const queryJson = serializeRtsQueryValue(query);
|
|
3139
|
-
const projectionJson = options.projection ? serializeRtsQueryValue(options.projection) : "";
|
|
3140
|
-
const sortJson = options.sort ? serializeRtsQueryValue(options.sort) : "";
|
|
3141
|
-
const limitStr = typeof options.limit === "number" ? String(options.limit) : "";
|
|
3142
|
-
const populateJson = options.populate ? serializeRtsQueryValue(options.populate) : "";
|
|
3143
|
-
const paginationJson = options.pagination ? serializeRtsQueryValue(options.pagination) : "";
|
|
3144
|
-
preparePopulateCacheOptions({
|
|
3145
|
-
projection: options.projection,
|
|
3146
|
-
populate: options.populate
|
|
3147
|
-
}, "useQuery");
|
|
3148
|
-
const isPaginated = Boolean(options.pagination);
|
|
3149
|
-
if (options.pagination && (!Number.isInteger(options.pagination.limit) || options.pagination.limit < 1 || options.pagination.limit > RTS_QUERY_WINDOW_MAX_COUNT)) {
|
|
3150
|
-
throw new Error("useQuery: pagination limit must be an integer between 1 and 4096");
|
|
3151
|
-
}
|
|
3152
|
-
if (options.pagination?.cursor || options.pagination?.direction) {
|
|
3153
|
-
throw new Error("useQuery: cursor and direction are not supported by virtual query windows");
|
|
3154
|
-
}
|
|
3155
|
-
const initialWindowSize = isPaginated ? normalizeWindowSize(options.pagination?.limit) : void 0;
|
|
3156
|
-
const queryKey = computeRtsQueryKey(query, {
|
|
3157
|
-
key,
|
|
3158
|
-
projection: options.projection,
|
|
3159
|
-
sort: options.sort,
|
|
3160
|
-
limit: options.limit,
|
|
3161
|
-
populate: options.populate,
|
|
3162
|
-
pagination: options.pagination
|
|
3163
|
-
});
|
|
3164
|
-
const ssrRuntime = useRtsSsrRuntime();
|
|
3165
|
-
if (enabled && ssrEnabled && ssrRuntime) {
|
|
3166
|
-
ssrRuntime.registerQuery({
|
|
3167
|
-
modelName,
|
|
3168
|
-
query,
|
|
3169
|
-
options: {
|
|
3170
|
-
key,
|
|
3171
|
-
projection: options.projection,
|
|
3172
|
-
sort: options.sort,
|
|
3173
|
-
limit: options.limit,
|
|
3174
|
-
populate: options.populate,
|
|
3175
|
-
pagination: options.pagination
|
|
3176
|
-
},
|
|
3177
|
-
queryKey
|
|
3178
|
-
});
|
|
3179
|
-
}
|
|
3180
|
-
const seedDataRaw = useMemo(() => enabled && ssrEnabled ? ssrRuntime ? ssrRuntime.getQueryData(modelName, queryKey) : peekHydratedRtsQueryData(modelName, queryKey) : void 0, [enabled, ssrEnabled, ssrRuntime, modelName, queryKey]);
|
|
3181
|
-
const seedPageInfoRaw = useMemo(() => enabled && ssrEnabled ? ssrRuntime ? ssrRuntime.getQueryPageInfo(modelName, queryKey) : peekHydratedRtsQueryPageInfo(modelName, queryKey) : void 0, [enabled, ssrEnabled, ssrRuntime, modelName, queryKey]);
|
|
3182
|
-
const seedTotalCountRaw = useMemo(() => enabled && ssrEnabled ? ssrRuntime ? ssrRuntime.getQueryTotalCount(modelName, queryKey) : peekHydratedRtsQueryTotalCount(modelName, queryKey) : void 0, [enabled, ssrEnabled, ssrRuntime, modelName, queryKey]);
|
|
3183
|
-
const hasSeedData = Array.isArray(seedDataRaw);
|
|
3184
|
-
const seedData = hasSeedData ? seedDataRaw : void 0;
|
|
3185
|
-
const seedPageInfo = useMemo(() => normalizePageInfo(seedPageInfoRaw), [seedPageInfoRaw]);
|
|
3186
|
-
const seedTotalCount = normalizeTotalCount(seedTotalCountRaw);
|
|
3187
|
-
const seedJson = (() => {
|
|
3188
|
-
if (!hasSeedData) return "";
|
|
3189
|
-
try {
|
|
3190
|
-
return JSON.stringify(seedDataRaw);
|
|
3191
|
-
} catch {
|
|
3192
|
-
return "";
|
|
3193
|
-
}
|
|
3194
|
-
})();
|
|
3195
|
-
const seedPageInfoJson = (() => {
|
|
3196
|
-
if (!seedPageInfo) return "";
|
|
3197
|
-
try {
|
|
3198
|
-
return JSON.stringify(seedPageInfo);
|
|
3199
|
-
} catch {
|
|
3200
|
-
return "";
|
|
3201
|
-
}
|
|
3202
|
-
})();
|
|
3203
|
-
const seedTotalCountStr = seedTotalCount !== void 0 ? String(seedTotalCount) : "";
|
|
3204
|
-
const [data, setData] = useState(() => isPaginated ? void 0 : seedData);
|
|
3205
|
-
const [paginatedData, setPaginatedData] = useState(() => isPaginated ? seedData : void 0);
|
|
3206
|
-
const [pageInfo, setPageInfo] = useState(() => isPaginated ? seedPageInfo : void 0);
|
|
3207
|
-
const [totalCount, setTotalCount] = useState(() => isPaginated ? seedTotalCount : void 0);
|
|
3208
|
-
const [source, setSource] = useState(() => hasSeedData ? "cache" : void 0);
|
|
3209
|
-
const [error, setError] = useState(void 0);
|
|
3210
|
-
const [loading, setLoading] = useState(enabled && !hasSeedData);
|
|
3211
|
-
const hasFirstReply = useRef(false);
|
|
3212
|
-
const hasNetworkReply = useRef(false);
|
|
3213
|
-
const lastDataJsonRef = useRef("");
|
|
3214
|
-
const pageInfoRef = useRef(seedPageInfo);
|
|
3215
|
-
const requestedCountRef = useRef(initialWindowSize ?? 1);
|
|
3216
|
-
const pagingRef = useRef(false);
|
|
3217
|
-
useEffect(() => {
|
|
3218
|
-
pageInfoRef.current = pageInfo;
|
|
3219
|
-
}, [pageInfo]);
|
|
3220
|
-
useEffect(() => {
|
|
3221
|
-
if (!ssrRuntime && enabled && ssrEnabled && hasSeedData) {
|
|
3222
|
-
consumeHydratedRtsQueryData(modelName, queryKey);
|
|
3223
|
-
}
|
|
3224
|
-
hasFirstReply.current = hasSeedData;
|
|
3225
|
-
hasNetworkReply.current = false;
|
|
3226
|
-
lastDataJsonRef.current = seedJson;
|
|
3227
|
-
requestedCountRef.current = initialWindowSize ?? 1;
|
|
3228
|
-
pagingRef.current = false;
|
|
3229
|
-
setError(void 0);
|
|
3230
|
-
if (!enabled) {
|
|
3231
|
-
setLoading(false);
|
|
3232
|
-
setData(void 0);
|
|
3233
|
-
setPaginatedData(void 0);
|
|
3234
|
-
setPageInfo(void 0);
|
|
3235
|
-
setTotalCount(void 0);
|
|
3236
|
-
setSource(void 0);
|
|
3237
|
-
return;
|
|
3238
|
-
}
|
|
3239
|
-
if (hasSeedData) {
|
|
3240
|
-
const nextSeedData = seedData;
|
|
3241
|
-
setLoading(false);
|
|
3242
|
-
setSource("cache");
|
|
3243
|
-
if (isPaginated) {
|
|
3244
|
-
setPaginatedData(nextSeedData);
|
|
3245
|
-
setPageInfo(seedPageInfo);
|
|
3246
|
-
setTotalCount(seedTotalCount);
|
|
3247
|
-
setData(void 0);
|
|
3248
|
-
} else {
|
|
3249
|
-
setData(nextSeedData);
|
|
3250
|
-
setTotalCount(void 0);
|
|
3251
|
-
setPaginatedData(void 0);
|
|
3252
|
-
setPageInfo(void 0);
|
|
3253
|
-
}
|
|
3254
|
-
return;
|
|
3255
|
-
}
|
|
3256
|
-
setData(void 0);
|
|
3257
|
-
setPaginatedData(void 0);
|
|
3258
|
-
setPageInfo(void 0);
|
|
3259
|
-
setTotalCount(void 0);
|
|
3260
|
-
setLoading(true);
|
|
3261
|
-
}, [enabled, ssrEnabled, ssrRuntime, modelName, queryKey, hasSeedData, seedData, seedJson, seedPageInfo, seedPageInfoJson, seedTotalCount, seedTotalCountStr, isPaginated, initialWindowSize]);
|
|
3262
|
-
useEffect(() => {
|
|
3263
|
-
if (!enabled) return;
|
|
3264
|
-
const runInitialNetworkQuery = isPaginated || refreshOnMount || !hasSeedData;
|
|
3265
|
-
const runInitialLocalQuery = !hasSeedData;
|
|
3266
|
-
const unsubscribe = registerQuery(modelName, query, {
|
|
3267
|
-
key,
|
|
3268
|
-
projection: options.projection,
|
|
3269
|
-
sort: options.sort,
|
|
3270
|
-
limit: options.limit,
|
|
3271
|
-
populate: options.populate,
|
|
3272
|
-
pagination: options.pagination
|
|
3273
|
-
}, (err, result, context) => {
|
|
3274
|
-
if (context.source === "cache" && context.reset) {
|
|
3275
|
-
hasFirstReply.current = false;
|
|
3276
|
-
hasNetworkReply.current = false;
|
|
3277
|
-
lastDataJsonRef.current = "";
|
|
3278
|
-
pageInfoRef.current = void 0;
|
|
3279
|
-
requestedCountRef.current = initialWindowSize ?? 1;
|
|
3280
|
-
pagingRef.current = false;
|
|
3281
|
-
setData(void 0);
|
|
3282
|
-
setPaginatedData(void 0);
|
|
3283
|
-
setPageInfo(void 0);
|
|
3284
|
-
setTotalCount(void 0);
|
|
3285
|
-
setSource(void 0);
|
|
3286
|
-
setError(void 0);
|
|
3287
|
-
setLoading(true);
|
|
3288
|
-
return;
|
|
3289
|
-
}
|
|
3290
|
-
if (context.source === "cache" && hasNetworkReply.current && (!isPaginated || !context.window || context.window.requestedCount <= requestedCountRef.current)) return;
|
|
3291
|
-
if (context.source === "network") {
|
|
3292
|
-
hasNetworkReply.current = true;
|
|
3293
|
-
}
|
|
3294
|
-
setLoading(false);
|
|
3295
|
-
if (err) {
|
|
3296
|
-
setError(err);
|
|
3297
|
-
return;
|
|
3298
|
-
}
|
|
3299
|
-
if (!Array.isArray(result)) return;
|
|
3300
|
-
if (context.source === "network" && context.isLocal && options.skipLocal && hasFirstReply.current) {
|
|
3301
|
-
return;
|
|
3302
|
-
}
|
|
3303
|
-
hasFirstReply.current = true;
|
|
3304
|
-
const nextPageInfo = context.pageInfo;
|
|
3305
|
-
const nextTotalCount = context.totalCount;
|
|
3306
|
-
const nextRequestedCount = context.window?.requestedCount;
|
|
3307
|
-
if (isPaginated && nextRequestedCount !== void 0) {
|
|
3308
|
-
requestedCountRef.current = nextRequestedCount;
|
|
3309
|
-
}
|
|
3310
|
-
const payloadForHash = isPaginated ? {
|
|
3311
|
-
result,
|
|
3312
|
-
pageInfo: nextPageInfo,
|
|
3313
|
-
totalCount: nextTotalCount,
|
|
3314
|
-
requestedCount: nextRequestedCount
|
|
3315
|
-
} : result;
|
|
3316
|
-
let nextJson = "";
|
|
3317
|
-
try {
|
|
3318
|
-
nextJson = JSON.stringify(payloadForHash);
|
|
3319
|
-
} catch {
|
|
3320
|
-
nextJson = "";
|
|
3321
|
-
}
|
|
3322
|
-
if (nextJson && nextJson === lastDataJsonRef.current) {
|
|
3323
|
-
setSource(context.source);
|
|
3324
|
-
return;
|
|
3325
|
-
}
|
|
3326
|
-
lastDataJsonRef.current = nextJson;
|
|
3327
|
-
setSource(context.source);
|
|
3328
|
-
setError(void 0);
|
|
3329
|
-
if (isPaginated) {
|
|
3330
|
-
setPaginatedData(result);
|
|
3331
|
-
setPageInfo(nextPageInfo);
|
|
3332
|
-
setTotalCount(nextTotalCount);
|
|
3333
|
-
return;
|
|
3334
|
-
}
|
|
3335
|
-
setData(result);
|
|
3336
|
-
setTotalCount(void 0);
|
|
3337
|
-
}, {
|
|
3338
|
-
runInitialNetworkQuery,
|
|
3339
|
-
runInitialLocalQuery,
|
|
3340
|
-
forceRefreshOnMount: refreshOnMount,
|
|
3341
|
-
...hasSeedData ? {
|
|
3342
|
-
seedSnapshot: {
|
|
3343
|
-
error: null,
|
|
3344
|
-
data: seedData,
|
|
3345
|
-
context: {
|
|
3346
|
-
source: "cache",
|
|
3347
|
-
...seedPageInfo ? {
|
|
3348
|
-
pageInfo: seedPageInfo
|
|
3349
|
-
} : {},
|
|
3350
|
-
...seedTotalCount !== void 0 ? {
|
|
3351
|
-
totalCount: seedTotalCount
|
|
3352
|
-
} : {},
|
|
3353
|
-
...initialWindowSize ? {
|
|
3354
|
-
window: {
|
|
3355
|
-
requestedCount: initialWindowSize
|
|
3356
|
-
}
|
|
3357
|
-
} : {}
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
} : {}
|
|
3361
|
-
});
|
|
3362
|
-
return () => {
|
|
3363
|
-
unsubscribe?.();
|
|
3364
|
-
};
|
|
3365
|
-
}, [enabled, modelName, queryKey, queryJson, projectionJson, sortJson, limitStr, populateJson, paginationJson, hasSeedData, refreshOnMount, isPaginated, initialWindowSize]);
|
|
3366
|
-
const fetchNext = async () => {
|
|
3367
|
-
if (!enabled || !isPaginated || !options.pagination) return false;
|
|
3368
|
-
if (pagingRef.current || !pageInfoRef.current?.hasNextPage) return false;
|
|
3369
|
-
const pageSize = initialWindowSize ?? normalizeWindowSize(options.pagination.limit);
|
|
3370
|
-
const nextRequestedCount_0 = Math.min(RTS_QUERY_WINDOW_MAX_COUNT, requestedCountRef.current + pageSize);
|
|
3371
|
-
if (nextRequestedCount_0 === requestedCountRef.current) return false;
|
|
3372
|
-
pagingRef.current = true;
|
|
3373
|
-
setLoading(true);
|
|
3374
|
-
setError(void 0);
|
|
3375
|
-
try {
|
|
3376
|
-
return await setQueryWindowSize({
|
|
3377
|
-
modelName,
|
|
3378
|
-
query,
|
|
3379
|
-
options: {
|
|
3380
|
-
key,
|
|
3381
|
-
projection: options.projection,
|
|
3382
|
-
sort: options.sort,
|
|
3383
|
-
limit: options.limit,
|
|
3384
|
-
populate: options.populate,
|
|
3385
|
-
pagination: options.pagination
|
|
3386
|
-
},
|
|
3387
|
-
requestedCount: nextRequestedCount_0
|
|
3388
|
-
});
|
|
3389
|
-
} catch (err_0) {
|
|
3390
|
-
setError(err_0);
|
|
3391
|
-
return false;
|
|
3392
|
-
} finally {
|
|
3393
|
-
pagingRef.current = false;
|
|
3394
|
-
setLoading(false);
|
|
3395
|
-
}
|
|
3396
|
-
};
|
|
3397
|
-
const fetchPrevious = async () => {
|
|
3398
|
-
return false;
|
|
3399
|
-
};
|
|
3400
|
-
const resetPagination = () => {
|
|
3401
|
-
if (!enabled || !isPaginated || !options.pagination || !initialWindowSize) return;
|
|
3402
|
-
if (pagingRef.current || requestedCountRef.current === initialWindowSize) return;
|
|
3403
|
-
pagingRef.current = true;
|
|
3404
|
-
setLoading(true);
|
|
3405
|
-
setError(void 0);
|
|
3406
|
-
void setQueryWindowSize({
|
|
3407
|
-
modelName,
|
|
3408
|
-
query,
|
|
3409
|
-
options: {
|
|
3410
|
-
key,
|
|
3411
|
-
projection: options.projection,
|
|
3412
|
-
sort: options.sort,
|
|
3413
|
-
limit: options.limit,
|
|
3414
|
-
populate: options.populate,
|
|
3415
|
-
pagination: options.pagination
|
|
3416
|
-
},
|
|
3417
|
-
requestedCount: initialWindowSize
|
|
3418
|
-
}).finally(() => {
|
|
3419
|
-
pagingRef.current = false;
|
|
3420
|
-
setLoading(false);
|
|
3421
|
-
});
|
|
3422
|
-
};
|
|
3423
|
-
return {
|
|
3424
|
-
data: isPaginated ? paginatedData : data,
|
|
3425
|
-
pageInfo: isPaginated ? pageInfo : void 0,
|
|
3426
|
-
totalCount: isPaginated ? totalCount : void 0,
|
|
3427
|
-
source,
|
|
3428
|
-
error,
|
|
3429
|
-
loading,
|
|
3430
|
-
fetchNext,
|
|
3431
|
-
fetchPrevious,
|
|
3432
|
-
resetPagination
|
|
3433
|
-
};
|
|
3434
|
-
};
|
|
3435
|
-
export {
|
|
3436
|
-
registerQuery as A,
|
|
3437
|
-
resetRtsPouchStore as B,
|
|
3438
|
-
runNetworkCount as C,
|
|
3439
|
-
runNetworkQuery as D,
|
|
3440
|
-
runQuery as E,
|
|
3441
|
-
sendMessage as F,
|
|
3442
|
-
sendRtsMessage as G,
|
|
3443
|
-
syncRtsChanges as H,
|
|
3444
|
-
updateDocs as I,
|
|
3445
|
-
RtsSsrRuntimeProvider as R,
|
|
3446
|
-
STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY as S,
|
|
3447
|
-
consumeHydratedRtsCount as a,
|
|
3448
|
-
peekHydratedRtsQueryData as b,
|
|
3449
|
-
clearHydratedRtsQueryData as c,
|
|
3450
|
-
peekHydratedRtsQueryPageInfo as d,
|
|
3451
|
-
peekHydratedRtsQueryTotalCount as e,
|
|
3452
|
-
computeRtsQueryKey as f,
|
|
3453
|
-
useRtsSsrRuntime as g,
|
|
3454
|
-
hydrateRtsFromWindow as h,
|
|
3455
|
-
reconnect as i,
|
|
3456
|
-
disconnect as j,
|
|
3457
|
-
addLocalTxn as k,
|
|
3458
|
-
configureRtsPouchStore as l,
|
|
3459
|
-
connect as m,
|
|
3460
|
-
deleteDocs as n,
|
|
3461
|
-
destroyAllCollections as o,
|
|
3462
|
-
peekHydratedRtsCount as p,
|
|
3463
|
-
destroyCollection as q,
|
|
3464
|
-
registerCount as r,
|
|
3465
|
-
serializeRtsQueryValue as s,
|
|
3466
|
-
getCollection as t,
|
|
3467
|
-
useQuery as u,
|
|
3468
|
-
getConnectionError as v,
|
|
3469
|
-
getConnectionStatus as w,
|
|
3470
|
-
onConnectionStatusChange as x,
|
|
3471
|
-
onMessage as y,
|
|
3472
|
-
onRtsMessage as z
|
|
3473
|
-
};
|
|
3474
|
-
//# sourceMappingURL=useQuery-DZqYIJog.js.map
|