jexidb 2.1.1 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Database.cjs +7981 -231
- package/package.json +9 -2
- package/src/Database.mjs +372 -154
- package/src/SchemaManager.mjs +325 -268
- package/src/Serializer.mjs +20 -1
- package/src/managers/QueryManager.mjs +74 -18
- package/.babelrc +0 -13
- package/.gitattributes +0 -2
- package/CHANGELOG.md +0 -140
- package/babel.config.json +0 -5
- package/docs/API.md +0 -1057
- package/docs/EXAMPLES.md +0 -701
- package/docs/README.md +0 -194
- package/examples/iterate-usage-example.js +0 -157
- package/examples/simple-iterate-example.js +0 -115
- package/jest.config.js +0 -24
- package/scripts/README.md +0 -47
- package/scripts/benchmark-array-serialization.js +0 -108
- package/scripts/clean-test-files.js +0 -75
- package/scripts/prepare.js +0 -31
- package/scripts/run-tests.js +0 -80
- package/scripts/score-mode-demo.js +0 -45
- package/test/$not-operator-with-and.test.js +0 -282
- package/test/README.md +0 -8
- package/test/close-init-cycle.test.js +0 -256
- package/test/coverage-method.test.js +0 -93
- package/test/critical-bugs-fixes.test.js +0 -1069
- package/test/deserialize-corruption-fixes.test.js +0 -296
- package/test/exists-method.test.js +0 -318
- package/test/explicit-indexes-comparison.test.js +0 -219
- package/test/filehandler-non-adjacent-ranges-bug.test.js +0 -175
- package/test/index-line-number-regression.test.js +0 -100
- package/test/index-missing-index-data.test.js +0 -91
- package/test/index-persistence.test.js +0 -491
- package/test/index-serialization.test.js +0 -314
- package/test/indexed-query-mode.test.js +0 -360
- package/test/insert-session-auto-flush.test.js +0 -353
- package/test/iterate-method.test.js +0 -272
- package/test/legacy-operator-compat.test.js +0 -154
- package/test/query-operators.test.js +0 -238
- package/test/regex-array-fields.test.js +0 -129
- package/test/score-method.test.js +0 -298
- package/test/setup.js +0 -17
- package/test/term-mapping-minimal.test.js +0 -154
- package/test/term-mapping-simple.test.js +0 -257
- package/test/term-mapping.test.js +0 -514
- package/test/writebuffer-flush-resilience.test.js +0 -204
package/docs/EXAMPLES.md
DELETED
|
@@ -1,701 +0,0 @@
|
|
|
1
|
-
# JexiDB Examples
|
|
2
|
-
|
|
3
|
-
This document provides practical examples of using JexiDB in real-world scenarios.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
1. [Basic Usage](#basic-usage)
|
|
8
|
-
2. [User Management System](#user-management-system)
|
|
9
|
-
3. [Product Catalog](#product-catalog)
|
|
10
|
-
4. [Blog System](#blog-system)
|
|
11
|
-
5. [Analytics Dashboard](#analytics-dashboard)
|
|
12
|
-
6. [Performance Optimization](#performance-optimization)
|
|
13
|
-
|
|
14
|
-
## Basic Usage
|
|
15
|
-
|
|
16
|
-
### Simple Todo List
|
|
17
|
-
|
|
18
|
-
```javascript
|
|
19
|
-
import { Database } from 'jexidb'
|
|
20
|
-
|
|
21
|
-
const todos = new Database('todos.jdb', {
|
|
22
|
-
fields: { // REQUIRED - Define schema
|
|
23
|
-
id: 'number',
|
|
24
|
-
title: 'string',
|
|
25
|
-
completed: 'boolean',
|
|
26
|
-
priority: 'string'
|
|
27
|
-
},
|
|
28
|
-
indexes: { // OPTIONAL - Only fields you query frequently
|
|
29
|
-
completed: 'boolean', // ✅ Filter by completion status
|
|
30
|
-
priority: 'string' // ✅ Filter by priority level
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
await todos.init()
|
|
35
|
-
|
|
36
|
-
// Add todos
|
|
37
|
-
await todos.insert({ id: 1, title: 'Learn JexiDB', completed: false, priority: 'high' })
|
|
38
|
-
await todos.insert({ id: 2, title: 'Build app', completed: false, priority: 'medium' })
|
|
39
|
-
await todos.insert({ id: 3, title: 'Deploy app', completed: true, priority: 'low' })
|
|
40
|
-
|
|
41
|
-
// Query todos
|
|
42
|
-
const pending = await todos.find({ completed: false })
|
|
43
|
-
const highPriority = await todos.find({ priority: 'high' })
|
|
44
|
-
|
|
45
|
-
// Update todo
|
|
46
|
-
await todos.update({ id: 1 }, { completed: true })
|
|
47
|
-
|
|
48
|
-
// Save changes
|
|
49
|
-
await todos.save()
|
|
50
|
-
|
|
51
|
-
// Clean up
|
|
52
|
-
await todos.destroy()
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## User Management System
|
|
56
|
-
|
|
57
|
-
### User Registration and Authentication
|
|
58
|
-
|
|
59
|
-
```javascript
|
|
60
|
-
import { Database } from 'jexidb'
|
|
61
|
-
import bcrypt from 'bcrypt'
|
|
62
|
-
|
|
63
|
-
const users = new Database('users.jdb', {
|
|
64
|
-
fields: { // REQUIRED - Define schema
|
|
65
|
-
id: 'number',
|
|
66
|
-
email: 'string',
|
|
67
|
-
username: 'string',
|
|
68
|
-
role: 'string',
|
|
69
|
-
createdAt: 'number'
|
|
70
|
-
},
|
|
71
|
-
indexes: { // OPTIONAL - Only fields you query frequently
|
|
72
|
-
email: 'string', // ✅ Login queries
|
|
73
|
-
role: 'string' // ✅ Filter by user role
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
await users.init()
|
|
78
|
-
|
|
79
|
-
// Register user
|
|
80
|
-
async function registerUser(userData) {
|
|
81
|
-
const hashedPassword = await bcrypt.hash(userData.password, 10)
|
|
82
|
-
|
|
83
|
-
await users.insert({
|
|
84
|
-
id: Date.now(),
|
|
85
|
-
email: userData.email,
|
|
86
|
-
username: userData.username,
|
|
87
|
-
password: hashedPassword,
|
|
88
|
-
role: 'user',
|
|
89
|
-
createdAt: Date.now(),
|
|
90
|
-
profile: {
|
|
91
|
-
firstName: userData.firstName,
|
|
92
|
-
lastName: userData.lastName,
|
|
93
|
-
avatar: userData.avatar
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
await users.save()
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Find user by email
|
|
101
|
-
async function findUserByEmail(email) {
|
|
102
|
-
return await users.findOne({ email })
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Get users by role
|
|
106
|
-
async function getUsersByRole(role) {
|
|
107
|
-
return await users.find({ role })
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Update user profile
|
|
111
|
-
async function updateProfile(userId, updates) {
|
|
112
|
-
await users.update({ id: userId }, { profile: updates })
|
|
113
|
-
await users.save()
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Example usage
|
|
117
|
-
await registerUser({
|
|
118
|
-
email: 'john@example.com',
|
|
119
|
-
username: 'john_doe',
|
|
120
|
-
password: 'secure123',
|
|
121
|
-
firstName: 'John',
|
|
122
|
-
lastName: 'Doe'
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
const user = await findUserByEmail('john@example.com')
|
|
126
|
-
console.log(user.username) // 'john_doe'
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Product Catalog
|
|
130
|
-
|
|
131
|
-
### E-commerce Product Management
|
|
132
|
-
|
|
133
|
-
```javascript
|
|
134
|
-
import { Database } from 'jexidb'
|
|
135
|
-
|
|
136
|
-
const products = new Database('products.jdb', {
|
|
137
|
-
fields: { // REQUIRED - Define schema
|
|
138
|
-
id: 'string',
|
|
139
|
-
name: 'string',
|
|
140
|
-
category: 'string',
|
|
141
|
-
price: 'number',
|
|
142
|
-
inStock: 'boolean',
|
|
143
|
-
rating: 'number',
|
|
144
|
-
tags: 'array:string',
|
|
145
|
-
features: 'array:string',
|
|
146
|
-
categories: 'array:string'
|
|
147
|
-
},
|
|
148
|
-
indexes: { // OPTIONAL - Only fields you query frequently
|
|
149
|
-
name: 'string', // ✅ Search by product name
|
|
150
|
-
category: 'string', // ✅ Filter by category
|
|
151
|
-
price: 'number', // ✅ Filter by price range
|
|
152
|
-
inStock: 'boolean', // ✅ Filter by stock status
|
|
153
|
-
tags: 'array:string' // ✅ Search by tags
|
|
154
|
-
}
|
|
155
|
-
// termMapping is now auto-enabled for array:string fields
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
await products.init()
|
|
159
|
-
|
|
160
|
-
// Add products
|
|
161
|
-
const productData = [
|
|
162
|
-
{
|
|
163
|
-
id: 'prod-001',
|
|
164
|
-
name: 'Wireless Headphones',
|
|
165
|
-
category: 'electronics',
|
|
166
|
-
price: 99.99,
|
|
167
|
-
inStock: true,
|
|
168
|
-
rating: 4.5,
|
|
169
|
-
tags: ['wireless', 'audio', 'bluetooth'],
|
|
170
|
-
features: ['noise-canceling', 'battery-life', 'comfort'],
|
|
171
|
-
description: 'High-quality wireless headphones with noise cancellation'
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
id: 'prod-002',
|
|
175
|
-
name: 'Smart Watch',
|
|
176
|
-
category: 'electronics',
|
|
177
|
-
price: 199.99,
|
|
178
|
-
inStock: true,
|
|
179
|
-
rating: 4.2,
|
|
180
|
-
tags: ['smart', 'fitness', 'watch'],
|
|
181
|
-
features: ['heart-rate', 'gps', 'waterproof'],
|
|
182
|
-
description: 'Advanced smartwatch with health monitoring'
|
|
183
|
-
}
|
|
184
|
-
]
|
|
185
|
-
|
|
186
|
-
for (const product of productData) {
|
|
187
|
-
await products.insert(product)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Search products
|
|
191
|
-
async function searchProducts(query) {
|
|
192
|
-
return await products.find({
|
|
193
|
-
$or: [
|
|
194
|
-
{ name: { $regex: query, $options: 'i' } },
|
|
195
|
-
{ tags: query },
|
|
196
|
-
{ category: query }
|
|
197
|
-
]
|
|
198
|
-
})
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Get products by price range
|
|
202
|
-
async function getProductsByPrice(min, max) {
|
|
203
|
-
return await products.find({
|
|
204
|
-
price: { '>=': min, '<=': max },
|
|
205
|
-
inStock: true
|
|
206
|
-
})
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Update product stock
|
|
210
|
-
async function updateStock(productId, newStock) {
|
|
211
|
-
await products.update({ id: productId }, {
|
|
212
|
-
inStock: newStock > 0,
|
|
213
|
-
stockCount: newStock
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Bulk price update
|
|
218
|
-
async function updatePrices(category, discountPercent) {
|
|
219
|
-
for await (const product of products.iterate({ category })) {
|
|
220
|
-
product.price = Math.round(product.price * (1 - discountPercent/100) * 100) / 100
|
|
221
|
-
product.lastPriceUpdate = Date.now()
|
|
222
|
-
}
|
|
223
|
-
await products.save()
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Example usage
|
|
227
|
-
const searchResults = await searchProducts('wireless')
|
|
228
|
-
const affordableProducts = await getProductsByPrice(50, 150)
|
|
229
|
-
await updatePrices('electronics', 10) // 10% discount
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
## Blog System
|
|
233
|
-
|
|
234
|
-
### Blog Posts with Comments
|
|
235
|
-
|
|
236
|
-
```javascript
|
|
237
|
-
import { Database } from 'jexidb'
|
|
238
|
-
|
|
239
|
-
const blog = new Database('blog.jdb', {
|
|
240
|
-
fields: { // REQUIRED - Define schema
|
|
241
|
-
id: 'string',
|
|
242
|
-
title: 'string',
|
|
243
|
-
author: 'string',
|
|
244
|
-
published: 'boolean',
|
|
245
|
-
createdAt: 'number',
|
|
246
|
-
category: 'string',
|
|
247
|
-
tags: 'array:string',
|
|
248
|
-
keywords: 'array:string'
|
|
249
|
-
},
|
|
250
|
-
indexes: { // OPTIONAL - Only fields you query frequently
|
|
251
|
-
category: 'string', // ✅ Filter by category
|
|
252
|
-
published: 'boolean', // ✅ Filter published posts
|
|
253
|
-
title: 'string', // ✅ Search by title
|
|
254
|
-
tags: 'array:string', // ✅ Search by tags
|
|
255
|
-
keywords: 'array:string' // ✅ Search by keywords
|
|
256
|
-
}
|
|
257
|
-
// termMapping is now auto-enabled for array:string fields
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
await blog.init()
|
|
261
|
-
|
|
262
|
-
// Create blog post
|
|
263
|
-
async function createPost(postData) {
|
|
264
|
-
const post = {
|
|
265
|
-
id: `post-${Date.now()}`,
|
|
266
|
-
title: postData.title,
|
|
267
|
-
content: postData.content,
|
|
268
|
-
author: postData.author,
|
|
269
|
-
tags: postData.tags || [],
|
|
270
|
-
keywords: postData.keywords || [],
|
|
271
|
-
category: postData.category,
|
|
272
|
-
published: false,
|
|
273
|
-
createdAt: Date.now(),
|
|
274
|
-
updatedAt: Date.now(),
|
|
275
|
-
views: 0,
|
|
276
|
-
likes: 0,
|
|
277
|
-
comments: []
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
await blog.insert(post)
|
|
281
|
-
await blog.save()
|
|
282
|
-
return post.id
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Publish post
|
|
286
|
-
async function publishPost(postId) {
|
|
287
|
-
await blog.update({ id: postId }, {
|
|
288
|
-
published: true,
|
|
289
|
-
publishedAt: Date.now()
|
|
290
|
-
})
|
|
291
|
-
await blog.save()
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Add comment
|
|
295
|
-
async function addComment(postId, comment) {
|
|
296
|
-
const post = await blog.findOne({ id: postId })
|
|
297
|
-
if (post) {
|
|
298
|
-
const newComment = {
|
|
299
|
-
id: `comment-${Date.now()}`,
|
|
300
|
-
author: comment.author,
|
|
301
|
-
content: comment.content,
|
|
302
|
-
createdAt: Date.now(),
|
|
303
|
-
likes: 0
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
post.comments.push(newComment)
|
|
307
|
-
await blog.update({ id: postId }, { comments: post.comments })
|
|
308
|
-
await blog.save()
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Get posts by category
|
|
313
|
-
async function getPostsByCategory(category) {
|
|
314
|
-
return await blog.find({
|
|
315
|
-
category,
|
|
316
|
-
published: true
|
|
317
|
-
})
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Search posts
|
|
321
|
-
async function searchPosts(query) {
|
|
322
|
-
return await blog.find({
|
|
323
|
-
$and: [
|
|
324
|
-
{ published: true },
|
|
325
|
-
{
|
|
326
|
-
$or: [
|
|
327
|
-
{ title: { $regex: query, $options: 'i' } },
|
|
328
|
-
{ tags: query },
|
|
329
|
-
{ keywords: query }
|
|
330
|
-
]
|
|
331
|
-
}
|
|
332
|
-
]
|
|
333
|
-
})
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Update view count
|
|
337
|
-
async function incrementViews(postId) {
|
|
338
|
-
await blog.update({ id: postId }, {
|
|
339
|
-
views: { $inc: 1 }
|
|
340
|
-
})
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Example usage
|
|
344
|
-
const postId = await createPost({
|
|
345
|
-
title: 'Getting Started with JexiDB',
|
|
346
|
-
content: 'JexiDB is a powerful...',
|
|
347
|
-
author: 'john_doe',
|
|
348
|
-
tags: ['database', 'javascript', 'tutorial'],
|
|
349
|
-
keywords: ['jexidb', 'nosql', 'nodejs'],
|
|
350
|
-
category: 'tutorial'
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
await publishPost(postId)
|
|
354
|
-
await addComment(postId, {
|
|
355
|
-
author: 'jane_smith',
|
|
356
|
-
content: 'Great tutorial! Very helpful.'
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
const tutorialPosts = await getPostsByCategory('tutorial')
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
## Analytics Dashboard
|
|
363
|
-
|
|
364
|
-
### Data Analytics with Aggregations
|
|
365
|
-
|
|
366
|
-
```javascript
|
|
367
|
-
import { Database } from 'jexidb'
|
|
368
|
-
|
|
369
|
-
const analytics = new Database('analytics.jdb', {
|
|
370
|
-
fields: { // REQUIRED - Define schema
|
|
371
|
-
id: 'string',
|
|
372
|
-
event: 'string',
|
|
373
|
-
userId: 'string',
|
|
374
|
-
timestamp: 'number',
|
|
375
|
-
category: 'string'
|
|
376
|
-
},
|
|
377
|
-
indexes: { // OPTIONAL - Only fields you query frequently
|
|
378
|
-
timestamp: 'number', // ✅ Filter by date range
|
|
379
|
-
userId: 'string', // ✅ Filter by user
|
|
380
|
-
event: 'string', // ✅ Filter by event type
|
|
381
|
-
category: 'string' // ✅ Filter by category
|
|
382
|
-
}
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
await analytics.init()
|
|
386
|
-
|
|
387
|
-
// Track events
|
|
388
|
-
async function trackEvent(eventData) {
|
|
389
|
-
await analytics.insert({
|
|
390
|
-
id: `event-${Date.now()}-${Math.random()}`,
|
|
391
|
-
event: eventData.event,
|
|
392
|
-
userId: eventData.userId,
|
|
393
|
-
timestamp: Date.now(),
|
|
394
|
-
category: eventData.category,
|
|
395
|
-
properties: eventData.properties || {}
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Get daily statistics
|
|
400
|
-
async function getDailyStats(date) {
|
|
401
|
-
const startOfDay = new Date(date).setHours(0, 0, 0, 0)
|
|
402
|
-
const endOfDay = new Date(date).setHours(23, 59, 59, 999)
|
|
403
|
-
|
|
404
|
-
const events = await analytics.find({
|
|
405
|
-
timestamp: { '>=': startOfDay, '<=': endOfDay }
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
// Aggregate data
|
|
409
|
-
const stats = {
|
|
410
|
-
totalEvents: events.length,
|
|
411
|
-
uniqueUsers: new Set(events.map(e => e.userId)).size,
|
|
412
|
-
eventsByCategory: {},
|
|
413
|
-
eventsByType: {}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
events.forEach(event => {
|
|
417
|
-
// Count by category
|
|
418
|
-
stats.eventsByCategory[event.category] =
|
|
419
|
-
(stats.eventsByCategory[event.category] || 0) + 1
|
|
420
|
-
|
|
421
|
-
// Count by event type
|
|
422
|
-
stats.eventsByType[event.event] =
|
|
423
|
-
(stats.eventsByType[event.event] || 0) + 1
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
return stats
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Get user activity
|
|
430
|
-
async function getUserActivity(userId, days = 30) {
|
|
431
|
-
const since = Date.now() - (days * 24 * 60 * 60 * 1000)
|
|
432
|
-
|
|
433
|
-
return await analytics.find({
|
|
434
|
-
userId,
|
|
435
|
-
timestamp: { '>=': since }
|
|
436
|
-
})
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Top performing content
|
|
440
|
-
async function getTopContent(limit = 10) {
|
|
441
|
-
const events = await analytics.find({
|
|
442
|
-
event: 'content_view'
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
const contentViews = {}
|
|
446
|
-
events.forEach(event => {
|
|
447
|
-
const contentId = event.properties.contentId
|
|
448
|
-
if (contentId) {
|
|
449
|
-
contentViews[contentId] = (contentViews[contentId] || 0) + 1
|
|
450
|
-
}
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
return Object.entries(contentViews)
|
|
454
|
-
.sort(([,a], [,b]) => b - a)
|
|
455
|
-
.slice(0, limit)
|
|
456
|
-
.map(([contentId, views]) => ({ contentId, views }))
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Example usage
|
|
460
|
-
await trackEvent({
|
|
461
|
-
event: 'page_view',
|
|
462
|
-
userId: 'user123',
|
|
463
|
-
category: 'navigation',
|
|
464
|
-
properties: { page: '/dashboard', referrer: 'google.com' }
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
const todayStats = await getDailyStats(new Date())
|
|
468
|
-
const userActivity = await getUserActivity('user123', 7)
|
|
469
|
-
const topContent = await getTopContent(5)
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
## Performance Optimization
|
|
473
|
-
|
|
474
|
-
### Large Dataset Handling
|
|
475
|
-
|
|
476
|
-
```javascript
|
|
477
|
-
import { Database } from 'jexidb'
|
|
478
|
-
|
|
479
|
-
const largeDB = new Database('large-dataset.jdb', {
|
|
480
|
-
fields: { // REQUIRED - Define schema
|
|
481
|
-
id: 'number',
|
|
482
|
-
type: 'string',
|
|
483
|
-
status: 'string',
|
|
484
|
-
tags: 'array:string',
|
|
485
|
-
categories: 'array:string'
|
|
486
|
-
},
|
|
487
|
-
indexes: { // OPTIONAL - Only fields you query frequently
|
|
488
|
-
status: 'string', // ✅ Filter by status
|
|
489
|
-
type: 'string', // ✅ Filter by type
|
|
490
|
-
tags: 'array:string' // ✅ Search by tags
|
|
491
|
-
}
|
|
492
|
-
// termMapping is now auto-enabled for array:string fields
|
|
493
|
-
})
|
|
494
|
-
|
|
495
|
-
await largeDB.init()
|
|
496
|
-
|
|
497
|
-
// Bulk insert with progress tracking
|
|
498
|
-
async function bulkInsert(data, batchSize = 1000) {
|
|
499
|
-
console.log(`Inserting ${data.length} records...`)
|
|
500
|
-
|
|
501
|
-
for (let i = 0; i < data.length; i += batchSize) {
|
|
502
|
-
const batch = data.slice(i, i + batchSize)
|
|
503
|
-
|
|
504
|
-
for (const record of batch) {
|
|
505
|
-
await largeDB.insert(record)
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (i % (batchSize * 10) === 0) {
|
|
509
|
-
console.log(`Processed ${i + batch.length} records`)
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
await largeDB.save()
|
|
514
|
-
console.log('Bulk insert completed')
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Bulk update with iterate()
|
|
518
|
-
async function bulkUpdateStatus(oldStatus, newStatus) {
|
|
519
|
-
console.log(`Updating status from ${oldStatus} to ${newStatus}`)
|
|
520
|
-
|
|
521
|
-
let processed = 0
|
|
522
|
-
let modified = 0
|
|
523
|
-
|
|
524
|
-
for await (const record of largeDB.iterate({ status: oldStatus })) {
|
|
525
|
-
record.status = newStatus
|
|
526
|
-
record.updatedAt = Date.now()
|
|
527
|
-
|
|
528
|
-
processed++
|
|
529
|
-
if (processed % 1000 === 0) {
|
|
530
|
-
console.log(`Processed ${processed} records, modified ${modified}`)
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
await largeDB.save()
|
|
535
|
-
console.log(`Bulk update completed: ${processed} processed, ${modified} modified`)
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Memory-efficient querying
|
|
539
|
-
async function getPaginatedResults(criteria, page = 1, limit = 100) {
|
|
540
|
-
const skip = (page - 1) * limit
|
|
541
|
-
|
|
542
|
-
// Use walk() for memory efficiency
|
|
543
|
-
const results = []
|
|
544
|
-
let count = 0
|
|
545
|
-
|
|
546
|
-
for await (const record of largeDB.walk()) {
|
|
547
|
-
// Apply criteria filtering
|
|
548
|
-
if (matchesCriteria(record, criteria)) {
|
|
549
|
-
if (count >= skip && results.length < limit) {
|
|
550
|
-
results.push(record)
|
|
551
|
-
}
|
|
552
|
-
count++
|
|
553
|
-
|
|
554
|
-
if (results.length >= limit) break
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
return {
|
|
559
|
-
data: results,
|
|
560
|
-
total: count,
|
|
561
|
-
page,
|
|
562
|
-
limit,
|
|
563
|
-
hasMore: count > skip + limit
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Helper function for criteria matching
|
|
568
|
-
function matchesCriteria(record, criteria) {
|
|
569
|
-
for (const [key, value] of Object.entries(criteria)) {
|
|
570
|
-
if (typeof value === 'object' && value !== null) {
|
|
571
|
-
// Handle operators like { '>': 10 }
|
|
572
|
-
for (const [op, opValue] of Object.entries(value)) {
|
|
573
|
-
if (!evaluateOperator(record[key], op, opValue)) {
|
|
574
|
-
return false
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
} else if (record[key] !== value) {
|
|
578
|
-
return false
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
return true
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function evaluateOperator(recordValue, operator, criteriaValue) {
|
|
585
|
-
switch (operator) {
|
|
586
|
-
case '>': return recordValue > criteriaValue
|
|
587
|
-
case '>=': return recordValue >= criteriaValue
|
|
588
|
-
case '<': return recordValue < criteriaValue
|
|
589
|
-
case '<=': return recordValue <= criteriaValue
|
|
590
|
-
case '!=': return recordValue !== criteriaValue
|
|
591
|
-
case '$in': return criteriaValue.includes(recordValue)
|
|
592
|
-
default: return recordValue === criteriaValue
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Example usage
|
|
597
|
-
const largeDataset = Array.from({ length: 100000 }, (_, i) => ({
|
|
598
|
-
id: i + 1,
|
|
599
|
-
type: ['A', 'B', 'C'][i % 3],
|
|
600
|
-
status: ['active', 'inactive', 'pending'][i % 3],
|
|
601
|
-
tags: [`tag${i % 10}`, `category${i % 5}`],
|
|
602
|
-
createdAt: Date.now() - (i * 1000)
|
|
603
|
-
}))
|
|
604
|
-
|
|
605
|
-
await bulkInsert(largeDataset)
|
|
606
|
-
await bulkUpdateStatus('pending', 'active')
|
|
607
|
-
|
|
608
|
-
const page1 = await getPaginatedResults({ type: 'A' }, 1, 50)
|
|
609
|
-
console.log(`Page 1: ${page1.data.length} results, ${page1.total} total`)
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
## Error Handling Examples
|
|
613
|
-
|
|
614
|
-
### Robust Error Handling
|
|
615
|
-
|
|
616
|
-
```javascript
|
|
617
|
-
import { Database } from 'jexidb'
|
|
618
|
-
|
|
619
|
-
async function robustDatabaseOperations() {
|
|
620
|
-
const db = new Database('robust-db.jdb', {
|
|
621
|
-
fields: { // REQUIRED - Define schema
|
|
622
|
-
id: 'number',
|
|
623
|
-
name: 'string'
|
|
624
|
-
},
|
|
625
|
-
indexes: { // OPTIONAL - Only fields you query frequently
|
|
626
|
-
name: 'string' // ✅ Search by name
|
|
627
|
-
}
|
|
628
|
-
})
|
|
629
|
-
|
|
630
|
-
try {
|
|
631
|
-
await db.init()
|
|
632
|
-
|
|
633
|
-
// Insert with validation
|
|
634
|
-
try {
|
|
635
|
-
await db.insert({ id: 1, name: 'Test' })
|
|
636
|
-
await db.save()
|
|
637
|
-
console.log('Insert successful')
|
|
638
|
-
} catch (insertError) {
|
|
639
|
-
console.error('Insert failed:', insertError.message)
|
|
640
|
-
// Handle insert error (e.g., duplicate ID)
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Query with fallback
|
|
644
|
-
try {
|
|
645
|
-
const results = await db.find({ name: 'Test' })
|
|
646
|
-
console.log('Query successful:', results.length, 'results')
|
|
647
|
-
} catch (queryError) {
|
|
648
|
-
console.error('Query failed:', queryError.message)
|
|
649
|
-
// Return empty results or cached data
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
} catch (initError) {
|
|
653
|
-
console.error('Database initialization failed:', initError.message)
|
|
654
|
-
// Handle initialization error
|
|
655
|
-
} finally {
|
|
656
|
-
try {
|
|
657
|
-
await db.destroy()
|
|
658
|
-
} catch (destroyError) {
|
|
659
|
-
console.error('Database cleanup failed:', destroyError.message)
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// Retry mechanism
|
|
665
|
-
async function retryOperation(operation, maxRetries = 3) {
|
|
666
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
667
|
-
try {
|
|
668
|
-
return await operation()
|
|
669
|
-
} catch (error) {
|
|
670
|
-
console.log(`Attempt ${attempt} failed:`, error.message)
|
|
671
|
-
|
|
672
|
-
if (attempt === maxRetries) {
|
|
673
|
-
throw new Error(`Operation failed after ${maxRetries} attempts: ${error.message}`)
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// Wait before retry (exponential backoff)
|
|
677
|
-
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000))
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Example usage with retry
|
|
683
|
-
const db = new Database('retry-db.jdb', {
|
|
684
|
-
fields: { // REQUIRED - Define schema
|
|
685
|
-
id: 'number',
|
|
686
|
-
data: 'string'
|
|
687
|
-
}
|
|
688
|
-
})
|
|
689
|
-
await db.init()
|
|
690
|
-
|
|
691
|
-
try {
|
|
692
|
-
await retryOperation(async () => {
|
|
693
|
-
await db.insert({ id: Date.now(), data: 'important' })
|
|
694
|
-
await db.save()
|
|
695
|
-
})
|
|
696
|
-
} catch (error) {
|
|
697
|
-
console.error('All retry attempts failed:', error.message)
|
|
698
|
-
}
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
These examples demonstrate various real-world use cases for JexiDB, from simple todo lists to complex analytics systems. Each example includes error handling and performance considerations.
|