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,238 +0,0 @@
1
- /**
2
- * Comprehensive tests for query operators
3
- * Tests the fixes for the $not operator and default operator behavior bugs
4
- */
5
-
6
- import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
7
- import Database from '../src/Database.mjs';
8
- import fs from 'fs';
9
- import path from 'path';
10
-
11
- describe('Query Operators', () => {
12
- let db;
13
- const testDbPath = 'test-query-operators-comprehensive';
14
-
15
- beforeEach(async () => {
16
- // Clean up any existing test database
17
- if (fs.existsSync(testDbPath + '.jdb')) {
18
- fs.unlinkSync(testDbPath + '.jdb');
19
- }
20
- if (fs.existsSync(testDbPath + '.terms.jdb')) {
21
- fs.unlinkSync(testDbPath + '.terms.jdb');
22
- }
23
-
24
- db = new Database(testDbPath, {
25
- debugMode: false,
26
- termMapping: true,
27
- termMappingFields: ['nameTerms', 'tags']
28
- });
29
-
30
- await db.init();
31
-
32
- // Insert comprehensive test data
33
- const testData = [
34
- { id: 1, name: 'TV Câmara', nameTerms: ['tv', 'câmara'], tags: ['news', 'politics'], group: 'Brazil', rating: 4.5 },
35
- { id: 2, name: 'TV Cultura', nameTerms: ['tv', 'cultura'], tags: ['culture', 'education'], group: 'Brazil', rating: 4.2 },
36
- { id: 3, name: 'SBT', nameTerms: ['sbt'], tags: ['entertainment'], group: 'Brazil', rating: 3.8 },
37
- { id: 4, name: 'Record News', nameTerms: ['record', 'news'], tags: ['news'], group: 'Brazil', rating: 4.0 },
38
- { id: 5, name: 'CNN', nameTerms: ['cnn'], tags: ['news', 'international'], group: 'International', rating: 4.7 },
39
- { id: 6, name: 'BBC', nameTerms: ['bbc'], tags: ['news', 'international'], group: 'International', rating: 4.6 },
40
- { id: 7, name: 'TV Globo', nameTerms: ['tv', 'globo'], tags: ['entertainment', 'news'], group: 'Brazil', rating: 4.3 },
41
- { id: 8, name: 'TV Record', nameTerms: ['tv', 'record'], tags: ['entertainment'], group: 'Brazil', rating: 3.9 },
42
- { id: 9, name: 'Discovery', nameTerms: ['discovery'], tags: ['documentary', 'education'], group: 'International', rating: 4.4 },
43
- { id: 10, name: 'National Geographic', nameTerms: ['national', 'geographic'], tags: ['documentary', 'nature'], group: 'International', rating: 4.8 }
44
- ];
45
-
46
- for (const record of testData) {
47
- await db.insert(record);
48
- }
49
- });
50
-
51
- afterEach(async () => {
52
- if (db) {
53
- await db.close();
54
- }
55
-
56
- // Clean up test files
57
- const files = [testDbPath + '.jdb', testDbPath + '.terms.jdb'];
58
- for (const file of files) {
59
- if (fs.existsSync(file)) {
60
- fs.unlinkSync(file);
61
- }
62
- }
63
- });
64
-
65
- describe('$not Operator Fixes', () => {
66
- it('should handle $not operator consistently with or without explicit $and', async () => {
67
- // Test 1: $not without explicit $and (the bug case)
68
- const result1 = await db.find({nameTerms: 'tv', $not: {nameTerms: { $in: ['cultura'] }}});
69
- expect(result1).toHaveLength(3); // TV Câmara, TV Globo, TV Record
70
- expect(result1.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'TV Globo', 'TV Record']));
71
- expect(result1.map(r => r.name)).not.toContain('TV Cultura');
72
-
73
- // Test 2: $not with explicit $and (should give same result)
74
- const result2 = await db.find({"$and": [{nameTerms: 'tv'}, {$not: {nameTerms: { $in: ['cultura'] }}}]});
75
- expect(result2).toHaveLength(3);
76
- expect(result2.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'TV Globo', 'TV Record']));
77
- expect(result2.map(r => r.name)).not.toContain('TV Cultura');
78
-
79
- // Both results should be identical
80
- expect(result1.map(r => r.id).sort()).toEqual(result2.map(r => r.id).sort());
81
- });
82
-
83
- it('should handle $not with different fields', async () => {
84
- const result = await db.find({group: 'Brazil', $not: {nameTerms: { $in: ['globo'] }}});
85
- expect(result).toHaveLength(5); // All Brazil channels except TV Globo
86
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'TV Cultura', 'SBT', 'Record News', 'TV Record']));
87
- expect(result.map(r => r.name)).not.toContain('TV Globo');
88
- });
89
-
90
- it('should handle complex $not queries', async () => {
91
- const result = await db.find({nameTerms: 'tv', $not: {nameTerms: { $in: ['cultura', 'globo'] }}});
92
- expect(result).toHaveLength(2); // TV Câmara, TV Record
93
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'TV Record']));
94
- expect(result.map(r => r.name)).not.toContain('TV Cultura');
95
- expect(result.map(r => r.name)).not.toContain('TV Globo');
96
- });
97
-
98
- it('should handle $not with numeric comparisons', async () => {
99
- const result = await db.find({group: 'Brazil', $not: {rating: { $gte: 4.0 }}});
100
- expect(result).toHaveLength(2); // SBT (3.8), TV Record (3.9)
101
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['SBT', 'TV Record']));
102
- });
103
-
104
- it('should handle nested $not operators', async () => {
105
- const result = await db.find({$not: {nameTerms: { $in: ['tv'] }}});
106
- expect(result).toHaveLength(6); // All channels without 'tv' in nameTerms
107
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['SBT', 'Record News', 'CNN', 'BBC', 'Discovery', 'National Geographic']));
108
- expect(result.map(r => r.name)).not.toContain('TV Câmara');
109
- expect(result.map(r => r.name)).not.toContain('TV Cultura');
110
- expect(result.map(r => r.name)).not.toContain('TV Globo');
111
- expect(result.map(r => r.name)).not.toContain('TV Record');
112
- });
113
- });
114
-
115
- describe('Default Operator Behavior (AND Logic)', () => {
116
- it('should use AND logic for multiple conditions at root level', async () => {
117
- const result = await db.find({nameTerms: 'tv', group: 'Brazil'});
118
- expect(result).toHaveLength(4); // All TV channels in Brazil
119
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'TV Cultura', 'TV Globo', 'TV Record']));
120
- });
121
-
122
- it('should use AND logic for multiple field conditions', async () => {
123
- const result = await db.find({group: 'International', rating: { $gte: 4.5 }});
124
- expect(result).toHaveLength(3); // CNN, BBC, National Geographic
125
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['CNN', 'BBC', 'National Geographic']));
126
- });
127
-
128
- it('should use AND logic with array field conditions', async () => {
129
- const result = await db.find({tags: { $in: ['news'] }, group: 'Brazil'});
130
- expect(result).toHaveLength(3); // TV Câmara, Record News, TV Globo
131
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'Record News', 'TV Globo']));
132
- });
133
-
134
- it('should use AND logic with mixed operators', async () => {
135
- const result = await db.find({
136
- nameTerms: 'tv',
137
- group: 'Brazil',
138
- rating: { $gte: 4.0 },
139
- tags: { $in: ['news'] }
140
- });
141
- expect(result).toHaveLength(2); // TV Câmara, TV Globo
142
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'TV Globo']));
143
- });
144
- });
145
-
146
- describe('Complex Query Combinations', () => {
147
- it('should handle $and with $not', async () => {
148
- const result = await db.find({
149
- "$and": [
150
- {group: 'Brazil'},
151
- {$not: {nameTerms: { $in: ['cultura', 'globo'] }}}
152
- ]
153
- });
154
- expect(result).toHaveLength(4); // All Brazil channels except TV Cultura and TV Globo
155
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'SBT', 'Record News', 'TV Record']));
156
- });
157
-
158
- it('should handle $or with $not', async () => {
159
- const result = await db.find({
160
- "$or": [
161
- {nameTerms: 'tv'},
162
- {group: 'International'}
163
- ],
164
- $not: {rating: { $lt: 4.0 }}
165
- });
166
- // Should include TV channels with rating >= 4.0 OR International channels with rating >= 4.0
167
- expect(result.length).toBeGreaterThan(0);
168
- result.forEach(record => {
169
- expect(record.rating).toBeGreaterThanOrEqual(4.0);
170
- });
171
- });
172
-
173
- it('should handle multiple $not conditions', async () => {
174
- // Note: Multiple $not conditions with the same key will overwrite each other
175
- // This is expected JavaScript behavior. For multiple conditions, use $and
176
- const result = await db.find({
177
- "$and": [
178
- {group: 'Brazil'},
179
- {$not: {nameTerms: { $in: ['cultura'] }}},
180
- {$not: {rating: { $lt: 4.0 }}}
181
- ]
182
- });
183
- // This should be equivalent to: Brazil AND NOT cultura AND NOT rating < 4.0
184
- expect(result.length).toBeGreaterThan(0);
185
- result.forEach(record => {
186
- expect(record.group).toBe('Brazil');
187
- expect(record.nameTerms).not.toContain('cultura');
188
- expect(record.rating).toBeGreaterThanOrEqual(4.0);
189
- });
190
- });
191
- });
192
-
193
- describe('Edge Cases', () => {
194
- it('should handle empty $not condition', async () => {
195
- const result = await db.find({nameTerms: 'tv', $not: {}});
196
- expect(result).toHaveLength(4); // All TV channels (empty $not matches nothing, so excludes nothing)
197
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'TV Cultura', 'TV Globo', 'TV Record']));
198
- });
199
-
200
- it('should handle $not with non-existent field', async () => {
201
- const result = await db.find({nameTerms: 'tv', $not: {nonExistentField: 'value'}});
202
- expect(result).toHaveLength(4); // All TV channels (non-existent field never matches)
203
- expect(result.map(r => r.name)).toEqual(expect.arrayContaining(['TV Câmara', 'TV Cultura', 'TV Globo', 'TV Record']));
204
- });
205
-
206
- it('should handle $not with null values', async () => {
207
- // Insert a record with null value
208
- await db.insert({ id: 99, name: 'Test Channel', nameTerms: null, group: 'Test' });
209
-
210
- const result = await db.find({group: 'Test', $not: {nameTerms: null}});
211
- expect(result).toHaveLength(0); // No records match (the only Test record has null nameTerms)
212
- });
213
- });
214
-
215
- describe('Performance and Consistency', () => {
216
- it('should produce consistent results across multiple queries', async () => {
217
- const query = {nameTerms: 'tv', $not: {nameTerms: { $in: ['cultura'] }}};
218
-
219
- // Run the same query multiple times
220
- const results = [];
221
- for (let i = 0; i < 5; i++) {
222
- const result = await db.find(query);
223
- results.push(result.map(r => r.id).sort());
224
- }
225
-
226
- // All results should be identical
227
- for (let i = 1; i < results.length; i++) {
228
- expect(results[i]).toEqual(results[0]);
229
- }
230
- });
231
-
232
- it('should handle large result sets with $not', async () => {
233
- // This test ensures the fix works with larger datasets
234
- const result = await db.find({$not: {group: 'NonExistent'}});
235
- expect(result).toHaveLength(10); // All records (since no records have group 'NonExistent')
236
- });
237
- });
238
- });
@@ -1,129 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach } from '@jest/globals'
2
- import { Database } from '../src/Database.mjs'
3
- import path from 'path'
4
-
5
- describe('RegExp Queries on Array Fields', () => {
6
- let testDir
7
- let db
8
-
9
- beforeEach(async () => {
10
- testDir = path.join(process.cwd(), 'test-dbs')
11
- })
12
-
13
- afterEach(async () => {
14
- if (db) {
15
- await db.save()
16
- await db.destroy()
17
- db = null
18
- }
19
- })
20
-
21
- describe('Bug Fix: RegExp on Array Fields', () => {
22
- test('should correctly filter array fields with RegExp queries', async () => {
23
- const dbPath = path.join(testDir, 'regex-array-fields.jdb')
24
-
25
- db = new Database(dbPath, {
26
- clear: true,
27
- create: true,
28
- debugMode: false
29
- })
30
-
31
- await db.init()
32
-
33
- // Insert test data
34
- await db.insert({ name: 'Globo', nameTerms: ['globo'] })
35
- await db.insert({ name: 'FlixHD', nameTerms: ['flixhd'] })
36
- await db.insert({ name: 'Netflix', nameTerms: ['netflix'] })
37
- await db.insert({ name: 'Global News', nameTerms: ['global', 'news'] })
38
-
39
- // Test 1: RegExp query that should match multiple records
40
- const results1 = await db.find({ nameTerms: new RegExp('glob', 'i') })
41
- expect(results1.length).toBe(2)
42
- expect(results1.some(r => r.name === 'Globo')).toBe(true)
43
- expect(results1.some(r => r.name === 'Global News')).toBe(true)
44
-
45
- // Test 2: RegExp query with impossible pattern
46
- const results2 = await db.find({ nameTerms: new RegExp('IMPOSSIBLE_PATTERN_12345', 'i') })
47
- expect(results2.length).toBe(0)
48
-
49
- // Test 3: RegExp query that matches multiple records with "flix"
50
- const results3 = await db.find({ nameTerms: new RegExp('flix', 'i') })
51
- expect(results3.length).toBe(2)
52
- expect(results3.some(r => r.name === 'FlixHD')).toBe(true)
53
- expect(results3.some(r => r.name === 'Netflix')).toBe(true)
54
-
55
- // Test 4: RegExp query with anchor (start of string)
56
- const results4 = await db.find({ nameTerms: new RegExp('^flix', 'i') })
57
- expect(results4.length).toBe(1)
58
- expect(results4[0].name).toBe('FlixHD')
59
-
60
- // Test 5: RegExp query with case sensitivity
61
- const results5 = await db.find({ nameTerms: new RegExp('GLOBO') })
62
- expect(results5.length).toBe(0) // Should not match because it's case-sensitive
63
-
64
- const results6 = await db.find({ nameTerms: new RegExp('GLOBO', 'i') })
65
- expect(results6.length).toBe(1) // Should match because it's case-insensitive
66
- expect(results6[0].name).toBe('Globo')
67
- })
68
-
69
- test('should correctly handle RegExp queries with multi-element arrays', async () => {
70
- const dbPath = path.join(testDir, 'regex-multi-array.jdb')
71
-
72
- db = new Database(dbPath, {
73
- clear: true,
74
- create: true,
75
- debugMode: false
76
- })
77
-
78
- await db.init()
79
-
80
- // Insert test data with multi-element arrays
81
- await db.insert({ name: 'Test 1', tags: ['javascript', 'nodejs', 'backend'] })
82
- await db.insert({ name: 'Test 2', tags: ['python', 'django', 'backend'] })
83
- await db.insert({ name: 'Test 3', tags: ['javascript', 'react', 'frontend'] })
84
- await db.insert({ name: 'Test 4', tags: ['ruby', 'rails', 'backend'] })
85
-
86
- // Test: RegExp query that matches first element
87
- const results1 = await db.find({ tags: new RegExp('java', 'i') })
88
- expect(results1.length).toBe(2)
89
- expect(results1.some(r => r.name === 'Test 1')).toBe(true)
90
- expect(results1.some(r => r.name === 'Test 3')).toBe(true)
91
-
92
- // Test: RegExp query that matches middle element
93
- const results2 = await db.find({ tags: new RegExp('react', 'i') })
94
- expect(results2.length).toBe(1)
95
- expect(results2[0].name).toBe('Test 3')
96
-
97
- // Test: RegExp query that matches last element
98
- const results3 = await db.find({ tags: new RegExp('backend', 'i') })
99
- expect(results3.length).toBe(3)
100
- expect(results3.some(r => r.name === 'Test 1')).toBe(true)
101
- expect(results3.some(r => r.name === 'Test 2')).toBe(true)
102
- expect(results3.some(r => r.name === 'Test 4')).toBe(true)
103
- })
104
-
105
- test('should correctly handle $regex operator on array fields', async () => {
106
- const dbPath = path.join(testDir, 'regex-operator-array.jdb')
107
-
108
- db = new Database(dbPath, {
109
- clear: true,
110
- create: true,
111
- debugMode: false
112
- })
113
-
114
- await db.init()
115
-
116
- // Insert test data
117
- await db.insert({ name: 'Globo', nameTerms: ['globo'] })
118
- await db.insert({ name: 'FlixHD', nameTerms: ['flixhd'] })
119
- await db.insert({ name: 'Netflix', nameTerms: ['netflix'] })
120
- await db.insert({ name: 'Global News', nameTerms: ['global', 'news'] })
121
-
122
- // Test: $regex operator query
123
- const results = await db.find({ nameTerms: { $regex: 'glob' } })
124
- expect(results.length).toBe(2)
125
- expect(results.some(r => r.name === 'Globo')).toBe(true)
126
- expect(results.some(r => r.name === 'Global News')).toBe(true)
127
- })
128
- })
129
- })
@@ -1,238 +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('Edge Cases', () => {
176
- test('should return empty array for empty scores', async () => {
177
- await db.insert({ id: 1, title: 'Test', terms: ['a'] })
178
- await db.save()
179
-
180
- const results = await db.score('terms', {})
181
- expect(results).toHaveLength(0)
182
- })
183
-
184
- test('should return empty array when no terms match', async () => {
185
- await db.insert({ id: 1, title: 'Test', terms: ['a'] })
186
- await db.save()
187
-
188
- const results = await db.score('terms', {
189
- 'nonexistent': 1.0
190
- })
191
-
192
- expect(results).toHaveLength(0)
193
- })
194
-
195
- test('should handle empty database', async () => {
196
- const results = await db.score('terms', {
197
- 'a': 1.0
198
- })
199
-
200
- expect(results).toHaveLength(0)
201
- })
202
-
203
- test('should handle multiple occurrences of same term', async () => {
204
- await db.insert({ id: 1, title: 'Test 1', terms: ['important', 'important'] })
205
- await db.insert({ id: 2, title: 'Test 2', terms: ['important'] })
206
- await db.save()
207
-
208
- const results = await db.score('terms', {
209
- 'important': 1.0
210
- })
211
-
212
- // Both should have score 1.0 (duplicates in array don't multiply score)
213
- expect(results).toHaveLength(2)
214
- })
215
- })
216
-
217
- describe('Error Handling', () => {
218
- test('should throw error for invalid fieldName', async () => {
219
- await expect(db.score('', { 'a': 1.0 })).rejects.toThrow('non-empty string')
220
- await expect(db.score(null, { 'a': 1.0 })).rejects.toThrow('non-empty string')
221
- })
222
-
223
- test('should throw error for non-indexed field', async () => {
224
- await expect(db.score('nonexistent', { 'a': 1.0 }))
225
- .rejects.toThrow('not indexed')
226
- })
227
-
228
- test('should throw error for invalid scores object', async () => {
229
- await expect(db.score('terms', null)).rejects.toThrow('must be an object')
230
- await expect(db.score('terms', [])).rejects.toThrow('must be an object')
231
- })
232
-
233
- test('should throw error for non-numeric scores', async () => {
234
- await expect(db.score('terms', { 'a': 'invalid' }))
235
- .rejects.toThrow('must be a number')
236
- })
237
- })
238
- })
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