@keepkit/core 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,320 @@
1
+ import {
2
+ validateKeepItem
3
+ } from "./chunk-THZ3ACR2.js";
4
+
5
+ // src/migration.ts
6
+ async function mergeKeepItems(localItems, target) {
7
+ if (target.merge) return target.merge(localItems);
8
+ const remoteItems = await target.getAll();
9
+ const byId = new Map(remoteItems.map((item) => [item.id, item]));
10
+ for (const localItem of localItems) {
11
+ const remoteItem = byId.get(localItem.id);
12
+ if (!remoteItem || localItem.updatedAt > remoteItem.updatedAt) {
13
+ byId.set(localItem.id, localItem);
14
+ }
15
+ }
16
+ const merged = [...byId.values()].sort((a, b) => b.updatedAt - a.updatedAt);
17
+ await Promise.all(merged.map((item) => target.set(item)));
18
+ return merged;
19
+ }
20
+ async function migrateKeepItems(source, target) {
21
+ const localItems = await source.getAll();
22
+ const merged = await mergeKeepItems(localItems, target);
23
+ await source.clear();
24
+ return merged;
25
+ }
26
+
27
+ // src/backup.ts
28
+ var KEEP_BACKUP_FORMAT = "keepkit";
29
+ var KEEP_BACKUP_VERSION = 1;
30
+ var KeepBackupParseError = class extends Error {
31
+ constructor(message, options) {
32
+ super(message);
33
+ this.name = "KeepBackupParseError";
34
+ if (options?.cause !== void 0) this.cause = options.cause;
35
+ }
36
+ };
37
+ var KeepBackupImportError = class extends Error {
38
+ constructor(message, options) {
39
+ super(message);
40
+ this.name = "KeepBackupImportError";
41
+ this.mode = options.mode;
42
+ this.imported = options.imported;
43
+ this.failed = options.failed;
44
+ if (options.cause !== void 0) this.cause = options.cause;
45
+ }
46
+ };
47
+ async function exportItems(adapter) {
48
+ const backup = {
49
+ format: KEEP_BACKUP_FORMAT,
50
+ version: KEEP_BACKUP_VERSION,
51
+ exportedAt: Date.now(),
52
+ items: await adapter.getAll()
53
+ };
54
+ return JSON.stringify(backup, null, 2);
55
+ }
56
+ async function importItems(adapter, data, options = {}) {
57
+ const backup = parseBackup(data);
58
+ const mode = options.mode ?? "merge";
59
+ const validItems = [];
60
+ let failed = 0;
61
+ for (const item of backup.items) {
62
+ if (!options.schema) {
63
+ validItems.push(item);
64
+ continue;
65
+ }
66
+ try {
67
+ validItems.push(await validateKeepItem(item, options.schema));
68
+ } catch (cause) {
69
+ options.onInvalidItem?.(cause, item);
70
+ if ((options.invalidItemPolicy ?? "error") === "drop") {
71
+ failed += 1;
72
+ continue;
73
+ }
74
+ throw cause;
75
+ }
76
+ }
77
+ let items;
78
+ if (mode === "merge") {
79
+ try {
80
+ items = await mergeKeepItems(validItems, adapter);
81
+ } catch (cause) {
82
+ throw new KeepBackupImportError("KeepKit could not merge the backup.", {
83
+ mode,
84
+ imported: 0,
85
+ failed: validItems.length + failed,
86
+ cause
87
+ });
88
+ }
89
+ } else {
90
+ let imported = 0;
91
+ try {
92
+ await adapter.clear();
93
+ for (const item of validItems) {
94
+ await adapter.set(item);
95
+ imported += 1;
96
+ }
97
+ items = await adapter.getAll();
98
+ } catch (cause) {
99
+ throw new KeepBackupImportError("KeepKit could not replace the stored items.", {
100
+ mode,
101
+ imported,
102
+ failed: validItems.length + failed - imported,
103
+ cause
104
+ });
105
+ }
106
+ }
107
+ return { mode, imported: validItems.length, failed, total: items.length, items };
108
+ }
109
+ function parseBackup(data) {
110
+ let value = data;
111
+ if (typeof data === "string") {
112
+ try {
113
+ value = JSON.parse(data);
114
+ } catch (cause) {
115
+ throw new KeepBackupParseError("KeepKit backup is not valid JSON.", { cause });
116
+ }
117
+ }
118
+ if (!isRecord(value)) throw new KeepBackupParseError("KeepKit backup must be an object.");
119
+ if (value.format !== KEEP_BACKUP_FORMAT || value.version !== KEEP_BACKUP_VERSION) {
120
+ throw new KeepBackupParseError("KeepKit backup format or version is unsupported.");
121
+ }
122
+ if (typeof value.exportedAt !== "number" || !Number.isFinite(value.exportedAt)) {
123
+ throw new KeepBackupParseError("KeepKit backup has an invalid export timestamp.");
124
+ }
125
+ if (!Array.isArray(value.items) || !value.items.every(isKeepItem)) {
126
+ throw new KeepBackupParseError("KeepKit backup contains invalid items.");
127
+ }
128
+ return value;
129
+ }
130
+ function isKeepItem(value) {
131
+ if (!isRecord(value)) return false;
132
+ return typeof value.id === "string" && typeof value.savedAt === "number" && Number.isFinite(value.savedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) && "meta" in value && (value.targetType === void 0 || typeof value.targetType === "string") && (value.note === void 0 || typeof value.note === "string") && (value.schemaVersion === void 0 || typeof value.schemaVersion === "number" && Number.isFinite(value.schemaVersion)) && (value.revision === void 0 || typeof value.revision === "string") && (value.metaUpdatedAt === void 0 || typeof value.metaUpdatedAt === "number" && Number.isFinite(value.metaUpdatedAt)) && (value.status === void 0 || value.status === "available" || value.status === "expired" || value.status === "removed" || value.status === "deleted" || value.status === "private" || value.status === "unknown") && (value.statusReason === void 0 || typeof value.statusReason === "string") && (value.scope === void 0 || isSyncScope(value.scope)) && (value.tags === void 0 || Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === "string"));
133
+ }
134
+ function isRecord(value) {
135
+ return typeof value === "object" && value !== null;
136
+ }
137
+ function isSyncScope(value) {
138
+ return isRecord(value) && (value.userId === void 0 || typeof value.userId === "string") && (value.tenantId === void 0 || typeof value.tenantId === "string");
139
+ }
140
+
141
+ // src/query.ts
142
+ function queryKeepItems(source, query = {}) {
143
+ const filtered = source.filter((item) => {
144
+ const [from, to] = query.savedBetween ?? [];
145
+ const savedAt = item.savedAt;
146
+ const lowerBound = from === void 0 ? void 0 : toTimestamp(from);
147
+ const upperBound = to === void 0 ? void 0 : toTimestamp(to);
148
+ return (query.targetType === void 0 || item.targetType === query.targetType) && (query.tags === void 0 || query.tags.every((tag) => item.tags?.includes(tag))) && (lowerBound === void 0 || savedAt >= lowerBound) && (upperBound === void 0 || savedAt <= upperBound) && matchesSearch(item, query.search) && (query.filter?.(item) ?? true);
149
+ });
150
+ const tagCounts = getTagCounts(filtered);
151
+ const sortBy = query.sort?.by;
152
+ const direction = query.sort?.direction === "asc" ? 1 : -1;
153
+ const sorted = sortBy ? [...filtered].sort((a, b) => (a[sortBy] - b[sortBy]) * direction) : filtered;
154
+ const pageSize = Math.max(1, query.pagination?.pageSize ?? (sorted.length || 1));
155
+ const pageCount = Math.max(1, Math.ceil(sorted.length / pageSize));
156
+ const page = Math.min(Math.max(1, query.pagination?.page ?? 1), pageCount);
157
+ const offset = (page - 1) * pageSize;
158
+ return {
159
+ items: sorted.slice(offset, offset + pageSize),
160
+ totalCount: sorted.length,
161
+ tagCounts,
162
+ page,
163
+ pageCount,
164
+ hasNextPage: page < pageCount,
165
+ hasPreviousPage: page > 1
166
+ };
167
+ }
168
+ function getTagCounts(items) {
169
+ const counts = {};
170
+ for (const item of items) {
171
+ for (const tag of item.tags ?? []) counts[tag] = (counts[tag] ?? 0) + 1;
172
+ }
173
+ return Object.fromEntries(Object.entries(counts).sort(([a], [b]) => a.localeCompare(b)));
174
+ }
175
+ function matchesSearch(item, search) {
176
+ const query = search?.query;
177
+ if (!query?.trim()) return true;
178
+ const fields = search?.fields ?? ["note", "meta", "tags"];
179
+ const values = fields.map((field) => {
180
+ if (field === "note") return item.note ?? "";
181
+ if (field === "tags") return (item.tags ?? []).join(" ");
182
+ try {
183
+ return JSON.stringify(item.meta) ?? "";
184
+ } catch {
185
+ return String(item.meta);
186
+ }
187
+ });
188
+ const text = values.join(" ").toLocaleLowerCase();
189
+ const normalized = query.trim().toLocaleLowerCase();
190
+ const needles = search?.tokenize === false ? [normalized] : normalized.split(/\s+/).filter(Boolean);
191
+ const matches = needles.map((needle) => text.includes(needle));
192
+ return search?.mode === "or" ? matches.some(Boolean) : matches.every(Boolean);
193
+ }
194
+ function toTimestamp(value) {
195
+ return value instanceof Date ? value.getTime() : value;
196
+ }
197
+
198
+ // src/revalidation.ts
199
+ function isKeepItemMetadataStale(item, maxAgeMs, now = Date.now) {
200
+ return item.metaUpdatedAt === void 0 || now() - item.metaUpdatedAt >= maxAgeMs;
201
+ }
202
+ async function revalidateKeepItems(source, revalidator, options = {}) {
203
+ const removeStatuses = new Set(options.removeStatuses ?? []);
204
+ const now = options.now ?? Date.now;
205
+ const items = [];
206
+ const updatedItems = [];
207
+ const removedIds = [];
208
+ const results = [];
209
+ for (const item of source) {
210
+ const rawResult = await revalidator(item);
211
+ const result = typeof rawResult === "string" ? { status: rawResult } : rawResult;
212
+ const reason = result.status === "available" ? void 0 : result.reason;
213
+ const defaultResolved = {
214
+ ...item,
215
+ status: result.status,
216
+ ...result.status === "available" ? { statusReason: void 0 } : { statusReason: reason }
217
+ };
218
+ const resolved = options.resolveItem ? await options.resolveItem(item, result) : defaultResolved;
219
+ if (resolved === void 0) {
220
+ removedIds.push(item.id);
221
+ results.push({ item, status: result.status, reason, updated: false });
222
+ continue;
223
+ }
224
+ if (result.status === "available") {
225
+ const timestamp = now();
226
+ const availableItem = clearItemStatus(resolved);
227
+ const updated = result.meta === void 0 ? availableItem : { ...availableItem, meta: result.meta, metaUpdatedAt: timestamp, updatedAt: timestamp };
228
+ const didUpdate = updated !== item;
229
+ items.push(updated);
230
+ if (didUpdate) updatedItems.push(updated);
231
+ results.push({ item: updated, status: "available", updated: didUpdate });
232
+ continue;
233
+ }
234
+ const withStatus = { ...resolved, status: result.status, ...reason ? { statusReason: reason } : {} };
235
+ const shouldRemove = removeStatuses.has(result.status);
236
+ if (shouldRemove) removedIds.push(item.id);
237
+ else items.push(withStatus);
238
+ if (!shouldRemove && withStatus !== item) updatedItems.push(withStatus);
239
+ results.push({ item: withStatus, status: result.status, reason, updated: !shouldRemove && withStatus !== item });
240
+ }
241
+ return {
242
+ items,
243
+ checked: source.length,
244
+ updated: updatedItems.length,
245
+ removed: removedIds.length,
246
+ updatedItems,
247
+ removedIds,
248
+ results
249
+ };
250
+ }
251
+ async function reconcileKeepItems(storage, revalidator, options = {}) {
252
+ const source = await storage.getAll();
253
+ const summary = await revalidateKeepItems(source, revalidator, options);
254
+ if (summary.updatedItems.length > 0) await persistItems(storage, summary.updatedItems);
255
+ if (summary.removedIds.length > 0) await removeItems(storage, summary.removedIds);
256
+ return summary;
257
+ }
258
+ async function persistItems(storage, items) {
259
+ if (storage.setMany) {
260
+ await storage.setMany(items);
261
+ return;
262
+ }
263
+ for (const item of items) await storage.set(item);
264
+ }
265
+ async function removeItems(storage, ids) {
266
+ if (storage.removeMany) {
267
+ await storage.removeMany(ids);
268
+ return;
269
+ }
270
+ for (const id of ids) await storage.remove(id);
271
+ }
272
+ function clearItemStatus(item) {
273
+ const next = { ...item };
274
+ delete next.status;
275
+ delete next.statusReason;
276
+ return next;
277
+ }
278
+
279
+ // src/store.ts
280
+ var KeepStore = class {
281
+ constructor(initialState) {
282
+ this.listeners = /* @__PURE__ */ new Set();
283
+ this.getSnapshot = () => this.state;
284
+ this.subscribe = (listener) => {
285
+ this.listeners.add(listener);
286
+ return () => this.listeners.delete(listener);
287
+ };
288
+ this.state = initialState;
289
+ }
290
+ setState(next) {
291
+ let changed = false;
292
+ for (const key of Object.keys(next)) {
293
+ if (!Object.is(this.state[key], next[key])) {
294
+ changed = true;
295
+ break;
296
+ }
297
+ }
298
+ if (!changed) return;
299
+ this.state = { ...this.state, ...next };
300
+ for (const listener of this.listeners) listener();
301
+ }
302
+ };
303
+
304
+ export {
305
+ mergeKeepItems,
306
+ migrateKeepItems,
307
+ KEEP_BACKUP_FORMAT,
308
+ KEEP_BACKUP_VERSION,
309
+ KeepBackupParseError,
310
+ KeepBackupImportError,
311
+ exportItems,
312
+ importItems,
313
+ queryKeepItems,
314
+ getTagCounts,
315
+ isKeepItemMetadataStale,
316
+ revalidateKeepItems,
317
+ reconcileKeepItems,
318
+ KeepStore
319
+ };
320
+ //# sourceMappingURL=chunk-XF5VI6O5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/migration.ts","../src/backup.ts","../src/query.ts","../src/revalidation.ts","../src/store.ts"],"sourcesContent":["import type { KeepItem, StorageAdapter } from \"./types\";\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 remoteItems = await target.getAll();\n const byId = new Map(remoteItems.map((item) => [item.id, item]));\n for (const localItem of localItems) {\n const remoteItem = byId.get(localItem.id);\n if (!remoteItem || localItem.updatedAt > remoteItem.updatedAt) {\n byId.set(localItem.id, localItem);\n }\n }\n\n const merged = [...byId.values()].sort((a, b) => b.updatedAt - a.updatedAt);\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","import { mergeKeepItems } from \"./migration\";\nimport { validateKeepItem } from \"./schema\";\nimport type { KeepInvalidItemPolicy, KeepItem, KeepSchema, StorageAdapter } from \"./types\";\n\nexport const KEEP_BACKUP_FORMAT = \"keepkit\";\nexport const KEEP_BACKUP_VERSION = 1;\n\nexport type KeepBackup<TMeta = Record<string, unknown>> = {\n format: typeof KEEP_BACKUP_FORMAT;\n version: typeof KEEP_BACKUP_VERSION;\n exportedAt: number;\n items: KeepItem<TMeta>[];\n};\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};\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};\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 cause?: unknown;\n\n constructor(\n message: string,\n options: {\n mode: \"replace\" | \"merge\";\n imported: number;\n failed: number;\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 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 backup: KeepBackup<TMeta> = {\n format: KEEP_BACKUP_FORMAT,\n version: KEEP_BACKUP_VERSION,\n exportedAt: Date.now(),\n items: await adapter.getAll(),\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 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 let items: KeepItem<TMeta>[];\n\n if (mode === \"merge\") {\n try {\n items = await mergeKeepItems(validItems, adapter);\n } catch (cause) {\n throw new KeepBackupImportError(\"KeepKit could not merge the backup.\", {\n mode,\n imported: 0,\n failed: validItems.length + failed,\n cause,\n });\n }\n } else {\n let imported = 0;\n try {\n await adapter.clear();\n for (const item of validItems) {\n await adapter.set(item);\n imported += 1;\n }\n items = await adapter.getAll();\n } catch (cause) {\n throw new KeepBackupImportError(\"KeepKit could not replace the stored items.\", {\n mode,\n imported,\n failed: validItems.length + failed - imported,\n cause,\n });\n }\n }\n\n return { mode, imported: validItems.length, failed, total: items.length, items };\n}\n\nfunction parseBackup<TMeta>(data: string | KeepBackup<TMeta>): KeepBackup<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 !== 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 return value as KeepBackup<TMeta>;\n}\n\nfunction isKeepItem(value: unknown): value is KeepItem {\n if (!isRecord(value)) return false;\n return (\n typeof value.id === \"string\" &&\n typeof value.savedAt === \"number\" &&\n Number.isFinite(value.savedAt) &&\n typeof value.updatedAt === \"number\" &&\n Number.isFinite(value.updatedAt) &&\n \"meta\" in value &&\n (value.targetType === undefined || typeof value.targetType === \"string\") &&\n (value.note === undefined || typeof value.note === \"string\") &&\n (value.schemaVersion === undefined ||\n (typeof value.schemaVersion === \"number\" && Number.isFinite(value.schemaVersion))) &&\n (value.revision === undefined || typeof value.revision === \"string\") &&\n (value.metaUpdatedAt === undefined ||\n (typeof value.metaUpdatedAt === \"number\" && Number.isFinite(value.metaUpdatedAt))) &&\n (value.status === undefined ||\n value.status === \"available\" ||\n value.status === \"expired\" ||\n value.status === \"removed\" ||\n value.status === \"deleted\" ||\n value.status === \"private\" ||\n value.status === \"unknown\") &&\n (value.statusReason === undefined || typeof value.statusReason === \"string\") &&\n (value.scope === undefined || isSyncScope(value.scope)) &&\n (value.tags === undefined || (Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === \"string\")))\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isSyncScope(value: unknown): boolean {\n return (\n isRecord(value) &&\n (value.userId === undefined || typeof value.userId === \"string\") &&\n (value.tenantId === undefined || typeof value.tenantId === \"string\")\n );\n}\n","import type { KeepItem } from \"./types\";\n\nexport type KeepListQuery<TMeta = Record<string, unknown>> = {\n targetType?: string;\n tags?: string[];\n sort?: {\n by: \"savedAt\" | \"updatedAt\";\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\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 (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 sorted = sortBy ? [...filtered].sort((a, b) => (a[sortBy] - b[sortBy]) * direction) : filtered;\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\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 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 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 persistItems(storage, summary.updatedItems);\n if (summary.removedIds.length > 0) await removeItems(storage, summary.removedIds);\n return summary;\n}\n\nasync function persistItems<TMeta>(storage: StorageAdapter<TMeta>, items: KeepItem<TMeta>[]): Promise<void> {\n if (storage.setMany) {\n await storage.setMany(items);\n return;\n }\n for (const item of items) await storage.set(item);\n}\n\nasync function removeItems<TMeta>(storage: StorageAdapter<TMeta>, ids: string[]): Promise<void> {\n if (storage.removeMany) {\n await storage.removeMany(ids);\n return;\n }\n for (const id of ids) await storage.remove(id);\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 \"./revalidation\";\nimport type { KeepChangeContext, KeepItem } from \"./types\";\n\nexport type KeepStoreState<TMeta = Record<string, unknown>> = {\n items: KeepItem<TMeta>[];\n isLoading: boolean;\n isHydrated: boolean;\n isMutating: boolean;\n error: unknown | null;\n lastChange?: KeepChangeContext<TMeta>;\n};\n\nexport type KeepStoreActions<TMeta = Record<string, unknown>> = {\n saveItem: (item: KeepItem<TMeta>) => Promise<void>;\n updateNote: (id: string, note?: string) => Promise<void>;\n updateTags: (id: string, tags?: 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 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};\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":[]}
package/dist/core.d.ts CHANGED
@@ -1,53 +1,9 @@
1
- import { K as KeepSchema, b as KeepInvalidItemPolicy, a as KeepItem, S as StorageAdapter, c as KeepPluginContext, d as KeepPlugin } from './types-BCziYE6E.js';
2
- export { e as KeepAction, f as KeepChangeContext, g as KeepChangePhase, h as KeepConflictContext, i as KeepConflictResolver, j as KeepErrorContext, k as KeepErrorHandler, l as KeepEventHandlers, m as KeepItemInput, n as KeepSchemaParseResult, o as KeepStorageAccessError, p as KeepStorageError, q as KeepStorageOperation, r as KeepStorageParseError, s as KeepStorageQuotaError, t as KeepSyncState, u as KeepSyncStatus, R as RemoteSyncDriver, v as RemoteSyncResult, w as SyncCapableStorageAdapter, x as SyncOperation, y as SyncQueueAdapter, z as normalizeKeepTags } from './types-BCziYE6E.js';
3
- export { K as KeepItemMetadataRefresher, a as KeepItemRevalidationRecord, b as KeepItemRevalidationResult, c as KeepItemRevalidationSummary, d as KeepItemRevalidator, e as KeepItemStatus, f as KeepListQuery, g as KeepStore, h as KeepStoreActions, i as KeepStoreState, Q as QueryKeepItemsResult, R as RevalidateKeepItemsOptions, j as getTagCounts, k as isKeepItemMetadataStale, q as queryKeepItems, r as reconcileKeepItems, l as revalidateKeepItems } from './store-CqNh-2DS.js';
1
+ export { I as ImportItemsOptions, a as ImportItemsResult, K as KEEP_BACKUP_FORMAT, b as KEEP_BACKUP_VERSION, c as KeepBackup, d as KeepBackupImportError, e as KeepBackupParseError, f as KeepItemMetadataRefresher, g as KeepItemResolver, h as KeepItemRevalidationRecord, i as KeepItemRevalidationResult, j as KeepItemRevalidationSummary, k as KeepItemRevalidator, l as KeepListQuery, m as KeepStore, n as KeepStoreActions, o as KeepStoreState, Q as QueryKeepItemsResult, R as RevalidateKeepItemsOptions, p as exportItems, q as getTagCounts, r as importItems, s as isKeepItemMetadataStale, t as queryKeepItems, u as reconcileKeepItems, v as revalidateKeepItems } from './store-Cgqx7_xd.js';
2
+ import { b as KeepPluginContext, c as KeepPlugin, a as KeepItem, S as StorageAdapter } from './types--YahIoEB.js';
3
+ export { d as KeepAction, e as KeepChangeContext, f as KeepChangePhase, g as KeepConflictContext, h as KeepConflictResolver, i as KeepErrorContext, j as KeepErrorHandler, k as KeepEventHandlers, l as KeepInvalidItemPolicy, m as KeepItemInput, n as KeepItemStatus, K as KeepSchema, o as KeepSchemaParseResult, p as KeepStorageAccessError, q as KeepStorageError, r as KeepStorageOperation, s as KeepStorageParseError, t as KeepStorageQuotaError, u as KeepSyncState, v as KeepSyncStatus, R as RemoteSyncDriver, w as RemoteSyncResult, x as SyncCapableStorageAdapter, y as SyncOperation, z as SyncQueueAdapter, A as SyncScope, B as normalizeKeepTags } from './types--YahIoEB.js';
4
4
  export { KeepSchemaValidationError, parseKeepMeta, validateKeepItem } from './schema.js';
