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,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
|
-
})
|