jexidb 2.1.1 → 2.1.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.
- package/dist/Database.cjs +7981 -231
- package/package.json +9 -2
- package/src/Database.mjs +372 -154
- package/src/SchemaManager.mjs +325 -268
- package/src/Serializer.mjs +20 -1
- package/src/managers/QueryManager.mjs +74 -18
- package/.babelrc +0 -13
- package/.gitattributes +0 -2
- package/CHANGELOG.md +0 -140
- package/babel.config.json +0 -5
- package/docs/API.md +0 -1057
- package/docs/EXAMPLES.md +0 -701
- package/docs/README.md +0 -194
- package/examples/iterate-usage-example.js +0 -157
- package/examples/simple-iterate-example.js +0 -115
- package/jest.config.js +0 -24
- package/scripts/README.md +0 -47
- package/scripts/benchmark-array-serialization.js +0 -108
- package/scripts/clean-test-files.js +0 -75
- package/scripts/prepare.js +0 -31
- package/scripts/run-tests.js +0 -80
- package/scripts/score-mode-demo.js +0 -45
- package/test/$not-operator-with-and.test.js +0 -282
- package/test/README.md +0 -8
- package/test/close-init-cycle.test.js +0 -256
- package/test/coverage-method.test.js +0 -93
- package/test/critical-bugs-fixes.test.js +0 -1069
- package/test/deserialize-corruption-fixes.test.js +0 -296
- package/test/exists-method.test.js +0 -318
- package/test/explicit-indexes-comparison.test.js +0 -219
- package/test/filehandler-non-adjacent-ranges-bug.test.js +0 -175
- package/test/index-line-number-regression.test.js +0 -100
- package/test/index-missing-index-data.test.js +0 -91
- package/test/index-persistence.test.js +0 -491
- package/test/index-serialization.test.js +0 -314
- package/test/indexed-query-mode.test.js +0 -360
- package/test/insert-session-auto-flush.test.js +0 -353
- package/test/iterate-method.test.js +0 -272
- package/test/legacy-operator-compat.test.js +0 -154
- package/test/query-operators.test.js +0 -238
- package/test/regex-array-fields.test.js +0 -129
- package/test/score-method.test.js +0 -298
- package/test/setup.js +0 -17
- package/test/term-mapping-minimal.test.js +0 -154
- package/test/term-mapping-simple.test.js +0 -257
- package/test/term-mapping.test.js +0 -514
- package/test/writebuffer-flush-resilience.test.js +0 -204
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
import { Database } from '../src/Database.mjs'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
|
|
5
|
-
describe('InsertSession Auto-Flush', () => {
|
|
6
|
-
let testDir
|
|
7
|
-
let db
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
testDir = path.join(process.cwd(), 'test-files', 'insert-session-auto-flush')
|
|
11
|
-
fs.mkdirSync(testDir, { recursive: true })
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
afterEach(async () => {
|
|
15
|
-
if (db) {
|
|
16
|
-
// Wait for all active insert sessions to complete
|
|
17
|
-
if (db.activeInsertSessions && db.activeInsertSessions.size > 0) {
|
|
18
|
-
const sessions = Array.from(db.activeInsertSessions)
|
|
19
|
-
await Promise.all(sessions.map(session => {
|
|
20
|
-
return session.waitForAutoFlushes().catch(() => {})
|
|
21
|
-
}))
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Wait for any pending operations before closing
|
|
25
|
-
await db.waitForOperations()
|
|
26
|
-
await db.close()
|
|
27
|
-
}
|
|
28
|
-
// Clean up test files
|
|
29
|
-
if (fs.existsSync(testDir)) {
|
|
30
|
-
try {
|
|
31
|
-
fs.rmSync(testDir, { recursive: true, force: true })
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.warn('Could not clean up test directory:', testDir)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
describe('Auto-flush on batch size', () => {
|
|
39
|
-
test('should auto-flush when batch size is reached', async () => {
|
|
40
|
-
const dbPath = path.join(testDir, 'auto-flush-basic.jdb')
|
|
41
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
42
|
-
await db.init()
|
|
43
|
-
|
|
44
|
-
const session = db.beginInsertSession({ batchSize: 10 })
|
|
45
|
-
|
|
46
|
-
// Insert exactly 10 records (should trigger auto-flush)
|
|
47
|
-
for (let i = 0; i < 10; i++) {
|
|
48
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Wait a bit for auto-flush to complete
|
|
52
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
53
|
-
|
|
54
|
-
// Verify data was inserted (auto-flush should have processed it)
|
|
55
|
-
expect(db.length).toBe(10)
|
|
56
|
-
|
|
57
|
-
// Verify batches array is empty (auto-flush processed it)
|
|
58
|
-
expect(session.batches.length).toBe(0)
|
|
59
|
-
|
|
60
|
-
await session.commit()
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
test('should auto-flush multiple batches without accumulating in memory', async () => {
|
|
64
|
-
const dbPath = path.join(testDir, 'auto-flush-multiple.jdb')
|
|
65
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
66
|
-
await db.init()
|
|
67
|
-
|
|
68
|
-
const session = db.beginInsertSession({ batchSize: 100 })
|
|
69
|
-
|
|
70
|
-
// Insert 5000 records (enough to test auto-flush without timeout)
|
|
71
|
-
// This should create ~50 batches, but they should be auto-flushed
|
|
72
|
-
const totalRecords = 5000
|
|
73
|
-
|
|
74
|
-
for (let i = 0; i < totalRecords; i++) {
|
|
75
|
-
await session.add({
|
|
76
|
-
name: `Record ${i}`,
|
|
77
|
-
value: i,
|
|
78
|
-
data: `Data for record ${i}`.repeat(10) // Add some data to make it realistic
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
// Periodically check that batches don't accumulate
|
|
82
|
-
if (i % 1000 === 0 && i > 0) {
|
|
83
|
-
// Wait a bit for auto-flushes to catch up
|
|
84
|
-
await new Promise(resolve => setTimeout(resolve, 50))
|
|
85
|
-
|
|
86
|
-
// Verify batches array doesn't grow unbounded
|
|
87
|
-
// It should be small (only pending batches waiting to be flushed)
|
|
88
|
-
expect(session.batches.length).toBeLessThan(10)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Wait for all auto-flushes to complete
|
|
93
|
-
await session.waitForAutoFlushes()
|
|
94
|
-
|
|
95
|
-
// Final commit should be fast (most data already flushed)
|
|
96
|
-
await session.commit()
|
|
97
|
-
|
|
98
|
-
// Verify all data was inserted
|
|
99
|
-
expect(db.length).toBe(totalRecords)
|
|
100
|
-
|
|
101
|
-
// Verify batches are empty after commit
|
|
102
|
-
expect(session.batches.length).toBe(0)
|
|
103
|
-
expect(session.currentBatch.length).toBe(0)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
test('should handle concurrent inserts during flush', async () => {
|
|
107
|
-
const dbPath = path.join(testDir, 'auto-flush-concurrent.jdb')
|
|
108
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
109
|
-
await db.init()
|
|
110
|
-
|
|
111
|
-
const session = db.beginInsertSession({ batchSize: 50 })
|
|
112
|
-
|
|
113
|
-
// Insert records concurrently while flush is happening
|
|
114
|
-
const insertPromises = []
|
|
115
|
-
for (let i = 0; i < 500; i++) {
|
|
116
|
-
insertPromises.push(
|
|
117
|
-
session.add({ name: `Record ${i}`, value: i })
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// All inserts should complete
|
|
122
|
-
await Promise.all(insertPromises)
|
|
123
|
-
|
|
124
|
-
// Wait for all auto-flushes
|
|
125
|
-
await session.waitForAutoFlushes()
|
|
126
|
-
|
|
127
|
-
// Commit should process any remaining data
|
|
128
|
-
await session.commit()
|
|
129
|
-
|
|
130
|
-
// Verify all data was inserted
|
|
131
|
-
expect(db.length).toBe(500)
|
|
132
|
-
})
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
describe('Commit waits for auto-flushes', () => {
|
|
136
|
-
test('commit() should wait for all pending auto-flushes', async () => {
|
|
137
|
-
const dbPath = path.join(testDir, 'commit-waits.jdb')
|
|
138
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
139
|
-
await db.init()
|
|
140
|
-
|
|
141
|
-
const session = db.beginInsertSession({ batchSize: 10 })
|
|
142
|
-
|
|
143
|
-
// Insert many records to trigger multiple auto-flushes
|
|
144
|
-
for (let i = 0; i < 250; i++) {
|
|
145
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Commit should wait for all auto-flushes to complete
|
|
149
|
-
const insertedCount = await session.commit()
|
|
150
|
-
|
|
151
|
-
// Verify all records were inserted
|
|
152
|
-
expect(insertedCount).toBe(250)
|
|
153
|
-
expect(db.length).toBe(250)
|
|
154
|
-
|
|
155
|
-
// Verify no pending operations
|
|
156
|
-
expect(session.hasPendingOperations()).toBe(false)
|
|
157
|
-
|
|
158
|
-
// Verify all auto-flushes completed (pendingAutoFlushes should be empty)
|
|
159
|
-
expect(session.pendingAutoFlushes.size).toBe(0)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
test('commit() should flush remaining data after waiting for auto-flushes', async () => {
|
|
163
|
-
const dbPath = path.join(testDir, 'commit-flush-remaining.jdb')
|
|
164
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
165
|
-
await db.init()
|
|
166
|
-
|
|
167
|
-
const session = db.beginInsertSession({ batchSize: 100 })
|
|
168
|
-
|
|
169
|
-
// Insert 83 records (less than batchSize, so no auto-flush)
|
|
170
|
-
for (let i = 0; i < 83; i++) {
|
|
171
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Verify data is in currentBatch but not flushed
|
|
175
|
-
expect(session.currentBatch.length).toBe(83)
|
|
176
|
-
expect(db.length).toBe(0) // Not yet inserted
|
|
177
|
-
|
|
178
|
-
// Commit should flush the remaining data
|
|
179
|
-
await session.commit()
|
|
180
|
-
|
|
181
|
-
// Verify all data was inserted
|
|
182
|
-
expect(db.length).toBe(83)
|
|
183
|
-
expect(session.currentBatch.length).toBe(0)
|
|
184
|
-
})
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
describe('_doFlush handles concurrent inserts', () => {
|
|
188
|
-
test('_doFlush should process all data even if new data is added during flush', async () => {
|
|
189
|
-
const dbPath = path.join(testDir, 'doflush-concurrent.jdb')
|
|
190
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
191
|
-
await db.init()
|
|
192
|
-
|
|
193
|
-
const session = db.beginInsertSession({ batchSize: 10 })
|
|
194
|
-
|
|
195
|
-
// Add initial batch
|
|
196
|
-
for (let i = 0; i < 10; i++) {
|
|
197
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Manually trigger flush, but add more data during flush
|
|
201
|
-
const flushPromise = session._doFlush()
|
|
202
|
-
|
|
203
|
-
// Add more data while flush is happening
|
|
204
|
-
for (let i = 10; i < 25; i++) {
|
|
205
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Wait for flush to complete
|
|
209
|
-
await flushPromise
|
|
210
|
-
|
|
211
|
-
// Verify all data was processed
|
|
212
|
-
// The flush should have processed everything, including data added during flush
|
|
213
|
-
expect(session.batches.length).toBe(0)
|
|
214
|
-
|
|
215
|
-
// Final commit to ensure everything is inserted
|
|
216
|
-
await session.commit()
|
|
217
|
-
expect(db.length).toBe(25)
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
test('_doFlush should continue until queue is empty', async () => {
|
|
221
|
-
const dbPath = path.join(testDir, 'doflush-empty-queue.jdb')
|
|
222
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
223
|
-
await db.init()
|
|
224
|
-
|
|
225
|
-
const session = db.beginInsertSession({ batchSize: 5 })
|
|
226
|
-
|
|
227
|
-
// Add multiple batches
|
|
228
|
-
for (let i = 0; i < 23; i++) {
|
|
229
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Wait for any auto-flushes to complete
|
|
233
|
-
await session.waitForAutoFlushes()
|
|
234
|
-
|
|
235
|
-
// Manually trigger flush (should process everything including currentBatch)
|
|
236
|
-
await session._doFlush()
|
|
237
|
-
|
|
238
|
-
// Verify all batches were processed
|
|
239
|
-
// _doFlush processes everything, including partial currentBatch
|
|
240
|
-
expect(session.batches.length).toBe(0)
|
|
241
|
-
// After _doFlush, currentBatch should be empty (it was processed)
|
|
242
|
-
expect(session.currentBatch.length).toBe(0)
|
|
243
|
-
|
|
244
|
-
// Verify all data was inserted
|
|
245
|
-
expect(db.length).toBe(23)
|
|
246
|
-
})
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
describe('Memory management', () => {
|
|
250
|
-
test('should not accumulate batches in memory', async () => {
|
|
251
|
-
const dbPath = path.join(testDir, 'memory-management.jdb')
|
|
252
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
253
|
-
await db.init()
|
|
254
|
-
|
|
255
|
-
const session = db.beginInsertSession({ batchSize: 100 })
|
|
256
|
-
|
|
257
|
-
// Track memory usage
|
|
258
|
-
const initialBatches = session.batches.length
|
|
259
|
-
|
|
260
|
-
// Insert large amount of data
|
|
261
|
-
for (let i = 0; i < 10000; i++) {
|
|
262
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
263
|
-
|
|
264
|
-
// Check periodically that batches don't accumulate
|
|
265
|
-
if (i % 500 === 0 && i > 0) {
|
|
266
|
-
await new Promise(resolve => setTimeout(resolve, 10))
|
|
267
|
-
|
|
268
|
-
// Batches should be small (auto-flush is working)
|
|
269
|
-
expect(session.batches.length).toBeLessThan(5)
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Wait for all auto-flushes
|
|
274
|
-
await session.waitForAutoFlushes()
|
|
275
|
-
|
|
276
|
-
// Final commit
|
|
277
|
-
await session.commit()
|
|
278
|
-
|
|
279
|
-
// Verify all data was inserted
|
|
280
|
-
expect(db.length).toBe(10000)
|
|
281
|
-
|
|
282
|
-
// Verify batches are empty
|
|
283
|
-
expect(session.batches.length).toBe(0)
|
|
284
|
-
expect(session.currentBatch.length).toBe(0)
|
|
285
|
-
})
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
describe('Edge cases', () => {
|
|
289
|
-
test('should handle empty commit', async () => {
|
|
290
|
-
const dbPath = path.join(testDir, 'empty-commit.jdb')
|
|
291
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
292
|
-
await db.init()
|
|
293
|
-
|
|
294
|
-
const session = db.beginInsertSession()
|
|
295
|
-
|
|
296
|
-
// Commit without adding anything
|
|
297
|
-
const count = await session.commit()
|
|
298
|
-
|
|
299
|
-
expect(count).toBe(0)
|
|
300
|
-
expect(db.length).toBe(0)
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
test('should handle multiple commits on same session', async () => {
|
|
304
|
-
const dbPath = path.join(testDir, 'multiple-commits.jdb')
|
|
305
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
306
|
-
await db.init()
|
|
307
|
-
|
|
308
|
-
const session = db.beginInsertSession({ batchSize: 10 })
|
|
309
|
-
|
|
310
|
-
// First commit
|
|
311
|
-
for (let i = 0; i < 15; i++) {
|
|
312
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
313
|
-
}
|
|
314
|
-
await session.commit()
|
|
315
|
-
expect(db.length).toBe(15)
|
|
316
|
-
|
|
317
|
-
// Second commit
|
|
318
|
-
for (let i = 15; i < 30; i++) {
|
|
319
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
320
|
-
}
|
|
321
|
-
await session.commit()
|
|
322
|
-
expect(db.length).toBe(30)
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
test('should handle hasPendingOperations correctly', async () => {
|
|
326
|
-
const dbPath = path.join(testDir, 'pending-operations.jdb')
|
|
327
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
328
|
-
await db.init()
|
|
329
|
-
|
|
330
|
-
const session = db.beginInsertSession({ batchSize: 10 })
|
|
331
|
-
|
|
332
|
-
// Initially no pending operations
|
|
333
|
-
expect(session.hasPendingOperations()).toBe(false)
|
|
334
|
-
|
|
335
|
-
// Add records (triggers auto-flush)
|
|
336
|
-
for (let i = 0; i < 15; i++) {
|
|
337
|
-
await session.add({ name: `Record ${i}`, value: i })
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Should have pending operations (auto-flush in progress)
|
|
341
|
-
// Wait a bit and check
|
|
342
|
-
await new Promise(resolve => setTimeout(resolve, 50))
|
|
343
|
-
|
|
344
|
-
// After auto-flush completes, should have no pending (except currentBatch if < batchSize)
|
|
345
|
-
await session.waitForAutoFlushes()
|
|
346
|
-
|
|
347
|
-
// Commit to clear everything
|
|
348
|
-
await session.commit()
|
|
349
|
-
expect(session.hasPendingOperations()).toBe(false)
|
|
350
|
-
})
|
|
351
|
-
})
|
|
352
|
-
})
|
|
353
|
-
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test suite for the new iterate() method
|
|
3
|
-
* Tests bulk update capabilities with streaming performance
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Database } from '../src/Database.mjs'
|
|
7
|
-
import fs from 'fs'
|
|
8
|
-
import path from 'path'
|
|
9
|
-
|
|
10
|
-
describe('Database.iterate() Method', () => {
|
|
11
|
-
let db
|
|
12
|
-
const testFile = 'test-iterate.jdb'
|
|
13
|
-
const testIdxFile = 'test-iterate.idx.jdb'
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
// Clean up any existing test files
|
|
17
|
-
if (fs.existsSync(testFile)) fs.unlinkSync(testFile)
|
|
18
|
-
if (fs.existsSync(testIdxFile)) fs.unlinkSync(testIdxFile)
|
|
19
|
-
|
|
20
|
-
db = new Database(testFile, {
|
|
21
|
-
debugMode: false,
|
|
22
|
-
termMapping: true,
|
|
23
|
-
indexedFields: ['category', 'name', 'price']
|
|
24
|
-
})
|
|
25
|
-
await db.init()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
afterEach(async () => {
|
|
29
|
-
if (db && !db.destroyed) {
|
|
30
|
-
await db.close()
|
|
31
|
-
}
|
|
32
|
-
// Clean up test files
|
|
33
|
-
if (fs.existsSync(testFile)) fs.unlinkSync(testFile)
|
|
34
|
-
if (fs.existsSync(testIdxFile)) fs.unlinkSync(testIdxFile)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe('Basic Functionality', () => {
|
|
38
|
-
test('should iterate through records without modifications', async () => {
|
|
39
|
-
// Insert test data
|
|
40
|
-
await db.insert({ id: 1, name: 'Apple', category: 'fruits', price: 1.50 })
|
|
41
|
-
await db.insert({ id: 2, name: 'Banana', category: 'fruits', price: 0.80 })
|
|
42
|
-
await db.insert({ id: 3, name: 'Carrot', category: 'vegetables', price: 0.60 })
|
|
43
|
-
|
|
44
|
-
const results = []
|
|
45
|
-
for await (const entry of db.iterate({ category: 'fruits' })) {
|
|
46
|
-
results.push(entry)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
expect(results).toHaveLength(2)
|
|
50
|
-
expect(results.map(r => r.name)).toEqual(['Apple', 'Banana'])
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('should detect and process modifications', async () => {
|
|
54
|
-
// Insert test data
|
|
55
|
-
await db.insert({ id: 1, name: 'Apple', category: 'fruits', price: 1.50 })
|
|
56
|
-
await db.insert({ id: 2, name: 'Banana', category: 'fruits', price: 0.80 })
|
|
57
|
-
|
|
58
|
-
// Iterate and modify records
|
|
59
|
-
for await (const entry of db.iterate({ category: 'fruits' })) {
|
|
60
|
-
if (entry.name === 'Apple') {
|
|
61
|
-
entry.price = 2.00 // Modify price
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Verify changes were applied
|
|
66
|
-
const apple = await db.findOne({ name: 'Apple' })
|
|
67
|
-
expect(apple.price).toBe(2.00)
|
|
68
|
-
|
|
69
|
-
const banana = await db.findOne({ name: 'Banana' })
|
|
70
|
-
expect(banana.price).toBe(0.80) // Unchanged
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test('should handle deletions by setting entry to null', async () => {
|
|
74
|
-
// Insert test data
|
|
75
|
-
await db.insert({ id: 1, name: 'Apple', category: 'fruits', price: 1.50 })
|
|
76
|
-
await db.insert({ id: 2, name: 'Banana', category: 'fruits', price: 0.80 })
|
|
77
|
-
await db.insert({ id: 3, name: 'Carrot', category: 'vegetables', price: 0.60 })
|
|
78
|
-
|
|
79
|
-
// Iterate and delete some records
|
|
80
|
-
for await (const entry of db.iterate({ category: 'fruits' })) {
|
|
81
|
-
if (entry.name === 'Apple') {
|
|
82
|
-
// Delete the record using the delete method
|
|
83
|
-
entry.delete()
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Verify deletion
|
|
88
|
-
const fruits = await db.find({ category: 'fruits' })
|
|
89
|
-
expect(fruits).toHaveLength(1)
|
|
90
|
-
expect(fruits[0].name).toBe('Banana')
|
|
91
|
-
|
|
92
|
-
// Verify other categories unaffected
|
|
93
|
-
const vegetables = await db.find({ category: 'vegetables' })
|
|
94
|
-
expect(vegetables).toHaveLength(1)
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
describe('Performance Features', () => {
|
|
99
|
-
test('should process records in batches', async () => {
|
|
100
|
-
// Insert many records
|
|
101
|
-
const records = []
|
|
102
|
-
for (let i = 1; i <= 2500; i++) {
|
|
103
|
-
records.push({
|
|
104
|
-
id: i,
|
|
105
|
-
name: `Item${i}`,
|
|
106
|
-
category: i % 2 === 0 ? 'even' : 'odd',
|
|
107
|
-
price: i * 0.1
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Insert all records
|
|
112
|
-
for (const record of records) {
|
|
113
|
-
await db.insert(record)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
let processedCount = 0
|
|
117
|
-
let modifiedCount = 0
|
|
118
|
-
|
|
119
|
-
// Iterate with progress callback
|
|
120
|
-
for await (const entry of db.iterate(
|
|
121
|
-
{ category: 'even' },
|
|
122
|
-
{
|
|
123
|
-
chunkSize: 500,
|
|
124
|
-
progressCallback: (progress) => {
|
|
125
|
-
processedCount = progress.processed
|
|
126
|
-
modifiedCount = progress.modified
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
)) {
|
|
130
|
-
// Modify every 20th record (to get exactly 125 from 1250)
|
|
131
|
-
if (entry.id % 20 === 0) {
|
|
132
|
-
entry.price = entry.price * 2
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
expect(processedCount).toBe(1250) // Half of 2500
|
|
137
|
-
expect(modifiedCount).toBe(125) // Every 20th of 1250 (20, 40, 60, ..., 2500)
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
test('should handle large datasets efficiently', async () => {
|
|
141
|
-
// Insert test data
|
|
142
|
-
const records = []
|
|
143
|
-
for (let i = 1; i <= 1000; i++) {
|
|
144
|
-
records.push({
|
|
145
|
-
id: i,
|
|
146
|
-
name: `Product${i}`,
|
|
147
|
-
category: 'electronics',
|
|
148
|
-
price: Math.random() * 100
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Insert all records
|
|
153
|
-
for (const record of records) {
|
|
154
|
-
await db.insert(record)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const startTime = Date.now()
|
|
158
|
-
let count = 0
|
|
159
|
-
|
|
160
|
-
// Iterate through all records
|
|
161
|
-
for await (const entry of db.iterate({ category: 'electronics' })) {
|
|
162
|
-
count++
|
|
163
|
-
// Simple modification
|
|
164
|
-
entry.lastProcessed = Date.now()
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const elapsed = Date.now() - startTime
|
|
168
|
-
|
|
169
|
-
expect(count).toBe(1000)
|
|
170
|
-
expect(elapsed).toBeLessThan(5000) // Should complete in under 5 seconds
|
|
171
|
-
|
|
172
|
-
// Verify modifications were applied
|
|
173
|
-
const sample = await db.findOne({ id: 1 })
|
|
174
|
-
expect(sample.lastProcessed).toBeDefined()
|
|
175
|
-
})
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
describe('Options and Configuration', () => {
|
|
179
|
-
test('should respect chunkSize option', async () => {
|
|
180
|
-
// Insert test data
|
|
181
|
-
for (let i = 1; i <= 100; i++) {
|
|
182
|
-
await db.insert({ id: i, name: `Item${i}`, category: 'test' })
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let batchCount = 0
|
|
186
|
-
const chunkSize = 25
|
|
187
|
-
|
|
188
|
-
for await (const entry of db.iterate(
|
|
189
|
-
{ category: 'test' },
|
|
190
|
-
{
|
|
191
|
-
chunkSize,
|
|
192
|
-
progressCallback: (progress) => {
|
|
193
|
-
if (progress.processed > 0 && progress.processed % chunkSize === 0) {
|
|
194
|
-
batchCount++
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
)) {
|
|
199
|
-
entry.processed = true
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Should have processed in 4-5 batches (100 / 25)
|
|
203
|
-
expect(batchCount).toBeGreaterThanOrEqual(4)
|
|
204
|
-
expect(batchCount).toBeLessThanOrEqual(5)
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
test('should work with manual change detection', async () => {
|
|
208
|
-
await db.insert({ id: 1, name: 'Test', category: 'test', value: 1 })
|
|
209
|
-
|
|
210
|
-
for await (const entry of db.iterate(
|
|
211
|
-
{ category: 'test' },
|
|
212
|
-
{ detectChanges: false }
|
|
213
|
-
)) {
|
|
214
|
-
entry.value = 2
|
|
215
|
-
entry._modified = true // Manual flag
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const result = await db.findOne({ id: 1 })
|
|
219
|
-
expect(result.value).toBe(2)
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
test('should handle autoSave option', async () => {
|
|
223
|
-
await db.insert({ id: 1, name: 'Test', category: 'test' })
|
|
224
|
-
|
|
225
|
-
for await (const entry of db.iterate(
|
|
226
|
-
{ category: 'test' },
|
|
227
|
-
{ autoSave: true, chunkSize: 1 }
|
|
228
|
-
)) {
|
|
229
|
-
entry.name = 'Modified'
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Verify changes were saved
|
|
233
|
-
const result = await db.findOne({ id: 1 })
|
|
234
|
-
expect(result.name).toBe('Modified')
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
describe('Error Handling', () => {
|
|
239
|
-
test('should handle errors gracefully', async () => {
|
|
240
|
-
await db.insert({ id: 1, name: 'Test', category: 'test' })
|
|
241
|
-
|
|
242
|
-
let errorCaught = false
|
|
243
|
-
try {
|
|
244
|
-
for await (const entry of db.iterate({ category: 'test' })) {
|
|
245
|
-
// Simulate an error
|
|
246
|
-
throw new Error('Test error')
|
|
247
|
-
}
|
|
248
|
-
} catch (error) {
|
|
249
|
-
errorCaught = true
|
|
250
|
-
expect(error.message).toBe('Test error')
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
expect(errorCaught).toBe(true)
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
test('should validate state before iteration', async () => {
|
|
257
|
-
await db.destroy()
|
|
258
|
-
|
|
259
|
-
let errorCaught = false
|
|
260
|
-
try {
|
|
261
|
-
for await (const entry of db.iterate({})) {
|
|
262
|
-
// This should not execute
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
errorCaught = true
|
|
266
|
-
expect(error.message).toContain('destroyed')
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
expect(errorCaught).toBe(true)
|
|
270
|
-
})
|
|
271
|
-
})
|
|
272
|
-
})
|