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,75 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from 'fs';
4
- import path from 'path';
5
-
6
- function cleanTestFiles() {
7
- const startTime = Date.now();
8
- const currentDir = process.cwd();
9
- const files = fs.readdirSync(currentDir);
10
-
11
- const testFilePatterns = [
12
- /^test-db-.*\.jdb$/,
13
- /^test-db-.*\.offsets\.jdb$/,
14
- /^test-db-.*\.idx\.jdb$/,
15
- /^test-db-.*$/, // Files without extension
16
- /^test-normalize.*\.jdb$/,
17
- /^test-normalize.*\.offsets\.jdb$/,
18
- /^test-normalize.*\.idx\.jdb$/,
19
- /^test-normalize.*$/, // Files without extension
20
- /^test-confusion.*\.jdb$/,
21
- /^test-confusion.*\.offsets\.jdb$/,
22
- /^test-confusion.*\.idx\.jdb$/,
23
- /^test-confusion.*$/, // Files without extension
24
- /^debug-.*\.jdb$/,
25
- /^debug-.*\.offsets\.jdb$/,
26
- /^debug-.*\.idx\.jdb$/,
27
- /^debug-.*$/, // Files without extension
28
- /^test-simple-.*$/, // test-simple files
29
- /^test-count\.jdb$/, // test-count.jdb file
30
- /^test-index-persistence-.*\.jdb$/, // index persistence test files
31
- /^test-index-persistence-.*\.idx\.jdb$/, // index persistence idx files
32
- /^test-indexed-mode-.*\.jdb$/, // indexed mode test files
33
- /^test-indexed-mode-.*\.idx\.jdb$/, // indexed mode idx files
34
- /^test-term-mapping-.*\.jdb$/, // term mapping test files
35
- /^test-term-mapping-.*\.idx\.jdb$/, // term mapping idx files
36
- /^test-.*\.jdb$/, // Any test file with .jdb extension
37
- /^test-.*\.idx\.jdb$/ // Any test file with .idx.jdb extension
38
- ];
39
-
40
- let cleanedCount = 0;
41
-
42
- files.forEach(file => {
43
- const filePath = path.join(currentDir, file);
44
- const stats = fs.statSync(filePath);
45
-
46
- if (stats.isFile()) {
47
- const shouldDelete = testFilePatterns.some(pattern => pattern.test(file));
48
-
49
- if (shouldDelete) {
50
- try {
51
- fs.unlinkSync(filePath);
52
- cleanedCount++;
53
- } catch (error) {
54
- console.warn(`Warning: Could not delete ${file}: ${error.message}`);
55
- }
56
- }
57
- }
58
- });
59
-
60
- const endTime = Date.now();
61
- const duration = endTime - startTime;
62
-
63
- if (cleanedCount > 0) {
64
- console.log(`✅ Cleaned ${cleanedCount} test files.`);
65
- } else {
66
- console.log('No test files found to clean.');
67
- }
68
- }
69
-
70
- // Run cleanup if this script is executed directly
71
- if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('clean-test-files.js')) {
72
- cleanTestFiles();
73
- }
74
-
75
- export default cleanTestFiles;
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync } from 'fs';
4
- import { execSync } from 'child_process';
5
- import { fileURLToPath } from 'url';
6
- import { dirname, join } from 'path';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
- const rootDir = join(__dirname, '..');
11
- const distFile = join(rootDir, 'dist', 'Database.cjs');
12
-
13
- // Check if the bundle already exists
14
- if (existsSync(distFile)) {
15
- console.log('✅ Bundle already exists, skipping build');
16
- process.exit(0);
17
- }
18
-
19
- console.log('📦 Bundle not found, building...');
20
-
21
- try {
22
- execSync('npm run build', {
23
- stdio: 'inherit',
24
- cwd: rootDir
25
- });
26
- console.log('✅ Build completed successfully');
27
- } catch (error) {
28
- console.error('❌ Build failed:', error.message);
29
- process.exit(1);
30
- }
31
-
@@ -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,45 +0,0 @@
1
- import { Database } from '../src/Database.mjs'
2
- import path from 'path'
3
- import fs from 'fs'
4
-
5
- const dbPath = path.join(process.cwd(), 'temp-score-demo.jdb')
6
- const idxPath = dbPath.replace('.jdb', '.idx.jdb')
7
-
8
- for (const file of [dbPath, idxPath]) {
9
- if (fs.existsSync(file)) {
10
- fs.unlinkSync(file)
11
- }
12
- }
13
-
14
- const db = new Database(dbPath, {
15
- indexes: { terms: 'array:string' }
16
- })
17
-
18
- await db.init()
19
-
20
- await db.insert({ id: 1, title: 'Ação', terms: ['action'] })
21
- await db.insert({ id: 2, title: 'Comédia', terms: ['comedy'] })
22
- await db.insert({ id: 3, title: 'Ação + Comédia', terms: ['action', 'comedy'] })
23
- await db.save()
24
-
25
- const weights = { action: 2.0, comedy: 1.0 }
26
-
27
- const modes = ['sum', 'max', 'avg', 'first']
28
-
29
- for (const mode of modes) {
30
- const results = await db.score('terms', weights, { mode })
31
- console.log(`\nmode=${mode}`)
32
- for (const entry of results) {
33
- console.log(` ${entry.title.padEnd(16)} score=${entry.score}`)
34
- }
35
- }
36
-
37
- await db.destroy()
38
-
39
- for (const file of [dbPath, idxPath]) {
40
- if (fs.existsSync(file)) {
41
- fs.unlinkSync(file)
42
- }
43
- }
44
-
45
-
@@ -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.