@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,473 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { createStateProxy } from '../src/proxy';
3
-
4
- describe('2.1 Flat State Proxy Tracking', () => {
5
- it.each([
6
- ['string', { a: 'str' }, 'a'],
7
- ['number', { a: 1 }, 'a'],
8
- ['boolean', { a: true }, 'a'],
9
- ['null', { a: null }, 'a'],
10
- ['undefined', { a: undefined as unknown }, 'a']
11
- ])('Reading %s key returns correct value', (_, initial, key) => {
12
- const proxy = createStateProxy(initial as Record<string, unknown>, vi.fn());
13
- expect((proxy as Record<string, unknown>)[key]).toBe((initial as Record<string, unknown>)[key]);
14
- });
15
-
16
- it.each([
17
- ['string', { a: 'str' }, 'a', 'new-str'],
18
- ['number', { a: 1 }, 'a', 2],
19
- ['boolean', { a: true }, 'a', false],
20
- ['null to a key', { a: 'old' }, 'a', null],
21
- ['undefined to a key', { a: 'old' }, 'a', undefined],
22
- ['0 to a key', { a: 'old' }, 'a', 0],
23
- ['false to a key', { a: 'old' }, 'a', false],
24
- ['empty string to a key', { a: 'old' }, 'a', '']
25
- ])('Writing %s notifies subscribers', (_, initial, key, newValue) => {
26
- const listener = vi.fn();
27
- const proxy = createStateProxy(initial as Record<string, unknown>, listener);
28
- (proxy as Record<string, unknown>)[key] = newValue;
29
- expect(listener).toHaveBeenCalledTimes(1);
30
- });
31
-
32
- it('Writing same value still notifies subscribers', () => {
33
- const listener = vi.fn();
34
- const proxy = createStateProxy({ a: 1 }, listener);
35
- proxy.a = 1;
36
- expect(listener).toHaveBeenCalledTimes(1);
37
- });
38
-
39
- it('Multiple writes notify correct number of times', () => {
40
- const listener = vi.fn();
41
- const proxy = createStateProxy({ a: 1 }, listener);
42
- proxy.a = 2;
43
- proxy.a = 3;
44
- proxy.a = 4;
45
- expect(listener).toHaveBeenCalledTimes(3);
46
- });
47
-
48
- it('Proxy does not expose internal implementation details', () => {
49
- const proxy = createStateProxy({ a: 1 }, vi.fn());
50
- expect((proxy as Record<string, unknown>).__proxy).toBeUndefined();
51
- });
52
- });
53
-
54
- describe('2.2 Nested Object Tracking', () => {
55
- it.each([
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- [1, { l1: { val: 1 } }, (p: any) => p.l1.val],
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
- [2, { l1: { l2: { val: 2 } } }, (p: any) => p.l1.l2.val],
60
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
- [3, { l1: { l2: { l3: { val: 3 } } } }, (p: any) => p.l1.l2.l3.val],
62
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
- [5, { l1: { l2: { l3: { l4: { l5: { val: 5 } } } } } }, (p: any) => p.l1.l2.l3.l4.l5.val],
64
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
- [10, { l1: { l2: { l3: { l4: { l5: { l6: { l7: { l8: { l9: { l10: { val: 10 } } } } } } } } } } }, (p: any) => p.l1.l2.l3.l4.l5.l6.l7.l8.l9.l10.val]
66
- ])('Reading nested key (%i levels) returns correct value', (val, initial, readFn) => {
67
- const proxy = createStateProxy(initial, vi.fn());
68
- expect(readFn(proxy)).toBe(val);
69
- });
70
-
71
- it.each([
72
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
- [1, { l1: { val: 0 } }, (p: any) => { p.l1.val = 1; }],
74
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
- [2, { l1: { l2: { val: 0 } } }, (p: any) => { p.l1.l2.val = 2; }],
76
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
- [3, { l1: { l2: { l3: { val: 0 } } } }, (p: any) => { p.l1.l2.l3.val = 3; }],
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- [5, { l1: { l2: { l3: { l4: { l5: { val: 0 } } } } } }, (p: any) => { p.l1.l2.l3.l4.l5.val = 5; }],
80
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
- [10, { l1: { l2: { l3: { l4: { l5: { l6: { l7: { l8: { l9: { l10: { val: 0 } } } } } } } } } } }, (p: any) => { p.l1.l2.l3.l4.l5.l6.l7.l8.l9.l10.val = 10; }]
82
- ])('Writing nested key (%i levels) notifies subscribers', (_, initial, writeFn) => {
83
- const listener = vi.fn();
84
- const proxy = createStateProxy(initial, listener);
85
- writeFn(proxy);
86
- expect(listener).toHaveBeenCalledTimes(1);
87
- });
88
-
89
- it('Adding new key to nested object notifies subscribers', () => {
90
- const listener = vi.fn();
91
- const proxy = createStateProxy({ nested: { a: 1 } }, listener);
92
- (proxy.nested as Record<string, unknown>).b = 2;
93
- expect(listener).toHaveBeenCalledTimes(1);
94
- });
95
-
96
- it('Deleting key from nested object notifies subscribers', () => {
97
- const listener = vi.fn();
98
- const proxy = createStateProxy({ nested: { a: 1 } }, listener);
99
- delete (proxy.nested as Record<string, unknown>).a;
100
- expect(listener).toHaveBeenCalledTimes(1);
101
- });
102
-
103
- it('Replacing nested object with new object notifies subscribers', () => {
104
- const listener = vi.fn();
105
- const proxy = createStateProxy({ nested: { a: 1 } }, listener);
106
- proxy.nested = { b: 2 } as unknown as { a: number };
107
- expect(listener).toHaveBeenCalledTimes(1);
108
- });
109
-
110
- it('New assigned nested object is also tracked (eager wrapping)', () => {
111
- const listener = vi.fn();
112
- const proxy = createStateProxy({ nested: { a: 1 } }, listener);
113
- proxy.nested = { b: 2 } as unknown as { a: number };
114
- listener.mockClear();
115
- (proxy.nested as Record<string, number>).b = 3;
116
- expect(listener).toHaveBeenCalledTimes(1);
117
- });
118
-
119
- it('Replacing nested object — old object is no longer tracked', () => {
120
- const listener = vi.fn();
121
- const initialSub = { a: 1 };
122
- const state = { nested: initialSub as { a: number } | Record<string, number> };
123
- const proxy = createStateProxy(state, listener);
124
- proxy.nested = { b: 2 } as Record<string, number>;
125
- listener.mockClear();
126
- initialSub.a = 2;
127
- expect(listener).not.toHaveBeenCalled();
128
- });
129
-
130
- it('Two sibling nested objects tracked independently', () => {
131
- const listener = vi.fn();
132
- const proxy = createStateProxy({ a: { val: 1 }, b: { val: 2 } }, listener);
133
- (proxy.a as Record<string, number>).val = 10;
134
- expect(listener).toHaveBeenCalledTimes(1);
135
- (proxy.b as Record<string, number>).val = 20;
136
- expect(listener).toHaveBeenCalledTimes(2);
137
- });
138
-
139
- it('Writing to sibling object does not affect other sibling', () => {
140
- const proxy = createStateProxy({ a: { val: 1 }, b: { val: 2 } }, vi.fn());
141
- (proxy.a as Record<string, number>).val = 10;
142
- expect((proxy.b as Record<string, number>).val).toBe(2);
143
- });
144
-
145
- it('null nested value does not crash on read', () => {
146
- const proxy = createStateProxy({ nested: null }, vi.fn());
147
- expect(proxy.nested).toBeNull();
148
- });
149
-
150
- it('Reassigning nested object to null notifies subscribers', () => {
151
- const listener = vi.fn();
152
- const proxy = createStateProxy({ nested: { a: 1 } }, listener);
153
- (proxy as Record<string, unknown>).nested = null;
154
- expect(listener).toHaveBeenCalledTimes(1);
155
- });
156
-
157
- it('Reassigning nested object from null to object notifies subscribers', () => {
158
- const listener = vi.fn();
159
- const proxy = createStateProxy({ nested: null as { a: number } | null }, listener);
160
- proxy.nested = { a: 1 };
161
- expect(listener).toHaveBeenCalledTimes(1);
162
- });
163
- });
164
-
165
- describe('2.3 Array Tracking', () => {
166
- it('Reading array item by index returns correct value', () => {
167
- const proxy = createStateProxy({ arr: [1, 2, 3] }, vi.fn());
168
- expect(proxy.arr[1]).toBe(2);
169
- });
170
-
171
- it('Reading array length returns correct value', () => {
172
- const proxy = createStateProxy({ arr: [1, 2, 3] }, vi.fn());
173
- expect(proxy.arr.length).toBe(3);
174
- });
175
-
176
- it('Setting array item by index notifies subscribers', () => {
177
- const listener = vi.fn();
178
- const proxy = createStateProxy({ arr: [1, 2, 3] }, listener);
179
- proxy.arr[1] = 10;
180
- expect(listener).toHaveBeenCalledTimes(1);
181
- });
182
-
183
- it.each([
184
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
- ['push()', (p: any) => p.arr.push(4), [1, 2, 3, 4]],
186
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
187
- ['pop()', (p: any) => p.arr.pop(), [1, 2]],
188
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
- ['shift()', (p: any) => p.arr.shift(), [2, 3]],
190
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
- ['unshift()', (p: any) => p.arr.unshift(0), [0, 1, 2, 3]],
192
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
193
- ['splice() removes', (p: any) => p.arr.splice(1, 1), [1, 3]],
194
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
- ['splice() adds', (p: any) => p.arr.splice(1, 0, 1.5), [1, 1.5, 2, 3]],
196
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
- ['splice() removes and adds', (p: any) => p.arr.splice(1, 1, 2.5), [1, 2.5, 3]],
198
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
- ['sort()', (p: any) => p.arr.sort((a: number, b: number) => b - a), [3, 2, 1]],
200
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
201
- ['reverse()', (p: any) => p.arr.reverse(), [3, 2, 1]]
202
- ])('%s notifies subscribers and modifies correctly', (_, action, expected) => {
203
- const listener = vi.fn();
204
- const proxy = createStateProxy({ arr: [1, 2, 3] }, listener);
205
- action(proxy);
206
- expect(proxy.arr).toEqual(expected);
207
- expect(listener).toHaveBeenCalledTimes(1);
208
- });
209
-
210
- it('fill() notifies subscribers', () => {
211
- const listener = vi.fn();
212
- const proxy = createStateProxy({ arr: [1, 2, 3] }, listener);
213
- proxy.arr.fill(0);
214
- expect(proxy.arr).toEqual([0, 0, 0]);
215
- expect(listener).toHaveBeenCalled();
216
- });
217
-
218
- it('push() multiple items notifies subscribers exactly once', () => {
219
- const listener = vi.fn();
220
- const proxy = createStateProxy({ arr: [1] }, listener);
221
- proxy.arr.push(2, 3, 4);
222
- expect(listener).toHaveBeenCalledTimes(1);
223
- });
224
-
225
- it('pop() on empty array does not crash', () => {
226
- const listener = vi.fn();
227
- const proxy = createStateProxy({ arr: [] }, listener);
228
- expect(() => proxy.arr.pop()).not.toThrow();
229
- expect(listener).toHaveBeenCalledTimes(1);
230
- });
231
-
232
- it('splice() on empty array does not crash', () => {
233
- const listener = vi.fn();
234
- const proxy = createStateProxy({ arr: [] }, listener);
235
- expect(() => proxy.arr.splice(0, 1)).not.toThrow();
236
- expect(listener).toHaveBeenCalledTimes(1);
237
- });
238
-
239
- it.each([
240
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
- ['concat()', (p: any) => p.arr.concat([4]), [1, 2, 3, 4]],
242
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
243
- ['map()', (p: any) => p.arr.map((x: number) => x * 2), [2, 4, 6]],
244
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
- ['filter()', (p: any) => p.arr.filter((x: number) => x > 1), [2, 3]]
246
- ])('%s does not mutate original — no notification', (_, action, expectedResult) => {
247
- const listener = vi.fn();
248
- const proxy = createStateProxy({ arr: [1, 2, 3] }, listener);
249
- const result = action(proxy);
250
- expect(result).toEqual(expectedResult);
251
- expect(listener).not.toHaveBeenCalled();
252
- expect(proxy.arr).toEqual([1, 2, 3]);
253
- });
254
-
255
- it('forEach() does not mutate original — no notification', () => {
256
- const listener = vi.fn();
257
- const proxy = createStateProxy({ arr: [1, 2, 3] }, listener);
258
- let sum = 0;
259
- proxy.arr.forEach(x => sum += x);
260
- expect(sum).toBe(6);
261
- expect(listener).not.toHaveBeenCalled();
262
- });
263
-
264
- it('Array length assignment notifies subscribers', () => {
265
- const listener = vi.fn();
266
- const proxy = createStateProxy({ arr: [1, 2, 3] }, listener);
267
- proxy.arr.length = 1;
268
- expect(listener).toHaveBeenCalledTimes(1);
269
- expect(proxy.arr).toEqual([1]);
270
- });
271
-
272
- it('Direct index assignment beyond length notifies subscribers', () => {
273
- const listener = vi.fn();
274
- const proxy = createStateProxy({ arr: [1] }, listener);
275
- proxy.arr[5] = 10;
276
- expect(listener).toHaveBeenCalledTimes(1);
277
- expect(proxy.arr.length).toBe(6);
278
- });
279
-
280
- it('Array of objects — modifying nested object notifies subscribers', () => {
281
- const listener = vi.fn();
282
- const proxy = createStateProxy({ arr: [{ id: 1 }] }, listener);
283
- proxy.arr[0].id = 2;
284
- expect(listener).toHaveBeenCalledTimes(1);
285
- });
286
-
287
- it('Array push of object — new object is tracked', () => {
288
- const listener = vi.fn();
289
- const proxy = createStateProxy({ arr: [] as unknown[] }, listener);
290
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
291
- proxy.arr.push({ id: 1 } as any);
292
- listener.mockClear();
293
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
294
- (proxy.arr[0] as any).id = 2;
295
- expect(listener).toHaveBeenCalledTimes(1);
296
- });
297
-
298
- it('Nested array inside object is tracked', () => {
299
- const listener = vi.fn();
300
- const proxy = createStateProxy({ obj: { arr: [1] } }, listener);
301
- proxy.obj.arr.push(2);
302
- expect(listener).toHaveBeenCalledTimes(1);
303
- });
304
-
305
- it('Array inside array is tracked', () => {
306
- const listener = vi.fn();
307
- const proxy = createStateProxy({ arr: [[1]] }, listener);
308
- proxy.arr[0].push(2);
309
- expect(listener).toHaveBeenCalledTimes(1);
310
- });
311
- });
312
-
313
- describe('2.4 WeakMap & Proxy Safety', () => {
314
- it('Same object is not double-proxied', () => {
315
- const obj = { a: 1 };
316
- const p1 = createStateProxy(obj, vi.fn());
317
- const p2 = createStateProxy(obj, vi.fn());
318
- expect(p1).toBe(p2);
319
- });
320
-
321
- it('Proxied object passed to createStore does not double-wrap', () => {
322
- const obj = { a: 1 };
323
- const p1 = createStateProxy(obj, vi.fn());
324
- const p2 = createStateProxy(p1, vi.fn());
325
- expect(p2).toBe(p1);
326
- });
327
-
328
- it('Proxy identity check — proxied object is not strictly equal to raw', () => {
329
- const obj = { a: 1 };
330
- const proxy = createStateProxy(obj, vi.fn());
331
- expect(proxy).not.toBe(obj);
332
- });
333
-
334
- it('Proxy does not interfere with instanceof checks', () => {
335
- class MyClass { }
336
- const proxy = createStateProxy({ inst: new MyClass() }, vi.fn());
337
- expect(proxy.inst instanceof MyClass).toBe(true);
338
- });
339
-
340
- it('Proxy does not interfere with typeof checks', () => {
341
- const proxy = createStateProxy({ a: 1 }, vi.fn());
342
- expect(typeof proxy).toBe('object');
343
- expect(typeof proxy.a).toBe('number');
344
- });
345
-
346
- it.each([
347
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
- ['Object.keys()', (p: any) => Object.keys(p), ['a', 'b']],
349
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
350
- ['Object.values()', (p: any) => Object.values(p), [1, 2]],
351
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
352
- ['Object.entries()', (p: any) => Object.entries(p), [['a', 1], ['b', 2]]]
353
- ])('%s works correctly on proxied object', (_, fn, expected) => {
354
- const proxy = createStateProxy({ a: 1, b: 2 }, vi.fn());
355
- expect(fn(proxy)).toEqual(expected);
356
- });
357
-
358
- it('JSON.stringify() works correctly on proxied object', () => {
359
- const proxy = createStateProxy({ a: 1 }, vi.fn());
360
- expect(JSON.stringify(proxy)).toBe('{"a":1}');
361
- });
362
-
363
- it('Spread operator works correctly on proxied object', () => {
364
- const proxy = createStateProxy({ a: 1, b: 2 }, vi.fn());
365
- const spread = { ...proxy };
366
- expect(spread).toEqual({ a: 1, b: 2 });
367
- });
368
-
369
- it('Destructuring works correctly on proxied object', () => {
370
- const proxy = createStateProxy({ a: 1, b: 2 }, vi.fn());
371
- const { a, b } = proxy;
372
- expect(a).toBe(1);
373
- expect(b).toBe(2);
374
- });
375
-
376
- it('for...in loop works correctly on proxied object', () => {
377
- const proxy = createStateProxy({ a: 1, b: 2 }, vi.fn());
378
- const keys: string[] = [];
379
- for (const k in proxy) keys.push(k);
380
- expect(keys).toEqual(['a', 'b']);
381
- });
382
-
383
- it('for...of loop works correctly on proxied array', () => {
384
- const proxy = createStateProxy({ arr: [1, 2] }, vi.fn());
385
- const vals: number[] = [];
386
- for (const v of proxy.arr) vals.push(v);
387
- expect(vals).toEqual([1, 2]);
388
- });
389
-
390
- it('in operator works correctly on proxied object', () => {
391
- const proxy = createStateProxy({ a: 1 }, vi.fn());
392
- expect('a' in proxy).toBe(true);
393
- expect('b' in proxy).toBe(false);
394
- });
395
-
396
- it('hasOwnProperty works correctly on proxied object', () => {
397
- const proxy = createStateProxy({ a: 1 }, vi.fn());
398
- expect(Object.prototype.hasOwnProperty.call(proxy, 'a')).toBe(true);
399
- });
400
- });
401
-
402
- describe('2.5 Non-Proxied Value Types', () => {
403
- it.each([
404
- ['Date object', new Date()],
405
- ['RegExp', /test/],
406
- ['Function', () => { }],
407
- ['Map', new Map()],
408
- ['Set', new Set()],
409
- ['Primitive string', 'str'],
410
- ['Primitive number', 42],
411
- ['Primitive boolean', true],
412
- ['null', null],
413
- ['undefined', undefined]
414
- ])('%s is not wrapped in Proxy', (_, val) => {
415
- const proxy = createStateProxy(val as object, vi.fn());
416
- expect(proxy).toBe(val);
417
- });
418
-
419
- it('Class instance is not wrapped in Proxy', () => {
420
- class MyClass { }
421
- const inst = new MyClass();
422
- const proxy = createStateProxy(inst as object, vi.fn());
423
- expect(proxy).toBe(inst);
424
- });
425
- });
426
-
427
- describe('2.6 Assigning a Proxy Value into Another Property', () => {
428
- it('Assigning a proxied object to another key unwraps and stores the raw value', () => {
429
- // proxy.b is itself a proxied nested object; assigning it to proxy.a
430
- // must hit the rawMap.has(value) === true branch and unwrap it.
431
- const listener = vi.fn();
432
- const proxy = createStateProxy({ a: { x: 1 }, b: { x: 2 } }, listener);
433
-
434
- // Read proxy.b — this returns the proxied wrapper for { x: 2 }
435
- const proxiedB = proxy.b;
436
-
437
- // Assign the proxy to proxy.a — exercises the true branch on line 51
438
- proxy.a = proxiedB as typeof proxy.a;
439
- expect(listener).toHaveBeenCalled();
440
-
441
- // After assignment, proxy.a should reflect the correct value
442
- expect(proxy.a.x).toBe(2);
443
- });
444
-
445
- it('Object assigned via proxy reference is still tracked after assignment', () => {
446
- // Ensures the assigned (unwrapped) object gets re-proxied so writes to
447
- // proxy.a still notify subscribers.
448
- const listener = vi.fn();
449
- const proxy = createStateProxy({ a: { x: 1 }, b: { x: 2 } }, listener);
450
- proxy.a = proxy.b as typeof proxy.a; // true branch: rawMap.has(proxy.b) === true
451
- listener.mockClear();
452
-
453
- proxy.a.x = 99;
454
- expect(listener).toHaveBeenCalledTimes(1);
455
- expect(proxy.a.x).toBe(99);
456
- });
457
- });
458
-
459
- describe('2.7 Notification Correctness', () => {
460
- it('Listener does not fire when no state changes', () => {
461
- const listener = vi.fn();
462
- const proxy = createStateProxy({ a: 1 }, listener);
463
- proxy.a;
464
- expect(listener).not.toHaveBeenCalled();
465
- });
466
-
467
- it('Nested write fires listener exactly once', () => {
468
- const listener = vi.fn();
469
- const proxy = createStateProxy({ a: { b: 1 } }, listener);
470
- proxy.a.b = 2;
471
- expect(listener).toHaveBeenCalledTimes(1);
472
- });
473
- });
@@ -1,67 +0,0 @@
1
- /**
2
- * Phase 2 spike: Extension registry and no-op extension validation.
3
- */
4
- import { describe, it, expect, beforeEach, vi } from 'vitest';
5
- import { createStore } from '../src/store';
6
- import { registerExtension, __testingOnlyClearExtensions } from '../src/registry';
7
-
8
- beforeEach(() => {
9
- __testingOnlyClearExtensions();
10
- });
11
-
12
- describe('Extension registry spike', () => {
13
- it('store works with no extensions', () => {
14
- const store = createStore({ count: 0, name: 'test' });
15
- expect(store.getState()).toEqual({ count: 0, name: 'test' });
16
- store.setState({ count: 1 });
17
- expect(store.getState()).toEqual({ count: 1, name: 'test' });
18
- });
19
-
20
- it('store works with no-op extension registered', () => {
21
- registerExtension({
22
- key: '__noop',
23
- processDefinition: (def) => ({ state: { ...def } }),
24
- });
25
- const store = createStore({ count: 0, name: 'test' });
26
- expect(store.getState()).toEqual({ count: 0, name: 'test' });
27
- store.setState({ count: 42 });
28
- expect(store.getState()).toEqual({ count: 42, name: 'test' });
29
- });
30
-
31
- it('extension can transform definition (pass-through)', () => {
32
- registerExtension({
33
- key: '__passthrough',
34
- processDefinition: (def) => ({ state: { ...def, extra: 'added' } }),
35
- });
36
- const store = createStore({ a: 1 });
37
- expect(store.getState()).toEqual({ a: 1, extra: 'added' });
38
- });
39
-
40
- it('noop extension registers on import and passes through', async () => {
41
- __testingOnlyClearExtensions();
42
- await import('../src/extensions/noop');
43
- const store = createStore({ x: 1, y: 2 });
44
- expect(store.getState()).toEqual({ x: 1, y: 2 });
45
- });
46
-
47
- it('store without async extension has stub methods that throw', async () => {
48
- __testingOnlyClearExtensions();
49
- const store = createStore({ count: 0 });
50
- await expect(store.fetch('count' as never)).rejects.toThrow(/no async key.*Import "storve\/async"/);
51
- expect(store.getAsyncState('count' as never)).toBeUndefined();
52
- await store.refetch('count' as never);
53
- store.invalidate('count' as never);
54
- store.invalidateAll();
55
- });
56
-
57
- it('subscribe during batch with pending changes runs runOnStateChanged', () => {
58
- __testingOnlyClearExtensions();
59
- const store = createStore({ a: 1 });
60
- const listener = vi.fn();
61
- store.batch(() => {
62
- store.setState({ a: 2 });
63
- store.subscribe(listener);
64
- });
65
- expect(listener).toHaveBeenCalledWith({ a: 2 });
66
- });
67
- });