@pixagram/lacerta-db 0.0.3 → 0.2.0
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/browser.js +24 -3
- package/dist/browser.min.js +8 -1
- package/dist/index.min.js +8 -1
- package/index.js +2087 -1711
- package/package.json +3 -2
- package/readme.md +132 -923
package/readme.md
CHANGED
|
@@ -1,998 +1,207 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
## Table of Contents
|
|
5
|
-
1. [Installation & Prerequisites](#installation--prerequisites)
|
|
6
|
-
2. [Architecture Overview](#architecture-overview)
|
|
7
|
-
3. [API Reference](#api-reference)
|
|
8
|
-
4. [Data Schemas](#data-schemas)
|
|
9
|
-
5. [Error Handling](#error-handling)
|
|
10
|
-
6. [Edge Cases & Behaviors](#edge-cases--behaviors)
|
|
11
|
-
7. [Performance Optimization](#performance-optimization)
|
|
12
|
-
8. [Security Model](#security-model)
|
|
13
|
-
9. [Code Examples](#code-examples)
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Installation & Prerequisites
|
|
18
|
-
|
|
19
|
-
### Critical Dependencies
|
|
20
|
-
```javascript
|
|
21
|
-
// REQUIRED: JOYSON library for binary serialization
|
|
22
|
-
npm install joyson
|
|
23
|
-
// OR include via CDN
|
|
24
|
-
<script src="https://cdn.jsdelivr.net/npm/joyson/dist/joyson.min.js"></script>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Browser Requirements
|
|
28
|
-
| API | Minimum Version | Required |
|
|
29
|
-
|-----|-----------------|----------|
|
|
30
|
-
| IndexedDB | Chrome 24+ | ✅ |
|
|
31
|
-
| localStorage | All modern | ✅ |
|
|
32
|
-
| Web Crypto API | Chrome 37+ | ✅ |
|
|
33
|
-
| CompressionStream | Chrome 86+ | ✅ |
|
|
34
|
-
| Origin Private File System | Chrome 86+ | ✅ |
|
|
35
|
-
|
|
36
|
-
### Module Import
|
|
37
|
-
```javascript
|
|
38
|
-
import { Database, Document, Collection } from './lacertadb.js';
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Architecture Overview
|
|
44
|
-
|
|
45
|
-
### Storage Hierarchy
|
|
46
|
-
```
|
|
47
|
-
┌─────────────────────────────────────────┐
|
|
48
|
-
│ Application Layer │
|
|
49
|
-
└─────────────┬───────────────────────────┘
|
|
50
|
-
│
|
|
51
|
-
┌─────────────▼───────────────────────────┐
|
|
52
|
-
│ Database Instance │
|
|
53
|
-
│ • Collections Management │
|
|
54
|
-
│ • Global Settings │
|
|
55
|
-
│ • Metadata Coordination │
|
|
56
|
-
└─────────────┬───────────────────────────┘
|
|
57
|
-
│
|
|
58
|
-
┌─────────────▼───────────────────────────┐
|
|
59
|
-
│ Storage Layers │
|
|
60
|
-
├─────────────────────────────────────────┤
|
|
61
|
-
│ IndexedDB │ Primary document storage │
|
|
62
|
-
│ localStorage │ Metadata & settings │
|
|
63
|
-
│ OPFS │ Binary attachments │
|
|
64
|
-
│ QuickStore │ Sync localStorage cache │
|
|
65
|
-
└─────────────────────────────────────────┘
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Transaction Flow
|
|
69
|
-
```
|
|
70
|
-
User Request → TransactionManager → Queue → IndexedDB
|
|
71
|
-
↓
|
|
72
|
-
Metadata Update
|
|
73
|
-
↓
|
|
74
|
-
Observer Events
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## API Reference
|
|
80
|
-
|
|
81
|
-
### Database Class
|
|
82
|
-
|
|
83
|
-
#### Constructor
|
|
84
|
-
```javascript
|
|
85
|
-
new Database(dbName: string, settings?: DatabaseSettings)
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Parameters:**
|
|
89
|
-
- `dbName` (string): Unique database identifier
|
|
90
|
-
- `settings` (object, optional): Configuration object
|
|
91
|
-
|
|
92
|
-
**Settings Schema:**
|
|
93
|
-
```typescript
|
|
94
|
-
interface DatabaseSettings {
|
|
95
|
-
sizeLimitKB?: number; // Max size before cleanup (default: Infinity)
|
|
96
|
-
bufferLimitKB?: number; // Buffer before trigger (default: -20% of sizeLimitKB)
|
|
97
|
-
freeSpaceEvery?: number; // Ms between cleanups (default: 10000, min: 1000)
|
|
98
|
-
}
|
|
99
|
-
```
|
|
1
|
+
LacertaDB (@pixagram/lacerta-db)
|
|
2
|
+
LacertaDB is a Javascript IndexedDB Database for Web Browsers. Simple, Fast, Secure.
|
|
100
3
|
|
|
101
|
-
|
|
4
|
+
LacertaDB offers a powerful, modern, and feature-rich interface for working with IndexedDB in the browser. It's designed as a document-oriented database, providing a developer-friendly API similar to MongoDB. It prioritizes security, performance, and advanced data handling capabilities right out of the box.
|
|
102
5
|
|
|
103
|
-
|
|
104
|
-
|
|
6
|
+
✨ Key Features
|
|
7
|
+
Simple API: An intuitive and modern API that makes database operations straightforward.
|
|
105
8
|
|
|
106
|
-
|
|
9
|
+
Strong Encryption: Built-in AES-GCM encryption to secure sensitive documents with a password.
|
|
107
10
|
|
|
108
|
-
|
|
109
|
-
```javascript
|
|
110
|
-
const db = new Database('myapp');
|
|
111
|
-
await db.init();
|
|
112
|
-
```
|
|
11
|
+
Efficient Compression: Automatic data compression using the Compression Streams API to save storage space.
|
|
113
12
|
|
|
114
|
-
|
|
13
|
+
OPFS Attachments: Store large files like images or videos efficiently using the Origin Private File System (OPFS), linking them directly to your documents.
|
|
115
14
|
|
|
116
|
-
|
|
117
|
-
Creates a new collection or returns existing one.
|
|
15
|
+
Advanced Queries: A powerful MongoDB-like query engine with support for complex operators ($gt, $in, $regex, etc.) and logical combinations ($and, $or).
|
|
118
16
|
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
{
|
|
122
|
-
success: boolean,
|
|
123
|
-
data: Collection | null,
|
|
124
|
-
error: LacertaDBError | null
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
**Behavior Matrix:**
|
|
129
|
-
| Scenario | `success` | `data` | `error` |
|
|
130
|
-
|----------|-----------|---------|----------|
|
|
131
|
-
| New collection created | `true` | Collection instance | `null` |
|
|
132
|
-
| Collection already exists | `false` | Existing Collection | `COLLECTION_EXISTS` error |
|
|
133
|
-
|
|
134
|
-
**Example:**
|
|
135
|
-
```javascript
|
|
136
|
-
const result = await db.createCollection('users');
|
|
137
|
-
if (result.success) {
|
|
138
|
-
const collection = result.data;
|
|
139
|
-
} else {
|
|
140
|
-
console.log('Collection exists:', result.data);
|
|
141
|
-
}
|
|
142
|
-
```
|
|
17
|
+
Aggregation Pipeline: Perform complex data analysis and transformations directly in the database with a multi-stage aggregation pipeline ($match, $group, $sort, etc.).
|
|
143
18
|
|
|
144
|
-
|
|
19
|
+
High-Performance Serialization: Uses turboserial and turbobase64 instead of JSON for faster serialization and deserialization of data.
|
|
145
20
|
|
|
146
|
-
|
|
147
|
-
Retrieves a collection by name.
|
|
21
|
+
Automatic Cleanup: Set storage limits and let the database automatically manage space by removing the oldest, non-permanent documents.
|
|
148
22
|
|
|
149
|
-
|
|
23
|
+
Full-Featured: Includes batch operations, query caching, metadata management, and a migration system to handle schema changes over time.
|
|
150
24
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|----------|-----------|---------|----------|
|
|
154
|
-
| Collection exists in memory | `true` | Collection instance | `null` |
|
|
155
|
-
| Collection exists in DB only | `true` | New Collection instance | `null` |
|
|
156
|
-
| Collection doesn't exist | `false` | `null` | `COLLECTION_NOT_FOUND` error |
|
|
25
|
+
🚀 Installation
|
|
26
|
+
Install the package using your favorite package manager.
|
|
157
27
|
|
|
158
|
-
|
|
159
|
-
```javascript
|
|
160
|
-
const result = await db.getCollection('users');
|
|
161
|
-
if (result.success) {
|
|
162
|
-
const collection = result.data;
|
|
163
|
-
} else {
|
|
164
|
-
console.error('Collection not found:', result.error.message);
|
|
165
|
-
}
|
|
166
|
-
```
|
|
28
|
+
npm install @pixagram/lacerta-db
|
|
167
29
|
|
|
168
|
-
|
|
30
|
+
⚡ Quick Start
|
|
31
|
+
Here's how to get up and running with LacertaDB in just a few lines of code.
|
|
169
32
|
|
|
170
|
-
|
|
171
|
-
|
|
33
|
+
// Import the default instance
|
|
34
|
+
import LacertaDB from '@pixagram/lacerta-db';
|
|
172
35
|
|
|
173
|
-
|
|
36
|
+
async function main() {
|
|
37
|
+
try {
|
|
38
|
+
// 1. Get a database instance
|
|
39
|
+
const db = await LacertaDB.getDatabase('my-app-db');
|
|
174
40
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
| Collection deleted | `true` | `null` | `null` |
|
|
179
|
-
| Collection doesn't exist | `false` | `null` | `COLLECTION_NOT_FOUND` error |
|
|
41
|
+
// 2. Get or create a collection
|
|
42
|
+
// Collections are created automatically on first access
|
|
43
|
+
const users = await db.getCollection('users');
|
|
180
44
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
45
|
+
// 3. Add a new document
|
|
46
|
+
const newUserId = await users.add({
|
|
47
|
+
name: 'Alex',
|
|
48
|
+
level: 10,
|
|
49
|
+
joined: new Date()
|
|
50
|
+
});
|
|
51
|
+
console.log(`User created with ID: ${newUserId}`);
|
|
186
52
|
|
|
187
|
-
|
|
53
|
+
// 4. Get a document by its ID
|
|
54
|
+
const user = await users.get(newUserId);
|
|
55
|
+
console.log('Retrieved user:', user);
|
|
188
56
|
|
|
189
|
-
|
|
190
|
-
|
|
57
|
+
// 5. Query for documents
|
|
58
|
+
const highLevelUsers = await users.query({ level: { '$gt': 5 } });
|
|
59
|
+
console.log('High-level users:', highLevelUsers);
|
|
191
60
|
|
|
192
|
-
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Database operation failed:', error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
193
65
|
|
|
194
|
-
|
|
66
|
+
main();
|
|
195
67
|
|
|
196
|
-
|
|
197
|
-
|
|
68
|
+
📖 API Reference
|
|
69
|
+
Database
|
|
70
|
+
The Database object is your main entry point for managing collections.
|
|
198
71
|
|
|
199
|
-
|
|
72
|
+
getDatabase(name)
|
|
73
|
+
Retrieves a database instance. If it doesn't exist, it's created.
|
|
200
74
|
|
|
201
|
-
|
|
202
|
-
- Deletes IndexedDB database
|
|
203
|
-
- Removes all localStorage metadata
|
|
204
|
-
- Clears all settings
|
|
205
|
-
- Invalidates all collection references
|
|
75
|
+
const db = await LacertaDB.getDatabase('my-app-db');
|
|
206
76
|
|
|
207
|
-
|
|
77
|
+
getCollection(name)
|
|
78
|
+
Retrieves a collection from the database. If it doesn't exist, it's created.
|
|
208
79
|
|
|
209
|
-
|
|
80
|
+
const usersCollection = await db.getCollection('users');
|
|
210
81
|
|
|
211
|
-
|
|
82
|
+
dropCollection(name)
|
|
83
|
+
Deletes a collection and all of its documents and attachments.
|
|
212
84
|
|
|
213
|
-
|
|
214
|
-
|----------|------|-------------|
|
|
215
|
-
| `name` | string | Collection identifier |
|
|
216
|
-
| `sizeKB` | number | Total size in kilobytes |
|
|
217
|
-
| `length` | number | Document count |
|
|
218
|
-
| `keys` | string[] | All document IDs |
|
|
219
|
-
| `observer` | Observer | Event emitter instance |
|
|
220
|
-
| `documentsMetadata` | Array | Document metadata array |
|
|
85
|
+
await db.dropCollection('users');
|
|
221
86
|
|
|
222
|
-
|
|
87
|
+
Collection
|
|
88
|
+
The Collection object provides methods to interact with documents.
|
|
223
89
|
|
|
224
|
-
|
|
90
|
+
add(data, [options])
|
|
91
|
+
Adds a new document to the collection.
|
|
225
92
|
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
interface DocumentInput {
|
|
229
|
-
_id?: string; // Auto-generated if omitted
|
|
230
|
-
_permanent?: boolean; // Prevents auto-deletion (default: false)
|
|
231
|
-
_compressed?: boolean; // Enable compression (default: false)
|
|
232
|
-
data: object; // Document content
|
|
233
|
-
attachments?: Array<{data: Blob}>; // File attachments
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Returns:** `boolean` - `true` if new document, `false` if updated
|
|
238
|
-
|
|
239
|
-
**Behavior:**
|
|
240
|
-
- Auto-generates ID if not provided
|
|
241
|
-
- Updates existing document if ID exists
|
|
242
|
-
- Saves attachments to OPFS
|
|
243
|
-
- Triggers space management
|
|
244
|
-
- Emits `beforeAdd` and `afterAdd` events
|
|
245
|
-
|
|
246
|
-
**Example:**
|
|
247
|
-
```javascript
|
|
248
|
-
const isNew = await collection.addDocument({
|
|
249
|
-
data: { name: 'Alice', age: 30 },
|
|
250
|
-
_permanent: true,
|
|
251
|
-
attachments: [{ data: imageBlob }]
|
|
252
|
-
}, 'encryption-password');
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
##### `async getDocument(docId: string, encryptionKey?: string, includeAttachments?: boolean): Promise<Document | false>`
|
|
258
|
-
|
|
259
|
-
**Returns:** Document object or `false`
|
|
260
|
-
|
|
261
|
-
**Behavior Matrix:**
|
|
262
|
-
| Scenario | Return Value |
|
|
263
|
-
|----------|--------------|
|
|
264
|
-
| Document exists, no encryption | Document object |
|
|
265
|
-
| Document exists, correct key | Document object |
|
|
266
|
-
| Document exists, wrong/missing key | `false` |
|
|
267
|
-
| Document doesn't exist | `false` |
|
|
268
|
-
|
|
269
|
-
---
|
|
270
|
-
|
|
271
|
-
##### `async deleteDocument(docId: string, force?: boolean): Promise<boolean>`
|
|
272
|
-
|
|
273
|
-
**Parameters:**
|
|
274
|
-
- `docId`: Document identifier
|
|
275
|
-
- `force`: Override permanent flag (default: false)
|
|
276
|
-
|
|
277
|
-
**Returns:** `boolean` - Success status
|
|
278
|
-
|
|
279
|
-
**Behavior Matrix:**
|
|
280
|
-
| Scenario | `force` | Result |
|
|
281
|
-
|----------|---------|---------|
|
|
282
|
-
| Non-permanent document | any | Deleted |
|
|
283
|
-
| Permanent document | `false` | Not deleted |
|
|
284
|
-
| Permanent document | `true` | Deleted |
|
|
285
|
-
| Document doesn't exist | any | Returns `false` |
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
##### `async query(filter?: object, options?: QueryOptions): Promise<Document[]>`
|
|
290
|
-
|
|
291
|
-
**Parameters:**
|
|
292
|
-
```typescript
|
|
293
|
-
interface QueryOptions {
|
|
294
|
-
encryptionKey?: string;
|
|
295
|
-
limit?: number; // Max results (default: Infinity)
|
|
296
|
-
offset?: number; // Skip documents (default: 0)
|
|
297
|
-
orderBy?: 'asc' | 'desc';
|
|
298
|
-
index?: string; // Index name for optimization
|
|
299
|
-
}
|
|
300
|
-
```
|
|
93
|
+
data (Object): The document data.
|
|
301
94
|
|
|
302
|
-
|
|
303
|
-
```javascript
|
|
304
|
-
// Simple equality
|
|
305
|
-
{ status: 'active' }
|
|
95
|
+
options (Object, optional):
|
|
306
96
|
|
|
307
|
-
|
|
308
|
-
{ 'address.city': 'New York' }
|
|
309
|
-
```
|
|
97
|
+
encrypted (boolean): Set to true to encrypt the document.
|
|
310
98
|
|
|
311
|
-
|
|
99
|
+
password (string): Required if encrypted is true.
|
|
312
100
|
|
|
313
|
-
|
|
101
|
+
permanent (boolean): If true, the document won't be deleted by the auto-cleanup process.
|
|
314
102
|
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
interface IndexOptions {
|
|
318
|
-
name?: string; // Custom index name
|
|
319
|
-
unique?: boolean; // Enforce uniqueness
|
|
320
|
-
multiEntry?: boolean; // Index array values
|
|
321
|
-
}
|
|
322
|
-
```
|
|
103
|
+
attachments (Array): An array of file attachments.
|
|
323
104
|
|
|
324
|
-
|
|
325
|
-
```javascript
|
|
326
|
-
await collection.createIndex('data.email', {
|
|
327
|
-
name: 'email_idx',
|
|
328
|
-
unique: true
|
|
329
|
-
});
|
|
330
|
-
```
|
|
105
|
+
const userId = await users.add({ name: 'Zoe' }, { permanent: true });
|
|
331
106
|
|
|
332
|
-
|
|
107
|
+
get(id, [options])
|
|
108
|
+
Retrieves a single document by its _id.
|
|
333
109
|
|
|
334
|
-
|
|
110
|
+
id (string): The document ID.
|
|
335
111
|
|
|
336
|
-
|
|
337
|
-
- `size`: Target size in KB (positive) or amount to free (negative)
|
|
112
|
+
options (Object, optional):
|
|
338
113
|
|
|
339
|
-
|
|
114
|
+
password (string): Required to decrypt an encrypted document.
|
|
340
115
|
|
|
341
|
-
|
|
342
|
-
1. Sorts non-permanent documents by modification time
|
|
343
|
-
2. Deletes oldest first
|
|
344
|
-
3. Stops when target reached
|
|
116
|
+
includeAttachments (boolean): If true, retrieves file data from OPFS.
|
|
345
117
|
|
|
346
|
-
|
|
118
|
+
const user = await users.get(userId);
|
|
347
119
|
|
|
348
|
-
|
|
120
|
+
query(filter, [options])
|
|
121
|
+
Finds documents matching the filter object.
|
|
349
122
|
|
|
350
|
-
|
|
351
|
-
```javascript
|
|
352
|
-
new Document(data: DocumentData, encryptionKey?: string)
|
|
353
|
-
```
|
|
123
|
+
filter (Object): A MongoDB-style query object.
|
|
354
124
|
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
interface DocumentData {
|
|
358
|
-
_id?: string;
|
|
359
|
-
_created?: number;
|
|
360
|
-
_modified?: number;
|
|
361
|
-
_permanent?: boolean;
|
|
362
|
-
_encrypted?: boolean;
|
|
363
|
-
_compressed?: boolean;
|
|
364
|
-
data?: object;
|
|
365
|
-
packedData?: Uint8Array;
|
|
366
|
-
attachments?: Array;
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
#### Methods
|
|
371
|
-
|
|
372
|
-
##### `async pack(): Promise<Uint8Array>`
|
|
373
|
-
Serializes, compresses, and encrypts document data.
|
|
374
|
-
|
|
375
|
-
##### `async unpack(): Promise<object>`
|
|
376
|
-
Deserializes, decompresses, and decrypts document data.
|
|
377
|
-
|
|
378
|
-
##### `packSync(): Uint8Array`
|
|
379
|
-
Synchronous packing (no encryption/compression).
|
|
125
|
+
options (Object, optional):
|
|
380
126
|
|
|
381
|
-
|
|
127
|
+
sort (Object): Sort order (e.g., { level: -1 }).
|
|
382
128
|
|
|
383
|
-
|
|
384
|
-
Synchronous unpacking (no encryption/compression).
|
|
129
|
+
limit (number): Max number of documents to return.
|
|
385
130
|
|
|
386
|
-
|
|
131
|
+
skip (number): Number of documents to skip.
|
|
387
132
|
|
|
388
|
-
|
|
133
|
+
const results = await users.query({ name: 'Alex' });
|
|
389
134
|
|
|
390
|
-
|
|
135
|
+
update(id, updates)
|
|
136
|
+
Updates the data of a specific document.
|
|
391
137
|
|
|
392
|
-
|
|
138
|
+
await users.update(userId, { level: 11 });
|
|
393
139
|
|
|
394
|
-
|
|
395
|
-
|
|
140
|
+
delete(id)
|
|
141
|
+
Removes a document from the collection.
|
|
396
142
|
|
|
397
|
-
|
|
143
|
+
await users.delete(userId);
|
|
398
144
|
|
|
399
|
-
|
|
145
|
+
aggregate(pipeline)
|
|
146
|
+
Processes documents through an aggregation pipeline.
|
|
400
147
|
|
|
401
|
-
|
|
148
|
+
const results = await users.aggregate([
|
|
149
|
+
{ '$match': { level: { '$gt': 5 } } },
|
|
150
|
+
{ '$group': { _id: null, averageLevel: { '$avg': '$level' } } }
|
|
151
|
+
]);
|
|
402
152
|
|
|
403
|
-
|
|
153
|
+
🛠️ Advanced Usage
|
|
154
|
+
Encryption 🔒
|
|
155
|
+
To store sensitive data, simply set the encrypted flag and provide a password. LacertaDB handles the rest.
|
|
404
156
|
|
|
405
|
-
|
|
157
|
+
const secretData = { account: '123-456', secret: 'my-secret-key' };
|
|
406
158
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
"_id": "xxxx-xxxx-xxxx",
|
|
411
|
-
"_created": 1234567890,
|
|
412
|
-
"_modified": 1234567890,
|
|
413
|
-
"_permanent": false,
|
|
414
|
-
"_encrypted": false,
|
|
415
|
-
"_compressed": false,
|
|
416
|
-
"packedData": Uint8Array,
|
|
417
|
-
"attachments": ["db/collection/doc/0", "db/collection/doc/1"]
|
|
418
|
-
}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### Document Schema (Retrieved)
|
|
422
|
-
```javascript
|
|
423
|
-
{
|
|
424
|
-
"_id": "xxxx-xxxx-xxxx",
|
|
425
|
-
"_created": 1234567890,
|
|
426
|
-
"_modified": 1234567890,
|
|
427
|
-
"_permanent": false,
|
|
428
|
-
"_encrypted": false,
|
|
429
|
-
"_compressed": false,
|
|
430
|
-
"data": { /* user data */ },
|
|
431
|
-
"attachments": [
|
|
432
|
-
{ "path": "db/collection/doc/0", "data": File },
|
|
433
|
-
{ "path": "db/collection/doc/1", "data": File }
|
|
434
|
-
]
|
|
435
|
-
}
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Metadata Schema
|
|
439
|
-
```javascript
|
|
440
|
-
{
|
|
441
|
-
"name": "database_name",
|
|
442
|
-
"collections": {
|
|
443
|
-
"collection_name": {
|
|
444
|
-
"name": "collection_name",
|
|
445
|
-
"sizeKB": 1024.5,
|
|
446
|
-
"length": 100,
|
|
447
|
-
"createdAt": 1234567890,
|
|
448
|
-
"modifiedAt": 1234567890,
|
|
449
|
-
"documentSizes": { "docId": 10.5 },
|
|
450
|
-
"documentModifiedAt": { "docId": 1234567890 },
|
|
451
|
-
"documentPermanent": { "docId": 1 },
|
|
452
|
-
"documentAttachments": { "docId": 2 }
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
"totalSizeKB": 2048.5,
|
|
456
|
-
"totalLength": 200,
|
|
457
|
-
"modifiedAt": 1234567890
|
|
458
|
-
}
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
---
|
|
462
|
-
|
|
463
|
-
## Error Handling
|
|
464
|
-
|
|
465
|
-
### Error Codes
|
|
466
|
-
| Code | Description | Recovery Strategy |
|
|
467
|
-
|------|-------------|-------------------|
|
|
468
|
-
| `COLLECTION_EXISTS` | Collection already exists | Use existing collection |
|
|
469
|
-
| `COLLECTION_NOT_FOUND` | Collection doesn't exist | Create collection first |
|
|
470
|
-
| `DOCUMENT_NOT_FOUND` | Document doesn't exist | Check document ID |
|
|
471
|
-
| `ENCRYPTION_FAILED` | Encryption/decryption failed | Verify encryption key |
|
|
472
|
-
| `TRANSACTION_FAILED` | Database transaction failed | Retry operation |
|
|
473
|
-
| `QUOTA_EXCEEDED` | Storage quota exceeded | Free space or increase quota |
|
|
474
|
-
| `ATTACHMENT_DELETE_FAILED` | Attachment cleanup failed | Manual OPFS cleanup |
|
|
475
|
-
| `METADATA_SYNC_FAILED` | Metadata save failed | Check localStorage quota |
|
|
476
|
-
|
|
477
|
-
### Error Handling Pattern
|
|
478
|
-
```javascript
|
|
479
|
-
try {
|
|
480
|
-
const result = await db.createCollection('users');
|
|
481
|
-
if (result.success) {
|
|
482
|
-
// Handle success
|
|
483
|
-
} else {
|
|
484
|
-
switch (result.error.code) {
|
|
485
|
-
case ErrorCodes.COLLECTION_EXISTS:
|
|
486
|
-
// Use existing collection
|
|
487
|
-
break;
|
|
488
|
-
default:
|
|
489
|
-
throw result.error;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
} catch (error) {
|
|
493
|
-
if (error instanceof LacertaDBError) {
|
|
494
|
-
console.error(`Database error ${error.code}: ${error.message}`);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
---
|
|
500
|
-
|
|
501
|
-
## Edge Cases & Behaviors
|
|
502
|
-
|
|
503
|
-
### Collection Operations
|
|
504
|
-
|
|
505
|
-
#### Creating Existing Collection
|
|
506
|
-
```javascript
|
|
507
|
-
const result1 = await db.createCollection('users');
|
|
508
|
-
// result1.success = true, result1.data = Collection
|
|
509
|
-
|
|
510
|
-
const result2 = await db.createCollection('users');
|
|
511
|
-
// result2.success = false
|
|
512
|
-
// result2.data = existing Collection instance
|
|
513
|
-
// result2.error.code = 'COLLECTION_EXISTS'
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
#### Getting Non-Existent Collection
|
|
517
|
-
```javascript
|
|
518
|
-
const result = await db.getCollection('nonexistent');
|
|
519
|
-
// result.success = false
|
|
520
|
-
// result.data = null
|
|
521
|
-
// result.error.code = 'COLLECTION_NOT_FOUND'
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
#### Deleting Non-Existent Collection
|
|
525
|
-
```javascript
|
|
526
|
-
const result = await db.deleteCollection('nonexistent');
|
|
527
|
-
// result.success = false
|
|
528
|
-
// result.data = null
|
|
529
|
-
// result.error.code = 'COLLECTION_NOT_FOUND'
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
### Document Operations
|
|
533
|
-
|
|
534
|
-
#### Adding Document with Existing ID
|
|
535
|
-
```javascript
|
|
536
|
-
// First add
|
|
537
|
-
await collection.addDocument({ _id: 'doc1', data: { v: 1 } });
|
|
538
|
-
// Returns: true (new document)
|
|
539
|
-
|
|
540
|
-
// Second add with same ID
|
|
541
|
-
await collection.addDocument({ _id: 'doc1', data: { v: 2 } });
|
|
542
|
-
// Returns: false (updated existing)
|
|
543
|
-
// Document is updated, not duplicated
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
#### Getting Encrypted Document Without Key
|
|
547
|
-
```javascript
|
|
548
|
-
// Add encrypted document
|
|
549
|
-
await collection.addDocument(
|
|
550
|
-
{ data: { secret: 'data' } },
|
|
551
|
-
'encryption-key'
|
|
552
|
-
);
|
|
553
|
-
|
|
554
|
-
// Try to get without key
|
|
555
|
-
const doc = await collection.getDocument(docId);
|
|
556
|
-
// Returns: false (cannot decrypt)
|
|
557
|
-
|
|
558
|
-
// Try with wrong key
|
|
559
|
-
const doc2 = await collection.getDocument(docId, 'wrong-key');
|
|
560
|
-
// Returns: false (decryption fails)
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
#### Deleting Permanent Document
|
|
564
|
-
```javascript
|
|
565
|
-
// Add permanent document
|
|
566
|
-
await collection.addDocument({
|
|
567
|
-
_permanent: true,
|
|
568
|
-
data: { important: 'data' }
|
|
159
|
+
const docId = await db.collection('secrets').add(secretData, {
|
|
160
|
+
encrypted: true,
|
|
161
|
+
password: 'a-very-strong-password'
|
|
569
162
|
});
|
|
570
163
|
|
|
571
|
-
//
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
// Force delete
|
|
576
|
-
const forceDeleted = await collection.deleteDocument(docId, true);
|
|
577
|
-
// Returns: true (deleted)
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
### Space Management Behaviors
|
|
581
|
-
|
|
582
|
-
#### Automatic Cleanup Trigger
|
|
583
|
-
```javascript
|
|
584
|
-
const db = new Database('myapp', {
|
|
585
|
-
sizeLimitKB: 1000, // 1MB limit
|
|
586
|
-
bufferLimitKB: -200, // Start at 800KB
|
|
587
|
-
freeSpaceEvery: 5000 // Check every 5 seconds
|
|
164
|
+
// You MUST provide the same password to retrieve it
|
|
165
|
+
const retrieved = await db.collection('secrets').get(docId, {
|
|
166
|
+
password: 'a-very-strong-password'
|
|
588
167
|
});
|
|
589
168
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
// - Deletes oldest non-permanent documents
|
|
593
|
-
// - Stops at 1000KB or when all deletable removed
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
#### Manual Space Freeing
|
|
597
|
-
```javascript
|
|
598
|
-
// Keep only 500KB
|
|
599
|
-
const freed = await collection.freeSpace(500);
|
|
600
|
-
|
|
601
|
-
// Free 200KB
|
|
602
|
-
const freed = await collection.freeSpace(-200);
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### Transaction Behaviors
|
|
606
|
-
|
|
607
|
-
#### Retry Logic
|
|
608
|
-
- Transactions retry up to 3 times on failure
|
|
609
|
-
- Exponential backoff: 100ms, 200ms, 400ms
|
|
610
|
-
- Includes idempotency tracking to prevent duplicates
|
|
611
|
-
|
|
612
|
-
#### Concurrent Operations
|
|
613
|
-
```javascript
|
|
614
|
-
// Operations are queued and executed serially
|
|
615
|
-
const promises = [
|
|
616
|
-
collection.addDocument({ data: { id: 1 } }),
|
|
617
|
-
collection.addDocument({ data: { id: 2 } }),
|
|
618
|
-
collection.addDocument({ data: { id: 3 } })
|
|
619
|
-
];
|
|
620
|
-
await Promise.all(promises);
|
|
621
|
-
// Executed in order, prevents race conditions
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
---
|
|
625
|
-
|
|
626
|
-
## Performance Optimization
|
|
627
|
-
|
|
628
|
-
### Indexing Strategy
|
|
629
|
-
```javascript
|
|
630
|
-
// Create indexes for frequently queried fields
|
|
631
|
-
await collection.createIndex('data.status');
|
|
632
|
-
await collection.createIndex('data.userId');
|
|
633
|
-
|
|
634
|
-
// Use indexes in queries
|
|
635
|
-
const results = await collection.query(
|
|
636
|
-
{ status: 'active' },
|
|
637
|
-
{ index: 'data_status' } // Index name is fieldPath with dots replaced
|
|
638
|
-
);
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
### Batch Operations Pattern
|
|
642
|
-
```javascript
|
|
643
|
-
// Efficient batch insert
|
|
644
|
-
const documents = generateDocuments(1000);
|
|
645
|
-
const promises = documents.map(doc =>
|
|
646
|
-
collection.addDocument(doc)
|
|
647
|
-
);
|
|
648
|
-
await Promise.all(promises);
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
### Memory Management
|
|
652
|
-
```javascript
|
|
653
|
-
// Process large datasets in chunks
|
|
654
|
-
async function* queryInChunks(collection, filter, chunkSize = 100) {
|
|
655
|
-
let offset = 0;
|
|
656
|
-
while (true) {
|
|
657
|
-
const chunk = await collection.query(filter, {
|
|
658
|
-
limit: chunkSize,
|
|
659
|
-
offset: offset
|
|
660
|
-
});
|
|
661
|
-
if (chunk.length === 0) break;
|
|
662
|
-
yield chunk;
|
|
663
|
-
offset += chunkSize;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// Usage
|
|
668
|
-
for await (const chunk of queryInChunks(collection, {})) {
|
|
669
|
-
processChunk(chunk);
|
|
670
|
-
}
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
---
|
|
674
|
-
|
|
675
|
-
## Security Model
|
|
169
|
+
File Attachments 📎
|
|
170
|
+
You can attach files (like File objects or Uint8Array data) to a document. They are stored efficiently in the browser's Origin Private File System.
|
|
676
171
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
### Security Best Practices
|
|
685
|
-
```javascript
|
|
686
|
-
// Use strong passwords
|
|
687
|
-
const strongKey = crypto.randomUUID() + crypto.randomUUID();
|
|
688
|
-
|
|
689
|
-
// Encrypt sensitive collections
|
|
690
|
-
class SecureCollection {
|
|
691
|
-
constructor(collection, masterKey) {
|
|
692
|
-
this.collection = collection;
|
|
693
|
-
this.masterKey = masterKey;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
async add(data) {
|
|
697
|
-
return this.collection.addDocument(data, this.masterKey);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
async get(id) {
|
|
701
|
-
return this.collection.getDocument(id, this.masterKey);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// Field-level encryption pattern
|
|
706
|
-
function encryptField(field, key) {
|
|
707
|
-
// Implement field-specific encryption
|
|
708
|
-
return encryptedValue;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
const doc = {
|
|
712
|
-
data: {
|
|
713
|
-
public: 'visible',
|
|
714
|
-
ssn: encryptField('123-45-6789', fieldKey)
|
|
715
|
-
}
|
|
172
|
+
// Create a sample file
|
|
173
|
+
const fileContent = new TextEncoder().encode('This is the content of my file.');
|
|
174
|
+
const attachment = {
|
|
175
|
+
name: 'readme.txt',
|
|
176
|
+
type: 'text/plain',
|
|
177
|
+
data: fileContent
|
|
716
178
|
};
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
---
|
|
720
|
-
|
|
721
|
-
## Code Examples
|
|
722
|
-
|
|
723
|
-
### Complete Application Example
|
|
724
|
-
```javascript
|
|
725
|
-
import { Database } from './lacertadb.js';
|
|
726
|
-
|
|
727
|
-
class TodoApp {
|
|
728
|
-
async init() {
|
|
729
|
-
// Initialize database
|
|
730
|
-
this.db = new Database('todos', {
|
|
731
|
-
sizeLimitKB: 5000,
|
|
732
|
-
bufferLimitKB: -1000,
|
|
733
|
-
freeSpaceEvery: 60000
|
|
734
|
-
});
|
|
735
|
-
await this.db.init();
|
|
736
|
-
|
|
737
|
-
// Get or create collection
|
|
738
|
-
const result = await this.db.getCollection('tasks');
|
|
739
|
-
if (!result.success) {
|
|
740
|
-
const createResult = await this.db.createCollection('tasks');
|
|
741
|
-
this.tasks = createResult.data;
|
|
742
|
-
} else {
|
|
743
|
-
this.tasks = result.data;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Create indexes
|
|
747
|
-
await this.tasks.createIndex('data.priority');
|
|
748
|
-
await this.tasks.createIndex('data.dueDate');
|
|
749
|
-
|
|
750
|
-
// Setup observers
|
|
751
|
-
this.tasks.observer.on('afterAdd', (doc) => {
|
|
752
|
-
this.updateUI('added', doc);
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
this.tasks.observer.on('afterDelete', (id) => {
|
|
756
|
-
this.updateUI('deleted', id);
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
async addTask(title, priority, dueDate) {
|
|
761
|
-
return await this.tasks.addDocument({
|
|
762
|
-
data: {
|
|
763
|
-
title,
|
|
764
|
-
priority,
|
|
765
|
-
dueDate,
|
|
766
|
-
completed: false,
|
|
767
|
-
createdAt: Date.now()
|
|
768
|
-
}
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
async getHighPriorityTasks() {
|
|
773
|
-
return await this.tasks.query(
|
|
774
|
-
{ priority: 'high' },
|
|
775
|
-
{
|
|
776
|
-
index: 'data_priority',
|
|
777
|
-
orderBy: 'asc'
|
|
778
|
-
}
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
async getOverdueTasks() {
|
|
783
|
-
const now = Date.now();
|
|
784
|
-
const allTasks = await this.tasks.query({}, {
|
|
785
|
-
index: 'data_dueDate',
|
|
786
|
-
orderBy: 'asc'
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
return allTasks.filter(task =>
|
|
790
|
-
task.data.dueDate < now && !task.data.completed
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
async completeTask(taskId) {
|
|
795
|
-
const task = await this.tasks.getDocument(taskId);
|
|
796
|
-
if (task) {
|
|
797
|
-
task.data.completed = true;
|
|
798
|
-
task.data.completedAt = Date.now();
|
|
799
|
-
await this.tasks.addDocument(task);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
async cleanup() {
|
|
804
|
-
await this.db.close();
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
updateUI(action, data) {
|
|
808
|
-
console.log(`UI Update: ${action}`, data);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Usage
|
|
813
|
-
const app = new TodoApp();
|
|
814
|
-
await app.init();
|
|
815
|
-
await app.addTask('Review PRs', 'high', Date.now() + 86400000);
|
|
816
|
-
const urgent = await app.getHighPriorityTasks();
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
### Encrypted Notes Application
|
|
820
|
-
```javascript
|
|
821
|
-
class SecureNotes {
|
|
822
|
-
constructor(password) {
|
|
823
|
-
this.password = password;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
async init() {
|
|
827
|
-
this.db = new Database('secure_notes');
|
|
828
|
-
await this.db.init();
|
|
829
|
-
|
|
830
|
-
const result = await this.db.createCollection('notes');
|
|
831
|
-
this.notes = result.success ? result.data : result.data;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
async addNote(title, content, attachments = []) {
|
|
835
|
-
return await this.notes.addDocument({
|
|
836
|
-
data: {
|
|
837
|
-
title,
|
|
838
|
-
content,
|
|
839
|
-
tags: [],
|
|
840
|
-
createdAt: Date.now()
|
|
841
|
-
},
|
|
842
|
-
attachments,
|
|
843
|
-
_permanent: true,
|
|
844
|
-
_encrypted: true
|
|
845
|
-
}, this.password);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
async getNote(id) {
|
|
849
|
-
return await this.notes.getDocument(
|
|
850
|
-
id,
|
|
851
|
-
this.password,
|
|
852
|
-
true // Include attachments
|
|
853
|
-
);
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
async searchNotes(searchTerm) {
|
|
857
|
-
// Get all notes (encrypted)
|
|
858
|
-
const allNotes = await this.notes.query(
|
|
859
|
-
{},
|
|
860
|
-
{ encryptionKey: this.password }
|
|
861
|
-
);
|
|
862
|
-
|
|
863
|
-
// Search in decrypted content
|
|
864
|
-
return allNotes.filter(note =>
|
|
865
|
-
note.data.title.includes(searchTerm) ||
|
|
866
|
-
note.data.content.includes(searchTerm)
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
### Data Migration Pattern
|
|
873
|
-
```javascript
|
|
874
|
-
class DatabaseMigration {
|
|
875
|
-
static async migrate(fromDb, toDb) {
|
|
876
|
-
const sourceDb = new Database(fromDb);
|
|
877
|
-
const targetDb = new Database(toDb);
|
|
878
|
-
|
|
879
|
-
await sourceDb.init();
|
|
880
|
-
await targetDb.init();
|
|
881
|
-
|
|
882
|
-
// Get all collections
|
|
883
|
-
for (const collectionName of sourceDb.metadata.getCollectionNames()) {
|
|
884
|
-
const sourceResult = await sourceDb.getCollection(collectionName);
|
|
885
|
-
if (!sourceResult.success) continue;
|
|
886
|
-
|
|
887
|
-
const source = sourceResult.data;
|
|
888
|
-
const targetResult = await targetDb.createCollection(collectionName);
|
|
889
|
-
const target = targetResult.success ?
|
|
890
|
-
targetResult.data : targetResult.data;
|
|
891
|
-
|
|
892
|
-
// Copy all documents
|
|
893
|
-
const docs = await source.query({});
|
|
894
|
-
for (const doc of docs) {
|
|
895
|
-
await target.addDocument(doc);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
await sourceDb.close();
|
|
900
|
-
await targetDb.close();
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
```
|
|
904
|
-
|
|
905
|
-
---
|
|
906
|
-
|
|
907
|
-
## Troubleshooting
|
|
908
179
|
|
|
909
|
-
|
|
180
|
+
// Add a document with the attachment
|
|
181
|
+
const reportId = await db.collection('reports').add(
|
|
182
|
+
{ title: 'Q3 Report' },
|
|
183
|
+
{ attachments: [attachment] }
|
|
184
|
+
);
|
|
910
185
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const db = new Database('app', {
|
|
915
|
-
sizeLimitKB: 5000,
|
|
916
|
-
freeSpaceEvery: 30000
|
|
186
|
+
// Retrieve the document and its attachments
|
|
187
|
+
const report = await db.collection('reports').get(reportId, {
|
|
188
|
+
includeAttachments: true
|
|
917
189
|
});
|
|
918
190
|
|
|
919
|
-
//
|
|
920
|
-
|
|
191
|
+
console.log(report.data._attachments[0].name); // "readme.txt"
|
|
192
|
+
const content = new TextDecoder().decode(report.data._attachments[0].data);
|
|
193
|
+
console.log(content); // "This is the content of my file."
|
|
921
194
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
await navigator.storage.persist();
|
|
925
|
-
}
|
|
926
|
-
```
|
|
927
|
-
|
|
928
|
-
#### Encryption Key Lost
|
|
929
|
-
```javascript
|
|
930
|
-
// Implement key recovery
|
|
931
|
-
class KeyManager {
|
|
932
|
-
static async deriveFromPassword(password, salt) {
|
|
933
|
-
const encoder = new TextEncoder();
|
|
934
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
935
|
-
'raw',
|
|
936
|
-
encoder.encode(password),
|
|
937
|
-
'PBKDF2',
|
|
938
|
-
false,
|
|
939
|
-
['deriveBits']
|
|
940
|
-
);
|
|
941
|
-
|
|
942
|
-
const keyBits = await crypto.subtle.deriveBits(
|
|
943
|
-
{
|
|
944
|
-
name: 'PBKDF2',
|
|
945
|
-
salt: encoder.encode(salt),
|
|
946
|
-
iterations: 100000,
|
|
947
|
-
hash: 'SHA-256'
|
|
948
|
-
},
|
|
949
|
-
keyMaterial,
|
|
950
|
-
256
|
|
951
|
-
);
|
|
952
|
-
|
|
953
|
-
return btoa(String.fromCharCode(...new Uint8Array(keyBits)));
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
```
|
|
957
|
-
|
|
958
|
-
#### Performance Degradation
|
|
959
|
-
```javascript
|
|
960
|
-
// Monitor and optimize
|
|
961
|
-
class PerformanceMonitor {
|
|
962
|
-
static async analyzeCollection(collection) {
|
|
963
|
-
return {
|
|
964
|
-
size: collection.sizeKB,
|
|
965
|
-
documents: collection.length,
|
|
966
|
-
avgDocSize: collection.sizeKB / collection.length,
|
|
967
|
-
metadata: collection.documentsMetadata,
|
|
968
|
-
recommendation: this.getRecommendation(collection)
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
static getRecommendation(collection) {
|
|
973
|
-
if (collection.length > 10000) {
|
|
974
|
-
return 'Consider sharding';
|
|
975
|
-
}
|
|
976
|
-
if (collection.sizeKB / collection.length > 100) {
|
|
977
|
-
return 'Consider compression';
|
|
978
|
-
}
|
|
979
|
-
return 'Optimal';
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
```
|
|
195
|
+
⚙️ Serialization: TurboSerial
|
|
196
|
+
A key performance feature of LacertaDB is its use of turboserial instead of JSON. This provides several advantages:
|
|
983
197
|
|
|
984
|
-
|
|
198
|
+
⚡ Speed: turboserial is significantly faster at serializing and deserializing complex JavaScript objects.
|
|
985
199
|
|
|
986
|
-
|
|
200
|
+
📦 Efficiency: It produces a more compact binary output, especially when compression is enabled, saving storage space.
|
|
987
201
|
|
|
988
|
-
|
|
989
|
-
- Fixed metadata synchronization issues
|
|
990
|
-
- Improved transaction retry logic with idempotency
|
|
991
|
-
- Added observer cleanup to prevent memory leaks
|
|
992
|
-
- Enhanced error handling with LacertaDBResult
|
|
993
|
-
- Added comprehensive edge case handling
|
|
202
|
+
🔬 Type Support: It correctly handles more data types than JSON, including Date, Map, Set, BigInt, and typed arrays.
|
|
994
203
|
|
|
995
|
-
|
|
204
|
+
All data stored in localStorage (like metadata) or passed to IndexedDB is processed through turboserial and turbobase64, ensuring top-tier performance.
|
|
996
205
|
|
|
997
|
-
|
|
998
|
-
|
|
206
|
+
📜 License
|
|
207
|
+
LacertaDB is licensed under the MIT License.
|