@storve/core 1.0.1 → 1.0.3

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.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +993 -26
  3. package/dist/adapters/indexedDB.cjs +0 -1
  4. package/dist/adapters/indexedDB.mjs +0 -1
  5. package/dist/adapters/localStorage.cjs +0 -1
  6. package/dist/adapters/localStorage.mjs +0 -1
  7. package/dist/adapters/memory.cjs +0 -1
  8. package/dist/adapters/memory.mjs +0 -1
  9. package/dist/adapters/sessionStorage.cjs +0 -1
  10. package/dist/adapters/sessionStorage.mjs +0 -1
  11. package/dist/async-entry.d.ts +0 -1
  12. package/dist/async.cjs +0 -1
  13. package/dist/async.d.ts +0 -1
  14. package/dist/async.mjs +0 -1
  15. package/dist/batch.d.ts +0 -1
  16. package/dist/compose.d.ts +0 -1
  17. package/dist/computed-entry.d.ts +0 -1
  18. package/dist/computed.cjs +0 -1
  19. package/dist/computed.d.ts +0 -1
  20. package/dist/computed.mjs +0 -1
  21. package/dist/devtools/history.d.ts +0 -1
  22. package/dist/devtools/index.d.ts +0 -1
  23. package/dist/devtools/redux-bridge.d.ts +0 -1
  24. package/dist/devtools/snapshots.d.ts +0 -1
  25. package/dist/devtools/withDevtools.d.ts +0 -1
  26. package/dist/devtools.cjs +0 -1
  27. package/dist/devtools.mjs +0 -1
  28. package/dist/extensions/noop.d.ts +0 -1
  29. package/dist/index.cjs +0 -1
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.mjs +0 -1
  32. package/dist/persist/adapters/indexedDB.d.ts +0 -1
  33. package/dist/persist/adapters/localStorage.d.ts +0 -1
  34. package/dist/persist/adapters/memory.d.ts +0 -1
  35. package/dist/persist/adapters/sessionStorage.d.ts +0 -1
  36. package/dist/persist/debounce.d.ts +0 -1
  37. package/dist/persist/hydrate.d.ts +0 -1
  38. package/dist/persist/index.d.ts +0 -1
  39. package/dist/persist/serialize.d.ts +0 -1
  40. package/dist/persist.cjs +0 -1
  41. package/dist/persist.mjs +0 -1
  42. package/dist/proxy.d.ts +0 -1
  43. package/dist/registry-qtr1UpFU.js +0 -1
  44. package/dist/registry-zaKZ1P-s.js +0 -1
  45. package/dist/registry.d.ts +0 -1
  46. package/dist/signals/createSignal.d.ts +0 -1
  47. package/dist/signals/index.d.ts +0 -1
  48. package/dist/signals/useSignal.d.ts +0 -1
  49. package/dist/signals.cjs +0 -1
  50. package/dist/signals.mjs +0 -1
  51. package/dist/store.d.ts +0 -1
  52. package/dist/sync/channel.d.ts +0 -1
  53. package/dist/sync/index.d.ts +0 -1
  54. package/dist/sync/protocol.d.ts +0 -1
  55. package/dist/sync/withSync.d.ts +0 -1
  56. package/dist/sync.cjs +0 -1
  57. package/dist/sync.mjs +0 -1
  58. package/dist/types.d.ts +0 -1
  59. package/package.json +9 -3
  60. package/CHANGELOG.md +0 -151
  61. package/benchmarks/run.ts +0 -102
  62. package/benchmarks/week2.md +0 -9
  63. package/benchmarks/week2.ts +0 -64
  64. package/benchmarks/week4.md +0 -13
  65. package/benchmarks/week4.ts +0 -178
  66. package/benchmarks/week5.md +0 -15
  67. package/benchmarks/week5.ts +0 -184
  68. package/coverage/coverage-summary.json +0 -31
  69. package/dist/adapters/indexedDB.cjs.map +0 -1
  70. package/dist/adapters/indexedDB.mjs.map +0 -1
  71. package/dist/adapters/localStorage.cjs.map +0 -1
  72. package/dist/adapters/localStorage.mjs.map +0 -1
  73. package/dist/adapters/memory.cjs.map +0 -1
  74. package/dist/adapters/memory.mjs.map +0 -1
  75. package/dist/adapters/sessionStorage.cjs.map +0 -1
  76. package/dist/adapters/sessionStorage.mjs.map +0 -1
  77. package/dist/async-entry.d.ts.map +0 -1
  78. package/dist/async.cjs.map +0 -1
  79. package/dist/async.d.ts.map +0 -1
  80. package/dist/async.mjs.map +0 -1
  81. package/dist/batch.d.ts.map +0 -1
  82. package/dist/compose.d.ts.map +0 -1
  83. package/dist/computed-entry.d.ts.map +0 -1
  84. package/dist/computed.cjs.map +0 -1
  85. package/dist/computed.d.ts.map +0 -1
  86. package/dist/computed.mjs.map +0 -1
  87. package/dist/devtools/history.d.ts.map +0 -1
  88. package/dist/devtools/index.d.ts.map +0 -1
  89. package/dist/devtools/redux-bridge.d.ts.map +0 -1
  90. package/dist/devtools/snapshots.d.ts.map +0 -1
  91. package/dist/devtools/withDevtools.d.ts.map +0 -1
  92. package/dist/devtools.cjs.map +0 -1
  93. package/dist/devtools.mjs.map +0 -1
  94. package/dist/extensions/noop.d.ts.map +0 -1
  95. package/dist/index.cjs.js +0 -118
  96. package/dist/index.cjs.js.map +0 -1
  97. package/dist/index.cjs.map +0 -1
  98. package/dist/index.d.ts.map +0 -1
  99. package/dist/index.esm.js +0 -116
  100. package/dist/index.esm.js.map +0 -1
  101. package/dist/index.mjs.map +0 -1
  102. package/dist/persist/adapters/indexedDB.d.ts.map +0 -1
  103. package/dist/persist/adapters/localStorage.d.ts.map +0 -1
  104. package/dist/persist/adapters/memory.d.ts.map +0 -1
  105. package/dist/persist/adapters/sessionStorage.d.ts.map +0 -1
  106. package/dist/persist/debounce.d.ts.map +0 -1
  107. package/dist/persist/hydrate.d.ts.map +0 -1
  108. package/dist/persist/index.d.ts.map +0 -1
  109. package/dist/persist/serialize.d.ts.map +0 -1
  110. package/dist/persist.cjs.map +0 -1
  111. package/dist/persist.mjs.map +0 -1
  112. package/dist/proxy.d.ts.map +0 -1
  113. package/dist/registry-D3X0HSbl.js +0 -26
  114. package/dist/registry-D3X0HSbl.js.map +0 -1
  115. package/dist/registry-RDjbeJdx.js +0 -29
  116. package/dist/registry-RDjbeJdx.js.map +0 -1
  117. package/dist/registry-qtr1UpFU.js.map +0 -1
  118. package/dist/registry-zaKZ1P-s.js.map +0 -1
  119. package/dist/registry.d.ts.map +0 -1
  120. package/dist/signals/createSignal.d.ts.map +0 -1
  121. package/dist/signals/index.d.ts.map +0 -1
  122. package/dist/signals/useSignal.d.ts.map +0 -1
  123. package/dist/signals.cjs.map +0 -1
  124. package/dist/signals.mjs.map +0 -1
  125. package/dist/stats.html +0 -4949
  126. package/dist/store.d.ts.map +0 -1
  127. package/dist/sync/channel.d.ts.map +0 -1
  128. package/dist/sync/index.d.ts.map +0 -1
  129. package/dist/sync/protocol.d.ts.map +0 -1
  130. package/dist/sync/withSync.d.ts.map +0 -1
  131. package/dist/sync.cjs.map +0 -1
  132. package/dist/sync.mjs.map +0 -1
  133. package/dist/types.d.ts.map +0 -1
  134. package/rollup.config.mjs +0 -44
  135. package/src/async-entry.ts +0 -6
  136. package/src/async.ts +0 -240
  137. package/src/batch.ts +0 -33
  138. package/src/compose.ts +0 -50
  139. package/src/computed-entry.ts +0 -6
  140. package/src/computed.ts +0 -187
  141. package/src/devtools/history.ts +0 -103
  142. package/src/devtools/index.ts +0 -5
  143. package/src/devtools/redux-bridge.ts +0 -70
  144. package/src/devtools/snapshots.ts +0 -54
  145. package/src/devtools/withDevtools.ts +0 -196
  146. package/src/extensions/noop.ts +0 -12
  147. package/src/index.ts +0 -4
  148. package/src/persist/adapters/indexedDB.ts +0 -114
  149. package/src/persist/adapters/localStorage.ts +0 -28
  150. package/src/persist/adapters/memory.ts +0 -26
  151. package/src/persist/adapters/sessionStorage.ts +0 -28
  152. package/src/persist/debounce.ts +0 -28
  153. package/src/persist/hydrate.ts +0 -60
  154. package/src/persist/index.ts +0 -141
  155. package/src/persist/serialize.ts +0 -60
  156. package/src/proxy.ts +0 -87
  157. package/src/registry.ts +0 -67
  158. package/src/signals/createSignal.ts +0 -81
  159. package/src/signals/index.ts +0 -20
  160. package/src/signals/useSignal.ts +0 -18
  161. package/src/store.ts +0 -250
  162. package/src/sync/channel.ts +0 -15
  163. package/src/sync/index.ts +0 -3
  164. package/src/sync/protocol.ts +0 -18
  165. package/src/sync/withSync.ts +0 -147
  166. package/src/types.ts +0 -159
  167. package/tests/async.test.ts +0 -1100
  168. package/tests/batch.test.ts +0 -41
  169. package/tests/compose.test.ts +0 -209
  170. package/tests/computed.test.ts +0 -867
  171. package/tests/devtools.test.ts +0 -1039
  172. package/tests/integration/persist.integration.test.ts +0 -258
  173. package/tests/integration/signals.integration.test.ts +0 -309
  174. package/tests/integration.test.ts +0 -278
  175. package/tests/persist/adapters/indexedDB.adapter.test.ts +0 -185
  176. package/tests/persist/adapters/localStorage.adapter.test.ts +0 -105
  177. package/tests/persist/adapters/memory.adapter.test.ts +0 -112
  178. package/tests/persist/adapters/sessionStorage.adapter.test.ts +0 -128
  179. package/tests/persist/debounce.test.ts +0 -121
  180. package/tests/persist/hydrate.test.ts +0 -120
  181. package/tests/persist/migrate.test.ts +0 -208
  182. package/tests/persist/persist.test.ts +0 -357
  183. package/tests/persist/serialize.test.ts +0 -128
  184. package/tests/proxy.test.ts +0 -473
  185. package/tests/registry.test.ts +0 -67
  186. package/tests/signals/derived.test.ts +0 -244
  187. package/tests/signals/inference.test.ts +0 -108
  188. package/tests/signals/signal.test.ts +0 -348
  189. package/tests/signals/useSignal.test.tsx +0 -275
  190. package/tests/store.test.ts +0 -482
  191. package/tests/stress.test.ts +0 -268
  192. package/tests/sync.test.ts +0 -576
  193. package/tests/types.test.ts +0 -32
  194. package/tests/v0.3.test.ts +0 -813
  195. package/tree-shake-test.js +0 -1
  196. package/tsconfig.json +0 -15
  197. package/vitest.config.ts +0 -22
  198. package/vitest_play.ts +0 -7
