@keepkit/core 0.28.3 → 0.28.5
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 +26 -2
- package/dist/{chunk-IIGN4U7N.js → chunk-AUNFZU3A.js} +61 -6
- package/dist/chunk-AUNFZU3A.js.map +1 -0
- package/dist/{chunk-MZFZQIM5.js → chunk-NZLAW4LY.js} +467 -27
- package/dist/chunk-NZLAW4LY.js.map +1 -0
- package/dist/core.d.ts +164 -7
- package/dist/core.js +483 -7
- package/dist/core.js.map +1 -1
- package/dist/react.d.ts +5 -5
- package/dist/react.js +58 -22
- package/dist/react.js.map +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/{scope-BsU6xvN9.d.ts → scope-BteyH-b9.d.ts} +9 -3
- package/dist/storage.d.ts +47 -9
- package/dist/storage.js +1 -1
- package/dist/{store-C7pEDMLm.d.ts → store-DVP3QyVp.d.ts} +13 -4
- package/dist/{types-CJIbuqTO.d.ts → types-SKIL2p2c.d.ts} +43 -3
- package/package.json +1 -1
- package/dist/chunk-IIGN4U7N.js.map +0 -1
- package/dist/chunk-MZFZQIM5.js.map +0 -1
|
@@ -81,6 +81,12 @@ function isKeepItem(value) {
|
|
|
81
81
|
function isKeepItemArray(value) {
|
|
82
82
|
return Array.isArray(value) && value.every(isKeepItem);
|
|
83
83
|
}
|
|
84
|
+
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));
|
|
86
|
+
}
|
|
87
|
+
function isKeepCollectionMembership(value) {
|
|
88
|
+
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));
|
|
89
|
+
}
|
|
84
90
|
function isRecord(value) {
|
|
85
91
|
return typeof value === "object" && value !== null;
|
|
86
92
|
}
|
|
@@ -102,6 +108,28 @@ var ScopedStorageAdapter = class {
|
|
|
102
108
|
this.scope = scope;
|
|
103
109
|
const scopeKey = getKeepScopeKey(scope);
|
|
104
110
|
this.storageKey = base.storageKey ? base.storageKey.endsWith(scopeKey) && scopeKey ? base.storageKey : `${base.storageKey}${scopeKey}` : void 0;
|
|
111
|
+
if (base.getCollections && base.setCollection && base.removeCollection) {
|
|
112
|
+
const getCollections = base.getCollections.bind(base);
|
|
113
|
+
const setCollection = base.setCollection.bind(base);
|
|
114
|
+
const removeCollection = base.removeCollection.bind(base);
|
|
115
|
+
this.getCollections = async () => {
|
|
116
|
+
const collections = await getCollections();
|
|
117
|
+
return this.scope ? collections.filter((collection) => isSameKeepScope(collection.scope, this.scope)) : collections;
|
|
118
|
+
};
|
|
119
|
+
this.setCollection = (collection) => setCollection({ ...collection, ...this.scope ? { scope: this.scope } : {} });
|
|
120
|
+
this.removeCollection = (id) => removeCollection(id, this.scope);
|
|
121
|
+
}
|
|
122
|
+
if (base.getCollectionMemberships && base.setCollectionMembership && base.removeCollectionMembership) {
|
|
123
|
+
const getMemberships = base.getCollectionMemberships.bind(base);
|
|
124
|
+
const setMembership = base.setCollectionMembership.bind(base);
|
|
125
|
+
const removeMembership = base.removeCollectionMembership.bind(base);
|
|
126
|
+
this.getCollectionMemberships = async () => {
|
|
127
|
+
const memberships = await getMemberships();
|
|
128
|
+
return this.scope ? memberships.filter((membership) => isSameKeepScope(membership.scope, this.scope)) : memberships;
|
|
129
|
+
};
|
|
130
|
+
this.setCollectionMembership = (membership) => setMembership({ ...membership, ...this.scope ? { scope: this.scope } : {} });
|
|
131
|
+
this.removeCollectionMembership = (collectionId, itemId) => removeMembership(collectionId, itemId, this.scope);
|
|
132
|
+
}
|
|
105
133
|
}
|
|
106
134
|
async getAll() {
|
|
107
135
|
const items = await this.base.getAll();
|
|
@@ -211,7 +239,7 @@ var LocalStorageSyncQueueAdapter = class {
|
|
|
211
239
|
} catch (cause) {
|
|
212
240
|
throw Object.assign(new Error("KeepKit sync queue contains invalid JSON."), { cause });
|
|
213
241
|
}
|
|
214
|
-
if (!Array.isArray(value) || !value.every(
|
|
242
|
+
if (!Array.isArray(value) || !value.every(isSyncQueueOperation)) {
|
|
215
243
|
throw new Error("KeepKit sync queue contains invalid operations.");
|
|
216
244
|
}
|
|
217
245
|
return value;
|
|
@@ -241,7 +269,7 @@ var IndexedDBSyncQueueAdapter = class {
|
|
|
241
269
|
if (!database) return [];
|
|
242
270
|
const transaction = database.transaction(this.storeName, "readonly");
|
|
243
271
|
const value = await requestToPromise(transaction.objectStore(this.storeName).getAll());
|
|
244
|
-
if (!Array.isArray(value) || !value.every(
|
|
272
|
+
if (!Array.isArray(value) || !value.every(isSyncQueueOperation)) {
|
|
245
273
|
throw new Error("KeepKit sync queue contains invalid operations.");
|
|
246
274
|
}
|
|
247
275
|
return value;
|
|
@@ -362,6 +390,69 @@ var SyncStorageAdapter = class {
|
|
|
362
390
|
this.retryDelayMs = Math.max(0, options.retryDelayMs ?? 0);
|
|
363
391
|
this.retryBackoff = Math.max(1, options.retryBackoff ?? 2);
|
|
364
392
|
this.storageKey = this.local.storageKey;
|
|
393
|
+
if (this.local.getCollections && this.local.setCollection && this.local.removeCollection) {
|
|
394
|
+
const getCollections = this.local.getCollections.bind(this.local);
|
|
395
|
+
const setCollection = this.local.setCollection.bind(this.local);
|
|
396
|
+
const removeCollection = this.local.removeCollection.bind(this.local);
|
|
397
|
+
this.getCollections = async () => {
|
|
398
|
+
const collections = await getCollections();
|
|
399
|
+
const scope = this.scope;
|
|
400
|
+
return scope ? collections.filter((collection) => sameScope(collection.scope, scope)) : collections;
|
|
401
|
+
};
|
|
402
|
+
this.setCollection = async (collection) => {
|
|
403
|
+
const scopedCollection = { ...collection, ...this.scope ? { scope: this.scope } : {} };
|
|
404
|
+
if (this.remote.pushCollection) {
|
|
405
|
+
const operation = this.createCollectionOperation("upsert", scopedCollection.id, scopedCollection);
|
|
406
|
+
await this.enqueueBeforeLocalWrite(operation);
|
|
407
|
+
try {
|
|
408
|
+
await setCollection(scopedCollection);
|
|
409
|
+
} catch (cause) {
|
|
410
|
+
await this.removeQueued(operation.operationId);
|
|
411
|
+
throw cause;
|
|
412
|
+
}
|
|
413
|
+
this.notifyDataListeners();
|
|
414
|
+
this.setPendingState();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
await setCollection(scopedCollection);
|
|
418
|
+
this.notifyDataListeners();
|
|
419
|
+
};
|
|
420
|
+
this.removeCollection = async (id) => {
|
|
421
|
+
if (this.remote.pushCollection) {
|
|
422
|
+
const operation = this.createCollectionOperation("remove", id);
|
|
423
|
+
await this.enqueueBeforeLocalWrite(operation);
|
|
424
|
+
try {
|
|
425
|
+
await removeCollection(id, this.scope);
|
|
426
|
+
} catch (cause) {
|
|
427
|
+
await this.removeQueued(operation.operationId);
|
|
428
|
+
throw cause;
|
|
429
|
+
}
|
|
430
|
+
this.notifyDataListeners();
|
|
431
|
+
this.setPendingState();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
await removeCollection(id, this.scope);
|
|
435
|
+
this.notifyDataListeners();
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
if (this.local.getCollectionMemberships && this.local.setCollectionMembership && this.local.removeCollectionMembership) {
|
|
439
|
+
const getMemberships = this.local.getCollectionMemberships.bind(this.local);
|
|
440
|
+
const setMembership = this.local.setCollectionMembership.bind(this.local);
|
|
441
|
+
const removeMembership = this.local.removeCollectionMembership.bind(this.local);
|
|
442
|
+
this.getCollectionMemberships = async () => {
|
|
443
|
+
const memberships = await getMemberships();
|
|
444
|
+
const scope = this.scope;
|
|
445
|
+
return scope ? memberships.filter((membership) => sameScope(membership.scope, scope)) : memberships;
|
|
446
|
+
};
|
|
447
|
+
this.setCollectionMembership = async (membership) => {
|
|
448
|
+
await setMembership({ ...membership, ...this.scope ? { scope: this.scope } : {} });
|
|
449
|
+
this.notifyDataListeners();
|
|
450
|
+
};
|
|
451
|
+
this.removeCollectionMembership = async (collectionId, itemId) => {
|
|
452
|
+
await removeMembership(collectionId, itemId, this.scope);
|
|
453
|
+
this.notifyDataListeners();
|
|
454
|
+
};
|
|
455
|
+
}
|
|
365
456
|
if (typeof window !== "undefined") {
|
|
366
457
|
this.onlineHandler = () => void this.flushSync();
|
|
367
458
|
window.addEventListener("online", this.onlineHandler);
|
|
@@ -496,6 +587,16 @@ var SyncStorageAdapter = class {
|
|
|
496
587
|
for (const operation of [...this.queueItems]) {
|
|
497
588
|
if (this.disposed) return;
|
|
498
589
|
try {
|
|
590
|
+
if (isCollectionSyncOperation(operation)) {
|
|
591
|
+
if (!this.remote.pushCollection) throw new Error("Remote sync driver does not support collection sync.");
|
|
592
|
+
await this.pushCollectionWithRetry(operation);
|
|
593
|
+
await this.removeQueued(operation.operationId);
|
|
594
|
+
this.updateState({
|
|
595
|
+
status: this.queueItems.length > 0 ? "syncing" : "synced",
|
|
596
|
+
lastSyncedAt: this.now()
|
|
597
|
+
});
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
499
600
|
const result = await this.pushWithRetry(operation);
|
|
500
601
|
if (result.type === "conflict") {
|
|
501
602
|
const local = operation.item;
|
|
@@ -560,6 +661,17 @@ var SyncStorageAdapter = class {
|
|
|
560
661
|
...this.scope ? { scope: this.scope } : {}
|
|
561
662
|
};
|
|
562
663
|
}
|
|
664
|
+
createCollectionOperation(type, id, collection) {
|
|
665
|
+
return {
|
|
666
|
+
operationId: `${this.clientId}:${this.now()}:${createId()}`,
|
|
667
|
+
type,
|
|
668
|
+
entity: "collection",
|
|
669
|
+
id,
|
|
670
|
+
...collection ? { collection } : {},
|
|
671
|
+
createdAt: this.now(),
|
|
672
|
+
...this.scope ? { scope: this.scope } : {}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
563
675
|
applyScope(item) {
|
|
564
676
|
return this.scope ? { ...item, scope: this.scope } : item;
|
|
565
677
|
}
|
|
@@ -576,6 +688,20 @@ var SyncStorageAdapter = class {
|
|
|
576
688
|
}
|
|
577
689
|
}
|
|
578
690
|
}
|
|
691
|
+
async pushCollectionWithRetry(operation) {
|
|
692
|
+
let attempt = 0;
|
|
693
|
+
while (true) {
|
|
694
|
+
try {
|
|
695
|
+
await this.remote.pushCollection?.({ ...operation, attempts: attempt });
|
|
696
|
+
return;
|
|
697
|
+
} catch (error) {
|
|
698
|
+
if (isKeepSyncAuthError(error) || attempt >= this.maxRetries) throw error;
|
|
699
|
+
attempt += 1;
|
|
700
|
+
const delay = this.retryDelayMs * this.retryBackoff ** (attempt - 1);
|
|
701
|
+
if (delay > 0) await wait(delay);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
579
705
|
async enqueueBeforeLocalWrite(operation) {
|
|
580
706
|
await this.enqueueManyBeforeLocalWrite([operation]);
|
|
581
707
|
}
|
|
@@ -584,7 +710,8 @@ var SyncStorageAdapter = class {
|
|
|
584
710
|
const next = [...this.queueItems];
|
|
585
711
|
for (const operation of operations) {
|
|
586
712
|
for (let index = next.length - 1; index >= 0; index -= 1) {
|
|
587
|
-
|
|
713
|
+
const queued = next[index];
|
|
714
|
+
if (!queued || getQueueIdentity(queued) !== getQueueIdentity(operation)) continue;
|
|
588
715
|
next.splice(index, 1);
|
|
589
716
|
}
|
|
590
717
|
next.push(operation);
|
|
@@ -644,30 +771,53 @@ var SyncStorageAdapter = class {
|
|
|
644
771
|
});
|
|
645
772
|
}
|
|
646
773
|
async pullRemote() {
|
|
647
|
-
if (!this.remote.pull) return true;
|
|
774
|
+
if (!this.remote.pull && !this.remote.pullCollections) return true;
|
|
648
775
|
try {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
|
|
776
|
+
if (this.remote.pull) {
|
|
777
|
+
const remoteItems = await this.remote.pull();
|
|
778
|
+
const pendingIds = new Set(
|
|
779
|
+
this.queueItems.filter((operation) => operation.entity !== "collection").map((operation) => operation.id)
|
|
780
|
+
);
|
|
781
|
+
const localItems = await this.getAll();
|
|
782
|
+
const localById = new Map(localItems.map((item) => [item.id, item]));
|
|
783
|
+
const remoteById = /* @__PURE__ */ new Map();
|
|
784
|
+
for (const item of remoteItems) {
|
|
785
|
+
if (this.scope && item.scope && !sameScope(item.scope, this.scope)) continue;
|
|
786
|
+
if (pendingIds.has(item.id)) continue;
|
|
787
|
+
const previous = remoteById.get(item.id);
|
|
788
|
+
remoteById.set(item.id, previous ? mergeKeepItemLists([item], [previous])[0] ?? item : item);
|
|
789
|
+
}
|
|
790
|
+
const incoming = [...remoteById.values()].flatMap((item) => {
|
|
791
|
+
const current = localById.get(item.id);
|
|
792
|
+
if (current && item.updatedAt < current.updatedAt && (item.lastOpenedAt ?? 0) <= (current.lastOpenedAt ?? 0)) {
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
const merged = current ? mergeKeepItemLists([item], [current])[0] ?? item : item;
|
|
796
|
+
return [this.applyScope(merged)];
|
|
797
|
+
});
|
|
798
|
+
if (incoming.length > 0) {
|
|
799
|
+
await persistKeepItems(this.local, incoming);
|
|
800
|
+
this.notifyDataListeners();
|
|
801
|
+
}
|
|
659
802
|
}
|
|
660
|
-
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
803
|
+
if (this.remote.pullCollections && this.local.getCollections && this.local.setCollection && this.local.removeCollection) {
|
|
804
|
+
const remoteCollections = await this.remote.pullCollections();
|
|
805
|
+
const localCollections = await this.local.getCollections();
|
|
806
|
+
const pendingIds = new Set(this.queueItems.filter(isCollectionSyncOperation).map((operation) => operation.id));
|
|
807
|
+
const remoteById = new Map(
|
|
808
|
+
remoteCollections.filter((collection) => !this.scope || sameScope(collection.scope, this.scope)).map((collection) => [collection.id, collection])
|
|
809
|
+
);
|
|
810
|
+
for (const collection of remoteById.values()) {
|
|
811
|
+
if (pendingIds.has(collection.id)) continue;
|
|
812
|
+
await this.local.setCollection({ ...collection, ...this.scope ? { scope: this.scope } : {} });
|
|
664
813
|
}
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
814
|
+
for (const collection of localCollections) {
|
|
815
|
+
if (pendingIds.has(collection.id) || remoteById.has(collection.id)) continue;
|
|
816
|
+
if (this.scope && !sameScope(collection.scope, this.scope)) continue;
|
|
817
|
+
await this.local.removeCollection(collection.id, this.scope);
|
|
818
|
+
}
|
|
819
|
+
this.notifyDataListeners();
|
|
820
|
+
}
|
|
671
821
|
return true;
|
|
672
822
|
} catch (error) {
|
|
673
823
|
this.updateState({ status: "error", error });
|
|
@@ -688,7 +838,17 @@ var SyncStorageAdapter = class {
|
|
|
688
838
|
};
|
|
689
839
|
function isSyncOperation(value) {
|
|
690
840
|
if (!isRecord(value)) return false;
|
|
691
|
-
return typeof value.operationId === "string" && (value.type === "upsert" || value.type === "remove") && typeof value.id === "string" && typeof value.createdAt === "number";
|
|
841
|
+
return typeof value.operationId === "string" && (value.type === "upsert" || value.type === "remove") && typeof value.id === "string" && typeof value.createdAt === "number" && (value.entity === void 0 || value.entity === "item" || value.entity === "collection") && (value.collection === void 0 || isKeepCollectionDefinition(value.collection)) && (value.entity !== "collection" || value.type !== "upsert" || isKeepCollectionDefinition(value.collection));
|
|
842
|
+
}
|
|
843
|
+
function isCollectionSyncOperation(value) {
|
|
844
|
+
if (!isRecord(value)) return false;
|
|
845
|
+
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
|
+
}
|
|
847
|
+
function isSyncQueueOperation(value) {
|
|
848
|
+
return isSyncOperation(value) || isCollectionSyncOperation(value);
|
|
849
|
+
}
|
|
850
|
+
function getQueueIdentity(operation) {
|
|
851
|
+
return `${operation.entity === "collection" ? "collection" : "item"}:${operation.id}`;
|
|
692
852
|
}
|
|
693
853
|
function createId() {
|
|
694
854
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
@@ -771,6 +931,45 @@ var FallbackStorageAdapter = class {
|
|
|
771
931
|
this.migrateFallbackOnEmpty = options.migrateFallbackOnEmpty ?? false;
|
|
772
932
|
this.mirrorWrites = options.mirrorWrites ?? false;
|
|
773
933
|
this.storageKey = options.fallback.storageKey ?? options.primary.storageKey;
|
|
934
|
+
if (this.primary.getCollections && this.primary.setCollection && this.primary.removeCollection && this.fallback.getCollections && this.fallback.setCollection && this.fallback.removeCollection) {
|
|
935
|
+
const fallbackGetCollections = this.fallback.getCollections.bind(this.fallback);
|
|
936
|
+
this.getCollections = async () => {
|
|
937
|
+
const collections = await this.execute((adapter) => {
|
|
938
|
+
if (!adapter.getCollections) throw new Error("Collection storage is unavailable.");
|
|
939
|
+
return adapter.getCollections();
|
|
940
|
+
});
|
|
941
|
+
if (this.active === "primary" && this.migrateFallbackOnEmpty && collections.length === 0) {
|
|
942
|
+
const fallbackCollections = await fallbackGetCollections();
|
|
943
|
+
for (const collection of fallbackCollections) await this.setCollection?.(collection);
|
|
944
|
+
return fallbackCollections;
|
|
945
|
+
}
|
|
946
|
+
return collections;
|
|
947
|
+
};
|
|
948
|
+
this.setCollection = (collection) => this.executeWrite((adapter) => {
|
|
949
|
+
if (!adapter.setCollection) throw new Error("Collection storage is unavailable.");
|
|
950
|
+
return adapter.setCollection(collection);
|
|
951
|
+
});
|
|
952
|
+
this.removeCollection = (id, scope) => this.executeWrite((adapter) => {
|
|
953
|
+
if (!adapter.removeCollection) throw new Error("Collection storage is unavailable.");
|
|
954
|
+
return adapter.removeCollection(id, scope);
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
if (this.primary.getCollectionMemberships && this.primary.setCollectionMembership && this.primary.removeCollectionMembership && this.fallback.getCollectionMemberships && this.fallback.setCollectionMembership && this.fallback.removeCollectionMembership) {
|
|
958
|
+
const fallbackGetMemberships = this.fallback.getCollectionMemberships.bind(this.fallback);
|
|
959
|
+
this.getCollectionMemberships = async () => {
|
|
960
|
+
const memberships = await this.execute(
|
|
961
|
+
(adapter) => adapter.getCollectionMemberships?.() ?? Promise.resolve([])
|
|
962
|
+
);
|
|
963
|
+
if (this.active !== "primary" || !this.migrateFallbackOnEmpty || memberships.length > 0) return memberships;
|
|
964
|
+
const fallbackMemberships = await fallbackGetMemberships();
|
|
965
|
+
for (const membership of fallbackMemberships) await this.setCollectionMembership?.(membership);
|
|
966
|
+
return fallbackMemberships;
|
|
967
|
+
};
|
|
968
|
+
this.setCollectionMembership = (membership) => this.executeWrite((adapter) => adapter.setCollectionMembership?.(membership) ?? Promise.resolve());
|
|
969
|
+
this.removeCollectionMembership = (collectionId, itemId, scope) => this.executeWrite(
|
|
970
|
+
(adapter) => adapter.removeCollectionMembership?.(collectionId, itemId, scope) ?? Promise.resolve()
|
|
971
|
+
);
|
|
972
|
+
}
|
|
774
973
|
}
|
|
775
974
|
get isUsingFallback() {
|
|
776
975
|
return this.active === "fallback";
|
|
@@ -895,8 +1094,24 @@ function createStorageAdapter(options) {
|
|
|
895
1094
|
const subscribe = options.subscribe;
|
|
896
1095
|
const setMany = options.setMany;
|
|
897
1096
|
const removeMany = options.removeMany;
|
|
1097
|
+
const getCollections = options.getCollections;
|
|
1098
|
+
const setCollection = options.setCollection;
|
|
1099
|
+
const removeCollection = options.removeCollection;
|
|
1100
|
+
const getCollectionMemberships = options.getCollectionMemberships;
|
|
1101
|
+
const setCollectionMembership = options.setCollectionMembership;
|
|
1102
|
+
const removeCollectionMembership = options.removeCollectionMembership;
|
|
898
1103
|
return {
|
|
899
1104
|
getAll: async () => options.getAll(),
|
|
1105
|
+
...getCollections && setCollection && removeCollection ? {
|
|
1106
|
+
getCollections: async () => getCollections(),
|
|
1107
|
+
setCollection: async (collection) => setCollection(collection),
|
|
1108
|
+
removeCollection: async (id, scope) => removeCollection(id, scope)
|
|
1109
|
+
} : {},
|
|
1110
|
+
...getCollectionMemberships && setCollectionMembership && removeCollectionMembership ? {
|
|
1111
|
+
getCollectionMemberships: async () => getCollectionMemberships(),
|
|
1112
|
+
setCollectionMembership: async (membership) => setCollectionMembership(membership),
|
|
1113
|
+
removeCollectionMembership: async (collectionId, itemId, scope) => removeCollectionMembership(collectionId, itemId, scope)
|
|
1114
|
+
} : {},
|
|
900
1115
|
set: async (item) => options.set(item),
|
|
901
1116
|
...setMany ? { setMany: async (items) => setMany(items) } : {},
|
|
902
1117
|
remove: async (id) => options.remove(id),
|
|
@@ -945,6 +1160,86 @@ var LocalStorageAdapter = class {
|
|
|
945
1160
|
});
|
|
946
1161
|
}
|
|
947
1162
|
}
|
|
1163
|
+
async getCollections() {
|
|
1164
|
+
if (!this.storage) return [];
|
|
1165
|
+
const storageKey = `${this.storageKey}:collections`;
|
|
1166
|
+
let raw;
|
|
1167
|
+
try {
|
|
1168
|
+
raw = this.storage.getItem(storageKey);
|
|
1169
|
+
} catch (cause) {
|
|
1170
|
+
throw new KeepStorageAccessError({ operation: "getAll", storageKey, cause });
|
|
1171
|
+
}
|
|
1172
|
+
if (!raw) return [];
|
|
1173
|
+
try {
|
|
1174
|
+
const value = JSON.parse(raw);
|
|
1175
|
+
if (!Array.isArray(value) || !value.every(isKeepCollectionDefinition)) {
|
|
1176
|
+
throw new KeepStorageParseError({ operation: "getAll", storageKey });
|
|
1177
|
+
}
|
|
1178
|
+
return value;
|
|
1179
|
+
} catch (cause) {
|
|
1180
|
+
if (cause instanceof KeepStorageParseError) throw cause;
|
|
1181
|
+
throw new KeepStorageParseError({ operation: "getAll", storageKey, cause });
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
async setCollection(collection) {
|
|
1185
|
+
if (!isKeepCollectionDefinition(collection)) throw new Error("Collection id and name are required.");
|
|
1186
|
+
const current = await this.getCollections();
|
|
1187
|
+
await this.writeCollections(
|
|
1188
|
+
[
|
|
1189
|
+
...current.filter((entry) => entry.id !== collection.id || !isSameKeepScope(entry.scope, collection.scope)),
|
|
1190
|
+
collection
|
|
1191
|
+
],
|
|
1192
|
+
"set"
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
async removeCollection(id, scope) {
|
|
1196
|
+
const current = await this.getCollections();
|
|
1197
|
+
await this.writeCollections(
|
|
1198
|
+
current.filter((entry) => entry.id !== id || !isSameKeepScope(entry.scope, scope)),
|
|
1199
|
+
"remove"
|
|
1200
|
+
);
|
|
1201
|
+
const memberships = await this.getCollectionMemberships();
|
|
1202
|
+
await this.writeMemberships(
|
|
1203
|
+
memberships.filter((membership) => membership.collectionId !== id || !isSameKeepScope(membership.scope, scope)),
|
|
1204
|
+
"remove"
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
async getCollectionMemberships() {
|
|
1208
|
+
if (!this.storage) return [];
|
|
1209
|
+
const storageKey = `${this.storageKey}:memberships`;
|
|
1210
|
+
let raw;
|
|
1211
|
+
try {
|
|
1212
|
+
raw = this.storage.getItem(storageKey);
|
|
1213
|
+
} catch (cause) {
|
|
1214
|
+
throw new KeepStorageAccessError({ operation: "getAll", storageKey, cause });
|
|
1215
|
+
}
|
|
1216
|
+
if (!raw) return [];
|
|
1217
|
+
try {
|
|
1218
|
+
const value = JSON.parse(raw);
|
|
1219
|
+
if (!Array.isArray(value) || !value.every(isKeepCollectionMembership)) {
|
|
1220
|
+
throw new KeepStorageParseError({ operation: "getAll", storageKey });
|
|
1221
|
+
}
|
|
1222
|
+
return value;
|
|
1223
|
+
} catch (cause) {
|
|
1224
|
+
if (cause instanceof KeepStorageParseError) throw cause;
|
|
1225
|
+
throw new KeepStorageParseError({ operation: "getAll", storageKey, cause });
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async setCollectionMembership(membership) {
|
|
1229
|
+
if (!isKeepCollectionMembership(membership)) throw new Error("Collection membership is invalid.");
|
|
1230
|
+
const current = await this.getCollectionMemberships();
|
|
1231
|
+
const sameMembership = (entry) => entry.collectionId === membership.collectionId && entry.itemId === membership.itemId && isSameKeepScope(entry.scope, membership.scope);
|
|
1232
|
+
await this.writeMemberships([...current.filter((entry) => !sameMembership(entry)), membership], "set");
|
|
1233
|
+
}
|
|
1234
|
+
async removeCollectionMembership(collectionId, itemId, scope) {
|
|
1235
|
+
const current = await this.getCollectionMemberships();
|
|
1236
|
+
await this.writeMemberships(
|
|
1237
|
+
current.filter(
|
|
1238
|
+
(entry) => entry.collectionId !== collectionId || entry.itemId !== itemId || !isSameKeepScope(entry.scope, scope)
|
|
1239
|
+
),
|
|
1240
|
+
"remove"
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
948
1243
|
async set(item) {
|
|
949
1244
|
await this.setMany([item]);
|
|
950
1245
|
}
|
|
@@ -985,7 +1280,8 @@ var LocalStorageAdapter = class {
|
|
|
985
1280
|
subscribe(listener) {
|
|
986
1281
|
if (typeof window === "undefined") return () => void 0;
|
|
987
1282
|
const handleStorage = (event) => {
|
|
988
|
-
if (event.key !== null && event.key !== this.storageKey)
|
|
1283
|
+
if (event.key !== null && event.key !== this.storageKey && event.key !== `${this.storageKey}:collections` && event.key !== `${this.storageKey}:memberships`)
|
|
1284
|
+
return;
|
|
989
1285
|
listener();
|
|
990
1286
|
};
|
|
991
1287
|
window.addEventListener("storage", handleStorage);
|
|
@@ -1010,6 +1306,26 @@ var LocalStorageAdapter = class {
|
|
|
1010
1306
|
});
|
|
1011
1307
|
}
|
|
1012
1308
|
}
|
|
1309
|
+
async writeCollections(collections, operation) {
|
|
1310
|
+
if (!this.storage) return;
|
|
1311
|
+
const storageKey = `${this.storageKey}:collections`;
|
|
1312
|
+
try {
|
|
1313
|
+
this.storage.setItem(storageKey, JSON.stringify(collections));
|
|
1314
|
+
} catch (cause) {
|
|
1315
|
+
if (isQuotaExceededError(cause)) throw new KeepStorageQuotaError({ operation, storageKey, cause });
|
|
1316
|
+
throw new KeepStorageAccessError({ operation, storageKey, cause });
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
async writeMemberships(memberships, operation) {
|
|
1320
|
+
if (!this.storage) return;
|
|
1321
|
+
const storageKey = `${this.storageKey}:memberships`;
|
|
1322
|
+
try {
|
|
1323
|
+
this.storage.setItem(storageKey, JSON.stringify(memberships));
|
|
1324
|
+
} catch (cause) {
|
|
1325
|
+
if (isQuotaExceededError(cause)) throw new KeepStorageQuotaError({ operation, storageKey, cause });
|
|
1326
|
+
throw new KeepStorageAccessError({ operation, storageKey, cause });
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1013
1329
|
};
|
|
1014
1330
|
var IndexedDBAdapter = class {
|
|
1015
1331
|
constructor(options = {}) {
|
|
@@ -1034,6 +1350,64 @@ var IndexedDBAdapter = class {
|
|
|
1034
1350
|
throw new KeepStorageAccessError({ operation: "getAll", storageKey: this.storageKey, cause });
|
|
1035
1351
|
}
|
|
1036
1352
|
}
|
|
1353
|
+
async getCollections() {
|
|
1354
|
+
const database = await this.openCollections("getAll");
|
|
1355
|
+
if (!database) return [];
|
|
1356
|
+
const storageKey = `${this.storageKey}:collections`;
|
|
1357
|
+
try {
|
|
1358
|
+
const transaction = database.transaction("collections", "readonly");
|
|
1359
|
+
const value = await requestToPromise2(transaction.objectStore("collections").getAll());
|
|
1360
|
+
if (!Array.isArray(value) || !value.every((entry) => isRecord(entry) && typeof entry.key === "string" && isKeepCollectionDefinition(entry))) {
|
|
1361
|
+
throw new KeepStorageParseError({ operation: "getAll", storageKey });
|
|
1362
|
+
}
|
|
1363
|
+
return value.map(({ key: _key, ...collection }) => collection);
|
|
1364
|
+
} catch (cause) {
|
|
1365
|
+
if (cause instanceof KeepStorageParseError) throw cause;
|
|
1366
|
+
throw new KeepStorageAccessError({ operation: "getAll", storageKey, cause });
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
async setCollection(collection) {
|
|
1370
|
+
if (!isKeepCollectionDefinition(collection)) throw new Error("Collection id and name are required.");
|
|
1371
|
+
await this.writeCollection(
|
|
1372
|
+
"set",
|
|
1373
|
+
(store) => store.put({ ...collection, key: collectionKey(collection.id, collection.scope) })
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
async removeCollection(id, scope) {
|
|
1377
|
+
await this.writeCollection("remove", (store) => store.delete(collectionKey(id, scope)));
|
|
1378
|
+
const memberships = await this.getCollectionMemberships();
|
|
1379
|
+
for (const membership of memberships) {
|
|
1380
|
+
if (membership.collectionId === id && isSameKeepScope(membership.scope, scope)) {
|
|
1381
|
+
await this.removeCollectionMembership(id, membership.itemId, scope);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
async getCollectionMemberships() {
|
|
1386
|
+
const database = await this.openCollections("getAll");
|
|
1387
|
+
if (!database) return [];
|
|
1388
|
+
const storageKey = `${this.storageKey}:memberships`;
|
|
1389
|
+
try {
|
|
1390
|
+
const transaction = database.transaction("memberships", "readonly");
|
|
1391
|
+
const value = await requestToPromise2(transaction.objectStore("memberships").getAll());
|
|
1392
|
+
if (!Array.isArray(value) || !value.every((entry) => isRecord(entry) && typeof entry.key === "string" && isKeepCollectionMembership(entry))) {
|
|
1393
|
+
throw new KeepStorageParseError({ operation: "getAll", storageKey });
|
|
1394
|
+
}
|
|
1395
|
+
return value.map(({ key: _key, ...membership }) => membership);
|
|
1396
|
+
} catch (cause) {
|
|
1397
|
+
if (cause instanceof KeepStorageParseError) throw cause;
|
|
1398
|
+
throw new KeepStorageAccessError({ operation: "getAll", storageKey, cause });
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
async setCollectionMembership(membership) {
|
|
1402
|
+
if (!isKeepCollectionMembership(membership)) throw new Error("Collection membership is invalid.");
|
|
1403
|
+
await this.writeCollectionMembership(
|
|
1404
|
+
"set",
|
|
1405
|
+
(store) => store.put({ ...membership, key: membershipKey(membership.collectionId, membership.itemId, membership.scope) })
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
async removeCollectionMembership(collectionId, itemId, scope) {
|
|
1409
|
+
await this.writeCollectionMembership("remove", (store) => store.delete(membershipKey(collectionId, itemId, scope)));
|
|
1410
|
+
}
|
|
1037
1411
|
async set(item) {
|
|
1038
1412
|
return this.setMany([item]);
|
|
1039
1413
|
}
|
|
@@ -1103,6 +1477,64 @@ var IndexedDBAdapter = class {
|
|
|
1103
1477
|
channel.postMessage({ type: "keepkit:changed" });
|
|
1104
1478
|
channel.close();
|
|
1105
1479
|
}
|
|
1480
|
+
async writeCollection(operation, write) {
|
|
1481
|
+
const database = await this.openCollections(operation);
|
|
1482
|
+
if (!database) return;
|
|
1483
|
+
const storageKey = `${this.storageKey}:collections`;
|
|
1484
|
+
try {
|
|
1485
|
+
const transaction = database.transaction("collections", "readwrite");
|
|
1486
|
+
write(transaction.objectStore("collections"));
|
|
1487
|
+
await transactionToPromise2(transaction);
|
|
1488
|
+
this.notifySubscribers();
|
|
1489
|
+
} catch (cause) {
|
|
1490
|
+
if (isQuotaExceededError(cause)) throw new KeepStorageQuotaError({ operation, storageKey, cause });
|
|
1491
|
+
throw new KeepStorageAccessError({ operation, storageKey, cause });
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
async writeCollectionMembership(operation, write) {
|
|
1495
|
+
const database = await this.openCollections(operation);
|
|
1496
|
+
if (!database) return;
|
|
1497
|
+
const storageKey = `${this.storageKey}:memberships`;
|
|
1498
|
+
try {
|
|
1499
|
+
const transaction = database.transaction("memberships", "readwrite");
|
|
1500
|
+
write(transaction.objectStore("memberships"));
|
|
1501
|
+
await transactionToPromise2(transaction);
|
|
1502
|
+
this.notifySubscribers();
|
|
1503
|
+
} catch (cause) {
|
|
1504
|
+
if (isQuotaExceededError(cause)) throw new KeepStorageQuotaError({ operation, storageKey, cause });
|
|
1505
|
+
throw new KeepStorageAccessError({ operation, storageKey, cause });
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
openCollections(operation) {
|
|
1509
|
+
if (!this.indexedDB) return Promise.resolve(void 0);
|
|
1510
|
+
const storageKey = `${this.storageKey}:collections`;
|
|
1511
|
+
if (!this.collectionDatabasePromise) {
|
|
1512
|
+
this.collectionDatabasePromise = new Promise((resolve, reject) => {
|
|
1513
|
+
let request;
|
|
1514
|
+
try {
|
|
1515
|
+
request = this.indexedDB?.open(storageKey, 2);
|
|
1516
|
+
} catch (cause) {
|
|
1517
|
+
reject(cause);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
request.onupgradeneeded = () => {
|
|
1521
|
+
if (!request.result.objectStoreNames.contains("collections")) {
|
|
1522
|
+
request.result.createObjectStore("collections", { keyPath: "key" });
|
|
1523
|
+
}
|
|
1524
|
+
if (!request.result.objectStoreNames.contains("memberships")) {
|
|
1525
|
+
request.result.createObjectStore("memberships", { keyPath: "key" });
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
request.onsuccess = () => resolve(request.result);
|
|
1529
|
+
request.onerror = () => reject(request.error);
|
|
1530
|
+
request.onblocked = () => reject(request.error ?? new Error("IndexedDB collection storage open was blocked."));
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
return this.collectionDatabasePromise.catch((cause) => {
|
|
1534
|
+
this.collectionDatabasePromise = void 0;
|
|
1535
|
+
throw new KeepStorageAccessError({ operation, storageKey, cause });
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1106
1538
|
open(operation) {
|
|
1107
1539
|
if (!this.indexedDB) return Promise.resolve(void 0);
|
|
1108
1540
|
if (!this.databasePromise) {
|
|
@@ -1131,6 +1563,12 @@ var IndexedDBAdapter = class {
|
|
|
1131
1563
|
});
|
|
1132
1564
|
}
|
|
1133
1565
|
};
|
|
1566
|
+
function collectionKey(id, scope) {
|
|
1567
|
+
return JSON.stringify([scope?.tenantId ?? null, scope?.userId ?? null, id]);
|
|
1568
|
+
}
|
|
1569
|
+
function membershipKey(collectionId, itemId, scope) {
|
|
1570
|
+
return JSON.stringify([scope?.tenantId ?? null, scope?.userId ?? null, collectionId, itemId]);
|
|
1571
|
+
}
|
|
1134
1572
|
function getBrowserStorage2() {
|
|
1135
1573
|
if (typeof window === "undefined") return void 0;
|
|
1136
1574
|
try {
|
|
@@ -1176,6 +1614,8 @@ export {
|
|
|
1176
1614
|
removeKeepItems,
|
|
1177
1615
|
mergeKeepItemLists,
|
|
1178
1616
|
isKeepItem,
|
|
1617
|
+
isKeepCollectionDefinition,
|
|
1618
|
+
isKeepCollectionMembership,
|
|
1179
1619
|
isRecord,
|
|
1180
1620
|
getKeepScopeKey,
|
|
1181
1621
|
isSameKeepScope,
|
|
@@ -1198,4 +1638,4 @@ export {
|
|
|
1198
1638
|
LocalStorageAdapter,
|
|
1199
1639
|
IndexedDBAdapter
|
|
1200
1640
|
};
|
|
1201
|
-
//# sourceMappingURL=chunk-
|
|
1641
|
+
//# sourceMappingURL=chunk-NZLAW4LY.js.map
|