liekodb 0.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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1227 -0
  3. package/liekodb.js +2014 -0
  4. package/package.json +29 -0
package/README.md ADDED
@@ -0,0 +1,1227 @@
1
+ # LiekoDB Documentation
2
+
3
+ ## Table of Contents
4
+ 1. [Introduction](#introduction)
5
+ 2. [Installation](#installation)
6
+ 3. [Quick Start](#quick-start)
7
+ 4. [Core Concepts](#core-concepts)
8
+ 5. [Collection Methods](#collection-methods)
9
+ 6. [Query Filters](#query-filters)
10
+ 7. [Examples](#examples)
11
+ 8. [Advanced Usage](#advanced-usage)
12
+ 9. [API Reference](#api-reference)
13
+ 10. [Best Practices](#best-practices)
14
+
15
+ ## Introduction
16
+
17
+ LiekoDB is a lightweight, file-based database for Node.js with an optional HTTP adapter for browser and remote usage. It provides MongoDB-like query syntax with local file persistence and automatic caching.
18
+
19
+ ### Key Features
20
+ - 🚀 **Zero-dependency** for local usage
21
+ - 📁 **File-based persistence** with automatic caching
22
+ - 🔄 **MongoDB-like query syntax**
23
+ - 🌐 **HTTP adapter** for browser/remote usage
24
+ - ⚡ **Auto-save** with configurable intervals
25
+ - 🔒 **Collection-level locking** for concurrency
26
+ - 📊 **Built-in logging and metrics**
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install liekodb
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### Basic Setup
37
+
38
+ ```javascript
39
+ // database.js
40
+ const LiekoDB = require('liekodb');
41
+
42
+ // Create a database instance
43
+ const db = new LiekoDB({
44
+ debug: true, // Enable debug logging
45
+ autoSaveInterval: 10000 // Auto-save every 10 seconds
46
+ });
47
+
48
+ // Export for use in other files
49
+ module.exports = { db };
50
+ ```
51
+
52
+ ### Your First Collection
53
+
54
+ ```javascript
55
+ // userService.js
56
+ const { db } = require('./database');
57
+
58
+ class UserService {
59
+ constructor() {
60
+ this.collection = db.collection('users');
61
+ }
62
+
63
+ async createUser(userData) {
64
+ const { error, data } = await this.collection.insert({
65
+ name: userData.name,
66
+ email: userData.email,
67
+ age: userData.age,
68
+ createdAt: new Date().toISOString()
69
+ });
70
+
71
+ if (error) throw new Error(error.message);
72
+ return data;
73
+ }
74
+
75
+ async findUserByEmail(email) {
76
+ const { error, data } = await this.collection.findOne({ email });
77
+ if (error) throw new Error(error.message);
78
+ return data;
79
+ }
80
+ }
81
+
82
+ module.exports = new UserService();
83
+ ```
84
+
85
+ ## Core Concepts
86
+
87
+ ### 1. Database Instance
88
+
89
+ Create a database instance with optional configuration:
90
+
91
+ ```javascript
92
+ const db = new LiekoDB({
93
+ debug: true, // Enable console logging
94
+ autoSaveInterval: 5000, // Auto-save interval in ms (0 to disable)
95
+ storagePath: './mydata', // Custom storage directory
96
+ token: 'your-token-here', // For HTTP adapter (browser/remote)
97
+ databaseUrl: 'http://api.example.com' // For remote database
98
+ });
99
+ ```
100
+
101
+ ### 2. Collections
102
+
103
+ Collections are similar to tables in SQL or collections in MongoDB:
104
+
105
+ ```javascript
106
+ // Get or create a collection
107
+ const users = db.collection('users');
108
+ const products = db.collection('products');
109
+ const orders = db.collection('orders');
110
+ ```
111
+
112
+ ### 3. Documents
113
+
114
+ Documents are JSON objects stored in collections:
115
+
116
+ ```javascript
117
+ const userDocument = {
118
+ id: 'abc123', // Unique identifier (auto-generated if not provided)
119
+ name: 'John Doe',
120
+ email: 'john@example.com',
121
+ age: 30,
122
+ tags: ['admin', 'premium'],
123
+ settings: {
124
+ notifications: true,
125
+ theme: 'dark'
126
+ },
127
+ createdAt: '2024-01-15T10:30:00Z',
128
+ updatedAt: '2024-01-15T10:30:00Z'
129
+ };
130
+ ```
131
+
132
+ ## Collection Methods
133
+
134
+ ### CRUD Operations
135
+
136
+ #### 1. **Insert Documents**
137
+
138
+ ```javascript
139
+ // Insert a single document
140
+ const { error, data } = await collection.insert({
141
+ name: 'Alice',
142
+ email: 'alice@example.com',
143
+ age: 25
144
+ });
145
+
146
+ // Insert multiple documents
147
+ const { error, data } = await collection.insert([
148
+ { name: 'Bob', email: 'bob@example.com' },
149
+ { name: 'Charlie', email: 'charlie@example.com' }
150
+ ]);
151
+
152
+ // Result: data = {
153
+ // insertedCount: 2,
154
+ // updatedCount: 0,
155
+ // totalDocuments: 5,
156
+ // insertedIds: ['abc123', 'def456']
157
+ // }
158
+ ```
159
+
160
+ #### 2. **Find Documents**
161
+
162
+ ```javascript
163
+ // Find all documents
164
+ const { error, data } = await collection.find();
165
+
166
+ // Find with filters
167
+ const { error, data } = await collection.find({
168
+ age: { $gt: 18 }
169
+ });
170
+
171
+ // Find with pagination
172
+ const { error, data } = await collection.find({}, {
173
+ limit: 10,
174
+ skip: 20,
175
+ sort: { createdAt: -1 } // -1 = descending, 1 = ascending
176
+ });
177
+
178
+ // Find one document
179
+ const { error, data } = await collection.findOne({ email: 'alice@example.com' });
180
+
181
+ // Find by ID
182
+ const { error, data } = await collection.findById('document-id-here');
183
+ ```
184
+
185
+ #### 3. **Update Documents**
186
+
187
+ ```javascript
188
+ // Update by ID
189
+ const { error, data } = await collection.updateById('doc-id', {
190
+ name: 'Updated Name',
191
+ age: 31
192
+ });
193
+
194
+ // Update multiple documents with filters
195
+ const { error, data } = await collection.update(
196
+ { status: 'pending' }, // Filter
197
+ { status: 'completed' }, // Update
198
+ { returnType: 'count' } // Options
199
+ );
200
+
201
+ // Using update operators
202
+ const { error, data } = await collection.updateById('doc-id', {
203
+ $set: { status: 'active' },
204
+ $inc: { loginCount: 1 },
205
+ $push: { logs: 'User logged in' }
206
+ });
207
+ ```
208
+
209
+ #### 4. **Delete Documents**
210
+
211
+ ```javascript
212
+ // Delete by ID
213
+ const { error, data } = await collection.deleteById('doc-id');
214
+
215
+ // Delete with filters
216
+ const { error, data } = await collection.delete({
217
+ status: 'inactive',
218
+ lastLogin: { $lt: '2023-01-01' }
219
+ });
220
+
221
+ // Delete entire collection
222
+ const { error, data } = await collection.drop();
223
+ ```
224
+
225
+ #### 5. **Count Documents**
226
+
227
+ ```javascript
228
+ // Count all documents
229
+ const { error, data } = await collection.count();
230
+
231
+ // Count with filters
232
+ const { error, data } = await collection.count({
233
+ status: 'active',
234
+ age: { $gte: 18 }
235
+ });
236
+ ```
237
+
238
+ ### Query Options
239
+
240
+ ```javascript
241
+ const options = {
242
+ sort: { createdAt: -1, name: 1 }, // Sort by multiple fields
243
+ limit: 50, // Limit results
244
+ skip: 100, // Skip first 100 results
245
+ page: 3, // Page number (requires limit)
246
+ fields: { name: 1, email: 1 }, // Include only specific fields
247
+ returnType: 'documents', // 'count', 'ids', or 'documents'
248
+ maxReturn: 1000 // Maximum documents to return
249
+ };
250
+
251
+ const { error, data } = await collection.find({}, options);
252
+ ```
253
+
254
+ ## Query Filters
255
+
256
+ LiekoDB supports MongoDB-like query syntax:
257
+
258
+ ### Comparison Operators
259
+
260
+ ```javascript
261
+ // Equality
262
+ { age: 25 }
263
+ { name: 'John' }
264
+ { status: { $eq: 'active' } }
265
+
266
+ // Inequality
267
+ { age: { $ne: 25 } }
268
+ { status: { $ne: 'inactive' } }
269
+
270
+ // Greater than / Less than
271
+ { age: { $gt: 18 } } // Greater than
272
+ { age: { $gte: 21 } } // Greater than or equal
273
+ { age: { $lt: 65 } } // Less than
274
+ { age: { $lte: 100 } } // Less than or equal
275
+
276
+ // In / Not in
277
+ { role: { $in: ['admin', 'moderator'] } }
278
+ { status: { $nin: ['banned', 'suspended'] } }
279
+
280
+ // Exists
281
+ { email: { $exists: true } }
282
+ { middleName: { $exists: false } }
283
+
284
+ // Regular expressions
285
+ { email: { $regex: /@gmail\.com$/ } }
286
+ { name: { $regex: '^J', $options: 'i' } } // Case insensitive
287
+
288
+ // Modulo
289
+ { age: { $mod: [2, 0] } } // Even numbers
290
+ ```
291
+
292
+ ### Logical Operators
293
+
294
+ ```javascript
295
+ // AND (default)
296
+ { age: { $gt: 18 }, status: 'active' }
297
+
298
+ // OR
299
+ { $or: [{ status: 'active' }, { verified: true }] }
300
+
301
+ // AND with OR
302
+ {
303
+ $and: [
304
+ { age: { $gte: 18 } },
305
+ {
306
+ $or: [
307
+ { role: 'admin' },
308
+ { premium: true }
309
+ ]
310
+ }
311
+ ]
312
+ }
313
+
314
+ // NOT
315
+ { status: { $not: { $eq: 'banned' } } }
316
+ { age: { $not: { $lt: 18 } } }
317
+ ```
318
+
319
+ ### Array Queries
320
+
321
+ ```javascript
322
+ // Match array element
323
+ { tags: 'javascript' } // Array contains 'javascript'
324
+ { tags: { $in: ['javascript', 'nodejs'] } } // Array contains any of these
325
+
326
+ // Array field queries
327
+ { 'skills.level': { $gt: 3 } } // Nested array field
328
+ { 'comments.0.author': 'admin' } // First element of array
329
+ ```
330
+
331
+ ### Nested Field Queries
332
+
333
+ ```javascript
334
+ // Dot notation for nested objects
335
+ { 'address.city': 'New York' }
336
+ { 'settings.theme': 'dark' }
337
+ { 'metrics.visits.count': { $gt: 100 } }
338
+ ```
339
+
340
+ ## Examples
341
+
342
+ ### Example 1: Todo List Application
343
+
344
+ ```javascript
345
+ // todoService.js
346
+ const { db } = require('./database');
347
+
348
+ class TodoService {
349
+ constructor() {
350
+ this.collection = db.collection('todos');
351
+ }
352
+
353
+ async createTodo(userId, text) {
354
+ const { error, data } = await this.collection.insert({
355
+ userId,
356
+ text,
357
+ completed: false,
358
+ createdAt: new Date().toISOString(),
359
+ priority: 'medium'
360
+ });
361
+
362
+ if (error) throw new Error(error.message);
363
+ return data;
364
+ }
365
+
366
+ async getUserTodos(userId, options = {}) {
367
+ const { error, data } = await this.collection.find(
368
+ { userId },
369
+ {
370
+ sort: { createdAt: -1 },
371
+ ...options
372
+ }
373
+ );
374
+
375
+ if (error) throw new Error(error.message);
376
+ return data.foundDocuments;
377
+ }
378
+
379
+ async completeTodo(todoId) {
380
+ const { error, data } = await this.collection.updateById(todoId, {
381
+ completed: true,
382
+ completedAt: new Date().toISOString()
383
+ });
384
+
385
+ if (error) throw new Error(error.message);
386
+ return data;
387
+ }
388
+
389
+ async deleteCompletedTodos(userId) {
390
+ const { error, data } = await this.collection.delete({
391
+ userId,
392
+ completed: true
393
+ });
394
+
395
+ if (error) throw new Error(error.message);
396
+ return data.deletedCount;
397
+ }
398
+
399
+ async getTodoStats(userId) {
400
+ const total = await this.collection.count({ userId });
401
+ const completed = await this.collection.count({
402
+ userId,
403
+ completed: true
404
+ });
405
+ const pending = await this.collection.count({
406
+ userId,
407
+ completed: false
408
+ });
409
+
410
+ return { total, completed, pending };
411
+ }
412
+ }
413
+
414
+ module.exports = new TodoService();
415
+ ```
416
+
417
+ ### Example 2: Blog System
418
+
419
+ ```javascript
420
+ // blogService.js
421
+ const { db } = require('./database');
422
+
423
+ class BlogService {
424
+ constructor() {
425
+ this.posts = db.collection('posts');
426
+ this.comments = db.collection('comments');
427
+ }
428
+
429
+ async createPost(authorId, title, content, tags = []) {
430
+ const { error, data } = await this.posts.insert({
431
+ authorId,
432
+ title,
433
+ content,
434
+ tags,
435
+ status: 'published',
436
+ views: 0,
437
+ likes: 0,
438
+ createdAt: new Date().toISOString(),
439
+ updatedAt: new Date().toISOString()
440
+ });
441
+
442
+ if (error) throw new Error(error.message);
443
+ return data;
444
+ }
445
+
446
+ async getPosts(options = {}) {
447
+ const { error, data } = await this.posts.find(
448
+ { status: 'published' },
449
+ {
450
+ sort: { createdAt: -1 },
451
+ limit: options.limit || 20,
452
+ skip: options.skip || 0,
453
+ fields: {
454
+ title: 1,
455
+ excerpt: 1,
456
+ authorId: 1,
457
+ tags: 1,
458
+ createdAt: 1,
459
+ views: 1,
460
+ likes: 1
461
+ }
462
+ }
463
+ );
464
+
465
+ if (error) throw new Error(error.message);
466
+ return data;
467
+ }
468
+
469
+ async incrementViews(postId) {
470
+ const { error, data } = await this.posts.updateById(postId, {
471
+ $inc: { views: 1 }
472
+ });
473
+
474
+ if (error) throw new Error(error.message);
475
+ return data;
476
+ }
477
+
478
+ async addComment(postId, userId, text) {
479
+ const { error, data } = await this.comments.insert({
480
+ postId,
481
+ userId,
482
+ text,
483
+ createdAt: new Date().toISOString()
484
+ });
485
+
486
+ if (error) throw new Error(error.message);
487
+ return data;
488
+ }
489
+
490
+ async getPostComments(postId) {
491
+ const { error, data } = await this.comments.find(
492
+ { postId },
493
+ { sort: { createdAt: -1 } }
494
+ );
495
+
496
+ if (error) throw new Error(error.message);
497
+ return data.foundDocuments;
498
+ }
499
+
500
+ async searchPosts(query) {
501
+ const { error, data } = await this.posts.find({
502
+ $or: [
503
+ { title: { $regex: query, $options: 'i' } },
504
+ { content: { $regex: query, $options: 'i' } },
505
+ { tags: query }
506
+ ]
507
+ });
508
+
509
+ if (error) throw new Error(error.message);
510
+ return data.foundDocuments;
511
+ }
512
+ }
513
+
514
+ module.exports = new BlogService();
515
+ ```
516
+
517
+ ### Example 3: E-commerce Product Catalog
518
+
519
+ ```javascript
520
+ // productService.js
521
+ const { db } = require('./database');
522
+
523
+ class ProductService {
524
+ constructor() {
525
+ this.products = db.collection('products');
526
+ this.categories = db.collection('categories');
527
+ }
528
+
529
+ async addProduct(productData) {
530
+ const { error, data } = await this.products.insert({
531
+ ...productData,
532
+ sku: this.generateSKU(),
533
+ stock: productData.stock || 0,
534
+ price: parseFloat(productData.price),
535
+ rating: 0,
536
+ reviewCount: 0,
537
+ createdAt: new Date().toISOString(),
538
+ updatedAt: new Date().toISOString()
539
+ });
540
+
541
+ if (error) throw new Error(error.message);
542
+ return data;
543
+ }
544
+
545
+ async getProducts(filters = {}, options = {}) {
546
+ const query = { ...filters, active: true };
547
+
548
+ const { error, data } = await this.products.find(query, {
549
+ sort: options.sort || { createdAt: -1 },
550
+ limit: options.limit || 50,
551
+ skip: options.skip || 0,
552
+ ...options
553
+ });
554
+
555
+ if (error) throw new Error(error.message);
556
+ return data;
557
+ }
558
+
559
+ async updateStock(productId, quantity) {
560
+ const { error, data } = await this.products.updateById(productId, {
561
+ $inc: { stock: quantity }
562
+ });
563
+
564
+ if (error) throw new Error(error.message);
565
+ return data;
566
+ }
567
+
568
+ async addReview(productId, rating, review) {
569
+ // Update product rating (average calculation)
570
+ const product = await this.products.findById(productId);
571
+
572
+ const newRating = (
573
+ (product.rating * product.reviewCount + rating) /
574
+ (product.reviewCount + 1)
575
+ ).toFixed(1);
576
+
577
+ const { error, data } = await this.products.updateById(productId, {
578
+ $inc: { reviewCount: 1 },
579
+ $set: { rating: parseFloat(newRating) }
580
+ });
581
+
582
+ if (error) throw new Error(error.message);
583
+
584
+ // Store individual review
585
+ await db.collection('reviews').insert({
586
+ productId,
587
+ rating,
588
+ review,
589
+ createdAt: new Date().toISOString()
590
+ });
591
+
592
+ return data;
593
+ }
594
+
595
+ async getProductsByCategory(categoryId, options = {}) {
596
+ const { error, data } = await this.products.find(
597
+ { categoryId, active: true },
598
+ {
599
+ sort: { price: options.sortPrice ? 1 : -1 },
600
+ limit: options.limit || 20,
601
+ ...options
602
+ }
603
+ );
604
+
605
+ if (error) throw new Error(error.message);
606
+ return data.foundDocuments;
607
+ }
608
+
609
+ async searchProducts(searchTerm, filters = {}) {
610
+ const query = {
611
+ $and: [
612
+ {
613
+ $or: [
614
+ { name: { $regex: searchTerm, $options: 'i' } },
615
+ { description: { $regex: searchTerm, $options: 'i' } },
616
+ { tags: searchTerm }
617
+ ]
618
+ },
619
+ { ...filters, active: true }
620
+ ]
621
+ };
622
+
623
+ const { error, data } = await this.products.find(query);
624
+ if (error) throw new Error(error.message);
625
+ return data.foundDocuments;
626
+ }
627
+
628
+ generateSKU() {
629
+ return 'SKU-' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5).toUpperCase();
630
+ }
631
+ }
632
+
633
+ module.exports = new ProductService();
634
+ ```
635
+
636
+ ### Example 4: Real-time Chat Application
637
+
638
+ ```javascript
639
+ // chatService.js
640
+ const { db } = require('./database');
641
+
642
+ class ChatService {
643
+ constructor() {
644
+ this.messages = db.collection('messages');
645
+ this.rooms = db.collection('chat_rooms');
646
+ this.users = db.collection('chat_users');
647
+ }
648
+
649
+ async createRoom(name, creatorId, isPrivate = false) {
650
+ const { error, data } = await this.rooms.insert({
651
+ name,
652
+ creatorId,
653
+ isPrivate,
654
+ members: [creatorId],
655
+ createdAt: new Date().toISOString()
656
+ });
657
+
658
+ if (error) throw new Error(error.message);
659
+ return data;
660
+ }
661
+
662
+ async sendMessage(roomId, userId, text) {
663
+ const { error, data } = await this.messages.insert({
664
+ roomId,
665
+ userId,
666
+ text,
667
+ timestamp: new Date().toISOString(),
668
+ readBy: [userId]
669
+ });
670
+
671
+ if (error) throw new Error(error.message);
672
+ return data;
673
+ }
674
+
675
+ async getRoomMessages(roomId, options = {}) {
676
+ const { error, data } = await this.messages.find(
677
+ { roomId },
678
+ {
679
+ sort: { timestamp: -1 },
680
+ limit: options.limit || 50,
681
+ ...options
682
+ }
683
+ );
684
+
685
+ if (error) throw new Error(error.message);
686
+ return data.foundDocuments.reverse(); // Return oldest first
687
+ }
688
+
689
+ async markAsRead(messageId, userId) {
690
+ const { error, data } = await this.messages.updateById(messageId, {
691
+ $addToSet: { readBy: userId }
692
+ });
693
+
694
+ if (error) throw new Error(error.message);
695
+ return data;
696
+ }
697
+
698
+ async getUnreadCount(roomId, userId) {
699
+ const { error, data } = await this.messages.count({
700
+ roomId,
701
+ readBy: { $nin: [userId] }
702
+ });
703
+
704
+ if (error) throw new Error(error.message);
705
+ return data;
706
+ }
707
+
708
+ async getUserRooms(userId) {
709
+ const { error, data } = await this.rooms.find({
710
+ members: userId
711
+ });
712
+
713
+ if (error) throw new Error(error.message);
714
+ return data.foundDocuments;
715
+ }
716
+
717
+ async addMemberToRoom(roomId, userId) {
718
+ const { error, data } = await this.rooms.updateById(roomId, {
719
+ $addToSet: { members: userId }
720
+ });
721
+
722
+ if (error) throw new Error(error.message);
723
+ return data;
724
+ }
725
+
726
+ async searchMessages(roomId, searchTerm) {
727
+ const { error, data } = await this.messages.find({
728
+ roomId,
729
+ text: { $regex: searchTerm, $options: 'i' }
730
+ });
731
+
732
+ if (error) throw new Error(error.message);
733
+ return data.foundDocuments;
734
+ }
735
+ }
736
+
737
+ module.exports = new ChatService();
738
+ ```
739
+
740
+ ### Example 5: Analytics Tracking System
741
+
742
+ ```javascript
743
+ // analyticsService.js
744
+ const { db } = require('./database');
745
+
746
+ class AnalyticsService {
747
+ constructor() {
748
+ this.events = db.collection('analytics_events');
749
+ this.sessions = db.collection('user_sessions');
750
+ }
751
+
752
+ async trackEvent(userId, eventType, properties = {}) {
753
+ const { error, data } = await this.events.insert({
754
+ userId: userId || 'anonymous',
755
+ eventType,
756
+ properties,
757
+ timestamp: new Date().toISOString(),
758
+ userAgent: properties.userAgent,
759
+ ip: properties.ip,
760
+ path: properties.path
761
+ });
762
+
763
+ if (error) throw new Error(error.message);
764
+ return data;
765
+ }
766
+
767
+ async startSession(userId, userAgent, ip) {
768
+ const sessionId = require('crypto').randomBytes(16).toString('hex');
769
+
770
+ const { error, data } = await this.sessions.insert({
771
+ sessionId,
772
+ userId,
773
+ userAgent,
774
+ ip,
775
+ startTime: new Date().toISOString(),
776
+ lastActivity: new Date().toISOString(),
777
+ events: []
778
+ });
779
+
780
+ if (error) throw new Error(error.message);
781
+ return sessionId;
782
+ }
783
+
784
+ async updateSessionActivity(sessionId) {
785
+ const { error, data } = await this.sessions.update(
786
+ { sessionId },
787
+ { lastActivity: new Date().toISOString() }
788
+ );
789
+
790
+ if (error) throw new Error(error.message);
791
+ return data;
792
+ }
793
+
794
+ async getEventStats(eventType, startDate, endDate) {
795
+ const { error, data } = await this.events.count({
796
+ eventType,
797
+ timestamp: {
798
+ $gte: startDate,
799
+ $lte: endDate
800
+ }
801
+ });
802
+
803
+ if (error) throw new Error(error.message);
804
+ return data;
805
+ }
806
+
807
+ async getUserActivity(userId, days = 7) {
808
+ const startDate = new Date();
809
+ startDate.setDate(startDate.getDate() - days);
810
+
811
+ const { error, data } = await this.events.find({
812
+ userId,
813
+ timestamp: { $gte: startDate.toISOString() }
814
+ }, {
815
+ sort: { timestamp: -1 },
816
+ fields: {
817
+ eventType: 1,
818
+ timestamp: 1,
819
+ 'properties.path': 1
820
+ }
821
+ });
822
+
823
+ if (error) throw new Error(error.message);
824
+ return data.foundDocuments;
825
+ }
826
+
827
+ async getPopularPages(limit = 10) {
828
+ // Group by path using multiple queries (simplified)
829
+ const allEvents = await this.events.find({}, {
830
+ fields: { 'properties.path': 1 },
831
+ limit: 10000
832
+ });
833
+
834
+ const pathCounts = {};
835
+ allEvents.foundDocuments.forEach(event => {
836
+ const path = event.properties?.path;
837
+ if (path) {
838
+ pathCounts[path] = (pathCounts[path] || 0) + 1;
839
+ }
840
+ });
841
+
842
+ return Object.entries(pathCounts)
843
+ .sort(([,a], [,b]) => b - a)
844
+ .slice(0, limit)
845
+ .map(([path, count]) => ({ path, count }));
846
+ }
847
+
848
+ async cleanupOldData(daysToKeep = 90) {
849
+ const cutoffDate = new Date();
850
+ cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
851
+
852
+ // Delete old events
853
+ const eventsResult = await this.events.delete({
854
+ timestamp: { $lt: cutoffDate.toISOString() }
855
+ });
856
+
857
+ // Delete old sessions
858
+ const sessionsResult = await this.sessions.delete({
859
+ lastActivity: { $lt: cutoffDate.toISOString() }
860
+ });
861
+
862
+ return {
863
+ deletedEvents: eventsResult.deletedCount || 0,
864
+ deletedSessions: sessionsResult.deletedCount || 0
865
+ };
866
+ }
867
+ }
868
+
869
+ module.exports = new AnalyticsService();
870
+ ```
871
+
872
+ ## Advanced Usage
873
+
874
+ ### Database Management
875
+
876
+ ```javascript
877
+ // Get database status
878
+ const status = await db.status();
879
+ console.log(status);
880
+ // {
881
+ // storagePath: './storage',
882
+ // collections: [...],
883
+ // totalCollections: 5,
884
+ // totalDocuments: 1234,
885
+ // totalCollectionsSize: 1024576,
886
+ // totalCollectionsSizeFormatted: '1.02 MB'
887
+ // }
888
+
889
+ // List all collections
890
+ const collections = await db.listCollections();
891
+ collections.forEach(col => {
892
+ console.log(`${col.name}: ${col.totalDocuments} docs (${col.sizeFormatted})`);
893
+ });
894
+
895
+ // Drop a collection
896
+ await db.dropCollection('old_data');
897
+
898
+ // Close database (flushes all pending writes)
899
+ await db.close();
900
+ ```
901
+
902
+ ### HTTP Adapter (Browser/Remote)
903
+
904
+ ```javascript
905
+ // Browser usage
906
+ const db = new LiekoDB({
907
+ token: 'your-api-token',
908
+ databaseUrl: 'https://api.yourservice.com'
909
+ });
910
+
911
+ // The API is the same as local usage
912
+ const users = db.collection('users');
913
+ const { data } = await users.find({ active: true });
914
+ ```
915
+
916
+ ### Custom Storage Path
917
+
918
+ ```javascript
919
+ const db = new LiekoDB({
920
+ storagePath: './myapp/data',
921
+ autoSaveInterval: 30000 // Save every 30 seconds
922
+ });
923
+
924
+ // Or set via environment variable
925
+ const db = new LiekoDB({
926
+ storagePath: process.env.DB_PATH || './storage'
927
+ });
928
+ ```
929
+
930
+ ### Error Handling
931
+
932
+ ```javascript
933
+ async function safeDatabaseOperation() {
934
+ try {
935
+ const { error, data } = await collection.insert(document);
936
+
937
+ if (error) {
938
+ // Handle specific error types
939
+ if (error.code === 404) {
940
+ console.error('Collection not found');
941
+ } else if (error.message.includes('validation')) {
942
+ console.error('Validation error:', error.message);
943
+ } else {
944
+ console.error('Database error:', error);
945
+ }
946
+ return null;
947
+ }
948
+
949
+ return data;
950
+ } catch (err) {
951
+ console.error('Unexpected error:', err);
952
+ throw err;
953
+ }
954
+ }
955
+ ```
956
+
957
+ ### Performance Optimization
958
+
959
+ ```javascript
960
+ const db = new LiekoDB({
961
+ autoSaveInterval: 30000, // Longer interval for write-heavy apps
962
+ debug: false // Disable debug in production
963
+ });
964
+
965
+ // Use projection to reduce data transfer
966
+ const { data } = await collection.find({}, {
967
+ fields: { name: 1, email: 1 }, // Only get these fields
968
+ limit: 100
969
+ });
970
+
971
+ // Use count instead of find when only need count
972
+ const { data: count } = await collection.count(filters);
973
+
974
+ // Use pagination for large datasets
975
+ async function getAllPaginated(collection, batchSize = 1000) {
976
+ let allDocuments = [];
977
+ let skip = 0;
978
+ let hasMore = true;
979
+
980
+ while (hasMore) {
981
+ const { data } = await collection.find({}, {
982
+ limit: batchSize,
983
+ skip: skip
984
+ });
985
+
986
+ if (data.foundDocuments.length === 0) {
987
+ hasMore = false;
988
+ } else {
989
+ allDocuments = allDocuments.concat(data.foundDocuments);
990
+ skip += batchSize;
991
+ }
992
+ }
993
+
994
+ return allDocuments;
995
+ }
996
+ ```
997
+
998
+ ## API Reference
999
+
1000
+ ### LiekoDB Constructor
1001
+
1002
+ ```javascript
1003
+ new LiekoDB(options)
1004
+ ```
1005
+
1006
+ **Options:**
1007
+ - `debug` (boolean): Enable debug logging (default: false)
1008
+ - `autoSaveInterval` (number): Auto-save interval in ms (default: 5000)
1009
+ - `storagePath` (string): Path for file storage (default: './storage')
1010
+ - `token` (string): Authentication token for HTTP adapter
1011
+ - `databaseUrl` (string): URL for remote database
1012
+ - `poolSize` (number): HTTP connection pool size (default: 10)
1013
+ - `maxRetries` (number): HTTP retry attempts (default: 3)
1014
+ - `timeout` (number): HTTP timeout in ms (default: 15000)
1015
+
1016
+ ### Collection Methods
1017
+
1018
+ All methods return a Promise resolving to `{ error?, data? }`
1019
+
1020
+ #### `insert(documents)`
1021
+ Insert one or multiple documents.
1022
+
1023
+ #### `find(filters?, options?)`
1024
+ Find documents matching filters.
1025
+
1026
+ #### `findOne(filters?, options?)`
1027
+ Find a single document.
1028
+
1029
+ #### `findById(id, options?)`
1030
+ Find document by ID.
1031
+
1032
+ #### `update(filters, update, options?)`
1033
+ Update multiple documents.
1034
+
1035
+ #### `updateById(id, update, options?)`
1036
+ Update document by ID.
1037
+
1038
+ #### `delete(filters)`
1039
+ Delete documents matching filters.
1040
+
1041
+ #### `deleteById(id)`
1042
+ Delete document by ID.
1043
+
1044
+ #### `count(filters?)`
1045
+ Count documents matching filters.
1046
+
1047
+ #### `drop()`
1048
+ Delete entire collection.
1049
+
1050
+ ### Query Operators
1051
+
1052
+ #### Comparison
1053
+ - `$eq` - Equal
1054
+ - `$ne` - Not equal
1055
+ - `$gt` - Greater than
1056
+ - `$gte` - Greater than or equal
1057
+ - `$lt` - Less than
1058
+ - `$lte` - Less than or equal
1059
+ - `$in` - In array
1060
+ - `$nin` - Not in array
1061
+ - `$exists` - Field exists
1062
+ - `$regex` - Regular expression
1063
+ - `$mod` - Modulo operation
1064
+
1065
+ #### Logical
1066
+ - `$and` - Logical AND
1067
+ - `$or` - Logical OR
1068
+ - `$not` - Logical NOT
1069
+ - `$nor` - Logical NOR
1070
+
1071
+ ### Update Operators
1072
+
1073
+ - `$set` - Set field value
1074
+ - `$unset` - Remove field
1075
+ - `$inc` - Increment field
1076
+ - `$push` - Append to array
1077
+ - `$pull` - Remove from array
1078
+ - `$addToSet` - Add to array if not exists
1079
+
1080
+ ## Best Practices
1081
+
1082
+ ### 1. **Always Handle Errors**
1083
+
1084
+ ```javascript
1085
+ async function safeOperation() {
1086
+ const { error, data } = await collection.operation();
1087
+ if (error) {
1088
+ // Handle appropriately
1089
+ throw new Error(`Database error: ${error.message}`);
1090
+ }
1091
+ return data;
1092
+ }
1093
+ ```
1094
+
1095
+ ### 2. **Use Indexes Wisely**
1096
+
1097
+ While LiekoDB doesn't have traditional indexes, you can:
1098
+ - Keep collections small and focused
1099
+ - Use meaningful IDs for quick lookups
1100
+ - Consider splitting large collections
1101
+
1102
+ ### 3. **Implement Data Validation**
1103
+
1104
+ ```javascript
1105
+ class UserService {
1106
+ async createUser(userData) {
1107
+ // Validate before inserting
1108
+ if (!userData.email || !userData.email.includes('@')) {
1109
+ throw new Error('Invalid email');
1110
+ }
1111
+
1112
+ const { error, data } = await this.collection.insert({
1113
+ ...userData,
1114
+ createdAt: new Date().toISOString(),
1115
+ updatedAt: new Date().toISOString()
1116
+ });
1117
+
1118
+ return data;
1119
+ }
1120
+ }
1121
+ ```
1122
+
1123
+ ### 4. **Optimize for Your Use Case**
1124
+
1125
+ ```javascript
1126
+ // For read-heavy: Larger auto-save interval
1127
+ const readHeavyDB = new LiekoDB({
1128
+ autoSaveInterval: 60000 // 1 minute
1129
+ });
1130
+
1131
+ // For write-heavy: Smaller auto-save interval
1132
+ const writeHeavyDB = new LiekoDB({
1133
+ autoSaveInterval: 1000 // 1 second
1134
+ });
1135
+ ```
1136
+
1137
+ ### 5. **Backup Strategy**
1138
+
1139
+ ```javascript
1140
+ // Simple backup function
1141
+ async function backupDatabase(sourcePath, backupPath) {
1142
+ const fs = require('fs').promises;
1143
+
1144
+ try {
1145
+ const files = await fs.readdir(sourcePath);
1146
+
1147
+ for (const file of files) {
1148
+ if (file.endsWith('.json')) {
1149
+ const source = `${sourcePath}/${file}`;
1150
+ const backup = `${backupPath}/${file}.${Date.now()}.bak`;
1151
+ await fs.copyFile(source, backup);
1152
+ }
1153
+ }
1154
+
1155
+ console.log('Backup completed successfully');
1156
+ } catch (error) {
1157
+ console.error('Backup failed:', error);
1158
+ }
1159
+ }
1160
+ ```
1161
+
1162
+ ### 6. **Monitor Performance**
1163
+
1164
+ ```javascript
1165
+ // Add performance logging
1166
+ const db = new LiekoDB({
1167
+ debug: process.env.NODE_ENV === 'development'
1168
+ });
1169
+
1170
+ // Log slow queries
1171
+ const start = Date.now();
1172
+ const { data } = await collection.find(complexQuery);
1173
+ const duration = Date.now() - start;
1174
+
1175
+ if (duration > 1000) { // 1 second
1176
+ console.warn(`Slow query: ${duration}ms`);
1177
+ }
1178
+ ```
1179
+
1180
+ ## Troubleshooting
1181
+
1182
+ ### Common Issues
1183
+
1184
+ 1. **"Collection name contains invalid characters"**
1185
+ - Use only alphanumeric characters, underscores, and hyphens
1186
+ - Cannot start with numbers
1187
+
1188
+ 2. **Data not persisting**
1189
+ - Check `autoSaveInterval` setting
1190
+ - Call `db.close()` before exiting
1191
+ - Check file permissions
1192
+
1193
+ 3. **Slow queries**
1194
+ - Use projection to limit returned fields
1195
+ - Implement pagination
1196
+ - Consider splitting large collections
1197
+
1198
+ 4. **Memory usage**
1199
+ - Monitor cache size for large collections
1200
+ - Use `limit` in queries
1201
+ - Consider disabling auto-save for read-only operations
1202
+
1203
+ ### Debug Mode
1204
+
1205
+ Enable debug mode to see detailed logs:
1206
+
1207
+ ```javascript
1208
+ const db = new LiekoDB({ debug: true });
1209
+ ```
1210
+
1211
+ This will log:
1212
+ - All database operations
1213
+ - Execution times
1214
+ - Response sizes
1215
+ - Errors and warnings
1216
+
1217
+ ## Conclusion
1218
+
1219
+ LiekoDB provides a simple yet powerful solution for local data persistence in Node.js applications. With its MongoDB-like API, automatic persistence, and flexible adapters, it's suitable for a wide range of applications from small projects to production systems.
1220
+
1221
+ Remember to:
1222
+ - Always handle errors properly
1223
+ - Implement appropriate data validation
1224
+ - Choose the right adapter for your environment
1225
+ - Monitor performance and implement backups
1226
+
1227
+ For more examples and advanced usage, check the source code and experiment with different configurations to find what works best for your specific use case.