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.
- package/LICENSE +21 -0
- package/README.md +1227 -0
- package/liekodb.js +2014 -0
- 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.
|