@pyreon/storage 0.14.0 → 0.16.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.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"a379c11c-1","name":"registry.ts"},{"uid":"a379c11c-3","name":"utils.ts"},{"uid":"a379c11c-5","name":"cookie.ts"},{"uid":"a379c11c-7","name":"custom.ts"},{"uid":"a379c11c-9","name":"indexed-db.ts"},{"uid":"a379c11c-11","name":"local.ts"},{"uid":"a379c11c-13","name":"session.ts"},{"uid":"a379c11c-15","name":"clear.ts"},{"uid":"a379c11c-17","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"a379c11c-1":{"renderedLength":1076,"gzipLength":468,"brotliLength":0,"metaUid":"a379c11c-0"},"a379c11c-3":{"renderedLength":1231,"gzipLength":565,"brotliLength":0,"metaUid":"a379c11c-2"},"a379c11c-5":{"renderedLength":3215,"gzipLength":1193,"brotliLength":0,"metaUid":"a379c11c-4"},"a379c11c-7":{"renderedLength":2242,"gzipLength":890,"brotliLength":0,"metaUid":"a379c11c-6"},"a379c11c-9":{"renderedLength":4535,"gzipLength":1524,"brotliLength":0,"metaUid":"a379c11c-8"},"a379c11c-11":{"renderedLength":3217,"gzipLength":1141,"brotliLength":0,"metaUid":"a379c11c-10"},"a379c11c-13":{"renderedLength":932,"gzipLength":464,"brotliLength":0,"metaUid":"a379c11c-12"},"a379c11c-15":{"renderedLength":1489,"gzipLength":604,"brotliLength":0,"metaUid":"a379c11c-14"},"a379c11c-17":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"a379c11c-16"}},"nodeMetas":{"a379c11c-0":{"id":"/src/registry.ts","moduleParts":{"index.js":"a379c11c-1"},"imported":[],"importedBy":[{"uid":"a379c11c-16"},{"uid":"a379c11c-4"},{"uid":"a379c11c-6"},{"uid":"a379c11c-8"},{"uid":"a379c11c-10"},{"uid":"a379c11c-12"},{"uid":"a379c11c-14"}]},"a379c11c-2":{"id":"/src/utils.ts","moduleParts":{"index.js":"a379c11c-3"},"imported":[],"importedBy":[{"uid":"a379c11c-4"},{"uid":"a379c11c-6"},{"uid":"a379c11c-8"},{"uid":"a379c11c-10"},{"uid":"a379c11c-12"},{"uid":"a379c11c-14"}]},"a379c11c-4":{"id":"/src/cookie.ts","moduleParts":{"index.js":"a379c11c-5"},"imported":[{"uid":"a379c11c-18"},{"uid":"a379c11c-0"},{"uid":"a379c11c-2"}],"importedBy":[{"uid":"a379c11c-16"}]},"a379c11c-6":{"id":"/src/custom.ts","moduleParts":{"index.js":"a379c11c-7"},"imported":[{"uid":"a379c11c-18"},{"uid":"a379c11c-0"},{"uid":"a379c11c-2"}],"importedBy":[{"uid":"a379c11c-16"}]},"a379c11c-8":{"id":"/src/indexed-db.ts","moduleParts":{"index.js":"a379c11c-9"},"imported":[{"uid":"a379c11c-18"},{"uid":"a379c11c-0"},{"uid":"a379c11c-2"}],"importedBy":[{"uid":"a379c11c-16"}]},"a379c11c-10":{"id":"/src/local.ts","moduleParts":{"index.js":"a379c11c-11"},"imported":[{"uid":"a379c11c-18"},{"uid":"a379c11c-0"},{"uid":"a379c11c-2"}],"importedBy":[{"uid":"a379c11c-16"},{"uid":"a379c11c-12"}]},"a379c11c-12":{"id":"/src/session.ts","moduleParts":{"index.js":"a379c11c-13"},"imported":[{"uid":"a379c11c-18"},{"uid":"a379c11c-10"},{"uid":"a379c11c-0"},{"uid":"a379c11c-2"}],"importedBy":[{"uid":"a379c11c-16"}]},"a379c11c-14":{"id":"/src/clear.ts","moduleParts":{"index.js":"a379c11c-15"},"imported":[{"uid":"a379c11c-0"},{"uid":"a379c11c-2"}],"importedBy":[{"uid":"a379c11c-16"}]},"a379c11c-16":{"id":"/src/index.ts","moduleParts":{"index.js":"a379c11c-17"},"imported":[{"uid":"a379c11c-4"},{"uid":"a379c11c-6"},{"uid":"a379c11c-8"},{"uid":"a379c11c-10"},{"uid":"a379c11c-12"},{"uid":"a379c11c-14"},{"uid":"a379c11c-0"}],"importedBy":[],"isEntry":true},"a379c11c-18":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"a379c11c-4"},{"uid":"a379c11c-6"},{"uid":"a379c11c-8"},{"uid":"a379c11c-10"},{"uid":"a379c11c-12"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"1fd55a13-1","name":"registry.ts"},{"uid":"1fd55a13-3","name":"utils.ts"},{"uid":"1fd55a13-5","name":"wrap-base-signal.ts"},{"uid":"1fd55a13-7","name":"cookie.ts"},{"uid":"1fd55a13-9","name":"custom.ts"},{"uid":"1fd55a13-11","name":"indexed-db.ts"},{"uid":"1fd55a13-13","name":"local.ts"},{"uid":"1fd55a13-15","name":"session.ts"},{"uid":"1fd55a13-17","name":"clear.ts"},{"uid":"1fd55a13-19","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"1fd55a13-1":{"renderedLength":1076,"gzipLength":468,"brotliLength":0,"metaUid":"1fd55a13-0"},"1fd55a13-3":{"renderedLength":1231,"gzipLength":565,"brotliLength":0,"metaUid":"1fd55a13-2"},"1fd55a13-5":{"renderedLength":2580,"gzipLength":1289,"brotliLength":0,"metaUid":"1fd55a13-4"},"1fd55a13-7":{"renderedLength":2914,"gzipLength":1091,"brotliLength":0,"metaUid":"1fd55a13-6"},"1fd55a13-9":{"renderedLength":1931,"gzipLength":789,"brotliLength":0,"metaUid":"1fd55a13-8"},"1fd55a13-11":{"renderedLength":4242,"gzipLength":1437,"brotliLength":0,"metaUid":"1fd55a13-10"},"1fd55a13-13":{"renderedLength":2916,"gzipLength":1041,"brotliLength":0,"metaUid":"1fd55a13-12"},"1fd55a13-15":{"renderedLength":932,"gzipLength":464,"brotliLength":0,"metaUid":"1fd55a13-14"},"1fd55a13-17":{"renderedLength":1489,"gzipLength":604,"brotliLength":0,"metaUid":"1fd55a13-16"},"1fd55a13-19":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"1fd55a13-18"}},"nodeMetas":{"1fd55a13-0":{"id":"/src/registry.ts","moduleParts":{"index.js":"1fd55a13-1"},"imported":[],"importedBy":[{"uid":"1fd55a13-18"},{"uid":"1fd55a13-6"},{"uid":"1fd55a13-8"},{"uid":"1fd55a13-10"},{"uid":"1fd55a13-12"},{"uid":"1fd55a13-14"},{"uid":"1fd55a13-16"}]},"1fd55a13-2":{"id":"/src/utils.ts","moduleParts":{"index.js":"1fd55a13-3"},"imported":[],"importedBy":[{"uid":"1fd55a13-6"},{"uid":"1fd55a13-8"},{"uid":"1fd55a13-10"},{"uid":"1fd55a13-12"},{"uid":"1fd55a13-14"},{"uid":"1fd55a13-16"}]},"1fd55a13-4":{"id":"/src/wrap-base-signal.ts","moduleParts":{"index.js":"1fd55a13-5"},"imported":[],"importedBy":[{"uid":"1fd55a13-6"},{"uid":"1fd55a13-8"},{"uid":"1fd55a13-10"},{"uid":"1fd55a13-12"}]},"1fd55a13-6":{"id":"/src/cookie.ts","moduleParts":{"index.js":"1fd55a13-7"},"imported":[{"uid":"1fd55a13-20"},{"uid":"1fd55a13-0"},{"uid":"1fd55a13-2"},{"uid":"1fd55a13-4"}],"importedBy":[{"uid":"1fd55a13-18"}]},"1fd55a13-8":{"id":"/src/custom.ts","moduleParts":{"index.js":"1fd55a13-9"},"imported":[{"uid":"1fd55a13-20"},{"uid":"1fd55a13-0"},{"uid":"1fd55a13-2"},{"uid":"1fd55a13-4"}],"importedBy":[{"uid":"1fd55a13-18"}]},"1fd55a13-10":{"id":"/src/indexed-db.ts","moduleParts":{"index.js":"1fd55a13-11"},"imported":[{"uid":"1fd55a13-20"},{"uid":"1fd55a13-0"},{"uid":"1fd55a13-2"},{"uid":"1fd55a13-4"}],"importedBy":[{"uid":"1fd55a13-18"}]},"1fd55a13-12":{"id":"/src/local.ts","moduleParts":{"index.js":"1fd55a13-13"},"imported":[{"uid":"1fd55a13-20"},{"uid":"1fd55a13-0"},{"uid":"1fd55a13-2"},{"uid":"1fd55a13-4"}],"importedBy":[{"uid":"1fd55a13-18"},{"uid":"1fd55a13-14"}]},"1fd55a13-14":{"id":"/src/session.ts","moduleParts":{"index.js":"1fd55a13-15"},"imported":[{"uid":"1fd55a13-20"},{"uid":"1fd55a13-12"},{"uid":"1fd55a13-0"},{"uid":"1fd55a13-2"}],"importedBy":[{"uid":"1fd55a13-18"}]},"1fd55a13-16":{"id":"/src/clear.ts","moduleParts":{"index.js":"1fd55a13-17"},"imported":[{"uid":"1fd55a13-0"},{"uid":"1fd55a13-2"}],"importedBy":[{"uid":"1fd55a13-18"}]},"1fd55a13-18":{"id":"/src/index.ts","moduleParts":{"index.js":"1fd55a13-19"},"imported":[{"uid":"1fd55a13-6"},{"uid":"1fd55a13-8"},{"uid":"1fd55a13-10"},{"uid":"1fd55a13-12"},{"uid":"1fd55a13-14"},{"uid":"1fd55a13-16"},{"uid":"1fd55a13-0"}],"importedBy":[],"isEntry":true},"1fd55a13-20":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"1fd55a13-6"},{"uid":"1fd55a13-8"},{"uid":"1fd55a13-10"},{"uid":"1fd55a13-12"},{"uid":"1fd55a13-14"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -94,6 +94,73 @@ function getWebStorage(type) {
94
94
  }
95
95
  }
