@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
package/README.md
CHANGED
|
@@ -52,9 +52,9 @@ const list = useKeepList({
|
|
|
52
52
|
|
|
53
53
|
保存対象の公開状態は`KeepItem.status`(`expired`、`removed`、`private`など)と`statusReason`で保持できます。`KeepProvider`の`validateItem` / `resolveItem`を指定すると、引数なしの`revalidateItems()`で検証できます。`revalidateItems`に`removeStatuses`を渡すと検出したアイテムを保存一覧から削除します。`SyncStorageAdapter`は`userId`、`tenantId`、`maxRetries`、`retryDelayMs`、`retryBackoff`に対応し、`retrySync()`で失敗後の同期を再開できます。
|
|
54
54
|
|
|
55
|
-
v0.28.
|
|
55
|
+
v0.28.7では、スコープをまたぐ結合と並行所属変更を補強し、履歴・鑑賞記録・進行位置の同一キー更新を保護します。閲覧履歴の文脈、鑑賞記録の作成/更新日時、見出しIDによる読書位置も利用できます。
|
|
56
56
|
|
|
57
|
-
`createAuthenticatedSyncKit`は、リクエストごとの`getAuthToken`、注入可能なpush/pull transport、401/403時の再認証callback、永続オフラインキュー、`setScope
|
|
57
|
+
`createAuthenticatedSyncKit`は、リクエストごとの`getAuthToken`、注入可能なpush/pull transport、401/403時の再認証callback、永続オフラインキュー、`setScope`による安全なユーザー/テナント切替を提供します。スコープを切り替えたり対象スコープのキューを削除・全消去しても、他スコープの未送信操作は保持されます。詳細は[`examples/authenticated-sync`](../../examples/authenticated-sync/README.md)を参照してください。
|
|
58
58
|
|
|
59
59
|
### 0.4.xからの変更
|
|
60
60
|
|
|
@@ -111,9 +111,9 @@ Use `@keepkit/core/core` for framework-neutral code, `@keepkit/core/react` for R
|
|
|
111
111
|
|
|
112
112
|
`KeepItem.status` and `statusReason` preserve source availability such as `expired`, `removed`, and `private`. Configure `KeepProvider` with `validateItem` / `resolveItem` to make `revalidateItems()` use those hooks by default. Pass `removeStatuses` to remove detected items from storage. `SyncStorageAdapter` supports scoped queues with `userId` and `tenantId`, configurable retries/backoff, and explicit `retrySync()` recovery.
|
|
113
113
|
|
|
114
|
-
v0.28.
|
|
114
|
+
v0.28.7 strengthens scope-aware merging, concurrent membership changes, and same-key updates to history, viewing records, and progress. It also includes history context, viewing-record timestamps, and heading-ID reading positions.
|
|
115
115
|
|
|
116
|
-
`createAuthenticatedSyncKit` provides a per-request `getAuthToken`, injectable push/pull transport, 401/403 reauthentication callbacks, persistent offline queues, and `setScope` for safe user or tenant changes. See [`examples/authenticated-sync`](../../examples/authenticated-sync/README.md) for a recipe.
|
|
116
|
+
`createAuthenticatedSyncKit` provides a per-request `getAuthToken`, injectable push/pull transport, 401/403 reauthentication callbacks, persistent offline queues, and `setScope` for safe user or tenant changes. Scope changes and scoped queue removal or clearing preserve pending operations for other scopes. See [`examples/authenticated-sync`](../../examples/authenticated-sync/README.md) for a recipe.
|
|
117
117
|
|
|
118
118
|
The v0.5 factory returns `Provider`, `Button`, `useContext`, `useItem`, `useList`, and `useShortcut`. Existing v0.4 applications should follow the migration guide in the repository root.
|
|
119
119
|
|
|
@@ -128,3 +128,65 @@ The standard browser adapters persist explicit collection definitions and course
|
|
|
128
128
|
`KeepItem` と `KeepItemInput` は `archived`、`pinned`、`collectionId` を任意で受け取れます。`useKeepList` は未アーカイブを既定とし、`archived: true` でアーカイブを、`archiveScope: "all"` で両方を、`collectionId` で完全一致のコレクションを取得できます。`useKeepCollections({ targetType, orderBy })` は全保存アイテムからコレクションID・名前・件数を重複なく導出します。`pinnedFirst: true` は既存の順序を保ったままピン留め項目を先頭へ安定移動します。`useKeepItem` と Provider には `toggleArchive`、`archiveItem`、`unarchiveItem`、`togglePin`、`moveToCollection` を追加しました。各操作は `updatedAt` を更新し、空のコレクション ID はプロパティを削除して、既存の永続化・rollback・plugin・`onChange` 経路を利用します。
|
|
129
129
|
|
|
130
130
|
標準ブラウザーストレージは、空のコレクション、変更した名前、コース別の所属と順序をアイテムとは別に永続化します。独自の`StorageAdapter`では`getCollections`、`setCollection`、`removeCollection`と`getCollectionMemberships`、`setCollectionMembership`、`removeCollectionMembership`を実装すると保存できます。`clear()`は保存アイテムのみを消去し、JSONバックアップv2はコレクションと所属も含みます。リモート同期は任意のコレクション用transportを追加すると利用できます。
|
|
131
|
+
|
|
132
|
+
### v0.28.7の保存・復元補完
|
|
133
|
+
|
|
134
|
+
同じlocalStorageキーへの履歴・鑑賞記録・進行位置の書き込みは、Web Locks APIが使えるブラウザーではタブ・インスタンス間で直列化されます。同一realm内の複数インスタンスもキーごとに直列化します。`saveItem()`は同じガイドの既存進行情報を読み、指定フィールドだけ更新して他の位置情報を保ちます。鑑賞記録・進行レコードの`set()`で古い`updatedAt`の値が届いた場合は、新しい保存値を残します。Web Locks APIがないブラウザーでは、競合上書きを避けるため書き込み前に`KeepActivityStorageError`または`KeepProgressStorageError`で失敗します。ブラウザー保存先がない場合も同じエラー型で失敗します。SSRでは初期化できますが、保存操作はブラウザー側で呼び出してください。
|
|
135
|
+
|
|
136
|
+
既存ストレージを開いた後、コース別所属を読む機能を使う前に`migrateLegacyCollectionMemberships(storage)`を実行して旧単一所属データを移行します。既存所属を上書きせず、所属保存後に旧`collectionId`を消去するため、利用者が所属を外した後の再実行で復活しません。途中でadapterの書き込みが失敗した場合はエラーを返し、再実行しても所属は重複しません。対象scopeに定義名のないIDは戻り値の`missingCollectionIds`で確認できます。項目削除時はlocalStorage、IndexedDB、スコープadapterが所属も消します。同じadapterへの`addKeepItemToCollection()`・`removeKeepItemFromCollection()`・`reorderKeepCollectionItems()`は、所属の読み取りから順序の再採番・書き込みまで直列化されます。
|
|
137
|
+
|
|
138
|
+
バックアップには保存アイテム・コレクション定義・所属のみが含まれ、閲覧履歴・鑑賞記録・進行位置は含まれません。`importItems()`の置換モードは空のバックアップでも対象範囲を空にします。v2バックアップと戻り値の`scopes`は含まれる利用者・テナント範囲を示し、`includedData`はデータ種別を示します。`imported`、`failed`、`total`は保存アイテムの件数で、`applied`は項目・定義・所属の適用数を個別に示します。スコープ付きadapterは別scopeを含むbackupを変更前に拒否します。v1の`collectionId`は名前を復元できないため`missingCollectionIds`で通知します。途中失敗は`KeepBackupImportError.failedStage`と`applied`で確認します。置換にrollbackはありません。merge時の同ID名は既定でバックアップ側を採用し、`collectionNameConflict: "existing"`を指定すると既存名を維持します。共有adapterでは項目・コレクションのIDと`tenantId`・`userId`で識別するため、別scopeの同IDデータは別に保たれます。scope未指定と`{}`は同じ扱いで、空文字列の`userId`または`tenantId`は明示値として別scopeです。複数scopeのバックアップは共有adapterへ復元でき、スコープ付きadapterは別scopeを変更前に拒否します。
|
|
139
|
+
|
|
140
|
+
履歴の文脈には`record(itemId, viewedAt, { language: "ja" })`で言語などを保存でき、`getAll()`で履歴と一緒に取得できます。鑑賞記録は`viewedAt`と`createdAt` / `updatedAt`を分けます。日時がない旧記録は最初に更新した時刻を作成時刻として補います。読書位置は数値offsetと安定した見出しID文字列を受け付けます。`getItem()`には必要な音声ID・言語・コンテンツ版を渡して互換位置だけを取得します。
|
|
141
|
+
|
|
142
|
+
同期adapterに`RemoteSyncDriver.pushMembership()` / `pullMemberships()`を指定すると、所属の追加・解除・コース別順序を永続キューで同期できます。所属解除はアイテム・コレクション削除より先に送信し、pull時は存在しない親への所属を適用しません。membership transportを実装しないdriverでは所属はローカル保存のみです。コレクション定義には`revision`と`updatedAt`を保存でき、操作は`baseRevision`を送ります。driverは`KeepCollectionSyncResult`で競合と選択結果を返し、アプリは`resolveCollectionSyncConflict()`からローカルまたはリモートを選べます。削除tombstoneの保持と競合方針はdriverまたはサーバーで実装します。これらは低レベルAPIであり標準画面へ自動接続されません。
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import {
|
|
146
|
+
LocalStorageKeepHistoryStorage,
|
|
147
|
+
LocalStorageKeepProgressStorage,
|
|
148
|
+
LocalStorageKeepViewingRecordStorage,
|
|
149
|
+
exportItems,
|
|
150
|
+
importItems,
|
|
151
|
+
migrateLegacyCollectionMemberships,
|
|
152
|
+
} from "@keepkit/core/core";
|
|
153
|
+
import { createBrowserStorageAdapter } from "@keepkit/core/storage";
|
|
154
|
+
|
|
155
|
+
const storage = createBrowserStorageAdapter({ key: "my-app:items", scope: { userId: "user-1" } });
|
|
156
|
+
const history = new LocalStorageKeepHistoryStorage({ key: "my-app:user-1:history" });
|
|
157
|
+
await history.record("guide-1", Date.now(), { language: "ja" });
|
|
158
|
+
await migrateLegacyCollectionMemberships(storage);
|
|
159
|
+
const backup = await exportItems(storage);
|
|
160
|
+
await importItems(storage, backup, { mode: "replace" });
|
|
161
|
+
|
|
162
|
+
const progress = new LocalStorageKeepProgressStorage({ key: "my-app:user-1:progress" });
|
|
163
|
+
await progress.saveItem("guide-1", {
|
|
164
|
+
audioId: "guide-1-ja",
|
|
165
|
+
audioPositionMs: 1250,
|
|
166
|
+
readingPosition: "heading-intro",
|
|
167
|
+
language: "ja",
|
|
168
|
+
contentVersion: "v2",
|
|
169
|
+
});
|
|
170
|
+
const resume = await progress.getItem("guide-1", {
|
|
171
|
+
audioId: "guide-1-ja",
|
|
172
|
+
language: "ja",
|
|
173
|
+
contentVersion: "v2",
|
|
174
|
+
});
|
|
175
|
+
const viewings = new LocalStorageKeepViewingRecordStorage({ key: "my-app:user-1:viewings" });
|
|
176
|
+
const record = { id: "viewing-1", itemId: "guide-1", viewedAt: Date.now(), note: "Finished" };
|
|
177
|
+
await viewings.set(record); // Reuse this stable ID when retrying the write.
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Persistence and restore improvements in v0.28.7
|
|
181
|
+
|
|
182
|
+
Writes to history, viewing records, and progress using the same localStorage key are serialized across tabs and instances when the browser provides the Web Locks API. Separate instances in one realm are also serialized by key. `saveItem()` reads existing progress for the same guide and updates only the supplied fields, preserving other positions. For viewing records and progress, `set()` does not replace a newer record with a value whose `updatedAt` is older. In a browser without Web Locks, writes fail before mutation with `KeepActivityStorageError` or `KeepProgressStorageError` to avoid silently overwriting concurrent data. The same error types report unavailable browser storage. Construction is SSR-safe; call persistent writes in the browser.
|
|
183
|
+
|
|
184
|
+
After opening existing storage and before reading course-specific memberships, run `migrateLegacyCollectionMemberships(storage)`. Existing memberships are never overwritten, and the legacy `collectionId` is cleared after migration so a later run will not recreate a membership the user removed. Adapter write errors are returned; rerunning after a partial failure is safe and does not duplicate memberships. IDs with no same-scope definition name are listed in `missingCollectionIds`. Removing a saved item also removes its memberships in localStorage, IndexedDB, and scoped adapters. `addKeepItemToCollection()`, `removeKeepItemFromCollection()`, and `reorderKeepCollectionItems()` serialize their read, reindex, and write steps when called concurrently with the same adapter instance.
|
|
185
|
+
|
|
186
|
+
Backups include saved items, collection definitions, and memberships. They exclude history, viewing records, and progress. Replace imports clear the target scope even for an empty backup. Version 2 backups and results expose represented `scopes`; scoped adapters reject data from a different scope before mutation. `imported`, `failed`, and `total` count saved items; `applied` reports items, definitions, and memberships separately. `includedData` identifies data types, and v1 collection IDs whose names cannot be restored are returned in `missingCollectionIds`. `KeepBackupImportError.failedStage` and `applied` describe partial application. Replace has no rollback. Merge keeps the backup name for a same-ID collision by default; choose `collectionNameConflict: "existing"` to retain the stored name. Shared adapters identify items and collections by ID plus `tenantId` and `userId`, keeping same-ID records in different scopes independent. An omitted scope and `{}` are equivalent; an empty-string `userId` or `tenantId` is an explicit, distinct scope. Shared adapters accept multi-scope backups, while scoped adapters reject another scope before mutation.
|
|
187
|
+
|
|
188
|
+
History context can be recorded with `record(itemId, viewedAt, { language: "ja" })` and retrieved with each entry from `getAll()`. Viewing records distinguish the self-reported `viewedAt` from `createdAt` / `updatedAt`. A legacy record without these timestamps receives its first-update time as its creation timestamp. Reading positions accept numeric offsets and stable heading ID strings; pass expected audio ID, language, and content version to `getItem()` to reject incompatible positions.
|
|
189
|
+
|
|
190
|
+
Implement `RemoteSyncDriver.pushMembership()` / `pullMemberships()` to sync membership changes and course-specific order through the durable queue. Membership removals are sent before item or collection deletions, and pulls skip memberships whose parent is missing. Without membership transport hooks, membership changes remain local. Collection definitions can store `revision` and `updatedAt`, and operations send `baseRevision`. Drivers return `KeepCollectionSyncResult` for conflicts and outcomes; the app can choose local or remote with `resolveCollectionSyncConflict()`. The server or driver owns deletion tombstones and conflict policy. These are low-level APIs and are not automatically connected to the standard UI.
|
|
191
|
+
|
|
192
|
+
The example above imports the persistence APIs from `@keepkit/core/core` and browser adapters from `@keepkit/core/storage`. Keep history and progress keys scoped by the host app's account or tenant when those records should not cross users. Reuse a viewing-record ID with `set()` when retrying so the event is not duplicated.
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
mergeKeepItemLists,
|
|
10
10
|
persistKeepItems,
|
|
11
11
|
removeKeepItems
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SLH4DFYQ.js";
|
|
13
13
|
|
|
14
14
|
// src/features/items/navigation.ts
|
|
15
15
|
function orderKeepItems(items) {
|
|
@@ -83,6 +83,52 @@ async function migrateKeepItems(source, target) {
|
|
|
83
83
|
await source.clear();
|
|
84
84
|
return merged;
|
|
85
85
|
}
|
|
86
|
+
async function migrateLegacyCollectionMemberships(storage) {
|
|
87
|
+
if (!storage.getCollectionMemberships || !storage.setCollectionMembership) {
|
|
88
|
+
throw new TypeError("The storage adapter does not support collection memberships.");
|
|
89
|
+
}
|
|
90
|
+
const [items, memberships, collections] = await Promise.all([
|
|
91
|
+
storage.getAll(),
|
|
92
|
+
storage.getCollectionMemberships(),
|
|
93
|
+
storage.getCollections?.() ?? Promise.resolve([])
|
|
94
|
+
]);
|
|
95
|
+
const existing = new Set(memberships.map(membershipKey));
|
|
96
|
+
const collectionIds = new Set(collections.map((collection) => collectionKey(collection.id, collection.scope)));
|
|
97
|
+
const missingCollectionIds = /* @__PURE__ */ new Set();
|
|
98
|
+
let migrated = 0;
|
|
99
|
+
let skipped = 0;
|
|
100
|
+
for (const item of items) {
|
|
101
|
+
if (!item.collectionId) continue;
|
|
102
|
+
const membership = {
|
|
103
|
+
collectionId: item.collectionId,
|
|
104
|
+
itemId: item.id,
|
|
105
|
+
order: item.order ?? 0,
|
|
106
|
+
...item.scope ? { scope: item.scope } : {}
|
|
107
|
+
};
|
|
108
|
+
const key = membershipKey(membership);
|
|
109
|
+
if (!collectionIds.has(collectionKey(item.collectionId, item.scope))) {
|
|
110
|
+
missingCollectionIds.add(item.collectionId);
|
|
111
|
+
}
|
|
112
|
+
if (existing.has(key)) {
|
|
113
|
+
skipped += 1;
|
|
114
|
+
const { collectionId: _legacyCollectionId2, ...migratedItem2 } = item;
|
|
115
|
+
await storage.set(migratedItem2);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
await storage.setCollectionMembership(membership);
|
|
119
|
+
existing.add(key);
|
|
120
|
+
migrated += 1;
|
|
121
|
+
const { collectionId: _legacyCollectionId, ...migratedItem } = item;
|
|
122
|
+
await storage.set(migratedItem);
|
|
123
|
+
}
|
|
124
|
+
return { migrated, skipped, missingCollectionIds: [...missingCollectionIds] };
|
|
125
|
+
}
|
|
126
|
+
function membershipKey(membership) {
|
|
127
|
+
return `${membership.scope?.tenantId ?? ""}\0${membership.scope?.userId ?? ""}\0${membership.collectionId}\0${membership.itemId}`;
|
|
128
|
+
}
|
|
129
|
+
function collectionKey(collectionId, scope) {
|
|
130
|
+
return `${scope?.tenantId ?? ""}\0${scope?.userId ?? ""}\0${collectionId}`;
|
|
131
|
+
}
|
|
86
132
|
|
|
87
133
|
// src/features/persistence/backup.ts
|
|
88
134
|
var KEEP_BACKUP_FORMAT = "keepkit";
|
|
@@ -101,28 +147,46 @@ var KeepBackupImportError = class extends Error {
|
|
|
101
147
|
this.mode = options.mode;
|
|
102
148
|
this.imported = options.imported;
|
|
103
149
|
this.failed = options.failed;
|
|
150
|
+
this.applied = options.applied ?? { items: options.imported, collections: 0, memberships: 0 };
|
|
151
|
+
this.failedStage = options.failedStage ?? "items";
|
|
104
152
|
if (options.cause !== void 0) this.cause = options.cause;
|
|
105
153
|
}
|
|
106
154
|
};
|
|
107
155
|
async function exportItems(adapter) {
|
|
156
|
+
const items = await adapter.getAll();
|
|
157
|
+
const collections = await adapter.getCollections?.() ?? [];
|
|
158
|
+
const memberships = await adapter.getCollectionMemberships?.() ?? [];
|
|
108
159
|
const backup = {
|
|
109
160
|
format: KEEP_BACKUP_FORMAT,
|
|
110
161
|
version: KEEP_BACKUP_VERSION,
|
|
111
162
|
exportedAt: Date.now(),
|
|
112
|
-
items
|
|
113
|
-
collections
|
|
114
|
-
memberships
|
|
163
|
+
items,
|
|
164
|
+
collections,
|
|
165
|
+
memberships,
|
|
166
|
+
scopes: collectScopes(items, collections, memberships, adapter.scope ? [adapter.scope] : [])
|
|
115
167
|
};
|
|
116
168
|
return JSON.stringify(backup, null, 2);
|
|
117
169
|
}
|
|
118
170
|
async function importItems(adapter, data, options = {}) {
|
|
119
171
|
const backup = parseBackup(data);
|
|
120
172
|
const mode = options.mode ?? "merge";
|
|
173
|
+
const scopes = collectScopes(backup.items, backup.collections, backup.memberships, backup.scopes);
|
|
174
|
+
if (adapter.scope && scopes.some((scope) => !sameScope(scope, adapter.scope))) {
|
|
175
|
+
throw new KeepBackupImportError("The backup contains data outside the active storage scope.", {
|
|
176
|
+
mode,
|
|
177
|
+
imported: 0,
|
|
178
|
+
failed: backup.items.length + backup.collections.length + backup.memberships.length,
|
|
179
|
+
applied: { items: 0, collections: 0, memberships: 0 },
|
|
180
|
+
failedStage: "validation"
|
|
181
|
+
});
|
|
182
|
+
}
|
|
121
183
|
if (backup.collections.length > 0 && (!adapter.getCollections || !adapter.setCollection || !adapter.removeCollection) || backup.memberships.length > 0 && (!adapter.getCollectionMemberships || !adapter.setCollectionMembership || !adapter.removeCollectionMembership)) {
|
|
122
184
|
throw new KeepBackupImportError("The storage adapter cannot restore collection data from this backup.", {
|
|
123
185
|
mode,
|
|
124
186
|
imported: 0,
|
|
125
|
-
failed: backup.items.length + backup.collections.length + backup.memberships.length
|
|
187
|
+
failed: backup.items.length + backup.collections.length + backup.memberships.length,
|
|
188
|
+
applied: { items: 0, collections: 0, memberships: 0 },
|
|
189
|
+
failedStage: "validation"
|
|
126
190
|
});
|
|
127
191
|
}
|
|
128
192
|
const validItems = [];
|
|
@@ -143,48 +207,87 @@ async function importItems(adapter, data, options = {}) {
|
|
|
143
207
|
throw cause;
|
|
144
208
|
}
|
|
145
209
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
await
|
|
151
|
-
await
|
|
152
|
-
|
|
153
|
-
throw new KeepBackupImportError("KeepKit could not merge the backup.", {
|
|
154
|
-
mode,
|
|
155
|
-
imported: 0,
|
|
156
|
-
failed: validItems.length + failed,
|
|
157
|
-
cause
|
|
158
|
-
});
|
|
210
|
+
const applied = { items: 0, collections: 0, memberships: 0 };
|
|
211
|
+
const missingCollectionIds = backup.version === 1 ? [...new Set(backup.items.flatMap((item) => item.collectionId ? [item.collectionId] : []))] : [];
|
|
212
|
+
try {
|
|
213
|
+
if (mode === "replace") {
|
|
214
|
+
await adapter.clear();
|
|
215
|
+
await restoreCollections(adapter, [], "replace", "backup", applied);
|
|
216
|
+
await restoreMemberships(adapter, [], "replace", applied);
|
|
159
217
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
218
|
+
} catch (cause) {
|
|
219
|
+
throw new KeepBackupImportError("KeepKit could not clear the data being replaced.", {
|
|
220
|
+
mode,
|
|
221
|
+
imported: applied.items,
|
|
222
|
+
failed: validItems.length + failed,
|
|
223
|
+
applied,
|
|
224
|
+
failedStage: "clear",
|
|
225
|
+
cause
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
for (const item of validItems) {
|
|
162
229
|
try {
|
|
163
|
-
await adapter
|
|
164
|
-
await
|
|
165
|
-
|
|
166
|
-
for (const item of validItems) {
|
|
167
|
-
await adapter.set(item);
|
|
168
|
-
imported += 1;
|
|
169
|
-
}
|
|
170
|
-
await restoreCollections(adapter, backup.collections, "replace");
|
|
171
|
-
await restoreMemberships(adapter, backup.memberships, "replace");
|
|
172
|
-
items = await adapter.getAll();
|
|
230
|
+
if (mode === "merge") await mergeKeepItems([item], adapter);
|
|
231
|
+
else await adapter.set(item);
|
|
232
|
+
applied.items += 1;
|
|
173
233
|
} catch (cause) {
|
|
174
|
-
throw new KeepBackupImportError(
|
|
234
|
+
throw new KeepBackupImportError(`KeepKit could not restore item ${item.id}.`, {
|
|
175
235
|
mode,
|
|
176
|
-
imported,
|
|
177
|
-
failed: validItems.length + failed -
|
|
236
|
+
imported: applied.items,
|
|
237
|
+
failed: validItems.length + failed - applied.items,
|
|
238
|
+
applied,
|
|
239
|
+
failedStage: "items",
|
|
178
240
|
cause
|
|
179
241
|
});
|
|
180
242
|
}
|
|
181
243
|
}
|
|
244
|
+
try {
|
|
245
|
+
await restoreCollections(adapter, backup.collections, mode, options.collectionNameConflict ?? "backup", applied);
|
|
246
|
+
} catch (cause) {
|
|
247
|
+
throw new KeepBackupImportError("KeepKit could not restore collection definitions.", {
|
|
248
|
+
mode,
|
|
249
|
+
imported: applied.items,
|
|
250
|
+
failed: validItems.length + failed - applied.items,
|
|
251
|
+
applied,
|
|
252
|
+
failedStage: "collections",
|
|
253
|
+
cause
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
await restoreMemberships(adapter, backup.memberships, mode, applied);
|
|
258
|
+
} catch (cause) {
|
|
259
|
+
throw new KeepBackupImportError("KeepKit could not restore collection memberships.", {
|
|
260
|
+
mode,
|
|
261
|
+
imported: applied.items,
|
|
262
|
+
failed: validItems.length + failed - applied.items,
|
|
263
|
+
applied,
|
|
264
|
+
failedStage: "memberships",
|
|
265
|
+
cause
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
let items;
|
|
269
|
+
try {
|
|
270
|
+
items = await adapter.getAll();
|
|
271
|
+
} catch (cause) {
|
|
272
|
+
throw new KeepBackupImportError("KeepKit restored the backup but could not read the resulting items.", {
|
|
273
|
+
mode,
|
|
274
|
+
imported: applied.items,
|
|
275
|
+
failed,
|
|
276
|
+
applied,
|
|
277
|
+
failedStage: "items",
|
|
278
|
+
cause
|
|
279
|
+
});
|
|
280
|
+
}
|
|
182
281
|
return {
|
|
183
282
|
mode,
|
|
184
|
-
imported:
|
|
283
|
+
imported: applied.items,
|
|
185
284
|
failed,
|
|
186
285
|
total: items.length,
|
|
187
|
-
items
|
|
286
|
+
items,
|
|
287
|
+
applied,
|
|
288
|
+
missingCollectionIds,
|
|
289
|
+
includedData: backup.version === 1 ? ["items"] : ["items", "collections", "memberships"],
|
|
290
|
+
scopes
|
|
188
291
|
};
|
|
189
292
|
}
|
|
190
293
|
function parseBackup(data) {
|
|
@@ -213,23 +316,47 @@ function parseBackup(data) {
|
|
|
213
316
|
exportedAt: value.exportedAt,
|
|
214
317
|
items: value.items,
|
|
215
318
|
collections: [],
|
|
216
|
-
memberships: []
|
|
319
|
+
memberships: [],
|
|
320
|
+
scopes: []
|
|
217
321
|
};
|
|
218
322
|
}
|
|
219
323
|
if (!Array.isArray(value.collections) || !value.collections.every(isKeepCollectionDefinition) || !Array.isArray(value.memberships) || !value.memberships.every(isKeepCollectionMembership)) {
|
|
220
324
|
throw new KeepBackupParseError("KeepKit backup contains invalid collection data.");
|
|
221
325
|
}
|
|
326
|
+
if (value.scopes !== void 0 && (!Array.isArray(value.scopes) || !value.scopes.every(isSyncScope))) {
|
|
327
|
+
throw new KeepBackupParseError("KeepKit backup contains invalid scope data.");
|
|
328
|
+
}
|
|
222
329
|
return value;
|
|
223
330
|
}
|
|
224
|
-
|
|
331
|
+
function collectScopes(items, collections, memberships, declaredScopes = []) {
|
|
332
|
+
const scopes = [
|
|
333
|
+
...declaredScopes,
|
|
334
|
+
...[...items, ...collections, ...memberships].flatMap((record) => record.scope ? [record.scope] : [])
|
|
335
|
+
].filter((scope, index, all) => all.findIndex((entry) => sameScope(entry, scope)) === index);
|
|
336
|
+
return scopes.map((scope) => ({ ...scope }));
|
|
337
|
+
}
|
|
338
|
+
function sameScope(left, right) {
|
|
339
|
+
return left.userId === right?.userId && left.tenantId === right?.tenantId;
|
|
340
|
+
}
|
|
341
|
+
function isSyncScope(value) {
|
|
342
|
+
return isRecord(value) && (value.userId === void 0 || typeof value.userId === "string") && (value.tenantId === void 0 || typeof value.tenantId === "string");
|
|
343
|
+
}
|
|
344
|
+
async function restoreCollections(adapter, collections, mode, conflict, applied) {
|
|
225
345
|
if (!adapter.getCollections || !adapter.setCollection || !adapter.removeCollection) return;
|
|
346
|
+
const existing = await adapter.getCollections();
|
|
226
347
|
if (mode === "replace") {
|
|
227
|
-
const
|
|
228
|
-
|
|
348
|
+
for (const collection of existing) await adapter.removeCollection(collection.id, collection.scope);
|
|
349
|
+
}
|
|
350
|
+
for (const collection of collections) {
|
|
351
|
+
const current = existing.find(
|
|
352
|
+
(entry) => entry.id === collection.id && sameScope(entry.scope ?? {}, collection.scope)
|
|
353
|
+
);
|
|
354
|
+
const value = mode === "merge" && conflict === "existing" && current ? { ...collection, name: current.name } : collection;
|
|
355
|
+
await adapter.setCollection(value);
|
|
356
|
+
applied.collections += 1;
|
|
229
357
|
}
|
|
230
|
-
for (const collection of collections) await adapter.setCollection(collection);
|
|
231
358
|
}
|
|
232
|
-
async function restoreMemberships(adapter, memberships, mode) {
|
|
359
|
+
async function restoreMemberships(adapter, memberships, mode, applied) {
|
|
233
360
|
if (!adapter.getCollectionMemberships || !adapter.setCollectionMembership || !adapter.removeCollectionMembership)
|
|
234
361
|
return;
|
|
235
362
|
if (mode === "replace") {
|
|
@@ -238,7 +365,10 @@ async function restoreMemberships(adapter, memberships, mode) {
|
|
|
238
365
|
await adapter.removeCollectionMembership(membership.collectionId, membership.itemId, membership.scope);
|
|
239
366
|
}
|
|
240
367
|
}
|
|
241
|
-
for (const membership of memberships)
|
|
368
|
+
for (const membership of memberships) {
|
|
369
|
+
await adapter.setCollectionMembership(membership);
|
|
370
|
+
applied.memberships += 1;
|
|
371
|
+
}
|
|
242
372
|
}
|
|
243
373
|
|
|
244
374
|
// src/features/items/query.ts
|
|
@@ -445,6 +575,7 @@ export {
|
|
|
445
575
|
moveKeepItem,
|
|
446
576
|
mergeKeepItems,
|
|
447
577
|
migrateKeepItems,
|
|
578
|
+
migrateLegacyCollectionMemberships,
|
|
448
579
|
KEEP_BACKUP_FORMAT,
|
|
449
580
|
KEEP_BACKUP_VERSION,
|
|
450
581
|
KeepBackupParseError,
|
|
@@ -460,4 +591,4 @@ export {
|
|
|
460
591
|
reconcileKeepItems,
|
|
461
592
|
KeepStore
|
|
462
593
|
};
|
|
463
|
-
//# sourceMappingURL=chunk-
|
|
594
|
+
//# sourceMappingURL=chunk-EMOPFB5D.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/features/items/navigation.ts","../src/features/persistence/migration.ts","../src/features/persistence/backup.ts","../src/features/items/query.ts","../src/features/items/revalidation.ts","../src/features/store/store.ts"],"sourcesContent":["import type { KeepItem } from \"./types\";\n\nexport type KeepNavigationState<TMeta = Record<string, unknown>> = {\n items: KeepItem<TMeta>[];\n currentIndex: number;\n currentPosition: number | null;\n currentItem: KeepItem<TMeta> | null;\n hasNext: boolean;\n hasPrev: boolean;\n nextItem: KeepItem<TMeta> | null;\n prevItem: KeepItem<TMeta> | null;\n};\n\n/** Return items in their persisted custom order, leaving legacy items stable. */\nexport function orderKeepItems<TMeta>(items: readonly KeepItem<TMeta>[]): KeepItem<TMeta>[] {\n if (!items.some((item) => item.order !== undefined)) return [...items];\n return items\n .map((item, index) => ({ item, index }))\n .sort((left, right) => {\n const leftOrder = left.item.order;\n const rightOrder = right.item.order;\n if (leftOrder === undefined && rightOrder !== undefined) return 1;\n if (leftOrder !== undefined && rightOrder === undefined) return -1;\n if (leftOrder !== undefined && rightOrder !== undefined && leftOrder !== rightOrder) {\n return leftOrder - rightOrder;\n }\n return left.index - right.index;\n })\n .map(({ item }) => item);\n}\n\n/** Derive the previous/current/next view for a list and an item id or index. */\nexport function getKeepNavigationState<TMeta = Record<string, unknown>>(\n source: readonly KeepItem<TMeta>[],\n current?: string | number,\n): KeepNavigationState<TMeta> {\n const items = orderKeepItems(source);\n const currentIndex =\n typeof current === \"number\"\n ? Number.isInteger(current) && current >= 0 && current < items.length\n ? current\n : -1\n : current === undefined\n ? -1\n : items.findIndex((item) => item.id === current);\n const currentItem = currentIndex >= 0 ? (items[currentIndex] ?? null) : null;\n return {\n items,\n currentIndex,\n currentPosition: currentIndex >= 0 ? currentIndex + 1 : null,\n currentItem,\n hasNext: currentIndex >= 0 && currentIndex < items.length - 1,\n hasPrev: currentIndex > 0,\n nextItem: currentIndex >= 0 ? (items[currentIndex + 1] ?? null) : null,\n prevItem: currentIndex > 0 ? (items[currentIndex - 1] ?? null) : null,\n };\n}\n\n/** Apply a partial or complete id order and assign stable zero-based positions. */\nexport function reorderKeepItems<TMeta = Record<string, unknown>>(\n source: readonly KeepItem<TMeta>[],\n orderedIds: readonly string[],\n): KeepItem<TMeta>[] {\n const byId = new Map(source.map((item) => [item.id, item]));\n const seen = new Set<string>();\n for (const id of orderedIds) {\n if (!byId.has(id)) throw new Error(`Cannot reorder unknown Keep item \"${id}\".`);\n if (seen.has(id)) throw new Error(`Cannot reorder Keep items with duplicate id \"${id}\".`);\n seen.add(id);\n }\n const current = orderKeepItems(source);\n const sequence = [...orderedIds, ...current.map((item) => item.id).filter((id) => !seen.has(id))];\n return sequence.map((id, order) => {\n const item = byId.get(id);\n if (!item) throw new Error(`Cannot reorder unknown Keep item \"${id}\".`);\n return { ...item, order };\n });\n}\n\n/** Move one item within the current custom/legacy order. */\nexport function moveKeepItem<TMeta = Record<string, unknown>>(\n source: readonly KeepItem<TMeta>[],\n id: string,\n targetIndex: number,\n): KeepItem<TMeta>[] {\n const items = orderKeepItems(source);\n const currentIndex = items.findIndex((item) => item.id === id);\n if (currentIndex < 0) throw new Error(`Cannot move unknown Keep item \"${id}\".`);\n if (!Number.isInteger(targetIndex)) throw new Error(\"Keep item targetIndex must be an integer.\");\n const nextIndex = Math.min(Math.max(0, targetIndex), items.length - 1);\n const [item] = items.splice(currentIndex, 1);\n items.splice(nextIndex, 0, item);\n return reorderKeepItems(\n items,\n items.map((entry) => entry.id),\n );\n}\n","import type { KeepCollectionMembership, KeepItem, StorageAdapter, SyncScope } from \"../items/types\";\nimport { mergeKeepItemLists } from \"./helpers\";\n\n/** Merge anonymous local items into a signed-in or remote adapter. */\nexport async function mergeKeepItems<TMeta>(\n localItems: KeepItem<TMeta>[],\n target: StorageAdapter<TMeta>,\n): Promise<KeepItem<TMeta>[]> {\n if (target.merge) return target.merge(localItems);\n\n const merged = mergeKeepItemLists(await target.getAll(), localItems);\n await Promise.all(merged.map((item) => target.set(item)));\n return merged;\n}\n\n/** Read anonymous items, merge them into the target, then clear the source. */\nexport async function migrateKeepItems<TMeta>(\n source: StorageAdapter<TMeta>,\n target: StorageAdapter<TMeta>,\n): Promise<KeepItem<TMeta>[]> {\n const localItems = await source.getAll();\n const merged = await mergeKeepItems(localItems, target);\n await source.clear();\n return merged;\n}\n\nexport type LegacyCollectionMigrationResult = {\n migrated: number;\n skipped: number;\n missingCollectionIds: string[];\n};\n\n/** Move legacy single-collection item fields into course-specific memberships once. */\nexport async function migrateLegacyCollectionMemberships<TMeta>(\n storage: StorageAdapter<TMeta>,\n): Promise<LegacyCollectionMigrationResult> {\n if (!storage.getCollectionMemberships || !storage.setCollectionMembership) {\n throw new TypeError(\"The storage adapter does not support collection memberships.\");\n }\n\n const [items, memberships, collections] = await Promise.all([\n storage.getAll(),\n storage.getCollectionMemberships(),\n storage.getCollections?.() ?? Promise.resolve([]),\n ]);\n const existing = new Set(memberships.map(membershipKey));\n const collectionIds = new Set(collections.map((collection) => collectionKey(collection.id, collection.scope)));\n const missingCollectionIds = new Set<string>();\n let migrated = 0;\n let skipped = 0;\n\n for (const item of items) {\n if (!item.collectionId) continue;\n const membership: KeepCollectionMembership = {\n collectionId: item.collectionId,\n itemId: item.id,\n order: item.order ?? 0,\n ...(item.scope ? { scope: item.scope } : {}),\n };\n const key = membershipKey(membership);\n if (!collectionIds.has(collectionKey(item.collectionId, item.scope))) {\n missingCollectionIds.add(item.collectionId);\n }\n if (existing.has(key)) {\n skipped += 1;\n const { collectionId: _legacyCollectionId, ...migratedItem } = item;\n await storage.set(migratedItem);\n continue;\n }\n await storage.setCollectionMembership(membership);\n existing.add(key);\n migrated += 1;\n const { collectionId: _legacyCollectionId, ...migratedItem } = item;\n await storage.set(migratedItem);\n }\n\n return { migrated, skipped, missingCollectionIds: [...missingCollectionIds] };\n}\n\nfunction membershipKey(membership: KeepCollectionMembership): string {\n return `${membership.scope?.tenantId ?? \"\"}\\u0000${membership.scope?.userId ?? \"\"}\\u0000${membership.collectionId}\\u0000${membership.itemId}`;\n}\n\nfunction collectionKey(collectionId: string, scope?: SyncScope): string {\n return `${scope?.tenantId ?? \"\"}\\u0000${scope?.userId ?? \"\"}\\u0000${collectionId}`;\n}\n","import type {\n KeepCollectionDefinition,\n KeepCollectionMembership,\n KeepInvalidItemPolicy,\n KeepItem,\n KeepSchema,\n StorageAdapter,\n SyncScope,\n} from \"../items/types\";\nimport { isKeepCollectionDefinition, isKeepCollectionMembership, isKeepItem, isRecord } from \"./helpers\";\nimport { mergeKeepItems } from \"./migration\";\nimport { validateKeepItem } from \"./schema\";\n\nexport const KEEP_BACKUP_FORMAT = \"keepkit\";\nexport const KEEP_BACKUP_VERSION = 2;\n\nexport type KeepBackupV1<TMeta = Record<string, unknown>> = {\n format: typeof KEEP_BACKUP_FORMAT;\n version: 1;\n exportedAt: number;\n items: KeepItem<TMeta>[];\n};\n\nexport type KeepBackupV2<TMeta = Record<string, unknown>> = {\n format: typeof KEEP_BACKUP_FORMAT;\n version: typeof KEEP_BACKUP_VERSION;\n exportedAt: number;\n items: KeepItem<TMeta>[];\n collections: KeepCollectionDefinition[];\n memberships: KeepCollectionMembership[];\n /** Scopes represented by the records in this backup. Omitted by early v2 backups. */\n scopes?: SyncScope[];\n};\n\nexport type KeepBackup<TMeta = Record<string, unknown>> = KeepBackupV1<TMeta> | KeepBackupV2<TMeta>;\n\ntype ParsedKeepBackup<TMeta> = Omit<KeepBackupV2<TMeta>, \"version\"> & { version: 1 | typeof KEEP_BACKUP_VERSION };\n\nexport type ImportItemsOptions<TMeta = unknown> = {\n mode?: \"replace\" | \"merge\";\n schema?: KeepSchema<TMeta>;\n invalidItemPolicy?: KeepInvalidItemPolicy;\n onInvalidItem?: (error: unknown, item: KeepItem<unknown>) => void;\n /** Resolves a same-ID collection name collision when merging. Defaults to the backup name. */\n collectionNameConflict?: \"backup\" | \"existing\";\n};\n\nexport type KeepBackupDataType = \"items\" | \"collections\" | \"memberships\";\nexport type KeepBackupAppliedCounts = { items: number; collections: number; memberships: number };\n\nexport type ImportItemsResult<TMeta = Record<string, unknown>> = {\n mode: \"replace\" | \"merge\";\n imported: number;\n failed: number;\n total: number;\n items: KeepItem<TMeta>[];\n applied: KeepBackupAppliedCounts;\n missingCollectionIds: string[];\n includedData: KeepBackupDataType[];\n scopes: SyncScope[];\n};\n\nexport class KeepBackupParseError extends Error {\n readonly cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n this.name = \"KeepBackupParseError\";\n if (options?.cause !== undefined) this.cause = options.cause;\n }\n}\n\nexport class KeepBackupImportError extends Error {\n readonly mode: \"replace\" | \"merge\";\n readonly imported: number;\n readonly failed: number;\n readonly applied: KeepBackupAppliedCounts;\n readonly failedStage: \"items\" | \"collections\" | \"memberships\" | \"clear\" | \"validation\";\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options: {\n mode: \"replace\" | \"merge\";\n imported: number;\n failed: number;\n applied?: KeepBackupAppliedCounts;\n failedStage?: \"items\" | \"collections\" | \"memberships\" | \"clear\" | \"validation\";\n cause?: unknown;\n },\n ) {\n super(message);\n this.name = \"KeepBackupImportError\";\n this.mode = options.mode;\n this.imported = options.imported;\n this.failed = options.failed;\n this.applied = options.applied ?? { items: options.imported, collections: 0, memberships: 0 };\n this.failedStage = options.failedStage ?? \"items\";\n if (options.cause !== undefined) this.cause = options.cause;\n }\n}\n\n/** Serialize all adapter data into a versioned JSON backup. */\nexport async function exportItems<TMeta>(adapter: StorageAdapter<TMeta>): Promise<string> {\n const items = await adapter.getAll();\n const collections = (await adapter.getCollections?.()) ?? [];\n const memberships = (await adapter.getCollectionMemberships?.()) ?? [];\n const backup: KeepBackupV2<TMeta> = {\n format: KEEP_BACKUP_FORMAT,\n version: KEEP_BACKUP_VERSION,\n exportedAt: Date.now(),\n items,\n collections,\n memberships,\n scopes: collectScopes(items, collections, memberships, adapter.scope ? [adapter.scope] : []),\n };\n return JSON.stringify(backup, null, 2);\n}\n\n/** Validate and restore a backup, either replacing or merging existing data. */\nexport async function importItems<TMeta>(\n adapter: StorageAdapter<TMeta>,\n data: string | KeepBackup<TMeta>,\n options: ImportItemsOptions<TMeta> = {},\n): Promise<ImportItemsResult<TMeta>> {\n const backup = parseBackup<TMeta>(data);\n const mode = options.mode ?? \"merge\";\n const scopes = collectScopes(backup.items, backup.collections, backup.memberships, backup.scopes);\n if (adapter.scope && scopes.some((scope) => !sameScope(scope, adapter.scope))) {\n throw new KeepBackupImportError(\"The backup contains data outside the active storage scope.\", {\n mode,\n imported: 0,\n failed: backup.items.length + backup.collections.length + backup.memberships.length,\n applied: { items: 0, collections: 0, memberships: 0 },\n failedStage: \"validation\",\n });\n }\n if (\n (backup.collections.length > 0 &&\n (!adapter.getCollections || !adapter.setCollection || !adapter.removeCollection)) ||\n (backup.memberships.length > 0 &&\n (!adapter.getCollectionMemberships || !adapter.setCollectionMembership || !adapter.removeCollectionMembership))\n ) {\n throw new KeepBackupImportError(\"The storage adapter cannot restore collection data from this backup.\", {\n mode,\n imported: 0,\n failed: backup.items.length + backup.collections.length + backup.memberships.length,\n applied: { items: 0, collections: 0, memberships: 0 },\n failedStage: \"validation\",\n });\n }\n const validItems: KeepItem<TMeta>[] = [];\n let failed = 0;\n for (const item of backup.items) {\n if (!options.schema) {\n validItems.push(item);\n continue;\n }\n try {\n validItems.push(await validateKeepItem(item, options.schema));\n } catch (cause) {\n options.onInvalidItem?.(cause, item);\n if ((options.invalidItemPolicy ?? \"error\") === \"drop\") {\n failed += 1;\n continue;\n }\n throw cause;\n }\n }\n const applied: KeepBackupAppliedCounts = { items: 0, collections: 0, memberships: 0 };\n const missingCollectionIds =\n backup.version === 1\n ? [...new Set(backup.items.flatMap((item) => (item.collectionId ? [item.collectionId] : [])))]\n : [];\n\n try {\n if (mode === \"replace\") {\n await adapter.clear();\n await restoreCollections(adapter, [], \"replace\", \"backup\", applied);\n await restoreMemberships(adapter, [], \"replace\", applied);\n }\n } catch (cause) {\n throw new KeepBackupImportError(\"KeepKit could not clear the data being replaced.\", {\n mode,\n imported: applied.items,\n failed: validItems.length + failed,\n applied,\n failedStage: \"clear\",\n cause,\n });\n }\n\n for (const item of validItems) {\n try {\n if (mode === \"merge\") await mergeKeepItems([item], adapter);\n else await adapter.set(item);\n applied.items += 1;\n } catch (cause) {\n throw new KeepBackupImportError(`KeepKit could not restore item ${item.id}.`, {\n mode,\n imported: applied.items,\n failed: validItems.length + failed - applied.items,\n applied,\n failedStage: \"items\",\n cause,\n });\n }\n }\n\n try {\n await restoreCollections(adapter, backup.collections, mode, options.collectionNameConflict ?? \"backup\", applied);\n } catch (cause) {\n throw new KeepBackupImportError(\"KeepKit could not restore collection definitions.\", {\n mode,\n imported: applied.items,\n failed: validItems.length + failed - applied.items,\n applied,\n failedStage: \"collections\",\n cause,\n });\n }\n try {\n await restoreMemberships(adapter, backup.memberships, mode, applied);\n } catch (cause) {\n throw new KeepBackupImportError(\"KeepKit could not restore collection memberships.\", {\n mode,\n imported: applied.items,\n failed: validItems.length + failed - applied.items,\n applied,\n failedStage: \"memberships\",\n cause,\n });\n }\n let items: KeepItem<TMeta>[];\n try {\n items = await adapter.getAll();\n } catch (cause) {\n throw new KeepBackupImportError(\"KeepKit restored the backup but could not read the resulting items.\", {\n mode,\n imported: applied.items,\n failed: failed,\n applied,\n failedStage: \"items\",\n cause,\n });\n }\n\n return {\n mode,\n imported: applied.items,\n failed,\n total: items.length,\n items,\n applied,\n missingCollectionIds,\n includedData: backup.version === 1 ? [\"items\"] : [\"items\", \"collections\", \"memberships\"],\n scopes,\n };\n}\n\nfunction parseBackup<TMeta>(data: string | KeepBackup<TMeta>): ParsedKeepBackup<TMeta> {\n let value: unknown = data;\n if (typeof data === \"string\") {\n try {\n value = JSON.parse(data);\n } catch (cause) {\n throw new KeepBackupParseError(\"KeepKit backup is not valid JSON.\", { cause });\n }\n }\n\n if (!isRecord(value)) throw new KeepBackupParseError(\"KeepKit backup must be an object.\");\n if (value.format !== KEEP_BACKUP_FORMAT || (value.version !== 1 && value.version !== KEEP_BACKUP_VERSION)) {\n throw new KeepBackupParseError(\"KeepKit backup format or version is unsupported.\");\n }\n if (typeof value.exportedAt !== \"number\" || !Number.isFinite(value.exportedAt)) {\n throw new KeepBackupParseError(\"KeepKit backup has an invalid export timestamp.\");\n }\n if (!Array.isArray(value.items) || !value.items.every(isKeepItem)) {\n throw new KeepBackupParseError(\"KeepKit backup contains invalid items.\");\n }\n if (value.version === 1) {\n return {\n format: KEEP_BACKUP_FORMAT,\n version: 1,\n exportedAt: value.exportedAt,\n items: value.items as KeepItem<TMeta>[],\n collections: [],\n memberships: [],\n scopes: [],\n };\n }\n if (\n !Array.isArray(value.collections) ||\n !value.collections.every(isKeepCollectionDefinition) ||\n !Array.isArray(value.memberships) ||\n !value.memberships.every(isKeepCollectionMembership)\n ) {\n throw new KeepBackupParseError(\"KeepKit backup contains invalid collection data.\");\n }\n if (value.scopes !== undefined && (!Array.isArray(value.scopes) || !value.scopes.every(isSyncScope))) {\n throw new KeepBackupParseError(\"KeepKit backup contains invalid scope data.\");\n }\n return value as ParsedKeepBackup<TMeta>;\n}\n\nfunction collectScopes(\n items: KeepItem<unknown>[],\n collections: KeepCollectionDefinition[],\n memberships: KeepCollectionMembership[],\n declaredScopes: SyncScope[] = [],\n): SyncScope[] {\n const scopes = [\n ...declaredScopes,\n ...[...items, ...collections, ...memberships].flatMap((record) => (record.scope ? [record.scope] : [])),\n ].filter((scope, index, all) => all.findIndex((entry) => sameScope(entry, scope)) === index);\n return scopes.map((scope) => ({ ...scope }));\n}\n\nfunction sameScope(left: SyncScope, right: SyncScope | undefined): boolean {\n return left.userId === right?.userId && left.tenantId === right?.tenantId;\n}\n\nfunction isSyncScope(value: unknown): value is SyncScope {\n return (\n isRecord(value) &&\n (value.userId === undefined || typeof value.userId === \"string\") &&\n (value.tenantId === undefined || typeof value.tenantId === \"string\")\n );\n}\n\nasync function restoreCollections<TMeta>(\n adapter: StorageAdapter<TMeta>,\n collections: KeepCollectionDefinition[],\n mode: \"merge\" | \"replace\",\n conflict: \"backup\" | \"existing\",\n applied: KeepBackupAppliedCounts,\n): Promise<void> {\n if (!adapter.getCollections || !adapter.setCollection || !adapter.removeCollection) return;\n const existing = await adapter.getCollections();\n if (mode === \"replace\") {\n for (const collection of existing) await adapter.removeCollection(collection.id, collection.scope);\n }\n for (const collection of collections) {\n const current = existing.find(\n (entry) => entry.id === collection.id && sameScope(entry.scope ?? {}, collection.scope),\n );\n const value =\n mode === \"merge\" && conflict === \"existing\" && current ? { ...collection, name: current.name } : collection;\n await adapter.setCollection(value);\n applied.collections += 1;\n }\n}\n\nasync function restoreMemberships<TMeta>(\n adapter: StorageAdapter<TMeta>,\n memberships: KeepCollectionMembership[],\n mode: \"merge\" | \"replace\",\n applied: KeepBackupAppliedCounts,\n): Promise<void> {\n if (!adapter.getCollectionMemberships || !adapter.setCollectionMembership || !adapter.removeCollectionMembership)\n return;\n if (mode === \"replace\") {\n const current = await adapter.getCollectionMemberships();\n for (const membership of current) {\n await adapter.removeCollectionMembership(membership.collectionId, membership.itemId, membership.scope);\n }\n }\n for (const membership of memberships) {\n await adapter.setCollectionMembership(membership);\n applied.memberships += 1;\n }\n}\n","import { orderKeepItems } from \"./navigation\";\nimport type { KeepItem } from \"./types\";\n\nexport type KeepListQuery<TMeta = Record<string, unknown>> = {\n targetType?: string;\n tags?: string[];\n /** Defaults to active (not archived) items; true selects archived items. */\n archived?: boolean;\n /** Optional explicit archive scope. When provided, this takes precedence over `archived`. */\n archiveScope?: \"active\" | \"archived\" | \"all\";\n collectionId?: string;\n organization?: {\n collection?: \"assigned\" | \"unassigned\";\n tags?: \"assigned\" | \"unassigned\";\n note?: \"assigned\" | \"unassigned\";\n };\n pinnedFirst?: boolean;\n activity?: {\n opened?: \"ever\" | \"never\";\n lastOpenedBefore?: number;\n lastOpenedAfter?: number;\n inactiveForMs?: number;\n };\n sort?: {\n by: \"savedAt\" | \"updatedAt\" | \"lastOpenedAt\";\n direction?: \"asc\" | \"desc\";\n };\n search?: {\n query?: string;\n mode?: \"and\" | \"or\";\n tokenize?: boolean;\n fields?: Array<\"note\" | \"meta\" | \"tags\">;\n };\n pagination?: {\n page?: number;\n pageSize?: number;\n };\n filter?: (item: KeepItem<TMeta>) => boolean;\n savedBetween?: readonly [Date | number, Date | number];\n};\n\nexport type QueryKeepItemsResult<TMeta = Record<string, unknown>> = {\n items: KeepItem<TMeta>[];\n totalCount: number;\n tagCounts: Record<string, number>;\n page: number;\n pageCount: number;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n};\n\nexport type KeepRediscoveryStrategy = \"never-opened\" | \"forgotten\" | \"recently-opened\";\n\nexport type KeepRediscoveryOptions = {\n strategy: KeepRediscoveryStrategy;\n inactiveForMs?: number;\n};\n\n/** Build the default Inbox query: active items without a collection. */\nexport function createInboxQuery<TMeta = Record<string, unknown>>(\n options: { organization?: KeepListQuery<TMeta>[\"organization\"] } = {},\n): KeepListQuery<TMeta> {\n return { organization: { collection: \"unassigned\", ...options.organization } };\n}\n\nconst DEFAULT_REDISCOVERY_INACTIVE_FOR_MS = 30 * 24 * 60 * 60 * 1000;\n\n/** Build a serializable query for common rediscovery views. */\nexport function createRediscoveryQuery<TMeta = Record<string, unknown>>(\n options: KeepRediscoveryOptions,\n): KeepListQuery<TMeta> {\n if (options.strategy === \"never-opened\") return { activity: { opened: \"never\" } };\n if (options.strategy === \"recently-opened\") {\n return { activity: { opened: \"ever\" }, sort: { by: \"lastOpenedAt\", direction: \"desc\" } };\n }\n return { activity: { inactiveForMs: options.inactiveForMs ?? DEFAULT_REDISCOVERY_INACTIVE_FOR_MS } };\n}\n\n/** Apply the collection query and one-based pagination without React. */\nexport function queryKeepItems<TMeta = Record<string, unknown>>(\n source: KeepItem<TMeta>[],\n query: KeepListQuery<TMeta> = {},\n): QueryKeepItemsResult<TMeta> {\n const filtered = source.filter((item) => {\n const [from, to] = query.savedBetween ?? [];\n const savedAt = item.savedAt;\n const lowerBound = from === undefined ? undefined : toTimestamp(from);\n const upperBound = to === undefined ? undefined : toTimestamp(to);\n return (\n (query.targetType === undefined || item.targetType === query.targetType) &&\n (query.tags === undefined || query.tags.every((tag) => item.tags?.includes(tag))) &&\n matchesArchiveScope(item, query) &&\n (query.collectionId === undefined || item.collectionId === query.collectionId) &&\n matchesOrganization(item, query.organization) &&\n matchesActivity(item, query.activity) &&\n (lowerBound === undefined || savedAt >= lowerBound) &&\n (upperBound === undefined || savedAt <= upperBound) &&\n matchesSearch(item, query.search) &&\n (query.filter?.(item) ?? true)\n );\n });\n const tagCounts = getTagCounts(filtered);\n const sortBy = query.sort?.by;\n const direction = query.sort?.direction === \"asc\" ? 1 : -1;\n const sortedBase = sortBy\n ? filtered\n .map((item, index) => ({ item, index }))\n .sort((a, b) => compareSortValues(a.item[sortBy], b.item[sortBy], direction) || a.index - b.index)\n .map(({ item }) => item)\n : orderKeepItems(filtered);\n const sorted = query.pinnedFirst\n ? [...sortedBase.filter((item) => item.pinned === true), ...sortedBase.filter((item) => item.pinned !== true)]\n : sortedBase;\n const pageSize = Math.max(1, query.pagination?.pageSize ?? (sorted.length || 1));\n const pageCount = Math.max(1, Math.ceil(sorted.length / pageSize));\n const page = Math.min(Math.max(1, query.pagination?.page ?? 1), pageCount);\n const offset = (page - 1) * pageSize;\n\n return {\n items: sorted.slice(offset, offset + pageSize),\n totalCount: sorted.length,\n tagCounts,\n page,\n pageCount,\n hasNextPage: page < pageCount,\n hasPreviousPage: page > 1,\n };\n}\n\nfunction matchesOrganization<TMeta>(\n item: KeepItem<TMeta>,\n organization: KeepListQuery<TMeta>[\"organization\"],\n): boolean {\n if (organization?.collection === \"assigned\" && !item.collectionId?.trim()) return false;\n if (organization?.collection === \"unassigned\" && item.collectionId?.trim()) return false;\n if (organization?.tags === \"assigned\" && !item.tags?.some((tag) => tag.trim())) return false;\n if (organization?.tags === \"unassigned\" && item.tags?.some((tag) => tag.trim())) return false;\n if (organization?.note === \"assigned\" && !item.note?.trim()) return false;\n if (organization?.note === \"unassigned\" && item.note?.trim()) return false;\n return true;\n}\n\nfunction matchesActivity<TMeta>(item: KeepItem<TMeta>, activity?: KeepListQuery<TMeta>[\"activity\"]): boolean {\n if (!activity) return true;\n const lastOpenedAt = item.lastOpenedAt;\n const opened = lastOpenedAt !== undefined;\n if (activity.opened === \"ever\" && !opened) return false;\n if (activity.opened === \"never\" && opened) return false;\n if (activity.lastOpenedBefore !== undefined && (!opened || lastOpenedAt >= activity.lastOpenedBefore)) return false;\n if (activity.lastOpenedAfter !== undefined && (!opened || lastOpenedAt <= activity.lastOpenedAfter)) return false;\n if (activity.inactiveForMs !== undefined) {\n const cutoff = Date.now() - activity.inactiveForMs;\n const lastActivityAt = item.lastOpenedAt ?? item.savedAt;\n if (lastActivityAt > cutoff) return false;\n }\n return true;\n}\n\nfunction compareSortValues(left: number | undefined, right: number | undefined, direction: 1 | -1): number {\n if (left === undefined && right === undefined) return 0;\n if (left === undefined) return 1;\n if (right === undefined) return -1;\n return (left - right) * direction;\n}\n\nfunction matchesArchiveScope<TMeta>(item: KeepItem<TMeta>, query: KeepListQuery<TMeta>): boolean {\n if (query.archiveScope === \"all\") return true;\n if (query.archiveScope === \"archived\") return item.archived === true;\n if (query.archiveScope === \"active\") return item.archived !== true;\n return query.archived === true ? item.archived === true : item.archived !== true;\n}\n\nexport function getTagCounts<TMeta = Record<string, unknown>>(items: KeepItem<TMeta>[]): Record<string, number> {\n const counts: Record<string, number> = {};\n for (const item of items) {\n for (const tag of item.tags ?? []) counts[tag] = (counts[tag] ?? 0) + 1;\n }\n return Object.fromEntries(Object.entries(counts).sort(([a], [b]) => a.localeCompare(b)));\n}\n\nfunction matchesSearch<TMeta>(item: KeepItem<TMeta>, search?: KeepListQuery<TMeta>[\"search\"]): boolean {\n const query = search?.query;\n if (!query?.trim()) return true;\n const fields = search?.fields ?? [\"note\", \"meta\", \"tags\"];\n const values = fields.map((field) => {\n if (field === \"note\") return item.note ?? \"\";\n if (field === \"tags\") return (item.tags ?? []).join(\" \");\n try {\n return JSON.stringify(item.meta) ?? \"\";\n } catch {\n return String(item.meta);\n }\n });\n const text = values.join(\" \").toLocaleLowerCase();\n const normalized = query.trim().toLocaleLowerCase();\n const needles = search?.tokenize === false ? [normalized] : normalized.split(/\\s+/).filter(Boolean);\n const matches = needles.map((needle) => text.includes(needle));\n return search?.mode === \"or\" ? matches.some(Boolean) : matches.every(Boolean);\n}\n\nfunction toTimestamp(value: Date | number): number {\n return value instanceof Date ? value.getTime() : value;\n}\n","import { persistKeepItems, removeKeepItems } from \"../persistence/helpers\";\nimport type { KeepItem, KeepItemStatus, StorageAdapter } from \"./types\";\n\nexport type { KeepItemStatus } from \"./types\";\n\nexport type KeepItemRevalidationResult<TMeta = Record<string, unknown>> =\n | { status: \"available\"; meta?: TMeta }\n | { status: Exclude<KeepItemStatus, \"available\">; reason?: string };\n\nexport type KeepItemRevalidator<TMeta = Record<string, unknown>> = (\n item: KeepItem<TMeta>,\n) =>\n | KeepItemRevalidationResult<TMeta>\n | KeepItemRevalidationResult<TMeta>[\"status\"]\n | Promise<KeepItemRevalidationResult<TMeta> | KeepItemRevalidationResult<TMeta>[\"status\"]>;\n\nexport type KeepItemResolver<TMeta = Record<string, unknown>> = (\n item: KeepItem<TMeta>,\n result: KeepItemRevalidationResult<TMeta>,\n) => KeepItem<TMeta> | undefined | Promise<KeepItem<TMeta> | undefined>;\n\nexport type KeepItemMetadataRefresher<TMeta = Record<string, unknown>> = (\n item: KeepItem<TMeta>,\n) => TMeta | Promise<TMeta>;\n\n/** Return whether source metadata should be fetched again based on its age. */\nexport function isKeepItemMetadataStale<TMeta>(\n item: KeepItem<TMeta>,\n maxAgeMs: number,\n now: () => number = Date.now,\n): boolean {\n return item.metaUpdatedAt === undefined || now() - item.metaUpdatedAt >= maxAgeMs;\n}\n\nexport type KeepItemRevalidationRecord<TMeta = Record<string, unknown>> = {\n item: KeepItem<TMeta>;\n status: KeepItemStatus;\n reason?: string;\n updated: boolean;\n};\n\nexport type RevalidateKeepItemsOptions<TMeta = Record<string, unknown>> = {\n /** Statuses that should be removed after they are detected. Detection is the default. */\n removeStatuses?: Array<Exclude<KeepItemStatus, \"available\">>;\n now?: () => number;\n resolveItem?: KeepItemResolver<TMeta>;\n};\n\nexport type KeepAutoRevalidationOptions<TMeta = Record<string, unknown>> = {\n /** Revalidate after the provider hydrates. Defaults to true when validateItem is supplied. */\n onMount?: boolean;\n /** Revalidate when the browser returns online. Defaults to true. */\n onReconnect?: boolean;\n /** Revalidate repeatedly while mounted. Disabled by default. */\n intervalMs?: number;\n removeStatuses?: Array<Exclude<KeepItemStatus, \"available\">>;\n revalidator?: KeepItemRevalidator<TMeta>;\n};\n\nexport type KeepItemRevalidationSummary<TMeta = Record<string, unknown>> = {\n items: KeepItem<TMeta>[];\n checked: number;\n updated: number;\n removed: number;\n updatedItems: KeepItem<TMeta>[];\n removedIds: string[];\n results: KeepItemRevalidationRecord<TMeta>[];\n};\n\n/** Revalidate saved items without coupling the checker to a network client. */\nexport async function revalidateKeepItems<TMeta = Record<string, unknown>>(\n source: KeepItem<TMeta>[],\n revalidator: KeepItemRevalidator<TMeta>,\n options: RevalidateKeepItemsOptions<TMeta> = {},\n): Promise<KeepItemRevalidationSummary<TMeta>> {\n const removeStatuses = new Set(options.removeStatuses ?? []);\n const now = options.now ?? Date.now;\n const items: KeepItem<TMeta>[] = [];\n const updatedItems: KeepItem<TMeta>[] = [];\n const removedIds: string[] = [];\n const results: KeepItemRevalidationRecord<TMeta>[] = [];\n\n for (const item of source) {\n const rawResult = await revalidator(item);\n const result: KeepItemRevalidationResult<TMeta> = typeof rawResult === \"string\" ? { status: rawResult } : rawResult;\n const reason = result.status === \"available\" ? undefined : result.reason;\n const defaultResolved = {\n ...item,\n status: result.status,\n ...(result.status === \"available\" ? { statusReason: undefined } : { statusReason: reason }),\n };\n const resolved = options.resolveItem\n ? await (options.resolveItem as KeepItemResolver<TMeta>)(item, result)\n : defaultResolved;\n if (resolved === undefined) {\n removedIds.push(item.id);\n results.push({ item, status: result.status, reason, updated: false });\n continue;\n }\n if (result.status === \"available\") {\n const timestamp = now();\n const availableItem = clearItemStatus(resolved);\n const updated =\n result.meta === undefined\n ? availableItem\n : { ...availableItem, meta: result.meta, metaUpdatedAt: timestamp, updatedAt: timestamp };\n const didUpdate = updated !== item;\n items.push(updated);\n if (didUpdate) updatedItems.push(updated);\n results.push({ item: updated, status: \"available\", updated: didUpdate });\n continue;\n }\n\n const withStatus = { ...resolved, status: result.status, ...(reason ? { statusReason: reason } : {}) };\n const shouldRemove = removeStatuses.has(result.status);\n if (shouldRemove) removedIds.push(item.id);\n else items.push(withStatus);\n if (!shouldRemove && withStatus !== item) updatedItems.push(withStatus);\n results.push({ item: withStatus, status: result.status, reason, updated: !shouldRemove && withStatus !== item });\n }\n\n return {\n items,\n checked: source.length,\n updated: updatedItems.length,\n removed: removedIds.length,\n updatedItems,\n removedIds,\n results,\n };\n}\n\n/** Revalidate and persist saved items for framework-neutral applications. */\nexport async function reconcileKeepItems<TMeta = Record<string, unknown>>(\n storage: StorageAdapter<TMeta>,\n revalidator: KeepItemRevalidator<TMeta>,\n options: RevalidateKeepItemsOptions<TMeta> = {},\n): Promise<KeepItemRevalidationSummary<TMeta>> {\n const source = await storage.getAll();\n const summary = await revalidateKeepItems(source, revalidator, options);\n if (summary.updatedItems.length > 0) await persistKeepItems(storage, summary.updatedItems);\n if (summary.removedIds.length > 0) await removeKeepItems(storage, summary.removedIds);\n return summary;\n}\n\nfunction clearItemStatus<TMeta>(item: KeepItem<TMeta>): KeepItem<TMeta> {\n const next = { ...item };\n delete next.status;\n delete next.statusReason;\n return next;\n}\n","import type {\n KeepItemMetadataRefresher,\n KeepItemRevalidationSummary,\n KeepItemRevalidator,\n RevalidateKeepItemsOptions,\n} from \"../items/revalidation\";\nimport type { KeepChangeContext, KeepItem, KeepUndoState } from \"../items/types\";\n\nexport type KeepCollectionMeta = { name: string };\n\nexport type KeepStoreState<TMeta = Record<string, unknown>> = {\n items: KeepItem<TMeta>[];\n collections: Record<string, KeepCollectionMeta>;\n isLoading: boolean;\n isHydrated: boolean;\n isMutating: boolean;\n error: unknown | null;\n lastChange?: KeepChangeContext<TMeta>;\n undo?: KeepUndoState;\n};\n\nexport type KeepStoreActions<TMeta = Record<string, unknown>> = {\n saveItem: (item: KeepItem<TMeta>) => Promise<void>;\n recordOpen: (id: string, openedAt?: number) => Promise<void>;\n updateNote: (id: string, note?: string) => Promise<void>;\n updateTags: (id: string, tags?: string[]) => Promise<void>;\n toggleArchive: (id: string) => Promise<void>;\n archiveItem: (id: string) => Promise<void>;\n unarchiveItem: (id: string) => Promise<void>;\n togglePin: (id: string) => Promise<void>;\n moveToCollection: (id: string, collectionId?: string) => Promise<void>;\n archiveBatch: (ids: string[]) => Promise<void>;\n unarchiveBatch: (ids: string[]) => Promise<void>;\n pinBatch: (ids: string[]) => Promise<void>;\n unpinBatch: (ids: string[]) => Promise<void>;\n moveToCollectionBatch: (ids: string[], collectionId?: string) => Promise<void>;\n createCollection: (id: string, name: string) => Promise<void>;\n renameCollection: (id: string, name: string) => Promise<void>;\n removeCollection: (id: string) => Promise<void>;\n updateTagsBatch: (ids: string[], tags?: string[]) => Promise<void>;\n addTagsBatch: (ids: string[], tags: string[]) => Promise<void>;\n removeTagsBatch: (ids: string[], tags: string[]) => Promise<void>;\n removeItem: (id: string) => Promise<void>;\n removeItems: (ids: string[]) => Promise<void>;\n removeItemWithUndo: (id: string) => Promise<void>;\n removeItemsWithUndo: (ids: string[]) => Promise<void>;\n undoLastRemoval: () => Promise<void>;\n clear: () => Promise<void>;\n refresh: () => Promise<void>;\n refreshItemMetadata: (id: string, refresh: KeepItemMetadataRefresher<TMeta>) => Promise<void>;\n revalidateItems: (\n revalidator: KeepItemRevalidator<TMeta>,\n options?: RevalidateKeepItemsOptions<TMeta>,\n ) => Promise<KeepItemRevalidationSummary<TMeta>>;\n reorderItems: (orderedIds: string[]) => Promise<void>;\n moveItem: (id: string, targetIndex: number) => Promise<void>;\n};\n\nexport class KeepStore<TMeta = Record<string, unknown>> {\n private state: KeepStoreState<TMeta>;\n private readonly listeners = new Set<() => void>();\n\n constructor(initialState: KeepStoreState<TMeta>) {\n this.state = initialState;\n }\n\n getSnapshot = (): KeepStoreState<TMeta> => this.state;\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n setState(next: Partial<KeepStoreState<TMeta>>): void {\n let changed = false;\n for (const key of Object.keys(next) as Array<keyof KeepStoreState<TMeta>>) {\n if (!Object.is(this.state[key], next[key])) {\n changed = true;\n break;\n }\n }\n if (!changed) return;\n this.state = { ...this.state, ...next };\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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","names":["item","_legacyCollectionId","migratedItem"]}
|