jexidb 2.0.3 → 2.1.1
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/.babelrc +13 -0
- package/.gitattributes +2 -0
- package/CHANGELOG.md +132 -101
- package/LICENSE +21 -21
- package/README.md +301 -639
- package/babel.config.json +5 -0
- package/dist/Database.cjs +5204 -0
- package/docs/API.md +908 -241
- package/docs/EXAMPLES.md +701 -177
- package/docs/README.md +194 -184
- package/examples/iterate-usage-example.js +157 -0
- package/examples/simple-iterate-example.js +115 -0
- package/jest.config.js +24 -0
- package/package.json +63 -54
- package/scripts/README.md +47 -0
- package/scripts/benchmark-array-serialization.js +108 -0
- package/scripts/clean-test-files.js +75 -0
- package/scripts/prepare.js +31 -0
- package/scripts/run-tests.js +80 -0
- package/scripts/score-mode-demo.js +45 -0
- package/src/Database.mjs +5325 -0
- package/src/FileHandler.mjs +1140 -0
- package/src/OperationQueue.mjs +279 -0
- package/src/SchemaManager.mjs +268 -0
- package/src/Serializer.mjs +702 -0
- package/src/managers/ConcurrencyManager.mjs +257 -0
- package/src/managers/IndexManager.mjs +2094 -0
- package/src/managers/QueryManager.mjs +1490 -0
- package/src/managers/StatisticsManager.mjs +262 -0
- package/src/managers/StreamingProcessor.mjs +429 -0
- package/src/managers/TermManager.mjs +278 -0
- package/src/utils/operatorNormalizer.mjs +116 -0
- package/test/$not-operator-with-and.test.js +282 -0
- package/test/README.md +8 -0
- package/test/close-init-cycle.test.js +256 -0
- package/test/coverage-method.test.js +93 -0
- package/test/critical-bugs-fixes.test.js +1069 -0
- package/test/deserialize-corruption-fixes.test.js +296 -0
- package/test/exists-method.test.js +318 -0
- package/test/explicit-indexes-comparison.test.js +219 -0
- package/test/filehandler-non-adjacent-ranges-bug.test.js +175 -0
- package/test/index-line-number-regression.test.js +100 -0
- package/test/index-missing-index-data.test.js +91 -0
- package/test/index-persistence.test.js +491 -0
- package/test/index-serialization.test.js +314 -0
- package/test/indexed-query-mode.test.js +360 -0
- package/test/insert-session-auto-flush.test.js +353 -0
- package/test/iterate-method.test.js +272 -0
- package/test/legacy-operator-compat.test.js +154 -0
- package/test/query-operators.test.js +238 -0
- package/test/regex-array-fields.test.js +129 -0
- package/test/score-method.test.js +298 -0
- package/test/setup.js +17 -0
- package/test/term-mapping-minimal.test.js +154 -0
- package/test/term-mapping-simple.test.js +257 -0
- package/test/term-mapping.test.js +514 -0
- package/test/writebuffer-flush-resilience.test.js +204 -0
- package/dist/FileHandler.js +0 -688
- package/dist/IndexManager.js +0 -353
- package/dist/IntegrityChecker.js +0 -364
- package/dist/JSONLDatabase.js +0 -1333
- package/dist/index.js +0 -617
- package/docs/MIGRATION.md +0 -295
- package/examples/auto-save-example.js +0 -158
- package/examples/cjs-usage.cjs +0 -82
- package/examples/close-vs-delete-example.js +0 -71
- package/examples/esm-usage.js +0 -113
- package/examples/example-columns.idx.jdb +0 -0
- package/examples/example-columns.jdb +0 -9
- package/examples/example-options.idx.jdb +0 -0
- package/examples/example-options.jdb +0 -0
- package/examples/example-users.idx.jdb +0 -0
- package/examples/example-users.jdb +0 -5
- package/examples/simple-test.js +0 -55
- package/src/FileHandler.js +0 -674
- package/src/IndexManager.js +0 -363
- package/src/IntegrityChecker.js +0 -379
- package/src/JSONLDatabase.js +0 -1391
- package/src/index.js +0 -608
package/docs/API.md
CHANGED
|
@@ -1,390 +1,1057 @@
|
|
|
1
|
-
# JexiDB API
|
|
1
|
+
# JexiDB API Documentation
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## ⚠️ Version 2.1.0 - Breaking Changes
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**This version is NOT backward compatible with databases created with previous versions (1.x.x).**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Major Changes:
|
|
8
|
+
- **`fields` is now MANDATORY** - Define your schema structure
|
|
9
|
+
- **Term mapping is auto-enabled** - No manual configuration needed
|
|
10
|
+
- **Database files are incompatible** - Export/import required for migration
|
|
11
|
+
- **Performance improvements** - Up to 77% size reduction for repetitive data
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
## Table of Contents
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
1. [Database Constructor](#database-constructor)
|
|
16
|
+
2. [Core Methods](#core-methods)
|
|
17
|
+
3. [Query Methods](#query-methods)
|
|
18
|
+
4. [Advanced Features](#advanced-features)
|
|
19
|
+
5. [Term Mapping](#term-mapping)
|
|
20
|
+
6. [Bulk Operations](#bulk-operations)
|
|
21
|
+
7. [Configuration Options](#configuration-options)
|
|
22
|
+
8. [Migration Guide](#migration-guide)
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
## Database Constructor
|
|
14
25
|
|
|
15
26
|
```javascript
|
|
16
|
-
|
|
27
|
+
import { Database } from 'jexidb'
|
|
28
|
+
|
|
29
|
+
const db = new Database('path/to/database.jdb', options)
|
|
17
30
|
```
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- `autoSaveInterval` (number): Flush buffer every N milliseconds (default: 5000)
|
|
31
|
-
- `forceSaveOnClose` (boolean): Always save when closing database (default: true)
|
|
32
|
-
|
|
33
|
-
**Performance Configuration:**
|
|
34
|
-
- `batchSize` (number): Batch size for inserts (default: 50)
|
|
35
|
-
- `adaptiveBatchSize` (boolean): Adjust batch size based on usage (default: true)
|
|
36
|
-
- `minBatchSize` (number): Minimum batch size for flush (default: 10)
|
|
37
|
-
- `maxBatchSize` (number): Maximum batch size for performance (default: 200)
|
|
38
|
-
|
|
39
|
-
**Memory Management:**
|
|
40
|
-
- `maxMemoryUsage` (string|number): Memory limit ('auto' or bytes, default: 'auto')
|
|
41
|
-
- `maxFlushChunkBytes` (number): Maximum chunk size for flush operations (default: 8MB)
|
|
32
|
+
### Constructor Options
|
|
33
|
+
|
|
34
|
+
| Option | Type | Default | Description |
|
|
35
|
+
|--------|------|---------|-------------|
|
|
36
|
+
| `create` | boolean | `true` | Create the file if it doesn't exist |
|
|
37
|
+
| `clear` | boolean | `false` | Clear existing files before loading |
|
|
38
|
+
| `fields` | object | **Required** | **MANDATORY** - Define the data structure schema |
|
|
39
|
+
| `indexes` | object | `{}` | **Optional** indexed fields for faster queries (not required) |
|
|
40
|
+
| `termMapping` | boolean | `true` | Enable term mapping for optimal performance (auto-detected) |
|
|
41
|
+
| `termMappingFields` | string[] | `[]` | Fields to apply term mapping to (auto-detected from indexes) |
|
|
42
|
+
| `termMappingCleanup` | boolean | `true` | Automatically clean up orphaned terms |
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
### Fields vs Indexes - Important Distinction
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
**Fields** (Schema - **MANDATORY**):
|
|
47
|
+
- ✅ **Required** - Define the structure of your data
|
|
48
|
+
- ✅ **Schema enforcement** - Controls which fields are allowed
|
|
49
|
+
- ✅ **All fields** are queryable by default
|
|
50
|
+
- ✅ **No performance impact** on memory usage
|
|
51
|
+
- ✅ **Data validation** - Ensures data consistency
|
|
52
|
+
|
|
53
|
+
**Indexes** (Performance Optimization - **Optional**):
|
|
54
|
+
- ⚠️ **Optional** - Only for fields you query frequently
|
|
55
|
+
- ⚠️ **Memory overhead** - Each index uses additional memory
|
|
56
|
+
- ⚠️ **Use sparingly** - Only index fields you actually query
|
|
57
|
+
- ⚠️ **Query performance** - Only affects query speed, not functionality
|
|
58
|
+
|
|
59
|
+
### When to Use Indexes
|
|
47
60
|
|
|
48
61
|
```javascript
|
|
62
|
+
// ❌ DON'T: Index everything (wastes memory)
|
|
63
|
+
const db = new Database('db.jdb', {
|
|
64
|
+
fields: { // REQUIRED - Define schema
|
|
65
|
+
id: 'number',
|
|
66
|
+
name: 'string',
|
|
67
|
+
email: 'string',
|
|
68
|
+
phone: 'string',
|
|
69
|
+
address: 'string',
|
|
70
|
+
city: 'string',
|
|
71
|
+
country: 'string',
|
|
72
|
+
status: 'string',
|
|
73
|
+
createdAt: 'number',
|
|
74
|
+
updatedAt: 'number'
|
|
75
|
+
},
|
|
76
|
+
indexes: { // OPTIONAL - Only for performance
|
|
77
|
+
id: 'number', // Maybe needed
|
|
78
|
+
name: 'string', // Maybe needed
|
|
79
|
+
email: 'string', // Maybe needed
|
|
80
|
+
phone: 'string', // Maybe needed
|
|
81
|
+
address: 'string', // Maybe needed
|
|
82
|
+
city: 'string', // Maybe needed
|
|
83
|
+
country: 'string', // Maybe needed
|
|
84
|
+
status: 'string', // Maybe needed
|
|
85
|
+
createdAt: 'number', // Maybe needed
|
|
86
|
+
updatedAt: 'number' // Maybe needed
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// ✅ DO: Define schema + index only what you query frequently
|
|
91
|
+
const db = new Database('db.jdb', {
|
|
92
|
+
fields: { // REQUIRED - Define schema
|
|
93
|
+
id: 'number',
|
|
94
|
+
name: 'string',
|
|
95
|
+
email: 'string',
|
|
96
|
+
phone: 'string',
|
|
97
|
+
address: 'string',
|
|
98
|
+
city: 'string',
|
|
99
|
+
country: 'string',
|
|
100
|
+
status: 'string',
|
|
101
|
+
createdAt: 'number',
|
|
102
|
+
updatedAt: 'number'
|
|
103
|
+
},
|
|
104
|
+
indexes: { // OPTIONAL - Only for performance
|
|
105
|
+
id: 'number', // Primary key - always index
|
|
106
|
+
email: 'string', // Login queries - index this
|
|
107
|
+
status: 'string' // Filter queries - index this
|
|
108
|
+
}
|
|
109
|
+
// Other fields (name, phone, address, etc.) are still queryable
|
|
110
|
+
// but will use slower sequential search
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Example
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
// Example: E-commerce product database
|
|
118
|
+
const db = new Database('products.jdb', {
|
|
119
|
+
create: true,
|
|
120
|
+
clear: false,
|
|
121
|
+
fields: { // REQUIRED - Define schema
|
|
122
|
+
id: 'number',
|
|
123
|
+
name: 'string',
|
|
124
|
+
description: 'string',
|
|
125
|
+
category: 'string',
|
|
126
|
+
tags: 'array:string',
|
|
127
|
+
price: 'number',
|
|
128
|
+
imageUrl: 'string',
|
|
129
|
+
inStock: 'boolean',
|
|
130
|
+
createdAt: 'number'
|
|
131
|
+
},
|
|
132
|
+
indexes: { // OPTIONAL - Only for performance
|
|
133
|
+
id: 'number', // Primary key - always index
|
|
134
|
+
category: 'string', // Filter by category - index this
|
|
135
|
+
tags: 'array:string', // Search by tags - index this
|
|
136
|
+
price: 'number' // Price range queries - index this
|
|
137
|
+
}
|
|
138
|
+
// Fields like 'name', 'description', 'imageUrl' are still queryable
|
|
139
|
+
// but will use slower sequential search (no index needed unless you query them frequently)
|
|
140
|
+
})
|
|
141
|
+
|
|
49
142
|
await db.init()
|
|
143
|
+
|
|
144
|
+
// All these queries work, but some are faster:
|
|
145
|
+
await db.find({ id: 1 }) // ✅ Fast (indexed)
|
|
146
|
+
await db.find({ category: 'electronics' }) // ✅ Fast (indexed)
|
|
147
|
+
await db.find({ tags: 'wireless' }) // ✅ Fast (indexed)
|
|
148
|
+
await db.find({ price: { '>': 100 } }) // ✅ Fast (indexed)
|
|
149
|
+
await db.find({ name: 'iPhone' }) // ⚠️ Slower (not indexed, but still works)
|
|
150
|
+
await db.find({ description: 'wireless' }) // ⚠️ Slower (not indexed, but still works)
|
|
50
151
|
```
|
|
51
152
|
|
|
52
|
-
|
|
53
|
-
|
|
153
|
+
## Core Methods
|
|
154
|
+
|
|
155
|
+
### `init()`
|
|
156
|
+
|
|
157
|
+
Initialize the database and load existing data.
|
|
54
158
|
|
|
55
159
|
```javascript
|
|
56
|
-
|
|
160
|
+
await db.init()
|
|
57
161
|
```
|
|
58
162
|
|
|
59
|
-
|
|
60
|
-
|
|
163
|
+
### `insert(data)`
|
|
164
|
+
|
|
165
|
+
Insert a new record into the database.
|
|
61
166
|
|
|
62
167
|
```javascript
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
168
|
+
await db.insert({
|
|
169
|
+
id: 1,
|
|
170
|
+
name: 'John Doe',
|
|
171
|
+
email: 'john@example.com'
|
|
172
|
+
})
|
|
67
173
|
```
|
|
68
174
|
|
|
69
|
-
|
|
70
|
-
|
|
175
|
+
### `update(criteria, updates)`
|
|
176
|
+
|
|
177
|
+
Update records matching the criteria.
|
|
71
178
|
|
|
72
179
|
```javascript
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
{
|
|
180
|
+
await db.update(
|
|
181
|
+
{ id: 1 },
|
|
182
|
+
{ email: 'newemail@example.com', updated: true }
|
|
76
183
|
)
|
|
77
184
|
```
|
|
78
185
|
|
|
79
|
-
|
|
80
|
-
|
|
186
|
+
### `delete(criteria)`
|
|
187
|
+
|
|
188
|
+
Delete records matching the criteria.
|
|
81
189
|
|
|
82
190
|
```javascript
|
|
83
|
-
|
|
191
|
+
await db.delete({ id: 1 })
|
|
84
192
|
```
|
|
85
193
|
|
|
86
|
-
|
|
87
|
-
|
|
194
|
+
### `save()`
|
|
195
|
+
|
|
196
|
+
Save all pending changes to disk.
|
|
88
197
|
|
|
89
198
|
```javascript
|
|
90
|
-
|
|
91
|
-
{ id: '1' },
|
|
92
|
-
{ name: 'John Updated', age: 30 }
|
|
93
|
-
)
|
|
199
|
+
await db.save()
|
|
94
200
|
```
|
|
95
201
|
|
|
96
|
-
|
|
97
|
-
|
|
202
|
+
### `destroy()`
|
|
203
|
+
|
|
204
|
+
Close the database and clean up resources.
|
|
98
205
|
|
|
99
206
|
```javascript
|
|
100
|
-
|
|
207
|
+
await db.destroy()
|
|
101
208
|
```
|
|
102
209
|
|
|
103
|
-
|
|
104
|
-
|
|
210
|
+
## Query Methods
|
|
211
|
+
|
|
212
|
+
### `find(criteria, options)`
|
|
213
|
+
|
|
214
|
+
Find records matching the criteria.
|
|
105
215
|
|
|
106
216
|
```javascript
|
|
107
|
-
|
|
217
|
+
// Basic query
|
|
218
|
+
const results = await db.find({ name: 'John Doe' })
|
|
219
|
+
|
|
220
|
+
// Query with conditions
|
|
221
|
+
const results = await db.find({
|
|
222
|
+
age: { '>': 18, '<': 65 },
|
|
223
|
+
status: 'active'
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// Case insensitive search
|
|
227
|
+
const results = await db.find(
|
|
228
|
+
{ name: 'john doe' },
|
|
229
|
+
{ caseInsensitive: true }
|
|
230
|
+
)
|
|
108
231
|
```
|
|
109
232
|
|
|
110
|
-
|
|
111
|
-
|
|
233
|
+
### `findOne(criteria, options)`
|
|
234
|
+
|
|
235
|
+
Find the first record matching the criteria.
|
|
112
236
|
|
|
113
237
|
```javascript
|
|
114
|
-
await db.
|
|
238
|
+
const user = await db.findOne({ id: 1 })
|
|
115
239
|
```
|
|
116
240
|
|
|
117
|
-
|
|
118
|
-
|
|
241
|
+
### `count(criteria)`
|
|
242
|
+
|
|
243
|
+
Count records matching the criteria.
|
|
119
244
|
|
|
120
245
|
```javascript
|
|
121
|
-
const
|
|
122
|
-
// Returns: number of records flushed
|
|
246
|
+
const userCount = await db.count({ status: 'active' })
|
|
123
247
|
```
|
|
124
248
|
|
|
125
|
-
|
|
126
|
-
|
|
249
|
+
### `score(fieldName, scores, options)`
|
|
250
|
+
|
|
251
|
+
Score and rank records based on weighted terms in an indexed `array:string` field. This method is optimized for in-memory operations and provides 10x+ performance improvement over equivalent `find()` queries.
|
|
127
252
|
|
|
128
253
|
```javascript
|
|
129
|
-
|
|
130
|
-
|
|
254
|
+
// Basic scoring
|
|
255
|
+
const results = await db.score('tags', {
|
|
256
|
+
'javascript': 1.0,
|
|
257
|
+
'node': 0.8,
|
|
258
|
+
'typescript': 0.9
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// With options
|
|
262
|
+
const results = await db.score('terms', {
|
|
263
|
+
'action': 1.0,
|
|
264
|
+
'comedy': 0.8
|
|
265
|
+
}, {
|
|
266
|
+
limit: 10,
|
|
267
|
+
sort: 'desc',
|
|
268
|
+
includeScore: true
|
|
269
|
+
})
|
|
131
270
|
```
|
|
132
271
|
|
|
133
|
-
|
|
134
|
-
|
|
272
|
+
**Parameters:**
|
|
273
|
+
|
|
274
|
+
- `fieldName` (string, required): Name of the indexed `array:string` field to score
|
|
275
|
+
- `scores` (object, required): Map of terms to numeric weights (e.g., `{ 'action': 1.0, 'comedy': 0.8 }`)
|
|
276
|
+
- `options` (object, optional):
|
|
277
|
+
- `limit` (number): Maximum results (default: 100)
|
|
278
|
+
- `sort` (string): "desc" or "asc" (default: "desc")
|
|
279
|
+
- `includeScore` (boolean): Include score in results (default: true)
|
|
280
|
+
- `mode` (string): Score aggregation strategy: `"sum"` (default), `"max"`, `"avg"`, or `"first"`
|
|
281
|
+
|
|
282
|
+
**Returns:**
|
|
283
|
+
|
|
284
|
+
Array of records ordered by score, with optional score property:
|
|
135
285
|
|
|
136
286
|
```javascript
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
287
|
+
// With includeScore: true (default)
|
|
288
|
+
[
|
|
289
|
+
{ _: 123, score: 1.8, title: "Action Comedy", terms: ["action", "comedy"] },
|
|
290
|
+
{ _: 456, score: 1.0, title: "Action Movie", terms: ["action", "movie"] },
|
|
291
|
+
{ _: 789, score: 0.8, title: "Comedy Show", terms: ["comedy", "show"] }
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
// With includeScore: false
|
|
295
|
+
[
|
|
296
|
+
{ _: 123, title: "Action Comedy", terms: ["action", "comedy"] },
|
|
297
|
+
{ _: 456, title: "Action Movie", terms: ["action", "movie"] },
|
|
298
|
+
{ _: 789, title: "Comedy Show", terms: ["comedy", "show"] }
|
|
299
|
+
]
|
|
147
300
|
```
|
|
148
301
|
|
|
149
|
-
|
|
150
|
-
|
|
302
|
+
**Score Calculation Modes:**
|
|
303
|
+
|
|
304
|
+
- `sum` *(default)*: score is the sum of all weights for the matched keywords.
|
|
305
|
+
- `max`: score is the highest weight among the matched keywords.
|
|
306
|
+
- `avg`: score is the arithmetic average of the weights for the matched keywords.
|
|
307
|
+
- `first`: score uses the weight of the first keyword that appears in the `scores` object and matches the record (subsequent keywords are ignored).
|
|
151
308
|
|
|
152
309
|
```javascript
|
|
153
|
-
db.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
})
|
|
310
|
+
await db.score('terms', { a: 1.0, b: 0.5 }, { mode: 'sum' }) // -> 1.5
|
|
311
|
+
await db.score('terms', { a: 1.0, b: 0.5 }, { mode: 'max' }) // -> 1.0
|
|
312
|
+
await db.score('terms', { a: 1.0, b: 0.5 }, { mode: 'avg' }) // -> 0.75
|
|
313
|
+
await db.score('terms', { a: 1.0, b: 0.5 }, { mode: 'first'}) // -> 1.0
|
|
158
314
|
```
|
|
159
315
|
|
|
160
|
-
|
|
161
|
-
Gets current performance configuration.
|
|
316
|
+
**Example Use Cases:**
|
|
162
317
|
|
|
163
318
|
```javascript
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
|
|
319
|
+
// Content recommendation system
|
|
320
|
+
const recommendations = await db.score('categories', {
|
|
321
|
+
'technology': 1.0,
|
|
322
|
+
'programming': 0.9,
|
|
323
|
+
'web': 0.8
|
|
324
|
+
}, { limit: 20 })
|
|
325
|
+
|
|
326
|
+
// Search with weighted keywords
|
|
327
|
+
const searchResults = await db.score('keywords', {
|
|
328
|
+
'urgent': 2.0,
|
|
329
|
+
'important': 1.5,
|
|
330
|
+
'notable': 1.0
|
|
331
|
+
}, { sort: 'desc', limit: 50 })
|
|
332
|
+
|
|
333
|
+
// Product recommendations
|
|
334
|
+
const productMatches = await db.score('tags', {
|
|
335
|
+
'wireless': 1.2,
|
|
336
|
+
'bluetooth': 1.0,
|
|
337
|
+
'rechargeable': 0.9,
|
|
338
|
+
'compact': 0.8
|
|
339
|
+
}, { includeScore: false })
|
|
173
340
|
```
|
|
174
341
|
|
|
175
|
-
|
|
176
|
-
|
|
342
|
+
**Performance Notes:**
|
|
343
|
+
|
|
344
|
+
- ⚡ **Memory-only operations** - Uses in-memory indices exclusively
|
|
345
|
+
- ⚡ **No physical I/O for scoring** - Only final record fetch requires disk access
|
|
346
|
+
- ⚡ **10x+ faster** than equivalent `find()` + manual scoring
|
|
347
|
+
- ⚡ **O(T × N) complexity** - T = terms in scores, N = avg records per term
|
|
348
|
+
- ⚡ **Optimal for selective queries** - Best when scoring subset of total records
|
|
349
|
+
|
|
350
|
+
**Requirements:**
|
|
351
|
+
|
|
352
|
+
- Field must be indexed as `array:string` type
|
|
353
|
+
- Field must be present in `indexes` configuration
|
|
354
|
+
- Returns empty array if no terms match
|
|
355
|
+
- Records with zero scores are excluded
|
|
356
|
+
|
|
357
|
+
**Error Handling:**
|
|
177
358
|
|
|
178
359
|
```javascript
|
|
179
|
-
|
|
360
|
+
try {
|
|
361
|
+
const results = await db.score('tags', { 'javascript': 1.0 })
|
|
362
|
+
} catch (error) {
|
|
363
|
+
// Error: Field "tags" is not indexed
|
|
364
|
+
// Error: Field "tags" must be of type "array:string"
|
|
365
|
+
// Error: scores must be an object
|
|
366
|
+
// Error: Score value for term "javascript" must be a number
|
|
367
|
+
}
|
|
180
368
|
```
|
|
181
369
|
|
|
182
|
-
|
|
183
|
-
|
|
370
|
+
### Query Operators
|
|
371
|
+
|
|
372
|
+
| Operator | Description | Example |
|
|
373
|
+
|----------|-------------|---------|
|
|
374
|
+
| `>` | Greater than | `{ age: { '>': 18 } }` |
|
|
375
|
+
| `>=` | Greater than or equal | `{ score: { '>=': 80 } }` |
|
|
376
|
+
| `<` | Less than | `{ price: { '<': 100 } }` |
|
|
377
|
+
| `<=` | Less than or equal | `{ rating: { '<=': 5 } }` |
|
|
378
|
+
| `!=` | Not equal | `{ status: { '!=': 'deleted' } }` |
|
|
379
|
+
| `$in` | In array | `{ category: { '$in': ['A', 'B'] } }` |
|
|
380
|
+
| `$not` | Not | `{ name: { '$not': 'John' } }` |
|
|
381
|
+
| `$and` | Logical AND | `{ '$and': [{ age: { '>': 18 } }, { status: 'active' }] }` |
|
|
382
|
+
| `$or` | Logical OR | `{ '$or': [{ type: 'admin' }, { type: 'moderator' }] }` |
|
|
383
|
+
|
|
384
|
+
### Complex Queries
|
|
184
385
|
|
|
185
386
|
```javascript
|
|
186
|
-
|
|
387
|
+
// Multiple conditions (AND by default)
|
|
388
|
+
const results = await db.find({
|
|
389
|
+
age: { '>': 18 },
|
|
390
|
+
status: 'active',
|
|
391
|
+
category: { '$in': ['premium', 'vip'] }
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
// Using logical operators
|
|
395
|
+
const results = await db.find({
|
|
396
|
+
'$or': [
|
|
397
|
+
{ type: 'admin' },
|
|
398
|
+
{ '$and': [
|
|
399
|
+
{ type: 'user' },
|
|
400
|
+
{ verified: true }
|
|
401
|
+
]}
|
|
402
|
+
]
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
// Array field queries
|
|
406
|
+
const results = await db.find({
|
|
407
|
+
tags: 'javascript', // Contains 'javascript'
|
|
408
|
+
tags: { '$all': ['js', 'node'] } // Contains all specified tags
|
|
409
|
+
})
|
|
187
410
|
```
|
|
188
411
|
|
|
189
|
-
|
|
190
|
-
|
|
412
|
+
## Advanced Features
|
|
413
|
+
|
|
414
|
+
### Indexed Query Mode
|
|
415
|
+
|
|
416
|
+
Control whether queries are restricted to indexed fields only.
|
|
191
417
|
|
|
192
418
|
```javascript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
//
|
|
419
|
+
// Strict mode - only indexed fields allowed in queries
|
|
420
|
+
const db = new Database('db.jdb', {
|
|
421
|
+
fields: { // REQUIRED - Define schema
|
|
422
|
+
id: 'number',
|
|
423
|
+
name: 'string',
|
|
424
|
+
age: 'number',
|
|
425
|
+
email: 'string'
|
|
426
|
+
},
|
|
427
|
+
indexes: { // OPTIONAL - Only fields you query frequently
|
|
428
|
+
name: 'string', // ✅ Search by name
|
|
429
|
+
age: 'number' // ✅ Filter by age
|
|
430
|
+
},
|
|
431
|
+
indexedQueryMode: 'strict'
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
// This will throw an error in strict mode
|
|
435
|
+
await db.find({ email: 'test@example.com' }) // Error: email is not indexed
|
|
436
|
+
|
|
437
|
+
// Permissive mode (default) - allows any field
|
|
438
|
+
const db = new Database('db.jdb', {
|
|
439
|
+
fields: { // REQUIRED - Define schema
|
|
440
|
+
id: 'number',
|
|
441
|
+
name: 'string',
|
|
442
|
+
age: 'number',
|
|
443
|
+
email: 'string'
|
|
444
|
+
},
|
|
445
|
+
indexes: { // OPTIONAL - Only fields you query frequently
|
|
446
|
+
name: 'string', // ✅ Search by name
|
|
447
|
+
age: 'number' // ✅ Filter by age
|
|
448
|
+
},
|
|
449
|
+
indexedQueryMode: 'permissive'
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
// This works in permissive mode, but will be slower than strict mode
|
|
453
|
+
await db.find({ email: 'test@example.com' }) // OK
|
|
196
454
|
```
|
|
197
455
|
|
|
198
|
-
|
|
199
|
-
|
|
456
|
+
### Query Performance
|
|
457
|
+
|
|
458
|
+
**Index Strategy Guidelines:**
|
|
459
|
+
|
|
460
|
+
1. **Always index primary keys** (usually `id`)
|
|
461
|
+
2. **Index frequently queried fields** (filters, searches)
|
|
462
|
+
3. **Don't index everything** - each index uses memory
|
|
463
|
+
4. **Use specific criteria** rather than broad queries
|
|
464
|
+
|
|
465
|
+
**Performance Examples:**
|
|
200
466
|
|
|
201
467
|
```javascript
|
|
202
|
-
|
|
203
|
-
|
|
468
|
+
// ✅ Good: Index only what you need
|
|
469
|
+
const db = new Database('users.jdb', {
|
|
470
|
+
fields: { // REQUIRED - Define schema
|
|
471
|
+
id: 'number',
|
|
472
|
+
email: 'string',
|
|
473
|
+
status: 'string',
|
|
474
|
+
name: 'string',
|
|
475
|
+
phone: 'string'
|
|
476
|
+
},
|
|
477
|
+
indexes: { // OPTIONAL - Only fields you query frequently
|
|
478
|
+
email: 'string', // ✅ Login queries
|
|
479
|
+
status: 'string' // ✅ Filter active/inactive users
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
// ❌ Bad: Over-indexing wastes memory
|
|
484
|
+
const db = new Database('users.jdb', {
|
|
485
|
+
fields: { // REQUIRED - Define schema
|
|
486
|
+
id: 'number',
|
|
487
|
+
name: 'string',
|
|
488
|
+
email: 'string',
|
|
489
|
+
phone: 'string',
|
|
490
|
+
address: 'string',
|
|
491
|
+
city: 'string',
|
|
492
|
+
country: 'string',
|
|
493
|
+
createdAt: 'number',
|
|
494
|
+
updatedAt: 'number'
|
|
495
|
+
},
|
|
496
|
+
indexes: { // OPTIONAL - Performance optimization (too many!)
|
|
497
|
+
name: 'string', // ❌ Only if you search by name frequently
|
|
498
|
+
email: 'string', // ❌ Only if you search by email frequently
|
|
499
|
+
phone: 'string', // ❌ Only if you search by phone frequently
|
|
500
|
+
address: 'string', // ❌ Only if you search by address frequently
|
|
501
|
+
city: 'string', // ❌ Only if you search by city frequently
|
|
502
|
+
country: 'string', // ❌ Only if you search by country frequently
|
|
503
|
+
createdAt: 'number', // ❌ Only if you filter by date frequently
|
|
504
|
+
updatedAt: 'number' // ❌ Only if you filter by date frequently
|
|
505
|
+
}
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
// Query performance comparison:
|
|
509
|
+
await db.find({ id: 1 }) // ✅ Fast (indexed)
|
|
510
|
+
await db.find({ email: 'user@example.com' }) // ✅ Fast (indexed)
|
|
511
|
+
await db.find({ status: 'active' }) // ✅ Fast (indexed)
|
|
512
|
+
await db.find({ name: 'John' }) // ⚠️ Slower (not indexed, but works)
|
|
513
|
+
await db.find({ phone: '123-456-7890' }) // ⚠️ Slower (not indexed, but works)
|
|
204
514
|
```
|
|
205
515
|
|
|
206
|
-
|
|
207
|
-
|
|
516
|
+
**Memory Impact:**
|
|
517
|
+
- Each index uses additional memory
|
|
518
|
+
- String indexes use more memory than number indexes
|
|
519
|
+
- Array indexes use more memory than single-value indexes
|
|
520
|
+
- **Rule of thumb**: Only index fields you query in 80%+ of your queries
|
|
521
|
+
|
|
522
|
+
## Term Mapping
|
|
523
|
+
|
|
524
|
+
Term mapping is a powerful optimization that reduces database size by mapping repetitive string terms to numeric IDs. **It's now enabled by default** and automatically detects which fields benefit from term mapping.
|
|
525
|
+
|
|
526
|
+
### Benefits
|
|
527
|
+
|
|
528
|
+
- **77% size reduction** in typical scenarios
|
|
529
|
+
- **Faster queries** with numeric comparisons
|
|
530
|
+
- **Automatic cleanup** of unused terms
|
|
531
|
+
- **Transparent operation** - same API
|
|
532
|
+
- **Auto-detection** of optimal fields
|
|
533
|
+
- **Zero configuration** required
|
|
534
|
+
|
|
535
|
+
### Automatic Configuration
|
|
536
|
+
|
|
537
|
+
Term mapping is now **automatically enabled** and detects fields that benefit from optimization:
|
|
208
538
|
|
|
209
539
|
```javascript
|
|
210
|
-
|
|
211
|
-
//
|
|
540
|
+
const db = new Database('my-db.jdb', {
|
|
541
|
+
fields: { // REQUIRED - Define schema
|
|
542
|
+
id: 'number',
|
|
543
|
+
name: 'string',
|
|
544
|
+
tags: 'array:string',
|
|
545
|
+
scores: 'array:number',
|
|
546
|
+
price: 'number'
|
|
547
|
+
},
|
|
548
|
+
indexes: { // OPTIONAL - Only fields you query frequently
|
|
549
|
+
name: 'string', // ✅ Search by name (auto term mapping)
|
|
550
|
+
tags: 'array:string' // ✅ Search by tags (auto term mapping)
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
// Term mapping is automatically enabled
|
|
555
|
+
console.log(db.opts.termMapping) // true
|
|
556
|
+
console.log(db.termManager.termMappingFields) // ['name', 'tags']
|
|
212
557
|
```
|
|
213
558
|
|
|
214
|
-
|
|
215
|
-
**⚠️ WARNING: This permanently deletes the database file!**
|
|
559
|
+
### How It Works
|
|
216
560
|
|
|
217
|
-
|
|
561
|
+
Term mapping automatically detects and optimizes fields:
|
|
562
|
+
|
|
563
|
+
1. **Auto-detection**: Fields with `'string'` or `'array:string'` types are automatically optimized
|
|
564
|
+
2. **Term ID mapping**: Each unique string term gets a numeric ID
|
|
565
|
+
3. **Efficient storage**: Term IDs are stored in optimized structures
|
|
566
|
+
4. **Transparent queries**: Queries automatically convert search terms to IDs
|
|
567
|
+
5. **Automatic cleanup**: Unused terms are automatically cleaned up
|
|
568
|
+
|
|
569
|
+
### Field Type Behavior
|
|
570
|
+
|
|
571
|
+
| Field Type | Term Mapping | Storage | Example |
|
|
572
|
+
|------------|--------------|---------|---------|
|
|
573
|
+
| `'string'` | ✅ Enabled | Term IDs | `"Brazil"` → `1` |
|
|
574
|
+
| `'array:string'` | ✅ Enabled | Term IDs | `["Brazil", "Argentina"]` → `[1, 2]` |
|
|
575
|
+
| `'number'` | ❌ Disabled | Direct values | `85` → `"85"` |
|
|
576
|
+
| `'array:number'` | ❌ Disabled | Direct values | `[85, 92]` → `["85", "92"]` |
|
|
577
|
+
| `'boolean'` | ❌ Disabled | Direct values | `true` → `"true"` |
|
|
578
|
+
| `'array:boolean'` | ❌ Disabled | Direct values | `[true, false]` → `["true", "false"]` |
|
|
579
|
+
|
|
580
|
+
### Example Usage
|
|
218
581
|
|
|
219
582
|
```javascript
|
|
220
|
-
|
|
583
|
+
// Create database with mixed field types
|
|
584
|
+
const db = new Database('products.jdb', {
|
|
585
|
+
fields: { // REQUIRED - Define schema
|
|
586
|
+
id: 'number',
|
|
587
|
+
name: 'string',
|
|
588
|
+
tags: 'array:string',
|
|
589
|
+
scores: 'array:number',
|
|
590
|
+
price: 'number'
|
|
591
|
+
},
|
|
592
|
+
indexes: { // OPTIONAL - Only fields you query frequently
|
|
593
|
+
name: 'string', // ✅ Search by name (auto term mapping)
|
|
594
|
+
tags: 'array:string' // ✅ Search by tags (auto term mapping)
|
|
595
|
+
}
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
// Insert data with repetitive terms
|
|
599
|
+
await db.insert({
|
|
600
|
+
name: 'Product A',
|
|
601
|
+
tags: ['electronics', 'gadget', 'wireless'],
|
|
602
|
+
scores: [85, 92, 78],
|
|
603
|
+
price: 299.99
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
await db.insert({
|
|
607
|
+
name: 'Product B',
|
|
608
|
+
tags: ['electronics', 'accessory', 'wireless'],
|
|
609
|
+
scores: [90, 88, 95],
|
|
610
|
+
price: 199.99
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
// Queries work normally - term mapping is transparent
|
|
614
|
+
const results = await db.find({ tags: 'electronics' })
|
|
615
|
+
const expensive = await db.find({ price: { '>': 250 } })
|
|
616
|
+
|
|
617
|
+
// Check term mapping status
|
|
618
|
+
console.log(db.opts.termMapping) // true
|
|
619
|
+
console.log(db.termManager.termMappingFields) // ['name', 'tags']
|
|
620
|
+
console.log(db.termManager.getStats()) // { totalTerms: 6, nextId: 7, orphanedTerms: 0 }
|
|
221
621
|
```
|
|
222
622
|
|
|
223
|
-
|
|
224
|
-
Removes the database file from disk (alias for deleteDatabase).
|
|
623
|
+
### Term Manager API
|
|
225
624
|
|
|
226
625
|
```javascript
|
|
227
|
-
|
|
626
|
+
// Get term ID (creates if doesn't exist)
|
|
627
|
+
const termId = db.termManager.getTermId('electronics')
|
|
628
|
+
|
|
629
|
+
// Get term by ID
|
|
630
|
+
const term = db.termManager.getTerm(1)
|
|
631
|
+
|
|
632
|
+
// Get statistics
|
|
633
|
+
const stats = db.termManager.getStats()
|
|
634
|
+
|
|
635
|
+
// Manual cleanup
|
|
636
|
+
const removedCount = await db.termManager.cleanupOrphanedTerms()
|
|
228
637
|
```
|
|
229
638
|
|
|
230
|
-
|
|
639
|
+
## Bulk Operations
|
|
231
640
|
|
|
232
|
-
|
|
233
|
-
- `indexStats`: Statistics about database indexes
|
|
641
|
+
### `iterate(criteria, options)`
|
|
234
642
|
|
|
235
|
-
|
|
643
|
+
High-performance bulk update method with streaming support.
|
|
236
644
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
645
|
+
```javascript
|
|
646
|
+
// Basic iteration
|
|
647
|
+
for await (const entry of db.iterate({ category: 'products' })) {
|
|
648
|
+
console.log(`${entry.name}: $${entry.price}`)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Bulk updates
|
|
652
|
+
for await (const entry of db.iterate({ inStock: true })) {
|
|
653
|
+
entry.price = Math.round(entry.price * 1.1 * 100) / 100
|
|
654
|
+
entry.lastUpdated = new Date().toISOString()
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Advanced options
|
|
658
|
+
for await (const entry of db.iterate(
|
|
659
|
+
{ category: 'electronics' },
|
|
660
|
+
{
|
|
661
|
+
chunkSize: 1000, // Process in batches
|
|
662
|
+
autoSave: false, // Auto-save after each chunk
|
|
663
|
+
detectChanges: true, // Auto-detect modifications
|
|
664
|
+
progressCallback: (progress) => {
|
|
665
|
+
console.log(`Processed: ${progress.processed}, Modified: ${progress.modified}`)
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
)) {
|
|
669
|
+
entry.processed = true
|
|
670
|
+
}
|
|
671
|
+
```
|
|
243
672
|
|
|
244
|
-
|
|
245
|
-
- `buffer-flush`: Emitted when buffer is flushed (count parameter)
|
|
246
|
-
- `buffer-full`: Emitted when buffer reaches threshold
|
|
247
|
-
- `auto-save-timer`: Emitted when time-based auto-save triggers
|
|
248
|
-
- `save-complete`: Emitted when save operation completes
|
|
249
|
-
- `close-save-complete`: Emitted when database closes with final save
|
|
250
|
-
- `close`: Emitted when database is closed
|
|
251
|
-
- `performance-configured`: Emitted when performance settings are changed
|
|
673
|
+
### Iterate Options
|
|
252
674
|
|
|
253
|
-
|
|
675
|
+
| Option | Type | Default | Description |
|
|
676
|
+
|--------|------|---------|-------------|
|
|
677
|
+
| `chunkSize` | number | 1000 | Batch size for processing |
|
|
678
|
+
| `strategy` | string | 'streaming' | Processing strategy |
|
|
679
|
+
| `autoSave` | boolean | false | Auto-save after each chunk |
|
|
680
|
+
| `detectChanges` | boolean | true | Auto-detect modifications |
|
|
681
|
+
| `progressCallback` | function | undefined | Progress callback function |
|
|
254
682
|
|
|
255
|
-
|
|
683
|
+
### Progress Callback
|
|
256
684
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
685
|
+
```javascript
|
|
686
|
+
{
|
|
687
|
+
processed: 1500, // Number of records processed
|
|
688
|
+
modified: 120, // Number of records modified
|
|
689
|
+
deleted: 5, // Number of records deleted
|
|
690
|
+
elapsed: 2500, // Time elapsed in milliseconds
|
|
691
|
+
completed: false // Whether the operation is complete
|
|
692
|
+
}
|
|
693
|
+
```
|
|
266
694
|
|
|
267
|
-
###
|
|
695
|
+
### `beginInsertSession(options)`
|
|
268
696
|
|
|
269
|
-
|
|
697
|
+
High-performance batch insertion method for inserting large amounts of data efficiently.
|
|
270
698
|
|
|
271
699
|
```javascript
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
700
|
+
// Example: EPG (Electronic Program Guide) data insertion
|
|
701
|
+
const session = db.beginInsertSession({
|
|
702
|
+
batchSize: 500, // Process in batches of 500
|
|
703
|
+
enableAutoSave: true // Auto-save after each batch
|
|
275
704
|
})
|
|
705
|
+
|
|
706
|
+
// Insert TV program data
|
|
707
|
+
const programmes = [
|
|
708
|
+
{
|
|
709
|
+
id: 1,
|
|
710
|
+
title: 'Breaking News',
|
|
711
|
+
channel: 'CNN',
|
|
712
|
+
startTime: 1640995200000,
|
|
713
|
+
endTime: 1640998800000,
|
|
714
|
+
terms: ['news', 'breaking', 'politics'],
|
|
715
|
+
category: 'news'
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
id: 2,
|
|
719
|
+
title: 'Sports Center',
|
|
720
|
+
channel: 'ESPN',
|
|
721
|
+
startTime: 1640998800000,
|
|
722
|
+
endTime: 1641002400000,
|
|
723
|
+
terms: ['sports', 'football', 'highlights'],
|
|
724
|
+
category: 'sports'
|
|
725
|
+
}
|
|
726
|
+
// ... thousands more programmes
|
|
727
|
+
]
|
|
728
|
+
|
|
729
|
+
// Add all programmes to the session
|
|
730
|
+
for (const programme of programmes) {
|
|
731
|
+
await session.add(programme)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Commit all records at once (much faster than individual inserts)
|
|
735
|
+
await session.commit()
|
|
736
|
+
await db.save()
|
|
737
|
+
|
|
738
|
+
console.log(`Inserted ${session.totalInserted} programmes`)
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### InsertSession Options
|
|
742
|
+
|
|
743
|
+
| Option | Type | Default | Description |
|
|
744
|
+
|--------|------|---------|-------------|
|
|
745
|
+
| `batchSize` | number | 100 | Number of records to process in each batch |
|
|
746
|
+
| `enableAutoSave` | boolean | false | Auto-save after each batch |
|
|
747
|
+
|
|
748
|
+
### InsertSession Methods
|
|
749
|
+
|
|
750
|
+
```javascript
|
|
751
|
+
const session = db.beginInsertSession({ batchSize: 500 })
|
|
752
|
+
|
|
753
|
+
// Add a single record
|
|
754
|
+
await session.add({ id: 1, name: 'John', email: 'john@example.com' })
|
|
755
|
+
|
|
756
|
+
// Add multiple records
|
|
757
|
+
for (const record of records) {
|
|
758
|
+
await session.add(record)
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Commit all pending records
|
|
762
|
+
await session.commit()
|
|
763
|
+
|
|
764
|
+
// Get statistics
|
|
765
|
+
console.log(`Inserted ${session.totalInserted} records`)
|
|
276
766
|
```
|
|
277
767
|
|
|
278
|
-
###
|
|
768
|
+
### When to Use `beginInsertSession()`
|
|
279
769
|
|
|
280
|
-
|
|
281
|
-
-
|
|
282
|
-
-
|
|
283
|
-
-
|
|
770
|
+
**Use `beginInsertSession()` for:**
|
|
771
|
+
- ✅ **Bulk insertions** (1000+ new records)
|
|
772
|
+
- ✅ **Data migration** from other databases
|
|
773
|
+
- ✅ **Initial data loading** (EPG, product catalogs, etc.)
|
|
774
|
+
- ✅ **Batch processing** of external data sources
|
|
284
775
|
|
|
285
|
-
|
|
776
|
+
### Performance Comparison
|
|
286
777
|
|
|
287
|
-
|
|
778
|
+
```javascript
|
|
779
|
+
// ❌ SLOW: Individual inserts (1000 records = 1000 database operations)
|
|
780
|
+
for (let i = 0; i < 1000; i++) {
|
|
781
|
+
await db.insert({ id: i, name: `Record ${i}` })
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// ✅ FAST: Batch insertion (1000 records = 1 database operation)
|
|
785
|
+
const session = db.beginInsertSession({ batchSize: 1000 })
|
|
786
|
+
for (let i = 0; i < 1000; i++) {
|
|
787
|
+
await session.add({ id: i, name: `Record ${i}` })
|
|
788
|
+
}
|
|
789
|
+
await session.commit()
|
|
790
|
+
```
|
|
288
791
|
|
|
289
|
-
###
|
|
792
|
+
### Performance Tips
|
|
290
793
|
|
|
291
|
-
**
|
|
292
|
-
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
794
|
+
1. **Use `beginInsertSession()`** for bulk insertions of 1000+ records
|
|
795
|
+
2. **Pre-initialize the schema** (via `fields` or `schema` options) before heavy ingestion to skip per-record auto-detection.
|
|
796
|
+
3. **Tune `batchSize` to your hardware**: start around 500–2000 records and watch memory/flush times; lower the value if auto-flush starts queuing.
|
|
797
|
+
4. **Enable autoSave** for critical operations to prevent data loss
|
|
798
|
+
5. **Use indexed fields** in your data for better query performance later
|
|
799
|
+
6. **Commit frequently** for very large datasets to avoid memory issues
|
|
296
800
|
|
|
297
|
-
|
|
298
|
-
- Disable auto-save with `autoSave: false`
|
|
299
|
-
- Manually call `flush()` and `save()` when needed
|
|
300
|
-
- Useful for applications requiring precise control over persistence timing
|
|
801
|
+
## Configuration Options
|
|
301
802
|
|
|
302
|
-
###
|
|
803
|
+
### Database Options
|
|
303
804
|
|
|
304
805
|
```javascript
|
|
305
|
-
const db = new Database('
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
autoSaveInterval: 5000, // Flush every 5 seconds
|
|
310
|
-
forceSaveOnClose: true, // Always save on close
|
|
806
|
+
const db = new Database('database.jdb', {
|
|
807
|
+
// Basic options
|
|
808
|
+
create: true, // Create file if doesn't exist
|
|
809
|
+
clear: false, // Clear existing files
|
|
311
810
|
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
811
|
+
// Schema (REQUIRED - define data structure)
|
|
812
|
+
fields: { // MANDATORY - Define all possible fields
|
|
813
|
+
id: 'number',
|
|
814
|
+
name: 'string',
|
|
815
|
+
email: 'string',
|
|
816
|
+
phone: 'string',
|
|
817
|
+
address: 'string',
|
|
818
|
+
city: 'string',
|
|
819
|
+
country: 'string',
|
|
820
|
+
status: 'string',
|
|
821
|
+
createdAt: 'number',
|
|
822
|
+
tags: 'array:string'
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
// Indexing (OPTIONAL - only for performance optimization)
|
|
826
|
+
indexes: { // Only index fields you query frequently
|
|
827
|
+
id: 'number', // Primary key - always index
|
|
828
|
+
email: 'string', // Login queries - index this
|
|
829
|
+
status: 'string' // Filter queries - index this
|
|
830
|
+
// Don't index: name, phone, address, etc. unless you query them frequently
|
|
831
|
+
},
|
|
832
|
+
|
|
833
|
+
// Query mode
|
|
834
|
+
indexedQueryMode: 'permissive', // 'strict' or 'permissive'
|
|
835
|
+
|
|
836
|
+
// Term mapping (enabled by default)
|
|
837
|
+
termMapping: true, // Enable term mapping (auto-detected)
|
|
838
|
+
termMappingFields: [], // Fields to map (auto-detected)
|
|
839
|
+
termMappingCleanup: true, // Auto cleanup
|
|
840
|
+
|
|
841
|
+
// Performance
|
|
842
|
+
chunkSize: 1000, // Default chunk size
|
|
843
|
+
autoSave: false, // Auto-save changes
|
|
844
|
+
|
|
845
|
+
// Debug
|
|
846
|
+
debugMode: false // Enable debug logging
|
|
847
|
+
})
|
|
318
848
|
```
|
|
319
849
|
|
|
320
|
-
###
|
|
850
|
+
### Schema vs Indexes - Complete Guide
|
|
851
|
+
|
|
852
|
+
**Understanding the Difference:**
|
|
321
853
|
|
|
322
854
|
```javascript
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
855
|
+
// Your data structure (schema) - MUST be defined in fields option
|
|
856
|
+
const userRecord = {
|
|
857
|
+
id: 1,
|
|
858
|
+
name: 'John Doe',
|
|
859
|
+
email: 'john@example.com',
|
|
860
|
+
phone: '123-456-7890',
|
|
861
|
+
address: '123 Main St',
|
|
862
|
+
city: 'New York',
|
|
863
|
+
country: 'USA',
|
|
864
|
+
status: 'active',
|
|
865
|
+
createdAt: 1640995200000,
|
|
866
|
+
tags: ['premium', 'verified']
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Database configuration
|
|
870
|
+
const db = new Database('users.jdb', {
|
|
871
|
+
fields: { // REQUIRED - Define schema structure
|
|
872
|
+
id: 'number',
|
|
873
|
+
name: 'string',
|
|
874
|
+
email: 'string',
|
|
875
|
+
phone: 'string',
|
|
876
|
+
address: 'string',
|
|
877
|
+
city: 'string',
|
|
878
|
+
country: 'string',
|
|
879
|
+
status: 'string',
|
|
880
|
+
createdAt: 'number',
|
|
881
|
+
tags: 'array:string'
|
|
882
|
+
},
|
|
883
|
+
indexes: { // OPTIONAL - Only for performance optimization
|
|
884
|
+
id: 'number', // Primary key - always index
|
|
885
|
+
email: 'string', // Login queries - index this
|
|
886
|
+
status: 'string', // Filter queries - index this
|
|
887
|
+
tags: 'array:string' // Search by tags - index this
|
|
888
|
+
}
|
|
889
|
+
// Fields like name, phone, address, city, country, createdAt
|
|
890
|
+
// are still queryable but will use slower sequential search
|
|
891
|
+
})
|
|
892
|
+
|
|
893
|
+
// All these queries work:
|
|
894
|
+
await db.find({ id: 1 }) // ✅ Fast (indexed)
|
|
895
|
+
await db.find({ email: 'john@example.com' }) // ✅ Fast (indexed)
|
|
896
|
+
await db.find({ status: 'active' }) // ✅ Fast (indexed)
|
|
897
|
+
await db.find({ tags: 'premium' }) // ✅ Fast (indexed)
|
|
898
|
+
await db.find({ name: 'John Doe' }) // ⚠️ Slower (not indexed, but works)
|
|
899
|
+
await db.find({ phone: '123-456-7890' }) // ⚠️ Slower (not indexed, but works)
|
|
900
|
+
await db.find({ city: 'New York' }) // ⚠️ Slower (not indexed, but works)
|
|
901
|
+
await db.find({ createdAt: { '>': 1640000000000 } }) // ⚠️ Slower (not indexed, but works)
|
|
902
|
+
```
|
|
327
903
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
904
|
+
**Key Points:**
|
|
905
|
+
- ✅ **All fields are queryable** regardless of indexing
|
|
906
|
+
- ✅ **Schema is auto-detected** from your data
|
|
907
|
+
- ⚠️ **Indexes are optional** - only for performance
|
|
908
|
+
- ⚠️ **Each index uses memory** - use sparingly
|
|
909
|
+
- ⚠️ **Index only what you query frequently** (80%+ of queries)
|
|
331
910
|
|
|
332
|
-
|
|
333
|
-
console.log('Time-based auto-save triggered');
|
|
334
|
-
});
|
|
911
|
+
### Field Types
|
|
335
912
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
913
|
+
Supported field types for indexing:
|
|
914
|
+
|
|
915
|
+
| Type | Term Mapping | Description | Example |
|
|
916
|
+
|------|--------------|-------------|---------|
|
|
917
|
+
| `'string'` | ✅ Auto-enabled | String values | `"Brazil"` |
|
|
918
|
+
| `'array:string'` | ✅ Auto-enabled | Array of strings | `["Brazil", "Argentina"]` |
|
|
919
|
+
| `'number'` | ❌ Disabled | Numeric values | `85` |
|
|
920
|
+
| `'array:number'` | ❌ Disabled | Array of numbers | `[85, 92, 78]` |
|
|
921
|
+
| `'boolean'` | ❌ Disabled | Boolean values | `true` |
|
|
922
|
+
| `'array:boolean'` | ❌ Disabled | Array of booleans | `[true, false]` |
|
|
923
|
+
| `'date'` | ❌ Disabled | Date objects (stored as timestamps) | `new Date()` |
|
|
924
|
+
|
|
925
|
+
## Error Handling
|
|
926
|
+
|
|
927
|
+
```javascript
|
|
928
|
+
try {
|
|
929
|
+
await db.init()
|
|
930
|
+
await db.insert({ id: 1, name: 'Test' })
|
|
931
|
+
await db.save()
|
|
932
|
+
} catch (error) {
|
|
933
|
+
console.error('Database error:', error.message)
|
|
934
|
+
} finally {
|
|
935
|
+
await db.destroy()
|
|
936
|
+
}
|
|
339
937
|
```
|
|
340
938
|
|
|
341
|
-
|
|
939
|
+
## Best Practices
|
|
940
|
+
|
|
941
|
+
### Database Operations
|
|
942
|
+
1. **Always define `fields`** - This is mandatory for schema definition
|
|
943
|
+
2. **Always call `init()`** before using the database
|
|
944
|
+
3. **Use `save()`** to persist changes to disk
|
|
945
|
+
4. **Call `destroy()`** when done to clean up resources
|
|
946
|
+
5. **Handle errors** appropriately
|
|
947
|
+
|
|
948
|
+
### Performance Optimization
|
|
949
|
+
6. **Index strategically** - only fields you query frequently (80%+ of queries)
|
|
950
|
+
7. **Don't over-index** - each index uses additional memory
|
|
951
|
+
8. **Term mapping is automatically enabled** for optimal performance
|
|
952
|
+
9. **Use `iterate()`** for bulk operations on large datasets
|
|
953
|
+
|
|
954
|
+
### Index Strategy
|
|
955
|
+
10. **Always index primary keys** (usually `id`)
|
|
956
|
+
11. **Index frequently filtered fields** (status, category, type)
|
|
957
|
+
12. **Index search fields** (email, username, tags)
|
|
958
|
+
13. **Don't index descriptive fields** (name, description, comments) unless you search them frequently
|
|
959
|
+
14. **Monitor memory usage** - too many indexes can impact performance
|
|
960
|
+
|
|
961
|
+
## Migration Guide
|
|
962
|
+
|
|
963
|
+
### From Previous Versions
|
|
964
|
+
|
|
965
|
+
If you're upgrading from an older version:
|
|
966
|
+
|
|
967
|
+
1. **Backup your data** before upgrading
|
|
968
|
+
2. **Check breaking changes** in the changelog
|
|
969
|
+
3. **Update your code** to use new APIs
|
|
970
|
+
4. **Test thoroughly** with your data
|
|
971
|
+
|
|
972
|
+
### Common Migration Tasks
|
|
342
973
|
|
|
343
974
|
```javascript
|
|
344
|
-
//
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
975
|
+
// Old way
|
|
976
|
+
const results = db.query({ name: 'John' })
|
|
977
|
+
|
|
978
|
+
// New way
|
|
979
|
+
const results = await db.find({ name: 'John' })
|
|
980
|
+
|
|
981
|
+
// Old way
|
|
982
|
+
db.insert({ id: 1, name: 'John' })
|
|
983
|
+
|
|
984
|
+
// New way
|
|
985
|
+
await db.insert({ id: 1, name: 'John' })
|
|
349
986
|
```
|
|
350
987
|
|
|
351
|
-
##
|
|
988
|
+
## Migration Guide
|
|
989
|
+
|
|
990
|
+
### ⚠️ Important: Database Files Are NOT Compatible
|
|
991
|
+
|
|
992
|
+
**Existing `.jdb` files from version 1.x.x will NOT work with version 2.1.0.**
|
|
352
993
|
|
|
353
|
-
###
|
|
994
|
+
### Step 1: Export Data from 1.x.x
|
|
995
|
+
|
|
996
|
+
```javascript
|
|
997
|
+
// In your 1.x.x application
|
|
998
|
+
const oldDb = new Database('old-database.jdb', {
|
|
999
|
+
indexes: { name: 'string', tags: 'array:string' }
|
|
1000
|
+
})
|
|
354
1001
|
|
|
355
|
-
|
|
1002
|
+
await oldDb.init()
|
|
1003
|
+
const allData = await oldDb.find({}) // Export all data
|
|
1004
|
+
await oldDb.destroy()
|
|
1005
|
+
```
|
|
356
1006
|
|
|
357
|
-
###
|
|
1007
|
+
### Step 2: Update Your Code
|
|
358
1008
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
1009
|
+
```javascript
|
|
1010
|
+
// ❌ OLD (1.x.x)
|
|
1011
|
+
const db = new Database('db.jdb', {
|
|
1012
|
+
indexes: { name: 'string', tags: 'array:string' }
|
|
1013
|
+
})
|
|
362
1014
|
|
|
363
|
-
|
|
1015
|
+
// ✅ NEW (2.1.0)
|
|
1016
|
+
const db = new Database('db.jdb', {
|
|
1017
|
+
fields: { // REQUIRED - Define schema
|
|
1018
|
+
id: 'number',
|
|
1019
|
+
name: 'string',
|
|
1020
|
+
tags: 'array:string'
|
|
1021
|
+
},
|
|
1022
|
+
indexes: { // OPTIONAL - Performance optimization
|
|
1023
|
+
name: 'string', // ✅ Search by name
|
|
1024
|
+
tags: 'array:string' // ✅ Search by tags
|
|
1025
|
+
}
|
|
1026
|
+
})
|
|
1027
|
+
```
|
|
364
1028
|
|
|
365
|
-
|
|
366
|
-
- **Index caching**: Caches index data for faster lookups
|
|
367
|
-
- **Adaptive eviction**: Automatically manages cache size
|
|
1029
|
+
### Step 3: Import Data to 2.1.0
|
|
368
1030
|
|
|
369
|
-
|
|
1031
|
+
```javascript
|
|
1032
|
+
// In your 2.1.0 application
|
|
1033
|
+
const newDb = new Database('new-database.jdb', {
|
|
1034
|
+
fields: { /* your schema */ },
|
|
1035
|
+
indexes: { /* your indexes */ }
|
|
1036
|
+
})
|
|
370
1037
|
|
|
371
|
-
|
|
372
|
-
- **Index optimization**: Optimizes indexes during idle time
|
|
373
|
-
- **Integrity checks**: Performs periodic integrity validation
|
|
1038
|
+
await newDb.init()
|
|
374
1039
|
|
|
375
|
-
|
|
1040
|
+
// Import all data
|
|
1041
|
+
for (const record of allData) {
|
|
1042
|
+
await newDb.insert(record)
|
|
1043
|
+
}
|
|
376
1044
|
|
|
377
|
-
|
|
1045
|
+
await newDb.save()
|
|
1046
|
+
```
|
|
378
1047
|
|
|
379
|
-
|
|
380
|
-
- **Index rebuilding**: Automatically rebuilds corrupted indexes
|
|
381
|
-
- **Memory pressure management**: Handles memory pressure gracefully
|
|
382
|
-
- **Compression fallback**: Falls back to no compression if needed
|
|
1048
|
+
### Key Changes Summary
|
|
383
1049
|
|
|
384
|
-
|
|
1050
|
+
| Feature | 1.x.x | 2.1.0 |
|
|
1051
|
+
|---------|-------|-------|
|
|
1052
|
+
| `fields` | Optional | **MANDATORY** |
|
|
1053
|
+
| `termMapping` | `false` (default) | `true` (default) |
|
|
1054
|
+
| `termMappingFields` | Manual config | Auto-detected |
|
|
1055
|
+
| Database files | Compatible | **NOT compatible** |
|
|
1056
|
+
| Performance | Basic | **77% size reduction** |
|
|
385
1057
|
|
|
386
|
-
- **Insert**: ~10,000 ops/sec (bulk), ~1,000 ops/sec (single)
|
|
387
|
-
- **Query**: ~5,000 ops/sec (indexed), ~500 ops/sec (unindexed)
|
|
388
|
-
- **Update**: ~1,000 ops/sec
|
|
389
|
-
- **Delete**: ~1,000 ops/sec
|
|
390
|
-
- **Compression**: 20-80% size reduction depending on data type
|