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,298 +0,0 @@
1
- import fs from 'fs'
2
- import path from 'path'
3
- import { Database } from '../src/Database.mjs'
4
-
5
- // Clean up test files
6
- const cleanUp = (filePath) => {
7
- try {
8
- if (fs.existsSync(filePath)) {
9
- fs.unlinkSync(filePath)
10
- }
11
- } catch (error) {
12
- // Ignore cleanup errors
13
- }
14
- }
15
-
16
- describe('Score Method Tests', () => {
17
- let db
18
- let testDbPath
19
- let testIdxPath
20
-
21
- beforeEach(async () => {
22
- const testId = Math.random().toString(36).substring(7)
23
- testDbPath = path.join(process.cwd(), `test-score-${testId}.jdb`)
24
- testIdxPath = path.join(process.cwd(), `test-score-${testId}.idx.jdb`)
25
-
26
- // Clean up any existing files
27
- cleanUp(testDbPath)
28
- cleanUp(testIdxPath)
29
-
30
- // Create and initialize database
31
- db = new Database(testDbPath, {
32
- indexes: {
33
- 'terms': 'array:string'
34
- }
35
- })
36
- await db.init()
37
- })
38
-
39
- afterEach(async () => {
40
- if (db) {
41
- await db.close()
42
- db = null
43
- }
44
-
45
- // Clean up test files
46
- cleanUp(testDbPath)
47
- cleanUp(testIdxPath)
48
- })
49
-
50
- describe('Basic Score Functionality', () => {
51
- test('should score records based on terms in array field', async () => {
52
- // Insert test data
53
- await db.insert({ id: 1, title: 'Action Movie', terms: ['action', 'movie'] })
54
- await db.insert({ id: 2, title: 'Comedy Show', terms: ['comedy', 'show'] })
55
- await db.insert({ id: 3, title: 'Action Comedy', terms: ['action', 'comedy'] })
56
- await db.insert({ id: 4, title: 'Documentary', terms: ['documentary'] })
57
- await db.save()
58
-
59
- // Score records
60
- const results = await db.score('terms', {
61
- 'action': 1.0,
62
- 'comedy': 0.8
63
- })
64
-
65
- expect(results).toHaveLength(3)
66
-
67
- // Check first result (Action Comedy - highest score: 1.8)
68
- expect(results[0].title).toBe('Action Comedy')
69
- expect(results[0].score).toBe(1.8)
70
- expect(results[0]._).toBeDefined()
71
-
72
- // Check second result (Action Movie - score: 1.0)
73
- expect(results[1].title).toBe('Action Movie')
74
- expect(results[1].score).toBe(1.0)
75
-
76
- // Check third result (Comedy Show - score: 0.8)
77
- expect(results[2].title).toBe('Comedy Show')
78
- expect(results[2].score).toBe(0.8)
79
- })
80
-
81
- test('should exclude records with zero scores', async () => {
82
- await db.insert({ id: 1, title: 'Item 1', terms: ['tech'] })
83
- await db.insert({ id: 2, title: 'Item 2', terms: ['science'] })
84
- await db.insert({ id: 3, title: 'Item 3', terms: ['news'] })
85
- await db.save()
86
-
87
- const results = await db.score('terms', {
88
- 'tech': 1.0
89
- })
90
-
91
- expect(results).toHaveLength(1)
92
- expect(results[0].title).toBe('Item 1')
93
- })
94
-
95
- test('should handle decimal weights', async () => {
96
- await db.insert({ id: 1, title: 'High Priority', terms: ['urgent', 'important'] })
97
- await db.insert({ id: 2, title: 'Normal Priority', terms: ['normal'] })
98
- await db.save()
99
-
100
- const results = await db.score('terms', {
101
- 'urgent': 0.9,
102
- 'important': 0.7,
103
- 'normal': 0.3
104
- })
105
-
106
- expect(results).toHaveLength(2)
107
- expect(results[0].title).toBe('High Priority')
108
- expect(results[0].score).toBe(1.6)
109
- expect(results[1].title).toBe('Normal Priority')
110
- expect(results[1].score).toBe(0.3)
111
- })
112
- })
113
-
114
- describe('Options Tests', () => {
115
- test('should respect limit option', async () => {
116
- await db.insert({ id: 1, title: 'Item 1', terms: ['a'] })
117
- await db.insert({ id: 2, title: 'Item 2', terms: ['a', 'b'] })
118
- await db.insert({ id: 3, title: 'Item 3', terms: ['a', 'b', 'c'] })
119
- await db.insert({ id: 4, title: 'Item 4', terms: ['a'] })
120
- await db.save()
121
-
122
- const results = await db.score('terms', {
123
- 'a': 1.0,
124
- 'b': 2.0,
125
- 'c': 3.0
126
- }, { limit: 2 })
127
-
128
- expect(results).toHaveLength(2)
129
- })
130
-
131
- test('should respect sort ascending option', async () => {
132
- await db.insert({ id: 1, title: 'High', terms: ['high'] })
133
- await db.insert({ id: 2, title: 'Medium', terms: ['medium'] })
134
- await db.insert({ id: 3, title: 'Low', terms: ['low'] })
135
- await db.save()
136
-
137
- const results = await db.score('terms', {
138
- 'high': 3.0,
139
- 'medium': 2.0,
140
- 'low': 1.0
141
- }, { sort: 'asc' })
142
-
143
- expect(results).toHaveLength(3)
144
- expect(results[0].title).toBe('Low')
145
- expect(results[1].title).toBe('Medium')
146
- expect(results[2].title).toBe('High')
147
- })
148
-
149
- test('should not include score when includeScore is false', async () => {
150
- await db.insert({ id: 1, title: 'Test', terms: ['a'] })
151
- await db.save()
152
-
153
- const results = await db.score('terms', {
154
- 'a': 1.0
155
- }, { includeScore: false })
156
-
157
- expect(results).toHaveLength(1)
158
- expect(results[0].score).toBeUndefined()
159
- expect(results[0]._).toBeDefined()
160
- })
161
-
162
- test('should default to including score', async () => {
163
- await db.insert({ id: 1, title: 'Test', terms: ['a'] })
164
- await db.save()
165
-
166
- const results = await db.score('terms', {
167
- 'a': 1.0
168
- })
169
-
170
- expect(results).toHaveLength(1)
171
- expect(results[0].score).toBe(1.0)
172
- })
173
- })
174
-
175
- describe('Mode Options', () => {
176
- test('should support max mode', async () => {
177
- await db.insert({ id: 1, title: 'Action Only', terms: ['action'] })
178
- await db.insert({ id: 2, title: 'Action Comedy', terms: ['action', 'comedy'] })
179
- await db.insert({ id: 3, title: 'Comedy Only', terms: ['comedy'] })
180
- await db.save()
181
-
182
- const results = await db.score('terms', {
183
- 'action': 2.0,
184
- 'comedy': 1.0
185
- }, { mode: 'max' })
186
-
187
- expect(results).toHaveLength(3)
188
- expect(results[0].title).toBe('Action Only')
189
- expect(results[0].score).toBe(2.0)
190
- expect(results[1].title).toBe('Action Comedy')
191
- expect(results[1].score).toBe(2.0)
192
- expect(results[2].title).toBe('Comedy Only')
193
- expect(results[2].score).toBe(1.0)
194
- })
195
-
196
- test('should support avg mode', async () => {
197
- await db.insert({ id: 1, title: 'Mixed', terms: ['action', 'comedy'] })
198
- await db.insert({ id: 2, title: 'Action Only', terms: ['action'] })
199
- await db.insert({ id: 3, title: 'Comedy Only', terms: ['comedy'] })
200
- await db.save()
201
-
202
- const results = await db.score('terms', {
203
- 'action': 1.5,
204
- 'comedy': 0.9
205
- }, { mode: 'avg' })
206
-
207
- expect(results).toHaveLength(3)
208
- expect(results[0].title).toBe('Action Only')
209
- expect(results[0].score).toBeCloseTo(1.5)
210
- expect(results[1].title).toBe('Mixed')
211
- expect(results[1].score).toBeCloseTo((1.5 + 0.9) / 2)
212
- expect(results[2].title).toBe('Comedy Only')
213
- expect(results[2].score).toBeCloseTo(0.9)
214
- })
215
-
216
- test('should support first mode with term priority', async () => {
217
- await db.insert({ id: 1, title: 'High Priority', terms: ['primary', 'secondary'] })
218
- await db.insert({ id: 2, title: 'Secondary Only', terms: ['secondary'] })
219
- await db.insert({ id: 3, title: 'Unmatched', terms: ['other'] })
220
- await db.save()
221
-
222
- const results = await db.score('terms', {
223
- 'primary': 5,
224
- 'secondary': 2
225
- }, { mode: 'first' })
226
-
227
- expect(results).toHaveLength(2)
228
- expect(results[0].title).toBe('High Priority')
229
- expect(results[0].score).toBe(5)
230
- expect(results[1].title).toBe('Secondary Only')
231
- expect(results[1].score).toBe(2)
232
- })
233
- })
234
-
235
- describe('Edge Cases', () => {
236
- test('should return empty array for empty scores', async () => {
237
- await db.insert({ id: 1, title: 'Test', terms: ['a'] })
238
- await db.save()
239
-
240
- const results = await db.score('terms', {})
241
- expect(results).toHaveLength(0)
242
- })
243
-
244
- test('should return empty array when no terms match', async () => {
245
- await db.insert({ id: 1, title: 'Test', terms: ['a'] })
246
- await db.save()
247
-
248
- const results = await db.score('terms', {
249
- 'nonexistent': 1.0
250
- })
251
-
252
- expect(results).toHaveLength(0)
253
- })
254
-
255
- test('should handle empty database', async () => {
256
- const results = await db.score('terms', {
257
- 'a': 1.0
258
- })
259
-
260
- expect(results).toHaveLength(0)
261
- })
262
-
263
- test('should handle multiple occurrences of same term', async () => {
264
- await db.insert({ id: 1, title: 'Test 1', terms: ['important', 'important'] })
265
- await db.insert({ id: 2, title: 'Test 2', terms: ['important'] })
266
- await db.save()
267
-
268
- const results = await db.score('terms', {
269
- 'important': 1.0
270
- })
271
-
272
- // Both should have score 1.0 (duplicates in array don't multiply score)
273
- expect(results).toHaveLength(2)
274
- })
275
- })
276
-
277
- describe('Error Handling', () => {
278
- test('should throw error for invalid fieldName', async () => {
279
- await expect(db.score('', { 'a': 1.0 })).rejects.toThrow('non-empty string')
280
- await expect(db.score(null, { 'a': 1.0 })).rejects.toThrow('non-empty string')
281
- })
282
-
283
- test('should throw error for non-indexed field', async () => {
284
- await expect(db.score('nonexistent', { 'a': 1.0 }))
285
- .rejects.toThrow('not indexed')
286
- })
287
-
288
- test('should throw error for invalid scores object', async () => {
289
- await expect(db.score('terms', null)).rejects.toThrow('must be an object')
290
- await expect(db.score('terms', [])).rejects.toThrow('must be an object')
291
- })
292
-
293
- test('should throw error for non-numeric scores', async () => {
294
- await expect(db.score('terms', { 'a': 'invalid' }))
295
- .rejects.toThrow('must be a number')
296
- })
297
- })
298
- })
package/test/setup.js DELETED
@@ -1,17 +0,0 @@
1
- // Setup file to add Mocha-style matchers to Jest
2
- import { expect } from '@jest/globals'
3
-
4
- // Add the chai-style API to expect
5
- expect.to = {
6
- deep: {
7
- equal: (received, expected) => {
8
- return expect(received).toEqual(expected)
9
- }
10
- },
11
- equal: (received, expected) => {
12
- return expect(received).toBe(expected)
13
- }
14
- }
15
-
16
- // Also add to global expect
17
- global.expect = expect
@@ -1,154 +0,0 @@
1
- import fs from 'fs'
2
- import path from 'path'
3
- import TermManager from '../src/managers/TermManager.mjs'
4
-
5
- describe('Term Mapping - Minimal Tests', () => {
6
- let termManager
7
-
8
- beforeEach(() => {
9
- termManager = new TermManager()
10
- })
11
-
12
- describe('TermManager', () => {
13
- it('should create and retrieve term IDs', () => {
14
- const id1 = termManager.getTermId('bra')
15
- const id2 = termManager.getTermId('globo')
16
- const id3 = termManager.getTermId('bra') // Same term
17
-
18
- expect(id1).toBe(1)
19
- expect(id2).toBe(2)
20
- expect(id3).toBe(1) // Should return same ID
21
-
22
- expect(termManager.getTerm(1)).toBe('bra')
23
- expect(termManager.getTerm(2)).toBe('globo')
24
- })
25
-
26
- it('should track term usage counts', () => {
27
- const id1 = termManager.getTermId('bra')
28
- const id2 = termManager.getTermId('bra')
29
- const id3 = termManager.getTermId('bra')
30
-
31
- expect(termManager.termCounts.get(id1)).toBe(3)
32
- })
33
-
34
- it('should clean up orphaned terms', () => {
35
- const id1 = termManager.getTermId('bra')
36
- const id2 = termManager.getTermId('globo')
37
-
38
- // Decrement counts to make them orphaned
39
- termManager.decrementTermCount(id1)
40
- termManager.decrementTermCount(id1)
41
- termManager.decrementTermCount(id2)
42
-
43
- const orphanedCount = termManager.cleanupOrphanedTerms(true)
44
- expect(orphanedCount).toBe(2)
45
-
46
- // Terms should be removed
47
- expect(termManager.getTerm(id1)).toBeNull()
48
- expect(termManager.getTerm(id2)).toBeNull()
49
- })
50
-
51
- it('should load and save terms', () => {
52
- // Create some terms
53
- termManager.getTermId('bra')
54
- termManager.getTermId('globo')
55
- termManager.getTermId('brasil')
56
-
57
- // Save terms
58
- const savedTerms = termManager.saveTerms()
59
- expect(savedTerms).toEqual({
60
- '1': 'bra',
61
- '2': 'globo',
62
- '3': 'brasil'
63
- })
64
-
65
- // Create new manager and load terms
66
- const newManager = new TermManager()
67
- newManager.loadTerms(savedTerms)
68
-
69
- expect(newManager.getTerm(1)).toBe('bra')
70
- expect(newManager.getTerm(2)).toBe('globo')
71
- expect(newManager.getTerm(3)).toBe('brasil')
72
- })
73
-
74
- it('should provide statistics', () => {
75
- termManager.getTermId('bra')
76
- termManager.getTermId('globo')
77
-
78
- const stats = termManager.getStats()
79
- expect(stats.totalTerms).toBe(2)
80
- expect(stats.nextId).toBe(3)
81
- })
82
- })
83
-
84
- describe('Term Mapping Concept', () => {
85
- it('should demonstrate term mapping benefits', () => {
86
- // Simulate a large dataset with repeated terms
87
- const terms = ['bra', 'globo', 'brasil', 'discovery', 'channel']
88
- const repeatedTerms = []
89
-
90
- // Create 1000 records with repeated terms
91
- for (let i = 0; i < 1000; i++) {
92
- const randomTerms = []
93
- for (let j = 0; j < 5; j++) {
94
- randomTerms.push(terms[Math.floor(Math.random() * terms.length)])
95
- }
96
- repeatedTerms.push(randomTerms)
97
- }
98
-
99
- // Map all terms to IDs
100
- const startTime = Date.now()
101
- const mappedData = repeatedTerms.map(record =>
102
- record.map(term => termManager.getTermId(term))
103
- )
104
- const mappingTime = Date.now() - startTime
105
-
106
- // Verify mapping worked
107
- expect(mappedData.length).toBe(1000)
108
- expect(mappedData[0].length).toBe(5)
109
- expect(termManager.getStats().totalTerms).toBe(5) // Only 5 unique terms
110
-
111
- console.log(`✅ Mapped 5000 terms to 5 unique IDs in ${mappingTime}ms`)
112
- console.log(`📊 Term mapping stats:`, termManager.getStats())
113
- })
114
-
115
- it('should show size reduction potential', () => {
116
- // Original data with repeated strings
117
- const originalData = [
118
- { id: 1, nameTerms: ['bra', 'globo', 'brasil'] },
119
- { id: 2, nameTerms: ['bra', 'discovery', 'channel'] },
120
- { id: 3, nameTerms: ['globo', 'brasil', 'discovery'] },
121
- { id: 4, nameTerms: ['bra', 'globo', 'channel'] },
122
- { id: 5, nameTerms: ['brasil', 'discovery', 'channel'] }
123
- ]
124
-
125
- // Calculate original size
126
- const originalSize = JSON.stringify(originalData).length
127
-
128
- // Map terms to IDs
129
- const mappedData = originalData.map(record => ({
130
- id: record.id,
131
- nameTerms: record.nameTerms.map(term => termManager.getTermId(term))
132
- }))
133
-
134
- // Create term mapping
135
- const termMapping = termManager.saveTerms()
136
-
137
- // Calculate new size
138
- const mappedSize = JSON.stringify(mappedData).length
139
- const termMappingSize = JSON.stringify(termMapping).length
140
- const totalNewSize = mappedSize + termMappingSize
141
-
142
- const reduction = ((originalSize - totalNewSize) / originalSize * 100).toFixed(1)
143
-
144
- console.log(`📊 Size comparison:`)
145
- console.log(` Original: ${originalSize} bytes`)
146
- console.log(` Mapped: ${mappedSize} bytes`)
147
- console.log(` Terms: ${termMappingSize} bytes`)
148
- console.log(` Total: ${totalNewSize} bytes`)
149
- console.log(` Reduction: ${reduction}%`)
150
-
151
- expect(totalNewSize).toBeLessThan(originalSize)
152
- })
153
- })
154
- })