5
5
  export { BrowserStorageAdapterOptions, DEFAULT_INDEXEDDB_DATABASE, DEFAULT_INDEXEDDB_STORE, DEFAULT_STORAGE_KEY, DEFAULT_SYNC_QUEUE_DATABASE, DEFAULT_SYNC_QUEUE_KEY, DEFAULT_SYNC_QUEUE_STORE, FallbackStorageAdapter, FallbackStorageAdapterOptions, FallbackSyncQueueAdapter, FallbackSyncQueueAdapterOptions, IndexedDBAdapter, IndexedDBAdapterOptions, IndexedDBSyncQueueAdapter, IndexedDBSyncQueueOptions, LocalStorageAdapter, LocalStorageAdapterOptions, LocalStorageSyncQueueAdapter, LocalStorageSyncQueueOptions, StorageAdapterFactoryOptions, SyncStorageAdapter, SyncStorageAdapterOptions, createBrowserStorageAdapter, createStorageAdapter } from './storage.js';
6
6
 
7
- declare const KEEP_BACKUP_FORMAT = "keepkit";
8
- declare const KEEP_BACKUP_VERSION = 1;
9
- type KeepBackup<TMeta = Record<string, unknown>> = {
10
- format: typeof KEEP_BACKUP_FORMAT;
11
- version: typeof KEEP_BACKUP_VERSION;
12
- exportedAt: number;
13
- items: KeepItem<TMeta>[];
14
- };
15
- type ImportItemsOptions<TMeta = unknown> = {
16
- mode?: "replace" | "merge";
17
- schema?: KeepSchema<TMeta>;
18
- invalidItemPolicy?: KeepInvalidItemPolicy;
19
- onInvalidItem?: (error: unknown, item: KeepItem<unknown>) => void;
20
- };
21
- type ImportItemsResult<TMeta = Record<string, unknown>> = {
22
- mode: "replace" | "merge";
23
- imported: number;
24
- failed: number;
25
- total: number;
26
- items: KeepItem<TMeta>[];
27
- };
28
- declare class KeepBackupParseError extends Error {
29
- readonly cause?: unknown;
30
- constructor(message: string, options?: {
31
- cause?: unknown;
32
- });
33
- }
34
- declare class KeepBackupImportError extends Error {
35
- readonly mode: "replace" | "merge";
36
- readonly imported: number;
37
- readonly failed: number;
38
- readonly cause?: unknown;
39
- constructor(message: string, options: {
40
- mode: "replace" | "merge";
41
- imported: number;
42
- failed: number;
43
- cause?: unknown;
44
- });
45
- }
46
- /** Serialize all adapter data into a versioned JSON backup. */
47
- declare function exportItems<TMeta>(adapter: StorageAdapter<TMeta>): Promise<string>;
48
- /** Validate and restore a backup, either replacing or merging existing data. */
49
- declare function importItems<TMeta>(adapter: StorageAdapter<TMeta>, data: string | KeepBackup<TMeta>, options?: ImportItemsOptions<TMeta>): Promise<ImportItemsResult<TMeta>>;
50
-
51
7
  type KeepInvalidationPluginOptions<TMeta = Record<string, unknown>> = {
52
8
  /** Query keys to invalidate after a successful local KeepKit mutation. */
53
9
  queryKeys: readonly unknown[] | ((context: KeepPluginContext<TMeta>) => readonly (readonly unknown[])[]);
@@ -63,4 +19,4 @@ declare function mergeKeepItems<TMeta>(localItems: KeepItem<TMeta>[], target: St
63
19
  /** Read anonymous items, merge them into the target, then clear the source. */
64
20
  declare function migrateKeepItems<TMeta>(source: StorageAdapter<TMeta>, target: StorageAdapter<TMeta>): Promise<KeepItem<TMeta>[]>;
65
21
 
66
- export { type ImportItemsOptions, type ImportItemsResult, KEEP_BACKUP_FORMAT, KEEP_BACKUP_VERSION, type KeepBackup, KeepBackupImportError, KeepBackupParseError, KeepInvalidItemPolicy, type KeepInvalidationPluginOptions, KeepItem, KeepPlugin, KeepPluginContext, KeepSchema, StorageAdapter, createKeepInvalidationPlugin, exportItems, importItems, mergeKeepItems, migrateKeepItems };
22
+ export { type KeepInvalidationPluginOptions, KeepItem, KeepPlugin, KeepPluginContext, StorageAdapter, createKeepInvalidationPlugin, mergeKeepItems, migrateKeepItems };
package/dist/core.js CHANGED
@@ -1,11 +1,19 @@
1
1
  import {
2
+ KEEP_BACKUP_FORMAT,
3
+ KEEP_BACKUP_VERSION,
4
+ KeepBackupImportError,
5
+ KeepBackupParseError,
2
6
  KeepStore,
7
+ exportItems,
3
8
  getTagCounts,
9
+ importItems,
4
10
  isKeepItemMetadataStale,
11
+ mergeKeepItems,
12
+ migrateKeepItems,
5
13
  queryKeepItems,
6
14
  reconcileKeepItems,
7
15
  revalidateKeepItems
8
- } from "./chunk-AWQWKK4V.js";
16
+ } from "./chunk-XF5VI6O5.js";
9
17
  import {
10
18
  KeepSchemaValidationError,
11
19
  parseKeepMeta,
@@ -32,140 +40,7 @@ import {
32
40
  createBrowserStorageAdapter,
33
41
  createStorageAdapter,
34
42
  normalizeKeepTags
35
- } from "./chunk-DBRHZ6XU.js";
36
-
37
- // src/migration.ts
38
- async function mergeKeepItems(localItems, target) {
39
- if (target.merge) return target.merge(localItems);
40
- const remoteItems = await target.getAll();
41
- const byId = new Map(remoteItems.map((item) => [item.id, item]));
42
- for (const localItem of localItems) {
43
- const remoteItem = byId.get(localItem.id);
44
- if (!remoteItem || localItem.updatedAt > remoteItem.updatedAt) {
45
- byId.set(localItem.id, localItem);
46
- }
47
- }
48
- const merged = [...byId.values()].sort((a, b) => b.updatedAt - a.updatedAt);
49
- await Promise.all(merged.map((item) => target.set(item)));
50
- return merged;
51
- }
52
- async function migrateKeepItems(source, target) {
53
- const localItems = await source.getAll();
54
- const merged = await mergeKeepItems(localItems, target);
55
- await source.clear();
56
- return merged;
57
- }
58
-
59
- // src/backup.ts
60
- var KEEP_BACKUP_FORMAT = "keepkit";
61
- var KEEP_BACKUP_VERSION = 1;
62
- var KeepBackupParseError = class extends Error {
63
- constructor(message, options) {
64
- super(message);
65
- this.name = "KeepBackupParseError";
66
- if (options?.cause !== void 0) this.cause = options.cause;
67
- }
68
- };
69
- var KeepBackupImportError = class extends Error {
70
- constructor(message, options) {
71
- super(message);
72
- this.name = "KeepBackupImportError";
73
- this.mode = options.mode;
74
- this.imported = options.imported;
75
- this.failed = options.failed;
76
- if (options.cause !== void 0) this.cause = options.cause;
77
- }
78
- };
79
- async function exportItems(adapter) {
80
- const backup = {
81
- format: KEEP_BACKUP_FORMAT,
82
- version: KEEP_BACKUP_VERSION,
83
- exportedAt: Date.now(),
84
- items: await adapter.getAll()
85
- };
86
- return JSON.stringify(backup, null, 2);
87
- }
88
- async function importItems(adapter, data, options = {}) {
89
- const backup = parseBackup(data);
90
- const mode = options.mode ?? "merge";
91
- const validItems = [];
92
- let failed = 0;
93
- for (const item of backup.items) {
94
- if (!options.schema) {
95
- validItems.push(item);
96
- continue;
97
- }
98
- try {
99
- validItems.push(await validateKeepItem(item, options.schema));
100
- } catch (cause) {
101
- options.onInvalidItem?.(cause, item);
102
- if ((options.invalidItemPolicy ?? "error") === "drop") {
103
- failed += 1;
104
- continue;
105
- }
106
- throw cause;
107
- }
108
- }
109
- let items;
110
- if (mode === "merge") {
111
- try {
112
- items = await mergeKeepItems(validItems, adapter);
113
- } catch (cause) {
114
- throw new KeepBackupImportError("KeepKit could not merge the backup.", {
115
- mode,
116
- imported: 0,
117
- failed: validItems.length + failed,
118
- cause
119
- });
120
- }
121
- } else {
122
- let imported = 0;
123
- try {
124
- await adapter.clear();
125
- for (const item of validItems) {
126
- await adapter.set(item);
127
- imported += 1;
128
- }
129
- items = await adapter.getAll();
130
- } catch (cause) {
131
- throw new KeepBackupImportError("KeepKit could not replace the stored items.", {
132
- mode,
133
- imported,
134
- failed: validItems.length + failed - imported,
135
- cause
136
- });
137
- }
138
- }
139
- return { mode, imported: validItems.length, failed, total: items.length, items };
140
- }
141
- function parseBackup(data) {
142
- let value = data;
143
- if (typeof data === "string") {
144
- try {
145
- value = JSON.parse(data);
146
- } catch (cause) {
147
- throw new KeepBackupParseError("KeepKit backup is not valid JSON.", { cause });
148
- }
149
- }
150
- if (!isRecord(value)) throw new KeepBackupParseError("KeepKit backup must be an object.");
151
- if (value.format !== KEEP_BACKUP_FORMAT || value.version !== KEEP_BACKUP_VERSION) {
152
- throw new KeepBackupParseError("KeepKit backup format or version is unsupported.");
153
- }
154
- if (typeof value.exportedAt !== "number" || !Number.isFinite(value.exportedAt)) {
155
- throw new KeepBackupParseError("KeepKit backup has an invalid export timestamp.");
156
- }
157
- if (!Array.isArray(value.items) || !value.items.every(isKeepItem)) {
158
- throw new KeepBackupParseError("KeepKit backup contains invalid items.");
159
- }
160
- return value;
161
- }
162
- function isKeepItem(value) {
163
- if (!isRecord(value)) return false;
164
- return typeof value.id === "string" && typeof value.savedAt === "number" && Number.isFinite(value.savedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) && "meta" in value && (value.targetType === void 0 || typeof value.targetType === "string") && (value.note === void 0 || typeof value.note === "string") && (value.schemaVersion === void 0 || typeof value.schemaVersion === "number" && Number.isFinite(value.schemaVersion)) && (value.revision === void 0 || typeof value.revision === "string") && (value.metaUpdatedAt === void 0 || typeof value.metaUpdatedAt === "number" && Number.isFinite(value.metaUpdatedAt)) && (value.tags === void 0 || Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === "string"));
165
- }
166
- function isRecord(value) {
167
- return typeof value === "object" && value !== null;
168
- }
43
+ } from "./chunk-EXE4E3GA.js";
169
44
 
