@keepkit/core 0.1.0 → 0.2.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,41 @@
1
+ // src/schema.ts
2
+ var KeepSchemaValidationError = class extends Error {
3
+ constructor(message, options = {}) {
4
+ super(message);
5
+ this.name = "KeepSchemaValidationError";
6
+ this.cause = options.cause;
7
+ this.itemId = options.itemId;
8
+ }
9
+ };
10
+ async function parseKeepMeta(schema, value) {
11
+ try {
12
+ if ("parse" in schema) return await schema.parse(value);
13
+ if ("safeParse" in schema) {
14
+ const result2 = await schema.safeParse(value);
15
+ if (result2.success) return result2.data;
16
+ throw new KeepSchemaValidationError("KeepKit metadata did not match the configured schema.", {
17
+ cause: result2.error
18
+ });
19
+ }
20
+ const result = await schema["~standard"].validate(value);
21
+ if (!result.issues && "value" in result) return result.value;
22
+ throw new KeepSchemaValidationError("KeepKit metadata did not match the configured schema.", {
23
+ cause: result.issues
24
+ });
25
+ } catch (cause) {
26
+ if (cause instanceof KeepSchemaValidationError) throw cause;
27
+ throw new KeepSchemaValidationError("KeepKit metadata did not match the configured schema.", {
28
+ cause
29
+ });
30
+ }
31
+ }
32
+ async function validateKeepItem(item, schema) {
33
+ return { ...item, meta: await parseKeepMeta(schema, item.meta) };
34
+ }
35
+
36
+ export {
37
+ KeepSchemaValidationError,
38
+ parseKeepMeta,
39
+ validateKeepItem
40
+ };
41
+ //# sourceMappingURL=chunk-THZ3ACR2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema.ts"],"sourcesContent":["import type { KeepItem, KeepSchema } from \"./types\";\n\nexport class KeepSchemaValidationError extends Error {\n readonly cause?: unknown;\n readonly itemId?: string;\n\n constructor(message: string, options: { cause?: unknown; itemId?: string } = {}) {\n super(message);\n this.name = \"KeepSchemaValidationError\";\n this.cause = options.cause;\n this.itemId = options.itemId;\n }\n}\n\n/** Parse metadata with a Zod-like, safeParse-like, or Standard Schema parser. */\nexport async function parseKeepMeta<T>(schema: KeepSchema<T>, value: unknown): Promise<T> {\n try {\n if (\"parse\" in schema) return await schema.parse(value);\n\n if (\"safeParse\" in schema) {\n const result = await schema.safeParse(value);\n if (result.success) return result.data;\n throw new KeepSchemaValidationError(\"KeepKit metadata did not match the configured schema.\", {\n cause: result.error,\n });\n }\n\n const result = await schema[\"~standard\"].validate(value);\n if (!result.issues && \"value\" in result) return result.value as T;\n throw new KeepSchemaValidationError(\"KeepKit metadata did not match the configured schema.\", {\n cause: result.issues,\n });\n } catch (cause) {\n if (cause instanceof KeepSchemaValidationError) throw cause;\n throw new KeepSchemaValidationError(\"KeepKit metadata did not match the configured schema.\", {\n cause,\n });\n }\n}\n\nexport async function validateKeepItem<TMeta>(\n item: KeepItem<unknown>,\n schema: KeepSchema<TMeta>,\n): Promise<KeepItem<TMeta>> {\n return { ...item, meta: await parseKeepMeta(schema, item.meta) };\n}\n"],"mappings":";AAEO,IAAM,4BAAN,cAAwC,MAAM;AAAA,EAInD,YAAY,SAAiB,UAAgD,CAAC,GAAG;AAC/E,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,QAAQ,QAAQ;AACrB,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAGA,eAAsB,cAAiB,QAAuB,OAA4B;AACxF,MAAI;AACF,QAAI,WAAW,OAAQ,QAAO,MAAM,OAAO,MAAM,KAAK;AAEtD,QAAI,eAAe,QAAQ;AACzB,YAAMA,UAAS,MAAM,OAAO,UAAU,KAAK;AAC3C,UAAIA,QAAO,QAAS,QAAOA,QAAO;AAClC,YAAM,IAAI,0BAA0B,yDAAyD;AAAA,QAC3F,OAAOA,QAAO;AAAA,MAChB,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,MAAM,OAAO,WAAW,EAAE,SAAS,KAAK;AACvD,QAAI,CAAC,OAAO,UAAU,WAAW,OAAQ,QAAO,OAAO;AACvD,UAAM,IAAI,0BAA0B,yDAAyD;AAAA,MAC3F,OAAO,OAAO;AAAA,IAChB,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,iBAAiB,0BAA2B,OAAM;AACtD,UAAM,IAAI,0BAA0B,yDAAyD;AAAA,MAC3F;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,iBACpB,MACA,QAC0B;AAC1B,SAAO,EAAE,GAAG,MAAM,MAAM,MAAM,cAAc,QAAQ,KAAK,IAAI,EAAE;AACjE;","names":["result"]}
package/dist/core.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { K as KeepSchema, b as KeepInvalidItemPolicy, a as KeepItem, S as StorageAdapter, c as KeepPluginContext, d as KeepPlugin } from './types-DtILwEQ0.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-DtILwEQ0.js';
3
+ export { K as KeepListOptions, a as KeepStore, b as KeepStoreActions, c as KeepStoreState, Q as QueryKeepItemsResult, g as getTagCounts, q as queryKeepItems } from './store-BAvx_Iaw.js';
4
+ export { KeepSchemaValidationError, parseKeepMeta, validateKeepItem } from './schema.js';
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
+
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
+ type KeepInvalidationPluginOptions<TMeta = Record<string, unknown>> = {
52
+ /** Query keys to invalidate after a successful local KeepKit mutation. */
53
+ queryKeys: readonly unknown[] | ((context: KeepPluginContext<TMeta>) => readonly (readonly unknown[])[]);
54
+ /** Connect this callback to queryClient.invalidateQueries or SWR mutate. */
55
+ invalidate: (queryKey: readonly unknown[], context: KeepPluginContext<TMeta>) => void | Promise<void>;
56
+ name?: string;
57
+ };
58
+ /** Framework-neutral bridge for TanStack Query, SWR, and similar caches. */
59
+ declare function createKeepInvalidationPlugin<TMeta = Record<string, unknown>>(options: KeepInvalidationPluginOptions<TMeta>): KeepPlugin<TMeta>;
60
+
61
+ /** Merge anonymous local items into a signed-in or remote adapter. */
62
+ declare function mergeKeepItems<TMeta>(localItems: KeepItem<TMeta>[], target: StorageAdapter<TMeta>): Promise<KeepItem<TMeta>[]>;
63
+ /** Read anonymous items, merge them into the target, then clear the source. */
64
+ declare function migrateKeepItems<TMeta>(source: StorageAdapter<TMeta>, target: StorageAdapter<TMeta>): Promise<KeepItem<TMeta>[]>;
65
+
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 };
package/dist/core.js ADDED
@@ -0,0 +1,214 @@
1
+ import {
2
+ KeepStore,
3
+ getTagCounts,
4
+ queryKeepItems
5
+ } from "./chunk-PLT2WJVM.js";
6
+ import {
7
+ KeepSchemaValidationError,
8
+ parseKeepMeta,
9
+ validateKeepItem
10
+ } from "./chunk-THZ3ACR2.js";
11
+ import {
12
+ DEFAULT_INDEXEDDB_DATABASE,
13
+ DEFAULT_INDEXEDDB_STORE,
14
+ DEFAULT_STORAGE_KEY,
15
+ DEFAULT_SYNC_QUEUE_DATABASE,
16
+ DEFAULT_SYNC_QUEUE_KEY,
17
+ DEFAULT_SYNC_QUEUE_STORE,
18
+ FallbackStorageAdapter,
19
+ FallbackSyncQueueAdapter,
20
+ IndexedDBAdapter,
21
+ IndexedDBSyncQueueAdapter,
22
+ KeepStorageAccessError,
23
+ KeepStorageError,
24
+ KeepStorageParseError,
25
+ KeepStorageQuotaError,
26
+ LocalStorageAdapter,
27
+ LocalStorageSyncQueueAdapter,
28
+ SyncStorageAdapter,
29
+ createBrowserStorageAdapter,
30
+ createStorageAdapter,
31
+ normalizeKeepTags
32
+ } from "./chunk-C3SOQCVW.js";
33
+
34
+ // src/migration.ts
35
+ async function mergeKeepItems(localItems, target) {
36
+ if (target.merge) return target.merge(localItems);
37
+ const remoteItems = await target.getAll();
38
+ const byId = new Map(remoteItems.map((item) => [item.id, item]));
39
+ for (const localItem of localItems) {
40
+ const remoteItem = byId.get(localItem.id);
41
+ if (!remoteItem || localItem.updatedAt > remoteItem.updatedAt) {
42
+ byId.set(localItem.id, localItem);
43
+ }
44
+ }
45
+ const merged = [...byId.values()].sort((a, b) => b.updatedAt - a.updatedAt);
46
+ await Promise.all(merged.map((item) => target.set(item)));
47
+ return merged;
48
+ }
49
+ async function migrateKeepItems(source, target) {
50
+ const localItems = await source.getAll();
51
+ const merged = await mergeKeepItems(localItems, target);
52
+ await source.clear();
53
+ return merged;
54
+ }
55
+
56
+ // src/backup.ts
57
+ var KEEP_BACKUP_FORMAT = "keepkit";
58
+ var KEEP_BACKUP_VERSION = 1;
59
+ var KeepBackupParseError = class extends Error {
60
+ constructor(message, options) {
61
+ super(message);
62
+ this.name = "KeepBackupParseError";
63
+ if (options?.cause !== void 0) this.cause = options.cause;
64
+ }
65
+ };
66
+ var KeepBackupImportError = class extends Error {
67
+ constructor(message, options) {
68
+ super(message);
69
+ this.name = "KeepBackupImportError";
70
+ this.mode = options.mode;
71
+ this.imported = options.imported;
72
+ this.failed = options.failed;
73
+ if (options.cause !== void 0) this.cause = options.cause;
74
+ }
75
+ };
76
+ async function exportItems(adapter) {
77
+ const backup = {
78
+ format: KEEP_BACKUP_FORMAT,
79
+ version: KEEP_BACKUP_VERSION,
80
+ exportedAt: Date.now(),
81
+ items: await adapter.getAll()
82
+ };
83
+ return JSON.stringify(backup, null, 2);
84
+ }
85
+ async function importItems(adapter, data, options = {}) {
86
+ const backup = parseBackup(data);
87
+ const mode = options.mode ?? "merge";
88
+ const validItems = [];
89
+ let failed = 0;
90
+ for (const item of backup.items) {
91
+ if (!options.schema) {
92
+ validItems.push(item);
93
+ continue;
94
+ }
95
+ try {
96
+ validItems.push(await validateKeepItem(item, options.schema));
97
+ } catch (cause) {
98
+ options.onInvalidItem?.(cause, item);
99
+ if ((options.invalidItemPolicy ?? "error") === "drop") {
100
+ failed += 1;
101
+ continue;
102
+ }
103
+ throw cause;
104
+ }
105
+ }
106
+ let items;
107
+ if (mode === "merge") {
108
+ try {
109
+ items = await mergeKeepItems(validItems, adapter);
110
+ } catch (cause) {
111
+ throw new KeepBackupImportError("KeepKit could not merge the backup.", {
112
+ mode,
113
+ imported: 0,
114
+ failed: validItems.length + failed,
115
+ cause
116
+ });
117
+ }
118
+ } else {
119
+ let imported = 0;
120
+ try {
121
+ await adapter.clear();
122
+ for (const item of validItems) {
123
+ await adapter.set(item);
124
+ imported += 1;
125
+ }
126
+ items = await adapter.getAll();
127
+ } catch (cause) {
128
+ throw new KeepBackupImportError("KeepKit could not replace the stored items.", {
129
+ mode,
130
+ imported,
131
+ failed: validItems.length + failed - imported,
132
+ cause
133
+ });
134
+ }
135
+ }
136
+ return { mode, imported: validItems.length, failed, total: items.length, items };
137
+ }
138
+ function parseBackup(data) {
139
+ let value = data;
140
+ if (typeof data === "string") {
141
+ try {
142
+ value = JSON.parse(data);
143
+ } catch (cause) {
144
+ throw new KeepBackupParseError("KeepKit backup is not valid JSON.", { cause });
145
+ }
146
+ }
147
+ if (!isRecord(value)) throw new KeepBackupParseError("KeepKit backup must be an object.");
148
+ if (value.format !== KEEP_BACKUP_FORMAT || value.version !== KEEP_BACKUP_VERSION) {
149
+ throw new KeepBackupParseError("KeepKit backup format or version is unsupported.");
150
+ }
151
+ if (typeof value.exportedAt !== "number" || !Number.isFinite(value.exportedAt)) {
152
+ throw new KeepBackupParseError("KeepKit backup has an invalid export timestamp.");
153
+ }
154
+ if (!Array.isArray(value.items) || !value.items.every(isKeepItem)) {
155
+ throw new KeepBackupParseError("KeepKit backup contains invalid items.");
156
+ }
157
+ return value;
158
+ }
159
+ function isKeepItem(value) {
160
+ if (!isRecord(value)) return false;
161
+ 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.tags === void 0 || Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === "string"));
162
+ }
163
+ function isRecord(value) {
164
+ return typeof value === "object" && value !== null;
165
+ }
166
+
167
+ // src/integrations.ts
168
+ function createKeepInvalidationPlugin(options) {
169
+ return {
170
+ name: options.name ?? "keepkit-cache-invalidation",
171
+ after: async (context) => {
172
+ const keys = typeof options.queryKeys === "function" ? options.queryKeys(context) : [options.queryKeys];
173
+ await Promise.all(keys.map((queryKey) => options.invalidate(queryKey, context)));
174
+ }
175
+ };
176
+ }
177
+ export {
178
+ DEFAULT_INDEXEDDB_DATABASE,
179
+ DEFAULT_INDEXEDDB_STORE,
180
+ DEFAULT_STORAGE_KEY,
181
+ DEFAULT_SYNC_QUEUE_DATABASE,
182
+ DEFAULT_SYNC_QUEUE_KEY,
183
+ DEFAULT_SYNC_QUEUE_STORE,
184
+ FallbackStorageAdapter,
185
+ FallbackSyncQueueAdapter,
186
+ IndexedDBAdapter,
187
+ IndexedDBSyncQueueAdapter,
188
+ KEEP_BACKUP_FORMAT,
189
+ KEEP_BACKUP_VERSION,
190
+ KeepBackupImportError,
191
+ KeepBackupParseError,
192
+ KeepSchemaValidationError,
193
+ KeepStorageAccessError,
194
+ KeepStorageError,
195
+ KeepStorageParseError,
196
+ KeepStorageQuotaError,
197
+ KeepStore,
198
+ LocalStorageAdapter,
199
+ LocalStorageSyncQueueAdapter,
200
+ SyncStorageAdapter,
201
+ createBrowserStorageAdapter,
202
+ createKeepInvalidationPlugin,
203
+ createStorageAdapter,
204
+ exportItems,
205
+ getTagCounts,
206
+ importItems,
207
+ mergeKeepItems,
208
+ migrateKeepItems,
209
+ normalizeKeepTags,
210
+ parseKeepMeta,
211
+ queryKeepItems,
212
+ validateKeepItem
213
+ };
214
+ //# sourceMappingURL=core.js.map
@@ -0,0 +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.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":[]}
@@ -0,0 +1,151 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, ButtonHTMLAttributes, MouseEvent, HTMLAttributes, ReactElement, PropsWithChildren, ComponentType } from 'react';
3
+ import { m as KeepItemInput, a as KeepItem, l as KeepEventHandlers, S as StorageAdapter, d as KeepPlugin, K as KeepSchema, b as KeepInvalidItemPolicy, t as KeepSyncState } from './types-DtILwEQ0.js';
4
+ import { K as KeepListOptions, a as KeepStore, b as KeepStoreActions } from './store-BAvx_Iaw.js';
5
+
6
+ type UseKeepItemResult<TMeta = Record<string, unknown>> = {
7
+ item: KeepItem<TMeta> | undefined;
8
+ isSaved: boolean;
9
+ isLoading: boolean;
10
+ isMutating: boolean;
11
+ error: unknown | null;
12
+ save: () => Promise<void>;
13
+ remove: () => Promise<void>;
14
+ toggle: () => Promise<void>;
15
+ updateNote: (note?: string) => Promise<void>;
16
+ updateTags: (tags?: string[]) => Promise<void>;
17
+ };
18
+ declare function useKeepItem<TMeta = Record<string, unknown>>(id: string, itemPayload?: KeepItemInput<TMeta>): UseKeepItemResult<TMeta>;
19
+
20
+ type UseKeepListResult<TMeta = Record<string, unknown>> = {
21
+ items: KeepItem<TMeta>[];
22
+ totalCount: number;
23
+ tags: string[];
24
+ tagCounts: Record<string, number>;
25
+ isLoading: boolean;
26
+ isHydrated: boolean;
27
+ isMutating: boolean;
28
+ error: unknown | null;
29
+ remove: (id: string) => Promise<void>;
30
+ removeBatch: (ids: string[]) => Promise<void>;
31
+ updateTagsBatch: (ids: string[], tags?: string[]) => Promise<void>;
32
+ addTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
33
+ removeTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
34
+ clear: () => Promise<void>;
35
+ refresh: () => Promise<void>;
36
+ };
37
+ declare function useKeepList<TMeta = Record<string, unknown>>(options?: KeepListOptions<TMeta>): UseKeepListResult<TMeta>;
38
+
39
+ type KeepShortcutModifier = "meta" | "ctrl" | "alt" | "shift";
40
+ type KeepShortcutOptions<TMeta = Record<string, unknown>> = {
41
+ key: string;
42
+ modifier?: KeepShortcutModifier;
43
+ id?: string;
44
+ itemPayload?: KeepItemInput<TMeta>;
45
+ action?: "toggle" | "save" | "remove";
46
+ enabled?: boolean;
47
+ preventDefault?: boolean;
48
+ allowInEditable?: boolean;
49
+ onTrigger?: (event: KeyboardEvent) => void | Promise<void>;
50
+ onError?: (error: unknown) => void;
51
+ };
52
+ /** Bind a keyboard shortcut to a Keep action or an arbitrary command. */
53
+ declare function useKeepShortcut<TMeta = Record<string, unknown>>(options: KeepShortcutOptions<TMeta>): void;
54
+
55
+ type KeepButtonItem<TMeta = Record<string, unknown>> = KeepItemInput<TMeta> & {
56
+ id: string;
57
+ };
58
+ type KeepButtonSharedProps<TMeta> = {
59
+ item: KeepButtonItem<TMeta>;
60
+ children?: ReactNode | ((state: KeepButtonState<TMeta>) => ReactNode);
61
+ savedLabel?: ReactNode;
62
+ unsavedLabel?: ReactNode;
63
+ disabled?: boolean;
64
+ onToggleError?: (error: unknown) => void;
65
+ };
66
+ type KeepButtonProps<TMeta = Record<string, unknown>> = KeepButtonSharedProps<TMeta> & ((Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onClick" | "aria-pressed"> & {
67
+ asChild?: false;
68
+ onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
69
+ }) | (Omit<HTMLAttributes<HTMLElement>, "children" | "onClick" | "aria-pressed"> & {
70
+ asChild: true;
71
+ children: ReactElement | ((state: KeepButtonState<TMeta>) => ReactElement);
72
+ onClick?: (event: MouseEvent<HTMLElement>) => void;
73
+ }));
74
+ type KeepButtonState<TMeta = Record<string, unknown>> = {
75
+ item: ReturnType<typeof useKeepItem<TMeta>>["item"];
76
+ isSaved: boolean;
77
+ isLoading: boolean;
78
+ isMutating: boolean;
79
+ error: unknown | null;
80
+ save: () => Promise<void>;
81
+ remove: () => Promise<void>;
82
+ toggle: () => Promise<void>;
83
+ updateNote: (note?: string) => Promise<void>;
84
+ updateTags: (tags?: string[]) => Promise<void>;
85
+ };
86
+ /** A style-free accessible save toggle. Consumers provide all visual styling. */
87
+ declare function KeepButton<TMeta = Record<string, unknown>>({ item, children, savedLabel, unsavedLabel, asChild, onToggleError, onClick, disabled, ...buttonProps }: KeepButtonProps<TMeta>): react.JSX.Element;
88
+
89
+ type KeepContextValue<TMeta = Record<string, unknown>> = {
90
+ items: KeepItem<TMeta>[];
91
+ isLoading: boolean;
92
+ isHydrated: boolean;
93
+ isMutating: boolean;
94
+ error: unknown | null;
95
+ syncState: KeepSyncState;
96
+ saveItem: (item: KeepItem<TMeta>) => Promise<void>;
97
+ updateNote: (id: string, note?: string) => Promise<void>;
98
+ updateTags: (id: string, tags?: string[]) => Promise<void>;
99
+ updateTagsBatch: (ids: string[], tags?: string[]) => Promise<void>;
100
+ addTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
101
+ removeTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
102
+ removeItem: (id: string) => Promise<void>;
103
+ removeItems: (ids: string[]) => Promise<void>;
104
+ clear: () => Promise<void>;
105
+ refresh: () => Promise<void>;
106
+ flushSync: () => Promise<void>;
107
+ };
108
+ type KeepProviderProps<TMeta = Record<string, unknown>> = PropsWithChildren<KeepEventHandlers<TMeta> & {
109
+ storage?: StorageAdapter<TMeta>;
110
+ /**
111
+ * Optional server-provided snapshot rendered before the client adapter
112
+ * finishes hydrating. The adapter remains the source of truth after the
113
+ * first refresh.
114
+ */
115
+ initialItems?: KeepItem<TMeta>[];
116
+ plugins?: KeepPlugin<TMeta>[];
117
+ schemaVersion?: number;
118
+ schema?: KeepSchema<TMeta>;
119
+ invalidItemPolicy?: KeepInvalidItemPolicy;
120
+ onInvalidItem?: (error: unknown, item: KeepItem<unknown>) => void;
121
+ migrateMeta?: (meta: unknown, fromVersion: number, toVersion: number, item: KeepItem<TMeta>) => TMeta | Promise<TMeta>;
122
+ }>;
123
+ type KeepStoreAccess<TMeta> = {
124
+ store: KeepStore<TMeta>;
125
+ actions: KeepStoreActions<TMeta>;
126
+ };
127
+ declare function KeepProvider<TMeta = Record<string, unknown>>({ storage, initialItems, onSave, onRemove, onNoteUpdate, onTagsUpdate, onChange, onError, plugins, schemaVersion, schema, invalidItemPolicy, onInvalidItem, migrateMeta, children, }: KeepProviderProps<TMeta>): react.JSX.Element;
128
+ declare function useKeepContext<TMeta = Record<string, unknown>>(): KeepContextValue<TMeta>;
129
+ declare function useKeepStore<TMeta = Record<string, unknown>>(): KeepStoreAccess<TMeta>;
130
+
131
+ type CreateKeepKitOptions<TMeta = Record<string, unknown>> = {
132
+ initialItems?: KeepProviderProps<TMeta>["initialItems"];
133
+ plugins?: KeepPlugin<TMeta>[];
134
+ schemaVersion?: number;
135
+ schema?: KeepSchema<TMeta>;
136
+ invalidItemPolicy?: KeepInvalidItemPolicy;
137
+ onInvalidItem?: KeepProviderProps<TMeta>["onInvalidItem"];
138
+ migrateMeta?: KeepProviderProps<TMeta>["migrateMeta"];
139
+ };
140
+ type KeepKit<TMeta> = {
141
+ KeepProvider: ComponentType<KeepProviderProps<TMeta>>;
142
+ KeepButton: ComponentType<KeepButtonProps<TMeta>>;
143
+ useKeepContext: () => ReturnType<typeof useKeepContext<TMeta>>;
144
+ useKeepItem: (id: string, itemPayload?: KeepItemInput<TMeta>) => UseKeepItemResult<TMeta>;
145
+ useKeepList: (options?: KeepListOptions<TMeta>) => UseKeepListResult<TMeta>;
146
+ useKeepShortcut: (options: KeepShortcutOptions<TMeta>) => void;
147
+ };
148
+ /** Create an app-specific, fully typed set of KeepKit components and hooks. */
149
+ declare function createKeepKit<TMeta = Record<string, unknown>>(options?: CreateKeepKitOptions<TMeta>): KeepKit<TMeta>;
150
+
151
+ export { type CreateKeepKitOptions, KeepButton, type KeepButtonItem, type KeepButtonProps, type KeepButtonState, type KeepContextValue, type KeepKit, KeepListOptions, KeepProvider, type KeepProviderProps, type KeepShortcutModifier, type KeepShortcutOptions, type UseKeepItemResult, type UseKeepListResult, createKeepKit, useKeepContext, useKeepItem, useKeepList, useKeepShortcut, useKeepStore };