jexidb 2.1.0 โ†’ 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 (41) hide show
  1. package/dist/Database.cjs +9253 -437
  2. package/package.json +9 -2
  3. package/src/Database.mjs +1572 -212
  4. package/src/FileHandler.mjs +83 -44
  5. package/src/OperationQueue.mjs +23 -23
  6. package/src/SchemaManager.mjs +325 -268
  7. package/src/Serializer.mjs +234 -24
  8. package/src/managers/IndexManager.mjs +778 -87
  9. package/src/managers/QueryManager.mjs +340 -67
  10. package/src/managers/TermManager.mjs +7 -7
  11. package/src/utils/operatorNormalizer.mjs +116 -0
  12. package/.babelrc +0 -13
  13. package/.gitattributes +0 -2
  14. package/CHANGELOG.md +0 -140
  15. package/babel.config.json +0 -5
  16. package/docs/API.md +0 -1051
  17. package/docs/EXAMPLES.md +0 -701
  18. package/docs/README.md +0 -194
  19. package/examples/iterate-usage-example.js +0 -157
  20. package/examples/simple-iterate-example.js +0 -115
  21. package/jest.config.js +0 -24
  22. package/scripts/README.md +0 -47
  23. package/scripts/clean-test-files.js +0 -75
  24. package/scripts/prepare.js +0 -31
  25. package/scripts/run-tests.js +0 -80
  26. package/test/$not-operator-with-and.test.js +0 -282
  27. package/test/README.md +0 -8
  28. package/test/close-init-cycle.test.js +0 -256
  29. package/test/critical-bugs-fixes.test.js +0 -1069
  30. package/test/index-persistence.test.js +0 -306
  31. package/test/index-serialization.test.js +0 -314
  32. package/test/indexed-query-mode.test.js +0 -360
  33. package/test/iterate-method.test.js +0 -272
  34. package/test/query-operators.test.js +0 -238
  35. package/test/regex-array-fields.test.js +0 -129
  36. package/test/score-method.test.js +0 -238
  37. package/test/setup.js +0 -17
  38. package/test/term-mapping-minimal.test.js +0 -154
  39. package/test/term-mapping-simple.test.js +0 -257
  40. package/test/term-mapping.test.js +0 -514
  41. package/test/writebuffer-flush-resilience.test.js +0 -204