170
45
  // src/integrations.ts
171
46
  function createKeepInvalidationPlugin(options) {
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/migration.ts","../src/backup.ts","../src/integrations.ts"],"sourcesContent":["import type { KeepItem, StorageAdapter } from \"./types\";\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 remoteItems = await target.getAll();\n const byId = new Map(remoteItems.map((item) => [item.id, item]));\n for (const localItem of localItems) {\n const remoteItem = byId.get(localItem.id);\n if (!remoteItem || localItem.updatedAt > remoteItem.updatedAt) {\n byId.set(localItem.id, localItem);\n }\n }\n\n const merged = [...byId.values()].sort((a, b) => b.updatedAt - a.updatedAt);\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","import { mergeKeepItems } from \"./migration\";\nimport { validateKeepItem } from \"./schema\";\nimport type { KeepInvalidItemPolicy, KeepItem, KeepSchema, StorageAdapter } from \"./types\";\n\nexport const KEEP_BACKUP_FORMAT = \"keepkit\";\nexport const KEEP_BACKUP_VERSION = 1;\n\nexport type KeepBackup<TMeta = Record<string, unknown>> = {\n format: typeof KEEP_BACKUP_FORMAT;\n version: typeof KEEP_BACKUP_VERSION;\n exportedAt: number;\n items: KeepItem<TMeta>[];\n};\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};\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};\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 cause?: unknown;\n\n constructor(\n message: string,\n options: {\n mode: \"replace\" | \"merge\";\n imported: number;\n failed: number;\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 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 backup: KeepBackup<TMeta> = {\n format: KEEP_BACKUP_FORMAT,\n version: KEEP_BACKUP_VERSION,\n exportedAt: Date.now(),\n items: await adapter.getAll(),\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 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 let items: KeepItem<TMeta>[];\n\n if (mode === \"merge\") {\n try {\n items = await mergeKeepItems(validItems, adapter);\n } catch (cause) {\n throw new KeepBackupImportError(\"KeepKit could not merge the backup.\", {\n mode,\n imported: 0,\n failed: validItems.length + failed,\n cause,\n });\n }\n } else {\n let imported = 0;\n try {\n await adapter.clear();\n for (const item of validItems) {\n await adapter.set(item);\n imported += 1;\n }\n items = await adapter.getAll();\n } catch (cause) {\n throw new KeepBackupImportError(\"KeepKit could not replace the stored items.\", {\n mode,\n imported,\n failed: validItems.length + failed - imported,\n cause,\n });\n }\n }\n\n return { mode, imported: validItems.length, failed, total: items.length, items };\n}\n\nfunction parseBackup<TMeta>(data: string | KeepBackup<TMeta>): KeepBackup<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 !== 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 return value as KeepBackup<TMeta>;\n}\n\nfunction isKeepItem(value: unknown): value is KeepItem {\n if (!isRecord(value)) return false;\n return (\n typeof value.id === \"string\" &&\n typeof value.savedAt === \"number\" &&\n Number.isFinite(value.savedAt) &&\n typeof value.updatedAt === \"number\" &&\n Number.isFinite(value.updatedAt) &&\n \"meta\" in value &&\n (value.targetType === undefined || typeof value.targetType === \"string\") &&\n (value.note === undefined || typeof value.note === \"string\") &&\n (value.schemaVersion === undefined ||\n (typeof value.schemaVersion === \"number\" && Number.isFinite(value.schemaVersion))) &&\n (value.revision === undefined || typeof value.revision === \"string\") &&\n (value.metaUpdatedAt === undefined ||\n (typeof value.metaUpdatedAt === \"number\" && Number.isFinite(value.metaUpdatedAt))) &&\n (value.tags === undefined || (Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === \"string\")))\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n","import type { KeepPlugin, KeepPluginContext } from \"./types\";\n\nexport type KeepInvalidationPluginOptions<TMeta = Record<string, unknown>> = {\n /** Query keys to invalidate after a successful local KeepKit mutation. */\n queryKeys: readonly unknown[] | ((context: KeepPluginContext<TMeta>) => readonly (readonly unknown[])[]);\n /** Connect this callback to queryClient.invalidateQueries or SWR mutate. */\n invalidate: (queryKey: readonly unknown[], context: KeepPluginContext<TMeta>) => void | Promise<void>;\n name?: string;\n};\n\n/** Framework-neutral bridge for TanStack Query, SWR, and similar caches. */\nexport function createKeepInvalidationPlugin<TMeta = Record<string, unknown>>(\n options: KeepInvalidationPluginOptions<TMeta>,\n): KeepPlugin<TMeta> {\n return {\n name: options.name ?? \"keepkit-cache-invalidation\",\n after: async (context) => {\n const keys = typeof options.queryKeys === \"function\" ? options.queryKeys(context) : [options.queryKeys];\n await Promise.all(keys.map((queryKey) => options.invalidate(queryKey, context)));\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/integrations.ts"],"sourcesContent":["import type { KeepPlugin, KeepPluginContext } from \"./types\";\n\nexport type KeepInvalidationPluginOptions<TMeta = Record<string, unknown>> = {\n /** Query keys to invalidate after a successful local KeepKit mutation. */\n queryKeys: readonly unknown[] | ((context: KeepPluginContext<TMeta>) => readonly (readonly unknown[])[]);\n /** Connect this callback to queryClient.invalidateQueries or SWR mutate. */\n invalidate: (queryKey: readonly unknown[], context: KeepPluginContext<TMeta>) => void | Promise<void>;\n name?: string;\n};\n\n/** Framework-neutral bridge for TanStack Query, SWR, and similar caches. */\nexport function createKeepInvalidationPlugin<TMeta = Record<string, unknown>>(\n options: KeepInvalidationPluginOptions<TMeta>,\n): KeepPlugin<TMeta> {\n return {\n name: options.name ?? \"keepkit-cache-invalidation\",\n after: async (context) => {\n const keys = typeof options.queryKeys === \"function\" ? options.queryKeys(context) : [options.queryKeys];\n await Promise.all(keys.map((queryKey) => options.invalidate(queryKey, context)));\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWO,SAAS,6BACd,SACmB;AACnB,SAAO;AAAA,IACL,MAAM,QAAQ,QAAQ;AAAA,IACtB,OAAO,OAAO,YAAY;AACxB,YAAM,OAAO,OAAO,QAAQ,cAAc,aAAa,QAAQ,UAAU,OAAO,IAAI,CAAC,QAAQ,SAAS;AACtG,YAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,aAAa,QAAQ,WAAW,UAAU,OAAO,CAAC,CAAC;AAAA,IACjF;AAAA,EACF;AACF;","names":[]}