@keepkit/core 0.28.5 → 0.28.7
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 +66 -4
- package/dist/{chunk-AUNFZU3A.js → chunk-EMOPFB5D.js} +174 -43
- package/dist/chunk-EMOPFB5D.js.map +1 -0
- package/dist/{chunk-NZLAW4LY.js → chunk-SLH4DFYQ.js} +382 -70
- package/dist/chunk-SLH4DFYQ.js.map +1 -0
- package/dist/core.d.ts +35 -10
- package/dist/core.js +252 -92
- package/dist/core.js.map +1 -1
- package/dist/react.d.ts +5 -5
- package/dist/react.js +2 -2
- package/dist/schema.d.ts +1 -1
- package/dist/{scope-BteyH-b9.d.ts → scope-1bxGd68Y.d.ts} +4 -2
- package/dist/storage.d.ts +18 -12
- package/dist/storage.js +1 -1
- package/dist/{store-DVP3QyVp.d.ts → store-98vWM3tV.d.ts} +20 -2
- package/dist/{types-SKIL2p2c.d.ts → types-GTCMFt5T.d.ts} +52 -5
- package/package.json +1 -1
- package/dist/chunk-AUNFZU3A.js.map +0 -1
- package/dist/chunk-NZLAW4LY.js.map +0 -1
|
@@ -61,18 +61,22 @@ async function removeKeepItems(storage, ids) {
|
|
|
61
61
|
for (const id of ids) await storage.remove(id);
|
|
62
62
|
}
|
|
63
63
|
function mergeKeepItemLists(remoteItems, localItems) {
|
|
64
|
-
const
|
|
64
|
+
const byScopeAndId = new Map(remoteItems.map((item) => [itemStorageKey(item), item]));
|
|
65
65
|
for (const localItem of localItems) {
|
|
66
|
-
const
|
|
66
|
+
const key = itemStorageKey(localItem);
|
|
67
|
+
const remoteItem = byScopeAndId.get(key);
|
|
67
68
|
if (!remoteItem) {
|
|
68
|
-
|
|
69
|
+
byScopeAndId.set(key, localItem);
|
|
69
70
|
continue;
|
|
70
71
|
}
|
|
71
72
|
const content = localItem.updatedAt > remoteItem.updatedAt ? localItem : remoteItem;
|
|
72
73
|
const lastOpenedAt = remoteItem.lastOpenedAt === void 0 ? localItem.lastOpenedAt : localItem.lastOpenedAt === void 0 ? remoteItem.lastOpenedAt : Math.max(remoteItem.lastOpenedAt, localItem.lastOpenedAt);
|
|
73
|
-
|
|
74
|
+
byScopeAndId.set(key, lastOpenedAt === void 0 ? content : { ...content, lastOpenedAt });
|
|
74
75
|
}
|
|
75
|
-
return [...
|
|
76
|
+
return [...byScopeAndId.values()].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
77
|
+
}
|
|
78
|
+
function itemStorageKey(item) {
|
|
79
|
+
return JSON.stringify([item.scope?.tenantId ?? null, item.scope?.userId ?? null, item.id]);
|
|
76
80
|
}
|
|
77
81
|
function isKeepItem(value) {
|
|
78
82
|
if (!isRecord(value)) return false;
|
|
@@ -82,7 +86,7 @@ function isKeepItemArray(value) {
|
|
|
82
86
|
return Array.isArray(value) && value.every(isKeepItem);
|
|
83
87
|
}
|
|
84
88
|
function isKeepCollectionDefinition(value) {
|
|
85
|
-
return isRecord(value) && typeof value.id === "string" && value.id.trim().length > 0 && typeof value.name === "string" && value.name.trim().length > 0 && (value.scope === void 0 || isSyncScope(value.scope));
|
|
89
|
+
return isRecord(value) && typeof value.id === "string" && value.id.trim().length > 0 && typeof value.name === "string" && value.name.trim().length > 0 && (value.revision === void 0 || typeof value.revision === "string") && (value.updatedAt === void 0 || typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt)) && (value.scope === void 0 || isSyncScope(value.scope));
|
|
86
90
|
}
|
|
87
91
|
function isKeepCollectionMembership(value) {
|
|
88
92
|
return isRecord(value) && typeof value.collectionId === "string" && value.collectionId.trim().length > 0 && typeof value.itemId === "string" && value.itemId.trim().length > 0 && typeof value.order === "number" && Number.isInteger(value.order) && Number.isFinite(value.order) && value.order >= 0 && (value.scope === void 0 || isSyncScope(value.scope));
|
|
@@ -96,7 +100,7 @@ function isSyncScope(value) {
|
|
|
96
100
|
|
|
97
101
|
// src/features/persistence/scope.ts
|
|
98
102
|
function getKeepScopeKey(scope) {
|
|
99
|
-
if (
|
|
103
|
+
if (scope?.userId === void 0 && scope?.tenantId === void 0) return "";
|
|
100
104
|
return `:${encodeURIComponent(scope.tenantId ?? "_")}:${encodeURIComponent(scope.userId ?? "_")}`;
|
|
101
105
|
}
|
|
102
106
|
function isSameKeepScope(left, right) {
|
|
@@ -143,37 +147,66 @@ var ScopedStorageAdapter = class {
|
|
|
143
147
|
const scoped = items.map((item) => ({ ...item, ...this.scope ? { scope: this.scope } : {} }));
|
|
144
148
|
const ids = new Set(scoped.map((item) => item.id));
|
|
145
149
|
const next = current.filter((item) => !ids.has(item.id) || !isSameKeepScope(item.scope, this.scope));
|
|
146
|
-
await writeAll(this.base, [...next, ...scoped]);
|
|
150
|
+
await writeAll(this.base, [...next, ...scoped], this.scope);
|
|
147
151
|
}
|
|
148
152
|
async remove(id) {
|
|
149
153
|
const current = await this.base.getAll();
|
|
150
154
|
await writeAll(
|
|
151
155
|
this.base,
|
|
152
|
-
current.filter((item) => item.id !== id || !isSameKeepScope(item.scope, this.scope))
|
|
156
|
+
current.filter((item) => item.id !== id || !isSameKeepScope(item.scope, this.scope)),
|
|
157
|
+
this.scope
|
|
153
158
|
);
|
|
159
|
+
await this.removeMembershipsForItems([id]);
|
|
154
160
|
}
|
|
155
161
|
async removeMany(ids) {
|
|
156
162
|
const idSet = new Set(ids);
|
|
157
163
|
const current = await this.base.getAll();
|
|
158
164
|
await writeAll(
|
|
159
165
|
this.base,
|
|
160
|
-
current.filter((item) => !idSet.has(item.id) || !isSameKeepScope(item.scope, this.scope))
|
|
166
|
+
current.filter((item) => !idSet.has(item.id) || !isSameKeepScope(item.scope, this.scope)),
|
|
167
|
+
this.scope
|
|
161
168
|
);
|
|
169
|
+
await this.removeMembershipsForItems([...idSet]);
|
|
162
170
|
}
|
|
163
171
|
async clear() {
|
|
164
172
|
const current = await this.base.getAll();
|
|
173
|
+
const removedIds = current.filter((item) => isSameKeepScope(item.scope, this.scope)).map((item) => item.id);
|
|
165
174
|
await writeAll(
|
|
166
175
|
this.base,
|
|
167
|
-
current.filter((item) => !isSameKeepScope(item.scope, this.scope))
|
|
176
|
+
current.filter((item) => !isSameKeepScope(item.scope, this.scope)),
|
|
177
|
+
this.scope
|
|
168
178
|
);
|
|
179
|
+
if (removedIds.length > 0) await this.removeMembershipsForItems(removedIds);
|
|
180
|
+
await this.clearMembershipsForScope();
|
|
169
181
|
}
|
|
170
182
|
async merge(items) {
|
|
171
|
-
|
|
172
|
-
|
|
183
|
+
const scopedItems = items.map((item) => ({ ...item, ...this.scope ? { scope: this.scope } : {} }));
|
|
184
|
+
const merged = mergeKeepItemLists(await this.getAll(), scopedItems);
|
|
185
|
+
await this.setMany(merged);
|
|
186
|
+
return merged;
|
|
173
187
|
}
|
|
174
188
|
subscribe(listener) {
|
|
175
189
|
return this.base.subscribe?.(listener) ?? (() => void 0);
|
|
176
190
|
}
|
|
191
|
+
async removeMembershipsForItems(ids) {
|
|
192
|
+
if (!this.base.getCollectionMemberships || !this.base.removeCollectionMembership || ids.length === 0) return;
|
|
193
|
+
const removedIds = new Set(ids);
|
|
194
|
+
const memberships = await this.base.getCollectionMemberships();
|
|
195
|
+
for (const membership of memberships) {
|
|
196
|
+
if (removedIds.has(membership.itemId) && isSameKeepScope(membership.scope, this.scope)) {
|
|
197
|
+
await this.base.removeCollectionMembership(membership.collectionId, membership.itemId, this.scope);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async clearMembershipsForScope() {
|
|
202
|
+
if (!this.base.getCollectionMemberships || !this.base.removeCollectionMembership) return;
|
|
203
|
+
const memberships = await this.base.getCollectionMemberships();
|
|
204
|
+
for (const membership of memberships) {
|
|
205
|
+
if (isSameKeepScope(membership.scope, this.scope)) {
|
|
206
|
+
await this.base.removeCollectionMembership(membership.collectionId, membership.itemId, this.scope);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
177
210
|
};
|
|
178
211
|
function createScopedStorageAdapter(base, scope) {
|
|
179
212
|
return new ScopedStorageAdapter(base, scope);
|
|
@@ -193,32 +226,38 @@ var ScopedSyncQueueAdapter = class {
|
|
|
193
226
|
const ids = new Set(scoped.map((operation) => operation.operationId));
|
|
194
227
|
await this.base.setMany([
|
|
195
228
|
...current.filter(
|
|
196
|
-
(operation) => !ids.has(operation.operationId)
|
|
229
|
+
(operation) => !ids.has(operation.operationId) || !isSameKeepScope(operation.scope, this.scope)
|
|
197
230
|
),
|
|
198
231
|
...scoped
|
|
199
232
|
]);
|
|
200
233
|
}
|
|
201
|
-
remove(operationIds) {
|
|
202
|
-
|
|
234
|
+
async remove(operationIds) {
|
|
235
|
+
const ids = new Set(operationIds);
|
|
236
|
+
const scopedIds = (await this.base.getAll()).filter((operation) => ids.has(operation.operationId) && isSameKeepScope(operation.scope, this.scope)).map((operation) => operation.operationId);
|
|
237
|
+
await this.base.remove(scopedIds);
|
|
203
238
|
}
|
|
204
|
-
clear() {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
239
|
+
async clear() {
|
|
240
|
+
const operationIds = (await this.base.getAll()).filter((operation) => isSameKeepScope(operation.scope, this.scope)).map((operation) => operation.operationId);
|
|
241
|
+
await this.base.remove(operationIds);
|
|
208
242
|
}
|
|
209
243
|
};
|
|
210
|
-
async function writeAll(base, items) {
|
|
244
|
+
async function writeAll(base, items, scope) {
|
|
245
|
+
const existing = await base.getAll();
|
|
246
|
+
const desiredItems = new Set(items.filter((item) => isSameKeepScope(item.scope, scope)).map(itemStorageKey2));
|
|
247
|
+
const removedIds = existing.filter((item) => isSameKeepScope(item.scope, scope) && !desiredItems.has(itemStorageKey2(item))).map((item) => item.id);
|
|
248
|
+
if (removedIds.length > 0) {
|
|
249
|
+
if (base.removeMany) await base.removeMany(removedIds, scope);
|
|
250
|
+
else await Promise.all(removedIds.map((id) => base.remove(id, scope)));
|
|
251
|
+
}
|
|
211
252
|
if (base.setMany) {
|
|
212
253
|
await base.setMany(items);
|
|
213
254
|
return;
|
|
214
255
|
}
|
|
215
|
-
const existing = await base.getAll();
|
|
216
|
-
const ids = new Set(items.map((item) => item.id));
|
|
217
|
-
for (const item of existing) {
|
|
218
|
-
if (!ids.has(item.id)) await base.remove(item.id);
|
|
219
|
-
}
|
|
220
256
|
for (const item of items) await base.set(item);
|
|
221
257
|
}
|
|
258
|
+
function itemStorageKey2(item) {
|
|
259
|
+
return JSON.stringify([item.scope?.tenantId ?? null, item.scope?.userId ?? null, item.id]);
|
|
260
|
+
}
|
|
222
261
|
|
|
223
262
|
// src/storage/sync.ts
|
|
224
263
|
var DEFAULT_SYNC_QUEUE_KEY = "keepkit:sync-queue";
|
|
@@ -381,11 +420,12 @@ var SyncStorageAdapter = class {
|
|
|
381
420
|
};
|
|
382
421
|
this.local = options.local;
|
|
383
422
|
this.remote = options.remote;
|
|
384
|
-
this.
|
|
423
|
+
this.scope = getSyncScope(options);
|
|
424
|
+
const queue = options.queue ?? createDefaultQueue(options);
|
|
425
|
+
this.queue = this.scope ? new ScopedSyncQueueAdapter(queue, this.scope) : queue;
|
|
385
426
|
this.clientId = options.clientId ?? createId();
|
|
386
427
|
this.now = options.now ?? Date.now;
|
|
387
428
|
this.resolveConflict = options.resolveConflict;
|
|
388
|
-
this.scope = getSyncScope(options);
|
|
389
429
|
this.maxRetries = Math.max(0, options.maxRetries ?? 3);
|
|
390
430
|
this.retryDelayMs = Math.max(0, options.retryDelayMs ?? 0);
|
|
391
431
|
this.retryBackoff = Math.max(1, options.retryBackoff ?? 2);
|
|
@@ -402,7 +442,13 @@ var SyncStorageAdapter = class {
|
|
|
402
442
|
this.setCollection = async (collection) => {
|
|
403
443
|
const scopedCollection = { ...collection, ...this.scope ? { scope: this.scope } : {} };
|
|
404
444
|
if (this.remote.pushCollection) {
|
|
405
|
-
const
|
|
445
|
+
const current = (await this.getCollections?.())?.find((entry) => entry.id === scopedCollection.id);
|
|
446
|
+
const operation = this.createCollectionOperation(
|
|
447
|
+
"upsert",
|
|
448
|
+
scopedCollection.id,
|
|
449
|
+
scopedCollection,
|
|
450
|
+
current?.revision
|
|
451
|
+
);
|
|
406
452
|
await this.enqueueBeforeLocalWrite(operation);
|
|
407
453
|
try {
|
|
408
454
|
await setCollection(scopedCollection);
|
|
@@ -418,13 +464,16 @@ var SyncStorageAdapter = class {
|
|
|
418
464
|
this.notifyDataListeners();
|
|
419
465
|
};
|
|
420
466
|
this.removeCollection = async (id) => {
|
|
467
|
+
const memberships = await this.getCollectionMemberships?.() ?? [];
|
|
468
|
+
const membershipOperations = this.remote.pushMembership ? memberships.filter((membership) => membership.collectionId === id).map((membership) => this.createMembershipOperation("remove", membership.collectionId, membership.itemId)) : [];
|
|
421
469
|
if (this.remote.pushCollection) {
|
|
422
|
-
const
|
|
423
|
-
|
|
470
|
+
const current = (await this.getCollections?.())?.find((collection) => collection.id === id);
|
|
471
|
+
const operation = this.createCollectionOperation("remove", id, void 0, current?.revision);
|
|
472
|
+
await this.enqueueManyBeforeLocalWrite([...membershipOperations, operation]);
|
|
424
473
|
try {
|
|
425
474
|
await removeCollection(id, this.scope);
|
|
426
475
|
} catch (cause) {
|
|
427
|
-
await this.removeQueued(operation.operationId);
|
|
476
|
+
await this.removeQueued([operation.operationId, ...membershipOperations.map((entry) => entry.operationId)]);
|
|
428
477
|
throw cause;
|
|
429
478
|
}
|
|
430
479
|
this.notifyDataListeners();
|
|
@@ -445,11 +494,36 @@ var SyncStorageAdapter = class {
|
|
|
445
494
|
return scope ? memberships.filter((membership) => sameScope(membership.scope, scope)) : memberships;
|
|
446
495
|
};
|
|
447
496
|
this.setCollectionMembership = async (membership) => {
|
|
448
|
-
|
|
497
|
+
const scoped = { ...membership, ...this.scope ? { scope: this.scope } : {} };
|
|
498
|
+
if (this.remote.pushMembership) {
|
|
499
|
+
const operation = this.createMembershipOperation("upsert", scoped.collectionId, scoped.itemId, scoped);
|
|
500
|
+
await this.enqueueBeforeLocalWrite(operation);
|
|
501
|
+
try {
|
|
502
|
+
await setMembership(scoped);
|
|
503
|
+
} catch (cause) {
|
|
504
|
+
await this.removeQueued(operation.operationId);
|
|
505
|
+
throw cause;
|
|
506
|
+
}
|
|
507
|
+
this.setPendingState();
|
|
508
|
+
} else {
|
|
509
|
+
await setMembership(scoped);
|
|
510
|
+
}
|
|
449
511
|
this.notifyDataListeners();
|
|
450
512
|
};
|
|
451
513
|
this.removeCollectionMembership = async (collectionId, itemId) => {
|
|
452
|
-
|
|
514
|
+
if (this.remote.pushMembership) {
|
|
515
|
+
const operation = this.createMembershipOperation("remove", collectionId, itemId);
|
|
516
|
+
await this.enqueueBeforeLocalWrite(operation);
|
|
517
|
+
try {
|
|
518
|
+
await removeMembership(collectionId, itemId, this.scope);
|
|
519
|
+
} catch (cause) {
|
|
520
|
+
await this.removeQueued(operation.operationId);
|
|
521
|
+
throw cause;
|
|
522
|
+
}
|
|
523
|
+
this.setPendingState();
|
|
524
|
+
} else {
|
|
525
|
+
await removeMembership(collectionId, itemId, this.scope);
|
|
526
|
+
}
|
|
453
527
|
this.notifyDataListeners();
|
|
454
528
|
};
|
|
455
529
|
}
|
|
@@ -493,11 +567,13 @@ var SyncStorageAdapter = class {
|
|
|
493
567
|
}
|
|
494
568
|
async remove(id) {
|
|
495
569
|
const operation = this.createOperation("remove", id);
|
|
496
|
-
await this.
|
|
570
|
+
const memberships = await this.getCollectionMemberships?.() ?? [];
|
|
571
|
+
const membershipOperations = this.remote.pushMembership ? memberships.filter((membership) => membership.itemId === id).map((membership) => this.createMembershipOperation("remove", membership.collectionId, membership.itemId)) : [];
|
|
572
|
+
await this.enqueueManyBeforeLocalWrite([...membershipOperations, operation]);
|
|
497
573
|
try {
|
|
498
|
-
await this.local.remove(id);
|
|
574
|
+
await this.local.remove(id, this.scope);
|
|
499
575
|
} catch (cause) {
|
|
500
|
-
await this.removeQueued(operation.operationId);
|
|
576
|
+
await this.removeQueued([operation.operationId, ...membershipOperations.map((entry) => entry.operationId)]);
|
|
501
577
|
throw cause;
|
|
502
578
|
}
|
|
503
579
|
this.notifyDataListeners();
|
|
@@ -505,11 +581,15 @@ var SyncStorageAdapter = class {
|
|
|
505
581
|
}
|
|
506
582
|
async removeMany(ids) {
|
|
507
583
|
const operations = [...new Set(ids)].map((id) => this.createOperation("remove", id));
|
|
508
|
-
|
|
584
|
+
const idSet = new Set(ids);
|
|
585
|
+
const memberships = await this.getCollectionMemberships?.() ?? [];
|
|
586
|
+
const membershipOperations = this.remote.pushMembership ? memberships.filter((membership) => idSet.has(membership.itemId)).map((membership) => this.createMembershipOperation("remove", membership.collectionId, membership.itemId)) : [];
|
|
587
|
+
await this.enqueueManyBeforeLocalWrite([...membershipOperations, ...operations]);
|
|
509
588
|
try {
|
|
510
|
-
|
|
589
|
+
if (this.local.removeMany) await this.local.removeMany(ids, this.scope);
|
|
590
|
+
else await Promise.all(ids.map((id) => this.local.remove(id, this.scope)));
|
|
511
591
|
} catch (cause) {
|
|
512
|
-
await this.removeQueued(operations.map((operation) => operation.operationId));
|
|
592
|
+
await this.removeQueued([...operations, ...membershipOperations].map((operation) => operation.operationId));
|
|
513
593
|
throw cause;
|
|
514
594
|
}
|
|
515
595
|
this.notifyDataListeners();
|
|
@@ -518,9 +598,17 @@ var SyncStorageAdapter = class {
|
|
|
518
598
|
async clear() {
|
|
519
599
|
const items = await this.getAll();
|
|
520
600
|
await this.removeMany(items.map((item) => item.id));
|
|
521
|
-
await this.local.clear();
|
|
601
|
+
if (!this.scope) await this.local.clear();
|
|
522
602
|
}
|
|
523
603
|
async merge(localItems) {
|
|
604
|
+
if (this.scope) {
|
|
605
|
+
const merged2 = mergeKeepItemLists(
|
|
606
|
+
await this.getAll(),
|
|
607
|
+
localItems.map((item) => this.applyScope(item))
|
|
608
|
+
);
|
|
609
|
+
await this.setMany(localItems);
|
|
610
|
+
return merged2;
|
|
611
|
+
}
|
|
524
612
|
const merged = this.local.merge ? await this.local.merge(localItems) : mergeKeepItemLists(await this.local.getAll(), localItems);
|
|
525
613
|
if (!this.local.merge) await persistKeepItems(this.local, merged);
|
|
526
614
|
await this.setMany(localItems);
|
|
@@ -569,6 +657,47 @@ var SyncStorageAdapter = class {
|
|
|
569
657
|
});
|
|
570
658
|
if (resolution !== "remote" && this.queueItems.length > 0) await this.flushSync();
|
|
571
659
|
}
|
|
660
|
+
async resolveCollectionSyncConflict(id, resolution, collection) {
|
|
661
|
+
await this.loadQueue();
|
|
662
|
+
const conflict = (this.state.collectionConflicts ?? []).find((entry) => entry.operation.id === id);
|
|
663
|
+
if (!conflict) return;
|
|
664
|
+
if (!this.local.getCollections || !this.local.setCollection || !this.local.removeCollection) {
|
|
665
|
+
throw new Error("KeepKit collection storage is unavailable.");
|
|
666
|
+
}
|
|
667
|
+
if (resolution === "remote") {
|
|
668
|
+
if (conflict.deleted) await this.local.removeCollection(id, this.scope);
|
|
669
|
+
else {
|
|
670
|
+
if (!conflict.collection) throw new Error("Remote collection conflict is missing its selected definition.");
|
|
671
|
+
await this.local.setCollection({
|
|
672
|
+
...conflict.collection,
|
|
673
|
+
...conflict.revision ? { revision: conflict.revision } : {},
|
|
674
|
+
...this.scope ? { scope: this.scope } : {}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
await this.removeQueued(conflict.operation.operationId);
|
|
678
|
+
this.updateState({
|
|
679
|
+
collectionConflicts: (this.state.collectionConflicts ?? []).filter((entry) => entry.operation.id !== id)
|
|
680
|
+
});
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (collection && collection.id !== id) {
|
|
684
|
+
throw new TypeError("KeepKit collection conflict resolution must use the requested collection ID.");
|
|
685
|
+
}
|
|
686
|
+
const chosen = collection ?? (await this.getCollections?.())?.find((entry) => entry.id === id);
|
|
687
|
+
const retry = {
|
|
688
|
+
...conflict.operation,
|
|
689
|
+
operationId: `${this.clientId}:${this.now()}:${createId()}`,
|
|
690
|
+
...conflict.operation.type === "upsert" && chosen ? { collection: { ...chosen, ...this.scope ? { scope: this.scope } : {} } } : {},
|
|
691
|
+
createdAt: this.now(),
|
|
692
|
+
baseRevision: conflict.revision,
|
|
693
|
+
attempts: 0
|
|
694
|
+
};
|
|
695
|
+
await this.replaceQueued(conflict.operation, retry);
|
|
696
|
+
this.updateState({
|
|
697
|
+
collectionConflicts: (this.state.collectionConflicts ?? []).filter((entry) => entry.operation.id !== id)
|
|
698
|
+
});
|
|
699
|
+
await this.flushSync();
|
|
700
|
+
}
|
|
572
701
|
dispose() {
|
|
573
702
|
this.disposed = true;
|
|
574
703
|
if (this.onlineHandler) window.removeEventListener("online", this.onlineHandler);
|
|
@@ -589,7 +718,29 @@ var SyncStorageAdapter = class {
|
|
|
589
718
|
try {
|
|
590
719
|
if (isCollectionSyncOperation(operation)) {
|
|
591
720
|
if (!this.remote.pushCollection) throw new Error("Remote sync driver does not support collection sync.");
|
|
592
|
-
await this.pushCollectionWithRetry(operation);
|
|
721
|
+
const result2 = await this.pushCollectionWithRetry(operation);
|
|
722
|
+
if (result2?.type === "conflict" && result2.resolution === "manual") {
|
|
723
|
+
const conflict = createCollectionSyncConflict(operation, result2);
|
|
724
|
+
this.updateState({
|
|
725
|
+
status: "conflict",
|
|
726
|
+
collectionConflicts: [
|
|
727
|
+
...(this.state.collectionConflicts ?? []).filter((entry) => entry.operation.id !== operation.id),
|
|
728
|
+
conflict
|
|
729
|
+
]
|
|
730
|
+
});
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
await this.applyCollectionPushResult(operation, result2);
|
|
734
|
+
await this.removeQueued(operation.operationId);
|
|
735
|
+
this.updateState({
|
|
736
|
+
status: this.queueItems.length > 0 ? "syncing" : "synced",
|
|
737
|
+
lastSyncedAt: this.now()
|
|
738
|
+
});
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (isMembershipSyncOperation(operation)) {
|
|
742
|
+
if (!this.remote.pushMembership) throw new Error("Remote sync driver does not support membership sync.");
|
|
743
|
+
await this.pushMembershipWithRetry(operation);
|
|
593
744
|
await this.removeQueued(operation.operationId);
|
|
594
745
|
this.updateState({
|
|
595
746
|
status: this.queueItems.length > 0 ? "syncing" : "synced",
|
|
@@ -661,7 +812,7 @@ var SyncStorageAdapter = class {
|
|
|
661
812
|
...this.scope ? { scope: this.scope } : {}
|
|
662
813
|
};
|
|
663
814
|
}
|
|
664
|
-
createCollectionOperation(type, id, collection) {
|
|
815
|
+
createCollectionOperation(type, id, collection, baseRevision) {
|
|
665
816
|
return {
|
|
666
817
|
operationId: `${this.clientId}:${this.now()}:${createId()}`,
|
|
667
818
|
type,
|
|
@@ -669,6 +820,20 @@ var SyncStorageAdapter = class {
|
|
|
669
820
|
id,
|
|
670
821
|
...collection ? { collection } : {},
|
|
671
822
|
createdAt: this.now(),
|
|
823
|
+
...baseRevision ? { baseRevision } : {},
|
|
824
|
+
...this.scope ? { scope: this.scope } : {}
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
createMembershipOperation(type, collectionId, itemId, membership) {
|
|
828
|
+
return {
|
|
829
|
+
operationId: `${this.clientId}:${this.now()}:${createId()}`,
|
|
830
|
+
type,
|
|
831
|
+
entity: "membership",
|
|
832
|
+
id: getMembershipId(collectionId, itemId),
|
|
833
|
+
collectionId,
|
|
834
|
+
itemId,
|
|
835
|
+
...membership ? { membership } : {},
|
|
836
|
+
createdAt: this.now(),
|
|
672
837
|
...this.scope ? { scope: this.scope } : {}
|
|
673
838
|
};
|
|
674
839
|
}
|
|
@@ -692,7 +857,48 @@ var SyncStorageAdapter = class {
|
|
|
692
857
|
let attempt = 0;
|
|
693
858
|
while (true) {
|
|
694
859
|
try {
|
|
695
|
-
await this.remote.pushCollection?.({ ...operation, attempts: attempt });
|
|
860
|
+
return await this.remote.pushCollection?.({ ...operation, attempts: attempt });
|
|
861
|
+
} catch (error) {
|
|
862
|
+
if (isKeepSyncAuthError(error) || attempt >= this.maxRetries) throw error;
|
|
863
|
+
attempt += 1;
|
|
864
|
+
const delay = this.retryDelayMs * this.retryBackoff ** (attempt - 1);
|
|
865
|
+
if (delay > 0) await wait(delay);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
async applyCollectionPushResult(operation, result) {
|
|
870
|
+
if (!this.local.getCollections || !this.local.setCollection || !this.local.removeCollection) return;
|
|
871
|
+
if (!result) return;
|
|
872
|
+
const outcome = result.type === "conflict" ? result.resolution : "remote";
|
|
873
|
+
if (outcome === "manual") return;
|
|
874
|
+
const deleted = outcome === "local" ? operation.type === "remove" : result.deleted ?? (operation.type === "remove" && outcome === "remote");
|
|
875
|
+
if (deleted) {
|
|
876
|
+
await this.local.removeCollection(operation.id, this.scope);
|
|
877
|
+
} else if (result.type === "synced" || outcome === "remote" || outcome === "local") {
|
|
878
|
+
const current = (outcome === "local" ? operation.collection : result.collection) ?? (await this.getCollections?.())?.find((entry) => entry.id === operation.id);
|
|
879
|
+
if (!current) return;
|
|
880
|
+
await this.local.setCollection({
|
|
881
|
+
...current,
|
|
882
|
+
...result.revision ? { revision: result.revision } : current.revision ? { revision: current.revision } : {},
|
|
883
|
+
...this.scope ? { scope: this.scope } : {}
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
if (result.type === "conflict") {
|
|
887
|
+
const conflict = createCollectionSyncConflict(operation, result);
|
|
888
|
+
this.updateState({
|
|
889
|
+
status: "conflict",
|
|
890
|
+
collectionConflicts: [
|
|
891
|
+
...(this.state.collectionConflicts ?? []).filter((entry) => entry.operation.id !== operation.id),
|
|
892
|
+
conflict
|
|
893
|
+
]
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
async pushMembershipWithRetry(operation) {
|
|
898
|
+
let attempt = 0;
|
|
899
|
+
while (true) {
|
|
900
|
+
try {
|
|
901
|
+
await this.remote.pushMembership?.({ ...operation, attempts: attempt });
|
|
696
902
|
return;
|
|
697
903
|
} catch (error) {
|
|
698
904
|
if (isKeepSyncAuthError(error) || attempt >= this.maxRetries) throw error;
|
|
@@ -771,7 +977,7 @@ var SyncStorageAdapter = class {
|
|
|
771
977
|
});
|
|
772
978
|
}
|
|
773
979
|
async pullRemote() {
|
|
774
|
-
if (!this.remote.pull && !this.remote.pullCollections) return true;
|
|
980
|
+
if (!this.remote.pull && !this.remote.pullCollections && !this.remote.pullMemberships) return true;
|
|
775
981
|
try {
|
|
776
982
|
if (this.remote.pull) {
|
|
777
983
|
const remoteItems = await this.remote.pull();
|
|
@@ -805,7 +1011,7 @@ var SyncStorageAdapter = class {
|
|
|
805
1011
|
const localCollections = await this.local.getCollections();
|
|
806
1012
|
const pendingIds = new Set(this.queueItems.filter(isCollectionSyncOperation).map((operation) => operation.id));
|
|
807
1013
|
const remoteById = new Map(
|
|
808
|
-
remoteCollections.filter((collection) => !this.scope || sameScope(collection.scope, this.scope)).map((collection) => [collection.id, collection])
|
|
1014
|
+
remoteCollections.filter((collection) => !this.scope || !collection.scope || sameScope(collection.scope, this.scope)).map((collection) => [collection.id, collection])
|
|
809
1015
|
);
|
|
810
1016
|
for (const collection of remoteById.values()) {
|
|
811
1017
|
if (pendingIds.has(collection.id)) continue;
|
|
@@ -818,6 +1024,32 @@ var SyncStorageAdapter = class {
|
|
|
818
1024
|
}
|
|
819
1025
|
this.notifyDataListeners();
|
|
820
1026
|
}
|
|
1027
|
+
if (this.remote.pullMemberships && this.getCollections && this.local.getCollectionMemberships && this.local.setCollectionMembership && this.local.removeCollectionMembership) {
|
|
1028
|
+
const remoteMemberships = await this.remote.pullMemberships();
|
|
1029
|
+
const localMemberships = await this.local.getCollectionMemberships();
|
|
1030
|
+
const pendingIds = new Set(this.queueItems.filter(isMembershipSyncOperation).map((operation) => operation.id));
|
|
1031
|
+
const itemIds = new Set((await this.getAll()).map((item) => item.id));
|
|
1032
|
+
const collectionIds = new Set((await this.getCollections()).map((collection) => collection.id));
|
|
1033
|
+
const remoteById = new Map(
|
|
1034
|
+
remoteMemberships.filter((membership) => {
|
|
1035
|
+
if (this.scope && membership.scope && !sameScope(membership.scope, this.scope)) return false;
|
|
1036
|
+
if (!itemIds.has(membership.itemId)) return false;
|
|
1037
|
+
return collectionIds.has(membership.collectionId);
|
|
1038
|
+
}).map((membership) => [getMembershipId(membership.collectionId, membership.itemId), membership])
|
|
1039
|
+
);
|
|
1040
|
+
for (const membership of remoteById.values()) {
|
|
1041
|
+
const id = getMembershipId(membership.collectionId, membership.itemId);
|
|
1042
|
+
if (pendingIds.has(id)) continue;
|
|
1043
|
+
await this.local.setCollectionMembership({ ...membership, ...this.scope ? { scope: this.scope } : {} });
|
|
1044
|
+
}
|
|
1045
|
+
for (const membership of localMemberships) {
|
|
1046
|
+
const id = getMembershipId(membership.collectionId, membership.itemId);
|
|
1047
|
+
if (pendingIds.has(id) || remoteById.has(id)) continue;
|
|
1048
|
+
if (this.scope && !sameScope(membership.scope, this.scope)) continue;
|
|
1049
|
+
await this.local.removeCollectionMembership(membership.collectionId, membership.itemId, this.scope);
|
|
1050
|
+
}
|
|
1051
|
+
this.notifyDataListeners();
|
|
1052
|
+
}
|
|
821
1053
|
return true;
|
|
822
1054
|
} catch (error) {
|
|
823
1055
|
this.updateState({ status: "error", error });
|
|
@@ -844,11 +1076,27 @@ function isCollectionSyncOperation(value) {
|
|
|
844
1076
|
if (!isRecord(value)) return false;
|
|
845
1077
|
return typeof value.operationId === "string" && value.entity === "collection" && (value.type === "upsert" || value.type === "remove") && typeof value.id === "string" && typeof value.createdAt === "number" && (value.collection === void 0 || isKeepCollectionDefinition(value.collection));
|
|
846
1078
|
}
|
|
1079
|
+
function isMembershipSyncOperation(value) {
|
|
1080
|
+
if (!isRecord(value)) return false;
|
|
1081
|
+
return typeof value.operationId === "string" && value.entity === "membership" && (value.type === "upsert" || value.type === "remove") && typeof value.id === "string" && typeof value.collectionId === "string" && typeof value.itemId === "string" && typeof value.createdAt === "number" && (value.membership === void 0 || isKeepCollectionMembership(value.membership)) && (value.type !== "upsert" || isKeepCollectionMembership(value.membership));
|
|
1082
|
+
}
|
|
847
1083
|
function isSyncQueueOperation(value) {
|
|
848
|
-
return isSyncOperation(value) || isCollectionSyncOperation(value);
|
|
1084
|
+
return isSyncOperation(value) || isCollectionSyncOperation(value) || isMembershipSyncOperation(value);
|
|
849
1085
|
}
|
|
850
1086
|
function getQueueIdentity(operation) {
|
|
851
|
-
return `${operation.entity
|
|
1087
|
+
return `${operation.entity ?? "item"}:${operation.id}`;
|
|
1088
|
+
}
|
|
1089
|
+
function getMembershipId(collectionId, itemId) {
|
|
1090
|
+
return `${collectionId}\0${itemId}`;
|
|
1091
|
+
}
|
|
1092
|
+
function createCollectionSyncConflict(operation, result) {
|
|
1093
|
+
const details = {
|
|
1094
|
+
operation,
|
|
1095
|
+
resolution: result.resolution,
|
|
1096
|
+
...result.revision ? { revision: result.revision } : {}
|
|
1097
|
+
};
|
|
1098
|
+
if (result.deleted) return { ...details, deleted: true };
|
|
1099
|
+
return { ...details, deleted: false, collection: result.collection };
|
|
852
1100
|
}
|
|
853
1101
|
function createId() {
|
|
854
1102
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
@@ -931,6 +1179,7 @@ var FallbackStorageAdapter = class {
|
|
|
931
1179
|
this.migrateFallbackOnEmpty = options.migrateFallbackOnEmpty ?? false;
|
|
932
1180
|
this.mirrorWrites = options.mirrorWrites ?? false;
|
|
933
1181
|
this.storageKey = options.fallback.storageKey ?? options.primary.storageKey;
|
|
1182
|
+
this.scope = options.primary.scope ?? options.fallback.scope;
|
|
934
1183
|
if (this.primary.getCollections && this.primary.setCollection && this.primary.removeCollection && this.fallback.getCollections && this.fallback.setCollection && this.fallback.removeCollection) {
|
|
935
1184
|
const fallbackGetCollections = this.fallback.getCollections.bind(this.fallback);
|
|
936
1185
|
this.getCollections = async () => {
|
|
@@ -985,12 +1234,12 @@ var FallbackStorageAdapter = class {
|
|
|
985
1234
|
(adapter) => adapter.setMany ? adapter.setMany(items) : Promise.all(items.map((item) => adapter.set(item))).then(() => void 0)
|
|
986
1235
|
);
|
|
987
1236
|
}
|
|
988
|
-
remove(id) {
|
|
989
|
-
return this.executeWrite((adapter) => adapter.remove(id));
|
|
1237
|
+
remove(id, scope) {
|
|
1238
|
+
return this.executeWrite((adapter) => adapter.remove(id, scope));
|
|
990
1239
|
}
|
|
991
|
-
removeMany(ids) {
|
|
1240
|
+
removeMany(ids, scope) {
|
|
992
1241
|
return this.executeWrite(
|
|
993
|
-
(adapter) => adapter.removeMany ? adapter.removeMany(ids) : Promise.all(ids.map((id) => adapter.remove(id))).then(() => void 0)
|
|
1242
|
+
(adapter) => adapter.removeMany ? adapter.removeMany(ids, scope) : Promise.all(ids.map((id) => adapter.remove(id, scope))).then(() => void 0)
|
|
994
1243
|
);
|
|
995
1244
|
}
|
|
996
1245
|
clear() {
|
|
@@ -1114,8 +1363,8 @@ function createStorageAdapter(options) {
|
|
|
1114
1363
|
} : {},
|
|
1115
1364
|
set: async (item) => options.set(item),
|
|
1116
1365
|
...setMany ? { setMany: async (items) => setMany(items) } : {},
|
|
1117
|
-
remove: async (id) => options.remove(id),
|
|
1118
|
-
...removeMany ? { removeMany: async (ids) => removeMany(ids) } : {},
|
|
1366
|
+
remove: async (id, scope) => options.remove(id, scope),
|
|
1367
|
+
...removeMany ? { removeMany: async (ids, scope) => removeMany(ids, scope) } : {},
|
|
1119
1368
|
clear: async () => options.clear(),
|
|
1120
1369
|
...merge ? { merge: async (items) => merge(items) } : {},
|
|
1121
1370
|
...subscribe ? {
|
|
@@ -1205,6 +1454,9 @@ var LocalStorageAdapter = class {
|
|
|
1205
1454
|
);
|
|
1206
1455
|
}
|
|
1207
1456
|
async getCollectionMemberships() {
|
|
1457
|
+
return this.readMemberships();
|
|
1458
|
+
}
|
|
1459
|
+
readMemberships() {
|
|
1208
1460
|
if (!this.storage) return [];
|
|
1209
1461
|
const storageKey = `${this.storageKey}:memberships`;
|
|
1210
1462
|
let raw;
|
|
@@ -1227,12 +1479,12 @@ var LocalStorageAdapter = class {
|
|
|
1227
1479
|
}
|
|
1228
1480
|
async setCollectionMembership(membership) {
|
|
1229
1481
|
if (!isKeepCollectionMembership(membership)) throw new Error("Collection membership is invalid.");
|
|
1230
|
-
const current =
|
|
1482
|
+
const current = this.readMemberships();
|
|
1231
1483
|
const sameMembership = (entry) => entry.collectionId === membership.collectionId && entry.itemId === membership.itemId && isSameKeepScope(entry.scope, membership.scope);
|
|
1232
1484
|
await this.writeMemberships([...current.filter((entry) => !sameMembership(entry)), membership], "set");
|
|
1233
1485
|
}
|
|
1234
1486
|
async removeCollectionMembership(collectionId, itemId, scope) {
|
|
1235
|
-
const current =
|
|
1487
|
+
const current = this.readMemberships();
|
|
1236
1488
|
await this.writeMemberships(
|
|
1237
1489
|
current.filter(
|
|
1238
1490
|
(entry) => entry.collectionId !== collectionId || entry.itemId !== itemId || !isSameKeepScope(entry.scope, scope)
|
|
@@ -1245,24 +1497,37 @@ var LocalStorageAdapter = class {
|
|
|
1245
1497
|
}
|
|
1246
1498
|
async setMany(items) {
|
|
1247
1499
|
const current = await this.getAll();
|
|
1248
|
-
const
|
|
1249
|
-
for (const item of items)
|
|
1250
|
-
this.write([...
|
|
1500
|
+
const byScopeAndId = new Map(current.map((item) => [getItemStorageKey(item), item]));
|
|
1501
|
+
for (const item of items) byScopeAndId.set(getItemStorageKey(item), item);
|
|
1502
|
+
this.write([...byScopeAndId.values()], "set");
|
|
1251
1503
|
}
|
|
1252
|
-
async remove(id) {
|
|
1253
|
-
await this.removeMany([id]);
|
|
1504
|
+
async remove(id, scope) {
|
|
1505
|
+
await this.removeMany([id], scope);
|
|
1254
1506
|
}
|
|
1255
|
-
async removeMany(ids) {
|
|
1507
|
+
async removeMany(ids, scope) {
|
|
1256
1508
|
const idSet = new Set(ids);
|
|
1257
1509
|
const items = await this.getAll();
|
|
1258
1510
|
this.write(
|
|
1259
|
-
items.filter((item) => !idSet.has(item.id)),
|
|
1511
|
+
items.filter((item) => !idSet.has(item.id) || scope !== void 0 && !isSameKeepScope(item.scope, scope)),
|
|
1260
1512
|
"remove"
|
|
1261
1513
|
);
|
|
1514
|
+
const memberships = await this.getCollectionMemberships();
|
|
1515
|
+
const membershipIds = memberships.filter(
|
|
1516
|
+
(membership) => idSet.has(membership.itemId) && (scope === void 0 || isSameKeepScope(membership.scope, scope))
|
|
1517
|
+
);
|
|
1518
|
+
for (const membership of membershipIds) {
|
|
1519
|
+
await this.removeCollectionMembership(membership.collectionId, membership.itemId, membership.scope);
|
|
1520
|
+
}
|
|
1262
1521
|
}
|
|
1263
1522
|
async clear() {
|
|
1264
1523
|
if (!this.storage) return;
|
|
1265
1524
|
try {
|
|
1525
|
+
const items = await this.getAll();
|
|
1526
|
+
if (items.length > 0) await this.removeMany(items.map((item) => item.id));
|
|
1527
|
+
const memberships = await this.getCollectionMemberships();
|
|
1528
|
+
for (const membership of memberships) {
|
|
1529
|
+
await this.removeCollectionMembership(membership.collectionId, membership.itemId, membership.scope);
|
|
1530
|
+
}
|
|
1266
1531
|
this.storage.removeItem(this.storageKey);
|
|
1267
1532
|
} catch (cause) {
|
|
1268
1533
|
throw new KeepStorageAccessError({
|
|
@@ -1344,7 +1609,7 @@ var IndexedDBAdapter = class {
|
|
|
1344
1609
|
if (!isKeepItemArray(value)) {
|
|
1345
1610
|
throw new KeepStorageParseError({ operation: "getAll", storageKey: this.storageKey });
|
|
1346
1611
|
}
|
|
1347
|
-
return value;
|
|
1612
|
+
return value.map(decodeIndexedDBItem);
|
|
1348
1613
|
} catch (cause) {
|
|
1349
1614
|
if (cause instanceof KeepStorageParseError) throw cause;
|
|
1350
1615
|
throw new KeepStorageAccessError({ operation: "getAll", storageKey: this.storageKey, cause });
|
|
@@ -1415,9 +1680,12 @@ var IndexedDBAdapter = class {
|
|
|
1415
1680
|
const database = await this.open("set");
|
|
1416
1681
|
if (!database) return;
|
|
1417
1682
|
try {
|
|
1683
|
+
const identities = new Set(items.map(getItemIdentity));
|
|
1684
|
+
const previous = (await this.getAll()).filter((item) => identities.has(getItemIdentity(item)));
|
|
1418
1685
|
const transaction = database.transaction(this.storeName, "readwrite");
|
|
1419
1686
|
const objectStore = transaction.objectStore(this.storeName);
|
|
1420
|
-
for (const item of
|
|
1687
|
+
for (const item of previous) objectStore.delete(getStoredIndexedDBItemId(item));
|
|
1688
|
+
for (const item of items) objectStore.put(encodeIndexedDBItem(item));
|
|
1421
1689
|
await transactionToPromise2(transaction);
|
|
1422
1690
|
this.notifySubscribers();
|
|
1423
1691
|
} catch (cause) {
|
|
@@ -1427,17 +1695,25 @@ var IndexedDBAdapter = class {
|
|
|
1427
1695
|
throw new KeepStorageAccessError({ operation: "set", storageKey: this.storageKey, cause });
|
|
1428
1696
|
}
|
|
1429
1697
|
}
|
|
1430
|
-
async remove(id) {
|
|
1431
|
-
return this.removeMany([id]);
|
|
1698
|
+
async remove(id, scope) {
|
|
1699
|
+
return this.removeMany([id], scope);
|
|
1432
1700
|
}
|
|
1433
|
-
async removeMany(ids) {
|
|
1701
|
+
async removeMany(ids, scope) {
|
|
1434
1702
|
const database = await this.open("remove");
|
|
1435
1703
|
if (!database) return;
|
|
1436
1704
|
try {
|
|
1705
|
+
const idSet = new Set(ids);
|
|
1706
|
+
const removableIds = (await this.getAll()).filter((item) => idSet.has(item.id) && (scope === void 0 || isSameKeepScope(item.scope, scope))).map(getStoredIndexedDBItemId);
|
|
1437
1707
|
const transaction = database.transaction(this.storeName, "readwrite");
|
|
1438
1708
|
const objectStore = transaction.objectStore(this.storeName);
|
|
1439
|
-
for (const id of
|
|
1709
|
+
for (const id of removableIds) objectStore.delete(id);
|
|
1440
1710
|
await transactionToPromise2(transaction);
|
|
1711
|
+
const memberships = await this.getCollectionMemberships();
|
|
1712
|
+
for (const membership of memberships) {
|
|
1713
|
+
if (idSet.has(membership.itemId) && (scope === void 0 || isSameKeepScope(membership.scope, scope))) {
|
|
1714
|
+
await this.removeCollectionMembership(membership.collectionId, membership.itemId, membership.scope);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1441
1717
|
this.notifySubscribers();
|
|
1442
1718
|
} catch (cause) {
|
|
1443
1719
|
throw new KeepStorageAccessError({ operation: "remove", storageKey: this.storageKey, cause });
|
|
@@ -1447,6 +1723,12 @@ var IndexedDBAdapter = class {
|
|
|
1447
1723
|
const database = await this.open("clear");
|
|
1448
1724
|
if (!database) return;
|
|
1449
1725
|
try {
|
|
1726
|
+
const items = await this.getAll();
|
|
1727
|
+
if (items.length > 0) await this.removeMany(items.map((item) => item.id));
|
|
1728
|
+
const memberships = await this.getCollectionMemberships();
|
|
1729
|
+
for (const membership of memberships) {
|
|
1730
|
+
await this.removeCollectionMembership(membership.collectionId, membership.itemId, membership.scope);
|
|
1731
|
+
}
|
|
1450
1732
|
const transaction = database.transaction(this.storeName, "readwrite");
|
|
1451
1733
|
transaction.objectStore(this.storeName).clear();
|
|
1452
1734
|
await transactionToPromise2(transaction);
|
|
@@ -1563,9 +1845,39 @@ var IndexedDBAdapter = class {
|
|
|
1563
1845
|
});
|
|
1564
1846
|
}
|
|
1565
1847
|
};
|
|
1848
|
+
var indexedDBStoredItemId = /* @__PURE__ */ Symbol("keepkit.indexeddb-stored-item-id");
|
|
1849
|
+
function encodeIndexedDBItem(item) {
|
|
1850
|
+
if (!item.scope) return item;
|
|
1851
|
+
return {
|
|
1852
|
+
...item,
|
|
1853
|
+
id: createScopedIndexedDBItemId(item),
|
|
1854
|
+
__keepkitOriginalId: item.id
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function decodeIndexedDBItem(item) {
|
|
1858
|
+
const originalId = item.__keepkitOriginalId;
|
|
1859
|
+
const isEncoded = typeof originalId === "string" && item.scope !== void 0 && item.id === createScopedIndexedDBItemId({ ...item, id: originalId });
|
|
1860
|
+
const { __keepkitOriginalId: _encodedOriginalId, ...record } = item;
|
|
1861
|
+
const decoded = isEncoded ? { ...record, id: originalId } : item;
|
|
1862
|
+
Object.defineProperty(decoded, indexedDBStoredItemId, { value: item.id });
|
|
1863
|
+
return decoded;
|
|
1864
|
+
}
|
|
1865
|
+
function getStoredIndexedDBItemId(item) {
|
|
1866
|
+
return item[indexedDBStoredItemId] ?? createScopedIndexedDBItemId(item);
|
|
1867
|
+
}
|
|
1868
|
+
function createScopedIndexedDBItemId(item) {
|
|
1869
|
+
if (!item.scope) return item.id;
|
|
1870
|
+
return `\0keepkit-scope:${JSON.stringify([item.scope.tenantId ?? null, item.scope.userId ?? null, item.id])}`;
|
|
1871
|
+
}
|
|
1872
|
+
function getItemIdentity(item) {
|
|
1873
|
+
return JSON.stringify([item.scope?.tenantId ?? null, item.scope?.userId ?? null, item.id]);
|
|
1874
|
+
}
|
|
1566
1875
|
function collectionKey(id, scope) {
|
|
1567
1876
|
return JSON.stringify([scope?.tenantId ?? null, scope?.userId ?? null, id]);
|
|
1568
1877
|
}
|
|
1878
|
+
function getItemStorageKey(item) {
|
|
1879
|
+
return JSON.stringify([item.scope?.tenantId ?? null, item.scope?.userId ?? null, item.id]);
|
|
1880
|
+
}
|
|
1569
1881
|
function membershipKey(collectionId, itemId, scope) {
|
|
1570
1882
|
return JSON.stringify([scope?.tenantId ?? null, scope?.userId ?? null, collectionId, itemId]);
|
|
1571
1883
|
}
|
|
@@ -1638,4 +1950,4 @@ export {
|
|
|
1638
1950
|
LocalStorageAdapter,
|
|
1639
1951
|
IndexedDBAdapter
|
|
1640
1952
|
};
|
|
1641
|
-
//# sourceMappingURL=chunk-
|
|
1953
|
+
//# sourceMappingURL=chunk-SLH4DFYQ.js.map
|