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,514 +0,0 @@
1
- import fs from 'fs'
2
- import path from 'path'
3
- import { Database } from '../src/Database.mjs'
4
- import TermManager from '../src/managers/TermManager.mjs'
5
-
6
- // Add Mocha-style matchers to Jest
7
- const originalExpect = expect
8
- global.expect = function(actual) {
9
- const matcher = originalExpect(actual)
10
- matcher.to = {
11
- deep: {
12
- equal: (expected) => {
13
- return originalExpect(actual).toEqual(expected)
14
- }
15
- },
16
- equal: (expected) => {
17
- return originalExpect(actual).toBe(expected)
18
- },
19
- have: {
20
- length: (expected) => {
21
- return originalExpect(actual).toHaveLength(expected)
22
- }
23
- },
24
- be: {
25
- false: () => {
26
- return originalExpect(actual).toBe(false)
27
- },
28
- true: () => {
29
- return originalExpect(actual).toBe(true)
30
- },
31
- greaterThan: (expected) => {
32
- return originalExpect(actual).toBeGreaterThan(expected)
33
- },
34
- lessThan: (expected) => {
35
- return originalExpect(actual).toBeLessThan(expected)
36
- }
37
- }
38
- }
39
- return matcher
40
- }
41
-
42
- describe('Term Mapping Tests', () => {
43
- let db
44
- let testDbPath
45
- let testIdxPath
46
-
47
- beforeEach(async () => {
48
- // Use unique test file names to avoid interference between tests
49
- const testId = Math.random().toString(36).substring(7)
50
- testDbPath = path.join(process.cwd(), `test-term-mapping-${testId}.jdb`)
51
- testIdxPath = path.join(process.cwd(), `test-term-mapping-${testId}.idx.jdb`)
52
-
53
- // Clean up any existing test files
54
- if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
55
- if (fs.existsSync(testIdxPath)) fs.unlinkSync(testIdxPath)
56
- if (fs.existsSync(testDbPath.replace('.jdb', '.terms.jdb'))) fs.unlinkSync(testDbPath.replace('.jdb', '.terms.jdb'))
57
- })
58
-
59
- afterEach(async () => {
60
- if (db) {
61
- await db.close()
62
- }
63
-
64
- // Clean up test files with error handling
65
- try {
66
- if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
67
- if (fs.existsSync(testIdxPath)) fs.unlinkSync(testIdxPath)
68
- if (fs.existsSync(testDbPath.replace('.jdb', '.terms.jdb'))) fs.unlinkSync(testDbPath.replace('.jdb', '.terms.jdb'))
69
- } catch (err) {
70
- // Ignore cleanup errors to prevent test failures
71
- console.warn('Warning: Could not clean up test files:', err.message)
72
- }
73
- })
74
-
75
- describe('TermManager', () => {
76
- let termManager
77
-
78
- beforeEach(() => {
79
- termManager = new TermManager()
80
- })
81
-
82
- it('should create and retrieve term IDs', () => {
83
- const term1 = termManager.getTermId('bra')
84
- const term2 = termManager.getTermId('globo')
85
- const term3 = termManager.getTermId('bra') // Duplicate
86
-
87
- expect(term1).toBe(1)
88
- expect(term2).toBe(2)
89
- expect(term3).toBe(1) // Should return same ID
90
-
91
- expect(termManager.getTerm(1)).toBe('bra')
92
- expect(termManager.getTerm(2)).toBe('globo')
93
- expect(termManager.getTerm(999)).toBeNull()
94
- })
95
-
96
- it('should track term usage counts', () => {
97
- const termId = termManager.getTermId('test')
98
- expect(termManager.termCounts.get(termId)).toBe(1)
99
-
100
- termManager.incrementTermCount(termId)
101
- expect(termManager.termCounts.get(termId)).toBe(2)
102
-
103
- termManager.decrementTermCount(termId)
104
- expect(termManager.termCounts.get(termId)).toBe(1)
105
-
106
- termManager.decrementTermCount(termId)
107
- expect(termManager.termCounts.get(termId)).toBe(0)
108
- })
109
-
110
- it('should clean up orphaned terms', async () => {
111
- const term1 = termManager.getTermId('orphan1')
112
- const term2 = termManager.getTermId('orphan2')
113
- const term3 = termManager.getTermId('active')
114
-
115
- // Make term1 and term2 orphaned
116
- termManager.decrementTermCount(term1)
117
- termManager.decrementTermCount(term2)
118
-
119
- const orphanedCount = await termManager.cleanupOrphanedTerms(true)
120
- expect(orphanedCount).toBe(2)
121
-
122
- expect(termManager.hasTerm('orphan1')).toBe(false)
123
- expect(termManager.hasTerm('orphan2')).toBe(false)
124
- expect(termManager.hasTerm('active')).toBe(true)
125
- })
126
-
127
- it('should load and save terms', async () => {
128
- // Add some terms
129
- termManager.getTermId('bra')
130
- termManager.getTermId('globo')
131
- termManager.getTermId('brasil')
132
-
133
- const termsData = await termManager.saveTerms()
134
- expect(termsData).to.deep.equal({
135
- '1': 'bra',
136
- '2': 'globo',
137
- '3': 'brasil'
138
- })
139
-
140
- // Create new manager and load terms
141
- const newManager = new TermManager()
142
- await newManager.loadTerms(termsData)
143
-
144
- expect(newManager.getTerm(1)).to.equal('bra')
145
- expect(newManager.getTerm(2)).to.equal('globo')
146
- expect(newManager.getTerm(3)).to.equal('brasil')
147
- expect(newManager.nextId).to.equal(4)
148
- })
149
-
150
- it('should provide statistics', () => {
151
- termManager.getTermId('term1')
152
- termManager.getTermId('term2')
153
-
154
- const stats = termManager.getStats()
155
- expect(stats.totalTerms).to.equal(2)
156
- expect(stats.nextId).to.equal(3)
157
- expect(stats.orphanedTerms).to.equal(0)
158
- })
159
- })
160
-
161
- describe('Database with Term Mapping', () => {
162
- beforeEach(async () => {
163
- db = new Database(testDbPath, {
164
- indexes: { nameTerms: 'array:string', groupTerms: 'array:string' },
165
- termMappingCleanup: true,
166
- debugMode: false
167
- })
168
- await db.initialize()
169
- })
170
-
171
- it('should insert records with term mapping', async () => {
172
- const record = {
173
- name: 'Test Record',
174
- nameTerms: ['bra', 'globo', 'brasil'],
175
- groupTerms: ['channel', 'discovery']
176
- }
177
-
178
- const inserted = await db.insert(record)
179
- expect(inserted.nameTerms).to.deep.equal(['bra', 'globo', 'brasil'])
180
- expect(inserted.groupTerms).to.deep.equal(['channel', 'discovery'])
181
-
182
- // Check that terms are mapped (order may vary)
183
- const allTerms = db.termManager.getAllTerms()
184
- expect(allTerms).toContain('bra')
185
- expect(allTerms).toContain('globo')
186
- expect(allTerms).toContain('brasil')
187
- expect(allTerms).toContain('channel')
188
- expect(allTerms).toContain('discovery')
189
-
190
- // Check that we have the expected number of terms
191
- expect(allTerms).to.have.length(5)
192
- })
193
-
194
- it('should save and load with term mapping', async () => {
195
- // Insert test data
196
- await db.insert({
197
- name: 'Record 1',
198
- nameTerms: ['bra', 'globo'],
199
- groupTerms: ['channel']
200
- })
201
-
202
- await db.insert({
203
- name: 'Record 2',
204
- nameTerms: ['brasil', 'discovery'],
205
- groupTerms: ['channel', 'discovery']
206
- })
207
-
208
- await db.save()
209
-
210
- // Small delay to ensure file operations are complete
211
- await new Promise(resolve => setTimeout(resolve, 10))
212
-
213
- // Create new database instance and load
214
- const newDb = new Database(testDbPath, {
215
- indexes: { nameTerms: 'array:string', groupTerms: 'array:string' },
216
- termMappingCleanup: true,
217
- debugMode: false
218
- })
219
- await newDb.initialize()
220
-
221
- // Check that terms were loaded (order may vary)
222
- const allTerms = newDb.termManager.getAllTerms()
223
- expect(allTerms).toContain('bra')
224
- expect(allTerms).toContain('globo')
225
- expect(allTerms).toContain('channel')
226
- expect(allTerms).toContain('brasil')
227
- expect(allTerms).toContain('discovery')
228
-
229
- // Check that data was loaded
230
- const records = await newDb.find({})
231
- expect(records).to.have.length(2)
232
- expect(records[0].name).to.equal('Record 1')
233
- expect(records[1].name).to.equal('Record 2')
234
- })
235
-
236
- it('should query with term mapping', async () => {
237
- // Insert test data
238
- await db.insert({
239
- name: 'Record 1',
240
- nameTerms: ['bra', 'globo'],
241
- groupTerms: ['channel']
242
- })
243
-
244
- await db.insert({
245
- name: 'Record 2',
246
- nameTerms: ['brasil', 'discovery'],
247
- groupTerms: ['channel', 'discovery']
248
- })
249
-
250
- // Query by term
251
- const results1 = await db.find({ nameTerms: 'bra' })
252
- console.log('TEST DEBUG: results1 =', results1, 'type:', typeof results1, 'isArray:', Array.isArray(results1))
253
- expect(results1).to.have.length(1)
254
- expect(results1[0].name).to.equal('Record 1')
255
-
256
- const results2 = await db.find({ groupTerms: 'channel' })
257
- expect(results2).to.have.length(2)
258
-
259
- const results3 = await db.find({ nameTerms: 'nonexistent' })
260
- expect(results3).to.have.length(0)
261
- })
262
-
263
- it('should update records with term mapping', async () => {
264
- const record = await db.insert({
265
- name: 'Test Record',
266
- nameTerms: ['bra', 'globo'],
267
- groupTerms: ['channel']
268
- })
269
-
270
- // Update with new terms
271
- await db.update({ id: record.id }, {
272
- nameTerms: ['brasil', 'discovery'],
273
- groupTerms: ['discovery']
274
- })
275
-
276
- // Check that old terms are decremented and new terms are added
277
- expect(db.termManager.termCounts.get(1)).to.equal(0) // 'bra' should be orphaned
278
- expect(db.termManager.termCounts.get(2)).to.equal(0) // 'globo' should be orphaned
279
- expect(db.termManager.termCounts.get(3)).to.equal(0) // 'channel' should be orphaned
280
- expect(db.termManager.termCounts.get(4)).to.equal(1) // 'brasil' should be active
281
- expect(db.termManager.termCounts.get(5)).to.equal(2) // 'discovery' should be active (used in both nameTerms and groupTerms)
282
-
283
- // Query to verify update
284
- const results = await db.find({ nameTerms: 'brasil' })
285
- expect(results).to.have.length(1)
286
- expect(results[0].nameTerms).to.deep.equal(['brasil', 'discovery'])
287
- })
288
-
289
- it('should delete records with term mapping', async () => {
290
- const record = await db.insert({
291
- name: 'Test Record',
292
- nameTerms: ['bra', 'globo'],
293
- groupTerms: ['channel']
294
- })
295
-
296
- await db.delete({ id: record.id })
297
-
298
- // Check that terms are decremented
299
- expect(db.termManager.termCounts.get(1)).to.equal(0) // 'bra' should be orphaned
300
- expect(db.termManager.termCounts.get(2)).to.equal(0) // 'globo' should be orphaned
301
- expect(db.termManager.termCounts.get(3)).to.equal(0) // 'channel' should be orphaned
302
-
303
- // Verify record is deleted
304
- const results = await db.find({ id: record.id })
305
- expect(results).to.have.length(0)
306
- })
307
-
308
- it('should clean up orphaned terms on save', async () => {
309
- // Insert and then delete to create orphaned terms
310
- const record = await db.insert({
311
- name: 'Test Record',
312
- nameTerms: ['bra', 'globo'],
313
- groupTerms: ['channel']
314
- })
315
-
316
- await db.delete({ id: record.id })
317
- await db.save()
318
-
319
- // Check that orphaned terms were cleaned up
320
- expect(db.termManager.hasTerm('bra')).to.be.false
321
- expect(db.termManager.hasTerm('globo')).to.be.false
322
- expect(db.termManager.hasTerm('channel')).to.be.false
323
- })
324
-
325
- it('should handle complex queries with term mapping', async () => {
326
- // Insert test data
327
- await db.insert({
328
- name: 'Record 1',
329
- nameTerms: ['bra', 'globo'],
330
- groupTerms: ['channel']
331
- })
332
-
333
- await db.insert({
334
- name: 'Record 2',
335
- nameTerms: ['brasil', 'discovery'],
336
- groupTerms: ['channel', 'discovery']
337
- })
338
-
339
- await db.insert({
340
- name: 'Record 3',
341
- nameTerms: ['bra', 'brasil'],
342
- groupTerms: ['discovery']
343
- })
344
-
345
- // Test $in queries
346
- const results1 = await db.find({ nameTerms: { $in: ['bra', 'discovery'] } })
347
- expect(results1).to.have.length(3)
348
-
349
- // Test $all queries
350
- const results2 = await db.find({ groupTerms: { $all: ['channel', 'discovery'] } })
351
- expect(results2).to.have.length(1)
352
- expect(results2[0].name).to.equal('Record 2')
353
-
354
- // Test multiple criteria
355
- const results3 = await db.find({
356
- nameTerms: 'bra',
357
- groupTerms: 'channel'
358
- })
359
- expect(results3).to.have.length(1)
360
- expect(results3[0].name).to.equal('Record 1')
361
- })
362
-
363
- it('should maintain compatibility with non-term-mapping fields', async () => {
364
- const record = await db.insert({
365
- name: 'Test Record',
366
- nameTerms: ['bra', 'globo'], // Term mapping field
367
- groupTerms: ['channel'], // Term mapping field
368
- category: 'news', // Non-term mapping field
369
- tags: ['urgent', 'breaking'] // Non-term mapping field
370
- })
371
-
372
- // Query by non-term-mapping field
373
- const results = await db.find({ category: 'news' })
374
- expect(results).to.have.length(1)
375
- expect(results[0].name).to.equal('Test Record')
376
-
377
- // Query by tags (non-term-mapping array field)
378
- const results2 = await db.find({ tags: 'urgent' })
379
- expect(results2).to.have.length(1)
380
- })
381
-
382
- it('should handle empty term arrays', async () => {
383
- const record = await db.insert({
384
- name: 'Test Record',
385
- nameTerms: [],
386
- groupTerms: ['channel']
387
- })
388
-
389
- // Query should work with empty arrays
390
- const results = await db.find({ nameTerms: 'bra' })
391
- expect(results).to.have.length(0)
392
-
393
- const results2 = await db.find({ groupTerms: 'channel' })
394
- expect(results2).to.have.length(1)
395
- })
396
-
397
- it('should provide correct statistics', async () => {
398
- await db.insert({
399
- name: 'Record 1',
400
- nameTerms: ['bra', 'globo'],
401
- groupTerms: ['channel']
402
- })
403
-
404
- await db.insert({
405
- name: 'Record 2',
406
- nameTerms: ['brasil', 'discovery'],
407
- groupTerms: ['channel', 'discovery']
408
- })
409
-
410
- const stats = db.getStats()
411
- expect(stats.records).to.equal(2)
412
-
413
- const termStats = db.termManager.getStats()
414
- expect(termStats.totalTerms).to.equal(5) // bra, globo, brasil, discovery, channel
415
- })
416
- })
417
-
418
- describe('Performance Tests', () => {
419
- beforeEach(async () => {
420
- db = new Database(testDbPath, {
421
- indexes: { nameTerms: 'array:string', groupTerms: 'array:string' },
422
- termMapping: true,
423
- termMappingFields: ['nameTerms', 'groupTerms'],
424
- debugMode: false
425
- })
426
- await db.initialize()
427
- })
428
-
429
- it('should handle large datasets efficiently', async () => {
430
- const startTime = Date.now()
431
-
432
- // OPTIMIZATION: Use InsertSession for batch operations
433
- const session = db.beginInsertSession({
434
- batchSize: 100,
435
- enableAutoSave: false
436
- })
437
-
438
- // Insert 1000 records with term mapping in batches
439
- for (let i = 0; i < 1000; i++) {
440
- await session.add({
441
- name: `Record ${i}`,
442
- nameTerms: [`term${i % 100}`, `word${i % 50}`],
443
- groupTerms: [`group${i % 20}`]
444
- })
445
- }
446
-
447
- await session.commit()
448
- const insertTime = Date.now() - startTime
449
- console.log(`Inserted 1000 records in ${insertTime}ms`)
450
-
451
- // Save the database
452
- const saveStart = Date.now()
453
- await db.save()
454
- const saveTime = Date.now() - saveStart
455
- console.log(`Saved database in ${saveTime}ms`)
456
-
457
- // Query performance
458
- const queryStart = Date.now()
459
- const results = await db.find({ nameTerms: 'term1' })
460
- const queryTime = Date.now() - queryStart
461
- console.log(`Query completed in ${queryTime}ms, found ${results.length} results`)
462
-
463
- expect(results.length).to.be.greaterThan(0)
464
- expect(insertTime).to.be.lessThan(500) // OPTIMIZED: Should complete within 500ms
465
- expect(saveTime).to.be.lessThan(1000) // OPTIMIZED: Save should be fast
466
- expect(queryTime).to.be.lessThan(300) // OPTIMIZED: Query should be very fast
467
- })
468
-
469
- it('should save and load large datasets efficiently', async () => {
470
- // OPTIMIZATION: Use InsertSession for batch operations
471
- const session = db.beginInsertSession({
472
- batchSize: 50,
473
- enableAutoSave: false
474
- })
475
-
476
- // Insert test data in batches
477
- for (let i = 0; i < 100; i++) {
478
- await session.add({
479
- name: `Record ${i}`,
480
- nameTerms: [`term${i % 20}`, `word${i % 10}`],
481
- groupTerms: [`group${i % 5}`]
482
- })
483
- }
484
-
485
- await session.commit()
486
-
487
- const saveStart = Date.now()
488
- await db.save()
489
- const saveTime = Date.now() - saveStart
490
- console.log(`Saved database in ${saveTime}ms`)
491
-
492
- // Create new database and load
493
- const newDb = new Database(testDbPath, {
494
- indexes: { nameTerms: 'array:string', groupTerms: 'array:string' },
495
- debugMode: false
496
- })
497
-
498
- const loadStart = Date.now()
499
- await newDb.initialize()
500
- const loadTime = Date.now() - loadStart
501
- console.log(`Loaded database in ${loadTime}ms`)
502
-
503
- // Verify data integrity
504
- const records = await newDb.find({})
505
- expect(records).to.have.length(100)
506
-
507
- const termStats = newDb.termManager.getStats()
508
- expect(termStats.totalTerms).to.equal(35) // 20 + 10 + 5 unique terms
509
-
510
- expect(saveTime).to.be.lessThan(1500) // OPTIMIZED: Save should be fast
511
- expect(loadTime).to.be.lessThan(1000) // OPTIMIZED: Load should be very fast
512
- })
513
- })
514
- })