dev-react-microstore 5.0.0 → 6.0.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.
- package/README.md +187 -220
- package/dist/index.d.mts +60 -16
- package/dist/index.d.ts +60 -16
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +16 -6
- package/src/hooks.test.tsx +271 -0
- package/src/index.ts +312 -122
- package/src/store.test.ts +997 -0
- package/src/types.test.ts +161 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time type tests. Run with: tsc --noEmit
|
|
3
|
+
* Every @ts-expect-error MUST produce an error — if it doesn't, tsc fails.
|
|
4
|
+
* Every other line MUST compile cleanly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createStoreState, createSelectorHook } from './index'
|
|
8
|
+
|
|
9
|
+
// ── Test store ──────────────────────────────────────────────────────────────
|
|
10
|
+
const store = createStoreState({
|
|
11
|
+
count: 0,
|
|
12
|
+
name: '',
|
|
13
|
+
user: { firstName: '', lastName: '', age: 0 },
|
|
14
|
+
tags: [] as string[],
|
|
15
|
+
nested: { a: { b: 1 } },
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// ── get ─────────────────────────────────────────────────────────────────────
|
|
19
|
+
const state = store.get()
|
|
20
|
+
state.count satisfies number
|
|
21
|
+
state.name satisfies string
|
|
22
|
+
state.user satisfies { firstName: string; lastName: string; age: number }
|
|
23
|
+
|
|
24
|
+
// ── getKey ──────────────────────────────────────────────────────────────────
|
|
25
|
+
store.getKey('count') satisfies number
|
|
26
|
+
store.getKey('name') satisfies string
|
|
27
|
+
store.getKey('user') satisfies { firstName: string; lastName: string; age: number }
|
|
28
|
+
|
|
29
|
+
// @ts-expect-error — 'nonexistent' is not a key
|
|
30
|
+
store.getKey('nonexistent')
|
|
31
|
+
|
|
32
|
+
// @ts-expect-error — getKey('count') returns number, not string
|
|
33
|
+
store.getKey('count') satisfies string
|
|
34
|
+
|
|
35
|
+
// ── setKey ──────────────────────────────────────────────────────────────────
|
|
36
|
+
store.setKey('count', 42)
|
|
37
|
+
store.setKey('name', 'hello')
|
|
38
|
+
store.setKey('user', { firstName: 'A', lastName: 'B', age: 1 })
|
|
39
|
+
|
|
40
|
+
// @ts-expect-error — wrong value type for 'count'
|
|
41
|
+
store.setKey('count', 'not a number')
|
|
42
|
+
|
|
43
|
+
// @ts-expect-error — wrong value type for 'name'
|
|
44
|
+
store.setKey('name', 123)
|
|
45
|
+
|
|
46
|
+
// @ts-expect-error — missing required property 'age'
|
|
47
|
+
store.setKey('user', { firstName: 'A', lastName: 'B' })
|
|
48
|
+
|
|
49
|
+
// ── set ─────────────────────────────────────────────────────────────────────
|
|
50
|
+
store.set({ count: 1 })
|
|
51
|
+
store.set({ count: 1, name: 'test' })
|
|
52
|
+
|
|
53
|
+
// @ts-expect-error — wrong type in partial
|
|
54
|
+
store.set({ count: 'oops' })
|
|
55
|
+
|
|
56
|
+
// ── merge (pure read) ──────────────────────────────────────────────────────
|
|
57
|
+
store.merge('user', { age: 31 }) satisfies { firstName: string; lastName: string; age: number }
|
|
58
|
+
store.merge('nested', { a: { b: 2 } }) satisfies { a: { b: number } }
|
|
59
|
+
|
|
60
|
+
// Verify partial — only some keys required
|
|
61
|
+
store.merge('user', { firstName: 'Alice' })
|
|
62
|
+
store.merge('user', {})
|
|
63
|
+
|
|
64
|
+
// @ts-expect-error — 'count' is a primitive, merge should reject
|
|
65
|
+
store.merge('count', 5)
|
|
66
|
+
|
|
67
|
+
// @ts-expect-error — 'name' is a primitive, merge should reject
|
|
68
|
+
store.merge('name', 'test')
|
|
69
|
+
|
|
70
|
+
// @ts-expect-error — wrong property type inside partial
|
|
71
|
+
store.merge('user', { age: 'not a number' })
|
|
72
|
+
|
|
73
|
+
// @ts-expect-error — property doesn't exist on user
|
|
74
|
+
store.merge('user', { nonexistent: true })
|
|
75
|
+
|
|
76
|
+
// ── mergeSet ────────────────────────────────────────────────────────────────
|
|
77
|
+
store.mergeSet('user', { age: 25 })
|
|
78
|
+
store.mergeSet('user', { firstName: 'Bob' })
|
|
79
|
+
|
|
80
|
+
// @ts-expect-error — primitive key
|
|
81
|
+
store.mergeSet('count', 5)
|
|
82
|
+
|
|
83
|
+
// @ts-expect-error — wrong property type
|
|
84
|
+
store.mergeSet('user', { age: 'wrong' })
|
|
85
|
+
|
|
86
|
+
// ── reset ───────────────────────────────────────────────────────────────────
|
|
87
|
+
store.reset()
|
|
88
|
+
store.reset(['count', 'name'])
|
|
89
|
+
|
|
90
|
+
// @ts-expect-error — invalid key
|
|
91
|
+
store.reset(['nonexistent'])
|
|
92
|
+
|
|
93
|
+
// ── batch ───────────────────────────────────────────────────────────────────
|
|
94
|
+
store.batch(() => {
|
|
95
|
+
store.setKey('count', 10)
|
|
96
|
+
store.mergeSet('user', { age: 5 })
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// ── subscribe ───────────────────────────────────────────────────────────────
|
|
100
|
+
store.subscribe(['count', 'name'], () => {}) satisfies () => void
|
|
101
|
+
|
|
102
|
+
// @ts-expect-error — invalid key in subscribe
|
|
103
|
+
store.subscribe(['nonexistent'], () => {})
|
|
104
|
+
|
|
105
|
+
// ── select ──────────────────────────────────────────────────────────────────
|
|
106
|
+
const picked = store.select(['count', 'name'])
|
|
107
|
+
picked.count satisfies number
|
|
108
|
+
picked.name satisfies string
|
|
109
|
+
|
|
110
|
+
// @ts-expect-error — 'user' not in selection
|
|
111
|
+
picked.user
|
|
112
|
+
|
|
113
|
+
// ── onChange ────────────────────────────────────────────────────────────────
|
|
114
|
+
store.onChange(['count', 'user'], (values, prev) => {
|
|
115
|
+
values.count satisfies number
|
|
116
|
+
values.user satisfies { firstName: string; lastName: string; age: number }
|
|
117
|
+
prev.count satisfies number
|
|
118
|
+
prev.user satisfies { firstName: string; lastName: string; age: number }
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// ── createSelectorHook ──────────────────────────────────────────────────────
|
|
122
|
+
const useStore = createSelectorHook(store)
|
|
123
|
+
|
|
124
|
+
function TestComponent() {
|
|
125
|
+
const s1 = useStore(['count', 'name'])
|
|
126
|
+
s1.count satisfies number
|
|
127
|
+
s1.name satisfies string
|
|
128
|
+
|
|
129
|
+
const s2 = useStore(['user'])
|
|
130
|
+
s2.user satisfies { firstName: string; lastName: string; age: number }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── addMiddleware ───────────────────────────────────────────────────────────
|
|
134
|
+
store.addMiddleware((currentState, update, next) => {
|
|
135
|
+
currentState.count satisfies number
|
|
136
|
+
update satisfies Partial<typeof state>
|
|
137
|
+
next()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
store.addMiddleware((currentState, _update, next) => {
|
|
141
|
+
next({ count: currentState.count + 1 })
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// ── skipSetWhen / removeSkipSetWhen ─────────────────────────────────────────
|
|
145
|
+
store.skipSetWhen('user', (prev, next) => prev.firstName === next.firstName && prev.age === next.age)
|
|
146
|
+
store.skipSetWhen('count', (prev, next) => prev + 1 === next + 1)
|
|
147
|
+
store.skipSetWhen('tags', (prev, next) => prev.length === next.length && prev.every((t, i) => t === next[i]))
|
|
148
|
+
|
|
149
|
+
// @ts-expect-error — wrong prev/next type in callback
|
|
150
|
+
store.skipSetWhen('count', (prev: string, next: string) => prev === next)
|
|
151
|
+
|
|
152
|
+
// @ts-expect-error — invalid key
|
|
153
|
+
store.skipSetWhen('nonexistent', () => true)
|
|
154
|
+
|
|
155
|
+
store.removeSkipSetWhen('user')
|
|
156
|
+
store.removeSkipSetWhen('count')
|
|
157
|
+
|
|
158
|
+
// @ts-expect-error — invalid key
|
|
159
|
+
store.removeSkipSetWhen('nonexistent')
|
|
160
|
+
|
|
161
|
+
void TestComponent
|