pbtsdb 0.10.2 → 2.0.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/README.md +103 -57
- package/dist/chunk-4CRCARB3.js +2954 -0
- package/dist/chunk-4CRCARB3.js.map +1 -0
- package/dist/core.d.ts +184 -86
- package/dist/core.js +1 -1
- package/dist/index.d.ts +6 -10
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/llms.txt +29 -25
- package/package.json +6 -11
- package/dist/chunk-X7ZGPVG2.js +0 -1543
- package/dist/chunk-X7ZGPVG2.js.map +0 -1
package/dist/chunk-X7ZGPVG2.js
DELETED
|
@@ -1,1543 +0,0 @@
|
|
|
1
|
-
import { BTreeIndex, createCollection as createCollection$1, parseWhereExpression, parseOrderByExpression, IR } from '@tanstack/db';
|
|
2
|
-
export { BTreeIndex, BasicIndex, ReverseIndex, createEffect, materialize, toArray } from '@tanstack/db';
|
|
3
|
-
import { queryCollectionOptions, DeleteOperationItemNotFoundError } from '@tanstack/query-db-collection';
|
|
4
|
-
|
|
5
|
-
// src/core.ts
|
|
6
|
-
|
|
7
|
-
// src/expand-paths.ts
|
|
8
|
-
function normalizePaths(paths) {
|
|
9
|
-
const set = /* @__PURE__ */ new Set();
|
|
10
|
-
for (const path of paths) {
|
|
11
|
-
const trimmed = path.trim();
|
|
12
|
-
if (trimmed) set.add(trimmed);
|
|
13
|
-
}
|
|
14
|
-
return [...set].sort();
|
|
15
|
-
}
|
|
16
|
-
function splitPaths(csv) {
|
|
17
|
-
return csv ? normalizePaths(csv.split(",")) : [];
|
|
18
|
-
}
|
|
19
|
-
function joinPaths(paths) {
|
|
20
|
-
const normalized = normalizePaths(paths);
|
|
21
|
-
return normalized.length > 0 ? normalized.join(",") : void 0;
|
|
22
|
-
}
|
|
23
|
-
var BACK_RELATION_EXPAND_CAP = 1e3;
|
|
24
|
-
function parseViaKey(key) {
|
|
25
|
-
const index = key.indexOf("_via_");
|
|
26
|
-
if (index < 0) return void 0;
|
|
27
|
-
const field = key.slice(index + "_via_".length);
|
|
28
|
-
return field ? { field } : void 0;
|
|
29
|
-
}
|
|
30
|
-
function markFiledSubset(target, key, values, parentId) {
|
|
31
|
-
const via = parseViaKey(key);
|
|
32
|
-
if (!via || values.length >= BACK_RELATION_EXPAND_CAP) return;
|
|
33
|
-
target.markSubsetLoaded(via.field, parentId);
|
|
34
|
-
}
|
|
35
|
-
function validateExpandPath(collectionName, targets, path) {
|
|
36
|
-
if (!targets) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
`Cannot expand "${path}" on collection "${collectionName}": no relations declared`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
let current = targets;
|
|
42
|
-
for (const segment of path.split(".")) {
|
|
43
|
-
const next = current?.[segment];
|
|
44
|
-
if (!next) {
|
|
45
|
-
throw new Error(
|
|
46
|
-
`Cannot expand "${path}" on collection "${collectionName}": segment "${segment}" is not a declared relation`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
current = next.relationTargets;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/logger.ts
|
|
54
|
-
var defaultLogger = {
|
|
55
|
-
debug: (msg, context) => {
|
|
56
|
-
if (process.env.NODE_ENV === "development") {
|
|
57
|
-
console.debug(`[pbtsdb] ${msg}`, context || "");
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
info: (msg, context) => {
|
|
61
|
-
console.info(`[pbtsdb] ${msg}`, context || "");
|
|
62
|
-
},
|
|
63
|
-
warn: (msg, context) => {
|
|
64
|
-
console.warn(`[pbtsdb] ${msg}`, context || "");
|
|
65
|
-
},
|
|
66
|
-
error: (msg, context) => {
|
|
67
|
-
console.error(`[pbtsdb] ${msg}`, context || "");
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
var currentLogger = defaultLogger;
|
|
71
|
-
var logger = {
|
|
72
|
-
debug: (msg, context) => currentLogger.debug(msg, context),
|
|
73
|
-
info: (msg, context) => currentLogger.info(msg, context),
|
|
74
|
-
warn: (msg, context) => currentLogger.warn(msg, context),
|
|
75
|
-
error: (msg, context) => currentLogger.error(msg, context)
|
|
76
|
-
};
|
|
77
|
-
function setLogger(customLogger) {
|
|
78
|
-
currentLogger = customLogger;
|
|
79
|
-
}
|
|
80
|
-
function resetLogger() {
|
|
81
|
-
currentLogger = defaultLogger;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/records.ts
|
|
85
|
-
function idOf(value) {
|
|
86
|
-
if (!value || typeof value !== "object" || !("id" in value)) return void 0;
|
|
87
|
-
return typeof value.id === "string" ? value.id : void 0;
|
|
88
|
-
}
|
|
89
|
-
function updatedAtOf(value) {
|
|
90
|
-
if (!value || typeof value !== "object" || !("updated" in value)) return void 0;
|
|
91
|
-
return typeof value.updated === "string" && value.updated !== "" ? value.updated : void 0;
|
|
92
|
-
}
|
|
93
|
-
function expandOf(value) {
|
|
94
|
-
if (!value || typeof value !== "object" || !("expand" in value)) return void 0;
|
|
95
|
-
const { expand } = value;
|
|
96
|
-
return expand && typeof expand === "object" ? expand : void 0;
|
|
97
|
-
}
|
|
98
|
-
function isObject(value) {
|
|
99
|
-
return typeof value === "object" && value !== null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// src/expand-filing.ts
|
|
103
|
-
function backRelationHeads(expandString, targets) {
|
|
104
|
-
if (!targets) return [];
|
|
105
|
-
const heads = [];
|
|
106
|
-
for (const key of splitPaths(expandString).map((path) => path.split(".")[0])) {
|
|
107
|
-
const target = targets[key];
|
|
108
|
-
const via = target && parseViaKey(key);
|
|
109
|
-
if (target && via) heads.push({ target, key, field: via.field });
|
|
110
|
-
}
|
|
111
|
-
return heads;
|
|
112
|
-
}
|
|
113
|
-
function targetsAlong(paths, targets) {
|
|
114
|
-
const found = /* @__PURE__ */ new Set();
|
|
115
|
-
for (const path of paths) {
|
|
116
|
-
let current = targets;
|
|
117
|
-
for (const segment of path.split(".")) {
|
|
118
|
-
const target = current?.[segment];
|
|
119
|
-
if (!target) break;
|
|
120
|
-
found.add(target);
|
|
121
|
-
current = target.relationTargets;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return found;
|
|
125
|
-
}
|
|
126
|
-
function withoutExpand(values) {
|
|
127
|
-
return values.map((value) => {
|
|
128
|
-
const { expand: _expand, ...plain } = value;
|
|
129
|
-
return plain;
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
function stripFetchedRelations(items, expandString) {
|
|
133
|
-
const heads = new Set(splitPaths(expandString).map((path) => path.split(".")[0]));
|
|
134
|
-
if (heads.size === 0) return items;
|
|
135
|
-
return items.map((item) => {
|
|
136
|
-
const { expand, ...plain } = item;
|
|
137
|
-
if (!expand) return item;
|
|
138
|
-
const kept = Object.fromEntries(Object.entries(expand).filter(([key]) => !heads.has(key)));
|
|
139
|
-
return Object.keys(kept).length > 0 ? { ...plain, expand: kept } : plain;
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
function groupExpandedByKey(records) {
|
|
143
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
144
|
-
for (const record of records) {
|
|
145
|
-
const expand = expandOf(record);
|
|
146
|
-
if (!expand) continue;
|
|
147
|
-
const parentId = idOf(record);
|
|
148
|
-
for (const [key, value] of Object.entries(expand)) {
|
|
149
|
-
const values = (Array.isArray(value) ? value : [value]).filter(isObject);
|
|
150
|
-
const group = byKey.get(key) ?? { values: [], byParent: [] };
|
|
151
|
-
group.values.push(...values);
|
|
152
|
-
if (parentId) group.byParent.push([parentId, values]);
|
|
153
|
-
byKey.set(key, group);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return byKey;
|
|
157
|
-
}
|
|
158
|
-
function lastById(values) {
|
|
159
|
-
const byId = /* @__PURE__ */ new Map();
|
|
160
|
-
for (const value of values) byId.set(idOf(value), value);
|
|
161
|
-
return [...byId.values()];
|
|
162
|
-
}
|
|
163
|
-
function createExpandFiler(collectionName, recordFiled) {
|
|
164
|
-
function recordFiledGroup(target, key, group, values) {
|
|
165
|
-
const via = parseViaKey(key);
|
|
166
|
-
if (via) {
|
|
167
|
-
recordFiled(
|
|
168
|
-
target,
|
|
169
|
-
via.field,
|
|
170
|
-
group.byParent.map(([parentId]) => parentId)
|
|
171
|
-
);
|
|
172
|
-
} else {
|
|
173
|
-
recordFiled(
|
|
174
|
-
target,
|
|
175
|
-
"id",
|
|
176
|
-
values.flatMap((value) => idOf(value) ?? [])
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
async function upsertExpanded(records, targets) {
|
|
181
|
-
if (!targets) return;
|
|
182
|
-
for (const [key, group] of groupExpandedByKey(records)) {
|
|
183
|
-
const target = targets[key];
|
|
184
|
-
if (!target) {
|
|
185
|
-
logger.debug("No relation target for expanded field", { collectionName, key });
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
const values = lastById(group.values);
|
|
189
|
-
const filed = await target.writeFiled(values);
|
|
190
|
-
await upsertExpanded(values, target.relationTargets);
|
|
191
|
-
if (!filed) continue;
|
|
192
|
-
recordFiledGroup(target, key, group, values);
|
|
193
|
-
for (const [parentId, parentValues] of group.byParent) {
|
|
194
|
-
markFiledSubset(target, key, parentValues, parentId);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return {
|
|
199
|
-
upsertExpanded,
|
|
200
|
-
markEmptyBackRelations(items, heads) {
|
|
201
|
-
if (heads.length === 0) return;
|
|
202
|
-
for (const item of items) {
|
|
203
|
-
const id = idOf(item);
|
|
204
|
-
if (!id) continue;
|
|
205
|
-
const expand = expandOf(item);
|
|
206
|
-
for (const { target, key, field } of heads) {
|
|
207
|
-
if (expand?.[key] !== void 0) continue;
|
|
208
|
-
target.markSubsetLoaded(field, id);
|
|
209
|
-
recordFiled(target, field, [id]);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// src/keyed-where.ts
|
|
217
|
-
function fieldRef(node) {
|
|
218
|
-
return node?.type === "ref" && node.path?.length === 1 ? node.path[0] : void 0;
|
|
219
|
-
}
|
|
220
|
-
function stringValue(node) {
|
|
221
|
-
return node?.type === "val" && typeof node.value === "string" ? node.value : void 0;
|
|
222
|
-
}
|
|
223
|
-
function collectEq(a, b) {
|
|
224
|
-
const field = fieldRef(a) ?? fieldRef(b);
|
|
225
|
-
if (field === void 0) return void 0;
|
|
226
|
-
const value = stringValue(fieldRef(a) !== void 0 ? b : a);
|
|
227
|
-
return value === void 0 ? void 0 : { field, values: [value] };
|
|
228
|
-
}
|
|
229
|
-
function collectIn(a, b) {
|
|
230
|
-
const field = fieldRef(a);
|
|
231
|
-
if (field === void 0 || b?.type !== "val" || !Array.isArray(b.value)) return void 0;
|
|
232
|
-
return b.value.every((v) => typeof v === "string") ? { field, values: b.value } : void 0;
|
|
233
|
-
}
|
|
234
|
-
function collectOr(args) {
|
|
235
|
-
let field;
|
|
236
|
-
const values = [];
|
|
237
|
-
for (const arg of args) {
|
|
238
|
-
const sub = collect(arg);
|
|
239
|
-
if (!sub || field !== void 0 && sub.field !== field) return void 0;
|
|
240
|
-
field = sub.field;
|
|
241
|
-
values.push(...sub.values);
|
|
242
|
-
}
|
|
243
|
-
return field === void 0 ? void 0 : { field, values };
|
|
244
|
-
}
|
|
245
|
-
function collect(node) {
|
|
246
|
-
if (node.type !== "func" || !node.args) return void 0;
|
|
247
|
-
const [a, b] = node.args;
|
|
248
|
-
switch (node.name) {
|
|
249
|
-
case "eq":
|
|
250
|
-
return collectEq(a, b);
|
|
251
|
-
case "in":
|
|
252
|
-
return collectIn(a, b);
|
|
253
|
-
case "or":
|
|
254
|
-
return collectOr(node.args);
|
|
255
|
-
default:
|
|
256
|
-
return void 0;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function subsetFromWhere(where) {
|
|
260
|
-
if (!where) return void 0;
|
|
261
|
-
const subset = collect(where);
|
|
262
|
-
return subset ? { field: subset.field, values: [...new Set(subset.values)].sort() } : void 0;
|
|
263
|
-
}
|
|
264
|
-
function matchesSubset(row, field, wanted) {
|
|
265
|
-
const value = row[field];
|
|
266
|
-
if (typeof value === "string") return wanted.has(value);
|
|
267
|
-
if (Array.isArray(value))
|
|
268
|
-
return value.some((item) => typeof item === "string" && wanted.has(item));
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
function escapeValue(value) {
|
|
272
|
-
if (value === null) {
|
|
273
|
-
return "null";
|
|
274
|
-
}
|
|
275
|
-
if (typeof value === "boolean") {
|
|
276
|
-
return value ? "true" : "false";
|
|
277
|
-
}
|
|
278
|
-
if (typeof value === "number") {
|
|
279
|
-
return value.toString();
|
|
280
|
-
}
|
|
281
|
-
if (typeof value === "string") {
|
|
282
|
-
return `"${value.replace(/"/g, '\\"')}"`;
|
|
283
|
-
}
|
|
284
|
-
if (value instanceof Date) {
|
|
285
|
-
return `"${value.toISOString()}"`;
|
|
286
|
-
}
|
|
287
|
-
if (Array.isArray(value)) {
|
|
288
|
-
return `[${value.map(escapeValue).join(",")}]`;
|
|
289
|
-
}
|
|
290
|
-
return `"${String(value)}"`;
|
|
291
|
-
}
|
|
292
|
-
function fieldPathToString(path) {
|
|
293
|
-
return path.join(".");
|
|
294
|
-
}
|
|
295
|
-
var NEVER_MATCH = "1 = 2";
|
|
296
|
-
var ALWAYS_MATCH = "1 = 1";
|
|
297
|
-
function membership(field, values, operator, joiner, empty) {
|
|
298
|
-
const valueArray = Array.isArray(values) ? values : [values];
|
|
299
|
-
const fieldStr = fieldPathToString(field);
|
|
300
|
-
const conditions = [...new Set(valueArray)].map(
|
|
301
|
-
(v) => `${fieldStr} ${operator} ${escapeValue(v)}`
|
|
302
|
-
);
|
|
303
|
-
if (conditions.length === 0) return empty;
|
|
304
|
-
return conditions.length > 1 ? `(${conditions.join(` ${joiner} `)})` : conditions[0];
|
|
305
|
-
}
|
|
306
|
-
var NEGATED_OPERATORS = {
|
|
307
|
-
eq: "neq",
|
|
308
|
-
neq: "eq",
|
|
309
|
-
gt: "lte",
|
|
310
|
-
gte: "lt",
|
|
311
|
-
lt: "gte",
|
|
312
|
-
lte: "gt",
|
|
313
|
-
in: "notIn",
|
|
314
|
-
notIn: "in",
|
|
315
|
-
like: "notLike",
|
|
316
|
-
notLike: "like",
|
|
317
|
-
isNull: "isNotNull",
|
|
318
|
-
isUndefined: "isNotNull",
|
|
319
|
-
isNotNull: "isNull"
|
|
320
|
-
};
|
|
321
|
-
function negate(expr) {
|
|
322
|
-
if (expr.type !== "func") {
|
|
323
|
-
throw new Error(
|
|
324
|
-
`Unsupported operand '${expr.type}' inside not() for PocketBase filter conversion`
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
if (expr.name === "not") {
|
|
328
|
-
return pushDownNot(expr.args[0]);
|
|
329
|
-
}
|
|
330
|
-
if (expr.name === "and" || expr.name === "or") {
|
|
331
|
-
return new IR.Func(expr.name === "and" ? "or" : "and", expr.args.map(negate));
|
|
332
|
-
}
|
|
333
|
-
const negated = NEGATED_OPERATORS[expr.name];
|
|
334
|
-
if (!negated) {
|
|
335
|
-
throw new Error(`Unsupported operator 'not(${expr.name})' for PocketBase filter conversion`);
|
|
336
|
-
}
|
|
337
|
-
return new IR.Func(negated, expr.args);
|
|
338
|
-
}
|
|
339
|
-
function pushDownNot(expr) {
|
|
340
|
-
if (expr.type !== "func") {
|
|
341
|
-
return expr;
|
|
342
|
-
}
|
|
343
|
-
if (expr.name === "not") {
|
|
344
|
-
return negate(expr.args[0]);
|
|
345
|
-
}
|
|
346
|
-
if (expr.name === "and" || expr.name === "or") {
|
|
347
|
-
return new IR.Func(expr.name, expr.args.map(pushDownNot));
|
|
348
|
-
}
|
|
349
|
-
return expr;
|
|
350
|
-
}
|
|
351
|
-
function convertToPocketBaseFilter(where) {
|
|
352
|
-
if (!where) {
|
|
353
|
-
return void 0;
|
|
354
|
-
}
|
|
355
|
-
const result = parseWhereExpression(pushDownNot(where), {
|
|
356
|
-
handlers: {
|
|
357
|
-
eq: (field, value) => {
|
|
358
|
-
return `${fieldPathToString(field)} = ${escapeValue(value)}`;
|
|
359
|
-
},
|
|
360
|
-
gt: (field, value) => {
|
|
361
|
-
return `${fieldPathToString(field)} > ${escapeValue(value)}`;
|
|
362
|
-
},
|
|
363
|
-
gte: (field, value) => {
|
|
364
|
-
return `${fieldPathToString(field)} >= ${escapeValue(value)}`;
|
|
365
|
-
},
|
|
366
|
-
lt: (field, value) => {
|
|
367
|
-
return `${fieldPathToString(field)} < ${escapeValue(value)}`;
|
|
368
|
-
},
|
|
369
|
-
lte: (field, value) => {
|
|
370
|
-
return `${fieldPathToString(field)} <= ${escapeValue(value)}`;
|
|
371
|
-
},
|
|
372
|
-
and: (...conditions) => {
|
|
373
|
-
if (conditions.length === 0) return "";
|
|
374
|
-
if (conditions.length === 1) return conditions[0];
|
|
375
|
-
return `(${conditions.join(" && ")})`;
|
|
376
|
-
},
|
|
377
|
-
or: (...conditions) => {
|
|
378
|
-
if (conditions.length === 0) return "";
|
|
379
|
-
if (conditions.length === 1) return conditions[0];
|
|
380
|
-
return `(${conditions.join(" || ")})`;
|
|
381
|
-
},
|
|
382
|
-
neq: (field, value) => {
|
|
383
|
-
return `${fieldPathToString(field)} != ${escapeValue(value)}`;
|
|
384
|
-
},
|
|
385
|
-
in: (field, values) => membership(field, values, "=", "||", NEVER_MATCH),
|
|
386
|
-
notIn: (field, values) => membership(field, values, "!=", "&&", ALWAYS_MATCH),
|
|
387
|
-
notLike: (field, value) => {
|
|
388
|
-
return `${fieldPathToString(field)} !~ ${escapeValue(value)}`;
|
|
389
|
-
},
|
|
390
|
-
like: (field, value) => {
|
|
391
|
-
return `${fieldPathToString(field)} ~ ${escapeValue(value)}`;
|
|
392
|
-
},
|
|
393
|
-
isNull: (field) => {
|
|
394
|
-
return `${fieldPathToString(field)} = null`;
|
|
395
|
-
},
|
|
396
|
-
isUndefined: (field) => {
|
|
397
|
-
return `${fieldPathToString(field)} = null`;
|
|
398
|
-
},
|
|
399
|
-
isNotNull: (field) => {
|
|
400
|
-
return `${fieldPathToString(field)} != null`;
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
onUnknownOperator: (operator, _args) => {
|
|
404
|
-
throw new Error(
|
|
405
|
-
`Unsupported operator '${operator}' for PocketBase filter conversion. Supported operators: eq, gt, gte, lt, lte, in, like, and, or, not, isNull, isUndefined`
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
return result || void 0;
|
|
410
|
-
}
|
|
411
|
-
function convertToPocketBaseSort(orderBy) {
|
|
412
|
-
if (!orderBy) {
|
|
413
|
-
return void 0;
|
|
414
|
-
}
|
|
415
|
-
const sorts = parseOrderByExpression(orderBy);
|
|
416
|
-
if (sorts.length === 0) {
|
|
417
|
-
return void 0;
|
|
418
|
-
}
|
|
419
|
-
return sorts.map((sort) => {
|
|
420
|
-
const field = fieldPathToString(sort.field);
|
|
421
|
-
return sort.direction === "desc" ? `-${field}` : field;
|
|
422
|
-
}).join(",");
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/pocketbase-limits.ts
|
|
426
|
-
var MAX_FILTER_LENGTH = 2500;
|
|
427
|
-
var REALTIME_MAX_FILTER_LENGTH = 1e3;
|
|
428
|
-
var REALTIME_TOPIC_MAX_LENGTH = 2500;
|
|
429
|
-
var SDK_RESERVED_OPTIONS = /* @__PURE__ */ new Set([
|
|
430
|
-
"requestKey",
|
|
431
|
-
"$cancelKey",
|
|
432
|
-
"$autoCancel",
|
|
433
|
-
"fetch",
|
|
434
|
-
"headers",
|
|
435
|
-
"body",
|
|
436
|
-
"query",
|
|
437
|
-
"params",
|
|
438
|
-
"cache",
|
|
439
|
-
"credentials",
|
|
440
|
-
"integrity",
|
|
441
|
-
"keepalive",
|
|
442
|
-
"method",
|
|
443
|
-
"mode",
|
|
444
|
-
"redirect",
|
|
445
|
-
"referrer",
|
|
446
|
-
"referrerPolicy",
|
|
447
|
-
"signal",
|
|
448
|
-
"window"
|
|
449
|
-
]);
|
|
450
|
-
function realtimeTopicLength(collectionName, options) {
|
|
451
|
-
const topic = `${collectionName}/*`;
|
|
452
|
-
if (!options) return topic.length;
|
|
453
|
-
const query = { ...options.query };
|
|
454
|
-
for (const [key, value] of Object.entries(options)) {
|
|
455
|
-
if (!SDK_RESERVED_OPTIONS.has(key)) query[key] = value;
|
|
456
|
-
}
|
|
457
|
-
const encoded = encodeURIComponent(JSON.stringify({ query, headers: options.headers }));
|
|
458
|
-
return `${topic}?options=${encoded}`.length;
|
|
459
|
-
}
|
|
460
|
-
function subsetFilters({ field, values }, maxLength = MAX_FILTER_LENGTH) {
|
|
461
|
-
const filters = [];
|
|
462
|
-
let current = "";
|
|
463
|
-
for (const value of values) {
|
|
464
|
-
const clause = `${field} = ${escapeValue(value)}`;
|
|
465
|
-
const joined = current ? `${current} || ${clause}` : clause;
|
|
466
|
-
if (current && joined.length > maxLength) {
|
|
467
|
-
filters.push(current);
|
|
468
|
-
current = clause;
|
|
469
|
-
} else {
|
|
470
|
-
current = joined;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
if (current) filters.push(current);
|
|
474
|
-
return filters;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// src/fetch-records.ts
|
|
478
|
-
function createFetcher(deps) {
|
|
479
|
-
const { pb, collectionName, queryClient, relationTargets, guard, filer, subsets } = deps;
|
|
480
|
-
const pendingFilings = /* @__PURE__ */ new Map();
|
|
481
|
-
function expectFiling(field, settles) {
|
|
482
|
-
let pending = pendingFilings.get(field);
|
|
483
|
-
if (!pending) {
|
|
484
|
-
pending = [];
|
|
485
|
-
pendingFilings.set(field, pending);
|
|
486
|
-
}
|
|
487
|
-
pending.push(settles);
|
|
488
|
-
return () => {
|
|
489
|
-
const index = pending.indexOf(settles);
|
|
490
|
-
if (index !== -1) pending.splice(index, 1);
|
|
491
|
-
if (pending.length === 0) pendingFilings.delete(field);
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
async function awaitPendingFiling(field) {
|
|
495
|
-
const pending = pendingFilings.get(field);
|
|
496
|
-
if (pending && pending.length > 0) await Promise.all(pending);
|
|
497
|
-
}
|
|
498
|
-
function rowsFromStore(ids) {
|
|
499
|
-
const rows = [];
|
|
500
|
-
for (const id of ids) {
|
|
501
|
-
const row = deps.syncedRow(id);
|
|
502
|
-
if (!row) return void 0;
|
|
503
|
-
rows.push(row);
|
|
504
|
-
}
|
|
505
|
-
return rows;
|
|
506
|
-
}
|
|
507
|
-
function fieldSubsetFromStore(subset) {
|
|
508
|
-
if (!subsets.isLoaded(subset)) return void 0;
|
|
509
|
-
const wanted = new Set(subset.values);
|
|
510
|
-
return [...deps.syncedRows()].filter((row) => matchesSubset(row, subset.field, wanted));
|
|
511
|
-
}
|
|
512
|
-
function servedFromStore(request) {
|
|
513
|
-
const { subset, sort, limit } = request;
|
|
514
|
-
if (!subset) return void 0;
|
|
515
|
-
if (subset.values.length === 0) return [];
|
|
516
|
-
if (request.expand || sort && limit) return void 0;
|
|
517
|
-
const rows = subset.field === "id" ? rowsFromStore(subset.values) : fieldSubsetFromStore(subset);
|
|
518
|
-
return rows && limit ? rows.slice(0, limit) : rows;
|
|
519
|
-
}
|
|
520
|
-
async function fetchPage(request, expand, filter, index) {
|
|
521
|
-
const { sort, limit } = request;
|
|
522
|
-
const requestKey = `${collectionName}:${index}`;
|
|
523
|
-
if (limit) {
|
|
524
|
-
const result = await pb.collection(collectionName).getList(1, limit, { filter, sort, skipTotal: true, expand, requestKey });
|
|
525
|
-
return result.items;
|
|
526
|
-
}
|
|
527
|
-
const items = await pb.collection(collectionName).getFullList({ filter, sort, expand, requestKey });
|
|
528
|
-
return items;
|
|
529
|
-
}
|
|
530
|
-
async function fetchItems(request, heads) {
|
|
531
|
-
const served = servedFromStore(request);
|
|
532
|
-
if (served) return served;
|
|
533
|
-
if (request.subset && request.subset.field !== "id" && heads.length === 0) {
|
|
534
|
-
await awaitPendingFiling(request.subset.field);
|
|
535
|
-
const servedAfterFiling = servedFromStore(request);
|
|
536
|
-
if (servedAfterFiling) return servedAfterFiling;
|
|
537
|
-
}
|
|
538
|
-
const filters = request.subset ? subsetFilters(request.subset) : [request.filter];
|
|
539
|
-
const expand = deps.activeExpand(request);
|
|
540
|
-
const pages = await Promise.all(
|
|
541
|
-
filters.map((filter, index) => fetchPage(request, expand, filter, index))
|
|
542
|
-
);
|
|
543
|
-
const items = pages.flat();
|
|
544
|
-
filer.markEmptyBackRelations(items, heads);
|
|
545
|
-
return items;
|
|
546
|
-
}
|
|
547
|
-
function isAutoCancelled(error) {
|
|
548
|
-
return deps.ignoreAutoCancellation && error instanceof Error && error.message.includes("autocancelled");
|
|
549
|
-
}
|
|
550
|
-
async function fetchRecords(request, queryKey) {
|
|
551
|
-
const tracked = guard.trackFetch();
|
|
552
|
-
let settleFiling = () => void 0;
|
|
553
|
-
const filed = new Promise((resolve) => {
|
|
554
|
-
settleFiling = resolve;
|
|
555
|
-
});
|
|
556
|
-
const heads = backRelationHeads(deps.activeExpand(request), relationTargets);
|
|
557
|
-
const unregister = heads.map(({ target, field }) => target.expectFiling(field, filed));
|
|
558
|
-
try {
|
|
559
|
-
let items;
|
|
560
|
-
try {
|
|
561
|
-
items = await fetchItems(request, heads);
|
|
562
|
-
} catch (error) {
|
|
563
|
-
if (!isAutoCancelled(error)) throw error;
|
|
564
|
-
return guard.withRowsConfirmedMidFlight(
|
|
565
|
-
queryClient.getQueryData(queryKey) ?? [],
|
|
566
|
-
tracked.confirmed
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
await filer.upsertExpanded(items, relationTargets);
|
|
570
|
-
return guard.withRowsConfirmedMidFlight(
|
|
571
|
-
stripFetchedRelations(items, deps.activeExpand(request)),
|
|
572
|
-
tracked.confirmed
|
|
573
|
-
);
|
|
574
|
-
} finally {
|
|
575
|
-
tracked.done();
|
|
576
|
-
settleFiling();
|
|
577
|
-
for (const fn of unregister) fn();
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return { fetchRecords, expectFiling };
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// src/held-targets.ts
|
|
584
|
-
function createHeldTargets(collectionName) {
|
|
585
|
-
const held = /* @__PURE__ */ new Map();
|
|
586
|
-
const filedByTarget = /* @__PURE__ */ new Map();
|
|
587
|
-
function filtersFor(target) {
|
|
588
|
-
const byField = filedByTarget.get(target);
|
|
589
|
-
if (!byField) return [];
|
|
590
|
-
const filters = [];
|
|
591
|
-
for (const [field, values] of byField) {
|
|
592
|
-
filters.push(
|
|
593
|
-
...subsetFilters({ field, values: [...values] }, REALTIME_MAX_FILTER_LENGTH)
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
return filters;
|
|
597
|
-
}
|
|
598
|
-
function release(target) {
|
|
599
|
-
const hold2 = held.get(target);
|
|
600
|
-
if (!hold2) return;
|
|
601
|
-
held.delete(target);
|
|
602
|
-
try {
|
|
603
|
-
hold2.release();
|
|
604
|
-
} catch (error) {
|
|
605
|
-
logger.error("Failed to release relation target subscription", {
|
|
606
|
-
collectionName,
|
|
607
|
-
error
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
function hold(target) {
|
|
612
|
-
if (held.has(target)) return;
|
|
613
|
-
try {
|
|
614
|
-
const hold2 = target.holdLive();
|
|
615
|
-
held.set(target, hold2);
|
|
616
|
-
hold2.setFilters(filtersFor(target));
|
|
617
|
-
} catch (error) {
|
|
618
|
-
logger.error("Failed to hold relation target subscription", { collectionName, error });
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
function sync(desired) {
|
|
622
|
-
for (const target of [...held.keys()]) if (!desired.has(target)) release(target);
|
|
623
|
-
for (const target of desired) hold(target);
|
|
624
|
-
}
|
|
625
|
-
return {
|
|
626
|
-
recordFiled(target, field, values) {
|
|
627
|
-
let byField = filedByTarget.get(target);
|
|
628
|
-
if (!byField) {
|
|
629
|
-
byField = /* @__PURE__ */ new Map();
|
|
630
|
-
filedByTarget.set(target, byField);
|
|
631
|
-
}
|
|
632
|
-
let recorded = byField.get(field);
|
|
633
|
-
if (!recorded) {
|
|
634
|
-
recorded = /* @__PURE__ */ new Set();
|
|
635
|
-
byField.set(field, recorded);
|
|
636
|
-
}
|
|
637
|
-
let grew = false;
|
|
638
|
-
for (const value of values) {
|
|
639
|
-
if (recorded.has(value)) continue;
|
|
640
|
-
recorded.add(value);
|
|
641
|
-
grew = true;
|
|
642
|
-
}
|
|
643
|
-
if (grew) held.get(target)?.setFilters(filtersFor(target));
|
|
644
|
-
},
|
|
645
|
-
sync,
|
|
646
|
-
releaseAll: () => sync(/* @__PURE__ */ new Set()),
|
|
647
|
-
clearFiled: () => filedByTarget.clear(),
|
|
648
|
-
count: () => held.size
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// src/request.ts
|
|
653
|
-
function toRequest(opts) {
|
|
654
|
-
const request = {};
|
|
655
|
-
const subset = subsetFromWhere(opts?.where);
|
|
656
|
-
const filter = subset ? void 0 : convertToPocketBaseFilter(opts?.where);
|
|
657
|
-
const sort = convertToPocketBaseSort(opts?.orderBy);
|
|
658
|
-
const expand = joinPaths(opts?.expand ?? []);
|
|
659
|
-
if (subset) request.subset = subset;
|
|
660
|
-
if (filter) request.filter = filter;
|
|
661
|
-
if (sort) request.sort = sort;
|
|
662
|
-
if (opts?.limit) request.limit = opts.limit;
|
|
663
|
-
if (expand) request.expand = expand;
|
|
664
|
-
return request;
|
|
665
|
-
}
|
|
666
|
-
function queryKeyFor(collectionName, opts) {
|
|
667
|
-
const request = toRequest(opts);
|
|
668
|
-
return Object.keys(request).length === 0 ? [collectionName] : [collectionName, request];
|
|
669
|
-
}
|
|
670
|
-
function requestFromQueryKey(queryKey) {
|
|
671
|
-
return queryKey[1];
|
|
672
|
-
}
|
|
673
|
-
function realtimeFiltersFor(request) {
|
|
674
|
-
if (request.subset) return subsetFilters(request.subset, REALTIME_MAX_FILTER_LENGTH);
|
|
675
|
-
return request.filter ? [request.filter] : void 0;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// src/loaded-subsets.ts
|
|
679
|
-
function createLoadedSubsets(collectionName, queryClient) {
|
|
680
|
-
const marks = /* @__PURE__ */ new Map();
|
|
681
|
-
function forgetValue(marked, value) {
|
|
682
|
-
if (typeof value === "string") return marked.delete(value) ? [value] : [];
|
|
683
|
-
if (!Array.isArray(value)) return [];
|
|
684
|
-
return value.filter((item) => typeof item === "string" && marked.delete(item));
|
|
685
|
-
}
|
|
686
|
-
function matchesMarked(query, isMarked) {
|
|
687
|
-
if (query.queryKey[0] !== collectionName) return false;
|
|
688
|
-
const subset = requestFromQueryKey(query.queryKey)?.subset;
|
|
689
|
-
if (!subset) return false;
|
|
690
|
-
return subset.values.some((value) => isMarked(subset.field, value));
|
|
691
|
-
}
|
|
692
|
-
function invalidate(isMarked) {
|
|
693
|
-
void queryClient.invalidateQueries({ predicate: (query) => matchesMarked(query, isMarked) }).catch(
|
|
694
|
-
(error) => logger.error("Failed to invalidate marked subset queries", {
|
|
695
|
-
collectionName,
|
|
696
|
-
error
|
|
697
|
-
})
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
return {
|
|
701
|
-
mark(field, value) {
|
|
702
|
-
let values = marks.get(field);
|
|
703
|
-
if (!values) {
|
|
704
|
-
values = /* @__PURE__ */ new Set();
|
|
705
|
-
marks.set(field, values);
|
|
706
|
-
}
|
|
707
|
-
values.add(value);
|
|
708
|
-
},
|
|
709
|
-
count() {
|
|
710
|
-
let count = 0;
|
|
711
|
-
for (const values of marks.values()) count += values.size;
|
|
712
|
-
return count;
|
|
713
|
-
},
|
|
714
|
-
isLoaded({ field, values }) {
|
|
715
|
-
const marked = marks.get(field);
|
|
716
|
-
return marked !== void 0 && values.every((value) => marked.has(value));
|
|
717
|
-
},
|
|
718
|
-
// Deferred: this runs inside TanStack's write batch, and
|
|
719
|
-
// invalidateQueries may start a queryFn synchronously.
|
|
720
|
-
forgetRow(row) {
|
|
721
|
-
if (!row || typeof row !== "object") return;
|
|
722
|
-
for (const [field, marked] of marks) {
|
|
723
|
-
const forgotten = forgetValue(marked, row[field]);
|
|
724
|
-
if (forgotten.length === 0) continue;
|
|
725
|
-
queueMicrotask(
|
|
726
|
-
() => invalidate(
|
|
727
|
-
(queryField, value) => queryField === field && forgotten.includes(value)
|
|
728
|
-
)
|
|
729
|
-
);
|
|
730
|
-
}
|
|
731
|
-
},
|
|
732
|
-
invalidateAll() {
|
|
733
|
-
invalidate((field, value) => marks.get(field)?.has(value) ?? false);
|
|
734
|
-
},
|
|
735
|
-
clear() {
|
|
736
|
-
marks.clear();
|
|
737
|
-
}
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// src/filter-refs.ts
|
|
742
|
-
var RefCounter = class {
|
|
743
|
-
constructor() {
|
|
744
|
-
this.counts = /* @__PURE__ */ new Map();
|
|
745
|
-
}
|
|
746
|
-
add(key) {
|
|
747
|
-
this.counts.set(key, (this.counts.get(key) ?? 0) + 1);
|
|
748
|
-
}
|
|
749
|
-
/** Drops one reference. Returns true when none remain. */
|
|
750
|
-
drop(key) {
|
|
751
|
-
const count = (this.counts.get(key) ?? 0) - 1;
|
|
752
|
-
if (count > 0) {
|
|
753
|
-
this.counts.set(key, count);
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
this.counts.delete(key);
|
|
757
|
-
return true;
|
|
758
|
-
}
|
|
759
|
-
has(key) {
|
|
760
|
-
return this.counts.has(key);
|
|
761
|
-
}
|
|
762
|
-
keys() {
|
|
763
|
-
return this.counts.keys();
|
|
764
|
-
}
|
|
765
|
-
clear() {
|
|
766
|
-
this.counts.clear();
|
|
767
|
-
}
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
// src/realtime-subscription.ts
|
|
771
|
-
function createRealtimeSubscription(deps) {
|
|
772
|
-
const { pb, collectionName } = deps;
|
|
773
|
-
let starUnsubscribe = null;
|
|
774
|
-
let starExpand;
|
|
775
|
-
const filterEntries = /* @__PURE__ */ new Map();
|
|
776
|
-
const waiters = /* @__PURE__ */ new Set();
|
|
777
|
-
let collectionSubscribers = 0;
|
|
778
|
-
let unfilteredQueryRefs = 0;
|
|
779
|
-
const queryRefs = new RefCounter();
|
|
780
|
-
const holdRefs = new RefCounter();
|
|
781
|
-
const oversizedFilters = /* @__PURE__ */ new Set();
|
|
782
|
-
const isStarOpen = () => starUnsubscribe !== null;
|
|
783
|
-
const isOpen = () => isStarOpen() || filterEntries.size > 0;
|
|
784
|
-
const wantedFilters = () => /* @__PURE__ */ new Set([...queryRefs.keys(), ...holdRefs.keys()]);
|
|
785
|
-
const wantsStar = () => collectionSubscribers > 0 || unfilteredQueryRefs > 0 || oversizedFilters.size > 0;
|
|
786
|
-
function dropRef(refs, filter) {
|
|
787
|
-
if (refs.drop(filter) && !queryRefs.has(filter) && !holdRefs.has(filter)) {
|
|
788
|
-
oversizedFilters.delete(filter);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
function notifyOpened() {
|
|
792
|
-
deps.onEntryOpened();
|
|
793
|
-
for (const resolve of waiters) resolve();
|
|
794
|
-
waiters.clear();
|
|
795
|
-
}
|
|
796
|
-
let work = Promise.resolve();
|
|
797
|
-
function enqueue(fn) {
|
|
798
|
-
work = work.then(fn, fn).catch((error) => {
|
|
799
|
-
logger.error("Subscription work failed", { collectionName, error });
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
async function openStar() {
|
|
803
|
-
if (isStarOpen()) return;
|
|
804
|
-
const expand = deps.pendingExpand();
|
|
805
|
-
try {
|
|
806
|
-
starUnsubscribe = await pb.collection(collectionName).subscribe("*", deps.handleEvent, deps.subscribeOptions());
|
|
807
|
-
starExpand = expand;
|
|
808
|
-
logger.debug("Subscription started", { collectionName });
|
|
809
|
-
notifyOpened();
|
|
810
|
-
} catch (error) {
|
|
811
|
-
logger.error("Failed to start subscription", { collectionName, error });
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
async function closeStar() {
|
|
815
|
-
const unsubscribe = starUnsubscribe;
|
|
816
|
-
if (!unsubscribe) return;
|
|
817
|
-
starUnsubscribe = null;
|
|
818
|
-
starExpand = void 0;
|
|
819
|
-
try {
|
|
820
|
-
await unsubscribe();
|
|
821
|
-
logger.debug("Subscription stopped", { collectionName });
|
|
822
|
-
} catch (error) {
|
|
823
|
-
logger.debug("Unsubscribe failed (expected if connection closed)", {
|
|
824
|
-
collectionName,
|
|
825
|
-
error
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
function filteredOptions(filter) {
|
|
830
|
-
const base = deps.subscribeOptions();
|
|
831
|
-
const combined = base?.filter ? `(${base.filter}) && (${filter})` : filter;
|
|
832
|
-
return { ...base, filter: combined };
|
|
833
|
-
}
|
|
834
|
-
async function openFilter(filter) {
|
|
835
|
-
if (filterEntries.has(filter)) return true;
|
|
836
|
-
const options = filteredOptions(filter);
|
|
837
|
-
const topicLength = realtimeTopicLength(collectionName, options);
|
|
838
|
-
if (topicLength > REALTIME_TOPIC_MAX_LENGTH) {
|
|
839
|
-
oversizedFilters.add(filter);
|
|
840
|
-
logger.warn("Realtime filter too long; subscribing to the whole collection", {
|
|
841
|
-
collectionName,
|
|
842
|
-
filterLength: filter.length,
|
|
843
|
-
topicLength
|
|
844
|
-
});
|
|
845
|
-
return false;
|
|
846
|
-
}
|
|
847
|
-
const expand = deps.pendingExpand();
|
|
848
|
-
try {
|
|
849
|
-
const unsubscribe = await pb.collection(collectionName).subscribe("*", deps.handleEvent, options);
|
|
850
|
-
filterEntries.set(filter, { unsubscribe, expand });
|
|
851
|
-
logger.debug("Filtered subscription started", { collectionName, filter });
|
|
852
|
-
notifyOpened();
|
|
853
|
-
} catch (error) {
|
|
854
|
-
logger.error("Failed to start filtered subscription", {
|
|
855
|
-
collectionName,
|
|
856
|
-
filter,
|
|
857
|
-
error
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
return true;
|
|
861
|
-
}
|
|
862
|
-
async function closeFilter(filter) {
|
|
863
|
-
const entry = filterEntries.get(filter);
|
|
864
|
-
if (!entry) return;
|
|
865
|
-
filterEntries.delete(filter);
|
|
866
|
-
try {
|
|
867
|
-
await entry.unsubscribe();
|
|
868
|
-
logger.debug("Filtered subscription stopped", { collectionName, filter });
|
|
869
|
-
} catch (error) {
|
|
870
|
-
logger.debug("Unsubscribe failed (expected if connection closed)", {
|
|
871
|
-
collectionName,
|
|
872
|
-
filter,
|
|
873
|
-
error
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
async function closeFilters(keep = () => false) {
|
|
878
|
-
for (const filter of [...filterEntries.keys()]) {
|
|
879
|
-
if (!keep(filter)) await closeFilter(filter);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
async function openWantedFilters() {
|
|
883
|
-
if (wantsStar()) return;
|
|
884
|
-
for (const filter of wantedFilters()) {
|
|
885
|
-
if (!await openFilter(filter)) return;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
function expandDrifted() {
|
|
889
|
-
const pending = deps.pendingExpand();
|
|
890
|
-
if (isStarOpen() && starExpand !== pending) return true;
|
|
891
|
-
for (const entry of filterEntries.values()) if (entry.expand !== pending) return true;
|
|
892
|
-
return false;
|
|
893
|
-
}
|
|
894
|
-
async function doReconcile() {
|
|
895
|
-
await openWantedFilters();
|
|
896
|
-
if (wantsStar()) {
|
|
897
|
-
await openStar();
|
|
898
|
-
if (isStarOpen()) await closeFilters();
|
|
899
|
-
} else {
|
|
900
|
-
await closeStar();
|
|
901
|
-
const wanted = wantedFilters();
|
|
902
|
-
await closeFilters((filter) => wanted.has(filter));
|
|
903
|
-
}
|
|
904
|
-
if (!isOpen() && !wantsStar() && wantedFilters().size === 0) deps.onIdle();
|
|
905
|
-
if (expandDrifted()) restart();
|
|
906
|
-
}
|
|
907
|
-
async function doRestart() {
|
|
908
|
-
if (!isOpen()) return;
|
|
909
|
-
await closeStar();
|
|
910
|
-
await closeFilters();
|
|
911
|
-
await doReconcile();
|
|
912
|
-
}
|
|
913
|
-
const reconcile = () => enqueue(doReconcile);
|
|
914
|
-
const restart = () => enqueue(doRestart);
|
|
915
|
-
return {
|
|
916
|
-
addCollectionSubscriber() {
|
|
917
|
-
collectionSubscribers += 1;
|
|
918
|
-
},
|
|
919
|
-
dropCollectionSubscriber() {
|
|
920
|
-
collectionSubscribers -= 1;
|
|
921
|
-
},
|
|
922
|
-
retainQueryFilters(filters) {
|
|
923
|
-
if (!filters) unfilteredQueryRefs += 1;
|
|
924
|
-
else for (const filter of filters) queryRefs.add(filter);
|
|
925
|
-
reconcile();
|
|
926
|
-
},
|
|
927
|
-
releaseQueryFilters(filters) {
|
|
928
|
-
if (!filters) unfilteredQueryRefs -= 1;
|
|
929
|
-
else for (const filter of filters) dropRef(queryRefs, filter);
|
|
930
|
-
reconcile();
|
|
931
|
-
},
|
|
932
|
-
resetQueryFilters() {
|
|
933
|
-
queryRefs.clear();
|
|
934
|
-
unfilteredQueryRefs = 0;
|
|
935
|
-
for (const filter of oversizedFilters) {
|
|
936
|
-
if (!holdRefs.has(filter)) oversizedFilters.delete(filter);
|
|
937
|
-
}
|
|
938
|
-
reconcile();
|
|
939
|
-
},
|
|
940
|
-
swapHoldFilters(previous, next) {
|
|
941
|
-
for (const filter of next) if (!previous.includes(filter)) holdRefs.add(filter);
|
|
942
|
-
for (const filter of previous) if (!next.includes(filter)) dropRef(holdRefs, filter);
|
|
943
|
-
reconcile();
|
|
944
|
-
},
|
|
945
|
-
reconcile,
|
|
946
|
-
restart,
|
|
947
|
-
isOpen,
|
|
948
|
-
wait(timeout = 5e3) {
|
|
949
|
-
if (isOpen()) return Promise.resolve();
|
|
950
|
-
return new Promise((resolve, reject) => {
|
|
951
|
-
const onOpen = () => {
|
|
952
|
-
clearTimeout(timer);
|
|
953
|
-
resolve();
|
|
954
|
-
};
|
|
955
|
-
const timer = setTimeout(() => {
|
|
956
|
-
waiters.delete(onOpen);
|
|
957
|
-
reject(new Error("Subscription timeout"));
|
|
958
|
-
}, timeout);
|
|
959
|
-
waiters.add(onOpen);
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// src/synced-write-guard.ts
|
|
966
|
-
function createSyncedWriteGuard(collectionName, store) {
|
|
967
|
-
let applyingOwnWrite = false;
|
|
968
|
-
const inFlight = /* @__PURE__ */ new Set();
|
|
969
|
-
function isStaleServerRecord(record) {
|
|
970
|
-
const id = idOf(record);
|
|
971
|
-
if (!id) return false;
|
|
972
|
-
const incoming = updatedAtOf(record);
|
|
973
|
-
if (!incoming) return false;
|
|
974
|
-
const current = updatedAtOf(store.syncedRow(id));
|
|
975
|
-
return current !== void 0 && incoming < current;
|
|
976
|
-
}
|
|
977
|
-
function writeKey(op) {
|
|
978
|
-
if (op.type !== "insert" && op.type !== "update") return null;
|
|
979
|
-
if (typeof op.key === "string") return op.key;
|
|
980
|
-
return idOf(op.value) ?? null;
|
|
981
|
-
}
|
|
982
|
-
function markConfirmedPresent(records) {
|
|
983
|
-
if (inFlight.size === 0) return;
|
|
984
|
-
for (const record of records) {
|
|
985
|
-
const id = idOf(record);
|
|
986
|
-
if (!id) continue;
|
|
987
|
-
for (const confirmed of inFlight) confirmed.add(id);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
function withRowsConfirmedMidFlight(items, confirmed) {
|
|
991
|
-
if (confirmed.size === 0) return items;
|
|
992
|
-
const resultIds = new Set(items.map(idOf));
|
|
993
|
-
const merged = [];
|
|
994
|
-
for (const id of confirmed) {
|
|
995
|
-
if (resultIds.has(id)) continue;
|
|
996
|
-
const row = store.syncedRow(id);
|
|
997
|
-
if (row) merged.push(row);
|
|
998
|
-
}
|
|
999
|
-
if (merged.length === 0) return items;
|
|
1000
|
-
logger.debug("Merging rows confirmed while fetch was in flight", {
|
|
1001
|
-
collectionName,
|
|
1002
|
-
ids: merged.map(idOf)
|
|
1003
|
-
});
|
|
1004
|
-
return [...items, ...merged];
|
|
1005
|
-
}
|
|
1006
|
-
return {
|
|
1007
|
-
writeOwn(fn) {
|
|
1008
|
-
applyingOwnWrite = true;
|
|
1009
|
-
try {
|
|
1010
|
-
fn();
|
|
1011
|
-
} finally {
|
|
1012
|
-
applyingOwnWrite = false;
|
|
1013
|
-
}
|
|
1014
|
-
},
|
|
1015
|
-
isOwnWrite: () => applyingOwnWrite,
|
|
1016
|
-
// A write to a key the synced store lacks is populating it and must
|
|
1017
|
-
// pass, or the row is absent once the overlay clears.
|
|
1018
|
-
shouldDrop(op) {
|
|
1019
|
-
const key = writeKey(op);
|
|
1020
|
-
if (key === null) return false;
|
|
1021
|
-
if (!applyingOwnWrite && store.hasPendingMutation(key) && store.syncedRow(key)) {
|
|
1022
|
-
logger.debug("Dropping synced write for optimistically-pending row", {
|
|
1023
|
-
collectionName,
|
|
1024
|
-
id: key
|
|
1025
|
-
});
|
|
1026
|
-
return true;
|
|
1027
|
-
}
|
|
1028
|
-
if (isStaleServerRecord(op.value)) {
|
|
1029
|
-
logger.debug("Dropping stale synced write", { collectionName, id: key });
|
|
1030
|
-
return true;
|
|
1031
|
-
}
|
|
1032
|
-
return false;
|
|
1033
|
-
},
|
|
1034
|
-
isStaleServerRecord,
|
|
1035
|
-
markConfirmedPresent,
|
|
1036
|
-
trackFetch() {
|
|
1037
|
-
const confirmed = /* @__PURE__ */ new Set();
|
|
1038
|
-
inFlight.add(confirmed);
|
|
1039
|
-
return { confirmed, done: () => inFlight.delete(confirmed) };
|
|
1040
|
-
},
|
|
1041
|
-
withRowsConfirmedMidFlight,
|
|
1042
|
-
// Rows are marked confirmed at once. The write waits for persistence
|
|
1043
|
-
// (see docs/internals.md, "Write-back timing"), skips a collection
|
|
1044
|
-
// that is not ready, and drops rows the store already supersedes.
|
|
1045
|
-
writeBackAfterPersisted(transaction, records) {
|
|
1046
|
-
markConfirmedPresent(records);
|
|
1047
|
-
void transaction.isPersisted.promise.then(
|
|
1048
|
-
() => {
|
|
1049
|
-
if (!store.isReady()) return;
|
|
1050
|
-
const fresh = records.filter((record) => !isStaleServerRecord(record));
|
|
1051
|
-
if (fresh.length > 0) this.writeOwn(() => store.writeUpsert(fresh));
|
|
1052
|
-
},
|
|
1053
|
-
() => void 0
|
|
1054
|
-
);
|
|
1055
|
-
}
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// src/views.ts
|
|
1060
|
-
function createViewRegistry(subscribe) {
|
|
1061
|
-
const tags = /* @__PURE__ */ new WeakMap();
|
|
1062
|
-
let subscribing;
|
|
1063
|
-
function tagFor(opts) {
|
|
1064
|
-
return (opts.subscription && tags.get(opts.subscription)) ?? subscribing;
|
|
1065
|
-
}
|
|
1066
|
-
return {
|
|
1067
|
-
tagFor,
|
|
1068
|
-
withViewExpand(opts) {
|
|
1069
|
-
const paths = tagFor(opts)?.paths;
|
|
1070
|
-
return paths ? { ...opts, expand: paths } : opts;
|
|
1071
|
-
},
|
|
1072
|
-
subscribeTagged(tag, args) {
|
|
1073
|
-
subscribing = tag;
|
|
1074
|
-
try {
|
|
1075
|
-
const subscription = subscribe(...args);
|
|
1076
|
-
if (tag) tags.set(subscription, tag);
|
|
1077
|
-
return subscription;
|
|
1078
|
-
} finally {
|
|
1079
|
-
subscribing = void 0;
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
function createViews(deps) {
|
|
1085
|
-
const { collectionName, collection, alwaysFetch, relationTargets, realtimeMode } = deps;
|
|
1086
|
-
const views = /* @__PURE__ */ new Map();
|
|
1087
|
-
function createView(tag) {
|
|
1088
|
-
const query = [];
|
|
1089
|
-
if (tag.paths.length > 0) query.push(`expand=${tag.paths.join(",")}`);
|
|
1090
|
-
if (tag.realtime) query.push(`realtime=${tag.realtime}`);
|
|
1091
|
-
const view = Object.create(collection);
|
|
1092
|
-
Object.defineProperties(view, {
|
|
1093
|
-
id: { value: `${collectionName}?${query.join("&")}` },
|
|
1094
|
-
subscribeChanges: { value: deps.subscribeChangesFor(tag) },
|
|
1095
|
-
fetchRelations: {
|
|
1096
|
-
value: (...more) => viewFor([...tag.paths, ...more], tag.realtime)
|
|
1097
|
-
},
|
|
1098
|
-
withRealtime: {
|
|
1099
|
-
value: (mode) => {
|
|
1100
|
-
deps.assertRealtimeMode(mode);
|
|
1101
|
-
const override = mode === realtimeMode ? void 0 : mode;
|
|
1102
|
-
if (tag.realtime !== void 0 && tag.realtime !== override) {
|
|
1103
|
-
throw new Error(
|
|
1104
|
-
`A view of "${collectionName}" already uses realtime '${tag.realtime}'`
|
|
1105
|
-
);
|
|
1106
|
-
}
|
|
1107
|
-
return viewFor(tag.paths, mode);
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
});
|
|
1111
|
-
return view;
|
|
1112
|
-
}
|
|
1113
|
-
function viewFor(paths, realtime) {
|
|
1114
|
-
for (const path of paths) validateExpandPath(collectionName, relationTargets, path);
|
|
1115
|
-
const all = normalizePaths([...alwaysFetch, ...paths]);
|
|
1116
|
-
const override = realtime === realtimeMode ? void 0 : realtime;
|
|
1117
|
-
if (override === void 0 && all.every((path) => alwaysFetch.includes(path))) {
|
|
1118
|
-
return collection;
|
|
1119
|
-
}
|
|
1120
|
-
const key = `${all.join(",")}|${override ?? ""}`;
|
|
1121
|
-
let view = views.get(key);
|
|
1122
|
-
if (!view) {
|
|
1123
|
-
view = createView({ paths: all, realtime: override });
|
|
1124
|
-
views.set(key, view);
|
|
1125
|
-
}
|
|
1126
|
-
return view;
|
|
1127
|
-
}
|
|
1128
|
-
return {
|
|
1129
|
-
fetchRelations: (...paths) => viewFor(paths, void 0),
|
|
1130
|
-
withRealtime: (mode) => {
|
|
1131
|
-
deps.assertRealtimeMode(mode);
|
|
1132
|
-
return viewFor([], mode);
|
|
1133
|
-
}
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// src/build-collection.ts
|
|
1138
|
-
var REALTIME_MODES = ["collection", "query"];
|
|
1139
|
-
function registerMarkInvalidationEvents(collection, clearMarks) {
|
|
1140
|
-
collection.onStatusChange((event) => {
|
|
1141
|
-
if (event.status === "cleaned-up") clearMarks();
|
|
1142
|
-
});
|
|
1143
|
-
collection.onTruncate(() => clearMarks());
|
|
1144
|
-
}
|
|
1145
|
-
function buildCollection(input) {
|
|
1146
|
-
const { pb, queryClient, factoryOptions, collectionName, options } = input;
|
|
1147
|
-
const relationTargets = options?.relations;
|
|
1148
|
-
for (const [key, target] of Object.entries(relationTargets ?? {})) {
|
|
1149
|
-
if (typeof target?.writeFiled !== "function" || typeof target.holdLive !== "function" || typeof target.expectFiling !== "function") {
|
|
1150
|
-
throw new Error(
|
|
1151
|
-
`Collection '${collectionName}': relation '${key}' is not a pbtsdb collection`
|
|
1152
|
-
);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
const alwaysFetch = normalizePaths(options?.alwaysFetchRelations ?? []);
|
|
1156
|
-
for (const path of alwaysFetch) validateExpandPath(collectionName, relationTargets, path);
|
|
1157
|
-
const syncMode = options?.syncMode ?? "eager";
|
|
1158
|
-
const realtimeMode = options?.realtime ?? "collection";
|
|
1159
|
-
const ignoreAutoCancellation = options?.ignoreAutoCancellation ?? true;
|
|
1160
|
-
const refetchOnMutation = options?.refetchOnMutation ?? false;
|
|
1161
|
-
function assertRealtimeMode(mode) {
|
|
1162
|
-
if (!REALTIME_MODES.includes(mode)) {
|
|
1163
|
-
throw new Error(
|
|
1164
|
-
`Collection '${collectionName}': unknown realtime mode '${String(mode)}'`
|
|
1165
|
-
);
|
|
1166
|
-
}
|
|
1167
|
-
if (mode === "query" && syncMode !== "on-demand") {
|
|
1168
|
-
throw new Error(
|
|
1169
|
-
`Collection '${collectionName}': realtime 'query' requires syncMode 'on-demand'`
|
|
1170
|
-
);
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
assertRealtimeMode(realtimeMode);
|
|
1174
|
-
const requestedExpand = /* @__PURE__ */ new Set();
|
|
1175
|
-
const pendingSubscribeExpand = () => joinPaths([...alwaysFetch, ...requestedExpand]);
|
|
1176
|
-
function activeExpand(request) {
|
|
1177
|
-
return joinPaths([
|
|
1178
|
-
...alwaysFetch,
|
|
1179
|
-
...splitPaths(request.expand),
|
|
1180
|
-
...syncMode === "eager" ? requestedExpand : []
|
|
1181
|
-
]);
|
|
1182
|
-
}
|
|
1183
|
-
function realtimeSubscribeOptions() {
|
|
1184
|
-
const base = factoryOptions?.subscribeOptions?.();
|
|
1185
|
-
const expand = joinPaths([
|
|
1186
|
-
...splitPaths(pendingSubscribeExpand()),
|
|
1187
|
-
...splitPaths(base?.expand)
|
|
1188
|
-
]);
|
|
1189
|
-
return expand ? { ...base, expand } : base;
|
|
1190
|
-
}
|
|
1191
|
-
function releaseAll() {
|
|
1192
|
-
held.releaseAll();
|
|
1193
|
-
subsets.invalidateAll();
|
|
1194
|
-
subsets.clear();
|
|
1195
|
-
}
|
|
1196
|
-
const syncedRow = (id) => collection._state.syncedData.get(id);
|
|
1197
|
-
const syncedRows = () => collection._state.syncedData.values();
|
|
1198
|
-
const subsets = createLoadedSubsets(collectionName, queryClient);
|
|
1199
|
-
const held = createHeldTargets(collectionName);
|
|
1200
|
-
const filer = createExpandFiler(collectionName, held.recordFiled);
|
|
1201
|
-
const guard = createSyncedWriteGuard(collectionName, {
|
|
1202
|
-
isReady: () => collection.isReady(),
|
|
1203
|
-
syncedRow,
|
|
1204
|
-
// A visible row reports its own overlay; an invisible row with a
|
|
1205
|
-
// synced copy is under an optimistic delete.
|
|
1206
|
-
hasPendingMutation: (id) => {
|
|
1207
|
-
const visible = collection.get(id);
|
|
1208
|
-
return visible ? !visible.$synced : syncedRow(id) !== void 0;
|
|
1209
|
-
},
|
|
1210
|
-
writeUpsert: (records) => collection.utils.writeUpsert(records)
|
|
1211
|
-
});
|
|
1212
|
-
const fetcher = createFetcher({
|
|
1213
|
-
pb,
|
|
1214
|
-
collectionName,
|
|
1215
|
-
queryClient,
|
|
1216
|
-
relationTargets,
|
|
1217
|
-
ignoreAutoCancellation,
|
|
1218
|
-
activeExpand,
|
|
1219
|
-
syncedRow,
|
|
1220
|
-
syncedRows,
|
|
1221
|
-
subsets,
|
|
1222
|
-
guard,
|
|
1223
|
-
filer
|
|
1224
|
-
});
|
|
1225
|
-
const realtime = createRealtimeSubscription({
|
|
1226
|
-
pb,
|
|
1227
|
-
collectionName,
|
|
1228
|
-
handleEvent: (event) => handleRealtimeEvent(event),
|
|
1229
|
-
pendingExpand: pendingSubscribeExpand,
|
|
1230
|
-
subscribeOptions: realtimeSubscribeOptions,
|
|
1231
|
-
onEntryOpened: () => held.sync(targetsAlong(splitPaths(pendingSubscribeExpand()), relationTargets)),
|
|
1232
|
-
onIdle: releaseAll
|
|
1233
|
-
});
|
|
1234
|
-
function resolveHandler(option, fallback) {
|
|
1235
|
-
return option === false ? void 0 : option ?? fallback;
|
|
1236
|
-
}
|
|
1237
|
-
const defaultInsert = async ({ transaction }) => {
|
|
1238
|
-
const created = await Promise.all(
|
|
1239
|
-
transaction.mutations.map(async (mutation) => {
|
|
1240
|
-
const {
|
|
1241
|
-
created: _created,
|
|
1242
|
-
updated: _updated,
|
|
1243
|
-
collectionId: _collectionId,
|
|
1244
|
-
collectionName: _collectionName,
|
|
1245
|
-
...data
|
|
1246
|
-
} = mutation.modified;
|
|
1247
|
-
return pb.collection(collectionName).create(data);
|
|
1248
|
-
})
|
|
1249
|
-
);
|
|
1250
|
-
guard.writeBackAfterPersisted(transaction, created);
|
|
1251
|
-
return { refetch: refetchOnMutation };
|
|
1252
|
-
};
|
|
1253
|
-
const defaultUpdate = async ({ transaction }) => {
|
|
1254
|
-
const updated = await Promise.all(
|
|
1255
|
-
transaction.mutations.map(async (mutation) => {
|
|
1256
|
-
return pb.collection(collectionName).update(mutation.original.id, mutation.changes);
|
|
1257
|
-
})
|
|
1258
|
-
);
|
|
1259
|
-
guard.writeBackAfterPersisted(transaction, updated);
|
|
1260
|
-
return { refetch: refetchOnMutation };
|
|
1261
|
-
};
|
|
1262
|
-
const defaultDelete = async ({ transaction }) => {
|
|
1263
|
-
await Promise.all(
|
|
1264
|
-
transaction.mutations.map(async (mutation) => {
|
|
1265
|
-
await pb.collection(collectionName).delete(mutation.original.id);
|
|
1266
|
-
})
|
|
1267
|
-
);
|
|
1268
|
-
return { refetch: refetchOnMutation };
|
|
1269
|
-
};
|
|
1270
|
-
const queryCollectionConfig = queryCollectionOptions({
|
|
1271
|
-
autoIndex: "eager",
|
|
1272
|
-
defaultIndexType: BTreeIndex,
|
|
1273
|
-
...options?.collectionOptions,
|
|
1274
|
-
queryClient,
|
|
1275
|
-
queryKey: (opts) => queryKeyFor(collectionName, opts),
|
|
1276
|
-
syncMode,
|
|
1277
|
-
queryFn: (ctx) => fetcher.fetchRecords(requestFromQueryKey(ctx.queryKey) ?? {}, ctx.queryKey),
|
|
1278
|
-
getKey: (item) => {
|
|
1279
|
-
const id = idOf(item);
|
|
1280
|
-
if (id === void 0) {
|
|
1281
|
-
throw new Error(
|
|
1282
|
-
`Record in collection '${collectionName}' is missing required 'id' field. Received: ${JSON.stringify(item)}`
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
return id;
|
|
1286
|
-
},
|
|
1287
|
-
onInsert: resolveHandler(options?.onInsert, defaultInsert),
|
|
1288
|
-
onUpdate: resolveHandler(options?.onUpdate, defaultUpdate),
|
|
1289
|
-
onDelete: resolveHandler(options?.onDelete, defaultDelete)
|
|
1290
|
-
});
|
|
1291
|
-
const collectionOptions = options?.collectionOptions?.gcTime === void 0 ? queryCollectionConfig : { ...queryCollectionConfig, gcTime: options.collectionOptions.gcTime };
|
|
1292
|
-
let retainedFilters = /* @__PURE__ */ new WeakMap();
|
|
1293
|
-
function retainQueryFilters(opts) {
|
|
1294
|
-
if ((registry.tagFor(opts)?.realtime ?? realtimeMode) !== "query") return;
|
|
1295
|
-
const filters = realtimeFiltersFor(toRequest(opts));
|
|
1296
|
-
const records = retainedFilters.get(opts) ?? [];
|
|
1297
|
-
records.push({ filters });
|
|
1298
|
-
retainedFilters.set(opts, records);
|
|
1299
|
-
realtime.retainQueryFilters(filters);
|
|
1300
|
-
}
|
|
1301
|
-
function releaseQueryFilters(opts) {
|
|
1302
|
-
const records = retainedFilters.get(opts);
|
|
1303
|
-
const retained = records?.shift();
|
|
1304
|
-
if (!retained) return;
|
|
1305
|
-
if (records?.length === 0) retainedFilters.delete(opts);
|
|
1306
|
-
realtime.releaseQueryFilters(retained.filters);
|
|
1307
|
-
}
|
|
1308
|
-
const innerSync = collectionOptions.sync.sync;
|
|
1309
|
-
collectionOptions.sync = {
|
|
1310
|
-
...collectionOptions.sync,
|
|
1311
|
-
sync: (params) => {
|
|
1312
|
-
const guardedWrite = (message) => {
|
|
1313
|
-
const op = message;
|
|
1314
|
-
if (op.type === "delete" && !guard.isOwnWrite()) subsets.forgetRow(op.value);
|
|
1315
|
-
if (guard.shouldDrop(op)) return;
|
|
1316
|
-
return params.write(message);
|
|
1317
|
-
};
|
|
1318
|
-
const res = innerSync({ ...params, write: guardedWrite });
|
|
1319
|
-
if (!res) return res;
|
|
1320
|
-
const parts = typeof res === "function" ? { cleanup: res } : res;
|
|
1321
|
-
const { cleanup, loadSubset, unloadSubset } = parts;
|
|
1322
|
-
return {
|
|
1323
|
-
...parts,
|
|
1324
|
-
// TanStack drops a discarded sync session's demands without
|
|
1325
|
-
// unloadSubset and reloads them on the next session.
|
|
1326
|
-
cleanup: () => {
|
|
1327
|
-
retainedFilters = /* @__PURE__ */ new WeakMap();
|
|
1328
|
-
realtime.resetQueryFilters();
|
|
1329
|
-
return cleanup?.();
|
|
1330
|
-
},
|
|
1331
|
-
loadSubset: loadSubset ? (opts) => {
|
|
1332
|
-
retainQueryFilters(opts);
|
|
1333
|
-
try {
|
|
1334
|
-
return loadSubset(registry.withViewExpand(opts));
|
|
1335
|
-
} catch (error) {
|
|
1336
|
-
releaseQueryFilters(opts);
|
|
1337
|
-
throw error;
|
|
1338
|
-
}
|
|
1339
|
-
} : void 0,
|
|
1340
|
-
unloadSubset: unloadSubset ? (opts) => {
|
|
1341
|
-
releaseQueryFilters(opts);
|
|
1342
|
-
return unloadSubset(registry.withViewExpand(opts));
|
|
1343
|
-
} : void 0
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
};
|
|
1347
|
-
const collection = createCollection$1(collectionOptions);
|
|
1348
|
-
const originalSubscribeChanges = collection.subscribeChanges.bind(collection);
|
|
1349
|
-
const registry = createViewRegistry(originalSubscribeChanges);
|
|
1350
|
-
function subscribeCounted(subscribe) {
|
|
1351
|
-
realtime.addCollectionSubscriber();
|
|
1352
|
-
let subscription;
|
|
1353
|
-
try {
|
|
1354
|
-
subscription = subscribe();
|
|
1355
|
-
} catch (error) {
|
|
1356
|
-
realtime.dropCollectionSubscriber();
|
|
1357
|
-
throw error;
|
|
1358
|
-
}
|
|
1359
|
-
const unsubscribe = subscription.unsubscribe.bind(subscription);
|
|
1360
|
-
let released = false;
|
|
1361
|
-
subscription.unsubscribe = () => {
|
|
1362
|
-
if (!released) {
|
|
1363
|
-
released = true;
|
|
1364
|
-
realtime.dropCollectionSubscriber();
|
|
1365
|
-
realtime.reconcile();
|
|
1366
|
-
}
|
|
1367
|
-
unsubscribe();
|
|
1368
|
-
};
|
|
1369
|
-
return subscription;
|
|
1370
|
-
}
|
|
1371
|
-
function subscribeChangesFor(tag) {
|
|
1372
|
-
return (...args) => {
|
|
1373
|
-
if (tag) noteViewSubscribed(tag.paths);
|
|
1374
|
-
const subscription = (tag?.realtime ?? realtimeMode) === "collection" ? subscribeCounted(() => registry.subscribeTagged(tag, args)) : registry.subscribeTagged(tag, args);
|
|
1375
|
-
realtime.reconcile();
|
|
1376
|
-
return subscription;
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
Object.defineProperty(collection, "subscribeChanges", {
|
|
1380
|
-
value: subscribeChangesFor(void 0)
|
|
1381
|
-
});
|
|
1382
|
-
const views = createViews({
|
|
1383
|
-
collectionName,
|
|
1384
|
-
collection,
|
|
1385
|
-
alwaysFetch,
|
|
1386
|
-
relationTargets,
|
|
1387
|
-
realtimeMode,
|
|
1388
|
-
assertRealtimeMode,
|
|
1389
|
-
subscribeChangesFor
|
|
1390
|
-
});
|
|
1391
|
-
function isStaleEcho(event) {
|
|
1392
|
-
if (event.action !== "create" && event.action !== "update") return false;
|
|
1393
|
-
if (!guard.isStaleServerRecord(event.record)) return false;
|
|
1394
|
-
logger.debug("Ignoring stale realtime echo", { collectionName, id: idOf(event.record) });
|
|
1395
|
-
return true;
|
|
1396
|
-
}
|
|
1397
|
-
function handleRealtimeEvent(event) {
|
|
1398
|
-
if (!collection.utils) return;
|
|
1399
|
-
if (event.action !== "delete") guard.markConfirmedPresent([event.record]);
|
|
1400
|
-
if (isStaleEcho(event)) return;
|
|
1401
|
-
const [stored] = stripFetchedRelations([event.record], pendingSubscribeExpand());
|
|
1402
|
-
try {
|
|
1403
|
-
guard.writeOwn(
|
|
1404
|
-
() => collection.utils.writeBatch(() => {
|
|
1405
|
-
switch (event.action) {
|
|
1406
|
-
case "create":
|
|
1407
|
-
collection.utils.writeInsert(stored);
|
|
1408
|
-
break;
|
|
1409
|
-
case "update":
|
|
1410
|
-
collection.utils.writeUpsert(stored);
|
|
1411
|
-
break;
|
|
1412
|
-
case "delete": {
|
|
1413
|
-
const id = idOf(event.record);
|
|
1414
|
-
if (id) collection.utils.writeDelete(id);
|
|
1415
|
-
break;
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
})
|
|
1419
|
-
);
|
|
1420
|
-
} catch (error) {
|
|
1421
|
-
if (!(error instanceof DeleteOperationItemNotFoundError)) throw error;
|
|
1422
|
-
logger.debug("Ignoring delete echo for already-removed record", {
|
|
1423
|
-
collectionName,
|
|
1424
|
-
id: idOf(event.record)
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1427
|
-
if (event.action !== "delete") {
|
|
1428
|
-
filer.upsertExpanded([event.record], relationTargets).catch(
|
|
1429
|
-
(error) => logger.error("Failed to upsert expanded records from realtime echo", {
|
|
1430
|
-
collectionName,
|
|
1431
|
-
error
|
|
1432
|
-
})
|
|
1433
|
-
);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
async function writeFiled(records) {
|
|
1437
|
-
if (!collection.isReady()) {
|
|
1438
|
-
const idle = collection.status === "idle" || collection.status === "cleaned-up";
|
|
1439
|
-
if (syncMode === "eager" && idle) {
|
|
1440
|
-
logger.warn(
|
|
1441
|
-
`not syncing filed rows into ${collectionName} because store is not yet ready`
|
|
1442
|
-
);
|
|
1443
|
-
return false;
|
|
1444
|
-
}
|
|
1445
|
-
await collection.preload();
|
|
1446
|
-
}
|
|
1447
|
-
collection.utils.writeUpsert(withoutExpand(records));
|
|
1448
|
-
return true;
|
|
1449
|
-
}
|
|
1450
|
-
function holdLive() {
|
|
1451
|
-
const subscribe = () => originalSubscribeChanges(() => {
|
|
1452
|
-
}, { includeInitialState: false });
|
|
1453
|
-
const subscription = realtimeMode === "collection" ? subscribeCounted(subscribe) : subscribe();
|
|
1454
|
-
let current = [];
|
|
1455
|
-
let released = false;
|
|
1456
|
-
realtime.reconcile();
|
|
1457
|
-
return {
|
|
1458
|
-
setFilters: (filters) => {
|
|
1459
|
-
if (released) return;
|
|
1460
|
-
const next = [...new Set(filters)];
|
|
1461
|
-
realtime.swapHoldFilters(current, next);
|
|
1462
|
-
current = next;
|
|
1463
|
-
},
|
|
1464
|
-
release: () => {
|
|
1465
|
-
if (released) return;
|
|
1466
|
-
released = true;
|
|
1467
|
-
realtime.swapHoldFilters(current, []);
|
|
1468
|
-
current = [];
|
|
1469
|
-
subscription.unsubscribe();
|
|
1470
|
-
}
|
|
1471
|
-
};
|
|
1472
|
-
}
|
|
1473
|
-
function noteViewSubscribed(paths) {
|
|
1474
|
-
let grew = false;
|
|
1475
|
-
for (const path of paths) {
|
|
1476
|
-
if (requestedExpand.has(path)) continue;
|
|
1477
|
-
requestedExpand.add(path);
|
|
1478
|
-
grew = true;
|
|
1479
|
-
}
|
|
1480
|
-
if (!grew) return;
|
|
1481
|
-
if (syncMode === "eager" && collection.status !== "idle" && collection.status !== "cleaned-up") {
|
|
1482
|
-
void collection.utils.refetch().catch(
|
|
1483
|
-
(error) => logger.error("Failed to refetch after widening expand", {
|
|
1484
|
-
collectionName,
|
|
1485
|
-
error
|
|
1486
|
-
})
|
|
1487
|
-
);
|
|
1488
|
-
}
|
|
1489
|
-
realtime.restart();
|
|
1490
|
-
}
|
|
1491
|
-
registerMarkInvalidationEvents(
|
|
1492
|
-
{
|
|
1493
|
-
onStatusChange: (callback) => collection.on("status:change", callback),
|
|
1494
|
-
onTruncate: (callback) => collection.on("truncate", callback)
|
|
1495
|
-
},
|
|
1496
|
-
() => {
|
|
1497
|
-
subsets.clear();
|
|
1498
|
-
held.clearFiled();
|
|
1499
|
-
}
|
|
1500
|
-
);
|
|
1501
|
-
Object.assign(collection, {
|
|
1502
|
-
collectionName,
|
|
1503
|
-
relationTargets,
|
|
1504
|
-
waitForSubscription: realtime.wait,
|
|
1505
|
-
isSubscribed: realtime.isOpen,
|
|
1506
|
-
heldRelationTargetCount: held.count,
|
|
1507
|
-
markSubsetLoaded: subsets.mark,
|
|
1508
|
-
loadedSubsetCount: subsets.count,
|
|
1509
|
-
holdLive,
|
|
1510
|
-
writeFiled,
|
|
1511
|
-
expectFiling: fetcher.expectFiling,
|
|
1512
|
-
fetchRelations: views.fetchRelations,
|
|
1513
|
-
withRealtime: views.withRealtime
|
|
1514
|
-
});
|
|
1515
|
-
return collection;
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
// src/collection.ts
|
|
1519
|
-
function createCollection(pb, queryClient, factoryOptions) {
|
|
1520
|
-
return (collectionName, options) => {
|
|
1521
|
-
return buildCollection({
|
|
1522
|
-
pb,
|
|
1523
|
-
queryClient,
|
|
1524
|
-
factoryOptions,
|
|
1525
|
-
collectionName,
|
|
1526
|
-
options
|
|
1527
|
-
});
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
// src/util.ts
|
|
1532
|
-
function newRecordId() {
|
|
1533
|
-
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
1534
|
-
let result = "";
|
|
1535
|
-
for (let i = 0; i < 15; i++) {
|
|
1536
|
-
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1537
|
-
}
|
|
1538
|
-
return result;
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
export { createCollection, newRecordId, resetLogger, setLogger };
|
|
1542
|
-
//# sourceMappingURL=chunk-X7ZGPVG2.js.map
|
|
1543
|
-
//# sourceMappingURL=chunk-X7ZGPVG2.js.map
|