muya 2.5.4 → 2.5.6
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 +14 -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/scheduler.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 +2 -4
- 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/dist/types/scheduler.d.ts +25 -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 +31 -8
- package/src/create.ts +7 -2
- package/src/debug/development-tools.ts +5 -40
- package/src/index.ts +2 -1
- package/src/scheduler.ts +77 -71
- package/src/select.ts +7 -2
- package/src/use-value.ts +1 -1
- 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/scheduler.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/scheduler.d.ts +0 -20
- 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}/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,264 +0,0 @@
|
|
|
1
|
-
import { createSqliteState } from '../create-sqlite'
|
|
2
|
-
import { bunMemoryBackend } from '../table/bun-backend'
|
|
3
|
-
|
|
4
|
-
const backend = bunMemoryBackend()
|
|
5
|
-
interface Person {
|
|
6
|
-
id: string
|
|
7
|
-
name: string
|
|
8
|
-
age: number
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('create-sqlite-state', () => {
|
|
12
|
-
it('should batchSet and update multiple documents', async () => {
|
|
13
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State2', key: 'id' })
|
|
14
|
-
await sql.batchSet([
|
|
15
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
16
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
17
|
-
])
|
|
18
|
-
const all = []
|
|
19
|
-
for await (const p of sql.search()) all.push(p)
|
|
20
|
-
expect(all).toHaveLength(2)
|
|
21
|
-
// update both
|
|
22
|
-
await sql.batchSet([
|
|
23
|
-
{ id: '1', name: 'Alice2', age: 31 },
|
|
24
|
-
{ id: '2', name: 'Bob2', age: 26 },
|
|
25
|
-
])
|
|
26
|
-
const updated = []
|
|
27
|
-
for await (const p of sql.search()) updated.push(p)
|
|
28
|
-
expect(updated).toEqual([
|
|
29
|
-
{ id: '1', name: 'Alice2', age: 31 },
|
|
30
|
-
{ id: '2', name: 'Bob2', age: 26 },
|
|
31
|
-
])
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('should deleteBy condition', async () => {
|
|
35
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State3', key: 'id' })
|
|
36
|
-
await sql.batchSet([
|
|
37
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
38
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
39
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
40
|
-
])
|
|
41
|
-
const deleted = await sql.deleteBy({ age: { gt: 30 } })
|
|
42
|
-
expect(deleted.length).toBe(1)
|
|
43
|
-
const all = []
|
|
44
|
-
for await (const p of sql.search()) all.push(p)
|
|
45
|
-
expect(all.map((p) => p.id)).toEqual(['1', '2'])
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('should get by key and with selector', async () => {
|
|
49
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State4', key: 'id' })
|
|
50
|
-
await sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
51
|
-
const doc = await sql.get('1')
|
|
52
|
-
expect(doc).toEqual({ id: '1', name: 'Alice', age: 30 })
|
|
53
|
-
const name = await sql.get('1', (d) => d.name)
|
|
54
|
-
expect(name).toBe('Alice')
|
|
55
|
-
const missing = await sql.get('999')
|
|
56
|
-
expect(missing).toBeUndefined()
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('should count documents with and without where', async () => {
|
|
60
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State5', key: 'id' })
|
|
61
|
-
await sql.batchSet([
|
|
62
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
63
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
64
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
65
|
-
])
|
|
66
|
-
expect(await sql.count()).toBe(3)
|
|
67
|
-
expect(await sql.count({ where: { age: { gt: 30 } } })).toBe(1)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should support search with options', async () => {
|
|
71
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State6', key: 'id' })
|
|
72
|
-
await sql.batchSet([
|
|
73
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
74
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
75
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
76
|
-
])
|
|
77
|
-
const results = []
|
|
78
|
-
for await (const p of sql.search({ where: { age: { lt: 35 } } })) results.push(p)
|
|
79
|
-
expect(results.map((p) => p.id)).toEqual(['1', '2'])
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
interface Product {
|
|
84
|
-
id: string
|
|
85
|
-
name: string
|
|
86
|
-
category: string
|
|
87
|
-
price: number
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
describe('groupBy', () => {
|
|
91
|
-
it('should group by a simple field and count', async () => {
|
|
92
|
-
const sql = createSqliteState<Product>({ backend, tableName: 'GroupBy1', key: 'id' })
|
|
93
|
-
await sql.batchSet([
|
|
94
|
-
{ id: '1', name: 'Apple', category: 'fruit', price: 1 },
|
|
95
|
-
{ id: '2', name: 'Banana', category: 'fruit', price: 2 },
|
|
96
|
-
{ id: '3', name: 'Carrot', category: 'vegetable', price: 1 },
|
|
97
|
-
{ id: '4', name: 'Orange', category: 'fruit', price: 3 },
|
|
98
|
-
{ id: '5', name: 'Broccoli', category: 'vegetable', price: 2 },
|
|
99
|
-
])
|
|
100
|
-
|
|
101
|
-
const grouped = await sql.groupBy('category')
|
|
102
|
-
|
|
103
|
-
expect(grouped).toHaveLength(2)
|
|
104
|
-
const fruitGroup = grouped.find((g) => g.key === 'fruit')
|
|
105
|
-
const vegetableGroup = grouped.find((g) => g.key === 'vegetable')
|
|
106
|
-
expect(fruitGroup?.count).toBe(3)
|
|
107
|
-
expect(vegetableGroup?.count).toBe(2)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('should group by with where clause filter', async () => {
|
|
111
|
-
const sql = createSqliteState<Product>({ backend, tableName: 'GroupBy2', key: 'id' })
|
|
112
|
-
await sql.batchSet([
|
|
113
|
-
{ id: '1', name: 'Apple', category: 'fruit', price: 1 },
|
|
114
|
-
{ id: '2', name: 'Banana', category: 'fruit', price: 5 },
|
|
115
|
-
{ id: '3', name: 'Carrot', category: 'vegetable', price: 1 },
|
|
116
|
-
{ id: '4', name: 'Orange', category: 'fruit', price: 3 },
|
|
117
|
-
{ id: '5', name: 'Broccoli', category: 'vegetable', price: 6 },
|
|
118
|
-
])
|
|
119
|
-
|
|
120
|
-
// Only group items with price > 2
|
|
121
|
-
const grouped = await sql.groupBy('category', { where: { price: { gt: 2 } } })
|
|
122
|
-
|
|
123
|
-
expect(grouped).toHaveLength(2)
|
|
124
|
-
const fruitGroup = grouped.find((g) => g.key === 'fruit')
|
|
125
|
-
const vegetableGroup = grouped.find((g) => g.key === 'vegetable')
|
|
126
|
-
expect(fruitGroup?.count).toBe(2) // Banana (5), Orange (3)
|
|
127
|
-
expect(vegetableGroup?.count).toBe(1) // Broccoli (6)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('should group by numeric field', async () => {
|
|
131
|
-
const sql = createSqliteState<Product>({ backend, tableName: 'GroupBy3', key: 'id' })
|
|
132
|
-
await sql.batchSet([
|
|
133
|
-
{ id: '1', name: 'Apple', category: 'fruit', price: 1 },
|
|
134
|
-
{ id: '2', name: 'Banana', category: 'fruit', price: 2 },
|
|
135
|
-
{ id: '3', name: 'Carrot', category: 'vegetable', price: 1 },
|
|
136
|
-
{ id: '4', name: 'Orange', category: 'fruit', price: 1 },
|
|
137
|
-
])
|
|
138
|
-
|
|
139
|
-
const grouped = await sql.groupBy('price')
|
|
140
|
-
|
|
141
|
-
expect(grouped).toHaveLength(2)
|
|
142
|
-
const price1 = grouped.find((g) => g.key === 1)
|
|
143
|
-
const price2 = grouped.find((g) => g.key === 2)
|
|
144
|
-
expect(price1?.count).toBe(3)
|
|
145
|
-
expect(price2?.count).toBe(1)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('should return empty array for empty table', async () => {
|
|
149
|
-
const sql = createSqliteState<Product>({ backend, tableName: 'GroupBy4', key: 'id' })
|
|
150
|
-
|
|
151
|
-
const grouped = await sql.groupBy('category')
|
|
152
|
-
|
|
153
|
-
expect(grouped).toEqual([])
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('should handle null/undefined values in grouped field', async () => {
|
|
157
|
-
interface ItemWithOptional {
|
|
158
|
-
id: string
|
|
159
|
-
name: string
|
|
160
|
-
tag?: string
|
|
161
|
-
}
|
|
162
|
-
const sql = createSqliteState<ItemWithOptional>({ backend, tableName: 'GroupBy5', key: 'id' })
|
|
163
|
-
await sql.batchSet([
|
|
164
|
-
{ id: '1', name: 'A', tag: 'red' },
|
|
165
|
-
{ id: '2', name: 'B', tag: 'blue' },
|
|
166
|
-
{ id: '3', name: 'C' }, // no tag
|
|
167
|
-
{ id: '4', name: 'D', tag: 'red' },
|
|
168
|
-
])
|
|
169
|
-
|
|
170
|
-
const grouped = await sql.groupBy('tag')
|
|
171
|
-
|
|
172
|
-
// Should have 3 groups: red, blue, and null/undefined
|
|
173
|
-
expect(grouped.length).toBeGreaterThanOrEqual(2)
|
|
174
|
-
const redGroup = grouped.find((g) => g.key === 'red')
|
|
175
|
-
const blueGroup = grouped.find((g) => g.key === 'blue')
|
|
176
|
-
expect(redGroup?.count).toBe(2)
|
|
177
|
-
expect(blueGroup?.count).toBe(1)
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
it('should verify count matches sum of grouped counts', async () => {
|
|
181
|
-
const sql = createSqliteState<Product>({ backend, tableName: 'GroupBy6', key: 'id' })
|
|
182
|
-
await sql.batchSet([
|
|
183
|
-
{ id: '1', name: 'Apple', category: 'fruit', price: 1 },
|
|
184
|
-
{ id: '2', name: 'Banana', category: 'fruit', price: 2 },
|
|
185
|
-
{ id: '3', name: 'Carrot', category: 'vegetable', price: 1 },
|
|
186
|
-
{ id: '4', name: 'Orange', category: 'fruit', price: 3 },
|
|
187
|
-
{ id: '5', name: 'Broccoli', category: 'vegetable', price: 2 },
|
|
188
|
-
])
|
|
189
|
-
|
|
190
|
-
const totalCount = await sql.count()
|
|
191
|
-
const grouped = await sql.groupBy('category')
|
|
192
|
-
const sumOfCounts = grouped.reduce((sum, group) => sum + group.count, 0)
|
|
193
|
-
|
|
194
|
-
expect(totalCount).toBe(5)
|
|
195
|
-
expect(sumOfCounts).toBe(totalCount)
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('should verify count with where matches grouped count with same where', async () => {
|
|
199
|
-
const sql = createSqliteState<Product>({ backend, tableName: 'GroupBy7', key: 'id' })
|
|
200
|
-
await sql.batchSet([
|
|
201
|
-
{ id: '1', name: 'Apple', category: 'fruit', price: 1 },
|
|
202
|
-
{ id: '2', name: 'Banana', category: 'fruit', price: 5 },
|
|
203
|
-
{ id: '3', name: 'Carrot', category: 'vegetable', price: 1 },
|
|
204
|
-
{ id: '4', name: 'Orange', category: 'fruit', price: 3 },
|
|
205
|
-
{ id: '5', name: 'Broccoli', category: 'vegetable', price: 6 },
|
|
206
|
-
])
|
|
207
|
-
|
|
208
|
-
const whereClause = { price: { gt: 2 } }
|
|
209
|
-
const filteredCount = await sql.count({ where: whereClause })
|
|
210
|
-
const grouped = await sql.groupBy('category', { where: whereClause })
|
|
211
|
-
const sumOfCounts = grouped.reduce((sum, group) => sum + group.count, 0)
|
|
212
|
-
|
|
213
|
-
expect(filteredCount).toBe(3) // Banana (5), Orange (3), Broccoli (6)
|
|
214
|
-
expect(sumOfCounts).toBe(filteredCount)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('should have proper TypeScript inference for key type', async () => {
|
|
218
|
-
const sql = createSqliteState<Product>({ backend, tableName: 'GroupBy8', key: 'id' })
|
|
219
|
-
await sql.batchSet([
|
|
220
|
-
{ id: '1', name: 'Apple', category: 'fruit', price: 1 },
|
|
221
|
-
{ id: '2', name: 'Banana', category: 'fruit', price: 2 },
|
|
222
|
-
])
|
|
223
|
-
|
|
224
|
-
// Group by string field - key should be string
|
|
225
|
-
const categoryGroups = await sql.groupBy('category')
|
|
226
|
-
const categoryKey: string = categoryGroups[0].key // TypeScript should infer string
|
|
227
|
-
expect(typeof categoryKey).toBe('string')
|
|
228
|
-
|
|
229
|
-
// Group by number field - key should be number
|
|
230
|
-
const priceGroups = await sql.groupBy('price')
|
|
231
|
-
const priceKey: number = priceGroups[0].key // TypeScript should infer number
|
|
232
|
-
expect(typeof priceKey).toBe('number')
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('should infer nested field types correctly', async () => {
|
|
236
|
-
interface NestedProduct {
|
|
237
|
-
id: string
|
|
238
|
-
details: {
|
|
239
|
-
category: string
|
|
240
|
-
info: {
|
|
241
|
-
rating: number
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
const sql = createSqliteState<NestedProduct>({ backend, tableName: 'GroupBy9', key: 'id' })
|
|
246
|
-
await sql.batchSet([
|
|
247
|
-
{ id: '1', details: { category: 'A', info: { rating: 5 } } },
|
|
248
|
-
{ id: '2', details: { category: 'A', info: { rating: 3 } } },
|
|
249
|
-
{ id: '3', details: { category: 'B', info: { rating: 4 } } },
|
|
250
|
-
])
|
|
251
|
-
|
|
252
|
-
// Group by nested string field
|
|
253
|
-
const categoryGroups = await sql.groupBy('details.category')
|
|
254
|
-
const nestedKey: string = categoryGroups[0].key
|
|
255
|
-
expect(typeof nestedKey).toBe('string')
|
|
256
|
-
expect(categoryGroups).toHaveLength(2)
|
|
257
|
-
|
|
258
|
-
// Group by deeply nested number field
|
|
259
|
-
const ratingGroups = await sql.groupBy('details.info.rating')
|
|
260
|
-
const ratingKey: number = ratingGroups[0].key
|
|
261
|
-
expect(typeof ratingKey).toBe('number')
|
|
262
|
-
expect(ratingGroups).toHaveLength(3)
|
|
263
|
-
})
|
|
264
|
-
})
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { MapDeque } from '../table/map-deque'
|
|
2
|
-
|
|
3
|
-
describe('MapDeque', () => {
|
|
4
|
-
it('should throw if maxSize <= 0', () => {
|
|
5
|
-
expect(() => new MapDeque(0)).toThrow(RangeError)
|
|
6
|
-
expect(() => new MapDeque(-1)).toThrow(RangeError)
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
it('should add items up to maxSize', () => {
|
|
10
|
-
const deque = new MapDeque<string, number>(2)
|
|
11
|
-
deque.set('a', 1)
|
|
12
|
-
deque.set('b', 2)
|
|
13
|
-
expect(deque.size).toBe(2)
|
|
14
|
-
expect(deque.get('a')).toBe(1)
|
|
15
|
-
expect(deque.get('b')).toBe(2)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('should evict the oldest item when maxSize is exceeded', () => {
|
|
19
|
-
const deque = new MapDeque<string, number>(2)
|
|
20
|
-
deque.set('a', 1)
|
|
21
|
-
deque.set('b', 2)
|
|
22
|
-
deque.set('c', 3)
|
|
23
|
-
expect(deque.size).toBe(2)
|
|
24
|
-
expect(deque.has('a')).toBe(false)
|
|
25
|
-
expect(deque.get('b')).toBe(2)
|
|
26
|
-
expect(deque.get('c')).toBe(3)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('should update value if key already exists and not evict', () => {
|
|
30
|
-
const deque = new MapDeque<string, number>(2)
|
|
31
|
-
deque.set('a', 1)
|
|
32
|
-
deque.set('b', 2)
|
|
33
|
-
// eslint-disable-next-line sonarjs/no-element-overwrite
|
|
34
|
-
deque.set('a', 42)
|
|
35
|
-
expect(deque.size).toBe(2)
|
|
36
|
-
expect(deque.get('a')).toBe(42)
|
|
37
|
-
expect(deque.get('b')).toBe(2)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('should work with initial entries', () => {
|
|
41
|
-
const entries: Array<[string, number]> = [
|
|
42
|
-
['x', 10],
|
|
43
|
-
['y', 20],
|
|
44
|
-
]
|
|
45
|
-
const deque = new MapDeque<string, number>(3, entries)
|
|
46
|
-
expect(deque.size).toBe(2)
|
|
47
|
-
expect(deque.get('x')).toBe(10)
|
|
48
|
-
expect(deque.get('y')).toBe(20)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('should evict in insertion order, not key order', () => {
|
|
52
|
-
const deque = new MapDeque<string, number>(2)
|
|
53
|
-
deque.set('b', 1)
|
|
54
|
-
deque.set('a', 2)
|
|
55
|
-
deque.set('c', 3)
|
|
56
|
-
expect(deque.size).toBe(2)
|
|
57
|
-
expect(deque.has('b')).toBe(false)
|
|
58
|
-
expect(deque.has('a')).toBe(true)
|
|
59
|
-
expect(deque.has('c')).toBe(true)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-shadow */
|
|
2
|
-
/* eslint-disable no-shadow */
|
|
3
|
-
/* eslint-disable sonarjs/pseudo-random */
|
|
4
|
-
import { bunMemoryBackend } from '../table/bun-backend'
|
|
5
|
-
import { createTable } from '../table/table'
|
|
6
|
-
|
|
7
|
-
interface Person {
|
|
8
|
-
name: string
|
|
9
|
-
age: number
|
|
10
|
-
city: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface PersonNested {
|
|
14
|
-
info: {
|
|
15
|
-
name: string
|
|
16
|
-
age: number
|
|
17
|
-
city: string
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
describe('table', () => {
|
|
21
|
-
let backend = bunMemoryBackend()
|
|
22
|
-
let table: ReturnType<typeof createTable<Person>> extends Promise<infer T> ? T : never
|
|
23
|
-
|
|
24
|
-
let tableNested: ReturnType<typeof createTable<PersonNested>> extends Promise<infer T> ? T : never
|
|
25
|
-
|
|
26
|
-
beforeEach(async () => {
|
|
27
|
-
backend = bunMemoryBackend()
|
|
28
|
-
table = await createTable<Person>({
|
|
29
|
-
backend,
|
|
30
|
-
tableName: 'TestTable',
|
|
31
|
-
key: 'name',
|
|
32
|
-
})
|
|
33
|
-
tableNested = await createTable<PersonNested>({
|
|
34
|
-
backend,
|
|
35
|
-
tableName: 'TestTableNested',
|
|
36
|
-
key: 'info.name',
|
|
37
|
-
indexes: ['info.age', 'info.city'],
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('should set and get items', async () => {
|
|
42
|
-
const mutation = await table.set({ name: 'Alice', age: 30, city: 'Paris' })
|
|
43
|
-
expect(mutation.key).toBe('Alice')
|
|
44
|
-
expect(mutation.op).toBe('insert')
|
|
45
|
-
const result = await table.get('Alice')
|
|
46
|
-
expect(result).toEqual({ name: 'Alice', age: 30, city: 'Paris' })
|
|
47
|
-
|
|
48
|
-
const updateMutation = await table.set({ name: 'Alice', age: 31, city: 'Paris' })
|
|
49
|
-
expect(updateMutation.key).toBe('Alice')
|
|
50
|
-
expect(updateMutation.op).toBe('update')
|
|
51
|
-
const updatedResult = await table.get('Alice')
|
|
52
|
-
expect(updatedResult).toEqual({ name: 'Alice', age: 31, city: 'Paris' })
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('should set and get nested key', async () => {
|
|
56
|
-
const mutation = await tableNested.set({ info: { name: 'Bob', age: 25, city: 'London' } })
|
|
57
|
-
expect(mutation.key).toBe('Bob')
|
|
58
|
-
expect(mutation.op).toBe('insert')
|
|
59
|
-
const result = await tableNested.get('Bob')
|
|
60
|
-
expect(result).toEqual({ info: { name: 'Bob', age: 25, city: 'London' } })
|
|
61
|
-
|
|
62
|
-
const updateMutation = await tableNested.set({ info: { name: 'Bob', age: 26, city: 'London' } })
|
|
63
|
-
expect(updateMutation.key).toBe('Bob')
|
|
64
|
-
expect(updateMutation.op).toBe('update')
|
|
65
|
-
const updatedResult = await tableNested.get('Bob')
|
|
66
|
-
expect(updatedResult).toEqual({ info: { name: 'Bob', age: 26, city: 'London' } })
|
|
67
|
-
|
|
68
|
-
// nested where query
|
|
69
|
-
const results: PersonNested[] = []
|
|
70
|
-
for await (const person of tableNested.search({
|
|
71
|
-
where: { info: { city: { like: 'London' } } },
|
|
72
|
-
})) {
|
|
73
|
-
results.push(person)
|
|
74
|
-
}
|
|
75
|
-
expect(results.length).toBe(1)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('should count items and count with where', async () => {
|
|
79
|
-
await table.set({ name: 'Alice', age: 30, city: 'Paris' })
|
|
80
|
-
await table.set({ name: 'Bob', age: 25, city: 'London' })
|
|
81
|
-
expect(await table.count()).toBe(2)
|
|
82
|
-
expect(await table.count({ where: { city: 'Paris' } })).toBe(1)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('should search with ordering, limit and offset', async () => {
|
|
86
|
-
const people: Person[] = [
|
|
87
|
-
{ name: 'Alice', age: 30, city: 'Paris' },
|
|
88
|
-
{ name: 'Bob', age: 25, city: 'London' },
|
|
89
|
-
{ name: 'Carol', age: 35, city: 'Berlin' },
|
|
90
|
-
]
|
|
91
|
-
for (const p of people) {
|
|
92
|
-
await table.set(p)
|
|
93
|
-
}
|
|
94
|
-
// sort by age ascending
|
|
95
|
-
const asc = [] as Person[]
|
|
96
|
-
for await (const p of table.search({ sortBy: 'age', order: 'asc' })) asc.push(p)
|
|
97
|
-
expect(asc.map((p) => p.name)).toEqual(['Bob', 'Alice', 'Carol'])
|
|
98
|
-
// limit and offset
|
|
99
|
-
const limited = [] as Person[]
|
|
100
|
-
for await (const p of table.search({ sortBy: 'age', order: 'asc', limit: 2 })) limited.push(p)
|
|
101
|
-
expect(limited.map((p) => p.name)).toEqual(['Bob', 'Alice'])
|
|
102
|
-
const offsetted = [] as Person[]
|
|
103
|
-
for await (const p of table.search({ sortBy: 'age', order: 'asc', offset: 1, limit: 2 })) offsetted.push(p)
|
|
104
|
-
expect(offsetted.map((p) => p.name)).toEqual(['Alice', 'Carol'])
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('should deleteBy where clause', async () => {
|
|
108
|
-
await table.set({ name: 'Dave', age: 40, city: 'NY' })
|
|
109
|
-
await table.set({ name: 'Eve', age: 45, city: 'NY' })
|
|
110
|
-
await table.set({ name: 'Frank', age: 50, city: 'LA' })
|
|
111
|
-
expect(await table.count()).toBe(3)
|
|
112
|
-
await table.deleteBy({ city: 'NY' })
|
|
113
|
-
expect(await table.count()).toBe(1)
|
|
114
|
-
expect(await table.get('Frank')).toEqual({ name: 'Frank', age: 50, city: 'LA' })
|
|
115
|
-
expect(await table.get('Dave')).toBeUndefined()
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('should use selector in get and search', async () => {
|
|
119
|
-
await table.set({ name: 'Gary', age: 60, city: 'SF' })
|
|
120
|
-
// selector in get
|
|
121
|
-
const ageOnly = await table.get('Gary', ({ age }) => age)
|
|
122
|
-
expect(ageOnly).toBe(60)
|
|
123
|
-
// selector in search
|
|
124
|
-
const cities: string[] = []
|
|
125
|
-
for await (const city of table.search({ select: ({ city }) => city })) cities.push(city)
|
|
126
|
-
expect(cities).toEqual(['SF'])
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('should delete items by key', async () => {
|
|
130
|
-
await table.set({ name: 'Helen', age: 28, city: 'Rome' })
|
|
131
|
-
expect(await table.get('Helen')).toBeDefined()
|
|
132
|
-
await table.delete('Helen')
|
|
133
|
-
expect(await table.get('Helen')).toBeUndefined()
|
|
134
|
-
})
|
|
135
|
-
it('should test search with 1000 items', async () => {
|
|
136
|
-
const people: Person[] = []
|
|
137
|
-
for (let index = 0; index < 1000; index++) {
|
|
138
|
-
people.push({
|
|
139
|
-
name: `Person${index}`,
|
|
140
|
-
age: Math.floor(Math.random() * 100),
|
|
141
|
-
city: 'City' + (index % 10),
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
for (const p of people) {
|
|
145
|
-
await table.set(p)
|
|
146
|
-
}
|
|
147
|
-
const results: Person[] = []
|
|
148
|
-
for await (const person of table.search({ sortBy: 'age', order: 'asc', limit: 100 })) {
|
|
149
|
-
results.push(person)
|
|
150
|
-
}
|
|
151
|
-
expect(results.length).toBe(100)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('should handle operations on an empty table', async () => {
|
|
155
|
-
expect(await table.count()).toBe(0)
|
|
156
|
-
expect(await table.get('NonExistent')).toBeUndefined()
|
|
157
|
-
const results: Person[] = []
|
|
158
|
-
for await (const person of table.search({ sortBy: 'age', order: 'asc' })) {
|
|
159
|
-
results.push(person)
|
|
160
|
-
}
|
|
161
|
-
expect(results.length).toBe(0)
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('should handle duplicate keys gracefully', async () => {
|
|
165
|
-
await table.set({ name: 'Alice', age: 30, city: 'Paris' })
|
|
166
|
-
await table.set({ name: 'Alice', age: 35, city: 'Berlin' })
|
|
167
|
-
const result = await table.get('Alice')
|
|
168
|
-
expect(result).toEqual({ name: 'Alice', age: 35, city: 'Berlin' })
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('should handle edge cases in selectors', async () => {
|
|
172
|
-
await table.set({ name: 'Charlie', age: 40, city: 'NY' })
|
|
173
|
-
const nullSelector = await table.get('Charlie', () => null)
|
|
174
|
-
expect(nullSelector).toBeNull()
|
|
175
|
-
const undefinedSelector = await table.get('Charlie', () => void 0)
|
|
176
|
-
expect(undefinedSelector).toBeUndefined()
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('should clear the table', async () => {
|
|
180
|
-
await table.set({ name: 'Alice', age: 30, city: 'Paris' })
|
|
181
|
-
await table.set({ name: 'Bob', age: 25, city: 'London' })
|
|
182
|
-
expect(await table.count()).toBe(2)
|
|
183
|
-
await table.clear()
|
|
184
|
-
expect(await table.count()).toBe(0)
|
|
185
|
-
})
|
|
186
|
-
it('should use fts index', async () => {
|
|
187
|
-
const tableFts = await createTable<{ id: string; content: string }>({
|
|
188
|
-
backend,
|
|
189
|
-
tableName: 'TestTableFTS',
|
|
190
|
-
key: 'id',
|
|
191
|
-
indexes: ['fts:content'],
|
|
192
|
-
})
|
|
193
|
-
await tableFts.set({ id: '1', content: 'The Čoho brown fox' })
|
|
194
|
-
await tableFts.set({ id: '2', content: 'jumps over the lazy dog' })
|
|
195
|
-
await tableFts.set({ id: '3', content: 'hello world' })
|
|
196
|
-
|
|
197
|
-
const results: { id: string; content: string }[] = []
|
|
198
|
-
for await (const doc of tableFts.search({ where: { content: { fts: ['coho', 'fox'] } } })) {
|
|
199
|
-
results.push(doc)
|
|
200
|
-
}
|
|
201
|
-
expect(results.length).toBe(1)
|
|
202
|
-
expect(results[0].id).toBe('1')
|
|
203
|
-
|
|
204
|
-
const results2: { id: string; content: string }[] = []
|
|
205
|
-
for await (const doc of tableFts.search({ where: { content: { fts: ['the'] } } })) {
|
|
206
|
-
results2.push(doc)
|
|
207
|
-
}
|
|
208
|
-
expect(results2.length).toBe(2)
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('should use fts index with custom tokenizer options', async () => {
|
|
212
|
-
// Use only ASCII letters for tokenchars/separators to avoid SQLite errors
|
|
213
|
-
const tableFts = await createTable<{ id: string; content: string }>({
|
|
214
|
-
backend,
|
|
215
|
-
tableName: 'TestTableFTS2',
|
|
216
|
-
key: 'id',
|
|
217
|
-
indexes: [
|
|
218
|
-
{
|
|
219
|
-
type: 'fts',
|
|
220
|
-
path: 'content',
|
|
221
|
-
tokenizer: { removeDiacritics: 0, tokenChars: 'abc', separators: 'xyz' },
|
|
222
|
-
},
|
|
223
|
-
],
|
|
224
|
-
})
|
|
225
|
-
await tableFts.set({ id: '1', content: 'abc xyz' })
|
|
226
|
-
await tableFts.set({ id: '2', content: 'abc' })
|
|
227
|
-
await tableFts.set({ id: '3', content: 'other' })
|
|
228
|
-
|
|
229
|
-
const results: { id: string; content: string }[] = []
|
|
230
|
-
for await (const doc of tableFts.search({ where: { content: { fts: ['abc'] } } })) {
|
|
231
|
-
results.push(doc)
|
|
232
|
-
}
|
|
233
|
-
expect(results.length).toBe(2)
|
|
234
|
-
|
|
235
|
-
const results2: { id: string; content: string }[] = []
|
|
236
|
-
for await (const doc of tableFts.search({ where: { content: { fts: ['xyz'] } } })) {
|
|
237
|
-
results2.push(doc)
|
|
238
|
-
}
|
|
239
|
-
expect(results2.length).toBe(1)
|
|
240
|
-
expect(results2[0].id).toBe('1')
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
it('should support multiple fts fields', async () => {
|
|
244
|
-
// Use a single FTS index with multiple fields (title, body) in one index
|
|
245
|
-
const tableFts = await createTable<{ id: string; title: string; body: string }>({
|
|
246
|
-
backend,
|
|
247
|
-
tableName: 'TestTableFTSMulti',
|
|
248
|
-
key: 'id',
|
|
249
|
-
indexes: [
|
|
250
|
-
{ type: 'fts', path: 'title' },
|
|
251
|
-
{ type: 'fts', path: 'body' },
|
|
252
|
-
],
|
|
253
|
-
})
|
|
254
|
-
await tableFts.set({ id: '1', title: 'Hello', body: 'World' })
|
|
255
|
-
await tableFts.set({ id: '2', title: 'Foo', body: 'Bar' })
|
|
256
|
-
await tableFts.set({ id: '3', title: 'Hello', body: 'Bar' })
|
|
257
|
-
|
|
258
|
-
// FTS search on title
|
|
259
|
-
const results: { id: string; title: string; body: string }[] = []
|
|
260
|
-
for await (const doc of tableFts.search({ where: { title: { fts: ['Hello'] } } })) {
|
|
261
|
-
results.push(doc)
|
|
262
|
-
}
|
|
263
|
-
expect(results.length).toBe(2)
|
|
264
|
-
|
|
265
|
-
// FTS search on body
|
|
266
|
-
const results2: { id: string; title: string; body: string }[] = []
|
|
267
|
-
for await (const doc of tableFts.search({ where: { body: { fts: ['Bar'] } } })) {
|
|
268
|
-
results2.push(doc)
|
|
269
|
-
}
|
|
270
|
-
expect(results2.length).toBe(2)
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
it('should handle fts search with no results', async () => {
|
|
274
|
-
const tableFts = await createTable<{ id: string; content: string }>({
|
|
275
|
-
backend,
|
|
276
|
-
tableName: 'TestTableFTSNone',
|
|
277
|
-
key: 'id',
|
|
278
|
-
indexes: ['fts:content'],
|
|
279
|
-
})
|
|
280
|
-
await tableFts.set({ id: '1', content: 'foo bar' })
|
|
281
|
-
const results: { id: string; content: string }[] = []
|
|
282
|
-
for await (const doc of tableFts.search({ where: { content: { fts: ['notfound'] } } })) {
|
|
283
|
-
results.push(doc)
|
|
284
|
-
}
|
|
285
|
-
expect(results.length).toBe(0)
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
it('should custom fn fts index', async () => {
|
|
289
|
-
const tableFts = await createTable<{ id: string; content: string }>({
|
|
290
|
-
backend,
|
|
291
|
-
tableName: 'TestTableFTS',
|
|
292
|
-
key: 'id',
|
|
293
|
-
indexes: [
|
|
294
|
-
{
|
|
295
|
-
type: 'fts',
|
|
296
|
-
path: 'content',
|
|
297
|
-
tokenizer: {
|
|
298
|
-
removeDiacritics: 1,
|
|
299
|
-
},
|
|
300
|
-
},
|
|
301
|
-
],
|
|
302
|
-
})
|
|
303
|
-
await tableFts.set({ id: '1', content: 'The Čoho brown fox' })
|
|
304
|
-
await tableFts.set({ id: '2', content: 'jumps over the lazy dog' })
|
|
305
|
-
await tableFts.set({ id: '3', content: 'hello world' })
|
|
306
|
-
|
|
307
|
-
const results: { id: string; content: string }[] = []
|
|
308
|
-
for await (const doc of tableFts.search({ where: { content: { fts: ['coho', 'fox'] } } })) {
|
|
309
|
-
results.push(doc)
|
|
310
|
-
}
|
|
311
|
-
expect(results.length).toBe(1)
|
|
312
|
-
expect(results[0].id).toBe('1')
|
|
313
|
-
|
|
314
|
-
const results2: { id: string; content: string }[] = []
|
|
315
|
-
for await (const doc of tableFts.search({ where: { content: { fts: ['the'] } } })) {
|
|
316
|
-
results2.push(doc)
|
|
317
|
-
}
|
|
318
|
-
expect(results2.length).toBe(2)
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('should test fts index with nested fields', async () => {
|
|
322
|
-
const tableFts = await createTable<{ id: string; info: { content: string } }>({
|
|
323
|
-
backend,
|
|
324
|
-
tableName: 'TestTableFTSNested',
|
|
325
|
-
key: 'id',
|
|
326
|
-
indexes: ['fts:info.content'],
|
|
327
|
-
})
|
|
328
|
-
await tableFts.set({ id: '1', info: { content: 'The quick brown fox' } })
|
|
329
|
-
await tableFts.set({ id: '2', info: { content: 'jumps over the lazy dog' } })
|
|
330
|
-
await tableFts.set({ id: '3', info: { content: 'hello world' } })
|
|
331
|
-
|
|
332
|
-
const results: { id: string; info: { content: string } }[] = []
|
|
333
|
-
for await (const doc of tableFts.search({
|
|
334
|
-
where: {
|
|
335
|
-
info: {
|
|
336
|
-
content: { fts: ['quick', 'fox'] },
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
|
-
})) {
|
|
340
|
-
results.push(doc)
|
|
341
|
-
}
|
|
342
|
-
expect(results.length).toBe(1)
|
|
343
|
-
expect(results[0].id).toBe('1')
|
|
344
|
-
|
|
345
|
-
const results2: { id: string; info: { content: string } }[] = []
|
|
346
|
-
for await (const doc of tableFts.search({ where: { info: { content: { fts: ['the'] } } } })) {
|
|
347
|
-
results2.push(doc)
|
|
348
|
-
}
|
|
349
|
-
expect(results2.length).toBe(2)
|
|
350
|
-
})
|
|
351
|
-
})
|