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.
Files changed (47) hide show
  1. package/dist/Database.cjs +7981 -231
  2. package/package.json +9 -2
  3. package/src/Database.mjs +372 -154
  4. package/src/SchemaManager.mjs +325 -268
  5. package/src/Serializer.mjs +20 -1
  6. package/src/managers/QueryManager.mjs +74 -18
  7. package/.babelrc +0 -13
  8. package/.gitattributes +0 -2
  9. package/CHANGELOG.md +0 -140
  10. package/babel.config.json +0 -5
  11. package/docs/API.md +0 -1057
  12. package/docs/EXAMPLES.md +0 -701
  13. package/docs/README.md +0 -194
  14. package/examples/iterate-usage-example.js +0 -157
  15. package/examples/simple-iterate-example.js +0 -115
  16. package/jest.config.js +0 -24
  17. package/scripts/README.md +0 -47
  18. package/scripts/benchmark-array-serialization.js +0 -108
  19. package/scripts/clean-test-files.js +0 -75
  20. package/scripts/prepare.js +0 -31
  21. package/scripts/run-tests.js +0 -80
  22. package/scripts/score-mode-demo.js +0 -45
  23. package/test/$not-operator-with-and.test.js +0 -282
  24. package/test/README.md +0 -8
  25. package/test/close-init-cycle.test.js +0 -256
  26. package/test/coverage-method.test.js +0 -93
  27. package/test/critical-bugs-fixes.test.js +0 -1069
  28. package/test/deserialize-corruption-fixes.test.js +0 -296
  29. package/test/exists-method.test.js +0 -318
  30. package/test/explicit-indexes-comparison.test.js +0 -219
  31. package/test/filehandler-non-adjacent-ranges-bug.test.js +0 -175
  32. package/test/index-line-number-regression.test.js +0 -100
  33. package/test/index-missing-index-data.test.js +0 -91
  34. package/test/index-persistence.test.js +0 -491
  35. package/test/index-serialization.test.js +0 -314
  36. package/test/indexed-query-mode.test.js +0 -360
  37. package/test/insert-session-auto-flush.test.js +0 -353
  38. package/test/iterate-method.test.js +0 -272
  39. package/test/legacy-operator-compat.test.js +0 -154
  40. package/test/query-operators.test.js +0 -238
  41. package/test/regex-array-fields.test.js +0 -129
  42. package/test/score-method.test.js +0 -298
  43. package/test/setup.js +0 -17
  44. package/test/term-mapping-minimal.test.js +0 -154
  45. package/test/term-mapping-simple.test.js +0 -257
  46. package/test/term-mapping.test.js +0 -514
  47. 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
- })