96
96
 
97
+ //#endregion
98
+ //#region src/wrap-base-signal.ts
99
+ /**
100
+ * Wrap a base `signal()` from `@pyreon/reactivity` with a callable that
101
+ * fully participates in Pyreon's reactivity, including the compiler-
102
+ * emitted DOM-binding fast paths (`_bindText` / `_bindDirect`).
103
+ *
104
+ * The wrapper:
105
+ * - Is callable: `wrapper()` returns `sig()` (read + subscribe).
106
+ * - Delegates `.peek` / `.subscribe` / `.direct` / `.debug` to the
107
+ * underlying signal — methods, not state, so re-binding is safe.
108
+ * - Forwards `.label` (getter + setter) to the underlying signal so
109
+ * dev-time naming carries through.
110
+ * - Forwards the internal `_v` field via getter so the compiler's
111
+ * `_bindText(wrapper, textNode)` fast path reads the live value.
112
+ * Without this, the binding writes `String(undefined)` → `''` on
113
+ * initial render AND every subscriber notification (the bug class
114
+ * fixed in PR #546 and now caught by the
115
+ * `pyreon/storage-signal-v-forwarding` lint rule).
116
+ *
117
+ * The wrapper is RETURNED as `signal()` minus the methods callers
118
+ * typically OVERRIDE (`.set`, `.update`, `.remove`, plus any factory-
119
+ * specific extras). Each storage factory layers its persistence
120
+ * behavior on top by assigning these fields to the returned wrapper
121
+ * before returning to the user.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const sig = signal<T>(initialValue)
126
+ * const storageSig = wrapBaseSignal(sig) as StorageSignal<T>
127
+ * storageSig.set = (value: T) => {
128
+ * sig.set(value)
129
+ * localStorage.setItem(key, serialize(value))
130
+ * }
131
+ * storageSig.remove = () => { ... }
132
+ * return storageSig
133
+ * ```
134
+ *
135
+ * **Why this exists**: pre-2026-05-13 the same wrapper shape was
136
+ * duplicated across 4 factories (~30 lines × 4 sites). The duplication
137
+ * structurally enabled the `_v` forwarding bug — only `local.ts:createStorageSignal`
138
+ * was shared between local + session; cookie / custom / indexed-db each had
139
+ * their own factory body. Forgetting `_v` in one site went unnoticed for
140
+ * ~9 months. This helper makes the contract single-source: every backend
141
+ * gets the same wrapper, and field additions to the signal protocol land
142
+ * in one place.
143
+ */
144
+ function wrapBaseSignal(sig) {
145
+ const wrapper = (() => sig());
146
+ wrapper.peek = () => sig.peek();
147
+ wrapper.subscribe = (listener) => sig.subscribe(listener);
148
+ wrapper.direct = (updater) => sig.direct(updater);
149
+ wrapper.debug = () => sig.debug();
150
+ Object.defineProperty(wrapper, "label", {
151
+ get: () => sig.label,
152
+ set: (v) => {
153
+ sig.label = v;
154
+ },
155
+ configurable: true
156
+ });
157
+ Object.defineProperty(wrapper, "_v", {
158
+ get: () => sig._v,
159
+ configurable: true
160
+ });
161
+ return wrapper;
162
+ }
163
+
97
164
  //#endregion
98
165
  //#region src/cookie.ts
99
166
  let serverCookieString = "";
