@pyreon/storage 0.11.4 → 0.11.6

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.
@@ -1,8 +1,8 @@
1
- import { effect } from "@pyreon/reactivity"
2
- import { afterEach, beforeEach, describe, expect, it } from "vitest"
3
- import { _resetRegistry, createStorage, useMemoryStorage } from "../index"
1
+ import { effect } from '@pyreon/reactivity'
2
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
3
+ import { _resetRegistry, createStorage, useMemoryStorage } from '../index'
4
4
 
5
- describe("useMemoryStorage", () => {
5
+ describe('useMemoryStorage', () => {
6
6
  beforeEach(() => {
7
7
  _resetRegistry()
8
8
  })
@@ -11,136 +11,136 @@ describe("useMemoryStorage", () => {
11
11
  _resetRegistry()
12
12
  })
13
13
 
14
- it("works without any browser storage APIs (SSR-safe)", () => {
15
- const sig = useMemoryStorage("ssr-key", "default")
16
- expect(sig()).toBe("default")
17
- sig.set("updated")
18
- expect(sig()).toBe("updated")
14
+ it('works without any browser storage APIs (SSR-safe)', () => {
15
+ const sig = useMemoryStorage('ssr-key', 'default')
16
+ expect(sig()).toBe('default')
17
+ sig.set('updated')
18
+ expect(sig()).toBe('updated')
19
19
  })
20
20
 
21
- it("persists values within the same session", () => {
22
- const sig = useMemoryStorage("persist-key", "initial")
23
- sig.set("saved")
24
- expect(sig()).toBe("saved")
21
+ it('persists values within the same session', () => {
22
+ const sig = useMemoryStorage('persist-key', 'initial')
23
+ sig.set('saved')
24
+ expect(sig()).toBe('saved')
25
25
  })
26
26
 
27
- it("deduplicates signals for same key", () => {
28
- const a = useMemoryStorage("dedup", "val")
29
- const b = useMemoryStorage("dedup", "val")
27
+ it('deduplicates signals for same key', () => {
28
+ const a = useMemoryStorage('dedup', 'val')
29
+ const b = useMemoryStorage('dedup', 'val')
30
30
  expect(a).toBe(b)
31
31
  })
32
32
 
33
- it("returns different signals for different keys", () => {
34
- const a = useMemoryStorage("key-a", "val")
35
- const b = useMemoryStorage("key-b", "val")
33
+ it('returns different signals for different keys', () => {
34
+ const a = useMemoryStorage('key-a', 'val')
35
+ const b = useMemoryStorage('key-b', 'val')
36
36
  expect(a).not.toBe(b)
37
37
  })
38
38
 
39
- it(".remove() resets to default", () => {
40
- const sig = useMemoryStorage("removable", "default")
41
- sig.set("changed")
39
+ it('.remove() resets to default', () => {
40
+ const sig = useMemoryStorage('removable', 'default')
41
+ sig.set('changed')
42
42
  sig.remove()
43
- expect(sig()).toBe("default")
43
+ expect(sig()).toBe('default')
44
44
  })
45
45
 
46
- it("after remove, a new call creates a fresh signal", () => {
47
- const a = useMemoryStorage("fresh", "first")
48
- a.set("modified")
46
+ it('after remove, a new call creates a fresh signal', () => {
47
+ const a = useMemoryStorage('fresh', 'first')
48
+ a.set('modified')
49
49
  a.remove()
50
50
 
51
- const b = useMemoryStorage("fresh", "second")
52
- expect(b()).toBe("second")
51
+ const b = useMemoryStorage('fresh', 'second')
52
+ expect(b()).toBe('second')
53
53
  expect(a).not.toBe(b)
54
54
  })
55
55
 
56
- it(".update() works", () => {
57
- const count = useMemoryStorage("count", 0)
56
+ it('.update() works', () => {
57
+ const count = useMemoryStorage('count', 0)
58
58
  count.update((n) => n + 1)
59
59
  count.update((n) => n + 1)
60
60
  expect(count()).toBe(2)
61
61
  })
62
62
 
63
- it(".peek() reads without subscribing", () => {
64
- const sig = useMemoryStorage("peek-key", "val")
65
- expect(sig.peek()).toBe("val")
63
+ it('.peek() reads without subscribing', () => {
64
+ const sig = useMemoryStorage('peek-key', 'val')
65
+ expect(sig.peek()).toBe('val')
66
66
  })
67
67
 
68
- it("is reactive in effects", () => {
69
- const sig = useMemoryStorage("reactive", "a")
68
+ it('is reactive in effects', () => {
69
+ const sig = useMemoryStorage('reactive', 'a')
70
70
  const values: string[] = []
71
71
 
72
72
  effect(() => {
73
73
  values.push(sig())
74
74
  })
75
75
 
76
- sig.set("b")
77
- sig.set("c")
76
+ sig.set('b')
77
+ sig.set('c')
78
78
 
79
- expect(values).toEqual(["a", "b", "c"])
79
+ expect(values).toEqual(['a', 'b', 'c'])
80
80
  })
81
81
 
82
- it("works with complex objects", () => {
83
- const sig = useMemoryStorage("obj", { name: "", items: [] as string[] })
84
- sig.set({ name: "test", items: ["a", "b"] })
85
- expect(sig()).toEqual({ name: "test", items: ["a", "b"] })
82
+ it('works with complex objects', () => {
83
+ const sig = useMemoryStorage('obj', { name: '', items: [] as string[] })
84
+ sig.set({ name: 'test', items: ['a', 'b'] })
85
+ expect(sig()).toEqual({ name: 'test', items: ['a', 'b'] })
86
86
  })
87
87
 
88
- it("works with arrays", () => {
89
- const sig = useMemoryStorage("arr", [1, 2, 3])
88
+ it('works with arrays', () => {
89
+ const sig = useMemoryStorage('arr', [1, 2, 3])
90
90
  sig.set([4, 5, 6])
91
91
  expect(sig()).toEqual([4, 5, 6])
92
92
  })
93
93
 
94
- it("works with booleans", () => {
95
- const sig = useMemoryStorage("bool", false)
94
+ it('works with booleans', () => {
95
+ const sig = useMemoryStorage('bool', false)
96
96
  sig.set(true)
97
97
  expect(sig()).toBe(true)
98
98
  })
99
99
 
100
- it("works with null default", () => {
101
- const sig = useMemoryStorage<string | null>("nullable", null)
100
+ it('works with null default', () => {
101
+ const sig = useMemoryStorage<string | null>('nullable', null)
102
102
  expect(sig()).toBeNull()
103
- sig.set("value")
104
- expect(sig()).toBe("value")
103
+ sig.set('value')
104
+ expect(sig()).toBe('value')
105
105
  sig.remove()
106
106
  expect(sig()).toBeNull()
107
107
  })
108
108
 
109
- it(".debug() returns debug info", () => {
110
- const sig = useMemoryStorage("debug", "test")
111
- expect(sig.debug().value).toBe("test")
109
+ it('.debug() returns debug info', () => {
110
+ const sig = useMemoryStorage('debug', 'test')
111
+ expect(sig.debug().value).toBe('test')
112
112
  })
113
113
 
114
- it(".label can be set and read", () => {
115
- const sig = useMemoryStorage("label", "val")
116
- sig.label = "my-memory-signal"
117
- expect(sig.label).toBe("my-memory-signal")
114
+ it('.label can be set and read', () => {
115
+ const sig = useMemoryStorage('label', 'val')
116
+ sig.label = 'my-memory-signal'
117
+ expect(sig.label).toBe('my-memory-signal')
118
118
  })
119
119
 
120
- it(".subscribe() works", () => {
121
- const sig = useMemoryStorage("sub", "a")
120
+ it('.subscribe() works', () => {
121
+ const sig = useMemoryStorage('sub', 'a')
122
122
  let called = false
123
123
  const unsub = sig.subscribe(() => {
124
124
  called = true
125
125
  })
126
- sig.set("b")
126
+ sig.set('b')
127
127
  expect(called).toBe(true)
128
128
  unsub()
129
129
  })
130
130
 
131
- it(".direct() works", () => {
132
- const sig = useMemoryStorage("dir", "a")
131
+ it('.direct() works', () => {
132
+ const sig = useMemoryStorage('dir', 'a')
133
133
  let called = false
134
134
  const unsub = sig.direct(() => {
135
135
  called = true
136
136
  })
137
- sig.set("b")
137
+ sig.set('b')
138
138
  expect(called).toBe(true)
139
139
  unsub()
140
140
  })
141
141
  })
142
142
 
143
- describe("createStorage — custom backends", () => {
143
+ describe('createStorage — custom backends', () => {
144
144
  beforeEach(() => {
145
145
  _resetRegistry()
146
146
  })
@@ -149,7 +149,7 @@ describe("createStorage — custom backends", () => {
149
149
  _resetRegistry()
150
150
  })
151
151
 
152
- it("creates storage with a simple Map backend", () => {
152
+ it('creates storage with a simple Map backend', () => {
153
153
  const store = new Map<string, string>()
154
154
  const useCustom = createStorage({
155
155
  get: (k) => store.get(k) ?? null,
@@ -157,15 +157,15 @@ describe("createStorage — custom backends", () => {
157
157
  remove: (k) => store.delete(k),
158
158
  })
159
159
 
160
- const sig = useCustom("key", "default")
161
- expect(sig()).toBe("default")
160
+ const sig = useCustom('key', 'default')
161
+ expect(sig()).toBe('default')
162
162
 
163
- sig.set("value")
164
- expect(sig()).toBe("value")
165
- expect(store.get("key")).toBe(JSON.stringify("value"))
163
+ sig.set('value')
164
+ expect(sig()).toBe('value')
165
+ expect(store.get('key')).toBe(JSON.stringify('value'))
166
166
  })
167
167
 
168
- it("creates a named backend for separate deduplication", () => {
168
+ it('creates a named backend for separate deduplication', () => {
169
169
  const storeA = new Map<string, string>()
170
170
  const storeB = new Map<string, string>()
171
171
 
@@ -175,7 +175,7 @@ describe("createStorage — custom backends", () => {
175
175
  set: (k, v) => storeA.set(k, v),
176
176
  remove: (k) => storeA.delete(k),
177
177
  },
178
- "backend-a",
178
+ 'backend-a',
179
179
  )
180
180
 
181
181
  const useBackendB = createStorage(
@@ -184,19 +184,19 @@ describe("createStorage — custom backends", () => {
184
184
  set: (k, v) => storeB.set(k, v),
185
185
  remove: (k) => storeB.delete(k),
186
186
  },
187
- "backend-b",
187
+ 'backend-b',
188
188
  )
189
189
 
190
- const sigA = useBackendA("key", "a-default")
191
- const sigB = useBackendB("key", "b-default")
190
+ const sigA = useBackendA('key', 'a-default')
191
+ const sigB = useBackendB('key', 'b-default')
192
192
 
193
193
  // Different backends, same key — should be different signals
194
194
  expect(sigA).not.toBe(sigB)
195
- expect(sigA()).toBe("a-default")
196
- expect(sigB()).toBe("b-default")
195
+ expect(sigA()).toBe('a-default')
196
+ expect(sigB()).toBe('b-default')
197
197
  })
198
198
 
199
- it("backend that transforms values (encryption-like)", () => {
199
+ it('backend that transforms values (encryption-like)', () => {
200
200
  // Simple "encryption" — just base64 encode/decode
201
201
  const store = new Map<string, string>()
202
202
  const useEncrypted = createStorage({
@@ -208,34 +208,34 @@ describe("createStorage — custom backends", () => {
208
208
  remove: (k) => store.delete(k),
209
209
  })
210
210
 
211
- const sig = useEncrypted("secret", "default")
212
- sig.set("my-secret-value")
211
+ const sig = useEncrypted('secret', 'default')
212
+ sig.set('my-secret-value')
213
213
 
214
- expect(sig()).toBe("my-secret-value")
214
+ expect(sig()).toBe('my-secret-value')
215
215
  // Raw stored value should be base64 encoded
216
- const raw = store.get("secret")
217
- expect(raw).toBe(btoa(JSON.stringify("my-secret-value")))
216
+ const raw = store.get('secret')
217
+ expect(raw).toBe(btoa(JSON.stringify('my-secret-value')))
218
218
  })
219
219
 
220
- it("handles backend.remove() errors gracefully", () => {
220
+ it('handles backend.remove() errors gracefully', () => {
221
221
  const useCustom = createStorage({
222
222
  get: () => null,
223
223
  set: () => {
224
224
  // no-op
225
225
  },
226
226
  remove: () => {
227
- throw new Error("remove failed")
227
+ throw new Error('remove failed')
228
228
  },
229
229
  })
230
230
 
231
- const sig = useCustom("key", "default")
232
- sig.set("value")
231
+ const sig = useCustom('key', 'default')
232
+ sig.set('value')
233
233
  // remove() should not throw
234
234
  sig.remove()
235
- expect(sig()).toBe("default")
235
+ expect(sig()).toBe('default')
236
236
  })
237
237
 
238
- it("multiple hooks on same custom backend share deduplication", () => {
238
+ it('multiple hooks on same custom backend share deduplication', () => {
239
239
  const store = new Map<string, string>()
240
240
  const useCustom = createStorage(
241
241
  {
@@ -243,14 +243,14 @@ describe("createStorage — custom backends", () => {
243
243
  set: (k, v) => store.set(k, v),
244
244
  remove: (k) => store.delete(k),
245
245
  },
246
- "shared",
246
+ 'shared',
247
247
  )
248
248
 
249
- const sig1 = useCustom("shared-key", "default")
250
- sig1.set("updated")
249
+ const sig1 = useCustom('shared-key', 'default')
250
+ sig1.set('updated')
251
251
 
252
- const sig2 = useCustom("shared-key", "default")
252
+ const sig2 = useCustom('shared-key', 'default')
253
253
  expect(sig1).toBe(sig2) // Same instance
254
- expect(sig2()).toBe("updated")
254
+ expect(sig2()).toBe('updated')
255
255
  })
256
256
  })
@@ -1,7 +1,7 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest"
2
- import { _resetRegistry, useSessionStorage } from "../index"
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
2
+ import { _resetRegistry, useSessionStorage } from '../index'
3
3
 
4
- describe("useSessionStorage", () => {
4
+ describe('useSessionStorage', () => {
5
5
  beforeEach(() => {
6
6
  sessionStorage.clear()
7
7
  _resetRegistry()
@@ -12,56 +12,56 @@ describe("useSessionStorage", () => {
12
12
  _resetRegistry()
13
13
  })
14
14
 
15
- it("returns default value when key is not in storage", () => {
16
- const step = useSessionStorage("step", 0)
15
+ it('returns default value when key is not in storage', () => {
16
+ const step = useSessionStorage('step', 0)
17
17
  expect(step()).toBe(0)
18
18
  })
19
19
 
20
- it("reads existing value from sessionStorage", () => {
21
- sessionStorage.setItem("step", JSON.stringify(3))
22
- const step = useSessionStorage("step", 0)
20
+ it('reads existing value from sessionStorage', () => {
21
+ sessionStorage.setItem('step', JSON.stringify(3))
22
+ const step = useSessionStorage('step', 0)
23
23
  expect(step()).toBe(3)
24
24
  })
25
25
 
26
- it(".set() updates signal and sessionStorage", () => {
27
- const step = useSessionStorage("step", 0)
26
+ it('.set() updates signal and sessionStorage', () => {
27
+ const step = useSessionStorage('step', 0)
28
28
  step.set(5)
29
29
  expect(step()).toBe(5)
30
- expect(JSON.parse(sessionStorage.getItem("step")!)).toBe(5)
30
+ expect(JSON.parse(sessionStorage.getItem('step')!)).toBe(5)
31
31
  })
32
32
 
33
- it(".remove() clears from storage and resets to default", () => {
34
- const step = useSessionStorage("step", 0)
33
+ it('.remove() clears from storage and resets to default', () => {
34
+ const step = useSessionStorage('step', 0)
35
35
  step.set(5)
36
36
  step.remove()
37
37
  expect(step()).toBe(0)
38
- expect(sessionStorage.getItem("step")).toBeNull()
38
+ expect(sessionStorage.getItem('step')).toBeNull()
39
39
  })
40
40
 
41
- it("returns same signal instance for same key", () => {
42
- const a = useSessionStorage("step", 0)
43
- const b = useSessionStorage("step", 0)
41
+ it('returns same signal instance for same key', () => {
42
+ const a = useSessionStorage('step', 0)
43
+ const b = useSessionStorage('step', 0)
44
44
  expect(a).toBe(b)
45
45
  })
46
46
 
47
- it("works with objects", () => {
48
- const form = useSessionStorage("form-draft", { name: "", email: "" })
49
- form.set({ name: "Alice", email: "alice@example.com" })
50
- expect(form()).toEqual({ name: "Alice", email: "alice@example.com" })
47
+ it('works with objects', () => {
48
+ const form = useSessionStorage('form-draft', { name: '', email: '' })
49
+ form.set({ name: 'Alice', email: 'alice@example.com' })
50
+ expect(form()).toEqual({ name: 'Alice', email: 'alice@example.com' })
51
51
  })
52
52
 
53
- it("handles corrupt storage values gracefully", () => {
54
- sessionStorage.setItem("broken", "{{invalid")
55
- const value = useSessionStorage("broken", "default")
56
- expect(value()).toBe("default")
53
+ it('handles corrupt storage values gracefully', () => {
54
+ sessionStorage.setItem('broken', '{{invalid')
55
+ const value = useSessionStorage('broken', 'default')
56
+ expect(value()).toBe('default')
57
57
  })
58
58
 
59
- it("does not share signals with localStorage", async () => {
60
- const { useStorage } = await import("../local")
61
- const local = useStorage("key", "local-default")
62
- const session = useSessionStorage("key", "session-default")
59
+ it('does not share signals with localStorage', async () => {
60
+ const { useStorage } = await import('../local')
61
+ const local = useStorage('key', 'local-default')
62
+ const session = useSessionStorage('key', 'session-default')
63
63
  expect(local).not.toBe(session)
64
- expect(local()).toBe("local-default")
65
- expect(session()).toBe("session-default")
64
+ expect(local()).toBe('local-default')
65
+ expect(session()).toBe('session-default')
66
66
  })
67
67
  })
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Signal } from "@pyreon/reactivity"
1
+ import type { Signal } from '@pyreon/reactivity'
2
2
 
3
3
  // ─── Storage Signal ──────────────────────────────────────────────────────────
4
4
 
@@ -42,7 +42,7 @@ export interface CookieOptions<T> extends StorageOptions<T> {
42
42
  /** HTTPS only — default: false */
43
43
  secure?: boolean
44
44
  /** SameSite policy — default: 'lax' */
45
- sameSite?: "strict" | "lax" | "none"
45
+ sameSite?: 'strict' | 'lax' | 'none'
46
46
  }
47
47
 
48
48
  // ─── IndexedDB Options ───────────────────────────────────────────────────────
package/src/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { StorageOptions } from "./types"
1
+ import type { StorageOptions } from './types'
2
2
 
3
3
  // ─── SSR Detection ───────────────────────────────────────────────────────────
4
4
 
@@ -6,7 +6,7 @@ import type { StorageOptions } from "./types"
6
6
  * Check if we're running in a browser environment.
7
7
  */
8
8
  export function isBrowser(): boolean {
9
- return typeof window !== "undefined" && typeof document !== "undefined"
9
+ return typeof window !== 'undefined' && typeof document !== 'undefined'
10
10
  }
11
11
 
12
12
  // ─── Serialization ───────────────────────────────────────────────────────────
@@ -14,7 +14,7 @@ export function isBrowser(): boolean {
14
14
  /**
15
15
  * Serialize a value to a string for storage.
16
16
  */
17
- export function serialize<T>(value: T, serializer?: StorageOptions<T>["serializer"]): string {
17
+ export function serialize<T>(value: T, serializer?: StorageOptions<T>['serializer']): string {
18
18
  if (serializer) return serializer(value)
19
19
  return JSON.stringify(value)
20
20
  }
@@ -26,8 +26,8 @@ export function serialize<T>(value: T, serializer?: StorageOptions<T>["serialize
26
26
  export function deserialize<T>(
27
27
  raw: string,
28
28
  defaultValue: T,
29
- deserializer?: StorageOptions<T>["deserializer"],
30
- onError?: StorageOptions<T>["onError"],
29
+ deserializer?: StorageOptions<T>['deserializer'],
30
+ onError?: StorageOptions<T>['onError'],
31
31
  ): T {
32
32
  try {
33
33
  if (deserializer) return deserializer(raw)
@@ -47,13 +47,13 @@ export function deserialize<T>(
47
47
  * Safely get a Web Storage instance (localStorage or sessionStorage).
48
48
  * Returns null if not available (SSR, security restrictions, etc.).
49
49
  */
50
- export function getWebStorage(type: "local" | "session"): Storage | null {
50
+ export function getWebStorage(type: 'local' | 'session'): Storage | null {
51
51
  if (!isBrowser()) return null
52
52
  try {
53
- const storage = type === "local" ? window.localStorage : window.sessionStorage
53
+ const storage = type === 'local' ? window.localStorage : window.sessionStorage
54
54
  // Test that it actually works (can throw in private browsing)
55
- const testKey = "__pyreon_storage_test__"
56
- storage.setItem(testKey, "1")
55
+ const testKey = '__pyreon_storage_test__'
56
+ storage.setItem(testKey, '1')
57
57
  storage.removeItem(testKey)
58
58
  return storage
59
59
  } catch {