pbtsdb 0.7.2 → 0.8.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 +74 -28
- package/dist/chunk-KMR6I4ZC.js +950 -0
- package/dist/chunk-KMR6I4ZC.js.map +1 -0
- package/dist/core.d.ts +138 -60
- package/dist/core.js +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/llms.txt +11 -7
- package/package.json +4 -4
- package/dist/chunk-7DHFWXB3.js +0 -510
- package/dist/chunk-7DHFWXB3.js.map +0 -1
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
import { BTreeIndex, createCollection as createCollection$1, parseWhereExpression, parseOrderByExpression, deepEquals } from '@tanstack/db';
|
|
2
|
+
export { BTreeIndex, BasicIndex, ReverseIndex, createEffect, toArray } from '@tanstack/db';
|
|
3
|
+
import { queryCollectionOptions, DeleteOperationItemNotFoundError } from '@tanstack/query-db-collection';
|
|
4
|
+
|
|
5
|
+
// src/core.ts
|
|
6
|
+
|
|
7
|
+
// src/expand-merge.ts
|
|
8
|
+
function sameRelationValue(a, b) {
|
|
9
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
10
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
11
|
+
}
|
|
12
|
+
return a === b;
|
|
13
|
+
}
|
|
14
|
+
function storedIsNewer(incoming, stored) {
|
|
15
|
+
const a = incoming;
|
|
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;
|
|
20
|
+
}
|
|
21
|
+
function newerEntry(incoming, stored) {
|
|
22
|
+
if (Array.isArray(incoming) && Array.isArray(stored)) {
|
|
23
|
+
let changed = false;
|
|
24
|
+
const merged = incoming.map((entry) => {
|
|
25
|
+
const match = stored.find(
|
|
26
|
+
(candidate) => candidate?.id === entry?.id
|
|
27
|
+
);
|
|
28
|
+
if (!storedIsNewer(entry, match)) return entry;
|
|
29
|
+
changed = true;
|
|
30
|
+
return match;
|
|
31
|
+
});
|
|
32
|
+
return changed ? merged : incoming;
|
|
33
|
+
}
|
|
34
|
+
return storedIsNewer(incoming, stored) ? stored : incoming;
|
|
35
|
+
}
|
|
36
|
+
function mergeExpand(incoming, existing) {
|
|
37
|
+
const stored = existing?.expand;
|
|
38
|
+
if (!stored) return incoming;
|
|
39
|
+
const row = incoming;
|
|
40
|
+
const merged = { ...row.expand ?? {} };
|
|
41
|
+
let carried = false;
|
|
42
|
+
for (const [relation, value] of Object.entries(stored)) {
|
|
43
|
+
if (relation in merged) {
|
|
44
|
+
const kept = newerEntry(merged[relation], value);
|
|
45
|
+
if (kept === merged[relation]) continue;
|
|
46
|
+
merged[relation] = kept;
|
|
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;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/logger.ts
|
|
58
|
+
var defaultLogger = {
|
|
59
|
+
debug: (msg, context) => {
|
|
60
|
+
if (process.env.NODE_ENV === "development") {
|
|
61
|
+
console.debug(`[pbtsdb] ${msg}`, context || "");
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
info: (msg, context) => {
|
|
65
|
+
console.info(`[pbtsdb] ${msg}`, context || "");
|
|
66
|
+
},
|
|
67
|
+
warn: (msg, context) => {
|
|
68
|
+
console.warn(`[pbtsdb] ${msg}`, context || "");
|
|
69
|
+
},
|
|
70
|
+
error: (msg, context) => {
|
|
71
|
+
console.error(`[pbtsdb] ${msg}`, context || "");
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var currentLogger = defaultLogger;
|
|
75
|
+
var logger = {
|
|
76
|
+
debug: (msg, context) => currentLogger.debug(msg, context),
|
|
77
|
+
info: (msg, context) => currentLogger.info(msg, context),
|
|
78
|
+
warn: (msg, context) => currentLogger.warn(msg, context),
|
|
79
|
+
error: (msg, context) => currentLogger.error(msg, context)
|
|
80
|
+
};
|
|
81
|
+
function setLogger(customLogger) {
|
|
82
|
+
currentLogger = customLogger;
|
|
83
|
+
}
|
|
84
|
+
function resetLogger() {
|
|
85
|
+
currentLogger = defaultLogger;
|
|
86
|
+
}
|
|
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
|
+
function escapeValue(value) {
|
|
194
|
+
if (value === null) {
|
|
195
|
+
return "null";
|
|
196
|
+
}
|
|
197
|
+
if (typeof value === "boolean") {
|
|
198
|
+
return value ? "true" : "false";
|
|
199
|
+
}
|
|
200
|
+
if (typeof value === "number") {
|
|
201
|
+
return value.toString();
|
|
202
|
+
}
|
|
203
|
+
if (typeof value === "string") {
|
|
204
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
205
|
+
}
|
|
206
|
+
if (value instanceof Date) {
|
|
207
|
+
return `"${value.toISOString()}"`;
|
|
208
|
+
}
|
|
209
|
+
if (Array.isArray(value)) {
|
|
210
|
+
return `[${value.map(escapeValue).join(",")}]`;
|
|
211
|
+
}
|
|
212
|
+
return `"${String(value)}"`;
|
|
213
|
+
}
|
|
214
|
+
function fieldPathToString(path) {
|
|
215
|
+
return path.join(".");
|
|
216
|
+
}
|
|
217
|
+
function convertToPocketBaseFilter(where) {
|
|
218
|
+
if (!where) {
|
|
219
|
+
return void 0;
|
|
220
|
+
}
|
|
221
|
+
const result = parseWhereExpression(where, {
|
|
222
|
+
handlers: {
|
|
223
|
+
eq: (field, value) => {
|
|
224
|
+
return `${fieldPathToString(field)} = ${escapeValue(value)}`;
|
|
225
|
+
},
|
|
226
|
+
gt: (field, value) => {
|
|
227
|
+
return `${fieldPathToString(field)} > ${escapeValue(value)}`;
|
|
228
|
+
},
|
|
229
|
+
gte: (field, value) => {
|
|
230
|
+
return `${fieldPathToString(field)} >= ${escapeValue(value)}`;
|
|
231
|
+
},
|
|
232
|
+
lt: (field, value) => {
|
|
233
|
+
return `${fieldPathToString(field)} < ${escapeValue(value)}`;
|
|
234
|
+
},
|
|
235
|
+
lte: (field, value) => {
|
|
236
|
+
return `${fieldPathToString(field)} <= ${escapeValue(value)}`;
|
|
237
|
+
},
|
|
238
|
+
and: (...conditions) => {
|
|
239
|
+
if (conditions.length === 0) return "";
|
|
240
|
+
if (conditions.length === 1) return conditions[0];
|
|
241
|
+
return `(${conditions.join(" && ")})`;
|
|
242
|
+
},
|
|
243
|
+
or: (...conditions) => {
|
|
244
|
+
if (conditions.length === 0) return "";
|
|
245
|
+
if (conditions.length === 1) return conditions[0];
|
|
246
|
+
return `(${conditions.join(" || ")})`;
|
|
247
|
+
},
|
|
248
|
+
not: (condition) => {
|
|
249
|
+
return `!(${condition})`;
|
|
250
|
+
},
|
|
251
|
+
in: (field, values) => {
|
|
252
|
+
const valueArray = Array.isArray(values) ? values : [values];
|
|
253
|
+
const uniqueValues = [...new Set(valueArray)];
|
|
254
|
+
const fieldStr = fieldPathToString(field);
|
|
255
|
+
const conditions = uniqueValues.map((v) => `${fieldStr} = ${escapeValue(v)}`);
|
|
256
|
+
return conditions.length > 1 ? `(${conditions.join(" || ")})` : conditions[0];
|
|
257
|
+
},
|
|
258
|
+
like: (field, value) => {
|
|
259
|
+
return `${fieldPathToString(field)} ~ ${escapeValue(value)}`;
|
|
260
|
+
},
|
|
261
|
+
isNull: (field) => {
|
|
262
|
+
return `${fieldPathToString(field)} = null`;
|
|
263
|
+
},
|
|
264
|
+
isUndefined: (field) => {
|
|
265
|
+
return `${fieldPathToString(field)} = null`;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
onUnknownOperator: (operator, _args) => {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Unsupported operator '${operator}' for PocketBase filter conversion. Supported operators: eq, gt, gte, lt, lte, in, like, and, or, not, isNull, isUndefined`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
return result || void 0;
|
|
275
|
+
}
|
|
276
|
+
function convertToPocketBaseSort(orderBy) {
|
|
277
|
+
if (!orderBy) {
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
280
|
+
const sorts = parseOrderByExpression(orderBy);
|
|
281
|
+
if (sorts.length === 0) {
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
return sorts.map((sort) => {
|
|
285
|
+
const field = fieldPathToString(sort.field);
|
|
286
|
+
return sort.direction === "desc" ? `-${field}` : field;
|
|
287
|
+
}).join(",");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/build-collection.ts
|
|
291
|
+
var viewPaths = /* @__PURE__ */ new WeakMap();
|
|
292
|
+
function buildCollection(input) {
|
|
293
|
+
const { pb, queryClient, factoryOptions, collectionName, options } = input;
|
|
294
|
+
const relationTargets = options?.relations;
|
|
295
|
+
const alwaysExpand = normalizePaths(options?.alwaysExpand ?? []);
|
|
296
|
+
for (const path of alwaysExpand) validateExpandPath(collectionName, relationTargets, path);
|
|
297
|
+
const syncMode = options?.syncMode ?? "eager";
|
|
298
|
+
const requestedExpand = /* @__PURE__ */ new Set();
|
|
299
|
+
function toRequest(opts) {
|
|
300
|
+
const request = {};
|
|
301
|
+
const filter = convertToPocketBaseFilter(opts?.where);
|
|
302
|
+
const sort = convertToPocketBaseSort(opts?.orderBy);
|
|
303
|
+
const expand2 = joinPaths(opts?.expand ?? []);
|
|
304
|
+
if (filter) request.filter = filter;
|
|
305
|
+
if (sort) request.sort = sort;
|
|
306
|
+
if (opts?.limit) request.limit = opts.limit;
|
|
307
|
+
if (expand2) request.expand = expand2;
|
|
308
|
+
return request;
|
|
309
|
+
}
|
|
310
|
+
function queryKeyFor(opts) {
|
|
311
|
+
const request = toRequest(opts);
|
|
312
|
+
return Object.keys(request).length === 0 ? [collectionName] : [collectionName, request];
|
|
313
|
+
}
|
|
314
|
+
function activeExpand(request) {
|
|
315
|
+
return joinPaths([
|
|
316
|
+
...alwaysExpand,
|
|
317
|
+
...splitPaths(request.expand),
|
|
318
|
+
...syncMode === "eager" ? requestedExpand : []
|
|
319
|
+
]);
|
|
320
|
+
}
|
|
321
|
+
const ignoreAutoCancellation = options?.ignoreAutoCancellation ?? true;
|
|
322
|
+
const refetchOnMutation = options?.refetchOnMutation ?? false;
|
|
323
|
+
function eagerSyncInFlight(target) {
|
|
324
|
+
return target.status !== void 0 && target.status !== "idle" && target.status !== "cleaned-up";
|
|
325
|
+
}
|
|
326
|
+
async function upsertInto(key, target, values) {
|
|
327
|
+
if (!target.utils) return;
|
|
328
|
+
if (!target.isReady()) {
|
|
329
|
+
if (target.config?.syncMode === "on-demand") {
|
|
330
|
+
await target._sync.startSync();
|
|
331
|
+
} else if (target.preload && eagerSyncInFlight(target)) {
|
|
332
|
+
await target.preload();
|
|
333
|
+
} else {
|
|
334
|
+
logger.warn(
|
|
335
|
+
`not syncing ${key} on ${collectionName} because store is not yet ready`
|
|
336
|
+
);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
target.utils.writeUpsert(values);
|
|
341
|
+
}
|
|
342
|
+
async function upsertExpandedField(key, value, targets) {
|
|
343
|
+
const target = targets[key];
|
|
344
|
+
if (!target) {
|
|
345
|
+
logger.debug("No relation target for expanded field", { collectionName, key });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const values = Array.isArray(value) ? value : [value];
|
|
349
|
+
await upsertInto(key, target, values);
|
|
350
|
+
await upsertExpanded(values, target.relationTargets);
|
|
351
|
+
}
|
|
352
|
+
async function upsertExpanded(records, targets) {
|
|
353
|
+
if (!targets) return;
|
|
354
|
+
for (const record of records) {
|
|
355
|
+
const expandData = record.expand;
|
|
356
|
+
if (!expandData) continue;
|
|
357
|
+
for (const [key, value] of Object.entries(expandData)) {
|
|
358
|
+
await upsertExpandedField(key, value, targets);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async function fetchItems(request) {
|
|
363
|
+
const { filter, sort, limit } = request;
|
|
364
|
+
const expand2 = activeExpand(request);
|
|
365
|
+
if (limit) {
|
|
366
|
+
const result = await pb.collection(collectionName).getList(1, limit, {
|
|
367
|
+
filter,
|
|
368
|
+
sort,
|
|
369
|
+
skipTotal: true,
|
|
370
|
+
// Optimize by skipping total count
|
|
371
|
+
expand: expand2
|
|
372
|
+
});
|
|
373
|
+
return result.items;
|
|
374
|
+
}
|
|
375
|
+
return await pb.collection(collectionName).getFullList({
|
|
376
|
+
filter,
|
|
377
|
+
sort,
|
|
378
|
+
expand: expand2
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
const inFlightConfirmedIds = /* @__PURE__ */ new Set();
|
|
382
|
+
function markConfirmedPresent(records) {
|
|
383
|
+
if (inFlightConfirmedIds.size === 0) return;
|
|
384
|
+
for (const record of records) {
|
|
385
|
+
const id = record?.id;
|
|
386
|
+
if (typeof id !== "string") continue;
|
|
387
|
+
for (const confirmed of inFlightConfirmedIds) confirmed.add(id);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function withRowsConfirmedMidFlight(items, confirmedMidFlight) {
|
|
391
|
+
if (confirmedMidFlight.size === 0) return items;
|
|
392
|
+
const resultIds = new Set(
|
|
393
|
+
items.map((item) => item?.id)
|
|
394
|
+
);
|
|
395
|
+
const mergedIds = [...confirmedMidFlight].filter(
|
|
396
|
+
(id) => !resultIds.has(id) && collection._state.syncedData.has(id)
|
|
397
|
+
);
|
|
398
|
+
if (mergedIds.length === 0) return items;
|
|
399
|
+
logger.debug("Merging rows confirmed while fetch was in flight", {
|
|
400
|
+
collectionName,
|
|
401
|
+
ids: mergedIds
|
|
402
|
+
});
|
|
403
|
+
return [
|
|
404
|
+
...items,
|
|
405
|
+
...mergedIds.map((id) => collection._state.syncedData.get(id))
|
|
406
|
+
];
|
|
407
|
+
}
|
|
408
|
+
async function fetchRecords(request, queryKey) {
|
|
409
|
+
const confirmedMidFlight = /* @__PURE__ */ new Set();
|
|
410
|
+
inFlightConfirmedIds.add(confirmedMidFlight);
|
|
411
|
+
try {
|
|
412
|
+
let items;
|
|
413
|
+
try {
|
|
414
|
+
items = await fetchItems(request);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (ignoreAutoCancellation && error instanceof Error && error.message.includes("autocancelled")) {
|
|
417
|
+
return withRowsConfirmedMidFlight(
|
|
418
|
+
queryClient.getQueryData(queryKey) ?? [],
|
|
419
|
+
confirmedMidFlight
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
await upsertExpanded(items, relationTargets);
|
|
425
|
+
if (request.expand) writeExpandedRows(items);
|
|
426
|
+
return withRowsConfirmedMidFlight(items, confirmedMidFlight);
|
|
427
|
+
} finally {
|
|
428
|
+
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
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const queryCollectionConfig = queryCollectionOptions({
|
|
472
|
+
autoIndex: "eager",
|
|
473
|
+
defaultIndexType: BTreeIndex,
|
|
474
|
+
...options?.collectionOptions,
|
|
475
|
+
queryClient,
|
|
476
|
+
queryKey: queryKeyFor,
|
|
477
|
+
syncMode,
|
|
478
|
+
queryFn: async (ctx) => {
|
|
479
|
+
const request = ctx.queryKey[1] ?? {};
|
|
480
|
+
return fetchRecords(request, ctx.queryKey);
|
|
481
|
+
},
|
|
482
|
+
getKey: (item) => {
|
|
483
|
+
const record = item;
|
|
484
|
+
if (!record || typeof record !== "object" || !("id" in record)) {
|
|
485
|
+
throw new Error(
|
|
486
|
+
`Record in collection '${collectionName}' is missing required 'id' field. Received: ${JSON.stringify(item)}`
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return record.id;
|
|
490
|
+
},
|
|
491
|
+
onInsert: options?.onInsert === false ? void 0 : options?.onInsert ?? (async ({ transaction }) => {
|
|
492
|
+
const created = await Promise.all(
|
|
493
|
+
transaction.mutations.map(async (mutation) => {
|
|
494
|
+
const {
|
|
495
|
+
created: _created,
|
|
496
|
+
updated: _updated,
|
|
497
|
+
collectionId: _collectionId,
|
|
498
|
+
collectionName: _collectionName,
|
|
499
|
+
...data
|
|
500
|
+
} = mutation.modified;
|
|
501
|
+
return pb.collection(collectionName).create(data);
|
|
502
|
+
})
|
|
503
|
+
);
|
|
504
|
+
writeBackAfterPersisted(transaction, created);
|
|
505
|
+
return { refetch: refetchOnMutation };
|
|
506
|
+
}),
|
|
507
|
+
onUpdate: options?.onUpdate === false ? void 0 : options?.onUpdate ?? (async ({ transaction }) => {
|
|
508
|
+
const updated = await Promise.all(
|
|
509
|
+
transaction.mutations.map(async (mutation) => {
|
|
510
|
+
const recordWithId = mutation.original;
|
|
511
|
+
return pb.collection(collectionName).update(recordWithId.id, mutation.changes);
|
|
512
|
+
})
|
|
513
|
+
);
|
|
514
|
+
writeBackAfterPersisted(transaction, updated);
|
|
515
|
+
return { refetch: refetchOnMutation };
|
|
516
|
+
}),
|
|
517
|
+
onDelete: options?.onDelete === false ? void 0 : options?.onDelete ?? (async ({ transaction }) => {
|
|
518
|
+
await Promise.all(
|
|
519
|
+
transaction.mutations.map(async (mutation) => {
|
|
520
|
+
const recordWithId = mutation.original;
|
|
521
|
+
await pb.collection(collectionName).delete(recordWithId.id);
|
|
522
|
+
})
|
|
523
|
+
);
|
|
524
|
+
return { refetch: refetchOnMutation };
|
|
525
|
+
})
|
|
526
|
+
});
|
|
527
|
+
const collectionOptions = options?.collectionOptions?.gcTime === void 0 ? queryCollectionConfig : { ...queryCollectionConfig, gcTime: options.collectionOptions.gcTime };
|
|
528
|
+
function writeBackAfterPersisted(transaction, records) {
|
|
529
|
+
markConfirmedPresent(records);
|
|
530
|
+
void transaction.isPersisted.promise.then(
|
|
531
|
+
() => {
|
|
532
|
+
if (!collection.utils || !collection.isReady()) return;
|
|
533
|
+
const fresh = records.filter((record) => !isStaleServerRecord(record));
|
|
534
|
+
if (fresh.length === 0) return;
|
|
535
|
+
writeOwn(() => collection.utils.writeUpsert(fresh));
|
|
536
|
+
},
|
|
537
|
+
// A rejected transaction rolled its draft back; there is
|
|
538
|
+
// nothing to write and nothing to report here.
|
|
539
|
+
() => void 0
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
let applyingOwnWrite = false;
|
|
543
|
+
function writeOwn(fn) {
|
|
544
|
+
applyingOwnWrite = true;
|
|
545
|
+
try {
|
|
546
|
+
fn();
|
|
547
|
+
} finally {
|
|
548
|
+
applyingOwnWrite = false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function syncedWriteKey(op) {
|
|
552
|
+
if (op.type !== "insert" && op.type !== "update") return null;
|
|
553
|
+
if (typeof op.key === "string") return op.key;
|
|
554
|
+
const id = op.value?.id;
|
|
555
|
+
return typeof id === "string" ? id : null;
|
|
556
|
+
}
|
|
557
|
+
function shouldDropSyncedWrite(op) {
|
|
558
|
+
const key = syncedWriteKey(op);
|
|
559
|
+
if (key === null) return false;
|
|
560
|
+
if (!applyingOwnWrite && hasPendingOptimisticMutation(key) && collection._state.syncedData.has(key)) {
|
|
561
|
+
logger.debug("Dropping synced write for optimistically-pending row", {
|
|
562
|
+
collectionName,
|
|
563
|
+
id: key
|
|
564
|
+
});
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
if (isStaleServerRecord(op.value)) {
|
|
568
|
+
logger.debug("Dropping stale synced write", { collectionName, id: key });
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
let subscribingViewPaths;
|
|
574
|
+
function withViewExpand(opts) {
|
|
575
|
+
const paths = (opts.subscription && viewPaths.get(opts.subscription)) ?? subscribingViewPaths;
|
|
576
|
+
return paths ? { ...opts, expand: paths } : opts;
|
|
577
|
+
}
|
|
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
|
+
const innerSync = collectionOptions.sync.sync;
|
|
584
|
+
collectionOptions.sync = {
|
|
585
|
+
...collectionOptions.sync,
|
|
586
|
+
sync: (params) => {
|
|
587
|
+
const guardedWrite = (message) => {
|
|
588
|
+
const op = message;
|
|
589
|
+
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
|
+
return params.write(message);
|
|
595
|
+
};
|
|
596
|
+
const res = innerSync({ ...params, write: guardedWrite });
|
|
597
|
+
if (!res || typeof res === "function") return res;
|
|
598
|
+
const { loadSubset, unloadSubset } = res;
|
|
599
|
+
return {
|
|
600
|
+
...res,
|
|
601
|
+
loadSubset: loadSubset ? (opts) => loadSubset(withViewExpand(opts)) : void 0,
|
|
602
|
+
unloadSubset: unloadSubset ? (opts) => unloadSubset(withViewExpand(opts)) : void 0
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
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
|
+
const views = /* @__PURE__ */ new Map();
|
|
616
|
+
function createView(paths) {
|
|
617
|
+
const view = Object.create(collection);
|
|
618
|
+
Object.defineProperties(view, {
|
|
619
|
+
id: { value: `${collectionName}?expand=${paths.join(",")}` },
|
|
620
|
+
subscribeChanges: {
|
|
621
|
+
value: (...args) => {
|
|
622
|
+
noteViewSubscribed(paths);
|
|
623
|
+
subscribingViewPaths = paths;
|
|
624
|
+
try {
|
|
625
|
+
const subscription = collection.subscribeChanges(...args);
|
|
626
|
+
viewPaths.set(subscription, paths);
|
|
627
|
+
return subscription;
|
|
628
|
+
} finally {
|
|
629
|
+
subscribingViewPaths = void 0;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
expand: {
|
|
634
|
+
value: () => {
|
|
635
|
+
throw new Error(`A view of "${collectionName}" cannot be expanded further`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
return view;
|
|
640
|
+
}
|
|
641
|
+
function expand(...paths) {
|
|
642
|
+
for (const path of paths) validateExpandPath(collectionName, relationTargets, path);
|
|
643
|
+
const all = normalizePaths([...alwaysExpand, ...paths]);
|
|
644
|
+
if (all.every((path) => alwaysExpand.includes(path))) return collection;
|
|
645
|
+
const key = all.join(",");
|
|
646
|
+
let view = views.get(key);
|
|
647
|
+
if (!view) {
|
|
648
|
+
view = createView(all);
|
|
649
|
+
views.set(key, view);
|
|
650
|
+
}
|
|
651
|
+
return view;
|
|
652
|
+
}
|
|
653
|
+
function hasPendingOptimisticMutation(key) {
|
|
654
|
+
const state = collection._state;
|
|
655
|
+
return state.optimisticUpserts.has(key) || state.optimisticDeletes.has(key);
|
|
656
|
+
}
|
|
657
|
+
function recordUpdatedAt(record) {
|
|
658
|
+
const updated = record?.updated;
|
|
659
|
+
return typeof updated === "string" && updated !== "" ? updated : void 0;
|
|
660
|
+
}
|
|
661
|
+
function isStaleServerRecord(record) {
|
|
662
|
+
const id = record?.id;
|
|
663
|
+
if (typeof id !== "string") return false;
|
|
664
|
+
const incoming = recordUpdatedAt(record);
|
|
665
|
+
if (!incoming) return false;
|
|
666
|
+
const current = recordUpdatedAt(
|
|
667
|
+
collection._state.syncedData.get(id)
|
|
668
|
+
);
|
|
669
|
+
if (current === void 0) return false;
|
|
670
|
+
return incoming < current;
|
|
671
|
+
}
|
|
672
|
+
function isStaleEcho(event) {
|
|
673
|
+
if (event.action !== "create" && event.action !== "update") return false;
|
|
674
|
+
if (!isStaleServerRecord(event.record)) return false;
|
|
675
|
+
logger.debug("Ignoring stale realtime echo", {
|
|
676
|
+
collectionName,
|
|
677
|
+
id: event.record?.id
|
|
678
|
+
});
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
let unsubscribeFn = null;
|
|
682
|
+
let isSubscribed = false;
|
|
683
|
+
let subscriptionPromise = null;
|
|
684
|
+
let subscriptionResolve = null;
|
|
685
|
+
let subscribedExpand;
|
|
686
|
+
let subscriptionWork = Promise.resolve();
|
|
687
|
+
function enqueueSubscriptionWork(fn) {
|
|
688
|
+
const run = subscriptionWork.then(fn, fn).catch((error) => {
|
|
689
|
+
logger.error("Subscription work failed", { collectionName, error });
|
|
690
|
+
});
|
|
691
|
+
subscriptionWork = run;
|
|
692
|
+
return run;
|
|
693
|
+
}
|
|
694
|
+
function markEchoPresence(event) {
|
|
695
|
+
if (event.action !== "delete") markConfirmedPresent([event.record]);
|
|
696
|
+
}
|
|
697
|
+
const handleRealtimeEvent = (event) => {
|
|
698
|
+
if (!collection.utils) return;
|
|
699
|
+
markEchoPresence(event);
|
|
700
|
+
if (isStaleEcho(event)) return;
|
|
701
|
+
try {
|
|
702
|
+
writeOwn(
|
|
703
|
+
() => collection.utils.writeBatch(() => {
|
|
704
|
+
switch (event.action) {
|
|
705
|
+
case "create":
|
|
706
|
+
collection.utils.writeInsert(event.record);
|
|
707
|
+
break;
|
|
708
|
+
case "update":
|
|
709
|
+
collection.utils.writeUpsert(event.record);
|
|
710
|
+
break;
|
|
711
|
+
case "delete":
|
|
712
|
+
if (event.record && "id" in event.record) {
|
|
713
|
+
collection.utils.writeDelete(event.record.id);
|
|
714
|
+
}
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
})
|
|
718
|
+
);
|
|
719
|
+
} catch (error) {
|
|
720
|
+
if (error instanceof DeleteOperationItemNotFoundError) {
|
|
721
|
+
logger.debug("Ignoring delete echo for already-removed record", {
|
|
722
|
+
collectionName,
|
|
723
|
+
id: event.record?.id
|
|
724
|
+
});
|
|
725
|
+
} else {
|
|
726
|
+
throw error;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (event.action !== "delete") {
|
|
730
|
+
upsertExpanded([event.record], relationTargets).catch(
|
|
731
|
+
(error) => logger.error("Failed to upsert expanded records from realtime echo", {
|
|
732
|
+
collectionName,
|
|
733
|
+
error
|
|
734
|
+
})
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
propagateRelatedChange(
|
|
738
|
+
collection,
|
|
739
|
+
event.action,
|
|
740
|
+
event.record,
|
|
741
|
+
/* @__PURE__ */ new Set()
|
|
742
|
+
);
|
|
743
|
+
};
|
|
744
|
+
function pendingSubscribeExpand() {
|
|
745
|
+
return joinPaths([...alwaysExpand, ...requestedExpand]);
|
|
746
|
+
}
|
|
747
|
+
function activeExpandTargets() {
|
|
748
|
+
const targets = /* @__PURE__ */ new Set();
|
|
749
|
+
for (const path of splitPaths(pendingSubscribeExpand())) {
|
|
750
|
+
let current = relationTargets;
|
|
751
|
+
for (const segment of path.split(".")) {
|
|
752
|
+
const target = current?.[segment];
|
|
753
|
+
if (!target) break;
|
|
754
|
+
targets.add(target);
|
|
755
|
+
current = target.relationTargets;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return targets;
|
|
759
|
+
}
|
|
760
|
+
const heldTargetSubscriptions = /* @__PURE__ */ new Map();
|
|
761
|
+
function releaseHeldTarget(target) {
|
|
762
|
+
const held = heldTargetSubscriptions.get(target);
|
|
763
|
+
if (!held) return;
|
|
764
|
+
heldTargetSubscriptions.delete(target);
|
|
765
|
+
try {
|
|
766
|
+
held.unsubscribe();
|
|
767
|
+
} catch (error) {
|
|
768
|
+
logger.error("Failed to release relation target subscription", {
|
|
769
|
+
collectionName,
|
|
770
|
+
error
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function holdTarget(target) {
|
|
775
|
+
if (heldTargetSubscriptions.has(target) || !target.subscribeChanges) return;
|
|
776
|
+
try {
|
|
777
|
+
const held = target.subscribeChanges(() => {
|
|
778
|
+
}, { includeInitialState: false });
|
|
779
|
+
heldTargetSubscriptions.set(target, held);
|
|
780
|
+
} catch (error) {
|
|
781
|
+
logger.error("Failed to hold relation target subscription", { collectionName, error });
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function syncHeldSubscriptions(desired) {
|
|
785
|
+
for (const target of [...heldTargetSubscriptions.keys()]) {
|
|
786
|
+
if (!desired.has(target)) releaseHeldTarget(target);
|
|
787
|
+
}
|
|
788
|
+
for (const target of desired) holdTarget(target);
|
|
789
|
+
}
|
|
790
|
+
function realtimeSubscribeOptions() {
|
|
791
|
+
const base = factoryOptions?.subscribeOptions?.();
|
|
792
|
+
const expand2 = joinPaths([
|
|
793
|
+
...splitPaths(pendingSubscribeExpand()),
|
|
794
|
+
...splitPaths(base?.expand)
|
|
795
|
+
]);
|
|
796
|
+
return expand2 ? { ...base, expand: expand2 } : base;
|
|
797
|
+
}
|
|
798
|
+
const doStartSubscription = async () => {
|
|
799
|
+
if (isSubscribed) return;
|
|
800
|
+
if (!subscriptionPromise) {
|
|
801
|
+
subscriptionPromise = new Promise((resolve) => {
|
|
802
|
+
subscriptionResolve = resolve;
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
const pendingExpand = pendingSubscribeExpand();
|
|
806
|
+
try {
|
|
807
|
+
unsubscribeFn = await pb.collection(collectionName).subscribe("*", handleRealtimeEvent, realtimeSubscribeOptions());
|
|
808
|
+
isSubscribed = true;
|
|
809
|
+
subscribedExpand = pendingExpand;
|
|
810
|
+
syncHeldSubscriptions(activeExpandTargets());
|
|
811
|
+
logger.debug("Subscription started", { collectionName });
|
|
812
|
+
if (subscriptionResolve) {
|
|
813
|
+
subscriptionResolve();
|
|
814
|
+
}
|
|
815
|
+
} catch (error) {
|
|
816
|
+
logger.error("Failed to start subscription", { collectionName, error });
|
|
817
|
+
}
|
|
818
|
+
if (pendingSubscribeExpand() !== subscribedExpand) {
|
|
819
|
+
enqueueSubscriptionWork(doRestartSubscription).catch(() => {
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
const doStopSubscription = async (releaseTargets = true) => {
|
|
824
|
+
if (releaseTargets) syncHeldSubscriptions(/* @__PURE__ */ new Set());
|
|
825
|
+
if (!isSubscribed || !unsubscribeFn) return;
|
|
826
|
+
try {
|
|
827
|
+
await unsubscribeFn();
|
|
828
|
+
logger.debug("Subscription stopped", { collectionName });
|
|
829
|
+
} catch (error) {
|
|
830
|
+
logger.debug("Unsubscribe failed (expected if connection closed)", {
|
|
831
|
+
collectionName,
|
|
832
|
+
error
|
|
833
|
+
});
|
|
834
|
+
} finally {
|
|
835
|
+
unsubscribeFn = null;
|
|
836
|
+
isSubscribed = false;
|
|
837
|
+
subscribedExpand = void 0;
|
|
838
|
+
subscriptionPromise = null;
|
|
839
|
+
subscriptionResolve = null;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
const doRestartSubscription = async () => {
|
|
843
|
+
if (!isSubscribed) return;
|
|
844
|
+
await doStopSubscription(false);
|
|
845
|
+
await doStartSubscription();
|
|
846
|
+
};
|
|
847
|
+
const startSubscription = () => enqueueSubscriptionWork(doStartSubscription);
|
|
848
|
+
const stopSubscription = () => enqueueSubscriptionWork(() => doStopSubscription());
|
|
849
|
+
const restartSubscription = () => enqueueSubscriptionWork(doRestartSubscription);
|
|
850
|
+
function noteViewSubscribed(paths) {
|
|
851
|
+
let grew = false;
|
|
852
|
+
for (const path of paths) {
|
|
853
|
+
if (requestedExpand.has(path)) continue;
|
|
854
|
+
requestedExpand.add(path);
|
|
855
|
+
grew = true;
|
|
856
|
+
}
|
|
857
|
+
if (!grew) return;
|
|
858
|
+
if (syncMode === "eager" && collection.status !== "idle" && collection.status !== "cleaned-up") {
|
|
859
|
+
void collection.utils.refetch().catch(
|
|
860
|
+
(error) => logger.error("Failed to refetch after widening expand", {
|
|
861
|
+
collectionName,
|
|
862
|
+
error
|
|
863
|
+
})
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
restartSubscription().catch(
|
|
867
|
+
(error) => logger.error("Failed to restart subscription with wider expand", {
|
|
868
|
+
collectionName,
|
|
869
|
+
error
|
|
870
|
+
})
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
const waitForSubscription = async (timeout = 5e3) => {
|
|
874
|
+
if (isSubscribed) return;
|
|
875
|
+
if (!subscriptionPromise) {
|
|
876
|
+
await new Promise((resolve) => {
|
|
877
|
+
const checkInterval = setInterval(() => {
|
|
878
|
+
if (subscriptionPromise) {
|
|
879
|
+
clearInterval(checkInterval);
|
|
880
|
+
resolve();
|
|
881
|
+
}
|
|
882
|
+
}, 10);
|
|
883
|
+
setTimeout(() => {
|
|
884
|
+
clearInterval(checkInterval);
|
|
885
|
+
resolve();
|
|
886
|
+
}, timeout);
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
if (subscriptionPromise) {
|
|
890
|
+
await Promise.race([
|
|
891
|
+
subscriptionPromise,
|
|
892
|
+
new Promise(
|
|
893
|
+
(_, reject) => setTimeout(() => reject(new Error("Subscription timeout")), timeout)
|
|
894
|
+
)
|
|
895
|
+
]);
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
collection.on(
|
|
899
|
+
"subscribers:change",
|
|
900
|
+
(event) => {
|
|
901
|
+
const newCount = event.subscriberCount;
|
|
902
|
+
const previousCount = event.previousSubscriberCount;
|
|
903
|
+
if (newCount > 0 && previousCount === 0) {
|
|
904
|
+
startSubscription().catch(() => {
|
|
905
|
+
});
|
|
906
|
+
} else if (newCount === 0 && previousCount > 0) {
|
|
907
|
+
stopSubscription().catch(() => {
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
Object.assign(collection, {
|
|
913
|
+
collectionName,
|
|
914
|
+
relationTargets,
|
|
915
|
+
waitForSubscription,
|
|
916
|
+
isSubscribed: () => isSubscribed,
|
|
917
|
+
heldRelationTargetCount: () => heldTargetSubscriptions.size,
|
|
918
|
+
expand,
|
|
919
|
+
relationDependents,
|
|
920
|
+
applyRelatedChange
|
|
921
|
+
});
|
|
922
|
+
return collection;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/collection.ts
|
|
926
|
+
function createCollection(pb, queryClient, factoryOptions) {
|
|
927
|
+
return (collectionName, options) => {
|
|
928
|
+
return buildCollection({
|
|
929
|
+
pb,
|
|
930
|
+
queryClient,
|
|
931
|
+
factoryOptions,
|
|
932
|
+
collectionName,
|
|
933
|
+
options
|
|
934
|
+
});
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/util.ts
|
|
939
|
+
function newRecordId() {
|
|
940
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
941
|
+
let result = "";
|
|
942
|
+
for (let i = 0; i < 15; i++) {
|
|
943
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
944
|
+
}
|
|
945
|
+
return result;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export { createCollection, newRecordId, resetLogger, setLogger };
|
|
949
|
+
//# sourceMappingURL=chunk-KMR6I4ZC.js.map
|
|
950
|
+
//# sourceMappingURL=chunk-KMR6I4ZC.js.map
|