pbtsdb 0.8.0 → 0.9.1
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 +184 -100
- package/dist/{chunk-KMR6I4ZC.js → chunk-LT7BDI37.js} +315 -247
- package/dist/chunk-LT7BDI37.js.map +1 -0
- package/dist/core.d.ts +37 -96
- package/dist/core.js +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/llms.txt +33 -13
- package/package.json +1 -1
- package/dist/chunk-KMR6I4ZC.js.map +0 -1
|
@@ -1,57 +1,109 @@
|
|
|
1
|
-
import { BTreeIndex, createCollection as createCollection$1, parseWhereExpression, parseOrderByExpression
|
|
2
|
-
export { BTreeIndex, BasicIndex, ReverseIndex, createEffect, toArray } from '@tanstack/db';
|
|
1
|
+
import { BTreeIndex, createCollection as createCollection$1, parseWhereExpression, parseOrderByExpression } from '@tanstack/db';
|
|
2
|
+
export { BTreeIndex, BasicIndex, ReverseIndex, createEffect, materialize, toArray } from '@tanstack/db';
|
|
3
3
|
import { queryCollectionOptions, DeleteOperationItemNotFoundError } from '@tanstack/query-db-collection';
|
|
4
4
|
|
|
5
5
|
// src/core.ts
|
|
6
6
|
|
|
7
|
-
// src/expand-
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
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);
|
|
11
13
|
}
|
|
12
|
-
return
|
|
14
|
+
return [...set].sort();
|
|
13
15
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
const b = stored;
|
|
17
|
-
if (!a || !b || a.id !== b.id) return false;
|
|
18
|
-
if (typeof a.updated !== "string" || typeof b.updated !== "string") return false;
|
|
19
|
-
return b.updated > a.updated;
|
|
16
|
+
function splitPaths(csv) {
|
|
17
|
+
return csv ? normalizePaths(csv.split(",")) : [];
|
|
20
18
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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`
|
|
27
47
|
);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
}
|
|
49
|
+
current = next.relationTargets;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/keyed-where.ts
|
|
54
|
+
function fieldRef(node) {
|
|
55
|
+
return node?.type === "ref" && node.path?.length === 1 ? node.path[0] : void 0;
|
|
56
|
+
}
|
|
57
|
+
function stringValue(node) {
|
|
58
|
+
return node?.type === "val" && typeof node.value === "string" ? node.value : void 0;
|
|
59
|
+
}
|
|
60
|
+
function collectEq(a, b) {
|
|
61
|
+
const field = fieldRef(a) ?? fieldRef(b);
|
|
62
|
+
if (field === void 0) return void 0;
|
|
63
|
+
const value = stringValue(fieldRef(a) !== void 0 ? b : a);
|
|
64
|
+
return value === void 0 ? void 0 : { field, values: [value] };
|
|
65
|
+
}
|
|
66
|
+
function collectIn(a, b) {
|
|
67
|
+
const field = fieldRef(a);
|
|
68
|
+
if (field === void 0 || b?.type !== "val" || !Array.isArray(b.value)) return void 0;
|
|
69
|
+
return b.value.every((v) => typeof v === "string") ? { field, values: b.value } : void 0;
|
|
70
|
+
}
|
|
71
|
+
function collectOr(args) {
|
|
72
|
+
let field;
|
|
73
|
+
const values = [];
|
|
74
|
+
for (const arg of args) {
|
|
75
|
+
const sub = collect(arg);
|
|
76
|
+
if (!sub || field !== void 0 && sub.field !== field) return void 0;
|
|
77
|
+
field = sub.field;
|
|
78
|
+
values.push(...sub.values);
|
|
79
|
+
}
|
|
80
|
+
return field === void 0 ? void 0 : { field, values };
|
|
81
|
+
}
|
|
82
|
+
function collect(node) {
|
|
83
|
+
if (node.type !== "func" || !node.args) return void 0;
|
|
84
|
+
const [a, b] = node.args;
|
|
85
|
+
switch (node.name) {
|
|
86
|
+
case "eq":
|
|
87
|
+
return collectEq(a, b);
|
|
88
|
+
case "in":
|
|
89
|
+
return collectIn(a, b);
|
|
90
|
+
case "or":
|
|
91
|
+
return collectOr(node.args);
|
|
92
|
+
default:
|
|
93
|
+
return void 0;
|
|
33
94
|
}
|
|
34
|
-
return storedIsNewer(incoming, stored) ? stored : incoming;
|
|
35
95
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
carried = true;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (!sameRelationValue(row[relation], existing[relation])) continue;
|
|
51
|
-
merged[relation] = value;
|
|
52
|
-
carried = true;
|
|
53
|
-
}
|
|
54
|
-
return carried ? { ...row, expand: merged } : incoming;
|
|
96
|
+
function subsetFromWhere(where) {
|
|
97
|
+
if (!where) return void 0;
|
|
98
|
+
const subset = collect(where);
|
|
99
|
+
return subset ? { field: subset.field, values: [...new Set(subset.values)].sort() } : void 0;
|
|
100
|
+
}
|
|
101
|
+
function matchesSubset(row, field, wanted) {
|
|
102
|
+
const value = row[field];
|
|
103
|
+
if (typeof value === "string") return wanted.has(value);
|
|
104
|
+
if (Array.isArray(value))
|
|
105
|
+
return value.some((item) => typeof item === "string" && wanted.has(item));
|
|
106
|
+
return false;
|
|
55
107
|
}
|
|
56
108
|
|
|
57
109
|
// src/logger.ts
|
|
@@ -84,112 +136,6 @@ function setLogger(customLogger) {
|
|
|
84
136
|
function resetLogger() {
|
|
85
137
|
currentLogger = defaultLogger;
|
|
86
138
|
}
|
|
87
|
-
|
|
88
|
-
// src/expand-patch.ts
|
|
89
|
-
function embeddedId(value) {
|
|
90
|
-
return value?.id;
|
|
91
|
-
}
|
|
92
|
-
function patchUpdate(row, field, record) {
|
|
93
|
-
const embedded = row.expand?.[field];
|
|
94
|
-
if (embedded === void 0) return void 0;
|
|
95
|
-
let next;
|
|
96
|
-
if (Array.isArray(embedded)) {
|
|
97
|
-
const index = embedded.findIndex((entry) => embeddedId(entry) === record.id);
|
|
98
|
-
if (index === -1) return void 0;
|
|
99
|
-
const replaced = mergeExpand(record, embedded[index]);
|
|
100
|
-
next = embedded.map((entry, i) => i === index ? replaced : entry);
|
|
101
|
-
} else {
|
|
102
|
-
if (embeddedId(embedded) !== record.id) return void 0;
|
|
103
|
-
next = mergeExpand(record, embedded);
|
|
104
|
-
}
|
|
105
|
-
if (deepEquals(next, embedded)) return void 0;
|
|
106
|
-
return { ...row, expand: { ...row.expand, [field]: next } };
|
|
107
|
-
}
|
|
108
|
-
function patchDelete(row, field, id) {
|
|
109
|
-
const value = row[field];
|
|
110
|
-
const embedded = row.expand?.[field];
|
|
111
|
-
if (Array.isArray(value)) {
|
|
112
|
-
if (!value.includes(id)) return void 0;
|
|
113
|
-
const patched2 = { ...row, [field]: value.filter((item) => item !== id) };
|
|
114
|
-
if (Array.isArray(embedded)) {
|
|
115
|
-
patched2.expand = {
|
|
116
|
-
...row.expand,
|
|
117
|
-
[field]: embedded.filter((entry) => embeddedId(entry) !== id)
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
return patched2;
|
|
121
|
-
}
|
|
122
|
-
if (value !== id) return void 0;
|
|
123
|
-
const patched = { ...row, [field]: "" };
|
|
124
|
-
if (embedded !== void 0) {
|
|
125
|
-
const expand = { ...row.expand };
|
|
126
|
-
delete expand[field];
|
|
127
|
-
patched.expand = expand;
|
|
128
|
-
}
|
|
129
|
-
return patched;
|
|
130
|
-
}
|
|
131
|
-
function patchEmbedded(row, field, action, record) {
|
|
132
|
-
const current = row;
|
|
133
|
-
const patched = action === "delete" ? patchDelete(current, field, record.id) : patchUpdate(current, field, record);
|
|
134
|
-
return patched;
|
|
135
|
-
}
|
|
136
|
-
function fieldsByParent(dependents) {
|
|
137
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
138
|
-
for (const { field, parent } of dependents) {
|
|
139
|
-
const fields = grouped.get(parent);
|
|
140
|
-
if (fields) fields.push(field);
|
|
141
|
-
else grouped.set(parent, [field]);
|
|
142
|
-
}
|
|
143
|
-
return grouped;
|
|
144
|
-
}
|
|
145
|
-
function propagateRelatedChange(source, action, record, visited) {
|
|
146
|
-
for (const [parent, fields] of fieldsByParent(source.relationDependents ?? [])) {
|
|
147
|
-
try {
|
|
148
|
-
parent.applyRelatedChange?.(fields, action, record, visited);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
logger.error("Failed to patch a relation dependent", {
|
|
151
|
-
collectionName: source.collectionName,
|
|
152
|
-
dependent: parent.collectionName,
|
|
153
|
-
fields,
|
|
154
|
-
error
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// src/expand-paths.ts
|
|
161
|
-
function normalizePaths(paths) {
|
|
162
|
-
const set = /* @__PURE__ */ new Set();
|
|
163
|
-
for (const path of paths) {
|
|
164
|
-
const trimmed = path.trim();
|
|
165
|
-
if (trimmed) set.add(trimmed);
|
|
166
|
-
}
|
|
167
|
-
return [...set].sort();
|
|
168
|
-
}
|
|
169
|
-
function splitPaths(csv) {
|
|
170
|
-
return csv ? normalizePaths(csv.split(",")) : [];
|
|
171
|
-
}
|
|
172
|
-
function joinPaths(paths) {
|
|
173
|
-
const normalized = normalizePaths(paths);
|
|
174
|
-
return normalized.length > 0 ? normalized.join(",") : void 0;
|
|
175
|
-
}
|
|
176
|
-
function validateExpandPath(collectionName, targets, path) {
|
|
177
|
-
if (!targets) {
|
|
178
|
-
throw new Error(
|
|
179
|
-
`Cannot expand "${path}" on collection "${collectionName}": no relations declared`
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
let current = targets;
|
|
183
|
-
for (const segment of path.split(".")) {
|
|
184
|
-
const next = current?.[segment];
|
|
185
|
-
if (!next) {
|
|
186
|
-
throw new Error(
|
|
187
|
-
`Cannot expand "${path}" on collection "${collectionName}": segment "${segment}" is not a declared relation`
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
current = next.relationTargets;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
139
|
function escapeValue(value) {
|
|
194
140
|
if (value === null) {
|
|
195
141
|
return "null";
|
|
@@ -289,22 +235,55 @@ function convertToPocketBaseSort(orderBy) {
|
|
|
289
235
|
|
|
290
236
|
// src/build-collection.ts
|
|
291
237
|
var viewPaths = /* @__PURE__ */ new WeakMap();
|
|
238
|
+
var pendingFilings = /* @__PURE__ */ new WeakMap();
|
|
239
|
+
function registerPendingFiling(target, field, settles) {
|
|
240
|
+
let byField = pendingFilings.get(target);
|
|
241
|
+
if (!byField) {
|
|
242
|
+
byField = /* @__PURE__ */ new Map();
|
|
243
|
+
pendingFilings.set(target, byField);
|
|
244
|
+
}
|
|
245
|
+
let pending = byField.get(field);
|
|
246
|
+
if (!pending) {
|
|
247
|
+
pending = [];
|
|
248
|
+
byField.set(field, pending);
|
|
249
|
+
}
|
|
250
|
+
pending.push(settles);
|
|
251
|
+
return () => {
|
|
252
|
+
const index = pending.indexOf(settles);
|
|
253
|
+
if (index !== -1) pending.splice(index, 1);
|
|
254
|
+
if (pending.length === 0) byField.delete(field);
|
|
255
|
+
if (byField.size === 0) pendingFilings.delete(target);
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async function awaitPendingFiling(target, field) {
|
|
259
|
+
const pending = pendingFilings.get(target)?.get(field);
|
|
260
|
+
if (!pending || pending.length === 0) return;
|
|
261
|
+
await Promise.all(pending);
|
|
262
|
+
}
|
|
263
|
+
function registerMarkInvalidationEvents(collection, clearMarks) {
|
|
264
|
+
collection.onStatusChange((event) => {
|
|
265
|
+
if (event.status === "cleaned-up") clearMarks();
|
|
266
|
+
});
|
|
267
|
+
collection.onTruncate(() => clearMarks());
|
|
268
|
+
}
|
|
292
269
|
function buildCollection(input) {
|
|
293
270
|
const { pb, queryClient, factoryOptions, collectionName, options } = input;
|
|
294
271
|
const relationTargets = options?.relations;
|
|
295
|
-
const
|
|
296
|
-
for (const path of
|
|
272
|
+
const alwaysFetch = normalizePaths(options?.alwaysFetchRelations ?? []);
|
|
273
|
+
for (const path of alwaysFetch) validateExpandPath(collectionName, relationTargets, path);
|
|
297
274
|
const syncMode = options?.syncMode ?? "eager";
|
|
298
275
|
const requestedExpand = /* @__PURE__ */ new Set();
|
|
299
276
|
function toRequest(opts) {
|
|
300
277
|
const request = {};
|
|
301
|
-
const
|
|
278
|
+
const subset = subsetFromWhere(opts?.where);
|
|
279
|
+
const filter = subset ? void 0 : convertToPocketBaseFilter(opts?.where);
|
|
302
280
|
const sort = convertToPocketBaseSort(opts?.orderBy);
|
|
303
|
-
const
|
|
281
|
+
const expand = joinPaths(opts?.expand ?? []);
|
|
282
|
+
if (subset) request.subset = subset;
|
|
304
283
|
if (filter) request.filter = filter;
|
|
305
284
|
if (sort) request.sort = sort;
|
|
306
285
|
if (opts?.limit) request.limit = opts.limit;
|
|
307
|
-
if (
|
|
286
|
+
if (expand) request.expand = expand;
|
|
308
287
|
return request;
|
|
309
288
|
}
|
|
310
289
|
function queryKeyFor(opts) {
|
|
@@ -313,18 +292,46 @@ function buildCollection(input) {
|
|
|
313
292
|
}
|
|
314
293
|
function activeExpand(request) {
|
|
315
294
|
return joinPaths([
|
|
316
|
-
...
|
|
295
|
+
...alwaysFetch,
|
|
317
296
|
...splitPaths(request.expand),
|
|
318
297
|
...syncMode === "eager" ? requestedExpand : []
|
|
319
298
|
]);
|
|
320
299
|
}
|
|
300
|
+
function backRelationTargetsFor(request) {
|
|
301
|
+
if (!relationTargets) return [];
|
|
302
|
+
const results = [];
|
|
303
|
+
for (const key of splitPaths(activeExpand(request)).map((path) => path.split(".")[0])) {
|
|
304
|
+
const target = relationTargets[key];
|
|
305
|
+
const via = target && parseViaKey(key);
|
|
306
|
+
if (target && via) results.push({ target, field: via.field });
|
|
307
|
+
}
|
|
308
|
+
return results;
|
|
309
|
+
}
|
|
310
|
+
function stripFetchedRelations(items, expandString) {
|
|
311
|
+
const heads = new Set(splitPaths(expandString).map((path) => path.split(".")[0]));
|
|
312
|
+
if (heads.size === 0) return items;
|
|
313
|
+
return items.map((item) => {
|
|
314
|
+
const { expand, ...plain } = item;
|
|
315
|
+
if (!expand) return item;
|
|
316
|
+
const kept = Object.fromEntries(
|
|
317
|
+
Object.entries(expand).filter(([key]) => !heads.has(key))
|
|
318
|
+
);
|
|
319
|
+
return Object.keys(kept).length > 0 ? { ...plain, expand: kept } : plain;
|
|
320
|
+
});
|
|
321
|
+
}
|
|
321
322
|
const ignoreAutoCancellation = options?.ignoreAutoCancellation ?? true;
|
|
322
323
|
const refetchOnMutation = options?.refetchOnMutation ?? false;
|
|
323
324
|
function eagerSyncInFlight(target) {
|
|
324
325
|
return target.status !== void 0 && target.status !== "idle" && target.status !== "cleaned-up";
|
|
325
326
|
}
|
|
327
|
+
function withoutExpand(values) {
|
|
328
|
+
return values.map((value) => {
|
|
329
|
+
const { expand: _expand, ...plain } = value;
|
|
330
|
+
return plain;
|
|
331
|
+
});
|
|
332
|
+
}
|
|
326
333
|
async function upsertInto(key, target, values) {
|
|
327
|
-
if (!target.utils) return;
|
|
334
|
+
if (!target.utils) return false;
|
|
328
335
|
if (!target.isReady()) {
|
|
329
336
|
if (target.config?.syncMode === "on-demand") {
|
|
330
337
|
await target._sync.startSync();
|
|
@@ -334,48 +341,148 @@ function buildCollection(input) {
|
|
|
334
341
|
logger.warn(
|
|
335
342
|
`not syncing ${key} on ${collectionName} because store is not yet ready`
|
|
336
343
|
);
|
|
337
|
-
return;
|
|
344
|
+
return false;
|
|
338
345
|
}
|
|
339
346
|
}
|
|
340
|
-
target.utils.writeUpsert(values);
|
|
347
|
+
target.utils.writeUpsert(withoutExpand(values));
|
|
348
|
+
return true;
|
|
341
349
|
}
|
|
342
|
-
async function upsertExpandedField(key, value, targets) {
|
|
350
|
+
async function upsertExpandedField(key, value, targets, parentId) {
|
|
343
351
|
const target = targets[key];
|
|
344
352
|
if (!target) {
|
|
345
353
|
logger.debug("No relation target for expanded field", { collectionName, key });
|
|
346
354
|
return;
|
|
347
355
|
}
|
|
348
356
|
const values = Array.isArray(value) ? value : [value];
|
|
349
|
-
await upsertInto(key, target, values);
|
|
357
|
+
const filed = await upsertInto(key, target, values);
|
|
350
358
|
await upsertExpanded(values, target.relationTargets);
|
|
359
|
+
if (filed && parentId !== void 0) markFiledSubset(target, key, values, parentId);
|
|
351
360
|
}
|
|
352
361
|
async function upsertExpanded(records, targets) {
|
|
353
362
|
if (!targets) return;
|
|
354
363
|
for (const record of records) {
|
|
355
364
|
const expandData = record.expand;
|
|
356
365
|
if (!expandData) continue;
|
|
366
|
+
const id = record.id;
|
|
367
|
+
const parentId = typeof id === "string" ? id : void 0;
|
|
357
368
|
for (const [key, value] of Object.entries(expandData)) {
|
|
358
|
-
await upsertExpandedField(key, value, targets);
|
|
369
|
+
await upsertExpandedField(key, value, targets, parentId);
|
|
359
370
|
}
|
|
360
371
|
}
|
|
361
372
|
}
|
|
373
|
+
function subsetFilter({ field, values }) {
|
|
374
|
+
return values.map((value) => `${field} = "${value.replace(/"/g, '\\"')}"`).join(" || ");
|
|
375
|
+
}
|
|
376
|
+
const loadedSubsets = /* @__PURE__ */ new Map();
|
|
377
|
+
function markSubsetLoaded(field, value) {
|
|
378
|
+
let values = loadedSubsets.get(field);
|
|
379
|
+
if (!values) {
|
|
380
|
+
values = /* @__PURE__ */ new Set();
|
|
381
|
+
loadedSubsets.set(field, values);
|
|
382
|
+
}
|
|
383
|
+
values.add(value);
|
|
384
|
+
}
|
|
385
|
+
function loadedSubsetCount() {
|
|
386
|
+
let count = 0;
|
|
387
|
+
for (const values of loadedSubsets.values()) count += values.size;
|
|
388
|
+
return count;
|
|
389
|
+
}
|
|
390
|
+
function subsetIsLoaded({ field, values }) {
|
|
391
|
+
const marked = loadedSubsets.get(field);
|
|
392
|
+
return marked !== void 0 && values.every((value) => marked.has(value));
|
|
393
|
+
}
|
|
394
|
+
function forgetMarkedValue(marked, value) {
|
|
395
|
+
if (typeof value === "string") return marked.delete(value) ? [value] : [];
|
|
396
|
+
if (!Array.isArray(value)) return [];
|
|
397
|
+
return value.filter((item) => typeof item === "string" && marked.delete(item));
|
|
398
|
+
}
|
|
399
|
+
function matchesMarkedSubset(query, isMarked) {
|
|
400
|
+
if (query.queryKey[0] !== collectionName) return false;
|
|
401
|
+
const request = query.queryKey[1];
|
|
402
|
+
if (!request?.subset) return false;
|
|
403
|
+
const { field, values } = request.subset;
|
|
404
|
+
return values.some((value) => isMarked(field, value));
|
|
405
|
+
}
|
|
406
|
+
function invalidateMarkedQueries(isMarked) {
|
|
407
|
+
void queryClient.invalidateQueries({ predicate: (query) => matchesMarkedSubset(query, isMarked) }).catch(
|
|
408
|
+
(error) => logger.error("Failed to invalidate marked subset queries", {
|
|
409
|
+
collectionName,
|
|
410
|
+
error
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
function invalidateForgottenSubset(field, values) {
|
|
415
|
+
invalidateMarkedQueries(
|
|
416
|
+
(queryField, value) => queryField === field && values.includes(value)
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
function invalidateAllMarkedSubsets() {
|
|
420
|
+
invalidateMarkedQueries((field, value) => loadedSubsets.get(field)?.has(value) ?? false);
|
|
421
|
+
}
|
|
422
|
+
function forgetMarksFor(row) {
|
|
423
|
+
if (!row || typeof row !== "object") return;
|
|
424
|
+
for (const [field, marked] of loadedSubsets) {
|
|
425
|
+
const forgotten = forgetMarkedValue(marked, row[field]);
|
|
426
|
+
if (forgotten.length > 0) {
|
|
427
|
+
queueMicrotask(() => invalidateForgottenSubset(field, forgotten));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function rowsFromStore(ids) {
|
|
432
|
+
const rows = [];
|
|
433
|
+
for (const id of ids) {
|
|
434
|
+
const row = collection._state.syncedData.get(id);
|
|
435
|
+
if (!row) return void 0;
|
|
436
|
+
rows.push(row);
|
|
437
|
+
}
|
|
438
|
+
return rows;
|
|
439
|
+
}
|
|
440
|
+
function idSubsetFromStore(subset, limit) {
|
|
441
|
+
const present = rowsFromStore(subset.values);
|
|
442
|
+
if (!present) return void 0;
|
|
443
|
+
return limit ? present.slice(0, limit) : present;
|
|
444
|
+
}
|
|
445
|
+
function fieldSubsetFromStore(subset, limit) {
|
|
446
|
+
if (!subsetIsLoaded(subset)) return void 0;
|
|
447
|
+
const wanted = new Set(subset.values);
|
|
448
|
+
const rows = [...collection._state.syncedData.values()].filter(
|
|
449
|
+
(row) => matchesSubset(row, subset.field, wanted)
|
|
450
|
+
);
|
|
451
|
+
return limit ? rows.slice(0, limit) : rows;
|
|
452
|
+
}
|
|
453
|
+
function servedFromStore(request) {
|
|
454
|
+
const { subset, sort, limit } = request;
|
|
455
|
+
if (!subset) return void 0;
|
|
456
|
+
if (subset.values.length === 0) return [];
|
|
457
|
+
if (request.expand) return void 0;
|
|
458
|
+
if (sort && limit) return void 0;
|
|
459
|
+
return subset.field === "id" ? idSubsetFromStore(subset, limit) : fieldSubsetFromStore(subset, limit);
|
|
460
|
+
}
|
|
362
461
|
async function fetchItems(request) {
|
|
363
|
-
const
|
|
364
|
-
|
|
462
|
+
const served = servedFromStore(request);
|
|
463
|
+
if (served) return served;
|
|
464
|
+
if (request.subset && request.subset.field !== "id" && backRelationTargetsFor(request).length === 0) {
|
|
465
|
+
await awaitPendingFiling(collection, request.subset.field);
|
|
466
|
+
const servedAfterFiling = servedFromStore(request);
|
|
467
|
+
if (servedAfterFiling) return servedAfterFiling;
|
|
468
|
+
}
|
|
469
|
+
const { sort, limit, subset } = request;
|
|
470
|
+
const filter = subset ? subsetFilter(subset) : request.filter;
|
|
471
|
+
const expand = activeExpand(request);
|
|
365
472
|
if (limit) {
|
|
366
473
|
const result = await pb.collection(collectionName).getList(1, limit, {
|
|
367
474
|
filter,
|
|
368
475
|
sort,
|
|
369
476
|
skipTotal: true,
|
|
370
477
|
// Optimize by skipping total count
|
|
371
|
-
expand
|
|
478
|
+
expand
|
|
372
479
|
});
|
|
373
480
|
return result.items;
|
|
374
481
|
}
|
|
375
482
|
return await pb.collection(collectionName).getFullList({
|
|
376
483
|
filter,
|
|
377
484
|
sort,
|
|
378
|
-
expand
|
|
485
|
+
expand
|
|
379
486
|
});
|
|
380
487
|
}
|
|
381
488
|
const inFlightConfirmedIds = /* @__PURE__ */ new Set();
|
|
@@ -408,6 +515,13 @@ function buildCollection(input) {
|
|
|
408
515
|
async function fetchRecords(request, queryKey) {
|
|
409
516
|
const confirmedMidFlight = /* @__PURE__ */ new Set();
|
|
410
517
|
inFlightConfirmedIds.add(confirmedMidFlight);
|
|
518
|
+
let settleFiling = () => void 0;
|
|
519
|
+
const filed = new Promise((resolve) => {
|
|
520
|
+
settleFiling = resolve;
|
|
521
|
+
});
|
|
522
|
+
const unregisterFilings = backRelationTargetsFor(request).map(
|
|
523
|
+
({ target, field }) => registerPendingFiling(target, field, filed)
|
|
524
|
+
);
|
|
411
525
|
try {
|
|
412
526
|
let items;
|
|
413
527
|
try {
|
|
@@ -422,50 +536,14 @@ function buildCollection(input) {
|
|
|
422
536
|
throw error;
|
|
423
537
|
}
|
|
424
538
|
await upsertExpanded(items, relationTargets);
|
|
425
|
-
|
|
426
|
-
|
|
539
|
+
return withRowsConfirmedMidFlight(
|
|
540
|
+
stripFetchedRelations(items, activeExpand(request)),
|
|
541
|
+
confirmedMidFlight
|
|
542
|
+
);
|
|
427
543
|
} finally {
|
|
428
544
|
inFlightConfirmedIds.delete(confirmedMidFlight);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
function writeExpandedRows(items) {
|
|
432
|
-
if (!collection.utils || !collection.isReady()) return;
|
|
433
|
-
collection.utils.writeUpsert(items);
|
|
434
|
-
}
|
|
435
|
-
function patchRowFields(row, fields, action, record) {
|
|
436
|
-
let current;
|
|
437
|
-
for (const field of fields) {
|
|
438
|
-
const next = patchEmbedded(current ?? row, field, action, record);
|
|
439
|
-
if (next) current = next;
|
|
440
|
-
}
|
|
441
|
-
return current;
|
|
442
|
-
}
|
|
443
|
-
function patchedRows(fields, action, record, visited) {
|
|
444
|
-
const patched = [];
|
|
445
|
-
for (const row of collection._state.syncedData.values()) {
|
|
446
|
-
const id = row.id;
|
|
447
|
-
if (typeof id !== "string") continue;
|
|
448
|
-
const key = `${collectionName}:${id}`;
|
|
449
|
-
if (visited.has(key)) continue;
|
|
450
|
-
const next = patchRowFields(row, fields, action, record);
|
|
451
|
-
if (!next) continue;
|
|
452
|
-
visited.add(key);
|
|
453
|
-
patched.push(next);
|
|
454
|
-
}
|
|
455
|
-
return patched;
|
|
456
|
-
}
|
|
457
|
-
function applyRelatedChange(fields, action, record, visited) {
|
|
458
|
-
if (!collection.utils || !collection.isReady()) return;
|
|
459
|
-
const patched = patchedRows(fields, action, record, visited);
|
|
460
|
-
if (patched.length === 0) return;
|
|
461
|
-
writeOwn(() => collection.utils.writeUpsert(patched));
|
|
462
|
-
for (const row of patched) {
|
|
463
|
-
propagateRelatedChange(
|
|
464
|
-
collection,
|
|
465
|
-
"update",
|
|
466
|
-
row,
|
|
467
|
-
visited
|
|
468
|
-
);
|
|
545
|
+
settleFiling();
|
|
546
|
+
for (const unregister of unregisterFilings) unregister();
|
|
469
547
|
}
|
|
470
548
|
}
|
|
471
549
|
const queryCollectionConfig = queryCollectionOptions({
|
|
@@ -575,22 +653,14 @@ function buildCollection(input) {
|
|
|
575
653
|
const paths = (opts.subscription && viewPaths.get(opts.subscription)) ?? subscribingViewPaths;
|
|
576
654
|
return paths ? { ...opts, expand: paths } : opts;
|
|
577
655
|
}
|
|
578
|
-
function mergedWriteValue(op) {
|
|
579
|
-
const key = syncedWriteKey(op);
|
|
580
|
-
const existing = key === null ? void 0 : collection._state.syncedData.get(key);
|
|
581
|
-
return mergeExpand(op.value, existing);
|
|
582
|
-
}
|
|
583
656
|
const innerSync = collectionOptions.sync.sync;
|
|
584
657
|
collectionOptions.sync = {
|
|
585
658
|
...collectionOptions.sync,
|
|
586
659
|
sync: (params) => {
|
|
587
660
|
const guardedWrite = (message) => {
|
|
588
661
|
const op = message;
|
|
662
|
+
if (op.type === "delete" && !applyingOwnWrite) forgetMarksFor(op.value);
|
|
589
663
|
if (shouldDropSyncedWrite(op)) return;
|
|
590
|
-
if ((op.type === "insert" || op.type === "update") && op.value && typeof op.value === "object") {
|
|
591
|
-
const merged = mergedWriteValue(op);
|
|
592
|
-
if (merged !== op.value) return params.write({ ...message, value: merged });
|
|
593
|
-
}
|
|
594
664
|
return params.write(message);
|
|
595
665
|
};
|
|
596
666
|
const res = innerSync({ ...params, write: guardedWrite });
|
|
@@ -604,14 +674,6 @@ function buildCollection(input) {
|
|
|
604
674
|
}
|
|
605
675
|
};
|
|
606
676
|
const collection = createCollection$1(collectionOptions);
|
|
607
|
-
const relationDependents = [];
|
|
608
|
-
for (const [field, target] of Object.entries(relationTargets ?? {})) {
|
|
609
|
-
target.relationDependents ?? (target.relationDependents = []);
|
|
610
|
-
target.relationDependents.push({
|
|
611
|
-
field,
|
|
612
|
-
parent: collection
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
677
|
const views = /* @__PURE__ */ new Map();
|
|
616
678
|
function createView(paths) {
|
|
617
679
|
const view = Object.create(collection);
|
|
@@ -630,18 +692,18 @@ function buildCollection(input) {
|
|
|
630
692
|
}
|
|
631
693
|
}
|
|
632
694
|
},
|
|
633
|
-
|
|
695
|
+
fetchRelations: {
|
|
634
696
|
value: () => {
|
|
635
|
-
throw new Error(`A view of "${collectionName}" cannot
|
|
697
|
+
throw new Error(`A view of "${collectionName}" cannot fetch further relations`);
|
|
636
698
|
}
|
|
637
699
|
}
|
|
638
700
|
});
|
|
639
701
|
return view;
|
|
640
702
|
}
|
|
641
|
-
function
|
|
703
|
+
function fetchRelations(...paths) {
|
|
642
704
|
for (const path of paths) validateExpandPath(collectionName, relationTargets, path);
|
|
643
|
-
const all = normalizePaths([...
|
|
644
|
-
if (all.every((path) =>
|
|
705
|
+
const all = normalizePaths([...alwaysFetch, ...paths]);
|
|
706
|
+
if (all.every((path) => alwaysFetch.includes(path))) return collection;
|
|
645
707
|
const key = all.join(",");
|
|
646
708
|
let view = views.get(key);
|
|
647
709
|
if (!view) {
|
|
@@ -698,15 +760,16 @@ function buildCollection(input) {
|
|
|
698
760
|
if (!collection.utils) return;
|
|
699
761
|
markEchoPresence(event);
|
|
700
762
|
if (isStaleEcho(event)) return;
|
|
763
|
+
const [stored] = stripFetchedRelations([event.record], pendingSubscribeExpand());
|
|
701
764
|
try {
|
|
702
765
|
writeOwn(
|
|
703
766
|
() => collection.utils.writeBatch(() => {
|
|
704
767
|
switch (event.action) {
|
|
705
768
|
case "create":
|
|
706
|
-
collection.utils.writeInsert(
|
|
769
|
+
collection.utils.writeInsert(stored);
|
|
707
770
|
break;
|
|
708
771
|
case "update":
|
|
709
|
-
collection.utils.writeUpsert(
|
|
772
|
+
collection.utils.writeUpsert(stored);
|
|
710
773
|
break;
|
|
711
774
|
case "delete":
|
|
712
775
|
if (event.record && "id" in event.record) {
|
|
@@ -734,15 +797,9 @@ function buildCollection(input) {
|
|
|
734
797
|
})
|
|
735
798
|
);
|
|
736
799
|
}
|
|
737
|
-
propagateRelatedChange(
|
|
738
|
-
collection,
|
|
739
|
-
event.action,
|
|
740
|
-
event.record,
|
|
741
|
-
/* @__PURE__ */ new Set()
|
|
742
|
-
);
|
|
743
800
|
};
|
|
744
801
|
function pendingSubscribeExpand() {
|
|
745
|
-
return joinPaths([...
|
|
802
|
+
return joinPaths([...alwaysFetch, ...requestedExpand]);
|
|
746
803
|
}
|
|
747
804
|
function activeExpandTargets() {
|
|
748
805
|
const targets = /* @__PURE__ */ new Set();
|
|
@@ -789,11 +846,11 @@ function buildCollection(input) {
|
|
|
789
846
|
}
|
|
790
847
|
function realtimeSubscribeOptions() {
|
|
791
848
|
const base = factoryOptions?.subscribeOptions?.();
|
|
792
|
-
const
|
|
849
|
+
const expand = joinPaths([
|
|
793
850
|
...splitPaths(pendingSubscribeExpand()),
|
|
794
851
|
...splitPaths(base?.expand)
|
|
795
852
|
]);
|
|
796
|
-
return
|
|
853
|
+
return expand ? { ...base, expand } : base;
|
|
797
854
|
}
|
|
798
855
|
const doStartSubscription = async () => {
|
|
799
856
|
if (isSubscribed) return;
|
|
@@ -821,7 +878,11 @@ function buildCollection(input) {
|
|
|
821
878
|
}
|
|
822
879
|
};
|
|
823
880
|
const doStopSubscription = async (releaseTargets = true) => {
|
|
824
|
-
if (releaseTargets)
|
|
881
|
+
if (releaseTargets) {
|
|
882
|
+
syncHeldSubscriptions(/* @__PURE__ */ new Set());
|
|
883
|
+
invalidateAllMarkedSubsets();
|
|
884
|
+
loadedSubsets.clear();
|
|
885
|
+
}
|
|
825
886
|
if (!isSubscribed || !unsubscribeFn) return;
|
|
826
887
|
try {
|
|
827
888
|
await unsubscribeFn();
|
|
@@ -909,15 +970,22 @@ function buildCollection(input) {
|
|
|
909
970
|
}
|
|
910
971
|
}
|
|
911
972
|
);
|
|
973
|
+
registerMarkInvalidationEvents(
|
|
974
|
+
{
|
|
975
|
+
onStatusChange: (callback) => collection.on("status:change", callback),
|
|
976
|
+
onTruncate: (callback) => collection.on("truncate", callback)
|
|
977
|
+
},
|
|
978
|
+
() => loadedSubsets.clear()
|
|
979
|
+
);
|
|
912
980
|
Object.assign(collection, {
|
|
913
981
|
collectionName,
|
|
914
982
|
relationTargets,
|
|
915
983
|
waitForSubscription,
|
|
916
984
|
isSubscribed: () => isSubscribed,
|
|
917
985
|
heldRelationTargetCount: () => heldTargetSubscriptions.size,
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
986
|
+
markSubsetLoaded,
|
|
987
|
+
loadedSubsetCount,
|
|
988
|
+
fetchRelations
|
|
921
989
|
});
|
|
922
990
|
return collection;
|
|
923
991
|
}
|
|
@@ -946,5 +1014,5 @@ function newRecordId() {
|
|
|
946
1014
|
}
|
|
947
1015
|
|
|
948
1016
|
export { createCollection, newRecordId, resetLogger, setLogger };
|
|
949
|
-
//# sourceMappingURL=chunk-
|
|
950
|
-
//# sourceMappingURL=chunk-
|
|
1017
|
+
//# sourceMappingURL=chunk-LT7BDI37.js.map
|
|
1018
|
+
//# sourceMappingURL=chunk-LT7BDI37.js.map
|