@keepkit/core 0.11.0 → 0.12.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.
- package/README.md +6 -2
- package/dist/{chunk-36YIELZE.js → chunk-VJDO3GFH.js} +68 -5
- package/dist/chunk-VJDO3GFH.js.map +1 -0
- package/dist/core.d.ts +50 -7
- package/dist/core.js +200 -1
- package/dist/core.js.map +1 -1
- package/dist/react.d.ts +7 -6
- package/dist/react.js +27 -3
- package/dist/react.js.map +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/{scope-CtLSlZhq.d.ts → scope-y_aBm363.d.ts} +1 -1
- package/dist/storage.d.ts +7 -4
- package/dist/storage.js +1 -2
- package/dist/{types-D-xRiz1Y.d.ts → types-iMK12pmy.d.ts} +28 -3
- package/dist/{url-BYFEfulm.d.ts → url-VhisKZCR.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-36YIELZE.js.map +0 -1
- package/dist/chunk-5QSZP6MT.js +0 -1
- package/dist/chunk-5QSZP6MT.js.map +0 -1
package/dist/core.js
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
KeepStorageError,
|
|
35
35
|
KeepStorageParseError,
|
|
36
36
|
KeepStorageQuotaError,
|
|
37
|
+
KeepSyncAuthError,
|
|
37
38
|
LocalStorageAdapter,
|
|
38
39
|
LocalStorageSyncQueueAdapter,
|
|
39
40
|
ScopedStorageAdapter,
|
|
@@ -43,9 +44,10 @@ import {
|
|
|
43
44
|
createScopedStorageAdapter,
|
|
44
45
|
createStorageAdapter,
|
|
45
46
|
getKeepScopeKey,
|
|
47
|
+
isKeepSyncAuthError,
|
|
46
48
|
isSameKeepScope,
|
|
47
49
|
normalizeKeepTags
|
|
48
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-VJDO3GFH.js";
|
|
49
51
|
|
|
50
52
|
// src/integrations.ts
|
|
51
53
|
function createKeepInvalidationPlugin(options) {
|
|
@@ -85,6 +87,200 @@ function createKeepKitPreset(options = {}) {
|
|
|
85
87
|
}
|
|
86
88
|
var createKeepKitSetup = createKeepKitPreset;
|
|
87
89
|
|
|
90
|
+
// src/templates/auth-sync.ts
|
|
91
|
+
function createAuthenticatedSyncKit(options) {
|
|
92
|
+
const controller = new AuthenticatedSyncStorageController(options);
|
|
93
|
+
return {
|
|
94
|
+
mode: "sync",
|
|
95
|
+
storage: controller,
|
|
96
|
+
get scope() {
|
|
97
|
+
return controller.scope;
|
|
98
|
+
},
|
|
99
|
+
get scopeKey() {
|
|
100
|
+
return controller.scopeKey;
|
|
101
|
+
},
|
|
102
|
+
getScope: () => controller.scope,
|
|
103
|
+
setScope: (scope) => controller.setScope(scope),
|
|
104
|
+
subscribeScope: (listener) => controller.subscribeScope(listener),
|
|
105
|
+
exportBackup: () => controller.exportBackup(),
|
|
106
|
+
dispose: () => controller.dispose()
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
var AuthenticatedSyncStorageController = class {
|
|
110
|
+
constructor(options) {
|
|
111
|
+
this.scopeListeners = /* @__PURE__ */ new Set();
|
|
112
|
+
this.dataListeners = /* @__PURE__ */ new Set();
|
|
113
|
+
this.syncListeners = /* @__PURE__ */ new Set();
|
|
114
|
+
this.unsubscribeData = () => void 0;
|
|
115
|
+
this.unsubscribeSync = () => void 0;
|
|
116
|
+
this.transition = Promise.resolve();
|
|
117
|
+
this.disposed = false;
|
|
118
|
+
this.options = options;
|
|
119
|
+
this.currentScope = options.scope;
|
|
120
|
+
this.current = this.createAdapter(this.currentScope);
|
|
121
|
+
this.attach(this.current);
|
|
122
|
+
}
|
|
123
|
+
get storageKey() {
|
|
124
|
+
return this.current.storageKey;
|
|
125
|
+
}
|
|
126
|
+
get scope() {
|
|
127
|
+
return this.currentScope;
|
|
128
|
+
}
|
|
129
|
+
get scopeKey() {
|
|
130
|
+
return getKeepScopeKey(this.currentScope);
|
|
131
|
+
}
|
|
132
|
+
async getAll() {
|
|
133
|
+
await this.ensureScope();
|
|
134
|
+
return this.current.getAll();
|
|
135
|
+
}
|
|
136
|
+
async set(item) {
|
|
137
|
+
await this.ensureScope();
|
|
138
|
+
return this.current.set(item);
|
|
139
|
+
}
|
|
140
|
+
async setMany(items) {
|
|
141
|
+
await this.ensureScope();
|
|
142
|
+
return this.current.setMany(items);
|
|
143
|
+
}
|
|
144
|
+
async remove(id) {
|
|
145
|
+
await this.ensureScope();
|
|
146
|
+
return this.current.remove(id);
|
|
147
|
+
}
|
|
148
|
+
async removeMany(ids) {
|
|
149
|
+
await this.ensureScope();
|
|
150
|
+
return this.current.removeMany(ids);
|
|
151
|
+
}
|
|
152
|
+
async clear() {
|
|
153
|
+
await this.ensureScope();
|
|
154
|
+
return this.current.clear();
|
|
155
|
+
}
|
|
156
|
+
async merge(items) {
|
|
157
|
+
await this.ensureScope();
|
|
158
|
+
return this.current.merge(items);
|
|
159
|
+
}
|
|
160
|
+
subscribe(listener) {
|
|
161
|
+
this.dataListeners.add(listener);
|
|
162
|
+
return () => this.dataListeners.delete(listener);
|
|
163
|
+
}
|
|
164
|
+
getSyncState() {
|
|
165
|
+
return this.current.getSyncState();
|
|
166
|
+
}
|
|
167
|
+
subscribeSync(listener) {
|
|
168
|
+
this.syncListeners.add(listener);
|
|
169
|
+
return () => this.syncListeners.delete(listener);
|
|
170
|
+
}
|
|
171
|
+
async flushSync() {
|
|
172
|
+
await this.ensureScope();
|
|
173
|
+
return this.current.flushSync();
|
|
174
|
+
}
|
|
175
|
+
async retrySync() {
|
|
176
|
+
await this.ensureScope();
|
|
177
|
+
return this.current.retrySync?.() ?? this.current.flushSync();
|
|
178
|
+
}
|
|
179
|
+
async resolveSyncConflict(id, resolution, item) {
|
|
180
|
+
await this.ensureScope();
|
|
181
|
+
if (!this.current.resolveSyncConflict) {
|
|
182
|
+
throw new Error("The authenticated sync adapter does not support conflict resolution.");
|
|
183
|
+
}
|
|
184
|
+
return this.current.resolveSyncConflict(id, resolution, item);
|
|
185
|
+
}
|
|
186
|
+
async setScope(nextScope) {
|
|
187
|
+
const run = this.transition.then(async () => {
|
|
188
|
+
if (isSameKeepScope(this.currentScope, nextScope)) return;
|
|
189
|
+
if (this.disposed) throw new Error("AuthenticatedSyncKit has been disposed.");
|
|
190
|
+
const previousScope = this.currentScope;
|
|
191
|
+
this.unsubscribeData();
|
|
192
|
+
this.unsubscribeSync();
|
|
193
|
+
this.current.dispose?.();
|
|
194
|
+
this.currentScope = nextScope;
|
|
195
|
+
this.current = this.createAdapter(nextScope);
|
|
196
|
+
this.attach(this.current);
|
|
197
|
+
await this.options.onScopeChange?.(nextScope, previousScope);
|
|
198
|
+
this.notify(this.scopeListeners);
|
|
199
|
+
this.notify(this.dataListeners);
|
|
200
|
+
this.notify(this.syncListeners);
|
|
201
|
+
});
|
|
202
|
+
this.transition = run.catch(() => void 0);
|
|
203
|
+
return run;
|
|
204
|
+
}
|
|
205
|
+
subscribeScope(listener) {
|
|
206
|
+
this.scopeListeners.add(listener);
|
|
207
|
+
return () => this.scopeListeners.delete(listener);
|
|
208
|
+
}
|
|
209
|
+
async exportBackup() {
|
|
210
|
+
await this.ensureScope();
|
|
211
|
+
return exportItems(this.current);
|
|
212
|
+
}
|
|
213
|
+
dispose() {
|
|
214
|
+
this.disposed = true;
|
|
215
|
+
this.unsubscribeData();
|
|
216
|
+
this.unsubscribeSync();
|
|
217
|
+
this.current.dispose?.();
|
|
218
|
+
this.scopeListeners.clear();
|
|
219
|
+
this.dataListeners.clear();
|
|
220
|
+
this.syncListeners.clear();
|
|
221
|
+
}
|
|
222
|
+
createAdapter(scope) {
|
|
223
|
+
const local = this.options.local ? scope ? createScopedStorageAdapter(this.options.local, scope) : this.options.local : createBrowserStorageAdapter({ key: this.options.key, databaseName: this.options.databaseName, scope });
|
|
224
|
+
const queue = this.options.queue && scope ? new ScopedSyncQueueAdapter(this.options.queue, scope) : this.options.queue;
|
|
225
|
+
const remote = createAuthenticatedRemote(this.options, scope);
|
|
226
|
+
return new SyncStorageAdapter({
|
|
227
|
+
...this.options,
|
|
228
|
+
local,
|
|
229
|
+
remote,
|
|
230
|
+
queue,
|
|
231
|
+
scope
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
attach(adapter) {
|
|
235
|
+
this.unsubscribeData = adapter.subscribe?.(() => this.notify(this.dataListeners)) ?? (() => void 0);
|
|
236
|
+
this.unsubscribeSync = adapter.subscribeSync(() => this.notify(this.syncListeners));
|
|
237
|
+
}
|
|
238
|
+
async ensureScope() {
|
|
239
|
+
if (!this.options.getScope) return;
|
|
240
|
+
await this.setScope(await this.options.getScope());
|
|
241
|
+
}
|
|
242
|
+
notify(listeners) {
|
|
243
|
+
for (const listener of listeners) listener();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
function createAuthenticatedRemote(options, scope) {
|
|
247
|
+
const pull = options.transport.pull;
|
|
248
|
+
return {
|
|
249
|
+
push: async (operation) => {
|
|
250
|
+
try {
|
|
251
|
+
const token = await options.getAuthToken();
|
|
252
|
+
return await options.transport.push(operation, { token, scope, operation });
|
|
253
|
+
} catch (cause) {
|
|
254
|
+
return handleAuthFailure(cause, options, { operation, scope });
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
pull: pull ? async () => {
|
|
258
|
+
try {
|
|
259
|
+
const token = await options.getAuthToken();
|
|
260
|
+
return await pull({ token, scope });
|
|
261
|
+
} catch (cause) {
|
|
262
|
+
return handleAuthFailure(cause, options, { scope });
|
|
263
|
+
}
|
|
264
|
+
} : void 0
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
async function handleAuthFailure(cause, options, context) {
|
|
268
|
+
const status = getAuthStatus(cause);
|
|
269
|
+
if (!status) throw cause;
|
|
270
|
+
const error = cause instanceof KeepSyncAuthError ? cause : new KeepSyncAuthError(status, { operation: context.operation, scope: context.scope, cause });
|
|
271
|
+
await options.onAuthError?.(error, context);
|
|
272
|
+
await options.onReauthenticate?.(error, context);
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
function getAuthStatus(error) {
|
|
276
|
+
if (error instanceof KeepSyncAuthError) return error.status;
|
|
277
|
+
if (!error || typeof error !== "object") return void 0;
|
|
278
|
+
const candidate = error;
|
|
279
|
+
if (candidate.status === 401 || candidate.status === 403) return candidate.status;
|
|
280
|
+
if (candidate.response?.status === 401 || candidate.response?.status === 403) return candidate.response.status;
|
|
281
|
+
return candidate.cause ? getAuthStatus(candidate.cause) : void 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
88
284
|
// src/url.ts
|
|
89
285
|
var DEFAULT_KEEP_URL_PARAMS = {
|
|
90
286
|
search: "q",
|
|
@@ -166,11 +362,13 @@ export {
|
|
|
166
362
|
KeepStorageParseError,
|
|
167
363
|
KeepStorageQuotaError,
|
|
168
364
|
KeepStore,
|
|
365
|
+
KeepSyncAuthError,
|
|
169
366
|
LocalStorageAdapter,
|
|
170
367
|
LocalStorageSyncQueueAdapter,
|
|
171
368
|
ScopedStorageAdapter,
|
|
172
369
|
ScopedSyncQueueAdapter,
|
|
173
370
|
SyncStorageAdapter,
|
|
371
|
+
createAuthenticatedSyncKit,
|
|
174
372
|
createBrowserStorageAdapter,
|
|
175
373
|
createKeepInvalidationPlugin,
|
|
176
374
|
createKeepKitPreset,
|
|
@@ -184,6 +382,7 @@ export {
|
|
|
184
382
|
getTagCounts,
|
|
185
383
|
importItems,
|
|
186
384
|
isKeepItemMetadataStale,
|
|
385
|
+
isKeepSyncAuthError,
|
|
187
386
|
isSameKeepScope,
|
|
188
387
|
mergeKeepItems,
|
|
189
388
|
mergeKeepListQueryFromUrl,
|
package/dist/core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/integrations.ts","../src/presets.ts","../src/url.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","import { exportItems } from \"./backup\";\nimport { createScopedStorageAdapter, type KeepScope } from \"./scope\";\nimport { createBrowserStorageAdapter } from \"./storage/index\";\nimport { SyncStorageAdapter } from \"./storage/sync\";\nimport type { RemoteSyncDriver, StorageAdapter } from \"./types\";\n\nexport type KeepKitPresetMode = \"local\" | \"sync\" | \"backup\";\n\nexport type KeepKitPresetOptions<TMeta = Record<string, unknown>> = {\n mode?: KeepKitPresetMode;\n key?: string;\n scope?: KeepScope;\n remote?: RemoteSyncDriver<TMeta>;\n storage?: StorageAdapter<TMeta>;\n};\n\nexport type KeepKitSetup<TMeta = Record<string, unknown>> = {\n mode: KeepKitPresetMode;\n scope?: KeepScope;\n storage: StorageAdapter<TMeta>;\n exportBackup: () => Promise<string>;\n};\n\n/**\n * Build the recommended local/sync/backup wiring without imposing an auth or\n * API client. Pass the current user and tenant scope whenever the account changes.\n */\nexport function createKeepKitPreset<TMeta = Record<string, unknown>>(\n options: KeepKitPresetOptions<TMeta> = {},\n): KeepKitSetup<TMeta> {\n const mode = options.mode ?? \"local\";\n const local = options.storage\n ? options.scope\n ? createScopedStorageAdapter(options.storage, options.scope)\n : options.storage\n : createBrowserStorageAdapter<TMeta>({ key: options.key, scope: options.scope });\n if (mode === \"sync\" && !options.remote) {\n throw new Error('createKeepKitPreset({ mode: \"sync\" }) requires a remote driver.');\n }\n let storage: StorageAdapter<TMeta> = local;\n if (mode === \"sync\") {\n const remote = options.remote;\n if (!remote) throw new Error('createKeepKitPreset({ mode: \"sync\" }) requires a remote driver.');\n storage = new SyncStorageAdapter<TMeta>({\n local,\n remote,\n userId: options.scope?.userId,\n tenantId: options.scope?.tenantId,\n });\n }\n return {\n mode,\n scope: options.scope,\n storage,\n exportBackup: () => exportItems(storage),\n };\n}\n\nexport const createKeepKitSetup = createKeepKitPreset;\n","import type { KeepListQuery } from \"./query\";\n\nexport type KeepUrlParamNames = {\n search: string;\n tags: string;\n sort: string;\n page: string;\n};\n\nexport type KeepUrlSyncOptions = {\n /** Parameters are intentionally short so shared collection URLs stay readable. */\n params?: Partial<KeepUrlParamNames>;\n /** Push is the default so browser back/forward restores collection states. */\n history?: \"replace\" | \"push\";\n /** URL to read/write. Defaults to the current browser URL. */\n url?: string;\n};\n\nexport const DEFAULT_KEEP_URL_PARAMS: KeepUrlParamNames = {\n search: \"q\",\n tags: \"tag\",\n sort: \"sort\",\n page: \"page\",\n};\n\nexport type KeepUrlState = Pick<KeepListQuery, \"search\" | \"tags\" | \"sort\" | \"pagination\">;\n\n/** Convert a list query to stable URLSearchParams without serializing functions or unsupported filters. */\nexport function encodeKeepListQuery<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta> = {},\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): URLSearchParams {\n const params = { ...DEFAULT_KEEP_URL_PARAMS, ...options.params };\n const result = new URLSearchParams();\n const search = query.search?.query?.trim();\n if (search) result.set(params.search, search);\n for (const tag of query.tags ?? []) {\n const normalized = tag.trim();\n if (normalized) result.append(params.tags, normalized);\n }\n if (query.sort?.by) result.set(params.sort, `${query.sort.by}:${query.sort.direction ?? \"desc\"}`);\n const page = query.pagination?.page;\n if (page !== undefined && Number.isFinite(page) && page > 1) result.set(params.page, String(Math.floor(page)));\n return result;\n}\n\n/** Parse a URL into the query fields supported by KeepCollection. Invalid values are ignored. */\nexport function decodeKeepListQuery(\n input: string | URL | URLSearchParams,\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): KeepUrlState {\n const params = { ...DEFAULT_KEEP_URL_PARAMS, ...options.params };\n const searchParams = input instanceof URLSearchParams ? input : new URL(input, \"http://keepkit.invalid\").searchParams;\n const search = searchParams.get(params.search)?.trim();\n const tags = [\n ...new Set(\n searchParams\n .getAll(params.tags)\n .map((tag) => tag.trim())\n .filter(Boolean),\n ),\n ];\n const sortValue = searchParams.get(params.sort)?.split(\":\");\n const sort: KeepListQuery[\"sort\"] =\n sortValue?.[0] === \"savedAt\" || sortValue?.[0] === \"updatedAt\"\n ? {\n by: sortValue[0],\n direction: sortValue[1] === \"asc\" ? (\"asc\" as const) : (\"desc\" as const),\n }\n : undefined;\n const rawPage = Number(searchParams.get(params.page));\n const page = Number.isInteger(rawPage) && rawPage > 0 ? rawPage : undefined;\n return {\n ...(search ? { search: { query: search } } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n ...(sort ? { sort } : {}),\n ...(page ? { pagination: { page } } : {}),\n };\n}\n\nexport function serializeKeepListQuery<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta> = {},\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): string {\n const value = encodeKeepListQuery(query, options).toString();\n return value ? `?${value}` : \"\";\n}\n\nexport function mergeKeepListQueryFromUrl<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta>,\n input: string | URL | URLSearchParams,\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): KeepListQuery<TMeta> {\n const decoded = decodeKeepListQuery(input, options);\n return {\n ...query,\n ...decoded,\n search: decoded.search ?? query.search,\n tags: decoded.tags ?? query.tags,\n sort: decoded.sort ?? query.sort,\n pagination: decoded.pagination ? { ...query.pagination, ...decoded.pagination } : query.pagination,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/integrations.ts","../src/presets.ts","../src/templates/auth-sync.ts","../src/url.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","import { exportItems } from \"./backup\";\nimport { createScopedStorageAdapter, type KeepScope } from \"./scope\";\nimport { createBrowserStorageAdapter } from \"./storage/index\";\nimport { SyncStorageAdapter } from \"./storage/sync\";\nimport type { RemoteSyncDriver, StorageAdapter } from \"./types\";\n\nexport type KeepKitPresetMode = \"local\" | \"sync\" | \"backup\";\n\nexport type KeepKitPresetOptions<TMeta = Record<string, unknown>> = {\n mode?: KeepKitPresetMode;\n key?: string;\n scope?: KeepScope;\n remote?: RemoteSyncDriver<TMeta>;\n storage?: StorageAdapter<TMeta>;\n};\n\nexport type KeepKitSetup<TMeta = Record<string, unknown>> = {\n mode: KeepKitPresetMode;\n scope?: KeepScope;\n storage: StorageAdapter<TMeta>;\n exportBackup: () => Promise<string>;\n};\n\n/**\n * Build the recommended local/sync/backup wiring without imposing an auth or\n * API client. Pass the current user and tenant scope whenever the account changes.\n */\nexport function createKeepKitPreset<TMeta = Record<string, unknown>>(\n options: KeepKitPresetOptions<TMeta> = {},\n): KeepKitSetup<TMeta> {\n const mode = options.mode ?? \"local\";\n const local = options.storage\n ? options.scope\n ? createScopedStorageAdapter(options.storage, options.scope)\n : options.storage\n : createBrowserStorageAdapter<TMeta>({ key: options.key, scope: options.scope });\n if (mode === \"sync\" && !options.remote) {\n throw new Error('createKeepKitPreset({ mode: \"sync\" }) requires a remote driver.');\n }\n let storage: StorageAdapter<TMeta> = local;\n if (mode === \"sync\") {\n const remote = options.remote;\n if (!remote) throw new Error('createKeepKitPreset({ mode: \"sync\" }) requires a remote driver.');\n storage = new SyncStorageAdapter<TMeta>({\n local,\n remote,\n userId: options.scope?.userId,\n tenantId: options.scope?.tenantId,\n });\n }\n return {\n mode,\n scope: options.scope,\n storage,\n exportBackup: () => exportItems(storage),\n };\n}\n\nexport const createKeepKitSetup = createKeepKitPreset;\n","import { exportItems } from \"../backup\";\nimport { createScopedStorageAdapter, getKeepScopeKey, isSameKeepScope, ScopedSyncQueueAdapter } from \"../scope\";\nimport { type BrowserStorageAdapterOptions, createBrowserStorageAdapter } from \"../storage\";\nimport { SyncStorageAdapter, type SyncStorageAdapterOptions } from \"../storage/sync\";\nimport type {\n KeepItem,\n KeepSyncAuthError,\n KeepSyncAuthStatus,\n KeepSyncState,\n RemoteSyncDriver,\n RemoteSyncResult,\n StorageAdapter,\n SyncCapableStorageAdapter,\n SyncOperation,\n SyncScope,\n} from \"../types\";\nimport { KeepSyncAuthError as KeepSyncAuthErrorClass } from \"../types\";\n\nexport type AuthenticatedSyncRequestContext<TMeta = Record<string, unknown>> = {\n token: string | null;\n scope?: SyncScope;\n operation?: SyncOperation<TMeta>;\n};\n\n/** Transport boundary for auth-aware requests; cookies and bearer tokens remain host concerns. */\nexport type AuthenticatedSyncTransport<TMeta = Record<string, unknown>> = {\n push: (\n operation: SyncOperation<TMeta>,\n context: AuthenticatedSyncRequestContext<TMeta>,\n ) => Promise<RemoteSyncResult<TMeta>>;\n pull?: (context: AuthenticatedSyncRequestContext<TMeta>) => Promise<KeepItem<TMeta>[]>;\n};\n\nexport type AuthenticatedSyncAuthContext<TMeta = Record<string, unknown>> = {\n operation?: SyncOperation<TMeta>;\n scope?: SyncScope;\n};\n\nexport type AuthenticatedSyncKitOptions<TMeta = Record<string, unknown>> = Omit<\n SyncStorageAdapterOptions<TMeta>,\n \"local\" | \"remote\" | \"scope\"\n> & {\n /** Optional custom local adapter. Browser storage is used when omitted. */\n local?: StorageAdapter<TMeta>;\n key?: BrowserStorageAdapterOptions[\"key\"];\n databaseName?: BrowserStorageAdapterOptions[\"databaseName\"];\n scope?: SyncScope;\n /** Resolve the active account or tenant before storage operations. */\n getScope?: () => SyncScope | undefined | Promise<SyncScope | undefined>;\n getAuthToken: () => Promise<string | null>;\n transport: AuthenticatedSyncTransport<TMeta>;\n onAuthError?: (error: KeepSyncAuthError<TMeta>, context: AuthenticatedSyncAuthContext<TMeta>) => void | Promise<void>;\n onReauthenticate?: (\n error: KeepSyncAuthError<TMeta>,\n context: AuthenticatedSyncAuthContext<TMeta>,\n ) => void | Promise<void>;\n onScopeChange?: (next: SyncScope | undefined, previous: SyncScope | undefined) => void | Promise<void>;\n};\n\nexport type AuthenticatedSyncKit<TMeta = Record<string, unknown>> = {\n readonly mode: \"sync\";\n readonly storage: SyncCapableStorageAdapter<TMeta>;\n readonly scope?: SyncScope;\n readonly scopeKey: string;\n getScope(): SyncScope | undefined;\n setScope(scope?: SyncScope): Promise<void>;\n subscribeScope(listener: () => void): () => void;\n exportBackup(): Promise<string>;\n dispose(): void;\n};\n\n/** Creates auth-independent sync wiring with per-request tokens and isolated account scopes. */\nexport function createAuthenticatedSyncKit<TMeta = Record<string, unknown>>(\n options: AuthenticatedSyncKitOptions<TMeta>,\n): AuthenticatedSyncKit<TMeta> {\n const controller = new AuthenticatedSyncStorageController(options);\n return {\n mode: \"sync\",\n storage: controller,\n get scope() {\n return controller.scope;\n },\n get scopeKey() {\n return controller.scopeKey;\n },\n getScope: () => controller.scope,\n setScope: (scope) => controller.setScope(scope),\n subscribeScope: (listener) => controller.subscribeScope(listener),\n exportBackup: () => controller.exportBackup(),\n dispose: () => controller.dispose(),\n };\n}\n\nclass AuthenticatedSyncStorageController<TMeta = Record<string, unknown>> implements SyncCapableStorageAdapter<TMeta> {\n private readonly options: AuthenticatedSyncKitOptions<TMeta>;\n private currentScope: SyncScope | undefined;\n private current: SyncStorageAdapter<TMeta>;\n private readonly scopeListeners = new Set<() => void>();\n private readonly dataListeners = new Set<() => void>();\n private readonly syncListeners = new Set<() => void>();\n private unsubscribeData: () => void = () => undefined;\n private unsubscribeSync: () => void = () => undefined;\n private transition = Promise.resolve();\n private disposed = false;\n\n constructor(options: AuthenticatedSyncKitOptions<TMeta>) {\n this.options = options;\n this.currentScope = options.scope;\n this.current = this.createAdapter(this.currentScope);\n this.attach(this.current);\n }\n\n get storageKey(): string | undefined {\n return this.current.storageKey;\n }\n\n get scope(): SyncScope | undefined {\n return this.currentScope;\n }\n\n get scopeKey(): string {\n return getKeepScopeKey(this.currentScope);\n }\n\n async getAll(): Promise<KeepItem<TMeta>[]> {\n await this.ensureScope();\n return this.current.getAll();\n }\n\n async set(item: KeepItem<TMeta>): Promise<void> {\n await this.ensureScope();\n return this.current.set(item);\n }\n\n async setMany(items: KeepItem<TMeta>[]): Promise<void> {\n await this.ensureScope();\n return this.current.setMany(items);\n }\n\n async remove(id: string): Promise<void> {\n await this.ensureScope();\n return this.current.remove(id);\n }\n\n async removeMany(ids: string[]): Promise<void> {\n await this.ensureScope();\n return this.current.removeMany(ids);\n }\n\n async clear(): Promise<void> {\n await this.ensureScope();\n return this.current.clear();\n }\n\n async merge(items: KeepItem<TMeta>[]): Promise<KeepItem<TMeta>[]> {\n await this.ensureScope();\n return this.current.merge(items);\n }\n\n subscribe(listener: () => void): () => void {\n this.dataListeners.add(listener);\n return () => this.dataListeners.delete(listener);\n }\n\n getSyncState(): KeepSyncState<TMeta> {\n return this.current.getSyncState();\n }\n\n subscribeSync(listener: () => void): () => void {\n this.syncListeners.add(listener);\n return () => this.syncListeners.delete(listener);\n }\n\n async flushSync(): Promise<void> {\n await this.ensureScope();\n return this.current.flushSync();\n }\n\n async retrySync(): Promise<void> {\n await this.ensureScope();\n return this.current.retrySync?.() ?? this.current.flushSync();\n }\n\n async resolveSyncConflict(\n id: string,\n resolution: \"local\" | \"remote\" | \"manual\",\n item?: KeepItem<TMeta>,\n ): Promise<void> {\n await this.ensureScope();\n if (!this.current.resolveSyncConflict) {\n throw new Error(\"The authenticated sync adapter does not support conflict resolution.\");\n }\n return this.current.resolveSyncConflict(id, resolution, item);\n }\n\n async setScope(nextScope?: SyncScope): Promise<void> {\n const run = this.transition.then(async () => {\n if (isSameKeepScope(this.currentScope, nextScope)) return;\n if (this.disposed) throw new Error(\"AuthenticatedSyncKit has been disposed.\");\n const previousScope = this.currentScope;\n this.unsubscribeData();\n this.unsubscribeSync();\n this.current.dispose?.();\n this.currentScope = nextScope;\n this.current = this.createAdapter(nextScope);\n this.attach(this.current);\n await this.options.onScopeChange?.(nextScope, previousScope);\n this.notify(this.scopeListeners);\n this.notify(this.dataListeners);\n this.notify(this.syncListeners);\n });\n this.transition = run.catch(() => undefined);\n return run;\n }\n\n subscribeScope(listener: () => void): () => void {\n this.scopeListeners.add(listener);\n return () => this.scopeListeners.delete(listener);\n }\n\n async exportBackup(): Promise<string> {\n await this.ensureScope();\n return exportItems(this.current);\n }\n\n dispose(): void {\n this.disposed = true;\n this.unsubscribeData();\n this.unsubscribeSync();\n this.current.dispose?.();\n this.scopeListeners.clear();\n this.dataListeners.clear();\n this.syncListeners.clear();\n }\n\n private createAdapter(scope: SyncScope | undefined): SyncStorageAdapter<TMeta> {\n const local = this.options.local\n ? scope\n ? createScopedStorageAdapter(this.options.local, scope)\n : this.options.local\n : createBrowserStorageAdapter<TMeta>({ key: this.options.key, databaseName: this.options.databaseName, scope });\n const queue =\n this.options.queue && scope ? new ScopedSyncQueueAdapter(this.options.queue, scope) : this.options.queue;\n const remote = createAuthenticatedRemote(this.options, scope);\n return new SyncStorageAdapter<TMeta>({\n ...this.options,\n local,\n remote,\n queue,\n scope,\n });\n }\n\n private attach(adapter: SyncStorageAdapter<TMeta>): void {\n this.unsubscribeData = adapter.subscribe?.(() => this.notify(this.dataListeners)) ?? (() => undefined);\n this.unsubscribeSync = adapter.subscribeSync(() => this.notify(this.syncListeners));\n }\n\n private async ensureScope(): Promise<void> {\n if (!this.options.getScope) return;\n await this.setScope(await this.options.getScope());\n }\n\n private notify(listeners: Set<() => void>): void {\n for (const listener of listeners) listener();\n }\n}\n\nfunction createAuthenticatedRemote<TMeta>(\n options: AuthenticatedSyncKitOptions<TMeta>,\n scope: SyncScope | undefined,\n): RemoteSyncDriver<TMeta> {\n const pull = options.transport.pull;\n return {\n push: async (operation) => {\n try {\n const token = await options.getAuthToken();\n return await options.transport.push(operation, { token, scope, operation });\n } catch (cause) {\n return handleAuthFailure(cause, options, { operation, scope });\n }\n },\n pull: pull\n ? async () => {\n try {\n const token = await options.getAuthToken();\n return await pull({ token, scope });\n } catch (cause) {\n return handleAuthFailure(cause, options, { scope });\n }\n }\n : undefined,\n };\n}\n\nasync function handleAuthFailure<TMeta>(\n cause: unknown,\n options: AuthenticatedSyncKitOptions<TMeta>,\n context: AuthenticatedSyncAuthContext<TMeta>,\n): Promise<never> {\n const status = getAuthStatus(cause);\n if (!status) throw cause;\n const error =\n cause instanceof KeepSyncAuthErrorClass\n ? cause\n : new KeepSyncAuthErrorClass(status, { operation: context.operation, scope: context.scope, cause });\n await options.onAuthError?.(error, context);\n await options.onReauthenticate?.(error, context);\n throw error;\n}\n\nfunction getAuthStatus(error: unknown): KeepSyncAuthStatus | undefined {\n if (error instanceof KeepSyncAuthErrorClass) return error.status;\n if (!error || typeof error !== \"object\") return undefined;\n const candidate = error as { status?: unknown; response?: { status?: unknown }; cause?: unknown };\n if (candidate.status === 401 || candidate.status === 403) return candidate.status;\n if (candidate.response?.status === 401 || candidate.response?.status === 403) return candidate.response.status;\n return candidate.cause ? getAuthStatus(candidate.cause) : undefined;\n}\n","import type { KeepListQuery } from \"./query\";\n\nexport type KeepUrlParamNames = {\n search: string;\n tags: string;\n sort: string;\n page: string;\n};\n\nexport type KeepUrlSyncOptions = {\n /** Parameters are intentionally short so shared collection URLs stay readable. */\n params?: Partial<KeepUrlParamNames>;\n /** Push is the default so browser back/forward restores collection states. */\n history?: \"replace\" | \"push\";\n /** URL to read/write. Defaults to the current browser URL. */\n url?: string;\n};\n\nexport const DEFAULT_KEEP_URL_PARAMS: KeepUrlParamNames = {\n search: \"q\",\n tags: \"tag\",\n sort: \"sort\",\n page: \"page\",\n};\n\nexport type KeepUrlState = Pick<KeepListQuery, \"search\" | \"tags\" | \"sort\" | \"pagination\">;\n\n/** Convert a list query to stable URLSearchParams without serializing functions or unsupported filters. */\nexport function encodeKeepListQuery<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta> = {},\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): URLSearchParams {\n const params = { ...DEFAULT_KEEP_URL_PARAMS, ...options.params };\n const result = new URLSearchParams();\n const search = query.search?.query?.trim();\n if (search) result.set(params.search, search);\n for (const tag of query.tags ?? []) {\n const normalized = tag.trim();\n if (normalized) result.append(params.tags, normalized);\n }\n if (query.sort?.by) result.set(params.sort, `${query.sort.by}:${query.sort.direction ?? \"desc\"}`);\n const page = query.pagination?.page;\n if (page !== undefined && Number.isFinite(page) && page > 1) result.set(params.page, String(Math.floor(page)));\n return result;\n}\n\n/** Parse a URL into the query fields supported by KeepCollection. Invalid values are ignored. */\nexport function decodeKeepListQuery(\n input: string | URL | URLSearchParams,\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): KeepUrlState {\n const params = { ...DEFAULT_KEEP_URL_PARAMS, ...options.params };\n const searchParams = input instanceof URLSearchParams ? input : new URL(input, \"http://keepkit.invalid\").searchParams;\n const search = searchParams.get(params.search)?.trim();\n const tags = [\n ...new Set(\n searchParams\n .getAll(params.tags)\n .map((tag) => tag.trim())\n .filter(Boolean),\n ),\n ];\n const sortValue = searchParams.get(params.sort)?.split(\":\");\n const sort: KeepListQuery[\"sort\"] =\n sortValue?.[0] === \"savedAt\" || sortValue?.[0] === \"updatedAt\"\n ? {\n by: sortValue[0],\n direction: sortValue[1] === \"asc\" ? (\"asc\" as const) : (\"desc\" as const),\n }\n : undefined;\n const rawPage = Number(searchParams.get(params.page));\n const page = Number.isInteger(rawPage) && rawPage > 0 ? rawPage : undefined;\n return {\n ...(search ? { search: { query: search } } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n ...(sort ? { sort } : {}),\n ...(page ? { pagination: { page } } : {}),\n };\n}\n\nexport function serializeKeepListQuery<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta> = {},\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): string {\n const value = encodeKeepListQuery(query, options).toString();\n return value ? `?${value}` : \"\";\n}\n\nexport function mergeKeepListQueryFromUrl<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta>,\n input: string | URL | URLSearchParams,\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): KeepListQuery<TMeta> {\n const decoded = decodeKeepListQuery(input, options);\n return {\n ...query,\n ...decoded,\n search: decoded.search ?? query.search,\n tags: decoded.tags ?? query.tags,\n sort: decoded.sort ?? query.sort,\n pagination: decoded.pagination ? { ...query.pagination, ...decoded.pagination } : query.pagination,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ButtonHTMLAttributes, MouseEvent, HTMLAttributes, ReactElement, ErrorInfo, Component, PropsWithChildren, ComponentType } from 'react';
|
|
3
|
-
import { g as KeepItemMetadataRefresher, l as KeepItemRevalidator, R as RevalidateKeepItemsOptions, k as KeepItemRevalidationSummary, m as KeepListQuery, h as KeepItemResolver, c as KeepAutoRevalidationOptions, I as ImportItemsOptions, a as ImportItemsResult, n as KeepStore, o as KeepStoreActions } from './url-
|
|
4
|
-
export { j as KeepItemRevalidationResult, q as KeepUrlParamNames, r as KeepUrlState, s as KeepUrlSyncOptions, y as isKeepItemMetadataStale } from './url-
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
7
|
-
export { K as KeepScope, S as ScopedStorageAdapter } from './scope-
|
|
3
|
+
import { g as KeepItemMetadataRefresher, l as KeepItemRevalidator, R as RevalidateKeepItemsOptions, k as KeepItemRevalidationSummary, m as KeepListQuery, h as KeepItemResolver, c as KeepAutoRevalidationOptions, I as ImportItemsOptions, a as ImportItemsResult, n as KeepStore, o as KeepStoreActions } from './url-VhisKZCR.js';
|
|
4
|
+
export { j as KeepItemRevalidationResult, q as KeepUrlParamNames, r as KeepUrlState, s as KeepUrlSyncOptions, y as isKeepItemMetadataStale } from './url-VhisKZCR.js';
|
|
5
|
+
import { s as KeepItemInput, a as KeepItem, q as KeepEventHandlers, b as StorageAdapter, f as KeepPlugin, K as KeepSchema, r as KeepInvalidItemPolicy, k as KeepChangeContext, D as KeepSyncState, F as KeepUndoState } from './types-iMK12pmy.js';
|
|
6
|
+
export { t as KeepItemStatus } from './types-iMK12pmy.js';
|
|
7
|
+
export { K as KeepScope, S as ScopedStorageAdapter } from './scope-y_aBm363.js';
|
|
8
8
|
|
|
9
9
|
type UseKeepItemResult<TMeta = Record<string, unknown>> = {
|
|
10
10
|
item: KeepItem<TMeta> | undefined;
|
|
@@ -126,7 +126,7 @@ type KeepContextValue<TMeta = Record<string, unknown>> = {
|
|
|
126
126
|
isMutating: boolean;
|
|
127
127
|
error: unknown | null;
|
|
128
128
|
lastChange?: KeepChangeContext<TMeta>;
|
|
129
|
-
syncState: KeepSyncState
|
|
129
|
+
syncState: KeepSyncState<TMeta>;
|
|
130
130
|
undo: KeepUndoState;
|
|
131
131
|
saveItem: (item: KeepItem<TMeta>) => Promise<void>;
|
|
132
132
|
updateNote: (id: string, note?: string) => Promise<void>;
|
|
@@ -142,6 +142,7 @@ type KeepContextValue<TMeta = Record<string, unknown>> = {
|
|
|
142
142
|
clear: () => Promise<void>;
|
|
143
143
|
refresh: () => Promise<void>;
|
|
144
144
|
flushSync: () => Promise<void>;
|
|
145
|
+
resolveSyncConflict: (id: string, resolution: "local" | "remote" | "manual", item?: KeepItem<TMeta>) => Promise<void>;
|
|
145
146
|
refreshItemMetadata: (id: string, refresh: KeepItemMetadataRefresher<TMeta>) => Promise<void>;
|
|
146
147
|
revalidateItems: (revalidator?: KeepItemRevalidator<TMeta>, options?: RevalidateKeepItemsOptions<TMeta>) => Promise<KeepItemRevalidationSummary<TMeta>>;
|
|
147
148
|
exportBackup: () => Promise<string>;
|
package/dist/react.js
CHANGED
|
@@ -10,11 +10,10 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
parseKeepMeta
|
|
12
12
|
} from "./chunk-THZ3ACR2.js";
|
|
13
|
-
import "./chunk-5QSZP6MT.js";
|
|
14
13
|
import {
|
|
15
14
|
createBrowserStorageAdapter,
|
|
16
15
|
normalizeKeepTags
|
|
17
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VJDO3GFH.js";
|
|
18
17
|
|
|
19
18
|
// src/hooks/useKeepItem.ts
|
|
20
19
|
import { useCallback as useCallback3 } from "react";
|
|
@@ -272,6 +271,16 @@ function KeepProviderContent({
|
|
|
272
271
|
if (!storage.subscribe) return;
|
|
273
272
|
return storage.subscribe(() => void refresh());
|
|
274
273
|
}, [refresh, storage]);
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
if (!isScopeAwareStorage(storage)) return;
|
|
276
|
+
return storage.subscribeScope(() => {
|
|
277
|
+
undoRef.current?.timer && clearTimeout(undoRef.current.timer);
|
|
278
|
+
undoRef.current = void 0;
|
|
279
|
+
itemsRef.current = [];
|
|
280
|
+
store.setState({ items: [], isHydrated: false, isLoading: true, error: null, undo: EMPTY_UNDO_STATE });
|
|
281
|
+
void refresh();
|
|
282
|
+
});
|
|
283
|
+
}, [refresh, storage, store]);
|
|
275
284
|
const runMutation = useCallback(
|
|
276
285
|
(action, id, createPlan) => {
|
|
277
286
|
pendingMutationsRef.current += 1;
|
|
@@ -656,6 +665,15 @@ function KeepProviderContent({
|
|
|
656
665
|
[revalidateItems]
|
|
657
666
|
);
|
|
658
667
|
const flushSync = useCallback(() => syncStorage ? syncStorage.flushSync() : Promise.resolve(), [syncStorage]);
|
|
668
|
+
const resolveSyncConflict = useCallback(
|
|
669
|
+
(id, resolution, item) => {
|
|
670
|
+
if (!syncStorage?.resolveSyncConflict) {
|
|
671
|
+
return Promise.reject(new Error("The configured storage does not support sync conflict resolution."));
|
|
672
|
+
}
|
|
673
|
+
return syncStorage.resolveSyncConflict(id, resolution, item);
|
|
674
|
+
},
|
|
675
|
+
[syncStorage]
|
|
676
|
+
);
|
|
659
677
|
const exportBackup = useCallback(() => exportItems(storage), [storage]);
|
|
660
678
|
const importBackup = useCallback(
|
|
661
679
|
async (data, options = {}) => {
|
|
@@ -708,6 +726,7 @@ function KeepProviderContent({
|
|
|
708
726
|
clear,
|
|
709
727
|
refresh,
|
|
710
728
|
flushSync,
|
|
729
|
+
resolveSyncConflict,
|
|
711
730
|
refreshItemMetadata,
|
|
712
731
|
revalidateItems,
|
|
713
732
|
exportBackup,
|
|
@@ -718,6 +737,7 @@ function KeepProviderContent({
|
|
|
718
737
|
error,
|
|
719
738
|
lastChange,
|
|
720
739
|
flushSync,
|
|
740
|
+
resolveSyncConflict,
|
|
721
741
|
isHydrated,
|
|
722
742
|
isLoading,
|
|
723
743
|
isMutating,
|
|
@@ -784,12 +804,16 @@ function KeepProviderContent({
|
|
|
784
804
|
var IDLE_SYNC_STATE = Object.freeze({
|
|
785
805
|
status: "idle",
|
|
786
806
|
pendingCount: 0,
|
|
787
|
-
conflictIds: []
|
|
807
|
+
conflictIds: [],
|
|
808
|
+
conflicts: []
|
|
788
809
|
});
|
|
789
810
|
var EMPTY_UNDO_STATE = Object.freeze({ canUndo: false, ids: [] });
|
|
790
811
|
function isSyncCapableStorage(storage) {
|
|
791
812
|
return "getSyncState" in storage && typeof storage.getSyncState === "function" && "subscribeSync" in storage && typeof storage.subscribeSync === "function" && "flushSync" in storage && typeof storage.flushSync === "function";
|
|
792
813
|
}
|
|
814
|
+
function isScopeAwareStorage(storage) {
|
|
815
|
+
return "subscribeScope" in storage && typeof storage.subscribeScope === "function";
|
|
816
|
+
}
|
|
793
817
|
async function parseKeepMetaItem(item, schema) {
|
|
794
818
|
return { ...item, meta: await parseKeepMeta(schema, item.meta) };
|
|
795
819
|
}
|