@@ -1,357 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
- import { createStore } from '../../src/store'
3
- import { withPersist } from '../../src/persist/index'
4
- import { memoryAdapter } from '../../src/persist/adapters/memory'
5
-
6
- describe('withPersist', () => {
7
- beforeEach(() => {
8
- vi.useFakeTimers({ now: Date.now() })
9
- })
10
-
11
- afterEach(() => {
12
- vi.useRealTimers()
13
- })
14
-
15
- describe('Direct form', () => {
16
- it('withPersist(store, options) returns a store with same getState/setState/subscribe', () => {
17
- const store = createStore({ count: 0 })
18
- const adapter = memoryAdapter()
19
- const enhanced = withPersist(store, { key: 'test', adapter })
20
-
21
- expect(typeof enhanced.getState).toBe('function')
22
- expect(typeof enhanced.setState).toBe('function')
23
- expect(typeof enhanced.subscribe).toBe('function')
24
- })
25
-
26
- it('enhanced store has a hydrated property that is a Promise', () => {
27
- const store = createStore({ count: 0 })
28
- const adapter = memoryAdapter()
29
- const enhanced = withPersist(store, { key: 'test', adapter })
30
-
31
- expect(enhanced.hydrated).toBeInstanceOf(Promise)
32
- })
33
- })
34
-
35
- describe('Curried form', () => {
36
- it('withPersist(options) returns a function', () => {
37
- const adapter = memoryAdapter()
38
- const enhancer = withPersist({ key: 'test', adapter })
39
- expect(typeof enhancer).toBe('function')
40
- })
41
-
42
- it('that function accepts a store and returns an enhanced store', () => {
43
- const store = createStore({ count: 0 })
44
- const adapter = memoryAdapter()
45
- const enhancer = withPersist({ key: 'test', adapter })
46
- const enhanced = enhancer(store)
47
-
48
- expect(enhanced.getState()).toEqual({ count: 0 })
49
- expect(enhanced.hydrated).toBeInstanceOf(Promise)
50
- })
51
-
52
- it('both forms produce identical behaviour', async () => {
53
- const adapter1 = memoryAdapter()
54
- const adapter2 = memoryAdapter()
55
-
56
- const store1 = withPersist(createStore({ a: 1 }), { key: 'x', adapter: adapter1 })
57
- const store2 = withPersist({ key: 'x', adapter: adapter2 })(createStore({ a: 1 }))
58
-
59
- await store1.hydrated
60
- await store2.hydrated
61
-
62
- expect(store1.getState()).toEqual({ a: 1 })
63
- expect(store2.getState()).toEqual({ a: 1 })
64
- })
65
- })
66
-
67
- describe('Hydration', () => {
68
- it('store is hydrated from adapter on init', async () => {
69
- const adapter = memoryAdapter()
70
- // manually seed the adapter
71
- await adapter.setItem('test', JSON.stringify({ count: 42, __version: 1 }))
72
-
73
- const store = createStore({ count: 0 })
74
- const enhanced = withPersist(store, { key: 'test', adapter })
75
-
76
- await enhanced.hydrated
77
- expect(enhanced.getState()).toEqual({ count: 42 })
78
- })
79
-
80
- it('hydrated Promise resolves after hydration completes', async () => {
81
- const adapter = memoryAdapter()
82
- const store = createStore({ count: 0 })
83
- const enhanced = withPersist(store, { key: 'test', adapter })
84
-
85
- let resolved = false
86
- enhanced.hydrated.then(() => { resolved = true })
87
-
88
- // Since it's an async operation, it isn't resolved synchronously
89
- // but will be resolved in the microtask queue
90
- await Promise.resolve()
91
- // the adapter methods return promises, so give it a tick
92
- for (let i = 0; i < 5; i++) await Promise.resolve()
93
-
94
- expect(resolved).toBe(true)
95
- })
96
-
97
- it('store state reflects persisted values after hydration', async () => {
98
- const adapter = memoryAdapter()
99
- await adapter.setItem('test', JSON.stringify({ name: 'Alice', __version: 1 }))
100
-
101
- const store = createStore({ name: 'Bob', age: 30 })
102
- const enhanced = withPersist(store, { key: 'test', adapter })
103
-
104
- await enhanced.hydrated
105
- // only name is hydrated, age is kept
106
- expect(enhanced.getState()).toEqual({ name: 'Alice', age: 30 })
107
- })
108
-
109
- it('store state is not overwritten if adapter has no data', async () => {
110
- const adapter = memoryAdapter() // empty
111
- const store = createStore({ count: 5 })
112
- const enhanced = withPersist(store, { key: 'test', adapter })
113
-
114
- await enhanced.hydrated
115
- expect(enhanced.getState()).toEqual({ count: 5 })
116
- })
117
-
118
- it('hydration merges into existing state — does not replace unrelated keys', async () => {
119
- const adapter = memoryAdapter()
120
- await adapter.setItem('test', JSON.stringify({ a: 10, __version: 1 }))
121
-
122
- const store = createStore({ a: 1, b: 2 })
123
- const enhanced = withPersist(store, { key: 'test', adapter })
124
-
125
- await enhanced.hydrated
126
- expect(enhanced.getState()).toEqual({ a: 10, b: 2 })
127
- })
128
- })
129
-
130
- describe('Writing on setState', () => {
131
- it('calling setState causes persisted keys to be written to adapter', async () => {
132
- const adapter = memoryAdapter()
133
- const store = withPersist(createStore({ count: 0 }), { key: 'test', adapter, debounce: 0 })
134
- await store.hydrated
135
-
136
- store.setState({ count: 5 })
137
- const value = await adapter.getItem('test')
138
- expect(value).toContain('"count":5')
139
- })
140
-
141
- it('written value is valid JSON containing the persisted keys', async () => {
142
- const adapter = memoryAdapter()
143
- const store = withPersist(createStore({ count: 0 }), { key: 'test', adapter, debounce: 0 })
144
- await store.hydrated
145
-
146
- store.setState({ count: 42 })
147
- const raw = await adapter.getItem('test')
148
- const parsed = JSON.parse(raw!)
149
- expect(parsed.count).toBe(42)
150
- })
151
-
152
- it('written value contains __version field', async () => {
153
- const adapter = memoryAdapter()
154
- const store = withPersist(createStore({ a: 1 }), { key: 'test', adapter, debounce: 0 })
155
- await store.hydrated
156
-
157
- store.setState({ a: 2 })
158
- const raw = await adapter.getItem('test')
159
- const parsed = JSON.parse(raw!)
160
- expect(parsed.__version).toBe(1) // default version is 1
161
- })
162
-
163
- it('unrelated keys (not in pick) are NOT written to adapter', async () => {
164
- const adapter = memoryAdapter()
165
- const store = withPersist(createStore({ a: 1, b: 2 }), { key: 'test', adapter, pick: ['a'], debounce: 0 })
166
- await store.hydrated
167
-
168
- store.setState({ b: 5 })
169
- // This state change triggers write, but b shouldn't be picked
170
- const raw = await adapter.getItem('test')
171
-
172
- // If the write actually happened or not, the result must not contain 'b'
173
- if (raw) {
174
- const parsed = JSON.parse(raw)
175
- expect(parsed.b).toBeUndefined()
176
- expect(parsed.a).toBe(1)
177
- }
178
- })
179
-
180
- it('debounce: write does not fire immediately when debounce > 0', async () => {
181
- const adapter = memoryAdapter()
182
- const store = withPersist(createStore({ count: 0 }), { key: 'test', adapter, debounce: 100 })
183
- await store.hydrated
184
-
185
- store.setState({ count: 1 })
186
- const raw = await adapter.getItem('test')
187
- expect(raw).toBeNull() // not written yet
188
- })
189
-
190
- it('debounce: write fires after debounce delay elapses', async () => {
191
- const adapter = memoryAdapter()
192
- const store = withPersist(createStore({ count: 0 }), { key: 'test', adapter, debounce: 100 })
193
- await store.hydrated
194
-
195
- store.setState({ count: 1 })
196
- vi.advanceTimersByTime(100)
197
-
198
- const raw = await adapter.getItem('test')
199
- expect(raw).toContain('"count":1')
200
- })
201
-
202
- it('debounce: multiple rapid setStates result in only one write', async () => {
203
- const adapter = memoryAdapter()
204
- const spy = vi.spyOn(adapter, 'setItem')
205
- const store = withPersist(createStore({ count: 0 }), { key: 'test', adapter, debounce: 100 })
206
- await store.hydrated
207
-
208
- spy.mockClear()
209
-
210
- store.setState({ count: 1 })
211
- vi.advanceTimersByTime(50)
212
- store.setState({ count: 2 })
213
- vi.advanceTimersByTime(50)
214
- store.setState({ count: 3 })
215
- vi.advanceTimersByTime(100)
216
-
217
- expect(spy).toHaveBeenCalledTimes(1)
218
- const raw = await adapter.getItem('test')
219
- expect(raw).toContain('"count":3')
220
- })
221
-
222
- it('debounce: 0 causes immediate write on every setState', async () => {
223
- const adapter = memoryAdapter()
224
- const spy = vi.spyOn(adapter, 'setItem')
225
- const store = withPersist(createStore({ count: 0 }), { key: 'test', adapter, debounce: 0 })
226
- await store.hydrated
227
-
228
- spy.mockClear()
229
-
230
- store.setState({ count: 1 })
231
- store.setState({ count: 2 })
232
-
233
- expect(spy).toHaveBeenCalledTimes(2)
234
- const raw = await adapter.getItem('test')
235
- expect(raw).toContain('"count":2')
236
- })
237
- })
238
-
239
- describe('pick option', () => {
240
- it('only picked keys are written to adapter', async () => {
241
- const adapter = memoryAdapter()
242
- const store = withPersist(createStore({ a: 1, b: 2, c: 3 }), { key: 'test', adapter, pick: ['a', 'c'], debounce: 0 })
243
- await store.hydrated
244
-
245
- store.setState({ a: 10 })
246
- const raw = await adapter.getItem('test')
247
- const parsed = JSON.parse(raw!)
248
-
249
- expect(parsed.a).toBe(10)
250
- expect(parsed.c).toBe(3)
251
- expect(parsed.b).toBeUndefined()
252
- })
253
-
254
- it('non-picked keys are not present in the serialized value', async () => {
255
- const adapter = memoryAdapter()
256
- const store = withPersist(createStore({ a: 1, secret: 'password' }), { key: 'test', adapter, pick: ['a'], debounce: 0 })
257
- await store.hydrated
258
-
259
- store.setState({ a: 2 })
260
- const raw = await adapter.getItem('test')
261
- expect(raw).not.toContain('password')
262
- expect(raw).not.toContain('secret')
263
- })
264
-
265
- it('if pick is omitted, all keys are persisted', async () => {
266
- const adapter = memoryAdapter()
267
- const store = withPersist(createStore({ a: 1, b: 2 }), { key: 'test', adapter, debounce: 0 }) // pick omitted
268
- await store.hydrated
269
-
270
- store.setState({ a: 2 })
271
- const raw = await adapter.getItem('test')
272
- const parsed = JSON.parse(raw!)
273
- expect(parsed.a).toBe(2)
274
- expect(parsed.b).toBe(2)
275
- })
276
- })
277
-
278
- describe('version option', () => {
279
- it('__version in stored JSON matches the version option', async () => {
280
- const adapter = memoryAdapter()
281
- const store = withPersist(createStore({ a: 1 }), { key: 'test', adapter, version: 5, debounce: 0 })
282
- await store.hydrated
283
-
284
- store.setState({ a: 2 })
285
- const raw = await adapter.getItem('test')
286
- const parsed = JSON.parse(raw!)
287
- expect(parsed.__version).toBe(5)
288
- })
289
-
290
- it('defaults to version 1 when not specified', async () => {
291
- const adapter = memoryAdapter()
292
- const store = withPersist(createStore({ a: 1 }), { key: 'test', adapter, debounce: 0 })
293
- await store.hydrated
294
-
295
- store.setState({ a: 2 })
296
- const raw = await adapter.getItem('test')
297
- const parsed = JSON.parse(raw!)
298
- expect(parsed.__version).toBe(1)
299
- })
300
- })
301
-
302
- describe('migrate option', () => {
303
- it('migrate is called during hydration when stored version differs', async () => {
304
- const adapter = memoryAdapter()
305
- await adapter.setItem('test', JSON.stringify({ oldKey: 'val', __version: 1 }))
306
-
307
- const migrate = vi.fn(() => ({ newKey: 'val' }))
308
- const store = withPersist(createStore({ newKey: '' }), { key: 'test', adapter, version: 2, migrate })
309
-
310
- await store.hydrated
311
- expect(migrate).toHaveBeenCalled()
312
- })
313
-
314
- it('store is populated with migrated values', async () => {
315
- const adapter = memoryAdapter()
316
- await adapter.setItem('test', JSON.stringify({ old: 'val', __version: 1 }))
317
-
318
- const migrate = vi.fn((state: Partial<{ new: string, old?: string }>) => ({ new: state.old }))
319
- const store = withPersist(createStore<{ new: string, old?: string }>({ new: '' }), { key: 'test', adapter, version: 2, migrate })
320
-
321
- await store.hydrated
322
- expect(store.getState()).toEqual({ new: 'val' })
323
- })
324
- })
325
-
326
- describe('Normal store API preserved', () => {
327
- it('subscribe still works correctly after withPersist', async () => {
328
- const adapter = memoryAdapter()
329
- const store = withPersist(createStore({ a: 1 }), { key: 'test', adapter })
330
-
331
- const sub = vi.fn()
332
- store.subscribe(sub)
333
-
334
- store.setState({ a: 2 })
335
- expect(sub).toHaveBeenCalledWith({ a: 2 })
336
- })
337
-
338
- it('setState still notifies subscribers', async () => {
339
- const adapter = memoryAdapter()
340
- const store = withPersist(createStore({ a: 1 }), { key: 'test', adapter })
341
-
342
- const sub = vi.fn()
343
- store.subscribe(sub)
344
-
345
- store.setState(state => ({ a: state.a + 1 }))
346
- expect(sub).toHaveBeenCalledWith({ a: 2 })
347
- })
348
-
349
- it('getState still returns correct state', async () => {
350
- const adapter = memoryAdapter()
351
- const store = withPersist(createStore({ a: 1 }), { key: 'test', adapter })
352
-
353
- store.setState({ a: 5 })
354
- expect(store.getState()).toEqual({ a: 5 })
355
- })
356
- })
357
- })
@@ -1,128 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { pick, toJSON, fromJSON } from '../../src/persist/serialize'
3
-
4
- describe('pick', () => {
5
- it('returns only specified keys from a flat object', () => {
6
- const state = { a: 1, b: 2, c: 3 }
7
- // TypeScript: picked result is typed as Partial<T>
8
- const result: Partial<typeof state> = pick(state, ['a', 'c'])
9
- expect(result).toEqual({ a: 1, c: 3 })
10
- })
11
-
12
- it('returns full state when keys is undefined', () => {
13
- const state = { a: 1, b: 2 }
14
- const result = pick(state)
15
- expect(result).toEqual({ a: 1, b: 2 })
16
- expect(result).not.toBe(state) // still a new object? or same? "Returns a new object"
17
- })
18
-
19
- it('returns full state when keys is empty array', () => {
20
- const state = { a: 1, b: 2 }
21
- const result = pick(state, [])
22
- expect(result).toEqual({ a: 1, b: 2 })
23
- expect(result).not.toBe(state)
24
- })
25
-
26
- it('returns empty object when keys don\'t exist in state', () => {
27
- const state = { a: 1, b: 2 }
28
- // @ts-expect-error testing invalid keys
29
- const result = pick(state, ['z'])
30
- expect(result).toEqual({})
31
- })
32
-
33
- it('does not mutate the original state object', () => {
34
- const state = { a: 1, b: 2 }
35
- const result = pick(state, ['a'])
36
- expect(result).toEqual({ a: 1 })
37
- expect(state).toEqual({ a: 1, b: 2 })
38
- })
39
-
40
- it('works with nested objects (picks the top-level key, not deep keys)', () => {
41
- const state = { nested: { x: 1, y: 2 }, other: 3 }
42
- const result = pick(state, ['nested'])
43
- expect(result).toEqual({ nested: { x: 1, y: 2 } })
44
- // check it picks top level ref
45
- expect(result.nested).toBe(state.nested)
46
- })
47
-
48
- it('works when state has a single key', () => {
49
- const state = { single: 'value' }
50
- const result = pick(state, ['single'])
51
- expect(result).toEqual({ single: 'value' })
52
- })
53
- })
54
-
55
- describe('toJSON', () => {
56
- it('serializes a flat object correctly', () => {
57
- const value = { a: 1, b: 'two', c: true }
58
- expect(toJSON(value)).toBe('{"a":1,"b":"two","c":true}')
59
- })
60
-
61
- it('serializes nested objects correctly', () => {
62
- const value = { a: { b: { c: 1 } } }
63
- expect(toJSON(value)).toBe('{"a":{"b":{"c":1}}}')
64
- })
65
-
66
- it('serializes arrays correctly', () => {
67
- const value = [1, 'two', { three: 3 }]
68
- expect(toJSON(value)).toBe('[1,"two",{"three":3}]')
69
- })
70
-
71
- it('serializes null, numbers, booleans correctly', () => {
72
- expect(toJSON(null)).toBe('null')
73
- expect(toJSON(42)).toBe('42')
74
- expect(toJSON(true)).toBe('true')
75
- })
76
-
77
- it('throws a descriptive error when value contains circular reference', () => {
78
- const circular: Record<string, unknown> = {}
79
- circular.self = circular
80
- expect(() => toJSON(circular)).toThrow(/circular|serialize|JSON/i)
81
- })
82
-
83
- it('throws a descriptive error when value contains a BigInt', () => {
84
- expect(() => toJSON({ num: BigInt(42) })).toThrow(/BigInt|serialize|JSON/i)
85
- })
86
- })
87
-
88
- describe('fromJSON', () => {
89
- it('parses a valid JSON string back to original object', () => {
90
- const raw = '{"a":1,"b":"two","c":true}'
91
- const parsed = fromJSON<{a: number; b: string; c: boolean}>(raw)
92
- expect(parsed).toEqual({ a: 1, b: 'two', c: true })
93
- })
94
-
95
- it('parses nested objects correctly', () => {
96
- const raw = '{"a":{"b":{"c":1}}}'
97
- const parsed = fromJSON<{a: {b: {c: number}}}>(raw)
98
- expect(parsed).toEqual({ a: { b: { c: 1 } } })
99
- })
100
-
101
- it('parses arrays correctly', () => {
102
- const raw = '[1,"two",{"three":3}]'
103
- const parsed = fromJSON<unknown[]>(raw)
104
- expect(parsed).toEqual([1, 'two', { three: 3 }])
105
- })
106
-
107
- it('throws a descriptive error on malformed JSON string', () => {
108
- const raw = '{"a":1' // missing closing brace
109
- expect(() => fromJSON(raw)).toThrow(/JSON|parse/i)
110
- })
111
-
112
- it('throws a descriptive error when raw is null', () => {
113
- // @ts-expect-error testing null input
114
- expect(() => fromJSON(null)).toThrow(/null|undefined|parse/i)
115
- })
116
-
117
- it('throws a descriptive error when raw is undefined', () => {
118
- // @ts-expect-error testing undefined input
119
- expect(() => fromJSON(undefined)).toThrow(/null|undefined|parse/i)
120
- })
121
-
122
- it('round-trip: toJSON then fromJSON returns deep-equal original value', () => {
123
- const original = { a: 1, nested: { b: [1, 2, 3] }, c: null, d: true }
124
- const serialized = toJSON(original)
125
- const reHydrated = fromJSON<typeof original>(serialized)
126
- expect(reHydrated).toEqual(original)
127
- })
128
- })