muya 2.5.3 → 2.5.5
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/{src/__tests__ → __tests__}/bench.test.tsx +4 -4
- package/{src/__tests__ → __tests__}/compare.test.tsx +8 -6
- package/{src/__tests__ → __tests__}/create.test.tsx +2 -2
- package/{src/utils/__tests__ → __tests__}/is.test.ts +3 -3
- package/{src/__tests__ → __tests__}/scheduler.test.tsx +1 -1
- package/{src/__tests__ → __tests__}/select.test.tsx +5 -5
- package/{src/utils/__tests__ → __tests__}/shallow.test.ts +1 -1
- package/{src/__tests__ → __tests__}/use-value-loadable.test.tsx +3 -3
- package/{src/__tests__ → __tests__}/use-value.test.tsx +8 -8
- package/build.ts +67 -0
- package/dist/cjs/index.js +1 -0
- package/dist/esm/create.js +1 -0
- package/dist/esm/debug/development-tools.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/select.js +1 -0
- package/{types → dist/types}/create-state.d.ts +1 -0
- package/dist/types/create-state.d.ts.map +1 -0
- package/{types → dist/types}/create.d.ts +1 -0
- package/dist/types/create.d.ts.map +1 -0
- package/dist/types/debug/development-tools.d.ts +13 -0
- package/dist/types/debug/development-tools.d.ts.map +1 -0
- package/{types → dist/types}/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -0
- package/{types → dist/types}/scheduler.d.ts +1 -0
- package/dist/types/scheduler.d.ts.map +1 -0
- package/{types → dist/types}/select.d.ts +1 -0
- package/dist/types/select.d.ts.map +1 -0
- package/{types → dist/types}/types.d.ts +1 -0
- package/dist/types/types.d.ts.map +1 -0
- package/{types → dist/types}/use-value-loadable.d.ts +1 -0
- package/dist/types/use-value-loadable.d.ts.map +1 -0
- package/{types → dist/types}/use-value.d.ts +2 -1
- package/dist/types/use-value.d.ts.map +1 -0
- package/{types → dist/types}/utils/common.d.ts +1 -0
- package/dist/types/utils/common.d.ts.map +1 -0
- package/{types → dist/types}/utils/create-emitter.d.ts +1 -0
- package/dist/types/utils/create-emitter.d.ts.map +1 -0
- package/{types → dist/types}/utils/id.d.ts +1 -0
- package/dist/types/utils/id.d.ts.map +1 -0
- package/{types → dist/types}/utils/is.d.ts +1 -0
- package/dist/types/utils/is.d.ts.map +1 -0
- package/{types → dist/types}/utils/shallow.d.ts +1 -0
- package/dist/types/utils/shallow.d.ts.map +1 -0
- package/package.json +23 -8
- package/src/create-state.d.ts.map +1 -0
- package/src/create.d.ts.map +1 -0
- package/src/create.ts +7 -2
- package/src/debug/development-tools.d.ts.map +1 -0
- package/src/debug/development-tools.ts +5 -40
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +2 -1
- package/src/scheduler.d.ts.map +1 -0
- package/src/select.d.ts.map +1 -0
- package/src/select.ts +7 -2
- package/src/types.d.ts.map +1 -0
- package/src/use-value-loadable.d.ts.map +1 -0
- package/src/use-value.d.ts.map +1 -0
- package/src/use-value.ts +1 -1
- package/src/utils/common.d.ts.map +1 -0
- package/src/utils/create-emitter.d.ts.map +1 -0
- package/src/utils/id.d.ts.map +1 -0
- package/src/utils/is.d.ts.map +1 -0
- package/src/utils/shallow.d.ts.map +1 -0
- package/tsconfig.build.json +12 -0
- package/cjs/index.js +0 -1
- package/esm/__tests__/test-utils.js +0 -1
- package/esm/create.js +0 -1
- package/esm/debug/development-tools.js +0 -1
- package/esm/index.js +0 -1
- package/esm/select.js +0 -1
- package/esm/sqlite/__tests__/create-sqlite.test.js +0 -1
- package/esm/sqlite/__tests__/map-deque.test.js +0 -1
- package/esm/sqlite/__tests__/table.test.js +0 -1
- package/esm/sqlite/__tests__/tokenizer.test.js +0 -1
- package/esm/sqlite/__tests__/where.test.js +0 -1
- package/esm/sqlite/create-sqlite.js +0 -1
- package/esm/sqlite/index.js +0 -1
- package/esm/sqlite/table/backend.js +0 -1
- package/esm/sqlite/table/bun-backend.js +0 -1
- package/esm/sqlite/table/index.js +0 -1
- package/esm/sqlite/table/map-deque.js +0 -1
- package/esm/sqlite/table/table.js +0 -43
- package/esm/sqlite/table/table.types.js +0 -0
- package/esm/sqlite/table/tokenizer.js +0 -1
- package/esm/sqlite/table/where.js +0 -1
- package/esm/sqlite/use-sqlite-count.js +0 -1
- package/esm/sqlite/use-sqlite.js +0 -1
- package/esm/utils/__tests__/is.test.js +0 -1
- package/esm/utils/__tests__/shallow.test.js +0 -1
- package/src/sqlite/__tests__/create-sqlite.test.ts +0 -264
- package/src/sqlite/__tests__/map-deque.test.ts +0 -61
- package/src/sqlite/__tests__/table.test.ts +0 -351
- package/src/sqlite/__tests__/tokenizer.test.ts +0 -43
- package/src/sqlite/__tests__/use-slite-count.test.tsx +0 -96
- package/src/sqlite/__tests__/use-sqlite.more.test.tsx +0 -637
- package/src/sqlite/__tests__/use-sqlite.test.tsx +0 -1008
- package/src/sqlite/__tests__/where.test.ts +0 -234
- package/src/sqlite/create-sqlite.ts +0 -164
- package/src/sqlite/index.ts +0 -4
- package/src/sqlite/table/backend.ts +0 -21
- package/src/sqlite/table/bun-backend.ts +0 -47
- package/src/sqlite/table/index.ts +0 -6
- package/src/sqlite/table/map-deque.ts +0 -29
- package/src/sqlite/table/table.ts +0 -353
- package/src/sqlite/table/table.types.ts +0 -129
- package/src/sqlite/table/tokenizer.ts +0 -35
- package/src/sqlite/table/where.ts +0 -207
- package/src/sqlite/use-sqlite-count.ts +0 -69
- package/src/sqlite/use-sqlite.ts +0 -250
- package/types/__tests__/test-utils.d.ts +0 -25
- package/types/debug/development-tools.d.ts +0 -8
- package/types/sqlite/create-sqlite.d.ts +0 -31
- package/types/sqlite/index.d.ts +0 -4
- package/types/sqlite/table/backend.d.ts +0 -20
- package/types/sqlite/table/bun-backend.d.ts +0 -6
- package/types/sqlite/table/index.d.ts +0 -6
- package/types/sqlite/table/map-deque.d.ts +0 -5
- package/types/sqlite/table/table.d.ts +0 -21
- package/types/sqlite/table/table.types.d.ts +0 -91
- package/types/sqlite/table/tokenizer.d.ts +0 -11
- package/types/sqlite/table/where.d.ts +0 -37
- package/types/sqlite/use-sqlite-count.d.ts +0 -17
- package/types/sqlite/use-sqlite.d.ts +0 -39
- /package/{src/__tests__ → __tests__}/test-utils.ts +0 -0
- /package/{esm → dist/esm}/create-state.js +0 -0
- /package/{esm → dist/esm}/scheduler.js +0 -0
- /package/{esm → dist/esm}/types.js +0 -0
- /package/{esm → dist/esm}/use-value-loadable.js +0 -0
- /package/{esm → dist/esm}/use-value.js +0 -0
- /package/{esm → dist/esm}/utils/common.js +0 -0
- /package/{esm → dist/esm}/utils/create-emitter.js +0 -0
- /package/{esm → dist/esm}/utils/id.js +0 -0
- /package/{esm → dist/esm}/utils/is.js +0 -0
- /package/{esm → dist/esm}/utils/shallow.js +0 -0
|
@@ -1,1008 +0,0 @@
|
|
|
1
|
-
/* eslint-disable jsdoc/require-jsdoc */
|
|
2
|
-
import { act, renderHook } from '@testing-library/react-hooks'
|
|
3
|
-
import { createSqliteState } from '../create-sqlite'
|
|
4
|
-
import { useSqliteValue } from '../use-sqlite'
|
|
5
|
-
import { waitFor } from '@testing-library/react'
|
|
6
|
-
import { bunMemoryBackend } from '../table/bun-backend'
|
|
7
|
-
import { StrictMode, Suspense, useState } from 'react'
|
|
8
|
-
import { DEFAULT_PAGE_SIZE } from '../table/table'
|
|
9
|
-
|
|
10
|
-
const backend = bunMemoryBackend()
|
|
11
|
-
interface Person {
|
|
12
|
-
id: string
|
|
13
|
-
name: string
|
|
14
|
-
age: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function Wrapper({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
18
|
-
return (
|
|
19
|
-
<StrictMode>
|
|
20
|
-
<Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
|
|
21
|
-
</StrictMode>
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Generate mock people for testing
|
|
27
|
-
* @param count Number of people to generate
|
|
28
|
-
* @returns Array of Person objects
|
|
29
|
-
*/
|
|
30
|
-
function generatePeople(count: number): Person[] {
|
|
31
|
-
return Array.from({ length: count }, (_, index) => ({
|
|
32
|
-
id: `person-${index}`,
|
|
33
|
-
name: `Person ${index}`,
|
|
34
|
-
age: 20 + (index % 60),
|
|
35
|
-
}))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe('use-sqlite-state', () => {
|
|
39
|
-
it('should get basic value states', async () => {
|
|
40
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State1', key: 'id' })
|
|
41
|
-
let reRenders = 0
|
|
42
|
-
const { result, rerender } = renderHook(
|
|
43
|
-
() => {
|
|
44
|
-
reRenders++
|
|
45
|
-
const aha = useSqliteValue(sql)
|
|
46
|
-
return aha
|
|
47
|
-
},
|
|
48
|
-
{ wrapper: Wrapper },
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
// Initial sync render = 1
|
|
52
|
-
expect(reRenders).toBe(1)
|
|
53
|
-
|
|
54
|
-
// Wait for initial data load
|
|
55
|
-
await waitFor(() => {
|
|
56
|
-
expect(result.current[0]).toEqual([])
|
|
57
|
-
})
|
|
58
|
-
const initialRenders = reRenders
|
|
59
|
-
|
|
60
|
-
act(() => {
|
|
61
|
-
sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
62
|
-
})
|
|
63
|
-
await waitFor(() => {
|
|
64
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Alice', age: 30 }])
|
|
65
|
-
})
|
|
66
|
-
const afterFirstSet = reRenders
|
|
67
|
-
|
|
68
|
-
act(() => {
|
|
69
|
-
sql.set({ id: '1', name: 'Alice2', age: 30 })
|
|
70
|
-
})
|
|
71
|
-
await waitFor(() => {
|
|
72
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Alice2', age: 30 }])
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
// delete item
|
|
76
|
-
act(() => {
|
|
77
|
-
sql.delete('1')
|
|
78
|
-
})
|
|
79
|
-
await waitFor(() => {
|
|
80
|
-
expect(result.current[0]).toEqual([])
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
// add two items
|
|
84
|
-
act(() => {
|
|
85
|
-
sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
86
|
-
sql.set({ id: '2', name: 'Bob', age: 25 })
|
|
87
|
-
})
|
|
88
|
-
await waitFor(() => {
|
|
89
|
-
expect(result.current[0]?.length).toBe(2)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
const beforeManualRerender = reRenders
|
|
93
|
-
act(() => {
|
|
94
|
-
rerender()
|
|
95
|
-
})
|
|
96
|
-
await waitFor(() => {
|
|
97
|
-
expect(reRenders).toBe(beforeManualRerender + 1)
|
|
98
|
-
expect(result.current[0]?.length).toBe(2)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// Verify re-renders happened (at least initial + operations)
|
|
102
|
-
expect(afterFirstSet).toBeGreaterThan(initialRenders)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('should use where clause changed via state', async () => {
|
|
106
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State2', key: 'id' })
|
|
107
|
-
await sql.batchSet([
|
|
108
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
109
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
110
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
111
|
-
])
|
|
112
|
-
let reRenders = 0
|
|
113
|
-
const { result } = renderHook(() => {
|
|
114
|
-
reRenders++
|
|
115
|
-
const [minAge, setMinAge] = useState(20)
|
|
116
|
-
return [useSqliteValue(sql, { where: { age: { gt: minAge } }, sortBy: 'age' }, [minAge]), setMinAge] as const
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
await waitFor(() => {
|
|
120
|
-
const names = result.current?.[0][0]?.map((p) => p.name)
|
|
121
|
-
expect(names).toEqual(['Bob', 'Alice', 'Carol'])
|
|
122
|
-
})
|
|
123
|
-
const initialRenders = reRenders
|
|
124
|
-
|
|
125
|
-
// change minAge to 29
|
|
126
|
-
act(() => {
|
|
127
|
-
result.current[1](29)
|
|
128
|
-
})
|
|
129
|
-
await waitFor(() => {
|
|
130
|
-
const names = result.current?.[0][0]?.map((p) => p.name)
|
|
131
|
-
expect(names).toEqual(['Alice', 'Carol'])
|
|
132
|
-
})
|
|
133
|
-
// Deps change should trigger re-renders (stale + data load)
|
|
134
|
-
expect(reRenders).toBeGreaterThan(initialRenders)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('should handle rapid dependency changes without excessive re-renders', async () => {
|
|
138
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'RapidDepsChange', key: 'id' })
|
|
139
|
-
await sql.batchSet([
|
|
140
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
141
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
142
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
143
|
-
{ id: '4', name: 'Dave', age: 35 },
|
|
144
|
-
])
|
|
145
|
-
|
|
146
|
-
let reRenders = 0
|
|
147
|
-
const staleLog: Array<{ render: number; isStale: boolean; filterAge: number; dataLength: number | undefined }> = []
|
|
148
|
-
|
|
149
|
-
const { result } = renderHook(() => {
|
|
150
|
-
reRenders++
|
|
151
|
-
const [filterAge, setFilterAge] = useState(20)
|
|
152
|
-
const [data, actions] = useSqliteValue(sql, { where: { age: { gt: filterAge } }, sortBy: 'age' }, [filterAge])
|
|
153
|
-
staleLog.push({ render: reRenders, isStale: actions.isStale, filterAge, dataLength: data?.length })
|
|
154
|
-
return { data, isStale: actions.isStale, setFilterAge }
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
await waitFor(() => {
|
|
158
|
-
expect(result.current.data?.length).toBe(4)
|
|
159
|
-
expect(result.current.isStale).toBe(false)
|
|
160
|
-
})
|
|
161
|
-
const beforeRapidChanges = reRenders
|
|
162
|
-
staleLog.length = 0 // Clear log for the interesting part
|
|
163
|
-
|
|
164
|
-
// Change deps rapidly: 20 -> 25 -> 30 -> 35 in quick succession (within same act)
|
|
165
|
-
act(() => {
|
|
166
|
-
result.current.setFilterAge(25)
|
|
167
|
-
result.current.setFilterAge(30)
|
|
168
|
-
result.current.setFilterAge(35)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
// isStale should be true immediately after deps change
|
|
172
|
-
expect(result.current.isStale).toBe(true)
|
|
173
|
-
|
|
174
|
-
// Wait for final data to load
|
|
175
|
-
await waitFor(() => {
|
|
176
|
-
expect(result.current.isStale).toBe(false)
|
|
177
|
-
// Only Carol (age 40) is > 35
|
|
178
|
-
expect(result.current.data?.length).toBe(1)
|
|
179
|
-
expect(result.current.data?.[0]?.name).toBe('Carol')
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
const afterRapidChanges = reRenders
|
|
183
|
-
const rendersForRapidChanges = afterRapidChanges - beforeRapidChanges
|
|
184
|
-
|
|
185
|
-
// eslint-disable-next-line no-console
|
|
186
|
-
console.log(`🔄 Rapid dep changes (3 setState calls in 1 act): ${rendersForRapidChanges} renders`)
|
|
187
|
-
// eslint-disable-next-line no-console
|
|
188
|
-
console.log('📋 Stale log:', staleLog)
|
|
189
|
-
|
|
190
|
-
// With batched setState, React batches the 3 calls into 1 render with filterAge=35
|
|
191
|
-
// Then we get: 1 render (batched setState) + 1 render (setSettledDeps) = 2 renders
|
|
192
|
-
// This demonstrates the optimization: rapid changes don't cause multiple stale cycles
|
|
193
|
-
expect(rendersForRapidChanges).toBeLessThanOrEqual(4)
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('should handle sequential dependency changes with proper stale tracking', async () => {
|
|
197
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'SequentialDepsChange', key: 'id' })
|
|
198
|
-
await sql.batchSet([
|
|
199
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
200
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
201
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
202
|
-
])
|
|
203
|
-
|
|
204
|
-
let reRenders = 0
|
|
205
|
-
const { result } = renderHook(() => {
|
|
206
|
-
reRenders++
|
|
207
|
-
const [filterAge, setFilterAge] = useState(20)
|
|
208
|
-
const [data, actions] = useSqliteValue(sql, { where: { age: { gt: filterAge } }, sortBy: 'age' }, [filterAge])
|
|
209
|
-
return { data, isStale: actions.isStale, setFilterAge }
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
await waitFor(() => {
|
|
213
|
-
expect(result.current.data?.length).toBe(3)
|
|
214
|
-
expect(result.current.isStale).toBe(false)
|
|
215
|
-
})
|
|
216
|
-
const afterInitialLoad = reRenders
|
|
217
|
-
|
|
218
|
-
// First change: 20 -> 25
|
|
219
|
-
act(() => {
|
|
220
|
-
result.current.setFilterAge(25)
|
|
221
|
-
})
|
|
222
|
-
expect(result.current.isStale).toBe(true)
|
|
223
|
-
|
|
224
|
-
await waitFor(() => {
|
|
225
|
-
expect(result.current.isStale).toBe(false)
|
|
226
|
-
expect(result.current.data?.length).toBe(2) // Alice (30) and Carol (40)
|
|
227
|
-
})
|
|
228
|
-
const afterFirstChange = reRenders
|
|
229
|
-
|
|
230
|
-
// Second change: 25 -> 35
|
|
231
|
-
act(() => {
|
|
232
|
-
result.current.setFilterAge(35)
|
|
233
|
-
})
|
|
234
|
-
expect(result.current.isStale).toBe(true)
|
|
235
|
-
|
|
236
|
-
await waitFor(() => {
|
|
237
|
-
expect(result.current.isStale).toBe(false)
|
|
238
|
-
expect(result.current.data?.length).toBe(1) // Only Carol (40)
|
|
239
|
-
})
|
|
240
|
-
const afterSecondChange = reRenders
|
|
241
|
-
|
|
242
|
-
// Each dep change should cause ~2 re-renders: 1 for setState + 1 for setSettledDeps
|
|
243
|
-
const rendersPerChange1 = afterFirstChange - afterInitialLoad
|
|
244
|
-
const rendersPerChange2 = afterSecondChange - afterFirstChange
|
|
245
|
-
|
|
246
|
-
// eslint-disable-next-line no-console
|
|
247
|
-
console.log(`🔄 Sequential dep changes: ${rendersPerChange1} renders for 1st, ${rendersPerChange2} renders for 2nd`)
|
|
248
|
-
|
|
249
|
-
// Should be 2-3 renders per change (setState + data load + setSettledDeps)
|
|
250
|
-
expect(rendersPerChange1).toBeLessThanOrEqual(4)
|
|
251
|
-
expect(rendersPerChange2).toBeLessThanOrEqual(4)
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('should support like in where clause and update results', async () => {
|
|
255
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State3Hook', key: 'id' })
|
|
256
|
-
await sql.batchSet([
|
|
257
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
258
|
-
{ id: '2', name: 'Alicia', age: 25 },
|
|
259
|
-
{ id: '3', name: 'Bob', age: 40 },
|
|
260
|
-
])
|
|
261
|
-
let reRenders = 0
|
|
262
|
-
const { result, rerender } = renderHook(
|
|
263
|
-
({ like }) => {
|
|
264
|
-
reRenders++
|
|
265
|
-
return useSqliteValue(sql, { where: { name: { like } } }, [like])
|
|
266
|
-
},
|
|
267
|
-
{ initialProps: { like: '%Ali%' } },
|
|
268
|
-
)
|
|
269
|
-
await waitFor(() => {
|
|
270
|
-
expect(result.current?.[0]?.map((p) => p.name)).toEqual(['Alice', 'Alicia'])
|
|
271
|
-
})
|
|
272
|
-
act(() => {
|
|
273
|
-
rerender({ like: '%Bob%' })
|
|
274
|
-
})
|
|
275
|
-
await waitFor(() => {
|
|
276
|
-
expect(result.current?.[0]?.map((p) => p.name)).toEqual(['Bob'])
|
|
277
|
-
})
|
|
278
|
-
expect(reRenders).toBeGreaterThanOrEqual(2)
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
it('should update results when changing order and limit options 1', async () => {
|
|
282
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State44Hook', key: 'id' })
|
|
283
|
-
await sql.batchSet([
|
|
284
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
285
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
286
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
287
|
-
])
|
|
288
|
-
const { result, rerender } = renderHook(
|
|
289
|
-
({ order, limit }) => useSqliteValue(sql, { sortBy: 'age', order, limit }, [order, limit]),
|
|
290
|
-
{ initialProps: { order: 'asc' as 'asc' | 'desc', limit: 2 } },
|
|
291
|
-
)
|
|
292
|
-
await waitFor(() => {
|
|
293
|
-
expect(result.current?.[0]?.map((p) => p.name)).toEqual(['Bob', 'Alice'])
|
|
294
|
-
})
|
|
295
|
-
act(() => {
|
|
296
|
-
rerender({ order: 'desc', limit: 2 })
|
|
297
|
-
})
|
|
298
|
-
await waitFor(() => {
|
|
299
|
-
expect(result.current?.[0]?.map((p) => p.name)).toEqual(['Carol', 'Alice'])
|
|
300
|
-
})
|
|
301
|
-
act(() => {
|
|
302
|
-
rerender({ order: 'desc', limit: 1 })
|
|
303
|
-
})
|
|
304
|
-
await waitFor(() => {
|
|
305
|
-
expect(result.current?.[0]?.map((p) => p.name)).toEqual(['Carol'])
|
|
306
|
-
})
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
it('should support actions.next and actions.refresh', async () => {
|
|
310
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State5Hook', key: 'id' })
|
|
311
|
-
await sql.batchSet([
|
|
312
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
313
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
314
|
-
])
|
|
315
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
316
|
-
// actions.next and actions.refresh should be functions
|
|
317
|
-
await waitFor(() => {
|
|
318
|
-
expect(typeof result.current[1].nextPage).toBe('function')
|
|
319
|
-
expect(typeof result.current[1].reset).toBe('function')
|
|
320
|
-
})
|
|
321
|
-
})
|
|
322
|
-
it('should handle thousands of records Here', async () => {
|
|
323
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State6Hook', key: 'id' })
|
|
324
|
-
const people: Person[] = []
|
|
325
|
-
const ITEMS_COUNT = 1000
|
|
326
|
-
for (let index = 1; index <= ITEMS_COUNT; index++) {
|
|
327
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
328
|
-
}
|
|
329
|
-
await sql.batchSet(people)
|
|
330
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
331
|
-
await waitFor(() => {
|
|
332
|
-
expect(result.current?.[0]?.length ?? 0).toBe(DEFAULT_PAGE_SIZE)
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
// // loop until we have all ITEMS_COUNT items
|
|
336
|
-
for (let index = 0; index < ITEMS_COUNT / DEFAULT_PAGE_SIZE; index++) {
|
|
337
|
-
act(() => {
|
|
338
|
-
result.current[1].nextPage()
|
|
339
|
-
})
|
|
340
|
-
await waitFor(() => {
|
|
341
|
-
expect(result.current?.[0]?.length).toBe(Math.min(DEFAULT_PAGE_SIZE * (index + 2), ITEMS_COUNT))
|
|
342
|
-
})
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
act(() => {
|
|
346
|
-
result.current[1].reset()
|
|
347
|
-
})
|
|
348
|
-
await waitFor(() => {
|
|
349
|
-
expect(result.current?.[0]?.length).toBe(DEFAULT_PAGE_SIZE)
|
|
350
|
-
})
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
it('should handle thousands of records with single update', async () => {
|
|
354
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State6Hook', key: 'id' })
|
|
355
|
-
const people: Person[] = []
|
|
356
|
-
const ITEMS_COUNT = 10_000
|
|
357
|
-
const pageSize = 500
|
|
358
|
-
for (let index = 1; index <= ITEMS_COUNT; index++) {
|
|
359
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
360
|
-
}
|
|
361
|
-
await sql.batchSet(people)
|
|
362
|
-
let reRenders = 0
|
|
363
|
-
const { result } = renderHook(() => {
|
|
364
|
-
reRenders++
|
|
365
|
-
return useSqliteValue(sql, { pageSize }, [])
|
|
366
|
-
})
|
|
367
|
-
await waitFor(() => {
|
|
368
|
-
expect(result.current?.[0]?.length).toBe(pageSize)
|
|
369
|
-
})
|
|
370
|
-
const initialRenders = reRenders
|
|
371
|
-
|
|
372
|
-
act(() => {
|
|
373
|
-
for (let index = 0; index < (ITEMS_COUNT - pageSize) / pageSize; index++) {
|
|
374
|
-
result.current[1].nextPage()
|
|
375
|
-
}
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
await waitFor(() => {
|
|
379
|
-
expect(result.current?.[0]?.length).toBe(ITEMS_COUNT)
|
|
380
|
-
})
|
|
381
|
-
const afterPagination = reRenders
|
|
382
|
-
|
|
383
|
-
act(() => {
|
|
384
|
-
result.current[1].reset()
|
|
385
|
-
})
|
|
386
|
-
await waitFor(() => {
|
|
387
|
-
expect(result.current?.[0]?.length).toBe(pageSize)
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
// Verify pagination and reset caused re-renders
|
|
391
|
-
expect(afterPagination).toBeGreaterThan(initialRenders)
|
|
392
|
-
expect(reRenders).toBeGreaterThan(afterPagination)
|
|
393
|
-
})
|
|
394
|
-
it('should change ordering', async () => {
|
|
395
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State7', key: 'id', indexes: ['age'] })
|
|
396
|
-
const people: Person[] = []
|
|
397
|
-
for (let index = 1; index <= 100; index++) {
|
|
398
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
399
|
-
}
|
|
400
|
-
await sql.batchSet(people)
|
|
401
|
-
const { result, rerender } = renderHook(({ order }) => useSqliteValue(sql, { sortBy: 'age', order }, [order]), {
|
|
402
|
-
initialProps: { order: 'asc' as 'asc' | 'desc' },
|
|
403
|
-
})
|
|
404
|
-
await waitFor(() => {
|
|
405
|
-
expect(result.current?.[0]?.[0]?.age).toBe(20)
|
|
406
|
-
})
|
|
407
|
-
act(() => {
|
|
408
|
-
rerender({ order: 'desc' })
|
|
409
|
-
})
|
|
410
|
-
await waitFor(() => {
|
|
411
|
-
expect(result.current?.[0]?.[0]?.age).toBe(69)
|
|
412
|
-
})
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
it('should support selector in options', async () => {
|
|
416
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State8', key: 'id' })
|
|
417
|
-
await sql.batchSet([
|
|
418
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
419
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
420
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
421
|
-
])
|
|
422
|
-
const { result } = renderHook(() =>
|
|
423
|
-
useSqliteValue(
|
|
424
|
-
sql,
|
|
425
|
-
{
|
|
426
|
-
sortBy: 'age',
|
|
427
|
-
select: (d) => d.name,
|
|
428
|
-
},
|
|
429
|
-
[],
|
|
430
|
-
),
|
|
431
|
-
)
|
|
432
|
-
await waitFor(() => {
|
|
433
|
-
expect(result.current[0]).toEqual(['Bob', 'Alice', 'Carol'])
|
|
434
|
-
})
|
|
435
|
-
})
|
|
436
|
-
it('should add 50 documents and then load with another hook', async () => {
|
|
437
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State9', key: 'id' })
|
|
438
|
-
let reRenders = 0
|
|
439
|
-
const { result: result1 } = renderHook(() => {
|
|
440
|
-
reRenders++
|
|
441
|
-
return useSqliteValue(
|
|
442
|
-
sql,
|
|
443
|
-
{
|
|
444
|
-
sortBy: 'age',
|
|
445
|
-
order: 'desc',
|
|
446
|
-
},
|
|
447
|
-
[],
|
|
448
|
-
)
|
|
449
|
-
})
|
|
450
|
-
await waitFor(() => {
|
|
451
|
-
// Initially empty
|
|
452
|
-
expect(result1.current?.[0]?.length ?? 0).toBeLessThanOrEqual(1)
|
|
453
|
-
})
|
|
454
|
-
const initialRenders = reRenders
|
|
455
|
-
|
|
456
|
-
const people: Person[] = []
|
|
457
|
-
for (let index = 1; index <= 50; index++) {
|
|
458
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
459
|
-
}
|
|
460
|
-
await sql.batchSet(people)
|
|
461
|
-
await waitFor(() => {
|
|
462
|
-
expect(result1.current?.[0]?.length).toBe(50)
|
|
463
|
-
})
|
|
464
|
-
// Data load should trigger re-renders
|
|
465
|
-
expect(reRenders).toBeGreaterThan(initialRenders)
|
|
466
|
-
|
|
467
|
-
const { result: result2 } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
468
|
-
await waitFor(() => {
|
|
469
|
-
expect(result2.current?.[0]?.length).toBe(50)
|
|
470
|
-
})
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
it('should handle update of deep fields with deep id', async () => {
|
|
474
|
-
interface DeepItem {
|
|
475
|
-
person: {
|
|
476
|
-
id: string
|
|
477
|
-
name: string
|
|
478
|
-
age: number
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
const sql = createSqliteState<DeepItem>({ backend, tableName: 'State10', key: 'person.id' })
|
|
482
|
-
let reRenders = 0
|
|
483
|
-
const { result } = renderHook(() => {
|
|
484
|
-
reRenders++
|
|
485
|
-
return useSqliteValue(sql, { sortBy: 'person.age' }, [])
|
|
486
|
-
})
|
|
487
|
-
|
|
488
|
-
await waitFor(() => {
|
|
489
|
-
expect(result.current?.[0]?.length).toBe(0)
|
|
490
|
-
})
|
|
491
|
-
const initialRenders = reRenders
|
|
492
|
-
|
|
493
|
-
act(() => {
|
|
494
|
-
sql.set({ person: { id: 'some_id', name: 'Alice', age: 30 } })
|
|
495
|
-
})
|
|
496
|
-
await waitFor(() => {
|
|
497
|
-
expect(result.current[0]).toEqual([{ person: { id: 'some_id', name: 'Alice', age: 30 } }])
|
|
498
|
-
})
|
|
499
|
-
const afterFirstSet = reRenders
|
|
500
|
-
|
|
501
|
-
// update deep field
|
|
502
|
-
act(() => {
|
|
503
|
-
sql.set({ person: { id: 'some_id', name: 'Alice', age: 31 } })
|
|
504
|
-
})
|
|
505
|
-
await waitFor(() => {
|
|
506
|
-
expect(result.current[0]).toEqual([{ person: { id: 'some_id', name: 'Alice', age: 31 } }])
|
|
507
|
-
})
|
|
508
|
-
|
|
509
|
-
// Each set should trigger re-renders
|
|
510
|
-
expect(afterFirstSet).toBeGreaterThan(initialRenders)
|
|
511
|
-
expect(reRenders).toBeGreaterThan(afterFirstSet)
|
|
512
|
-
|
|
513
|
-
// update same field
|
|
514
|
-
act(() => {
|
|
515
|
-
sql.set({ person: { id: 'some_id', name: 'Alice', age: 31 } })
|
|
516
|
-
})
|
|
517
|
-
// should not re-render
|
|
518
|
-
await waitFor(() => {
|
|
519
|
-
expect(result.current[0]).toEqual([{ person: { id: 'some_id', name: 'Alice', age: 31 } }])
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
// add another item
|
|
523
|
-
})
|
|
524
|
-
it('should test reset', async () => {
|
|
525
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State11', key: 'id' })
|
|
526
|
-
let reRenders = 0
|
|
527
|
-
|
|
528
|
-
await sql.set({ id: 'initial', name: 'initial', age: 1 })
|
|
529
|
-
const { result } = renderHook(() => {
|
|
530
|
-
reRenders++
|
|
531
|
-
// eslint-disable-next-line unicorn/prevent-abbreviations
|
|
532
|
-
const res = useSqliteValue(sql, {}, [])
|
|
533
|
-
return res
|
|
534
|
-
})
|
|
535
|
-
|
|
536
|
-
await waitFor(() => {
|
|
537
|
-
expect(result.current?.[0]?.length).toBe(1)
|
|
538
|
-
})
|
|
539
|
-
const initialRenders = reRenders
|
|
540
|
-
|
|
541
|
-
act(() => {
|
|
542
|
-
sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
543
|
-
})
|
|
544
|
-
await waitFor(() => {
|
|
545
|
-
expect(result.current?.[0]?.length).toBe(2)
|
|
546
|
-
})
|
|
547
|
-
const afterSet = reRenders
|
|
548
|
-
|
|
549
|
-
act(() => {
|
|
550
|
-
result.current[1].reset()
|
|
551
|
-
})
|
|
552
|
-
await waitFor(() => {
|
|
553
|
-
expect(result.current?.[0]?.length).toBe(2)
|
|
554
|
-
})
|
|
555
|
-
|
|
556
|
-
// Set and reset should trigger re-renders
|
|
557
|
-
expect(afterSet).toBeGreaterThan(initialRenders)
|
|
558
|
-
expect(reRenders).toBeGreaterThan(afterSet)
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
it('should handle no items in the database', async () => {
|
|
562
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'EmptyState', key: 'id' })
|
|
563
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
564
|
-
|
|
565
|
-
await waitFor(() => {
|
|
566
|
-
expect(result.current[0]).toEqual([])
|
|
567
|
-
})
|
|
568
|
-
})
|
|
569
|
-
|
|
570
|
-
it('should handle fewer items than page size', async () => {
|
|
571
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'FewItemsState', key: 'id' })
|
|
572
|
-
await sql.batchSet([
|
|
573
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
574
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
575
|
-
])
|
|
576
|
-
|
|
577
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
578
|
-
|
|
579
|
-
await waitFor(() => {
|
|
580
|
-
expect(result.current[0]).toEqual([
|
|
581
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
582
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
583
|
-
])
|
|
584
|
-
})
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
it('should handle exactly page size items', async () => {
|
|
588
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'ExactPageSizeState', key: 'id' })
|
|
589
|
-
const items = Array.from({ length: DEFAULT_PAGE_SIZE }, (_, index) => ({
|
|
590
|
-
id: `${index + 1}`,
|
|
591
|
-
name: `Person${index + 1}`,
|
|
592
|
-
age: 20 + (index % 50),
|
|
593
|
-
}))
|
|
594
|
-
await sql.batchSet(items)
|
|
595
|
-
|
|
596
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
597
|
-
|
|
598
|
-
await waitFor(() => {
|
|
599
|
-
expect(result.current[0]?.length).toBe(DEFAULT_PAGE_SIZE)
|
|
600
|
-
})
|
|
601
|
-
})
|
|
602
|
-
|
|
603
|
-
it('should have thousands items, and update in middle check', async () => {
|
|
604
|
-
let reRenders = 0
|
|
605
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'ManyItemsState', key: 'id' })
|
|
606
|
-
const ITEMS_COUNT = 1000
|
|
607
|
-
const people: Person[] = []
|
|
608
|
-
for (let index = 1; index <= ITEMS_COUNT; index++) {
|
|
609
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
610
|
-
}
|
|
611
|
-
await sql.batchSet(people)
|
|
612
|
-
|
|
613
|
-
const { result } = renderHook(() => {
|
|
614
|
-
reRenders++
|
|
615
|
-
return useSqliteValue(sql, { pageSize: 100 }, [])
|
|
616
|
-
})
|
|
617
|
-
|
|
618
|
-
await waitFor(() => {
|
|
619
|
-
expect(result.current[0]?.length).toBe(100)
|
|
620
|
-
})
|
|
621
|
-
const initialRenders = reRenders
|
|
622
|
-
|
|
623
|
-
act(() => {
|
|
624
|
-
for (let index = 0; index < (ITEMS_COUNT - 100) / 100; index++) {
|
|
625
|
-
result.current[1].nextPage()
|
|
626
|
-
}
|
|
627
|
-
})
|
|
628
|
-
await waitFor(() => {
|
|
629
|
-
expect(result.current[0]?.length).toBe(ITEMS_COUNT)
|
|
630
|
-
})
|
|
631
|
-
const afterPagination = reRenders
|
|
632
|
-
|
|
633
|
-
act(() => {
|
|
634
|
-
sql.set({ id: '500', name: 'UpdatedPerson500', age: 99 })
|
|
635
|
-
})
|
|
636
|
-
|
|
637
|
-
await waitFor(() => {
|
|
638
|
-
const updated = result.current[0]?.find((p) => p.id === '500')
|
|
639
|
-
expect(updated).toEqual({ id: '500', name: 'UpdatedPerson500', age: 99 })
|
|
640
|
-
expect(result.current[0]?.length).toBe(ITEMS_COUNT)
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
// Pagination and update should cause re-renders
|
|
644
|
-
expect(afterPagination).toBeGreaterThan(initialRenders)
|
|
645
|
-
expect(reRenders).toBeGreaterThan(afterPagination)
|
|
646
|
-
})
|
|
647
|
-
it("should test batch delete and its impact on the hook's results", async () => {
|
|
648
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'BatchDeleteState', key: 'id' })
|
|
649
|
-
const people: Person[] = []
|
|
650
|
-
for (let index = 1; index <= 20; index++) {
|
|
651
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
652
|
-
}
|
|
653
|
-
await sql.batchSet(people)
|
|
654
|
-
|
|
655
|
-
let reRenders = 0
|
|
656
|
-
const { result } = renderHook(() => {
|
|
657
|
-
reRenders++
|
|
658
|
-
return useSqliteValue(sql, {}, [])
|
|
659
|
-
})
|
|
660
|
-
|
|
661
|
-
await waitFor(() => {
|
|
662
|
-
expect(result.current[0]?.length).toBe(20)
|
|
663
|
-
})
|
|
664
|
-
const initialRenders = reRenders
|
|
665
|
-
|
|
666
|
-
act(() => {
|
|
667
|
-
sql.batchDelete(['5', '10', '15'])
|
|
668
|
-
})
|
|
669
|
-
|
|
670
|
-
await waitFor(() => {
|
|
671
|
-
expect(result.current[0]?.length).toBe(17)
|
|
672
|
-
expect(result.current[0]?.find((p) => p.id === '5')).toBeUndefined()
|
|
673
|
-
expect(result.current[0]?.find((p) => p.id === '10')).toBeUndefined()
|
|
674
|
-
expect(result.current[0]?.find((p) => p.id === '15')).toBeUndefined()
|
|
675
|
-
})
|
|
676
|
-
// Batch delete should trigger re-render
|
|
677
|
-
expect(reRenders).toBeGreaterThan(initialRenders)
|
|
678
|
-
})
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
/* eslint-disable no-console */
|
|
682
|
-
describe('use-sqlite-state performance benchmarks', () => {
|
|
683
|
-
describe('timing benchmarks', () => {
|
|
684
|
-
it('benchmark: initial load 100 items', async () => {
|
|
685
|
-
const testBackend = bunMemoryBackend()
|
|
686
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'Bench100', key: 'id', indexes: ['age'] })
|
|
687
|
-
await sql.batchSet(generatePeople(100))
|
|
688
|
-
|
|
689
|
-
const start = performance.now()
|
|
690
|
-
const { result } = renderHook(() => useSqliteValue(sql, { pageSize: 100 }, []))
|
|
691
|
-
|
|
692
|
-
await waitFor(() => {
|
|
693
|
-
expect(result.current[0]?.length).toBe(100)
|
|
694
|
-
})
|
|
695
|
-
|
|
696
|
-
const duration = performance.now() - start
|
|
697
|
-
console.log('📊 100 items initial load:', duration.toFixed(2), 'ms')
|
|
698
|
-
expect(duration).toBeLessThan(500)
|
|
699
|
-
})
|
|
700
|
-
|
|
701
|
-
it('benchmark: initial load 1000 items', async () => {
|
|
702
|
-
const testBackend = bunMemoryBackend()
|
|
703
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'Bench1000', key: 'id', indexes: ['age'] })
|
|
704
|
-
await sql.batchSet(generatePeople(1000))
|
|
705
|
-
|
|
706
|
-
const start = performance.now()
|
|
707
|
-
const { result } = renderHook(() => useSqliteValue(sql, { pageSize: 1000 }, []))
|
|
708
|
-
|
|
709
|
-
await waitFor(() => {
|
|
710
|
-
expect(result.current[0]?.length).toBe(1000)
|
|
711
|
-
})
|
|
712
|
-
|
|
713
|
-
const duration = performance.now() - start
|
|
714
|
-
console.log('📊 1000 items initial load:', duration.toFixed(2), 'ms')
|
|
715
|
-
expect(duration).toBeLessThan(2000)
|
|
716
|
-
})
|
|
717
|
-
|
|
718
|
-
it('benchmark: initial load 5000 items', async () => {
|
|
719
|
-
const testBackend = bunMemoryBackend()
|
|
720
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'Bench5000', key: 'id', indexes: ['age'] })
|
|
721
|
-
await sql.batchSet(generatePeople(5000))
|
|
722
|
-
|
|
723
|
-
const start = performance.now()
|
|
724
|
-
const { result } = renderHook(() => useSqliteValue(sql, { pageSize: 5000 }, []))
|
|
725
|
-
|
|
726
|
-
await waitFor(() => {
|
|
727
|
-
expect(result.current[0]?.length).toBe(5000)
|
|
728
|
-
})
|
|
729
|
-
|
|
730
|
-
const duration = performance.now() - start
|
|
731
|
-
console.log('📊 5000 items initial load:', duration.toFixed(2), 'ms')
|
|
732
|
-
expect(duration).toBeLessThan(5000)
|
|
733
|
-
})
|
|
734
|
-
|
|
735
|
-
it('benchmark: where clause filtering on 5000 items', async () => {
|
|
736
|
-
const testBackend = bunMemoryBackend()
|
|
737
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'BenchWhere5000', key: 'id', indexes: ['age'] })
|
|
738
|
-
await sql.batchSet(generatePeople(5000))
|
|
739
|
-
|
|
740
|
-
const start = performance.now()
|
|
741
|
-
const { result } = renderHook(() => useSqliteValue(sql, { where: { age: { gte: 50 } }, pageSize: 5000 }, []))
|
|
742
|
-
|
|
743
|
-
await waitFor(() => {
|
|
744
|
-
expect(result.current[0]?.length).toBeGreaterThan(0)
|
|
745
|
-
})
|
|
746
|
-
|
|
747
|
-
const duration = performance.now() - start
|
|
748
|
-
console.log('📊 5000 items WHERE filter:', duration.toFixed(2), 'ms', '| matched:', result.current[0]?.length)
|
|
749
|
-
expect(duration).toBeLessThan(2000)
|
|
750
|
-
})
|
|
751
|
-
|
|
752
|
-
it('benchmark: rapid sequential updates', async () => {
|
|
753
|
-
const testBackend = bunMemoryBackend()
|
|
754
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'BenchRapid', key: 'id', indexes: ['age'] })
|
|
755
|
-
await sql.batchSet(generatePeople(100))
|
|
756
|
-
|
|
757
|
-
const { result } = renderHook(() => useSqliteValue(sql, { pageSize: 200 }, []))
|
|
758
|
-
|
|
759
|
-
await waitFor(() => {
|
|
760
|
-
expect(result.current[0]?.length).toBe(100)
|
|
761
|
-
})
|
|
762
|
-
|
|
763
|
-
const updateCount = 50
|
|
764
|
-
const start = performance.now()
|
|
765
|
-
|
|
766
|
-
for (let index = 0; index < updateCount; index++) {
|
|
767
|
-
await act(async () => {
|
|
768
|
-
await sql.set({ id: `rapid-${index}`, name: `Rapid ${index}`, age: 25 })
|
|
769
|
-
})
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
await waitFor(() => {
|
|
773
|
-
expect(result.current[0]?.length).toBe(150)
|
|
774
|
-
})
|
|
775
|
-
|
|
776
|
-
const duration = performance.now() - start
|
|
777
|
-
console.log(
|
|
778
|
-
'📊',
|
|
779
|
-
updateCount,
|
|
780
|
-
'rapid inserts:',
|
|
781
|
-
duration.toFixed(2),
|
|
782
|
-
'ms',
|
|
783
|
-
'| avg:',
|
|
784
|
-
(duration / updateCount).toFixed(2),
|
|
785
|
-
'ms/op',
|
|
786
|
-
)
|
|
787
|
-
expect(duration).toBeLessThan(3000)
|
|
788
|
-
})
|
|
789
|
-
|
|
790
|
-
it('benchmark: pagination load all pages', async () => {
|
|
791
|
-
const testBackend = bunMemoryBackend()
|
|
792
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'BenchPagination', key: 'id', indexes: ['age'] })
|
|
793
|
-
const totalItems = 500
|
|
794
|
-
const pageSize = 50
|
|
795
|
-
await sql.batchSet(generatePeople(totalItems))
|
|
796
|
-
|
|
797
|
-
const { result } = renderHook(() => useSqliteValue(sql, { pageSize }, []))
|
|
798
|
-
|
|
799
|
-
await waitFor(() => {
|
|
800
|
-
expect(result.current[0]?.length).toBe(pageSize)
|
|
801
|
-
})
|
|
802
|
-
|
|
803
|
-
const start = performance.now()
|
|
804
|
-
const totalPages = Math.ceil(totalItems / pageSize) - 1
|
|
805
|
-
|
|
806
|
-
for (let page = 0; page < totalPages; page++) {
|
|
807
|
-
await act(async () => {
|
|
808
|
-
await result.current[1].nextPage()
|
|
809
|
-
})
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
await waitFor(() => {
|
|
813
|
-
expect(result.current[0]?.length).toBe(totalItems)
|
|
814
|
-
})
|
|
815
|
-
|
|
816
|
-
const duration = performance.now() - start
|
|
817
|
-
console.log(
|
|
818
|
-
'📊',
|
|
819
|
-
totalPages,
|
|
820
|
-
'page loads:',
|
|
821
|
-
duration.toFixed(2),
|
|
822
|
-
'ms',
|
|
823
|
-
'| avg:',
|
|
824
|
-
(duration / totalPages).toFixed(2),
|
|
825
|
-
'ms/page',
|
|
826
|
-
)
|
|
827
|
-
expect(duration).toBeLessThan(2000)
|
|
828
|
-
})
|
|
829
|
-
})
|
|
830
|
-
|
|
831
|
-
describe('re-render analysis', () => {
|
|
832
|
-
it('analyze: re-renders on initial load', async () => {
|
|
833
|
-
const testBackend = bunMemoryBackend()
|
|
834
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'RenderInit', key: 'id' })
|
|
835
|
-
await sql.batchSet(generatePeople(50))
|
|
836
|
-
|
|
837
|
-
let renderCount = 0
|
|
838
|
-
const { result } = renderHook(() => {
|
|
839
|
-
renderCount++
|
|
840
|
-
return useSqliteValue(sql, { pageSize: 50 }, [])
|
|
841
|
-
})
|
|
842
|
-
|
|
843
|
-
await waitFor(() => {
|
|
844
|
-
expect(result.current[0]?.length).toBe(50)
|
|
845
|
-
})
|
|
846
|
-
|
|
847
|
-
console.log('🔄 Initial load renders:', renderCount)
|
|
848
|
-
expect(renderCount).toBeLessThanOrEqual(3)
|
|
849
|
-
})
|
|
850
|
-
|
|
851
|
-
it('analyze: re-renders on single insert', async () => {
|
|
852
|
-
const testBackend = bunMemoryBackend()
|
|
853
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'RenderInsert', key: 'id' })
|
|
854
|
-
await sql.batchSet(generatePeople(10))
|
|
855
|
-
|
|
856
|
-
let renderCount = 0
|
|
857
|
-
const { result } = renderHook(() => {
|
|
858
|
-
renderCount++
|
|
859
|
-
return useSqliteValue(sql, { pageSize: 50 }, [])
|
|
860
|
-
})
|
|
861
|
-
|
|
862
|
-
await waitFor(() => {
|
|
863
|
-
expect(result.current[0]?.length).toBe(10)
|
|
864
|
-
})
|
|
865
|
-
|
|
866
|
-
const rendersBefore = renderCount
|
|
867
|
-
|
|
868
|
-
await act(async () => {
|
|
869
|
-
await sql.set({ id: 'new-item', name: 'New Person', age: 30 })
|
|
870
|
-
})
|
|
871
|
-
|
|
872
|
-
await waitFor(() => {
|
|
873
|
-
expect(result.current[0]?.length).toBe(11)
|
|
874
|
-
})
|
|
875
|
-
|
|
876
|
-
const rendersForInsert = renderCount - rendersBefore
|
|
877
|
-
console.log('🔄 Single insert renders:', rendersForInsert)
|
|
878
|
-
expect(rendersForInsert).toBeLessThanOrEqual(2)
|
|
879
|
-
})
|
|
880
|
-
|
|
881
|
-
it('analyze: re-renders on update with same data (shallow equal)', async () => {
|
|
882
|
-
const testBackend = bunMemoryBackend()
|
|
883
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'RenderShallow', key: 'id' })
|
|
884
|
-
await sql.set({ id: 'test-1', name: 'Test Person', age: 30 })
|
|
885
|
-
|
|
886
|
-
let renderCount = 0
|
|
887
|
-
const { result } = renderHook(() => {
|
|
888
|
-
renderCount++
|
|
889
|
-
return useSqliteValue(sql, { pageSize: 50 }, [])
|
|
890
|
-
})
|
|
891
|
-
|
|
892
|
-
await waitFor(() => {
|
|
893
|
-
expect(result.current[0]?.length).toBe(1)
|
|
894
|
-
})
|
|
895
|
-
|
|
896
|
-
const rendersBefore = renderCount
|
|
897
|
-
|
|
898
|
-
// Update with identical data
|
|
899
|
-
await act(async () => {
|
|
900
|
-
await sql.set({ id: 'test-1', name: 'Test Person', age: 30 })
|
|
901
|
-
})
|
|
902
|
-
|
|
903
|
-
// Small delay to ensure any async effects complete
|
|
904
|
-
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
905
|
-
|
|
906
|
-
const rendersForSameData = renderCount - rendersBefore
|
|
907
|
-
console.log('🔄 Same data update renders:', rendersForSameData, '(should be 0)')
|
|
908
|
-
expect(rendersForSameData).toBe(0)
|
|
909
|
-
})
|
|
910
|
-
|
|
911
|
-
it('analyze: re-renders on update with different data', async () => {
|
|
912
|
-
const testBackend = bunMemoryBackend()
|
|
913
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'RenderDiff', key: 'id' })
|
|
914
|
-
await sql.set({ id: 'test-1', name: 'Original', age: 30 })
|
|
915
|
-
|
|
916
|
-
let renderCount = 0
|
|
917
|
-
const { result } = renderHook(() => {
|
|
918
|
-
renderCount++
|
|
919
|
-
return useSqliteValue(sql, { pageSize: 50 }, [])
|
|
920
|
-
})
|
|
921
|
-
|
|
922
|
-
await waitFor(() => {
|
|
923
|
-
expect(result.current[0]?.[0]?.name).toBe('Original')
|
|
924
|
-
})
|
|
925
|
-
|
|
926
|
-
const rendersBefore = renderCount
|
|
927
|
-
|
|
928
|
-
await act(async () => {
|
|
929
|
-
await sql.set({ id: 'test-1', name: 'Updated', age: 31 })
|
|
930
|
-
})
|
|
931
|
-
|
|
932
|
-
await waitFor(() => {
|
|
933
|
-
expect(result.current[0]?.[0]?.name).toBe('Updated')
|
|
934
|
-
})
|
|
935
|
-
|
|
936
|
-
const rendersForUpdate = renderCount - rendersBefore
|
|
937
|
-
console.log('🔄 Different data update renders:', rendersForUpdate)
|
|
938
|
-
expect(rendersForUpdate).toBeLessThanOrEqual(2)
|
|
939
|
-
})
|
|
940
|
-
|
|
941
|
-
it('analyze: re-renders on batch operations', async () => {
|
|
942
|
-
const testBackend = bunMemoryBackend()
|
|
943
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'RenderBatch', key: 'id' })
|
|
944
|
-
await sql.batchSet(generatePeople(20))
|
|
945
|
-
|
|
946
|
-
let renderCount = 0
|
|
947
|
-
const { result } = renderHook(() => {
|
|
948
|
-
renderCount++
|
|
949
|
-
return useSqliteValue(sql, { pageSize: 50 }, [])
|
|
950
|
-
})
|
|
951
|
-
|
|
952
|
-
await waitFor(() => {
|
|
953
|
-
expect(result.current[0]?.length).toBe(20)
|
|
954
|
-
})
|
|
955
|
-
|
|
956
|
-
const rendersBefore = renderCount
|
|
957
|
-
|
|
958
|
-
// Batch delete 5 items
|
|
959
|
-
await act(async () => {
|
|
960
|
-
await sql.batchDelete(['person-0', 'person-1', 'person-2', 'person-3', 'person-4'])
|
|
961
|
-
})
|
|
962
|
-
|
|
963
|
-
await waitFor(() => {
|
|
964
|
-
expect(result.current[0]?.length).toBe(15)
|
|
965
|
-
})
|
|
966
|
-
|
|
967
|
-
const rendersForBatchDelete = renderCount - rendersBefore
|
|
968
|
-
console.log('🔄 Batch delete (5 items) renders:', rendersForBatchDelete)
|
|
969
|
-
// Batch operations should ideally cause minimal re-renders
|
|
970
|
-
expect(rendersForBatchDelete).toBeLessThanOrEqual(3)
|
|
971
|
-
})
|
|
972
|
-
|
|
973
|
-
it('analyze: re-renders on deps change', async () => {
|
|
974
|
-
const testBackend = bunMemoryBackend()
|
|
975
|
-
const sql = createSqliteState<Person>({ backend: testBackend, tableName: 'RenderDeps', key: 'id', indexes: ['age'] })
|
|
976
|
-
await sql.batchSet(generatePeople(100))
|
|
977
|
-
|
|
978
|
-
let renderCount = 0
|
|
979
|
-
const { result, rerender } = renderHook(
|
|
980
|
-
({ minAge }) => {
|
|
981
|
-
renderCount++
|
|
982
|
-
return useSqliteValue(sql, { where: { age: { gte: minAge } }, pageSize: 100 }, [minAge])
|
|
983
|
-
},
|
|
984
|
-
{ initialProps: { minAge: 50 } },
|
|
985
|
-
)
|
|
986
|
-
|
|
987
|
-
await waitFor(() => {
|
|
988
|
-
expect(result.current[0]?.length).toBeGreaterThan(0)
|
|
989
|
-
})
|
|
990
|
-
|
|
991
|
-
const rendersBefore = renderCount
|
|
992
|
-
|
|
993
|
-
act(() => {
|
|
994
|
-
rerender({ minAge: 60 })
|
|
995
|
-
})
|
|
996
|
-
|
|
997
|
-
await waitFor(() => {
|
|
998
|
-
// eslint-disable-next-line sonarjs/no-nested-functions
|
|
999
|
-
const minAgeInResults = Math.min(...(result.current[0]?.map((p) => p.age) ?? [0]))
|
|
1000
|
-
expect(minAgeInResults).toBeGreaterThanOrEqual(60)
|
|
1001
|
-
})
|
|
1002
|
-
|
|
1003
|
-
const rendersForDepsChange = renderCount - rendersBefore
|
|
1004
|
-
console.log('🔄 Deps change renders:', rendersForDepsChange)
|
|
1005
|
-
expect(rendersForDepsChange).toBeLessThanOrEqual(3)
|
|
1006
|
-
})
|
|
1007
|
-
})
|
|
1008
|
-
})
|