@@ -1,80 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { spawn } from 'child_process'
4
- import { fileURLToPath } from 'url'
5
- import { dirname, join } from 'path'
6
-
7
- const __filename = fileURLToPath(import.meta.url)
8
- const __dirname = dirname(__filename)
9
-
10
- async function runTests() {
11
- const startTime = Date.now()
12
-
13
- console.log('๐Ÿงช Running JexiDB tests...')
14
-
15
- // Run Jest tests
16
- const jestProcess = spawn('npx', ['jest', ...process.argv.slice(2)], {
17
- stdio: 'inherit',
18
- shell: true
19
- })
20
-
21
- jestProcess.on('close', async (code) => {
22
- if (code !== 0) {
23
- console.error(`โŒ Tests failed with exit code ${code}`)
24
- process.exit(code)
25
- }
26
-
27
- // Run cleanup
28
- const cleanupProcess = spawn('npm', ['run', 'clean:test-files'], {
29
- stdio: 'inherit',
30
- shell: true
31
- })
32
-
33
- cleanupProcess.on('close', (cleanupCode) => {
34
- const endTime = Date.now()
35
- const duration = Math.round((endTime - startTime) / 1000)
36
-
37
- // Display completion time
38
- const completionTime = new Date().toLocaleString('en-US', {
39
- year: 'numeric',
40
- month: '2-digit',
41
- day: '2-digit',
42
- hour: '2-digit',
43
- minute: '2-digit',
44
- second: '2-digit',
45
- hour12: true
46
- });
47
-
48
- console.log(`โœ… Tests completed at: ${completionTime}`);
49
- console.log(`๐Ÿ“ฆ Total execution time: ${duration}s`)
50
-
51
- if (cleanupCode !== 0) {
52
- console.warn(`โš ๏ธ Cleanup completed with warnings (exit code: ${cleanupCode})`)
53
- }
54
-
55
- process.exit(0)
56
- })
57
- })
58
-
59
- jestProcess.on('error', (error) => {
60
- console.error('โŒ Failed to start Jest:', error.message)
61
- process.exit(1)
62
- })
63
- }
64
-
65
- // Handle process termination
66
- process.on('SIGINT', () => {
67
- console.log('\n๐Ÿ›‘ Test execution interrupted by user')
68
- process.exit(0)
69
- })
70
-
71
- process.on('SIGTERM', () => {
72
- console.log('\n๐Ÿ›‘ Test execution terminated')
73
- process.exit(0)
74
- })
75
-
76
- // Run the tests
77
- runTests().catch(error => {
78
- console.error('โŒ Fatal error:', error.message)
79
- process.exit(1)
80
- })
@@ -1,282 +0,0 @@
1
- /**
2
- * $not Operator with $and on Array Fields Test
3
- *
4
- * Bug Report: https://github.com/yourrepo/jexidb/issues/XXX
5
- *
6
- * Issue: When using $not with $and on array fields in strict mode,
7
- * queries return empty results even when matching documents exist.
8
- *
9
- * Root Cause: IndexManager.query() did not handle the $not operator,
10
- * treating it as an unknown field and returning an empty set.
11
- * Additionally, when fields existed at both root level and inside $and,
12
- * only the $and conditions were being processed.
13
- *
14
- * Fix: Added proper $not handling in IndexManager.query() that:
15
- * 1. Gets all possible line numbers from database offsets
16
- * 2. Queries for the $not condition
17
- * 3. Returns the complement (all lines except those matching $not)
18
- * 4. Intersects with other root-level conditions if present
19
- * Also fixed $and to properly intersect with root-level fields.
20
- */
21
-
22
- import { Database } from '../src/Database.mjs'
23
- import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'
24
- import fs from 'fs'
25
-
26
- describe('$not Operator with $and on Array Fields', () => {
27
- let db
28
- const testFile = './test-files/not-operator-test.jdb'
29
- const testIdxFile = './test-files/not-operator-test.idx.jdb'
30
-
31
- beforeEach(async () => {
32
- // Clean up test files
33
- try {
34
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile)
35
- if (fs.existsSync(testIdxFile)) fs.unlinkSync(testIdxFile)
36
- } catch (err) {
37
- // Ignore cleanup errors
38
- }
39
-
40
- // Create database with array field
41
- db = new Database(testFile, {
42
- clear: true,
43
- create: true,
44
- integrityCheck: 'none',
45
- indexedQueryMode: 'strict',
46
- fields: {
47
- name: 'string',
48
- nameTerms: 'array:string',
49
- },
50
- indexes: ['name', 'nameTerms']
51
- })
52
-
53
- await db.init()
54
-
55
- // Insert test data
56
- const testData = [
57
- { name: 'SBT Nacional', nameTerms: ['sbt'] },
58
- { name: 'SBT HD', nameTerms: ['sbt'] },
59
- { name: 'SBT Radio', nameTerms: ['sbt', 'radio'] },
60
- { name: 'SBT FM', nameTerms: ['sbt', 'fm'] },
61
- { name: 'Radio FM', nameTerms: ['radio', 'fm'] },
62
- { name: 'Globo', nameTerms: ['globo'] },
63
- ]
64
-
65
- for (const doc of testData) {
66
- await db.insert(doc)
67
- }
68
-
69
- await db.flush()
70
- await db.close()
71
-
72
- // Re-open database
73
- db = new Database(testFile, {
74
- create: false,
75
- integrityCheck: 'none',
76
- indexedQueryMode: 'strict',
77
- fields: {
78
- name: 'string',
79
- nameTerms: 'array:string',
80
- },
81
- indexes: ['name', 'nameTerms']
82
- })
83
-
84
- await db.init()
85
- })
86
-
87
- afterEach(async () => {
88
- if (db && !db.destroyed) {
89
- try {
90
- await db.destroy()
91
- } catch (err) {
92
- // Ignore destroy errors
93
- }
94
- }
95
-
96
- // Clean up test files
97
- try {
98
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile)
99
- if (fs.existsSync(testIdxFile)) fs.unlinkSync(testIdxFile)
100
- } catch (err) {
101
- // Ignore cleanup errors
102
- }
103
- })
104
-
105
- it('should handle $not with $and (positive condition first)', async () => {
106
- const query = {
107
- $and: [
108
- { nameTerms: { $in: ['sbt'] } },
109
- { $not: { nameTerms: { $in: ['radio', 'fm'] } } }
110
- ]
111
- }
112
-
113
- const results = await db.find(query)
114
-
115
- expect(results).toHaveLength(2)
116
- expect(results[0].name).toBe('SBT Nacional')
117
- expect(results[1].name).toBe('SBT HD')
118
- })
119
-
120
- it('should handle $not with $and (negative condition first)', async () => {
121
- const query = {
122
- $and: [
123
- { $not: { nameTerms: { $in: ['radio', 'fm'] } } },
124
- { nameTerms: { $in: ['sbt'] } }
125
- ]
126
- }
127
-
128
- const results = await db.find(query)
129
-
130
- expect(results).toHaveLength(2)
131
- expect(results[0].name).toBe('SBT Nacional')
132
- expect(results[1].name).toBe('SBT HD')
133
- })
134
-
135
- it('should handle $not WITHOUT $and (root level)', async () => {
136
- const query = {
137
- nameTerms: { $in: ['sbt'] },
138
- $not: { nameTerms: { $in: ['radio', 'fm'] } }
139
- }
140
-
141
- const results = await db.find(query)
142
-
143
- expect(results).toHaveLength(2)
144
- expect(results[0].name).toBe('SBT Nacional')
145
- expect(results[1].name).toBe('SBT HD')
146
- })
147
-
148
- it('should handle multiple $not in $and with root-level field', async () => {
149
- const query = {
150
- nameTerms: { $in: ['sbt'] },
151
- $and: [
152
- { $not: { nameTerms: 'radio' } },
153
- { $not: { nameTerms: 'fm' } }
154
- ]
155
- }
156
-
157
- const results = await db.find(query)
158
-
159
- expect(results).toHaveLength(2)
160
- expect(results[0].name).toBe('SBT Nacional')
161
- expect(results[1].name).toBe('SBT HD')
162
- })
163
-
164
- it('should handle $not with single value', async () => {
165
- const query = {
166
- $and: [
167
- { nameTerms: { $in: ['sbt'] } },
168
- { $not: { nameTerms: 'radio' } }
169
- ]
170
- }
171
-
172
- const results = await db.find(query)
173
-
174
- expect(results).toHaveLength(3)
175
- const names = results.map(r => r.name).sort()
176
- expect(names).toEqual(['SBT FM', 'SBT HD', 'SBT Nacional'])
177
- })
178
-
179
- it('should handle complex $not queries with multiple conditions', async () => {
180
- const query = {
181
- $and: [
182
- { nameTerms: { $in: ['sbt', 'globo'] } },
183
- { $not: { nameTerms: { $in: ['radio', 'fm'] } } }
184
- ]
185
- }
186
-
187
- const results = await db.find(query)
188
-
189
- expect(results).toHaveLength(3)
190
- const names = results.map(r => r.name).sort()
191
- expect(names).toEqual(['Globo', 'SBT HD', 'SBT Nacional'])
192
- })
193
-
194
- it('should handle $not that excludes all results', async () => {
195
- const query = {
196
- $and: [
197
- { nameTerms: { $in: ['sbt'] } },
198
- { $not: { nameTerms: 'sbt' } }
199
- ]
200
- }
201
-
202
- const results = await db.find(query)
203
-
204
- expect(results).toHaveLength(0)
205
- })
206
-
207
- it('should handle $not with non-existent values', async () => {
208
- const query = {
209
- $and: [
210
- { nameTerms: { $in: ['sbt'] } },
211
- { $not: { nameTerms: { $in: ['nonexistent', 'invalid'] } } }
212
- ]
213
- }
214
-
215
- const results = await db.find(query)
216
-
217
- expect(results).toHaveLength(4)
218
- const names = results.map(r => r.name).sort()
219
- expect(names).toEqual(['SBT FM', 'SBT HD', 'SBT Nacional', 'SBT Radio'])
220
- })
221
-
222
- it('should handle $nin operator in strict mode', async () => {
223
- const query = {
224
- nameTerms: { $nin: ['radio', 'fm'] }
225
- }
226
-
227
- const results = await db.find(query)
228
-
229
- expect(results).toHaveLength(3)
230
- const names = results.map(r => r.name).sort()
231
- expect(names).toEqual(['Globo', 'SBT HD', 'SBT Nacional'])
232
- })
233
-
234
- it('should handle $nin with $in in strict mode', async () => {
235
- const query = {
236
- $and: [
237
- { nameTerms: { $in: ['sbt'] } },
238
- { nameTerms: { $nin: ['radio', 'fm'] } }
239
- ]
240
- }
241
-
242
- const results = await db.find(query)
243
-
244
- expect(results).toHaveLength(2)
245
- expect(results[0].name).toBe('SBT Nacional')
246
- expect(results[1].name).toBe('SBT HD')
247
- })
248
-
249
- it('should handle $nin with single value', async () => {
250
- const query = {
251
- nameTerms: { $nin: ['radio'] }
252
- }
253
-
254
- const results = await db.find(query)
255
-
256
- expect(results).toHaveLength(4)
257
- const names = results.map(r => r.name).sort()
258
- expect(names).toEqual(['Globo', 'SBT FM', 'SBT HD', 'SBT Nacional'])
259
- })
260
-
261
- it('should produce same results for $nin and $not+$in', async () => {
262
- // Query with $nin
263
- const ninQuery = {
264
- nameTerms: { $nin: ['radio', 'fm'] }
265
- }
266
-
267
- // Equivalent query with $not + $in
268
- const notQuery = {
269
- $not: { nameTerms: { $in: ['radio', 'fm'] } }
270
- }
271
-
272
- const ninResults = await db.find(ninQuery)
273
- const notResults = await db.find(notQuery)
274
-
275
- expect(ninResults).toHaveLength(notResults.length)
276
-
277
- const ninNames = ninResults.map(r => r.name).sort()
278
- const notNames = notResults.map(r => r.name).sort()
279
- expect(ninNames).toEqual(notNames)
280
- })
281
- })
282
-
package/test/README.md DELETED
@@ -1,8 +0,0 @@
1
- ## Test Results
2
- The following are the results of the automated tests conducted on my PC for JSON format.
3
-
4
- | Format | Size (bytes) | Time elapsed (ms) |
5
- |-------------------------------|--------------|--------------------|
6
- | JSON | 1117 | 21 |
7
-
8
- JSON format provides universal compatibility across all environments and Node.js versions.
@@ -1,256 +0,0 @@
1
- /**
2
- * Close โ†’ Init Cycle Tests
3
- *
4
- * Tests the ability to call init() after close() to reopen a database
5
- * This addresses the bug where databases couldn't be reopened after closing
6
- */
7
-
8
- import Database from '../src/Database.mjs'
9
- import fs from 'fs'
10
-
11
- describe('Close โ†’ Init Cycle', () => {
12
- let db
13
- const testFile = 'test-close-init-cycle.jdb'
14
-
15
- beforeEach(() => {
16
- // Clean up any existing test files
17
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile)
18
- if (fs.existsSync(testFile.replace('.jdb', '.idx.jdb'))) fs.unlinkSync(testFile.replace('.jdb', '.idx.jdb'))
19
-
20
- db = new Database(testFile, {
21
- create: true,
22
- debugMode: false
23
- })
24
- })
25
-
26
- afterEach(async () => {
27
- if (db && !db.destroyed) {
28
- try {
29
- // Save any pending data before destroying
30
- if (!db.closed && db.writeBuffer && db.writeBuffer.length > 0) {
31
- await db.save()
32
- }
33
- await db.destroy()
34
- } catch (error) {
35
- // Ignore destroy errors for this test
36
- console.warn('Destroy error ignored:', error.message)
37
- }
38
- }
39
- })
40
-
41
- describe('Basic Close โ†’ Init Cycle', () => {
42
- it('should allow init() after close()', async () => {
43
- // Initialize database
44
- await db.init()
45
- expect(db.initialized).toBe(true)
46
- expect(db.closed).toBe(false)
47
-
48
- // Insert some data
49
- await db.insert({ name: 'Test1', value: 100 })
50
- await db.insert({ name: 'Test2', value: 200 })
51
-
52
- // Close database
53
- await db.close()
54
- expect(db.closed).toBe(true)
55
- expect(db.initialized).toBe(false)
56
- expect(db.destroyed).toBe(false) // Should not be destroyed
57
-
58
- // Reinitialize database
59
- await db.init()
60
- expect(db.initialized).toBe(true)
61
- expect(db.closed).toBe(false)
62
-
63
- // Verify data is still accessible
64
- const results = await db.find({})
65
- expect(results).toHaveLength(2)
66
- expect(results.some(r => r.name === 'Test1')).toBe(true)
67
- expect(results.some(r => r.name === 'Test2')).toBe(true)
68
- })
69
-
70
- it('should support multiple close โ†’ init cycles', async () => {
71
- await db.init()
72
- await db.insert({ name: 'Test', value: 123 })
73
-
74
- // First cycle
75
- await db.close()
76
- await db.init()
77
- let results = await db.find({})
78
- expect(results).toHaveLength(1)
79
-
80
- // Second cycle
81
- await db.close()
82
- await db.init()
83
- results = await db.find({})
84
- expect(results).toHaveLength(1)
85
-
86
- // Third cycle
87
- await db.close()
88
- await db.init()
89
- results = await db.find({})
90
- expect(results).toHaveLength(1)
91
- })
92
-
93
- it('should preserve data across close โ†’ init cycles', async () => {
94
- await db.init()
95
-
96
- // Insert initial data
97
- await db.insert({ name: 'Initial', value: 1 })
98
- await db.close()
99
- await db.init()
100
-
101
- // Add more data
102
- await db.insert({ name: 'AfterReopen', value: 2 })
103
- await db.close()
104
- await db.init()
105
-
106
- // Verify all data is preserved
107
- const results = await db.find({})
108
- expect(results).toHaveLength(2)
109
- expect(results.some(r => r.name === 'Initial')).toBe(true)
110
- expect(results.some(r => r.name === 'AfterReopen')).toBe(true)
111
- })
112
- })
113
-
114
- describe('Operations After Reinit', () => {
115
- it('should support basic operations after reinit', async () => {
116
- await db.init()
117
- await db.insert({ name: 'Test1', value: 100 })
118
- await db.save() // Ensure data is saved
119
- await db.close()
120
- await db.init()
121
-
122
- // Should be able to query existing data
123
- const results = await db.find({})
124
- expect(results).toHaveLength(1)
125
- expect(results[0].name).toBe('Test1')
126
- })
127
-
128
- it('should support insert operations after reinit', async () => {
129
- await db.init()
130
- await db.close()
131
- await db.init()
132
-
133
- await db.insert({ name: 'Test2', value: 200 })
134
- const results = await db.find({})
135
- expect(results).toHaveLength(1)
136
- expect(results[0].name).toBe('Test2')
137
- })
138
- })
139
-
140
- describe('Error Handling', () => {
141
- it('should throw error for operations on closed database', async () => {
142
- await db.init()
143
- await db.insert({ name: 'Test', value: 123 })
144
- await db.close()
145
-
146
- // Operations on closed database should throw error
147
- await expect(db.find({})).rejects.toThrow('Database is closed')
148
- await expect(db.insert({ name: 'Test2', value: 456 })).rejects.toThrow('Database is closed')
149
- await expect(db.update({ name: 'Test' }, { value: 999 })).rejects.toThrow('Database is closed')
150
- await expect(db.delete({ name: 'Test' })).rejects.toThrow('Database is closed')
151
- })
152
-
153
- it('should allow reinit after operations on closed database', async () => {
154
- await db.init()
155
- await db.insert({ name: 'Test', value: 123 })
156
- await db.close()
157
-
158
- // Try operation on closed database (should fail)
159
- await expect(db.find({})).rejects.toThrow('Database is closed')
160
-
161
- // Reinit should work
162
- await db.init()
163
- const results = await db.find({})
164
- expect(results).toHaveLength(1)
165
- })
166
-
167
- it('should not allow init() on destroyed database', async () => {
168
- await db.init()
169
-
170
- // Skip this test due to writeBuffer bug in destroy()
171
- // TODO: Fix writeBuffer bug and re-enable this test
172
- console.log('โš ๏ธ Skipping destroyed database test due to writeBuffer bug')
173
- })
174
- })
175
-
176
- describe('State Management', () => {
177
- it('should have correct state flags during close โ†’ init cycle', async () => {
178
- // Initial state
179
- expect(db.initialized).toBe(false)
180
- expect(db.closed).toBe(false)
181
- expect(db.destroyed).toBe(false)
182
-
183
- // After init
184
- await db.init()
185
- expect(db.initialized).toBe(true)
186
- expect(db.closed).toBe(false)
187
- expect(db.destroyed).toBe(false)
188
-
189
- // After close
190
- await db.close()
191
- expect(db.initialized).toBe(false)
192
- expect(db.closed).toBe(true)
193
- expect(db.destroyed).toBe(false)
194
-
195
- // After reinit
196
- await db.init()
197
- expect(db.initialized).toBe(true)
198
- expect(db.closed).toBe(false)
199
- expect(db.destroyed).toBe(false)
200
- })
201
-
202
- it('should reset write buffer state after close', async () => {
203
- await db.init()
204
- await db.insert({ name: 'Test', value: 123 })
205
-
206
- // Write buffer should have data
207
- expect(db.writeBuffer.length).toBeGreaterThan(0)
208
-
209
- await db.close()
210
-
211
- // Write buffer should be cleared after close
212
- expect(db.writeBuffer.length).toBe(0)
213
- expect(db.shouldSave).toBe(false)
214
- expect(db.isSaving).toBe(false)
215
- })
216
- })
217
-
218
- describe('Performance and Memory', () => {
219
- it('should not leak memory during multiple close โ†’ init cycles', async () => {
220
- const initialMemory = process.memoryUsage().heapUsed
221
-
222
- for (let i = 0; i < 10; i++) {
223
- await db.init()
224
- await db.insert({ name: `Test${i}`, value: i })
225
- await db.close()
226
- }
227
-
228
- const finalMemory = process.memoryUsage().heapUsed
229
- const memoryIncrease = finalMemory - initialMemory
230
-
231
- // Memory increase should be reasonable (less than 10MB)
232
- expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024)
233
- })
234
-
235
- it('should handle large datasets across close โ†’ init cycles', async () => {
236
- await db.init()
237
-
238
- // Insert large dataset
239
- for (let i = 0; i < 1000; i++) {
240
- await db.insert({ name: `Record${i}`, value: i, data: 'x'.repeat(100) })
241
- }
242
-
243
- await db.close()
244
- await db.init()
245
-
246
- // Verify all data is accessible
247
- const results = await db.find({})
248
- expect(results).toHaveLength(1000)
249
-
250
- // Verify specific records
251
- const specific = await db.find({ name: 'Record500' })
252
- expect(specific).toHaveLength(1)
253
- expect(specific[0].value).toBe(500)
254
- })
255
- })
256
- })