@signaldb/localstorage 2.0.0-beta.21 → 2.0.0-beta.22

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/dist/index.mjs CHANGED
@@ -87,7 +87,10 @@ function r(r, i) {
87
87
  }
88
88
  }, C = (e) => {
89
89
  let t = d(), n = new Map(t.map((e) => [e.id, e])), r = /* @__PURE__ */ new Map();
90
- for (let t of e) x(r, n.get(t.id), t), n.set(t.id, t);
90
+ for (let t of e) {
91
+ let e = n.get(t.id);
92
+ x(r, e, t), n.set(t.id, t);
93
+ }
91
94
  f([...n.values()]), y(r);
92
95
  };
93
96
  return e({
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createStorageAdapter, get, serializeValue } from '@signaldb/core'\n\n/**\n * Creates a storage adapter for managing a SignalDB collection using localStorage.\n * @param name - A unique name for the collection, used as part of the localStorage key.\n * @param options - Optional configuration for the adapter.\n * @param options.databaseName - An optional name for the database to namespace the storage (default: 'signaldb').\n * @param options.serialize - A function to serialize items to a string (default: `JSON.stringify`).\n * @param options.deserialize - A function to deserialize a string into items (default: `JSON.parse`).\n * @returns A SignalDB storage adapter for managing data in localStorage.\n */\nexport default function createLocalStorageAdapter<\n T extends { id: I } & Record<string, any>,\n I,\n>(\n name: string,\n options?: {\n databaseName?: string,\n serialize?: (items: any) => string,\n deserialize?: (itemsString: string) => any,\n },\n\n) {\n const localStorage = globalThis.localStorage\n if (localStorage == null) {\n throw new Error('localStorage is not available in this environment')\n }\n\n const serialize = options?.serialize || (data => JSON.stringify(data))\n const deserialize = options?.deserialize || (input => JSON.parse(input))\n const databaseName = options?.databaseName || 'signaldb'\n const storeName = `${name}`\n\n // We use a single key that namespaces by database and store names\n const storageKey = `${databaseName}-${storeName}`\n\n const indexKeyFor = (field: string) => `${storageKey}-index-${field}`\n const indices: string[] = []\n\n const readFromStorage = (): T[] => {\n const serialized = localStorage.getItem(storageKey)\n if (!serialized) return []\n try {\n const parsed = deserialize(serialized)\n return Array.isArray(parsed) ? (parsed as T[]) : []\n } catch {\n // If parsing fails, treat as empty to avoid corrupting runtime\n return []\n }\n }\n\n const writeToStorage = (items: T[]) => {\n localStorage.setItem(storageKey, serialize(items))\n }\n\n const readIndex = async (field: string) => {\n const serialized = localStorage.getItem(indexKeyFor(field))\n if (!serialized) throw new Error(`Index on field \"${field}\" does not exist`)\n let data: Record<string, I[]>\n try {\n data = deserialize(serialized)\n } catch {\n throw new Error(`Corrupted index on field \"${field}\"`)\n }\n const index = new Map<any, Set<I>>()\n Object.entries(data).forEach(([key, ids]) => {\n if (!index.has(key)) index.set(key, new Set())\n ids.forEach(id => index.get(key)?.add(id))\n })\n return index\n }\n\n const saveIndexMap = (field: string, index: Map<any, Set<I>>) => {\n const safeIndex: Record<string, I[]> = {}\n index.forEach((ids, key) => {\n safeIndex[String(serializeValue(key))] = [...ids]\n })\n localStorage.setItem(indexKeyFor(field), serialize(safeIndex))\n }\n\n const ensureIndex = async (\n field: string,\n items: T[] = readFromStorage(),\n ) => {\n const index = new Map<any, Set<I>>()\n items.forEach((item) => {\n const fieldValue = get(item, field)\n if (fieldValue == null) return\n if (!index.has(fieldValue)) index.set(fieldValue, new Set())\n index.get(fieldValue)?.add(item.id)\n })\n saveIndexMap(field, index)\n }\n\n // --- Delta indexing helpers ---\n const safeKeyFor = (value: any) => String(serializeValue(value))\n\n const loadIndexMap = (field: string): Map<string, Set<I>> | undefined => {\n const serialized = localStorage.getItem(indexKeyFor(field))\n if (!serialized) return undefined\n let data: Record<string, I[]>\n try {\n data = deserialize(serialized)\n } catch {\n throw new Error(`Corrupted index on field \"${field}\"`)\n }\n const index = new Map<string, Set<I>>()\n Object.entries(data).forEach(([key, ids]) => {\n index.set(key, new Set(ids))\n })\n return index\n }\n\n type IndexDelta = {\n adds: Map<string, Set<I>>,\n removes: Map<string, Set<I>>,\n }\n\n const addToDelta = (\n deltas: Map<string, IndexDelta>,\n field: string,\n kind: 'add' | 'remove',\n key: string,\n id: I,\n ) => {\n if (!deltas.has(field)) {\n deltas.set(field, { adds: new Map(), removes: new Map() })\n }\n const delta = deltas.get(field)\n if (!delta) return\n const target = kind === 'add' ? delta.adds : delta.removes\n if (!target.has(key)) target.set(key, new Set<I>())\n target.get(key)?.add(id)\n }\n\n const applyIndexDeltas = (deltas: Map<string, IndexDelta>) => {\n // Update only the indices that have changes\n deltas.forEach((delta, field) => {\n const index = loadIndexMap(field)\n // If the index doesn't exist, skip (we only maintain indices that were created)\n if (!index) return\n\n // Apply removals\n delta.removes.forEach((ids, key) => {\n const set = index.get(key)\n if (!set) return\n ids.forEach(id => set.delete(id))\n if (set.size === 0) index.delete(key)\n })\n\n // Apply additions\n delta.adds.forEach((ids, key) => {\n let set = index.get(key)\n if (!set) {\n set = new Set<I>()\n index.set(key, set)\n }\n ids.forEach(id => set.add(id))\n })\n\n saveIndexMap(field, index)\n })\n }\n\n const addDeltaForChange = (\n deltas: Map<string, IndexDelta>,\n field: string,\n oldValue: any,\n newValue: any,\n id: I,\n ) => {\n const oldKey = oldValue == null ? undefined : safeKeyFor(oldValue)\n const newKey = newValue == null ? undefined : safeKeyFor(newValue)\n if (oldKey === newKey) return\n if (oldKey != null) addToDelta(deltas, field, 'remove', oldKey, id)\n if (newKey != null) addToDelta(deltas, field, 'add', newKey, id)\n }\n\n const accumulateUpsertDelta = (\n deltas: Map<string, IndexDelta>,\n existing: T | undefined,\n next: T,\n ) => {\n if (existing) {\n for (const field of indices) {\n addDeltaForChange(deltas, field, get(existing, field), get(next, field), next.id)\n }\n } else {\n for (const field of indices) {\n const value = get(next, field)\n if (value == null) continue\n addToDelta(deltas, field, 'add', safeKeyFor(value), next.id)\n }\n }\n }\n\n const accumulateRemoveDelta = (\n deltas: Map<string, IndexDelta>,\n existing: T,\n ) => {\n for (const field of indices) {\n const value = get(existing, field)\n if (value == null) continue\n addToDelta(deltas, field, 'remove', safeKeyFor(value), existing.id)\n }\n }\n\n const upsertItems = (itemsToUpsert: T[]) => {\n const items = readFromStorage()\n const byId = new Map<I, T>(items.map(item => [item.id, item]))\n\n const deltas = new Map<string, IndexDelta>()\n\n for (const item of itemsToUpsert) {\n const existing = byId.get(item.id)\n accumulateUpsertDelta(deltas, existing, item)\n byId.set(item.id, item)\n }\n\n writeToStorage([...byId.values()])\n applyIndexDeltas(deltas)\n }\n\n return createStorageAdapter<T, I>({\n // lifecycle methods\n setup: async () => {\n // For localStorage, there is no database to open; we just ensure the key exists\n if (localStorage.getItem(storageKey) == null) {\n writeToStorage([])\n }\n // Hydrate known index fields from existing keys so that we can keep them updated across sessions\n const prefix = `${storageKey}-index-`\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k && k.startsWith(prefix)) {\n const field = k.slice(prefix.length)\n if (!indices.includes(field)) indices.push(field)\n }\n }\n },\n teardown: async () => {\n // no-op\n },\n\n // data retrieval methods\n readAll: async () => {\n return readFromStorage()\n },\n readIds: async (ids) => {\n const items = readFromStorage()\n const idSet = new Set<I>(ids)\n return items.filter(item => idSet.has(item.id))\n },\n\n // index methods\n createIndex: async (field) => {\n if (field === 'id') throw new Error('Cannot create index on id field')\n if (!indices.includes(field)) indices.push(field)\n await ensureIndex(field)\n },\n dropIndex: async (field) => {\n if (indices.includes(field)) {\n const i = indices.indexOf(field)\n indices.splice(i, 1)\n }\n const key = indexKeyFor(field)\n if (localStorage.getItem(key) == null) {\n throw new Error(`Index on field \"${field}\" does not exist`)\n }\n localStorage.removeItem(key)\n },\n readIndex,\n\n // data manipulation methods\n insert: async (newItems) => {\n upsertItems(newItems)\n },\n replace: async (itemsToReplace) => {\n upsertItems(itemsToReplace)\n },\n remove: async (itemsToRemove) => {\n const items = readFromStorage()\n const byId = new Map<I, T>(items.map(item => [item.id, item]))\n\n const deltas = new Map<string, IndexDelta>()\n const removeSet = new Set<I>(itemsToRemove.map(item => item.id))\n\n removeSet.forEach((id) => {\n const existing = byId.get(id)\n if (!existing) return\n accumulateRemoveDelta(deltas, existing)\n byId.delete(id)\n })\n\n writeToStorage([...byId.values()])\n applyIndexDeltas(deltas)\n },\n removeAll: async () => {\n writeToStorage([])\n // remove all index keys for this store\n const prefix = `${storageKey}-index-`\n const keysToRemove: string[] = []\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k && k.startsWith(prefix)) keysToRemove.push(k)\n }\n keysToRemove.forEach(k => localStorage.removeItem(k))\n indices.splice(0)\n },\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createStorageAdapter, get, serializeValue } from '@signaldb/core'\n\n/**\n * Creates a storage adapter for managing a SignalDB collection using localStorage.\n * @param name - A unique name for the collection, used as part of the localStorage key.\n * @param options - Optional configuration for the adapter.\n * @param options.databaseName - An optional name for the database to namespace the storage (default: 'signaldb').\n * @param options.serialize - A function to serialize items to a string (default: `JSON.stringify`).\n * @param options.deserialize - A function to deserialize a string into items (default: `JSON.parse`).\n * @returns A SignalDB storage adapter for managing data in localStorage.\n */\nexport default function createLocalStorageAdapter<\n T extends { id: I } & Record<string, any>,\n I,\n>(\n name: string,\n options?: {\n databaseName?: string,\n serialize?: (items: any) => string,\n deserialize?: (itemsString: string) => any,\n },\n\n) {\n const localStorage = globalThis.localStorage\n if (localStorage == null) {\n throw new Error('localStorage is not available in this environment')\n }\n\n const serialize = options?.serialize || (data => JSON.stringify(data))\n const deserialize = options?.deserialize || (input => JSON.parse(input))\n const databaseName = options?.databaseName || 'signaldb'\n const storeName = `${name}`\n\n // We use a single key that namespaces by database and store names\n const storageKey = `${databaseName}-${storeName}`\n\n const indexKeyFor = (field: string) => `${storageKey}-index-${field}`\n const indices: string[] = []\n\n const readFromStorage = (): T[] => {\n const serialized = localStorage.getItem(storageKey)\n if (!serialized) return []\n try {\n const parsed = deserialize(serialized)\n return Array.isArray(parsed) ? (parsed as T[]) : []\n } catch {\n // If parsing fails, treat as empty to avoid corrupting runtime\n return []\n }\n }\n\n const writeToStorage = (items: T[]) => {\n localStorage.setItem(storageKey, serialize(items))\n }\n\n const readIndex = async (field: string) => {\n const serialized = localStorage.getItem(indexKeyFor(field))\n if (!serialized) throw new Error(`Index on field \"${field}\" does not exist`)\n let data: Record<string, I[]>\n try {\n data = deserialize(serialized)\n } catch {\n throw new Error(`Corrupted index on field \"${field}\"`)\n }\n const index = new Map<any, Set<I>>()\n Object.entries(data).forEach(([key, ids]) => {\n if (!index.has(key)) index.set(key, new Set())\n ids.forEach(id => index.get(key)?.add(id))\n })\n return index\n }\n\n const saveIndexMap = (field: string, index: Map<any, Set<I>>) => {\n const safeIndex: Record<string, I[]> = {}\n index.forEach((ids, key) => {\n safeIndex[String(serializeValue(key))] = [...ids]\n })\n localStorage.setItem(indexKeyFor(field), serialize(safeIndex))\n }\n\n const ensureIndex = async (\n field: string,\n items: T[] = readFromStorage(),\n ) => {\n const index = new Map<any, Set<I>>()\n items.forEach((item) => {\n const fieldValue = get(item, field)\n if (fieldValue == null) return\n if (!index.has(fieldValue)) index.set(fieldValue, new Set())\n index.get(fieldValue)?.add(item.id)\n })\n saveIndexMap(field, index)\n }\n\n // --- Delta indexing helpers ---\n const safeKeyFor = (value: any) => String(serializeValue(value))\n\n const loadIndexMap = (field: string): Map<string, Set<I>> | undefined => {\n const serialized = localStorage.getItem(indexKeyFor(field))\n if (!serialized) return undefined\n let data: Record<string, I[]>\n try {\n data = deserialize(serialized)\n } catch {\n throw new Error(`Corrupted index on field \"${field}\"`)\n }\n const index = new Map<string, Set<I>>()\n Object.entries(data).forEach(([key, ids]) => {\n index.set(key, new Set(ids))\n })\n return index\n }\n\n type IndexDelta = {\n adds: Map<string, Set<I>>,\n removes: Map<string, Set<I>>,\n }\n\n const addToDelta = (\n deltas: Map<string, IndexDelta>,\n field: string,\n kind: 'add' | 'remove',\n key: string,\n id: I,\n ) => {\n if (!deltas.has(field)) {\n deltas.set(field, { adds: new Map(), removes: new Map() })\n }\n const delta = deltas.get(field)\n if (!delta) return\n const target = kind === 'add' ? delta.adds : delta.removes\n if (!target.has(key)) target.set(key, new Set<I>())\n target.get(key)?.add(id)\n }\n\n const applyIndexDeltas = (deltas: Map<string, IndexDelta>) => {\n // Update only the indices that have changes\n deltas.forEach((delta, field) => {\n const index = loadIndexMap(field)\n // If the index doesn't exist, skip (we only maintain indices that were created)\n if (!index) return\n\n // Apply removals\n delta.removes.forEach((ids, key) => {\n const set = index.get(key)\n if (!set) return\n ids.forEach(id => set.delete(id))\n if (set.size === 0) index.delete(key)\n })\n\n // Apply additions\n delta.adds.forEach((ids, key) => {\n let set = index.get(key)\n if (!set) {\n set = new Set<I>()\n index.set(key, set)\n }\n ids.forEach(id => set.add(id))\n })\n\n saveIndexMap(field, index)\n })\n }\n\n const addDeltaForChange = (\n deltas: Map<string, IndexDelta>,\n field: string,\n oldValue: any,\n newValue: any,\n id: I,\n ) => {\n const oldKey = oldValue == null ? undefined : safeKeyFor(oldValue)\n const newKey = newValue == null ? undefined : safeKeyFor(newValue)\n if (oldKey === newKey) return\n if (oldKey != null) addToDelta(deltas, field, 'remove', oldKey, id)\n if (newKey != null) addToDelta(deltas, field, 'add', newKey, id)\n }\n\n const accumulateUpsertDelta = (\n deltas: Map<string, IndexDelta>,\n existing: T | undefined,\n next: T,\n ) => {\n if (existing) {\n for (const field of indices) {\n addDeltaForChange(deltas, field, get(existing, field), get(next, field), next.id)\n }\n } else {\n for (const field of indices) {\n const value = get(next, field)\n if (value == null) continue\n addToDelta(deltas, field, 'add', safeKeyFor(value), next.id)\n }\n }\n }\n\n const accumulateRemoveDelta = (\n deltas: Map<string, IndexDelta>,\n existing: T,\n ) => {\n for (const field of indices) {\n const value = get(existing, field)\n if (value == null) continue\n addToDelta(deltas, field, 'remove', safeKeyFor(value), existing.id)\n }\n }\n\n const upsertItems = (itemsToUpsert: T[]) => {\n const items = readFromStorage()\n const byId = new Map<I, T>(items.map(item => [item.id, item]))\n\n const deltas = new Map<string, IndexDelta>()\n\n for (const item of itemsToUpsert) {\n const existing = byId.get(item.id)\n accumulateUpsertDelta(deltas, existing, item)\n byId.set(item.id, item)\n }\n\n writeToStorage([...byId.values()])\n applyIndexDeltas(deltas)\n }\n\n return createStorageAdapter<T, I>({\n // lifecycle methods\n setup: async () => {\n // For localStorage, there is no database to open; we just ensure the key exists\n if (localStorage.getItem(storageKey) == null) {\n writeToStorage([])\n }\n // Hydrate known index fields from existing keys so that we can keep them updated across sessions\n const prefix = `${storageKey}-index-`\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k && k.startsWith(prefix)) {\n const field = k.slice(prefix.length)\n if (!indices.includes(field)) indices.push(field)\n }\n }\n },\n teardown: async () => {\n // no-op\n },\n\n // data retrieval methods\n readAll: async () => {\n return readFromStorage()\n },\n readIds: async (ids) => {\n const items = readFromStorage()\n const idSet = new Set<I>(ids)\n return items.filter(item => idSet.has(item.id))\n },\n\n // index methods\n createIndex: async (field) => {\n if (field === 'id') throw new Error('Cannot create index on id field')\n if (!indices.includes(field)) indices.push(field)\n await ensureIndex(field)\n },\n dropIndex: async (field) => {\n if (indices.includes(field)) {\n const i = indices.indexOf(field)\n indices.splice(i, 1)\n }\n const key = indexKeyFor(field)\n if (localStorage.getItem(key) == null) {\n throw new Error(`Index on field \"${field}\" does not exist`)\n }\n localStorage.removeItem(key)\n },\n readIndex,\n\n // data manipulation methods\n insert: async (newItems) => {\n upsertItems(newItems)\n },\n replace: async (itemsToReplace) => {\n upsertItems(itemsToReplace)\n },\n remove: async (itemsToRemove) => {\n const items = readFromStorage()\n const byId = new Map<I, T>(items.map(item => [item.id, item]))\n\n const deltas = new Map<string, IndexDelta>()\n const removeSet = new Set<I>(itemsToRemove.map(item => item.id))\n\n removeSet.forEach((id) => {\n const existing = byId.get(id)\n if (!existing) return\n accumulateRemoveDelta(deltas, existing)\n byId.delete(id)\n })\n\n writeToStorage([...byId.values()])\n applyIndexDeltas(deltas)\n },\n removeAll: async () => {\n writeToStorage([])\n // remove all index keys for this store\n const prefix = `${storageKey}-index-`\n const keysToRemove: string[] = []\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k && k.startsWith(prefix)) keysToRemove.push(k)\n }\n keysToRemove.forEach(k => localStorage.removeItem(k))\n indices.splice(0)\n },\n })\n}\n"],"mappings":";;AAWA,SAAwB,EAItB,GACA,GAMA;CACA,IAAM,IAAe,WAAW;CAChC,IAAI,KAAgB,MAClB,MAAU,MAAM,mDAAmD;CAGrE,IAAM,IAAY,GAAS,eAAc,MAAQ,KAAK,UAAU,CAAI,IAC9D,IAAc,GAAS,iBAAgB,MAAS,KAAK,MAAM,CAAK,IAKhE,IAAa,GAJE,GAAS,gBAAgB,WAIX,GAAG,GAHjB,OAKf,KAAe,MAAkB,GAAG,EAAW,SAAS,KACxD,IAAoB,CAAC,GAErB,UAA6B;EACjC,IAAM,IAAa,EAAa,QAAQ,CAAU;EAClD,IAAI,CAAC,GAAY,OAAO,CAAC;EACzB,IAAI;GACF,IAAM,IAAS,EAAY,CAAU;GACrC,OAAO,MAAM,QAAQ,CAAM,IAAK,IAAiB,CAAC;EACpD,QAAQ;GAEN,OAAO,CAAC;EACV;CACF,GAEM,KAAkB,MAAe;EACrC,EAAa,QAAQ,GAAY,EAAU,CAAK,CAAC;CACnD,GAEM,IAAY,OAAO,MAAkB;EACzC,IAAM,IAAa,EAAa,QAAQ,EAAY,CAAK,CAAC;EAC1D,IAAI,CAAC,GAAY,MAAU,MAAM,mBAAmB,EAAM,iBAAiB;EAC3E,IAAI;EACJ,IAAI;GACF,IAAO,EAAY,CAAU;EAC/B,QAAQ;GACN,MAAU,MAAM,6BAA6B,EAAM,EAAE;EACvD;EACA,IAAM,oBAAQ,IAAI,IAAiB;EAKnC,OAJA,OAAO,QAAQ,CAAI,CAAC,CAAC,SAAS,CAAC,GAAK,OAAS;GAE3C,AADK,EAAM,IAAI,CAAG,KAAG,EAAM,IAAI,mBAAK,IAAI,IAAI,CAAC,GAC7C,EAAI,SAAQ,MAAM,EAAM,IAAI,CAAG,CAAC,EAAE,IAAI,CAAE,CAAC;EAC3C,CAAC,GACM;CACT,GAEM,KAAgB,GAAe,MAA4B;EAC/D,IAAM,IAAiC,CAAC;EAIxC,AAHA,EAAM,SAAS,GAAK,MAAQ;GAC1B,EAAU,OAAO,EAAe,CAAG,CAAC,KAAK,CAAC,GAAG,CAAG;EAClD,CAAC,GACD,EAAa,QAAQ,EAAY,CAAK,GAAG,EAAU,CAAS,CAAC;CAC/D,GAEM,IAAc,OAClB,GACA,IAAa,EAAgB,MAC1B;EACH,IAAM,oBAAQ,IAAI,IAAiB;EAOnC,AANA,EAAM,SAAS,MAAS;GACtB,IAAM,IAAa,EAAI,GAAM,CAAK;GAC9B,KAAc,SACb,EAAM,IAAI,CAAU,KAAG,EAAM,IAAI,mBAAY,IAAI,IAAI,CAAC,GAC3D,EAAM,IAAI,CAAU,CAAC,EAAE,IAAI,EAAK,EAAE;EACpC,CAAC,GACD,EAAa,GAAO,CAAK;CAC3B,GAGM,KAAc,MAAe,OAAO,EAAe,CAAK,CAAC,GAEzD,KAAgB,MAAmD;EACvE,IAAM,IAAa,EAAa,QAAQ,EAAY,CAAK,CAAC;EAC1D,IAAI,CAAC,GAAY;EACjB,IAAI;EACJ,IAAI;GACF,IAAO,EAAY,CAAU;EAC/B,QAAQ;GACN,MAAU,MAAM,6BAA6B,EAAM,EAAE;EACvD;EACA,IAAM,oBAAQ,IAAI,IAAoB;EAItC,OAHA,OAAO,QAAQ,CAAI,CAAC,CAAC,SAAS,CAAC,GAAK,OAAS;GAC3C,EAAM,IAAI,GAAK,IAAI,IAAI,CAAG,CAAC;EAC7B,CAAC,GACM;CACT,GAOM,KACJ,GACA,GACA,GACA,GACA,MACG;EACH,AAAK,EAAO,IAAI,CAAK,KACnB,EAAO,IAAI,GAAO;GAAE,sBAAM,IAAI,IAAI;GAAG,yBAAS,IAAI,IAAI;EAAE,CAAC;EAE3D,IAAM,IAAQ,EAAO,IAAI,CAAK;EAC9B,IAAI,CAAC,GAAO;EACZ,IAAM,IAAS,MAAS,QAAQ,EAAM,OAAO,EAAM;EAEnD,AADK,EAAO,IAAI,CAAG,KAAG,EAAO,IAAI,mBAAK,IAAI,IAAO,CAAC,GAClD,EAAO,IAAI,CAAG,CAAC,EAAE,IAAI,CAAE;CACzB,GAEM,KAAoB,MAAoC;EAE5D,EAAO,SAAS,GAAO,MAAU;GAC/B,IAAM,IAAQ,EAAa,CAAK;GAE3B,MAGL,EAAM,QAAQ,SAAS,GAAK,MAAQ;IAClC,IAAM,IAAM,EAAM,IAAI,CAAG;IACpB,MACL,EAAI,SAAQ,MAAM,EAAI,OAAO,CAAE,CAAC,GAC5B,EAAI,SAAS,KAAG,EAAM,OAAO,CAAG;GACtC,CAAC,GAGD,EAAM,KAAK,SAAS,GAAK,MAAQ;IAC/B,IAAI,IAAM,EAAM,IAAI,CAAG;IAKvB,AAJK,MACH,oBAAM,IAAI,IAAO,GACjB,EAAM,IAAI,GAAK,CAAG,IAEpB,EAAI,SAAQ,MAAM,EAAI,IAAI,CAAE,CAAC;GAC/B,CAAC,GAED,EAAa,GAAO,CAAK;EAC3B,CAAC;CACH,GAEM,KACJ,GACA,GACA,GACA,GACA,MACG;EACH,IAAM,IAAS,KAAY,OAAO,KAAA,IAAY,EAAW,CAAQ,GAC3D,IAAS,KAAY,OAAO,KAAA,IAAY,EAAW,CAAQ;EAC7D,MAAW,MACX,KAAU,QAAM,EAAW,GAAQ,GAAO,UAAU,GAAQ,CAAE,GAC9D,KAAU,QAAM,EAAW,GAAQ,GAAO,OAAO,GAAQ,CAAE;CACjE,GAEM,KACJ,GACA,GACA,MACG;EACH,IAAI,GACF,KAAK,IAAM,KAAS,GAClB,EAAkB,GAAQ,GAAO,EAAI,GAAU,CAAK,GAAG,EAAI,GAAM,CAAK,GAAG,EAAK,EAAE;OAGlF,KAAK,IAAM,KAAS,GAAS;GAC3B,IAAM,IAAQ,EAAI,GAAM,CAAK;GACzB,KAAS,QACb,EAAW,GAAQ,GAAO,OAAO,EAAW,CAAK,GAAG,EAAK,EAAE;EAC7D;CAEJ,GAEM,KACJ,GACA,MACG;EACH,KAAK,IAAM,KAAS,GAAS;GAC3B,IAAM,IAAQ,EAAI,GAAU,CAAK;GAC7B,KAAS,QACb,EAAW,GAAQ,GAAO,UAAU,EAAW,CAAK,GAAG,EAAS,EAAE;EACpE;CACF,GAEM,KAAe,MAAuB;EAC1C,IAAM,IAAQ,EAAgB,GACxB,IAAO,IAAI,IAAU,EAAM,KAAI,MAAQ,CAAC,EAAK,IAAI,CAAI,CAAC,CAAC,GAEvD,oBAAS,IAAI,IAAwB;EAE3C,KAAK,IAAM,KAAQ,GAAe;GAChC,IAAM,IAAW,EAAK,IAAI,EAAK,EAAE;GAEjC,AADA,EAAsB,GAAQ,GAAU,CAAI,GAC5C,EAAK,IAAI,EAAK,IAAI,CAAI;EACxB;EAGA,AADA,EAAe,CAAC,GAAG,EAAK,OAAO,CAAC,CAAC,GACjC,EAAiB,CAAM;CACzB;CAEA,OAAO,EAA2B;EAEhC,OAAO,YAAY;GAEjB,AAAI,EAAa,QAAQ,CAAU,KACjC,EAAe,CAAC,CAAC;GAGnB,IAAM,IAAS,GAAG,EAAW;GAC7B,KAAK,IAAI,IAAI,GAAG,IAAI,EAAa,QAAQ,KAAK;IAC5C,IAAM,IAAI,EAAa,IAAI,CAAC;IAC5B,IAAI,KAAK,EAAE,WAAW,CAAM,GAAG;KAC7B,IAAM,IAAQ,EAAE,MAAM,EAAO,MAAM;KACnC,AAAK,EAAQ,SAAS,CAAK,KAAG,EAAQ,KAAK,CAAK;IAClD;GACF;EACF;EACA,UAAU,YAAY,CAEtB;EAGA,SAAS,YACA,EAAgB;EAEzB,SAAS,OAAO,MAAQ;GACtB,IAAM,IAAQ,EAAgB,GACxB,IAAQ,IAAI,IAAO,CAAG;GAC5B,OAAO,EAAM,QAAO,MAAQ,EAAM,IAAI,EAAK,EAAE,CAAC;EAChD;EAGA,aAAa,OAAO,MAAU;GAC5B,IAAI,MAAU,MAAM,MAAU,MAAM,iCAAiC;GAErE,AADK,EAAQ,SAAS,CAAK,KAAG,EAAQ,KAAK,CAAK,GAChD,MAAM,EAAY,CAAK;EACzB;EACA,WAAW,OAAO,MAAU;GAC1B,IAAI,EAAQ,SAAS,CAAK,GAAG;IAC3B,IAAM,IAAI,EAAQ,QAAQ,CAAK;IAC/B,EAAQ,OAAO,GAAG,CAAC;GACrB;GACA,IAAM,IAAM,EAAY,CAAK;GAC7B,IAAI,EAAa,QAAQ,CAAG,KAAK,MAC/B,MAAU,MAAM,mBAAmB,EAAM,iBAAiB;GAE5D,EAAa,WAAW,CAAG;EAC7B;EACA;EAGA,QAAQ,OAAO,MAAa;GAC1B,EAAY,CAAQ;EACtB;EACA,SAAS,OAAO,MAAmB;GACjC,EAAY,CAAc;EAC5B;EACA,QAAQ,OAAO,MAAkB;GAC/B,IAAM,IAAQ,EAAgB,GACxB,IAAO,IAAI,IAAU,EAAM,KAAI,MAAQ,CAAC,EAAK,IAAI,CAAI,CAAC,CAAC,GAEvD,oBAAS,IAAI,IAAwB;GAW3C,AARA,IAFsB,IAAO,EAAc,KAAI,MAAQ,EAAK,EAAE,CAE9D,CAAA,CAAU,SAAS,MAAO;IACxB,IAAM,IAAW,EAAK,IAAI,CAAE;IACvB,MACL,EAAsB,GAAQ,CAAQ,GACtC,EAAK,OAAO,CAAE;GAChB,CAAC,GAED,EAAe,CAAC,GAAG,EAAK,OAAO,CAAC,CAAC,GACjC,EAAiB,CAAM;EACzB;EACA,WAAW,YAAY;GACrB,EAAe,CAAC,CAAC;GAEjB,IAAM,IAAS,GAAG,EAAW,UACvB,IAAyB,CAAC;GAChC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAa,QAAQ,KAAK;IAC5C,IAAM,IAAI,EAAa,IAAI,CAAC;IAC5B,AAAI,KAAK,EAAE,WAAW,CAAM,KAAG,EAAa,KAAK,CAAC;GACpD;GAEA,AADA,EAAa,SAAQ,MAAK,EAAa,WAAW,CAAC,CAAC,GACpD,EAAQ,OAAO,CAAC;EAClB;CACF,CAAC;AACH"}
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?module.exports=t(require("@signaldb/core")):typeof define==`function`&&define.amd?define([`@signaldb/core`],t):(e=typeof globalThis<`u`?globalThis:e||self,e.SignalDB=t(e._signaldb_core))})(this,function(e){function t(t,n){let r=globalThis.localStorage;if(r==null)throw Error(`localStorage is not available in this environment`);let i=n?.serialize||(e=>JSON.stringify(e)),a=n?.deserialize||(e=>JSON.parse(e)),o=`${n?.databaseName||`signaldb`}-${`${t}`}`,s=e=>`${o}-index-${e}`,c=[],l=()=>{let e=r.getItem(o);if(!e)return[];try{let t=a(e);return Array.isArray(t)?t:[]}catch{return[]}},u=e=>{r.setItem(o,i(e))},d=async e=>{let t=r.getItem(s(e));if(!t)throw Error(`Index on field "${e}" does not exist`);let n;try{n=a(t)}catch{throw Error(`Corrupted index on field "${e}"`)}let i=new Map;return Object.entries(n).forEach(([e,t])=>{i.has(e)||i.set(e,new Set),t.forEach(t=>i.get(e)?.add(t))}),i},f=(t,n)=>{let a={};n.forEach((t,n)=>{a[String((0,e.serializeValue)(n))]=[...t]}),r.setItem(s(t),i(a))},p=async(t,n=l())=>{let r=new Map;n.forEach(n=>{let i=(0,e.get)(n,t);i!=null&&(r.has(i)||r.set(i,new Set),r.get(i)?.add(n.id))}),f(t,r)},m=t=>String((0,e.serializeValue)(t)),h=e=>{let t=r.getItem(s(e));if(!t)return;let n;try{n=a(t)}catch{throw Error(`Corrupted index on field "${e}"`)}let i=new Map;return Object.entries(n).forEach(([e,t])=>{i.set(e,new Set(t))}),i},g=(e,t,n,r,i)=>{e.has(t)||e.set(t,{adds:new Map,removes:new Map});let a=e.get(t);if(!a)return;let o=n===`add`?a.adds:a.removes;o.has(r)||o.set(r,new Set),o.get(r)?.add(i)},_=e=>{e.forEach((e,t)=>{let n=h(t);n&&(e.removes.forEach((e,t)=>{let r=n.get(t);r&&(e.forEach(e=>r.delete(e)),r.size===0&&n.delete(t))}),e.adds.forEach((e,t)=>{let r=n.get(t);r||(r=new Set,n.set(t,r)),e.forEach(e=>r.add(e))}),f(t,n))})},v=(e,t,n,r,i)=>{let a=n==null?void 0:m(n),o=r==null?void 0:m(r);a!==o&&(a!=null&&g(e,t,`remove`,a,i),o!=null&&g(e,t,`add`,o,i))},y=(t,n,r)=>{if(n)for(let i of c)v(t,i,(0,e.get)(n,i),(0,e.get)(r,i),r.id);else for(let n of c){let i=(0,e.get)(r,n);i!=null&&g(t,n,`add`,m(i),r.id)}},b=(t,n)=>{for(let r of c){let i=(0,e.get)(n,r);i!=null&&g(t,r,`remove`,m(i),n.id)}},x=e=>{let t=l(),n=new Map(t.map(e=>[e.id,e])),r=new Map;for(let t of e)y(r,n.get(t.id),t),n.set(t.id,t);u([...n.values()]),_(r)};return(0,e.createStorageAdapter)({setup:async()=>{r.getItem(o)??u([]);let e=`${o}-index-`;for(let t=0;t<r.length;t++){let n=r.key(t);if(n&&n.startsWith(e)){let t=n.slice(e.length);c.includes(t)||c.push(t)}}},teardown:async()=>{},readAll:async()=>l(),readIds:async e=>{let t=l(),n=new Set(e);return t.filter(e=>n.has(e.id))},createIndex:async e=>{if(e===`id`)throw Error(`Cannot create index on id field`);c.includes(e)||c.push(e),await p(e)},dropIndex:async e=>{if(c.includes(e)){let t=c.indexOf(e);c.splice(t,1)}let t=s(e);if(r.getItem(t)==null)throw Error(`Index on field "${e}" does not exist`);r.removeItem(t)},readIndex:d,insert:async e=>{x(e)},replace:async e=>{x(e)},remove:async e=>{let t=l(),n=new Map(t.map(e=>[e.id,e])),r=new Map;new Set(e.map(e=>e.id)).forEach(e=>{let t=n.get(e);t&&(b(r,t),n.delete(e))}),u([...n.values()]),_(r)},removeAll:async()=>{u([]);let e=`${o}-index-`,t=[];for(let n=0;n<r.length;n++){let i=r.key(n);i&&i.startsWith(e)&&t.push(i)}t.forEach(e=>r.removeItem(e)),c.splice(0)}})}return t});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?module.exports=t(require("@signaldb/core")):typeof define==`function`&&define.amd?define([`@signaldb/core`],t):(e=typeof globalThis<`u`?globalThis:e||self,e.SignalDB=t(e._signaldb_core))})(this,function(e){function t(t,n){let r=globalThis.localStorage;if(r==null)throw Error(`localStorage is not available in this environment`);let i=n?.serialize||(e=>JSON.stringify(e)),a=n?.deserialize||(e=>JSON.parse(e)),o=`${n?.databaseName||`signaldb`}-${`${t}`}`,s=e=>`${o}-index-${e}`,c=[],l=()=>{let e=r.getItem(o);if(!e)return[];try{let t=a(e);return Array.isArray(t)?t:[]}catch{return[]}},u=e=>{r.setItem(o,i(e))},d=async e=>{let t=r.getItem(s(e));if(!t)throw Error(`Index on field "${e}" does not exist`);let n;try{n=a(t)}catch{throw Error(`Corrupted index on field "${e}"`)}let i=new Map;return Object.entries(n).forEach(([e,t])=>{i.has(e)||i.set(e,new Set),t.forEach(t=>i.get(e)?.add(t))}),i},f=(t,n)=>{let a={};n.forEach((t,n)=>{a[String((0,e.serializeValue)(n))]=[...t]}),r.setItem(s(t),i(a))},p=async(t,n=l())=>{let r=new Map;n.forEach(n=>{let i=(0,e.get)(n,t);i!=null&&(r.has(i)||r.set(i,new Set),r.get(i)?.add(n.id))}),f(t,r)},m=t=>String((0,e.serializeValue)(t)),h=e=>{let t=r.getItem(s(e));if(!t)return;let n;try{n=a(t)}catch{throw Error(`Corrupted index on field "${e}"`)}let i=new Map;return Object.entries(n).forEach(([e,t])=>{i.set(e,new Set(t))}),i},g=(e,t,n,r,i)=>{e.has(t)||e.set(t,{adds:new Map,removes:new Map});let a=e.get(t);if(!a)return;let o=n===`add`?a.adds:a.removes;o.has(r)||o.set(r,new Set),o.get(r)?.add(i)},_=e=>{e.forEach((e,t)=>{let n=h(t);n&&(e.removes.forEach((e,t)=>{let r=n.get(t);r&&(e.forEach(e=>r.delete(e)),r.size===0&&n.delete(t))}),e.adds.forEach((e,t)=>{let r=n.get(t);r||(r=new Set,n.set(t,r)),e.forEach(e=>r.add(e))}),f(t,n))})},v=(e,t,n,r,i)=>{let a=n==null?void 0:m(n),o=r==null?void 0:m(r);a!==o&&(a!=null&&g(e,t,`remove`,a,i),o!=null&&g(e,t,`add`,o,i))},y=(t,n,r)=>{if(n)for(let i of c)v(t,i,(0,e.get)(n,i),(0,e.get)(r,i),r.id);else for(let n of c){let i=(0,e.get)(r,n);i!=null&&g(t,n,`add`,m(i),r.id)}},b=(t,n)=>{for(let r of c){let i=(0,e.get)(n,r);i!=null&&g(t,r,`remove`,m(i),n.id)}},x=e=>{let t=l(),n=new Map(t.map(e=>[e.id,e])),r=new Map;for(let t of e){let e=n.get(t.id);y(r,e,t),n.set(t.id,t)}u([...n.values()]),_(r)};return(0,e.createStorageAdapter)({setup:async()=>{r.getItem(o)??u([]);let e=`${o}-index-`;for(let t=0;t<r.length;t++){let n=r.key(t);if(n&&n.startsWith(e)){let t=n.slice(e.length);c.includes(t)||c.push(t)}}},teardown:async()=>{},readAll:async()=>l(),readIds:async e=>{let t=l(),n=new Set(e);return t.filter(e=>n.has(e.id))},createIndex:async e=>{if(e===`id`)throw Error(`Cannot create index on id field`);c.includes(e)||c.push(e),await p(e)},dropIndex:async e=>{if(c.includes(e)){let t=c.indexOf(e);c.splice(t,1)}let t=s(e);if(r.getItem(t)==null)throw Error(`Index on field "${e}" does not exist`);r.removeItem(t)},readIndex:d,insert:async e=>{x(e)},replace:async e=>{x(e)},remove:async e=>{let t=l(),n=new Map(t.map(e=>[e.id,e])),r=new Map;new Set(e.map(e=>e.id)).forEach(e=>{let t=n.get(e);t&&(b(r,t),n.delete(e))}),u([...n.values()]),_(r)},removeAll:async()=>{u([]);let e=`${o}-index-`,t=[];for(let n=0;n<r.length;n++){let i=r.key(n);i&&i.startsWith(e)&&t.push(i)}t.forEach(e=>r.removeItem(e)),c.splice(0)}})}return t});
2
2
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createStorageAdapter, get, serializeValue } from '@signaldb/core'\n\n/**\n * Creates a storage adapter for managing a SignalDB collection using localStorage.\n * @param name - A unique name for the collection, used as part of the localStorage key.\n * @param options - Optional configuration for the adapter.\n * @param options.databaseName - An optional name for the database to namespace the storage (default: 'signaldb').\n * @param options.serialize - A function to serialize items to a string (default: `JSON.stringify`).\n * @param options.deserialize - A function to deserialize a string into items (default: `JSON.parse`).\n * @returns A SignalDB storage adapter for managing data in localStorage.\n */\nexport default function createLocalStorageAdapter<\n T extends { id: I } & Record<string, any>,\n I,\n>(\n name: string,\n options?: {\n databaseName?: string,\n serialize?: (items: any) => string,\n deserialize?: (itemsString: string) => any,\n },\n\n) {\n const localStorage = globalThis.localStorage\n if (localStorage == null) {\n throw new Error('localStorage is not available in this environment')\n }\n\n const serialize = options?.serialize || (data => JSON.stringify(data))\n const deserialize = options?.deserialize || (input => JSON.parse(input))\n const databaseName = options?.databaseName || 'signaldb'\n const storeName = `${name}`\n\n // We use a single key that namespaces by database and store names\n const storageKey = `${databaseName}-${storeName}`\n\n const indexKeyFor = (field: string) => `${storageKey}-index-${field}`\n const indices: string[] = []\n\n const readFromStorage = (): T[] => {\n const serialized = localStorage.getItem(storageKey)\n if (!serialized) return []\n try {\n const parsed = deserialize(serialized)\n return Array.isArray(parsed) ? (parsed as T[]) : []\n } catch {\n // If parsing fails, treat as empty to avoid corrupting runtime\n return []\n }\n }\n\n const writeToStorage = (items: T[]) => {\n localStorage.setItem(storageKey, serialize(items))\n }\n\n const readIndex = async (field: string) => {\n const serialized = localStorage.getItem(indexKeyFor(field))\n if (!serialized) throw new Error(`Index on field \"${field}\" does not exist`)\n let data: Record<string, I[]>\n try {\n data = deserialize(serialized)\n } catch {\n throw new Error(`Corrupted index on field \"${field}\"`)\n }\n const index = new Map<any, Set<I>>()\n Object.entries(data).forEach(([key, ids]) => {\n if (!index.has(key)) index.set(key, new Set())\n ids.forEach(id => index.get(key)?.add(id))\n })\n return index\n }\n\n const saveIndexMap = (field: string, index: Map<any, Set<I>>) => {\n const safeIndex: Record<string, I[]> = {}\n index.forEach((ids, key) => {\n safeIndex[String(serializeValue(key))] = [...ids]\n })\n localStorage.setItem(indexKeyFor(field), serialize(safeIndex))\n }\n\n const ensureIndex = async (\n field: string,\n items: T[] = readFromStorage(),\n ) => {\n const index = new Map<any, Set<I>>()\n items.forEach((item) => {\n const fieldValue = get(item, field)\n if (fieldValue == null) return\n if (!index.has(fieldValue)) index.set(fieldValue, new Set())\n index.get(fieldValue)?.add(item.id)\n })\n saveIndexMap(field, index)\n }\n\n // --- Delta indexing helpers ---\n const safeKeyFor = (value: any) => String(serializeValue(value))\n\n const loadIndexMap = (field: string): Map<string, Set<I>> | undefined => {\n const serialized = localStorage.getItem(indexKeyFor(field))\n if (!serialized) return undefined\n let data: Record<string, I[]>\n try {\n data = deserialize(serialized)\n } catch {\n throw new Error(`Corrupted index on field \"${field}\"`)\n }\n const index = new Map<string, Set<I>>()\n Object.entries(data).forEach(([key, ids]) => {\n index.set(key, new Set(ids))\n })\n return index\n }\n\n type IndexDelta = {\n adds: Map<string, Set<I>>,\n removes: Map<string, Set<I>>,\n }\n\n const addToDelta = (\n deltas: Map<string, IndexDelta>,\n field: string,\n kind: 'add' | 'remove',\n key: string,\n id: I,\n ) => {\n if (!deltas.has(field)) {\n deltas.set(field, { adds: new Map(), removes: new Map() })\n }\n const delta = deltas.get(field)\n if (!delta) return\n const target = kind === 'add' ? delta.adds : delta.removes\n if (!target.has(key)) target.set(key, new Set<I>())\n target.get(key)?.add(id)\n }\n\n const applyIndexDeltas = (deltas: Map<string, IndexDelta>) => {\n // Update only the indices that have changes\n deltas.forEach((delta, field) => {\n const index = loadIndexMap(field)\n // If the index doesn't exist, skip (we only maintain indices that were created)\n if (!index) return\n\n // Apply removals\n delta.removes.forEach((ids, key) => {\n const set = index.get(key)\n if (!set) return\n ids.forEach(id => set.delete(id))\n if (set.size === 0) index.delete(key)\n })\n\n // Apply additions\n delta.adds.forEach((ids, key) => {\n let set = index.get(key)\n if (!set) {\n set = new Set<I>()\n index.set(key, set)\n }\n ids.forEach(id => set.add(id))\n })\n\n saveIndexMap(field, index)\n })\n }\n\n const addDeltaForChange = (\n deltas: Map<string, IndexDelta>,\n field: string,\n oldValue: any,\n newValue: any,\n id: I,\n ) => {\n const oldKey = oldValue == null ? undefined : safeKeyFor(oldValue)\n const newKey = newValue == null ? undefined : safeKeyFor(newValue)\n if (oldKey === newKey) return\n if (oldKey != null) addToDelta(deltas, field, 'remove', oldKey, id)\n if (newKey != null) addToDelta(deltas, field, 'add', newKey, id)\n }\n\n const accumulateUpsertDelta = (\n deltas: Map<string, IndexDelta>,\n existing: T | undefined,\n next: T,\n ) => {\n if (existing) {\n for (const field of indices) {\n addDeltaForChange(deltas, field, get(existing, field), get(next, field), next.id)\n }\n } else {\n for (const field of indices) {\n const value = get(next, field)\n if (value == null) continue\n addToDelta(deltas, field, 'add', safeKeyFor(value), next.id)\n }\n }\n }\n\n const accumulateRemoveDelta = (\n deltas: Map<string, IndexDelta>,\n existing: T,\n ) => {\n for (const field of indices) {\n const value = get(existing, field)\n if (value == null) continue\n addToDelta(deltas, field, 'remove', safeKeyFor(value), existing.id)\n }\n }\n\n const upsertItems = (itemsToUpsert: T[]) => {\n const items = readFromStorage()\n const byId = new Map<I, T>(items.map(item => [item.id, item]))\n\n const deltas = new Map<string, IndexDelta>()\n\n for (const item of itemsToUpsert) {\n const existing = byId.get(item.id)\n accumulateUpsertDelta(deltas, existing, item)\n byId.set(item.id, item)\n }\n\n writeToStorage([...byId.values()])\n applyIndexDeltas(deltas)\n }\n\n return createStorageAdapter<T, I>({\n // lifecycle methods\n setup: async () => {\n // For localStorage, there is no database to open; we just ensure the key exists\n if (localStorage.getItem(storageKey) == null) {\n writeToStorage([])\n }\n // Hydrate known index fields from existing keys so that we can keep them updated across sessions\n const prefix = `${storageKey}-index-`\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k && k.startsWith(prefix)) {\n const field = k.slice(prefix.length)\n if (!indices.includes(field)) indices.push(field)\n }\n }\n },\n teardown: async () => {\n // no-op\n },\n\n // data retrieval methods\n readAll: async () => {\n return readFromStorage()\n },\n readIds: async (ids) => {\n const items = readFromStorage()\n const idSet = new Set<I>(ids)\n return items.filter(item => idSet.has(item.id))\n },\n\n // index methods\n createIndex: async (field) => {\n if (field === 'id') throw new Error('Cannot create index on id field')\n if (!indices.includes(field)) indices.push(field)\n await ensureIndex(field)\n },\n dropIndex: async (field) => {\n if (indices.includes(field)) {\n const i = indices.indexOf(field)\n indices.splice(i, 1)\n }\n const key = indexKeyFor(field)\n if (localStorage.getItem(key) == null) {\n throw new Error(`Index on field \"${field}\" does not exist`)\n }\n localStorage.removeItem(key)\n },\n readIndex,\n\n // data manipulation methods\n insert: async (newItems) => {\n upsertItems(newItems)\n },\n replace: async (itemsToReplace) => {\n upsertItems(itemsToReplace)\n },\n remove: async (itemsToRemove) => {\n const items = readFromStorage()\n const byId = new Map<I, T>(items.map(item => [item.id, item]))\n\n const deltas = new Map<string, IndexDelta>()\n const removeSet = new Set<I>(itemsToRemove.map(item => item.id))\n\n removeSet.forEach((id) => {\n const existing = byId.get(id)\n if (!existing) return\n accumulateRemoveDelta(deltas, existing)\n byId.delete(id)\n })\n\n writeToStorage([...byId.values()])\n applyIndexDeltas(deltas)\n },\n removeAll: async () => {\n writeToStorage([])\n // remove all index keys for this store\n const prefix = `${storageKey}-index-`\n const keysToRemove: string[] = []\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k && k.startsWith(prefix)) keysToRemove.push(k)\n }\n keysToRemove.forEach(k => localStorage.removeItem(k))\n indices.splice(0)\n },\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createStorageAdapter, get, serializeValue } from '@signaldb/core'\n\n/**\n * Creates a storage adapter for managing a SignalDB collection using localStorage.\n * @param name - A unique name for the collection, used as part of the localStorage key.\n * @param options - Optional configuration for the adapter.\n * @param options.databaseName - An optional name for the database to namespace the storage (default: 'signaldb').\n * @param options.serialize - A function to serialize items to a string (default: `JSON.stringify`).\n * @param options.deserialize - A function to deserialize a string into items (default: `JSON.parse`).\n * @returns A SignalDB storage adapter for managing data in localStorage.\n */\nexport default function createLocalStorageAdapter<\n T extends { id: I } & Record<string, any>,\n I,\n>(\n name: string,\n options?: {\n databaseName?: string,\n serialize?: (items: any) => string,\n deserialize?: (itemsString: string) => any,\n },\n\n) {\n const localStorage = globalThis.localStorage\n if (localStorage == null) {\n throw new Error('localStorage is not available in this environment')\n }\n\n const serialize = options?.serialize || (data => JSON.stringify(data))\n const deserialize = options?.deserialize || (input => JSON.parse(input))\n const databaseName = options?.databaseName || 'signaldb'\n const storeName = `${name}`\n\n // We use a single key that namespaces by database and store names\n const storageKey = `${databaseName}-${storeName}`\n\n const indexKeyFor = (field: string) => `${storageKey}-index-${field}`\n const indices: string[] = []\n\n const readFromStorage = (): T[] => {\n const serialized = localStorage.getItem(storageKey)\n if (!serialized) return []\n try {\n const parsed = deserialize(serialized)\n return Array.isArray(parsed) ? (parsed as T[]) : []\n } catch {\n // If parsing fails, treat as empty to avoid corrupting runtime\n return []\n }\n }\n\n const writeToStorage = (items: T[]) => {\n localStorage.setItem(storageKey, serialize(items))\n }\n\n const readIndex = async (field: string) => {\n const serialized = localStorage.getItem(indexKeyFor(field))\n if (!serialized) throw new Error(`Index on field \"${field}\" does not exist`)\n let data: Record<string, I[]>\n try {\n data = deserialize(serialized)\n } catch {\n throw new Error(`Corrupted index on field \"${field}\"`)\n }\n const index = new Map<any, Set<I>>()\n Object.entries(data).forEach(([key, ids]) => {\n if (!index.has(key)) index.set(key, new Set())\n ids.forEach(id => index.get(key)?.add(id))\n })\n return index\n }\n\n const saveIndexMap = (field: string, index: Map<any, Set<I>>) => {\n const safeIndex: Record<string, I[]> = {}\n index.forEach((ids, key) => {\n safeIndex[String(serializeValue(key))] = [...ids]\n })\n localStorage.setItem(indexKeyFor(field), serialize(safeIndex))\n }\n\n const ensureIndex = async (\n field: string,\n items: T[] = readFromStorage(),\n ) => {\n const index = new Map<any, Set<I>>()\n items.forEach((item) => {\n const fieldValue = get(item, field)\n if (fieldValue == null) return\n if (!index.has(fieldValue)) index.set(fieldValue, new Set())\n index.get(fieldValue)?.add(item.id)\n })\n saveIndexMap(field, index)\n }\n\n // --- Delta indexing helpers ---\n const safeKeyFor = (value: any) => String(serializeValue(value))\n\n const loadIndexMap = (field: string): Map<string, Set<I>> | undefined => {\n const serialized = localStorage.getItem(indexKeyFor(field))\n if (!serialized) return undefined\n let data: Record<string, I[]>\n try {\n data = deserialize(serialized)\n } catch {\n throw new Error(`Corrupted index on field \"${field}\"`)\n }\n const index = new Map<string, Set<I>>()\n Object.entries(data).forEach(([key, ids]) => {\n index.set(key, new Set(ids))\n })\n return index\n }\n\n type IndexDelta = {\n adds: Map<string, Set<I>>,\n removes: Map<string, Set<I>>,\n }\n\n const addToDelta = (\n deltas: Map<string, IndexDelta>,\n field: string,\n kind: 'add' | 'remove',\n key: string,\n id: I,\n ) => {\n if (!deltas.has(field)) {\n deltas.set(field, { adds: new Map(), removes: new Map() })\n }\n const delta = deltas.get(field)\n if (!delta) return\n const target = kind === 'add' ? delta.adds : delta.removes\n if (!target.has(key)) target.set(key, new Set<I>())\n target.get(key)?.add(id)\n }\n\n const applyIndexDeltas = (deltas: Map<string, IndexDelta>) => {\n // Update only the indices that have changes\n deltas.forEach((delta, field) => {\n const index = loadIndexMap(field)\n // If the index doesn't exist, skip (we only maintain indices that were created)\n if (!index) return\n\n // Apply removals\n delta.removes.forEach((ids, key) => {\n const set = index.get(key)\n if (!set) return\n ids.forEach(id => set.delete(id))\n if (set.size === 0) index.delete(key)\n })\n\n // Apply additions\n delta.adds.forEach((ids, key) => {\n let set = index.get(key)\n if (!set) {\n set = new Set<I>()\n index.set(key, set)\n }\n ids.forEach(id => set.add(id))\n })\n\n saveIndexMap(field, index)\n })\n }\n\n const addDeltaForChange = (\n deltas: Map<string, IndexDelta>,\n field: string,\n oldValue: any,\n newValue: any,\n id: I,\n ) => {\n const oldKey = oldValue == null ? undefined : safeKeyFor(oldValue)\n const newKey = newValue == null ? undefined : safeKeyFor(newValue)\n if (oldKey === newKey) return\n if (oldKey != null) addToDelta(deltas, field, 'remove', oldKey, id)\n if (newKey != null) addToDelta(deltas, field, 'add', newKey, id)\n }\n\n const accumulateUpsertDelta = (\n deltas: Map<string, IndexDelta>,\n existing: T | undefined,\n next: T,\n ) => {\n if (existing) {\n for (const field of indices) {\n addDeltaForChange(deltas, field, get(existing, field), get(next, field), next.id)\n }\n } else {\n for (const field of indices) {\n const value = get(next, field)\n if (value == null) continue\n addToDelta(deltas, field, 'add', safeKeyFor(value), next.id)\n }\n }\n }\n\n const accumulateRemoveDelta = (\n deltas: Map<string, IndexDelta>,\n existing: T,\n ) => {\n for (const field of indices) {\n const value = get(existing, field)\n if (value == null) continue\n addToDelta(deltas, field, 'remove', safeKeyFor(value), existing.id)\n }\n }\n\n const upsertItems = (itemsToUpsert: T[]) => {\n const items = readFromStorage()\n const byId = new Map<I, T>(items.map(item => [item.id, item]))\n\n const deltas = new Map<string, IndexDelta>()\n\n for (const item of itemsToUpsert) {\n const existing = byId.get(item.id)\n accumulateUpsertDelta(deltas, existing, item)\n byId.set(item.id, item)\n }\n\n writeToStorage([...byId.values()])\n applyIndexDeltas(deltas)\n }\n\n return createStorageAdapter<T, I>({\n // lifecycle methods\n setup: async () => {\n // For localStorage, there is no database to open; we just ensure the key exists\n if (localStorage.getItem(storageKey) == null) {\n writeToStorage([])\n }\n // Hydrate known index fields from existing keys so that we can keep them updated across sessions\n const prefix = `${storageKey}-index-`\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k && k.startsWith(prefix)) {\n const field = k.slice(prefix.length)\n if (!indices.includes(field)) indices.push(field)\n }\n }\n },\n teardown: async () => {\n // no-op\n },\n\n // data retrieval methods\n readAll: async () => {\n return readFromStorage()\n },\n readIds: async (ids) => {\n const items = readFromStorage()\n const idSet = new Set<I>(ids)\n return items.filter(item => idSet.has(item.id))\n },\n\n // index methods\n createIndex: async (field) => {\n if (field === 'id') throw new Error('Cannot create index on id field')\n if (!indices.includes(field)) indices.push(field)\n await ensureIndex(field)\n },\n dropIndex: async (field) => {\n if (indices.includes(field)) {\n const i = indices.indexOf(field)\n indices.splice(i, 1)\n }\n const key = indexKeyFor(field)\n if (localStorage.getItem(key) == null) {\n throw new Error(`Index on field \"${field}\" does not exist`)\n }\n localStorage.removeItem(key)\n },\n readIndex,\n\n // data manipulation methods\n insert: async (newItems) => {\n upsertItems(newItems)\n },\n replace: async (itemsToReplace) => {\n upsertItems(itemsToReplace)\n },\n remove: async (itemsToRemove) => {\n const items = readFromStorage()\n const byId = new Map<I, T>(items.map(item => [item.id, item]))\n\n const deltas = new Map<string, IndexDelta>()\n const removeSet = new Set<I>(itemsToRemove.map(item => item.id))\n\n removeSet.forEach((id) => {\n const existing = byId.get(id)\n if (!existing) return\n accumulateRemoveDelta(deltas, existing)\n byId.delete(id)\n })\n\n writeToStorage([...byId.values()])\n applyIndexDeltas(deltas)\n },\n removeAll: async () => {\n writeToStorage([])\n // remove all index keys for this store\n const prefix = `${storageKey}-index-`\n const keysToRemove: string[] = []\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k && k.startsWith(prefix)) keysToRemove.push(k)\n }\n keysToRemove.forEach(k => localStorage.removeItem(k))\n indices.splice(0)\n },\n })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaldb/localstorage",
3
- "version": "2.0.0-beta.21",
3
+ "version": "2.0.0-beta.22",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && vite build",
@@ -53,6 +53,6 @@
53
53
  "dist"
54
54
  ],
55
55
  "peerDependencies": {
56
- "@signaldb/core": "2.0.0-beta.21"
56
+ "@signaldb/core": "2.0.0-beta.22"
57
57
  }
58
58
  }