@@ -169,17 +236,7 @@ function useCookie(key, defaultValue, options = {}) {
169
236
  if (existing) return existing.signal;
170
237
  const raw = readCookie(key);
171
238
  const sig = signal(raw !== null ? deserialize(raw, defaultValue, options.deserializer, options.onError) : defaultValue);
172
- const storageSig = (() => sig());
173
- storageSig.peek = () => sig.peek();
174
- storageSig.subscribe = (listener) => sig.subscribe(listener);
175
- storageSig.direct = (updater) => sig.direct(updater);
176
- storageSig.debug = () => sig.debug();
177
- Object.defineProperty(storageSig, "label", {
178
- get: () => sig.label,
179
- set: (v) => {
180
- sig.label = v;
181
- }
182
- });
239
+ const storageSig = wrapBaseSignal(sig);
183
240
  storageSig.set = (value) => {
184
241
  sig.set(value);
185
242
  writeCookie(key, value, options);
@@ -225,17 +282,7 @@ function createStorage(backend, backendName) {
225
282
  if (raw !== null) initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError);
226
283
  } catch {}
227
284
  const sig = signal(initialValue);
228
- const storageSig = (() => sig());
229
- storageSig.peek = () => sig.peek();
230
- storageSig.subscribe = (listener) => sig.subscribe(listener);
231
- storageSig.direct = (updater) => sig.direct(updater);
232
- storageSig.debug = () => sig.debug();
233
- Object.defineProperty(storageSig, "label", {
234
- get: () => sig.label,
235
- set: (v) => {
236
- sig.label = v;
237
- }
238
- });
285
+ const storageSig = wrapBaseSignal(sig);
239
286
  storageSig.set = (value) => {
240
287
  sig.set(value);
241
288
  try {
@@ -279,7 +326,7 @@ const useMemoryStorage = createStorage((() => {
279
326
 
280
327
  //#endregion
281
328
  //#region src/indexed-db.ts
282
- const __DEV__ = import.meta.env?.DEV === true;
329
+ const __DEV__ = process.env.NODE_ENV !== "production";
283
330
  const dbCache = /* @__PURE__ */ new Map();
284
331
  function openDB(dbName, storeName) {
285
332
  if (typeof indexedDB === "undefined") return Promise.reject(/* @__PURE__ */ new Error("[Pyreon] indexedDB is not available in this environment"));
@@ -363,17 +410,7 @@ function useIndexedDB(key, defaultValue, options = {}) {
363
410
  if (writeTimer !== null) clearTimeout(writeTimer);
364
411
  writeTimer = setTimeout(flushWrite, debounceMs);
365
412
  }
366
- const storageSig = (() => sig());
367
- storageSig.peek = () => sig.peek();
368
- storageSig.subscribe = (listener) => sig.subscribe(listener);
369
- storageSig.direct = (updater) => sig.direct(updater);
370
- storageSig.debug = () => sig.debug();
371
- Object.defineProperty(storageSig, "label", {
372
- get: () => sig.label,
373
- set: (v) => {
374
- sig.label = v;
375
- }
376
- });
413
+ const storageSig = wrapBaseSignal(sig);
377
414
  storageSig.set = (value) => {
378
415
  sig.set(value);
379
416
  scheduleWrite(value);
@@ -472,17 +509,7 @@ function useStorage(key, defaultValue, options) {
472
509
  */
473
510
  function createStorageSignal(sig, key, defaultValue, backend, options) {
474
511
  const storage = getWebStorage(backend);
475
- const storageSig = (() => sig());
476
- storageSig.peek = () => sig.peek();
477
- storageSig.subscribe = (listener) => sig.subscribe(listener);
478
- storageSig.direct = (updater) => sig.direct(updater);
479
- storageSig.debug = () => sig.debug();
480
- Object.defineProperty(storageSig, "label", {
481
- get: () => sig.label,
482
- set: (v) => {
483
- sig.label = v;
484
- }
485
- });
512
+ const storageSig = wrapBaseSignal(sig);
486
513
  storageSig.set = (value) => {
487
514
  sig.set(value);
488
515
  if (storage) try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/storage",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Reactive client-side storage for Pyreon — localStorage, sessionStorage, cookies, IndexedDB",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/storage#readme",
6
6
  "bugs": {
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "files": [
16
16
  "lib",
17
+ "!lib/**/*.map",
17
18
  "src",
18
19
  "README.md",
19
20
  "LICENSE"
@@ -43,11 +44,11 @@
43
44
  "devDependencies": {
44
45
  "@happy-dom/global-registrator": "^20.8.9",
45
46
  "@pyreon/manifest": "0.13.1",
46
- "@pyreon/reactivity": "^0.14.0",
47
- "@vitus-labs/tools-lint": "^1.15.5",
47
+ "@pyreon/reactivity": "^0.16.0",
48
+ "@vitus-labs/tools-lint": "^2.3.0",
48
49
  "bun-types": "^1.3.12"
49
50
  },
50
- "peerDependencies": {
51
- "@pyreon/reactivity": "^0.14.0"
51
+ "dependencies": {
52
+ "@pyreon/reactivity": "^0.16.0"
52
53
  }
53
54
  }
package/src/cookie.ts CHANGED
@@ -2,6 +2,7 @@ import { signal } from '@pyreon/reactivity'
2
2
  import { getEntry, removeEntry, setEntry } from './registry'
3
3
  import type { CookieOptions, StorageSignal } from './types'
4
4
  import { deserialize, isBrowser, serialize } from './utils'
5
+ import { wrapBaseSignal } from './wrap-base-signal'
5
6
 
6
7
  // ─── Server-side cookie source ───────────────────────────────────────────────
7
8
 
@@ -122,20 +123,8 @@ export function useCookie<T>(
122
123
 
123
124
  const sig = signal<T>(initialValue)
124
125
 
125
- // Build the storage signal
126
- const storageSig = (() => sig()) as unknown as StorageSignal<T>
127
-
128
- storageSig.peek = () => sig.peek()
129
- storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)
130
- storageSig.direct = (updater: () => void) => sig.direct(updater)
131
- storageSig.debug = () => sig.debug()
132
-
133
- Object.defineProperty(storageSig, 'label', {
134
- get: () => sig.label,
135
- set: (v: string | undefined) => {
136
- sig.label = v
137
- },
138
- })
126
+ // Shared base wrapper — see `wrap-base-signal.ts` for the full contract.
127
+ const storageSig = wrapBaseSignal(sig) as unknown as StorageSignal<T>
139
128
 
140
129
  storageSig.set = (value: T) => {
141
130
  sig.set(value)
package/src/custom.ts CHANGED
@@ -2,6 +2,7 @@ import { signal } from '@pyreon/reactivity'
2
2
  import { getEntry, removeEntry, setEntry } from './registry'
3
3
  import type { StorageBackend, StorageOptions, StorageSignal } from './types'
4
4
  import { deserialize, serialize } from './utils'
5
+ import { wrapBaseSignal } from './wrap-base-signal'
5
6
 
6
7
  // ─── createStorage ───────────────────────────────────────────────────────────
7
8
 
@@ -48,20 +49,8 @@ export function createStorage(
48
49
 
49
50
  const sig = signal<T>(initialValue)
50
51
 
51
- // Build the storage signal
52
- const storageSig = (() => sig()) as unknown as StorageSignal<T>
53
-
54
- storageSig.peek = () => sig.peek()
55
- storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)
56
- storageSig.direct = (updater: () => void) => sig.direct(updater)
57
- storageSig.debug = () => sig.debug()
58
-
59
- Object.defineProperty(storageSig, 'label', {
60
- get: () => sig.label,
61
- set: (v: string | undefined) => {
62
- sig.label = v
63
- },
64
- })
52
+ // Shared base wrapper — see `wrap-base-signal.ts` for the full contract.
53
+ const storageSig = wrapBaseSignal(sig) as unknown as StorageSignal<T>
65
54
 
66
55
  storageSig.set = (value: T) => {
67
56
  sig.set(value)
package/src/env.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Minimal process type — just enough for `process.env.NODE_ENV` checks.
3
+ * Avoids requiring @types/node in consumers that import pyreon source
4
+ * via the `"bun"` export condition.
5
+ */
6
+ declare var process: { env: { NODE_ENV?: string } }
package/src/indexed-db.ts CHANGED
@@ -2,9 +2,9 @@ import { signal } from '@pyreon/reactivity'
2
2
  import { getEntry, removeEntry, setEntry } from './registry'
3
3
  import type { IndexedDBOptions, StorageSignal } from './types'
4
4
  import { deserialize, isBrowser, serialize } from './utils'
5
+ import { wrapBaseSignal } from './wrap-base-signal'
5
6
 
6
- // @ts-ignore import.meta.env.DEV is Vite/Rolldown literal-replaced at build time
7
- const __DEV__: boolean = import.meta.env?.DEV === true
7
+ const __DEV__: boolean = process.env.NODE_ENV !== 'production'
8
8
 
9
9
  // ─── Database management ─────────────────────────────────────────────────────
10
10
 
@@ -141,20 +141,8 @@ export function useIndexedDB<T>(
141
141
  writeTimer = setTimeout(flushWrite, debounceMs)
142
142
  }
143
143
 
144
- // Build the storage signal
145
- const storageSig = (() => sig()) as unknown as StorageSignal<T>
146
-
147
- storageSig.peek = () => sig.peek()
148
- storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)
149
- storageSig.direct = (updater: () => void) => sig.direct(updater)
150
- storageSig.debug = () => sig.debug()
151
-
152
- Object.defineProperty(storageSig, 'label', {
153
- get: () => sig.label,
154
- set: (v: string | undefined) => {
155
- sig.label = v
156
- },
157
- })
144
+ // Shared base wrapper — see `wrap-base-signal.ts` for the full contract.
145
+ const storageSig = wrapBaseSignal(sig) as unknown as StorageSignal<T>
158
146
 
159
147
  storageSig.set = (value: T) => {
160
148
  sig.set(value)
package/src/local.ts CHANGED
@@ -2,6 +2,7 @@ import { signal } from '@pyreon/reactivity'
2
2
  import { getEntry, removeEntry, setEntry } from './registry'
3
3
  import type { StorageOptions, StorageSignal } from './types'
4
4
  import { deserialize, getWebStorage, isBrowser, serialize } from './utils'
5
+ import { wrapBaseSignal } from './wrap-base-signal'
5
6
 
6
7
  // ─── Cross-tab sync ──────────────────────────────────────────────────────────
7
8
 
@@ -119,21 +120,11 @@ export function createStorageSignal<T>(
119
120
  ): StorageSignal<T> {
120
121
  const storage = getWebStorage(backend)
121
122
 
122
- // The callable signal function (read)
123
- const storageSig = (() => sig()) as unknown as StorageSignal<T>
124
-
125
- // Delegate all signal methods
126
- storageSig.peek = () => sig.peek()
127
- storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)
128
- storageSig.direct = (updater: () => void) => sig.direct(updater)
129
- storageSig.debug = () => sig.debug()
130
-
131
- Object.defineProperty(storageSig, 'label', {
132
- get: () => sig.label,
133
- set: (v: string | undefined) => {
134
- sig.label = v
135
- },
136
- })
123
+ // Shared base wrapper — callable + `.peek` / `.subscribe` / `.direct` /
124
+ // `.debug` / `.label` / forwarded `_v`. See `wrap-base-signal.ts` for
125
+ // the full contract (including why `_v` forwarding is load-bearing for
126
+ // the compiler-emitted `_bindText` fast path).
127
+ const storageSig = wrapBaseSignal(sig) as unknown as StorageSignal<T>
137
128
 
138
129
  // Override set to persist
139
130
  storageSig.set = (value: T) => {
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Regression: storage signals didn't forward the internal `_v` field that
3
+ * the compiler-emitted `_bindText` / `_bindDirect` fast paths read.
4
+ *
5
+ * Bug shape: when JSX `{() => theme()}` (where `theme` was declared via
6
+ * `useStorage`) lands in a text binding, the compiler optimizes to
7
+ * `_bindText(theme, textNode)` instead of `_bindText(() => theme(), textNode)`.
8
+ * `_bindText`'s fast path reads `source._v` directly (skipping the function
9
+ * call) AND registers the text-update closure via `source.direct(...)`.
10
+ *
11
+ * Storage signals delegated `.direct` (so subscribe-on-change worked) but
12
+ * forgot `._v` — the text-update read undefined → wrote empty string. The
13
+ * symptom: SSR rendered `<strong>light</strong>` correctly but post-
14
+ * hydration the textNode went empty and stayed empty even after
15
+ * `theme.set('dark')` (the binding fired but read the missing `_v` again).
16
+ *
17
+ * Fix: forward `_v` via getter so storage signals honor the same
18
+ * structural contract as base signals.
19
+ */
20
+ import { afterEach, describe, expect, it } from 'vitest'
21
+ import {
22
+ _resetRegistry,
23
+ _resetStorageListener,
24
+ useCookie,
25
+ useMemoryStorage,
26
+ useSessionStorage,
27
+ useStorage,
28
+ } from '../index'
29
+
30
+ interface SignalLike<T> {
31
+ _v: T
32
+ }
33
+
34
+ const internal = <T,>(sig: unknown): SignalLike<T> => sig as SignalLike<T>
35
+
36
+ describe('storage signals — _bindText / _bindDirect compat (`_v` forwarding)', () => {
37
+ afterEach(() => {
38
+ _resetRegistry()
39
+ _resetStorageListener()
40
+ try {
41
+ localStorage.clear()
42
+ sessionStorage.clear()
43
+ } catch {
44
+ // Cross-origin / disabled — skip.
45
+ }
46
+ })
47
+
48
+ it('useStorage signal forwards `_v` to the underlying base signal', () => {
49
+ const theme = useStorage('test-theme-v', 'light')
50
+ // Bug-shape: pre-fix this is `undefined`.
51
+ expect(internal<string>(theme)._v).toBe('light')
52
+
53
+ // After set, both the public read and `_v` reflect the new value.
54
+ theme.set('dark')
55
+ expect(theme()).toBe('dark')
56
+ expect(internal<string>(theme)._v).toBe('dark')
57
+ })
58
+
59
+ it('useStorage `_v` reflects values read from localStorage on init', () => {
60
+ try {
61
+ localStorage.setItem('test-init', JSON.stringify('preloaded'))
62
+ } catch {
63
+ // Skip if storage unavailable.
64
+ return
65
+ }
66
+ const sig = useStorage('test-init', 'fallback')
67
+ expect(sig()).toBe('preloaded')
68
+ expect(internal<string>(sig)._v).toBe('preloaded')
69
+ })
70
+
71
+ it('useSessionStorage signal forwards `_v`', () => {
72
+ const step = useSessionStorage('test-step-v', 1)
73
+ expect(internal<number>(step)._v).toBe(1)
74
+ step.set(3)
75
+ expect(internal<number>(step)._v).toBe(3)
76
+ })
77
+
78
+ it('useCookie signal forwards `_v`', () => {
79
+ const locale = useCookie('test-locale-v', 'en')
80
+ expect(internal<string>(locale)._v).toBe('en')
81
+ locale.set('de')
82
+ expect(internal<string>(locale)._v).toBe('de')
83
+ })
84
+
85
+ it('useMemoryStorage signal forwards `_v`', () => {
86
+ const note = useMemoryStorage('test-note-v', '')
87
+ expect(internal<string>(note)._v).toBe('')
88
+ note.set('hello')
89
+ expect(internal<string>(note)._v).toBe('hello')
90
+ })
91
+
92
+ it('`_v` getter tracks the underlying signal even when the wrapper is held across mutations', () => {
93
+ // The wrapper is captured ONCE; the getter must read the LIVE value
94
+ // from the underlying signal each access. Pre-fix this is undefined
95
+ // forever. Post-fix it tracks the underlying `sig._v` on every read.
96
+ const theme = useStorage('test-live-v', 'a')
97
+ const captured = theme
98
+ expect(internal<string>(captured)._v).toBe('a')
99
+ theme.set('b')
100
+ expect(internal<string>(captured)._v).toBe('b')
101
+ theme.set('c')
102
+ expect(internal<string>(captured)._v).toBe('c')
103
+ })
104
+ })
@@ -0,0 +1,88 @@
1
+ import type { signal } from '@pyreon/reactivity'
2
+
3
+ /**
4
+ * Wrap a base `signal()` from `@pyreon/reactivity` with a callable that
5
+ * fully participates in Pyreon's reactivity, including the compiler-
6
+ * emitted DOM-binding fast paths (`_bindText` / `_bindDirect`).
7
+ *
8
+ * The wrapper:
9
+ * - Is callable: `wrapper()` returns `sig()` (read + subscribe).
10
+ * - Delegates `.peek` / `.subscribe` / `.direct` / `.debug` to the
11
+ * underlying signal — methods, not state, so re-binding is safe.
12
+ * - Forwards `.label` (getter + setter) to the underlying signal so
13
+ * dev-time naming carries through.
14
+ * - Forwards the internal `_v` field via getter so the compiler's
15
+ * `_bindText(wrapper, textNode)` fast path reads the live value.
16
+ * Without this, the binding writes `String(undefined)` → `''` on
17
+ * initial render AND every subscriber notification (the bug class
18
+ * fixed in PR #546 and now caught by the
19
+ * `pyreon/storage-signal-v-forwarding` lint rule).
20
+ *
21
+ * The wrapper is RETURNED as `signal()` minus the methods callers
22
+ * typically OVERRIDE (`.set`, `.update`, `.remove`, plus any factory-
23
+ * specific extras). Each storage factory layers its persistence
24
+ * behavior on top by assigning these fields to the returned wrapper
25
+ * before returning to the user.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const sig = signal<T>(initialValue)
30
+ * const storageSig = wrapBaseSignal(sig) as StorageSignal<T>
31
+ * storageSig.set = (value: T) => {
32
+ * sig.set(value)
33
+ * localStorage.setItem(key, serialize(value))
34
+ * }
35
+ * storageSig.remove = () => { ... }
36
+ * return storageSig
37
+ * ```
38
+ *
39
+ * **Why this exists**: pre-2026-05-13 the same wrapper shape was
40
+ * duplicated across 4 factories (~30 lines × 4 sites). The duplication
41
+ * structurally enabled the `_v` forwarding bug — only `local.ts:createStorageSignal`
42
+ * was shared between local + session; cookie / custom / indexed-db each had
43
+ * their own factory body. Forgetting `_v` in one site went unnoticed for
44
+ * ~9 months. This helper makes the contract single-source: every backend
45
+ * gets the same wrapper, and field additions to the signal protocol land
46
+ * in one place.
47
+ */
48
+ export function wrapBaseSignal<T>(sig: ReturnType<typeof signal<T>>): {
49
+ (): T
50
+ peek(): T
51
+ subscribe(listener: () => void): () => void
52
+ direct(updater: () => void): () => void
53
+ debug(): unknown
54
+ label: string | undefined
55
+ } {
56
+ type Wrapper = {
57
+ (): T
58
+ peek(): T
59
+ subscribe(listener: () => void): () => void
60
+ direct(updater: () => void): () => void
61
+ debug(): unknown
62
+ label: string | undefined
63
+ }
64
+
65
+ const wrapper = (() => sig()) as unknown as Wrapper
66
+
67
+ wrapper.peek = () => sig.peek()
68
+ wrapper.subscribe = (listener: () => void) => sig.subscribe(listener)
69
+ wrapper.direct = (updater: () => void) => sig.direct(updater)
70
+ wrapper.debug = () => sig.debug()
71
+
72
+ Object.defineProperty(wrapper, 'label', {
73
+ get: () => sig.label,
74
+ set: (v: string | undefined) => {
75
+ sig.label = v
76
+ },
77
+ configurable: true,
78
+ })
79
+
80
+ // Forward `_v` so the compiler-emitted `_bindText(wrapper, textNode)`
81
+ // fast path reads the live value. See file header for context.
82
+ Object.defineProperty(wrapper, '_v', {
83
+ get: () => (sig as unknown as { _v: T })._v,
84
+ configurable: true,
85
+ })
86
+
87
+ return wrapper
88
+ }
package/lib/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/registry.ts","../src/utils.ts","../src/cookie.ts","../src/custom.ts","../src/indexed-db.ts","../src/local.ts","../src/session.ts","../src/clear.ts"],"sourcesContent":["import type { StorageSignal } from './types'\n\n// ─── Signal Registry ─────────────────────────────────────────────────────────\n\ninterface RegistryEntry<T = unknown> {\n signal: StorageSignal<T>\n defaultValue: T\n backend: string\n}\n\nconst registry = new Map<string, RegistryEntry>()\n\n/**\n * Build a composite key from backend type + storage key to avoid\n * collisions between different backends using the same key name.\n */\nfunction registryKey(backend: string, key: string): string {\n return `${backend}:${key}`\n}\n\n/**\n * Get an existing signal from the registry.\n */\nexport function getEntry<T>(backend: string, key: string): RegistryEntry<T> | undefined {\n return registry.get(registryKey(backend, key)) as RegistryEntry<T> | undefined\n}\n\n/**\n * Register a new signal in the registry.\n */\nexport function setEntry<T>(\n backend: string,\n key: string,\n signal: StorageSignal<T>,\n defaultValue: T,\n): void {\n registry.set(registryKey(backend, key), { signal, defaultValue, backend })\n}\n\n/**\n * Remove an entry from the registry.\n */\nexport function removeEntry(backend: string, key: string): void {\n registry.delete(registryKey(backend, key))\n}\n\n/**\n * Get all entries for a specific backend.\n */\nexport function getEntriesByBackend(backend: string): RegistryEntry[] {\n const entries: RegistryEntry[] = []\n for (const entry of registry.values()) {\n if (entry.backend === backend) entries.push(entry)\n }\n return entries\n}\n\n/**\n * Clear all entries from the registry. Used for testing.\n */\nexport function _resetRegistry(): void {\n registry.clear()\n}\n","import type { StorageOptions } from './types'\n\n// ─── SSR Detection ───────────────────────────────────────────────────────────\n\n/**\n * Check if we're running in a browser environment.\n */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined'\n}\n\n// ─── Serialization ───────────────────────────────────────────────────────────\n\n/**\n * Serialize a value to a string for storage.\n */\nexport function serialize<T>(value: T, serializer?: StorageOptions<T>['serializer']): string {\n if (serializer) return serializer(value)\n return JSON.stringify(value)\n}\n\n/**\n * Deserialize a raw string from storage back to a typed value.\n * Returns the default value if deserialization fails.\n */\nexport function deserialize<T>(\n raw: string,\n defaultValue: T,\n deserializer?: StorageOptions<T>['deserializer'],\n onError?: StorageOptions<T>['onError'],\n): T {\n try {\n if (deserializer) return deserializer(raw)\n return JSON.parse(raw) as T\n } catch (e) {\n if (onError) {\n const result = onError(e as Error)\n return result !== undefined ? result : defaultValue\n }\n return defaultValue\n }\n}\n\n// ─── Safe Storage Access ─────────────────────────────────────────────────────\n\n/**\n * Safely get a Web Storage instance (localStorage or sessionStorage).\n * Returns null if not available (SSR, security restrictions, etc.).\n */\nexport function getWebStorage(type: 'local' | 'session'): Storage | null {\n if (!isBrowser()) return null\n try {\n const storage = type === 'local' ? window.localStorage : window.sessionStorage\n // Test that it actually works (can throw in private browsing)\n const testKey = '__pyreon_storage_test__'\n storage.setItem(testKey, '1')\n storage.removeItem(testKey)\n return storage\n } catch {\n return null\n }\n}\n","import { signal } from '@pyreon/reactivity'\nimport { getEntry, removeEntry, setEntry } from './registry'\nimport type { CookieOptions, StorageSignal } from './types'\nimport { deserialize, isBrowser, serialize } from './utils'\n\n// ─── Server-side cookie source ───────────────────────────────────────────────\n\nlet serverCookieString = ''\n\n/**\n * Set the cookie source string for SSR. Call this once per request\n * with the raw Cookie header value.\n *\n * @example\n * ```ts\n * // In your SSR request handler\n * setCookieSource(request.headers.get('cookie') ?? '')\n * ```\n */\nexport function setCookieSource(cookieHeader: string): void {\n serverCookieString = cookieHeader\n}\n\n// ─── Cookie parsing ──────────────────────────────────────────────────────────\n\nfunction parseCookies(cookieString: string): Map<string, string> {\n const cookies = new Map<string, string>()\n if (!cookieString) return cookies\n\n for (const pair of cookieString.split(';')) {\n const eqIndex = pair.indexOf('=')\n if (eqIndex === -1) continue\n const name = pair.slice(0, eqIndex).trim()\n const value = pair.slice(eqIndex + 1).trim()\n if (name) cookies.set(name, decodeURIComponent(value))\n }\n\n return cookies\n}\n\nfunction getCookieString(): string {\n if (isBrowser()) return document.cookie\n return serverCookieString\n}\n\nfunction readCookie(key: string): string | null {\n const cookies = parseCookies(getCookieString())\n return cookies.get(key) ?? null\n}\n\n// ─── Cookie writing ──────────────────────────────────────────────────────────\n\nfunction writeCookie<T>(key: string, value: T, options: CookieOptions<T>): void {\n if (!isBrowser()) return\n\n const serialized = serialize(value, options.serializer)\n let cookie = `${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`\n\n if (options.maxAge !== undefined) {\n cookie += `; max-age=${options.maxAge}`\n }\n if (options.expires) {\n cookie += `; expires=${options.expires.toUTCString()}`\n }\n cookie += `; path=${options.path ?? '/'}`\n if (options.domain) {\n cookie += `; domain=${options.domain}`\n }\n if (options.secure) {\n cookie += '; secure'\n }\n cookie += `; samesite=${options.sameSite ?? 'lax'}`\n\n document.cookie = cookie\n}\n\nfunction deleteCookie<T>(key: string, options: CookieOptions<T>): void {\n if (!isBrowser()) return\n\n let cookie = `${encodeURIComponent(key)}=; max-age=0`\n cookie += `; path=${options.path ?? '/'}`\n if (options.domain) {\n cookie += `; domain=${options.domain}`\n }\n\n document.cookie = cookie\n}\n\n// ─── useCookie ───────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by a browser cookie. SSR-compatible when\n * used with setCookieSource().\n *\n * @example\n * ```ts\n * const locale = useCookie('locale', 'en', {\n * maxAge: 60 * 60 * 24 * 365, // 1 year\n * path: '/',\n * sameSite: 'lax',\n * })\n * locale() // 'en'\n * locale.set('de') // sets cookie + updates signal\n * locale.remove() // deletes cookie, resets to default\n * ```\n */\nexport function useCookie<T>(\n key: string,\n defaultValue: T,\n options: CookieOptions<T> = {},\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>('cookie', key)\n if (existing) return existing.signal\n\n // Read initial value from cookie\n const raw = readCookie(key)\n const initialValue =\n raw !== null\n ? deserialize(raw, defaultValue, options.deserializer, options.onError)\n : defaultValue\n\n const sig = signal<T>(initialValue)\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, 'label', {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n writeCookie(key, value, options)\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n deleteCookie(key, options)\n removeEntry('cookie', key)\n }\n\n setEntry('cookie', key, storageSig, defaultValue)\n\n return storageSig\n}\n","import { signal } from '@pyreon/reactivity'\nimport { getEntry, removeEntry, setEntry } from './registry'\nimport type { StorageBackend, StorageOptions, StorageSignal } from './types'\nimport { deserialize, serialize } from './utils'\n\n// ─── createStorage ───────────────────────────────────────────────────────────\n\n/**\n * Create a custom storage hook backed by any synchronous storage backend.\n * Useful for encrypted storage, in-memory storage, or custom adapters.\n *\n * @example\n * ```ts\n * const useEncrypted = createStorage({\n * get: (key) => decrypt(localStorage.getItem(key)),\n * set: (key, value) => localStorage.setItem(key, encrypt(value)),\n * remove: (key) => localStorage.removeItem(key),\n * })\n *\n * const secret = useEncrypted('api-key', '')\n * ```\n */\nexport function createStorage(\n backend: StorageBackend,\n backendName?: string,\n): <T>(key: string, defaultValue: T, options?: StorageOptions<T>) => StorageSignal<T> {\n const name = backendName ?? 'custom'\n\n return function useCustomStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n ): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>(name, key)\n if (existing) return existing.signal\n\n // Read initial value\n let initialValue = defaultValue\n try {\n const raw = backend.get(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n } catch {\n // Backend read failed — use default\n }\n\n const sig = signal<T>(initialValue)\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, 'label', {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n try {\n backend.set(key, serialize(value, options?.serializer))\n } catch {\n // Write failed — signal still updates\n }\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n try {\n backend.remove(key)\n } catch {\n // Remove failed\n }\n removeEntry(name, key)\n }\n\n setEntry(name, key, storageSig, defaultValue)\n\n return storageSig\n }\n}\n\n// ─── Memory storage ──────────────────────────────────────────────────────────\n\n/**\n * In-memory storage backend. Useful for SSR, testing, or ephemeral state.\n * Values are lost on page unload.\n *\n * @example\n * ```ts\n * import { useMemoryStorage } from '@pyreon/storage'\n *\n * const temp = useMemoryStorage('key', 'default')\n * ```\n */\nexport const useMemoryStorage = createStorage(\n (() => {\n const store = new Map<string, string>()\n return {\n get: (key: string) => store.get(key) ?? null,\n set: (key: string, value: string) => store.set(key, value),\n remove: (key: string) => store.delete(key),\n }\n })(),\n 'memory',\n)\n","import { signal } from '@pyreon/reactivity'\nimport { getEntry, removeEntry, setEntry } from './registry'\nimport type { IndexedDBOptions, StorageSignal } from './types'\nimport { deserialize, isBrowser, serialize } from './utils'\n\n// @ts-ignore — import.meta.env.DEV is Vite/Rolldown literal-replaced at build time\nconst __DEV__: boolean = import.meta.env?.DEV === true\n\n// ─── Database management ─────────────────────────────────────────────────────\n\nconst dbCache = new Map<string, Promise<IDBDatabase>>()\n\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n if (typeof indexedDB === 'undefined') {\n return Promise.reject(new Error('[Pyreon] indexedDB is not available in this environment'))\n }\n const cacheKey = `${dbName}:${storeName}`\n const cached = dbCache.get(cacheKey)\n if (cached) return cached\n\n const promise = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(dbName, 1)\n\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName)\n }\n }\n\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n\n dbCache.set(cacheKey, promise)\n return promise\n}\n\nfunction idbGet(db: IDBDatabase, storeName: string, key: string): Promise<string | null> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, 'readonly')\n const store = tx.objectStore(storeName)\n const request = store.get(key)\n request.onsuccess = () =>\n resolve(request.result !== undefined ? (request.result as string) : null)\n request.onerror = () => reject(request.error)\n })\n}\n\nfunction idbSet(db: IDBDatabase, storeName: string, key: string, value: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, 'readwrite')\n const store = tx.objectStore(storeName)\n const request = store.put(value, key)\n request.onsuccess = () => resolve()\n request.onerror = () => reject(request.error)\n })\n}\n\nfunction idbDelete(db: IDBDatabase, storeName: string, key: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, 'readwrite')\n const store = tx.objectStore(storeName)\n const request = store.delete(key)\n request.onsuccess = () => resolve()\n request.onerror = () => reject(request.error)\n })\n}\n\n// ─── useIndexedDB ────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by IndexedDB. Suitable for large or structured\n * data that exceeds localStorage limits. Writes are debounced.\n *\n * The signal starts with `defaultValue` and updates asynchronously\n * when the stored value is read from IndexedDB.\n *\n * @example\n * ```ts\n * const draft = useIndexedDB('article-draft', { title: '', body: '' })\n * draft() // { title: '', body: '' } initially, then stored value\n * draft.set({ title: 'My Post', body: '...' }) // signal updates immediately, IDB write is debounced\n * ```\n */\nexport function useIndexedDB<T>(\n key: string,\n defaultValue: T,\n options: IndexedDBOptions<T> = {},\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>('indexeddb', key)\n if (existing) return existing.signal\n\n const dbName = options.dbName ?? 'pyreon-storage'\n const storeName = options.storeName ?? 'kv'\n const debounceMs = options.debounceMs ?? 100\n\n const sig = signal<T>(defaultValue)\n\n // Async initial load\n if (isBrowser() && typeof indexedDB !== 'undefined') {\n openDB(dbName, storeName)\n .then((db) => idbGet(db, storeName, key))\n .then((raw) => {\n if (raw !== null) {\n const value = deserialize(raw, defaultValue, options.deserializer, options.onError)\n sig.set(value)\n }\n })\n .catch((err) => {\n if (__DEV__) {\n // oxlint-disable-next-line no-console\n console.warn(`[Pyreon] IndexedDB \"${key}\" init failed, using default:`, err)\n }\n options.onError?.(err instanceof Error ? err : new Error(String(err)))\n })\n }\n\n // Debounced write\n let writeTimer: ReturnType<typeof setTimeout> | null = null\n let pendingValue: T | undefined\n\n function flushWrite(): void {\n if (pendingValue === undefined) return\n const value = pendingValue\n pendingValue = undefined\n\n if (!isBrowser() || typeof indexedDB === 'undefined') return\n\n openDB(dbName, storeName)\n .then((db) => idbSet(db, storeName, key, serialize(value, options.serializer)))\n .catch(() => {\n // Write failed — signal still has the correct value\n })\n }\n\n function scheduleWrite(value: T): void {\n pendingValue = value\n if (writeTimer !== null) clearTimeout(writeTimer)\n writeTimer = setTimeout(flushWrite, debounceMs)\n }\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, 'label', {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n scheduleWrite(value)\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n pendingValue = undefined\n if (writeTimer !== null) clearTimeout(writeTimer)\n\n if (isBrowser() && typeof indexedDB !== 'undefined') {\n openDB(dbName, storeName)\n .then((db) => idbDelete(db, storeName, key))\n .catch(() => {\n // Delete failed — signal already reset\n })\n }\n\n removeEntry('indexeddb', key)\n }\n\n setEntry('indexeddb', key, storageSig, defaultValue)\n\n return storageSig\n}\n\n/**\n * Reset the database cache. For testing only.\n */\nexport function _resetDBCache(): void {\n dbCache.clear()\n}\n","import { signal } from '@pyreon/reactivity'\nimport { getEntry, removeEntry, setEntry } from './registry'\nimport type { StorageOptions, StorageSignal } from './types'\nimport { deserialize, getWebStorage, isBrowser, serialize } from './utils'\n\n// ─── Cross-tab sync ──────────────────────────────────────────────────────────\n\n// Refcount the active localStorage signals so we can detach the `storage`\n// event listener when nothing subscribes anymore. Before the refcount, the\n// listener was attached on first `useStorage` and NEVER removed, leaking a\n// window-level handler across the lifetime of the page even after all\n// signals disposed via `.remove()`.\nlet activeCount = 0\nlet storageHandler: ((e: StorageEvent) => void) | null = null\n\nfunction onStorageEvent(e: StorageEvent): void {\n if (!e.key) return\n const entry = getEntry('local', e.key)\n if (!entry) return\n\n const newValue =\n e.newValue !== null ? deserialize(e.newValue, entry.defaultValue) : entry.defaultValue\n\n entry.signal.set(newValue)\n}\n\nfunction retainStorageListener(): void {\n if (!isBrowser()) return\n activeCount++\n if (storageHandler === null) {\n storageHandler = onStorageEvent\n window.addEventListener('storage', storageHandler)\n }\n}\n\n/**\n * Test-only: force-detach the cross-tab listener and reset the refcount.\n * Used in test teardown to keep `_resetRegistry` and listener state in sync.\n */\nexport function _resetStorageListener(): void {\n if (storageHandler !== null && isBrowser()) {\n window.removeEventListener('storage', storageHandler)\n }\n storageHandler = null\n activeCount = 0\n}\n\n/**\n * Release one refcount on the cross-tab listener. Detaches the window-level\n * handler when the count drops to zero. Called from `.remove()`.\n */\nexport function releaseStorageListener(): void {\n if (!isBrowser()) return\n if (activeCount === 0) return\n activeCount--\n if (activeCount === 0 && storageHandler !== null) {\n window.removeEventListener('storage', storageHandler)\n storageHandler = null\n }\n}\n\n// ─── useStorage ──────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by localStorage. Automatically syncs across\n * browser tabs via the native `storage` event.\n *\n * @example\n * ```ts\n * const theme = useStorage('theme', 'light')\n * theme() // 'light' (or stored value)\n * theme.set('dark') // updates signal + localStorage\n * theme.remove() // clears storage, resets to default\n * ```\n */\nexport function useStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>('local', key)\n if (existing) return existing.signal\n\n const storage = getWebStorage('local')\n\n // Read initial value from storage\n let initialValue = defaultValue\n if (storage) {\n const raw = storage.getItem(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n }\n\n const sig = signal<T>(initialValue)\n\n // Create the storage signal by extending the base signal\n const storageSig = createStorageSignal(sig, key, defaultValue, 'local', options)\n\n setEntry('local', key, storageSig, defaultValue)\n retainStorageListener()\n\n return storageSig\n}\n\n// ─── Storage Signal Factory ──────────────────────────────────────────────────\n\n/**\n * Wraps a base signal with storage persistence behavior.\n * Used by both useStorage and useSessionStorage.\n */\nexport function createStorageSignal<T>(\n sig: ReturnType<typeof signal<T>>,\n key: string,\n defaultValue: T,\n backend: 'local' | 'session',\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n const storage = getWebStorage(backend)\n\n // The callable signal function (read)\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n // Delegate all signal methods\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, 'label', {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n // Override set to persist\n storageSig.set = (value: T) => {\n sig.set(value)\n if (storage) {\n try {\n storage.setItem(key, serialize(value, options?.serializer))\n } catch {\n // Storage full or blocked — signal still updates\n }\n }\n }\n\n // Override update to persist\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n // Add remove method\n storageSig.remove = () => {\n sig.set(defaultValue)\n if (storage) {\n storage.removeItem(key)\n }\n removeEntry(backend, key)\n if (backend === 'local') {\n releaseStorageListener()\n }\n }\n\n return storageSig\n}\n","import { signal } from '@pyreon/reactivity'\nimport { createStorageSignal } from './local'\nimport { getEntry, setEntry } from './registry'\nimport type { StorageOptions, StorageSignal } from './types'\nimport { deserialize, getWebStorage } from './utils'\n\n// ─── useSessionStorage ───────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by sessionStorage. Scoped to the current\n * browser tab — does not sync across tabs.\n *\n * @example\n * ```ts\n * const step = useSessionStorage('wizard-step', 0)\n * step() // 0 (or stored value)\n * step.set(3) // updates signal + sessionStorage\n * step.remove() // clears storage, resets to default\n * ```\n */\nexport function useSessionStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>('session', key)\n if (existing) return existing.signal\n\n const storage = getWebStorage('session')\n\n // Read initial value from storage\n let initialValue = defaultValue\n if (storage) {\n const raw = storage.getItem(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n }\n\n const sig = signal<T>(initialValue)\n const storageSig = createStorageSignal(sig, key, defaultValue, 'session', options)\n\n setEntry('session', key, storageSig, defaultValue)\n\n return storageSig\n}\n","import { getEntriesByBackend, getEntry, removeEntry } from './registry'\nimport { getWebStorage, isBrowser } from './utils'\n\n// ─── Storage type mapping ────────────────────────────────────────────────────\n\ntype StorageType = 'local' | 'session' | 'cookie' | 'indexeddb' | 'all'\n\n// ─── removeStorage ───────────────────────────────────────────────────────────\n\n/**\n * Remove a specific key from storage and reset its signal to the default value.\n *\n * @example\n * ```ts\n * removeStorage('theme') // from localStorage\n * removeStorage('step', { type: 'session' }) // from sessionStorage\n * removeStorage('locale', { type: 'cookie' }) // deletes cookie\n * ```\n */\nexport function removeStorage(\n key: string,\n options?: { type?: 'local' | 'session' | 'cookie' | 'indexeddb' },\n): void {\n const type = options?.type ?? 'local'\n const entry = getEntry(type, key)\n\n if (entry) {\n entry.signal.remove()\n } else {\n // No signal registered — still try to clear the raw storage\n if (type === 'local' || type === 'session') {\n const storage = getWebStorage(type)\n if (storage) storage.removeItem(key)\n } else if (type === 'cookie' && isBrowser()) {\n document.cookie = `${encodeURIComponent(key)}=; max-age=0; path=/`\n }\n removeEntry(type, key)\n }\n}\n\n// ─── clearStorage ────────────────────────────────────────────────────────────\n\n/**\n * Clear all managed storage entries for a specific backend, or all backends.\n *\n * @example\n * ```ts\n * clearStorage() // clear all localStorage entries managed by @pyreon/storage\n * clearStorage('session') // clear all sessionStorage entries\n * clearStorage('cookie') // clear all managed cookies\n * clearStorage('all') // clear everything\n * ```\n */\nexport function clearStorage(type: StorageType = 'local'): void {\n if (type === 'all') {\n clearBackend('local')\n clearBackend('session')\n clearBackend('cookie')\n clearBackend('indexeddb')\n return\n }\n\n clearBackend(type)\n}\n\nfunction clearBackend(type: string): void {\n const entries = getEntriesByBackend(type)\n for (const entry of entries) {\n entry.signal.remove()\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/cookie.ts","../../../src/custom.ts","../../../src/indexed-db.ts","../../../src/local.ts","../../../src/session.ts","../../../src/clear.ts","../../../src/registry.ts"],"mappings":";;;;;AAQA;;UAAiB,aAAA,YAAyB,MAAA,CAAO,CAAA;EAAD;EAE9C,MAAA;AAAA;;;;UAQe,cAAA;EAAA;EAEf,UAAA,IAAc,KAAA,EAAO,CAAA;EAFQ;EAI7B,YAAA,IAAgB,GAAA,aAAgB,CAAA;EAAA;EAEhC,OAAA,IAAW,KAAA,EAAO,KAAA,KAAU,CAAA;AAAA;;;;UAQb,aAAA,YAAyB,cAAA,CAAe,CAAA;EAZlC;EAcrB,MAAA;EAZA;EAcA,OAAA,GAAU,IAAA;EAdsB;EAgBhC,IAAA;EAdkB;EAgBlB,MAAA;EAhB4B;EAkB5B,MAAA;EAlB6B;EAoB7B,QAAA;AAAA;;;;UAQe,gBAAA,YAA4B,cAAA,CAAe,CAAA;EApBJ;EAsBtD,MAAA;EAtB6B;EAwB7B,SAAA;EAxBuD;EA0BvD,UAAA;AAAA;;;;UAQe,cAAA;EAtBf;EAwBA,GAAA,CAAI,GAAA;EAxBI;EA0BR,GAAA,CAAI,GAAA,UAAa,KAAA;EAlBc;EAoB/B,MAAA,CAAO,GAAA;AAAA;;;;UAMQ,mBAAA;EAtBf;EAwBA,GAAA,CAAI,GAAA,WAAc,OAAA;EAtBR;EAwBV,GAAA,CAAI,GAAA,UAAa,KAAA,WAAgB,OAAA;EAhBlB;EAkBf,MAAA,CAAO,GAAA,WAAc,OAAA;AAAA;;;;;AA5EvB;;;;;;;;iBCWgB,eAAA,CAAgB,YAAA;;ADDhC;;;;;;;;;;;;;;;iBCwFgB,SAAA,GAAA,CACd,GAAA,UACA,YAAA,EAAc,CAAA,EACd,OAAA,GAAS,aAAA,CAAc,CAAA,IACtB,aAAA,CAAc,CAAA;;;;;ADtGjB;;;;;;;;;;AAUA;;;iBEIgB,aAAA,CACd,OAAA,EAAS,cAAA,EACT,WAAA,gBACK,GAAA,UAAa,YAAA,EAAc,CAAA,EAAG,OAAA,GAAU,cAAA,CAAe,CAAA,MAAO,aAAA,CAAc,CAAA;;;;;;;;;;;;cAmFtE,gBAAA,MAnFR,GAAA,UAAa,YAAA,EAAgB,CAAA,EAAC,OAAA,GAAY,cAAA,CAAe,CAAA,MAAO,aAAA,CAAc,CAAA;;;;;AFjBnF;;;;;;;;;;AAUA;;iBGmEgB,YAAA,GAAA,CACd,GAAA,UACA,YAAA,EAAc,CAAA,EACd,OAAA,GAAS,gBAAA,CAAiB,CAAA,IACzB,aAAA,CAAc,CAAA;;;;iBAuGD,aAAA,CAAA;;;;AHxLhB;;;iBI+BgB,qBAAA,CAAA;;AJrBhB;;;;;;;;;;;iBIyDgB,UAAA,GAAA,CACd,GAAA,UACA,YAAA,EAAc,CAAA,EACd,OAAA,GAAU,cAAA,CAAe,CAAA,IACxB,aAAA,CAAc,CAAA;;;;;AJvEjB;;;;;;;;;;iBKYgB,iBAAA,GAAA,CACd,GAAA,UACA,YAAA,EAAc,CAAA,EACd,OAAA,GAAU,cAAA,CAAe,CAAA,IACxB,aAAA,CAAc,CAAA;;;KCnBZ,WAAA;;;ANGL;;;;;;;;iBMWgB,aAAA,CACd,GAAA,UACA,OAAA;EAAY,IAAA;AAAA;;;;;;;;;;;;iBAgCE,YAAA,CAAa,IAAA,GAAM,WAAA;;;;;;iBCOnB,cAAA,CAAA"}