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
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.