jexidb 2.1.1 → 2.1.2

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 +7621 -113
  2. package/package.json +9 -2
  3. package/src/Database.mjs +244 -79
  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,491 +0,0 @@
1
- import { Database } from '../src/Database.mjs'
2
- import fs from 'fs'
3
-
4
- describe('Index File Persistence', () => {
5
- let testDbPath
6
- let testIdxPath
7
-
8
- beforeEach(() => {
9
- testDbPath = `test-index-persistence-${Date.now()}-${Math.random()}.jdb`
10
- testIdxPath = testDbPath.replace('.jdb', '.idx.jdb')
11
- })
12
-
13
- afterEach(() => {
14
- // Clean up test files
15
- const filesToClean = [testDbPath, testIdxPath]
16
- filesToClean.forEach(filePath => {
17
- if (fs.existsSync(filePath)) {
18
- try {
19
- fs.unlinkSync(filePath)
20
- } catch (error) {
21
- console.warn(`Warning: Could not delete ${filePath}: ${error.message}`)
22
- }
23
- }
24
- })
25
- })
26
-
27
- test('should generate .idx files with actual index data after database destruction', async () => {
28
- // Create database with indexes
29
- const db = new Database(testDbPath, {
30
- indexes: { name: 'string', category: 'string', tags: 'array' },
31
- debugMode: false
32
- })
33
-
34
- await db.init()
35
-
36
- // Insert test data with various field types including accented characters
37
- const testData = [
38
- { id: 1, name: 'João Silva', category: 'usuário', tags: ['admin', 'ativo'] },
39
- { id: 2, name: 'José Santos', category: 'usuário', tags: ['membro', 'ativo'] },
40
- { id: 3, name: 'Maria Antônia', category: 'administrador', tags: ['admin', 'super'] },
41
- { id: 4, name: 'Ana Carolina', category: 'usuário', tags: ['membro'] },
42
- { id: 5, name: 'Carlos Eduardo', category: 'convidado', tags: ['visitante'] },
43
- { id: 6, name: 'François Dubois', category: 'usuário', tags: ['francês', 'ativo'] },
44
- { id: 7, name: 'José María', category: 'administrador', tags: ['espanhol', 'admin'] }
45
- ]
46
-
47
- for (const record of testData) {
48
- await db.insert(record)
49
- }
50
-
51
- // Verify data was inserted
52
- expect(db.length).toBe(7)
53
-
54
- // Force index building by performing queries
55
- const userResults = await db.find({ category: 'usuário' })
56
- expect(userResults.length).toBe(4)
57
-
58
- const adminTagResults = await db.find({ tags: { $contains: 'admin' } })
59
- expect(adminTagResults.length).toBe(3)
60
-
61
- // Destroy the database instance (this should save indexes)
62
- await db.close()
63
-
64
- // Verify that .idx file was created
65
- expect(fs.existsSync(testIdxPath)).toBe(true)
66
-
67
- // Read and verify the .idx file contains actual index data
68
- const idxFileContent = fs.readFileSync(testIdxPath, 'utf8')
69
- expect(idxFileContent).toBeTruthy()
70
- expect(idxFileContent.length).toBeGreaterThan(0)
71
-
72
- // The .idx file should contain a single JSON object with combined index and offsets
73
- let combinedData
74
- try {
75
- combinedData = JSON.parse(idxFileContent)
76
- } catch (parseError) {
77
- throw new Error(`Failed to parse .idx file content: ${parseError.message}`)
78
- }
79
-
80
- // Verify the structure contains index and offsets
81
- expect(combinedData).toBeDefined()
82
- expect(combinedData.index).toBeDefined()
83
- expect(combinedData.offsets).toBeDefined()
84
- expect(Array.isArray(combinedData.offsets)).toBe(true)
85
- expect(combinedData.offsets.length).toBe(7) // Database uses offsets for efficient file operations
86
-
87
- // Verify the index data contains our indexed fields
88
- const indexData = combinedData.index.data
89
- expect(indexData).toBeDefined()
90
- expect(typeof indexData).toBe('object')
91
-
92
- // Check each indexed field has data
93
- const expectedFields = ['name', 'category', 'tags']
94
- for (const field of expectedFields) {
95
- expect(indexData[field]).toBeDefined()
96
- expect(typeof indexData[field]).toBe('object')
97
-
98
- // Verify the field index contains actual values from our test data
99
- const fieldIndex = indexData[field]
100
- const fieldKeys = Object.keys(fieldIndex)
101
- expect(fieldKeys.length).toBeGreaterThan(0)
102
-
103
- if (field === 'category') {
104
- // Fields with type 'string' use original values, not term IDs
105
- // Just verify that we have some actual category values
106
- const hasExpectedValues = fieldKeys.length > 0
107
- expect(hasExpectedValues).toBe(true)
108
- } else if (field === 'tags') {
109
- // Should contain tag entries like 'admin', 'membro', 'ativo', etc.
110
- const hasExpectedValues = fieldKeys.some(key =>
111
- key === 'admin' || key === 'membro' || key === 'ativo' || key === 'super' || key === 'visitante' || key === 'francês' || key === 'espanhol'
112
- )
113
- expect(hasExpectedValues).toBe(true)
114
- } else if (field === 'name') {
115
- // Fields with type 'string' use original values, not term IDs
116
- // Just verify that we have some actual name values
117
- const hasExpectedValues = fieldKeys.length > 0
118
- expect(hasExpectedValues).toBe(true)
119
- }
120
- }
121
-
122
- // Create a new database instance with the same path to verify indexes are loaded
123
- const db2 = new Database(testDbPath, {
124
- indexes: { name: 'string', category: 'string', tags: 'array' },
125
- debugMode: false
126
- })
127
-
128
- await db2.init()
129
-
130
- // Verify the new instance can use the persisted indexes
131
- const reloadedUserResults = await db2.find({ category: 'usuário' })
132
- expect(reloadedUserResults.length).toBe(4)
133
-
134
- const reloadedAdminTagResults = await db2.find({ tags: { $contains: 'admin' } })
135
- expect(reloadedAdminTagResults.length).toBe(3)
136
-
137
- // Verify data integrity
138
- expect(db2.length).toBe(7)
139
-
140
- await db2.destroy()
141
- })
142
-
143
- test('should handle empty database with indexes', async () => {
144
- const db = new Database(testDbPath, {
145
- indexes: { field1: 'string', field2: 'string' },
146
- debugMode: false
147
- })
148
-
149
- await db.init()
150
-
151
- // Don't insert any data, just close
152
- await db.close()
153
-
154
- // .idx file should NOT be created for empty databases
155
- // This prevents creating empty index files that could be mistaken for valid indexes
156
- expect(fs.existsSync(testIdxPath)).toBe(false)
157
-
158
- // Verify we can still recreate the database and it works correctly
159
- const db2 = new Database(testDbPath, {
160
- indexes: { field1: 'string', field2: 'string' },
161
- debugMode: false
162
- })
163
-
164
- await db2.init()
165
-
166
- // Database should be empty
167
- expect(db2.length).toBe(0)
168
-
169
- // Should be able to query (will use streaming since no indexes exist)
170
- const results = await db2.find({ field1: 'nonexistent' })
171
- expect(results.length).toBe(0)
172
-
173
- await db2.destroy()
174
- })
175
-
176
- test('should persist complex index structures', async () => {
177
- const db = new Database(testDbPath, {
178
- indexes: { simpleField: 'string', arrayField: 'array', nestedField: 'object' },
179
- debugMode: false
180
- })
181
-
182
- await db.init()
183
-
184
- // Insert data with complex structures
185
- await db.insert({
186
- id: 1,
187
- simpleField: 'simple_value',
188
- arrayField: ['item1', 'item2', 'item3'],
189
- nestedField: { subfield: 'nested_value' }
190
- })
191
-
192
- await db.insert({
193
- id: 2,
194
- simpleField: 'another_value',
195
- arrayField: ['item2', 'item4'],
196
- nestedField: { subfield: 'another_nested' }
197
- })
198
-
199
- // Force index usage with queries
200
- await db.find({ simpleField: 'simple_value' })
201
- await db.find({ arrayField: { $contains: 'item2' } })
202
-
203
- await db.close()
204
-
205
- // Verify .idx file was created and has content
206
- expect(fs.existsSync(testIdxPath)).toBe(true)
207
-
208
- const idxFileContent = fs.readFileSync(testIdxPath, 'utf8')
209
- expect(idxFileContent.length).toBeGreaterThan(0)
210
-
211
- // Verify we can recreate and use the database
212
- const db2 = new Database(testDbPath, {
213
- indexes: { simpleField: 'string', arrayField: 'array', nestedField: 'object' },
214
- debugMode: false
215
- })
216
-
217
- await db2.init()
218
-
219
- const results = await db2.find({ simpleField: 'simple_value' })
220
- expect(results.length).toBe(1)
221
- expect(results[0].id).toBe(1)
222
-
223
- await db2.destroy()
224
- })
225
-
226
- test('should maintain index consistency after multiple operations', async () => {
227
- const db = new Database(testDbPath, {
228
- indexes: { status: 'string', priority: 'string' },
229
- debugMode: false
230
- })
231
-
232
- await db.init()
233
-
234
- // Insert test data with different status and priority combinations
235
- await db.insert({ id: 1, status: 'active', priority: 'high' })
236
- await db.insert({ id: 2, status: 'inactive', priority: 'low' })
237
- await db.insert({ id: 3, status: 'pending', priority: 'medium' })
238
- await db.insert({ id: 4, status: 'active', priority: 'low' })
239
-
240
- // Query to ensure indexes are built
241
- const activeResults1 = await db.find({ status: 'active' })
242
- expect(activeResults1.length).toBe(2) // id 1 and id 4
243
-
244
- const highPriorityResults1 = await db.find({ priority: 'high' })
245
- expect(highPriorityResults1.length).toBe(1) // id 1
246
-
247
- await db.close()
248
-
249
- // Verify index file persistence
250
- expect(fs.existsSync(testIdxPath)).toBe(true)
251
-
252
- // Read and verify the index file contains the expected data
253
- const idxFileContent = fs.readFileSync(testIdxPath, 'utf8')
254
- const combinedData = JSON.parse(idxFileContent)
255
-
256
- expect(combinedData.index).toBeDefined()
257
- expect(combinedData.offsets).toBeDefined()
258
- expect(combinedData.offsets.length).toBe(4) // Database uses offsets for efficient file operations
259
-
260
- const indexData = combinedData.index.data
261
- expect(indexData.status).toBeDefined()
262
- expect(indexData.priority).toBeDefined()
263
-
264
- // Verify status index contains our test values
265
- // Fields with type 'string' use original values, not term IDs
266
- const statusKeys = Object.keys(indexData.status)
267
- expect(statusKeys.length).toBeGreaterThan(0)
268
- // Verify we have actual string values
269
- const hasStatusValues = statusKeys.length > 0
270
- expect(hasStatusValues).toBe(true)
271
-
272
- // Verify priority index contains our test values
273
- // Fields with type 'string' use original values, not term IDs
274
- const priorityKeys = Object.keys(indexData.priority)
275
- expect(priorityKeys.length).toBeGreaterThan(0)
276
- // Verify we have actual string values
277
- const hasPriorityValues = priorityKeys.length > 0
278
- expect(hasPriorityValues).toBe(true)
279
-
280
- // Recreate database and verify consistency
281
- const db2 = new Database(testDbPath, {
282
- indexes: { status: 'string', priority: 'string' },
283
- debugMode: false
284
- })
285
-
286
- await db2.init()
287
-
288
- // Verify data integrity
289
- expect(db2.length).toBe(4)
290
-
291
- // Test queries work correctly with reloaded indexes
292
- const activeResults2 = await db2.find({ status: 'active' })
293
- expect(activeResults2.length).toBe(2)
294
- expect(activeResults2.map(r => r.id).sort()).toEqual([1, 4])
295
-
296
- const highPriorityResults2 = await db2.find({ priority: 'high' })
297
- expect(highPriorityResults2.length).toBe(1)
298
- expect(highPriorityResults2[0].id).toBe(1)
299
-
300
- const pendingResults = await db2.find({ status: 'pending' })
301
- expect(pendingResults.length).toBe(1)
302
- expect(pendingResults[0].id).toBe(3)
303
-
304
- await db2.destroy()
305
- })
306
-
307
- test('should NOT overwrite valid index with empty data when closing empty database', async () => {
308
- // Create database with indexes and data
309
- const db1 = new Database(testDbPath, {
310
- indexes: { name: 'string', value: 'number' },
311
- debugMode: false
312
- })
313
-
314
- await db1.init()
315
-
316
- // Insert test data
317
- await db1.insert({ id: 1, name: 'Test', value: 100 })
318
- await db1.insert({ id: 2, name: 'Test2', value: 200 })
319
-
320
- // Query to build indexes
321
- await db1.find({ name: 'Test' })
322
-
323
- await db1.close()
324
-
325
- // Verify index file exists and has data
326
- expect(fs.existsSync(testIdxPath)).toBe(true)
327
- const idxContent1 = JSON.parse(fs.readFileSync(testIdxPath, 'utf8'))
328
- expect(idxContent1.index).toBeDefined()
329
- expect(Object.keys(idxContent1.index.data || {}).length).toBeGreaterThan(0)
330
- expect(idxContent1.offsets.length).toBe(2)
331
-
332
- // Now open database WITHOUT loading index properly (simulate corrupted load)
333
- // This should trigger a rebuild, but we'll simulate closing with empty index
334
- const db2 = new Database(testDbPath, {
335
- create: false,
336
- indexes: { name: 'string', value: 'number' },
337
- debugMode: false
338
- })
339
-
340
- await db2.init()
341
-
342
- // Verify index was loaded correctly
343
- const countBefore = await db2.count({ name: 'Test' })
344
- expect(countBefore).toBe(1)
345
-
346
- // Now simulate a scenario where index becomes empty (shouldn't happen, but test protection)
347
- // Clear the index manager's data
348
- db2.indexManager.index = { data: {} }
349
- db2.offsets = []
350
-
351
- // Close - this should NOT overwrite the valid index file
352
- await db2.close()
353
-
354
- // Verify index file still has the original data (not overwritten)
355
- const idxContent2 = JSON.parse(fs.readFileSync(testIdxPath, 'utf8'))
356
- expect(idxContent2.index).toBeDefined()
357
- // The index should still have data (either from original or from rebuild)
358
- // But importantly, it should NOT be empty
359
- expect(Object.keys(idxContent2.index.data || {}).length).toBeGreaterThan(0)
360
- expect(idxContent2.offsets.length).toBeGreaterThan(0)
361
-
362
- await db2.destroy()
363
- })
364
-
365
- test('should throw error when index is corrupted and allowIndexRebuild is false (default)', async () => {
366
- // Create database with indexes and data
367
- const db1 = new Database(testDbPath, {
368
- indexes: { name: 'string', value: 'number' },
369
- debugMode: false
370
- })
371
-
372
- await db1.init()
373
-
374
- // Insert test data
375
- await db1.insert({ id: 1, name: 'Test', value: 100 })
376
- await db1.insert({ id: 2, name: 'Test2', value: 200 })
377
-
378
- // Query to build indexes
379
- await db1.find({ name: 'Test' })
380
-
381
- await db1.close()
382
-
383
- // Corrupt the index file by making it empty but valid JSON
384
- // Need to ensure it has the right structure so it parses but has no data
385
- const corruptedIdx = JSON.stringify({
386
- index: { data: {} },
387
- offsets: [],
388
- indexOffset: 0,
389
- config: {
390
- schema: [],
391
- indexes: { name: 'string', value: 'number' }
392
- }
393
- })
394
- fs.writeFileSync(testIdxPath, corruptedIdx)
395
-
396
- // Try to open database - allowIndexRebuild defaults to false, will throw error
397
- const db2 = new Database(testDbPath, {
398
- create: false,
399
- indexes: { name: 'string', value: 'number' },
400
- // allowIndexRebuild defaults to false - will throw error
401
- debugMode: false
402
- })
403
-
404
- // Should throw error during init() - corrupted index (empty but file exists)
405
- await expect(db2.init()).rejects.toThrow(/Index file is corrupted.*exists but contains no index data/)
406
- })
407
-
408
- test('should throw error when index is missing and allowIndexRebuild is false (default)', async () => {
409
- // Create database with indexes and data
410
- const db1 = new Database(testDbPath, {
411
- indexes: { name: 'string', value: 'number' },
412
- debugMode: false
413
- })
414
-
415
- await db1.init()
416
-
417
- // Insert test data
418
- await db1.insert({ id: 1, name: 'Test', value: 100 })
419
-
420
- await db1.save()
421
- await db1.close()
422
-
423
- // Delete the index file
424
- if (fs.existsSync(testIdxPath)) {
425
- fs.unlinkSync(testIdxPath)
426
- }
427
-
428
- // Try to open database - allowIndexRebuild defaults to false, will throw error
429
- const db2 = new Database(testDbPath, {
430
- create: false,
431
- indexes: { name: 'string', value: 'number' },
432
- // allowIndexRebuild defaults to false - will throw error
433
- debugMode: false
434
- })
435
-
436
- // Should throw error during init() - missing index file
437
- await expect(db2.init()).rejects.toThrow(/Index file is missing or corrupted.*does not exist or is invalid/)
438
- })
439
-
440
- test('should rebuild automatically when allowIndexRebuild is explicitly true', async () => {
441
- // Create database with indexes and data
442
- const db1 = new Database(testDbPath, {
443
- indexes: { name: 'string', value: 'number' },
444
- debugMode: false
445
- })
446
-
447
- await db1.init()
448
-
449
- // Insert test data
450
- await db1.insert({ id: 1, name: 'Test', value: 100 })
451
- await db1.insert({ id: 2, name: 'Test2', value: 200 })
452
-
453
- await db1.save()
454
- await db1.close()
455
-
456
- // Corrupt the index file by making it empty but valid JSON with config
457
- // This simulates a corrupted index that still has config info
458
- const corruptedIdx = JSON.stringify({
459
- index: { data: {} },
460
- offsets: [],
461
- indexOffset: 0,
462
- config: {
463
- schema: ['id', 'name', 'value'],
464
- indexes: { name: 'string', value: 'number' }
465
- }
466
- })
467
- fs.writeFileSync(testIdxPath, corruptedIdx)
468
-
469
- // Open database with allowIndexRebuild explicitly set to true
470
- const db2 = new Database(testDbPath, {
471
- create: false,
472
- indexes: { name: 'string', value: 'number' },
473
- allowIndexRebuild: true, // Explicitly enable rebuild
474
- debugMode: false
475
- })
476
-
477
- // Should succeed and rebuild automatically
478
- await db2.init()
479
-
480
- // Rebuild happens lazily on first query - trigger it
481
- // Query should work after rebuild
482
- const count = await db2.count({ name: 'Test' })
483
- expect(count).toBe(1)
484
-
485
- const results = await db2.find({ name: 'Test' })
486
- expect(results.length).toBe(1)
487
- expect(results[0].id).toBe(1)
488
-
489
- await db2.destroy()
490
- })
491
- })