@pixagram/lacerta-db 0.9.1 → 0.9.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/readme.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  <p align="center">
3
2
  <img src="https://raw.githubusercontent.com/pixagram-blockchain/LacertaDB/main/logo.webp?raw=true" alt="LacertaDB Logo" width="200"/>
4
3
  </p>
@@ -6,7 +5,7 @@
6
5
  <h1 align="center">LacertaDB</h1>
7
6
 
8
7
  <p align="center">
9
- <strong>A high-performance, browser-native document database with encryption, indexing, and MongoDB-like queries</strong>
8
+ <strong>A high-performance, browser-native document database with encryption, indexing, and MongoDB-like queries.</strong>
10
9
  </p>
11
10
 
12
11
  <p align="center">
@@ -18,16 +17,19 @@
18
17
  </p>
19
18
 
20
19
  <p align="center">
21
- <img src="https://img.shields.io/badge/version-0.7.5-blue.svg" alt="Version"/>
20
+ <img src="https://img.shields.io/badge/version-0.9.2-blue.svg" alt="Version"/>
22
21
  <img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"/>
23
- <img src="https://img.shields.io/badge/browser-only-orange.svg" alt="Browser Only"/>
22
+ <img src="https://img.shields.io/badge/platform-browser-orange.svg" alt="Browser Only"/>
23
+ <img src="https://img.shields.io/badge/encryption-AES--GCM--256-red.svg" alt="Encryption"/>
24
24
  </p>
25
25
 
26
26
  ---
27
27
 
28
28
  > *"Store your data like a lizard stores heat — efficiently, locally, and securely."*
29
29
 
30
- LacertaDB is a **zero-dependency**, **browser-only** document database designed for Web3 applications, offline-first PWAs, and any browser application requiring robust local storage with encryption capabilities.
30
+ LacertaDB is a **browser-native** document database built for Web3 applications, offline-first PWAs, and any browser application requiring robust local storage with encryption. It combines IndexedDB, OPFS, and localStorage into a unified API with MongoDB-style queries, four index types, and military-grade encryption via the Web Crypto API.
31
+
32
+ **Dependencies:** [@pixagram/turboserial](https://www.npmjs.com/package/@pixagram/turboserial) (binary serialization) and [@pixagram/turbobase64](https://www.npmjs.com/package/@pixagram/turbobase64) (base64 encoding) — both installed automatically.
31
33
 
32
34
  ---
33
35
 
@@ -36,19 +38,23 @@ LacertaDB is a **zero-dependency**, **browser-only** document database designed
36
38
  | Section | Description |
37
39
  |---------|-------------|
38
40
  | [Features](#features) | What LacertaDB offers |
39
- | [Installation](#installation) | How to install |
41
+ | [Installation](#installation) | How to install and bundle |
40
42
  | [Quick Start](#quick-start) | Get running in 2 minutes |
41
43
  | [Architecture](#architecture) | System design overview |
42
44
  | [API Reference](#api-reference) | Complete method documentation |
43
45
  | [Query Operators](#query-operators) | MongoDB-style query syntax |
44
46
  | [Aggregation Pipeline](#aggregation-pipeline) | Data transformation stages |
45
47
  | [Indexing](#indexing) | B-Tree, Hash, Text, Geo indexes |
46
- | [Encryption](#encryption) | AES-GCM-256 security |
48
+ | [Encryption](#encryption) | AES-GCM-256 + Master Key Wrapping |
47
49
  | [Caching](#caching) | LRU, LFU, TTL strategies |
50
+ | [QuickStore](#quickstore) | Fast localStorage key-value access |
51
+ | [Binary Attachments](#binary-attachments) | File storage via OPFS |
48
52
  | [Migrations](#migrations) | Schema version management |
49
53
  | [Performance](#performance-monitoring) | Metrics and optimization |
50
- | [Error Codes](#error-codes) | Error handling reference |
54
+ | [Error Handling](#error-handling) | Error codes and patterns |
51
55
  | [Examples](#examples) | Real-world usage patterns |
56
+ | [Exports](#exports) | All exported classes |
57
+ | [Browser Compatibility](#browser-compatibility) | Supported browsers |
52
58
 
53
59
  ---
54
60
 
@@ -58,40 +64,47 @@ LacertaDB is a **zero-dependency**, **browser-only** document database designed
58
64
  <tr>
59
65
  <td width="50%">
60
66
 
61
- ### 🗄️ Storage
62
- - **IndexedDB** backend with connection pooling
63
- - **OPFS** for binary attachments
64
- - **localStorage** QuickStore for fast access
65
- - Automatic space management
67
+ ### Storage
68
+ - **IndexedDB** backend with connection pooling and retry logic
69
+ - **OPFS** (Origin Private File System) for binary attachments
70
+ - **localStorage** QuickStore for synchronous fast-access data
71
+ - Automatic space management with configurable limits
72
+ - Batch operations with atomic transactions
66
73
 
67
74
  </td>
68
75
  <td width="50%">
69
76
 
70
- ### 🔐 Security
71
- - **AES-GCM-256** encryption
72
- - **PBKDF2** key derivation (100k iterations)
73
- - **HMAC** integrity verification
74
- - Private key vault storage
77
+ ### Security
78
+ - **AES-GCM-256** encryption via Web Crypto API
79
+ - **PBKDF2** key derivation (600,000 iterations, OWASP standard)
80
+ - **Master Key Wrapping** PIN changes don't re-encrypt data
81
+ - **HMAC-SHA-256** integrity verification on every document
82
+ - Dedicated private key vault with additional authentication data
83
+ - Constant-time comparison to prevent timing attacks
75
84
 
76
85
  </td>
77
86
  </tr>
78
87
  <tr>
79
88
  <td>
80
89
 
81
- ### 🔍 Querying
82
- - **MongoDB-style** query syntax
83
- - **20+ operators** ($eq, $gt, $in, $regex...)
84
- - **Aggregation pipeline** with 7 stages
85
- - Full-text and geospatial search
90
+ ### Querying
91
+ - **MongoDB-style** query syntax with 20+ operators
92
+ - **Aggregation pipeline** with 7 stages including `$lookup` joins
93
+ - **B-Tree** indexes for range queries and sorting
94
+ - **Hash** indexes for O(1) exact-match lookups
95
+ - **Full-text search** with CJK support via `Intl.Segmenter`
96
+ - **Geospatial queries** with QuadTree-backed `$near` and `$within`
86
97
 
87
98
  </td>
88
99
  <td>
89
100
 
90
- ### Performance
91
- - **B-Tree** indexes for range queries
92
- - **LRU/LFU/TTL** caching strategies
93
- - **Compression** via CompressionStream
94
- - Async mutex for thread safety
101
+ ### Performance
102
+ - **LRU / LFU / TTL** caching strategies per collection
103
+ - **Compression** via CompressionStream (deflate) with magic-byte detection
104
+ - **Cursor-free batch indexing** to prevent `TransactionInactiveError`
105
+ - **Read-optimized mutex** no global lock on read transactions
106
+ - Built-in performance monitor with optimization tips
107
+ - Idle callback scheduling to keep UI responsive
95
108
 
96
109
  </td>
97
110
  </tr>
@@ -102,60 +115,118 @@ LacertaDB is a **zero-dependency**, **browser-only** document database designed
102
115
  ## Installation
103
116
 
104
117
  ```bash
105
- npm install @pixagram/lacertadb
118
+ npm install @pixagram/lacerta-db
106
119
  ```
107
120
 
108
- **Dependencies** (installed automatically):
121
+ Both required dependencies are installed automatically:
122
+
123
+ ```
124
+ @pixagram/lacerta-db
125
+ ├── @pixagram/turboserial # Binary serialization with compression
126
+ └── @pixagram/turbobase64 # High-performance base64 encoding
127
+ ```
128
+
129
+ ### Bundler Setup
130
+
131
+ LacertaDB is an ES module. It works out of the box with Webpack, Vite, Rollup, or any modern bundler.
132
+
133
+ ```javascript
134
+ // ES module import (recommended)
135
+ import { LacertaDB } from '@pixagram/lacerta-db';
136
+
137
+ // Named imports for specific components
138
+ import { LacertaDB, Database, Collection, LacertaDBError } from '@pixagram/lacerta-db';
139
+ ```
140
+
141
+ ### Build from Source
142
+
109
143
  ```bash
110
- npm install @pixagram/turboserial @pixagram/turbobase64
144
+ git clone https://github.com/pixagram-blockchain/LacertaDB.git
145
+ cd LacertaDB
146
+ npm install
147
+ npm run build
111
148
  ```
112
149
 
150
+ > **Note:** The build script uses `NODE_OPTIONS=--openssl-legacy-provider` for compatibility with Webpack 4. Node.js >= 0.8.0 is required for building, but the output runs exclusively in browsers.
151
+
113
152
  ---
114
153
 
115
154
  ## Quick Start
116
155
 
117
156
  ```javascript
118
- import { LacertaDB } from '@pixagram/lacertadb';
157
+ import { LacertaDB } from '@pixagram/lacerta-db';
119
158
 
120
- // Initialize
159
+ // 1. Initialize LacertaDB
121
160
  const lacerta = new LacertaDB();
122
161
 
123
- // Get or create a database
162
+ // 2. Get or create a database
124
163
  const db = await lacerta.getDatabase('myapp');
125
164
 
126
- // Create a collection
165
+ // 3. Create a collection
127
166
  const users = await db.createCollection('users');
128
167
 
129
- // Add documents
168
+ // 4. Add a document
130
169
  const userId = await users.add({
131
170
  name: 'Alice',
132
171
  email: 'alice@example.com',
133
172
  age: 28
134
173
  });
135
174
 
136
- // Query documents
175
+ // 5. Query with MongoDB-style operators
137
176
  const results = await users.query({ age: { $gte: 18 } });
138
177
 
139
- // Update
178
+ // 6. Update
140
179
  await users.update(userId, { age: 29 });
141
180
 
142
- // Delete
181
+ // 7. Delete
143
182
  await users.delete(userId);
144
183
  ```
145
184
 
146
185
  <details>
147
- <summary><strong>🔐 With Encryption</strong></summary>
186
+ <summary><strong>With Encryption</strong></summary>
148
187
 
149
188
  ```javascript
150
- // Create encrypted database
189
+ // Create an encrypted database — all documents are encrypted at rest
151
190
  const secureDb = await lacerta.getSecureDatabase('vault', '123456');
152
191
 
153
- // All documents are automatically encrypted
154
192
  const secrets = await secureDb.createCollection('secrets');
155
- await secrets.add({ apiKey: 'sk-xxx-secret' });
193
+ await secrets.add({ apiKey: 'sk-xxx-secret', privateData: 'sensitive' });
156
194
 
157
- // Change PIN (re-encrypts all data)
195
+ // Change PIN without re-encrypting documents (Master Key Wrapping)
158
196
  await secureDb.changePin('123456', 'newSecurePin!');
197
+
198
+ // Store blockchain private keys with additional authentication
199
+ await secureDb.storePrivateKey('wallet-main', privateKeyString, 'optionalAuthData');
200
+ const key = await secureDb.getPrivateKey('wallet-main', 'optionalAuthData');
201
+ ```
202
+
203
+ </details>
204
+
205
+ <details>
206
+ <summary><strong>With Indexes and Aggregation</strong></summary>
207
+
208
+ ```javascript
209
+ const orders = await db.createCollection('orders');
210
+
211
+ // Create indexes for performance
212
+ await orders.createIndex('customerId', { type: 'hash' });
213
+ await orders.createIndex('amount', { type: 'btree' });
214
+ await orders.createIndex('description', { type: 'text' });
215
+
216
+ // Indexed queries run in O(log N) instead of full scans
217
+ const bigOrders = await orders.query({ amount: { $gte: 1000 } });
218
+
219
+ // Aggregation pipeline
220
+ const report = await orders.aggregate([
221
+ { $match: { status: 'completed' } },
222
+ { $group: {
223
+ _id: '$customerId',
224
+ totalSpent: { $sum: '$amount' },
225
+ orderCount: { $count: 1 }
226
+ }},
227
+ { $sort: { totalSpent: -1 } },
228
+ { $limit: 10 }
229
+ ]);
159
230
  ```
160
231
 
161
232
  </details>
@@ -164,113 +235,146 @@ await secureDb.changePin('123456', 'newSecurePin!');
164
235
 
165
236
  ## Architecture
166
237
 
167
- LacertaDB follows a **fractal architecture** where each layer encapsulates complexity while exposing a simple interface.
238
+ LacertaDB follows a **layered architecture** where each component encapsulates complexity while exposing a simple interface.
168
239
 
169
240
  ```
170
- ┌─────────────────────────────────────────────────────────────────┐
171
- LacertaDB
172
- ┌───────────────────────────────────────────────────────────┐
173
- │ │ Database │ │
174
- │ │ ┌─────────────────────────────────────────────────────┐ │ │
175
- │ │ │ Collection │ │ │
176
- │ │ │ ┌───────────────────────────────────────────────┐ │ │ │
177
- │ │ │ │ Document │ │ │ │
178
- │ │ │ │ • Data (serialized) │ │ │ │
179
- │ │ │ │ • Metadata (_id, _created, _modified) │ │ │ │
180
- │ │ │ │ • Attachments (OPFS references) │ │ │ │
181
- │ │ │ └───────────────────────────────────────────────┘ │ │ │
182
- │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │
183
- │ │ │ │ IndexManager│ │CacheStrategy│ │ EventBus │ │ │
184
- │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │
185
- │ │ └─────────────────────────────────────────────────────┘ │ │
186
- │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │
187
- │ │ │ QuickStore │ Encryption │ │ MigrationManager │ │ │
188
- │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │
189
- └───────────────────────────────────────────────────────────┘
190
- ┌─────────────────────────────────────────────────────────────┐│
191
- │ │ Shared Infrastructure ││
192
- │ │ ConnectionPoolAsyncMutex QueryEngineSerializer ││
193
- └─────────────────────────────────────────────────────────────┘│
194
- └─────────────────────────────────────────────────────────────────┘
241
+ ┌──────────────────────────────────────────────────────────────────────┐
242
+ LacertaDB
243
+ ┌────────────────────────────────────────────────────────────────┐
244
+ │ │ Database │ │
245
+ │ │ ┌──────────────────────────────────────────────────────────┐ │ │
246
+ │ │ │ Collection │ │ │
247
+ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │
248
+ │ │ │ │ Document │ │ │ │
249
+ │ │ │ │ • Data (serialized via TurboSerial) │ │ │ │
250
+ │ │ │ │ • Metadata (_id, _created, _modified, _permanent) │ │ │ │
251
+ │ │ │ │ • Attachments (OPFS file references) │ │ │ │
252
+ │ │ │ └────────────────────────────────────────────────────┘ │ │ │
253
+ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
254
+ │ │ │ │ IndexManager │ │CacheStrategy │ │ Event Bus │ │ │
255
+ │ │ │ │ B-Tree │ │ LRU / LFU before/after
256
+ │ │ Hash │ TTL / None │ │ CRUD hooks │ │ │ │
257
+ │ │ │ │ Text │ │ │ │ │ │ │ │
258
+ │ │ │ │ Geo(Quad) │ │ │ │ │ │
259
+ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
260
+ └──────────────────────────────────────────────────────────┘
261
+ │ ┌──────────────┐ ┌────────────────────┐ ┌─────────────────┐ │ │
262
+ │ │ │ QuickStore │ │SecureDatabaseEncr. │ │MigrationManager │ │ │
263
+ │ │ │ (localStorage)│(Master Key Wrap)│ (versioned) │ │ │
264
+ │ └──────────────┘ └────────────────────┘ └─────────────────┘ │ │
265
+ │ └────────────────────────────────────────────────────────────────┘ │
266
+ │ ┌──────────────────────────────────────────────────────────────────┐│
267
+ │ │ Shared Infrastructure ││
268
+ │ │ ConnectionPool │ AsyncMutex │ QueryEngine │ AggregationPipeline ││
269
+ │ │ Compression │ Serializer │ Base64 │ OPFS Utility ││
270
+ │ └──────────────────────────────────────────────────────────────────┘│
271
+ └──────────────────────────────────────────────────────────────────────┘
195
272
  ```
196
273
 
197
- <details>
198
- <summary><strong>📦 Component Overview</strong></summary>
199
-
200
- | Component | Responsibility | Storage |
201
- |-----------|---------------|---------|
202
- | `LacertaDB` | Instance manager, backup/restore | Memory |
203
- | `Database` | Collection manager, encryption, settings | localStorage |
204
- | `Collection` | CRUD operations, queries, indexes | IndexedDB |
205
- | `Document` | Data container, pack/unpack | IndexedDB |
206
- | `QuickStore` | Fast key-value access | localStorage |
274
+ ### Component Responsibilities
275
+
276
+ | Component | Responsibility | Storage Backend |
277
+ |-----------|---------------|-----------------|
278
+ | `LacertaDB` | Top-level manager, backup/restore, database lifecycle | Memory |
279
+ | `Database` | Collection manager, encryption, settings, QuickStore | localStorage |
280
+ | `Collection` | CRUD, queries, indexes, caching, events | IndexedDB |
281
+ | `Document` | Data container, serialize/compress/encrypt pipeline | IndexedDB |
282
+ | `QuickStore` | Synchronous fast key-value access | localStorage |
207
283
  | `OPFSUtility` | Binary file attachments | OPFS |
284
+ | `SecureDatabaseEncryption` | Master key wrapping, PBKDF2, AES-GCM, HMAC | localStorage (metadata) |
208
285
 
209
- </details>
286
+ ### Data Flow
210
287
 
211
- <details>
212
- <summary><strong>🔄 Data Flow</strong></summary>
288
+ When a document is stored, it passes through a multi-stage pipeline. Each stage is optional and configurable:
213
289
 
214
290
  ```
215
- User Data → Serialize → Compress → Encrypt → Store
216
-
217
- User Data ← Deserialize ← Decompress ← Decrypt ← Retrieve
218
- ```
291
+ Write Path:
292
+ User Data → TurboSerial.serialize() → CompressionStream (deflate) → AES-GCM-256 encrypt → IndexedDB
219
293
 
220
- | Stage | Technology | Optional |
221
- |-------|------------|----------|
222
- | Serialize | TurboSerial (CBOR-like) | No |
223
- | Compress | CompressionStream (deflate) | Yes |
224
- | Encrypt | AES-GCM-256 | Yes |
225
- | Store | IndexedDB / OPFS | No |
294
+ Read Path:
295
+ IndexedDB → AES-GCM-256 decrypt → DecompressionStream (inflate) → TurboSerial.deserialize() → User Data
296
+ ```
226
297
 
227
- </details>
298
+ | Stage | Technology | Optional | Default |
299
+ |-------|------------|----------|---------|
300
+ | Serialize | TurboSerial (CBOR-like binary) | No | Always |
301
+ | Compress | CompressionStream (deflate) with magic byte | Yes | On |
302
+ | Encrypt | AES-GCM-256 + HMAC-SHA-256 | Yes | Off |
303
+ | Store | IndexedDB (documents) / OPFS (attachments) | No | Always |
228
304
 
229
305
  ---
230
306
 
231
307
  ## API Reference
232
308
 
233
- ### LacertaDB (Main Entry Point)
309
+ ### LacertaDB (Entry Point)
310
+
311
+ The top-level class manages databases and provides global operations.
312
+
313
+ ```javascript
314
+ const lacerta = new LacertaDB();
315
+ ```
234
316
 
235
317
  | Method | Parameters | Returns | Description |
236
318
  |--------|------------|---------|-------------|
237
- | `getDatabase` | `name`, `options?` | `Promise<Database>` | Get or create database |
238
- | `getSecureDatabase` | `name`, `pin`, `salt?`, `config?` | `Promise<Database>` | Get encrypted database |
239
- | `dropDatabase` | `name` | `Promise<void>` | Delete database completely |
240
- | `listDatabases` | — | `string[]` | List all database names |
241
- | `createBackup` | `password?` | `Promise<string>` | Export all databases |
242
- | `restoreBackup` | `data`, `password?` | `Promise<Object>` | Import backup data |
243
- | `close` | — | `void` | Close all connections |
244
- | `destroy` | — | `void` | Clean up all resources |
319
+ | `getDatabase` | `name`, `options?` | `Promise<Database>` | Get or create a database |
320
+ | `getSecureDatabase` | `name`, `pin`, `salt?`, `config?` | `Promise<Database>` | Get or create an encrypted database |
321
+ | `dropDatabase` | `name` | `Promise<void>` | Permanently delete a database and all its data |
322
+ | `listDatabases` | — | `string[]` | List all database names found in localStorage |
323
+ | `createBackup` | `password?` | `Promise<string>` | Export all databases as a base64 string |
324
+ | `restoreBackup` | `data`, `password?` | `Promise<Object>` | Import backup data, returns `{ databases, collections, documents }` |
325
+ | `close` | — | `void` | Close all IndexedDB connections |
326
+ | `destroy` | — | `void` | Destroy all database instances and close connections |
327
+
328
+ | Property | Type | Description |
329
+ |----------|------|-------------|
330
+ | `performanceMonitor` | `PerformanceMonitor` | Global performance metrics collector |
245
331
 
246
332
  ---
247
333
 
248
334
  ### Database
249
335
 
336
+ Each database manages its own collections, encryption, settings, and QuickStore.
337
+
250
338
  <details>
251
339
  <summary><strong>Collection Management</strong></summary>
252
340
 
253
341
  | Method | Parameters | Returns | Description |
254
342
  |--------|------------|---------|-------------|
255
- | `createCollection` | `name`, `options?` | `Promise<Collection>` | Create new collection |
256
- | `getCollection` | `name` | `Promise<Collection>` | Get existing collection |
257
- | `dropCollection` | `name` | `Promise<void>` | Delete collection |
343
+ | `createCollection` | `name` | `Promise<Collection>` | Create a new collection (throws if exists) |
344
+ | `getCollection` | `name` | `Promise<Collection>` | Get existing collection (auto-initializes) |
345
+ | `dropCollection` | `name` | `Promise<void>` | Delete collection and its IndexedDB store |
258
346
  | `listCollections` | — | `string[]` | List collection names |
259
347
 
348
+ ```javascript
349
+ const users = await db.createCollection('users');
350
+ const posts = await db.createCollection('posts');
351
+
352
+ console.log(db.listCollections()); // ['users', 'posts']
353
+
354
+ await db.dropCollection('posts');
355
+ ```
356
+
260
357
  </details>
261
358
 
262
359
  <details>
263
- <summary><strong>Encryption</strong></summary>
360
+ <summary><strong>Encryption & Key Management</strong></summary>
264
361
 
265
362
  | Method | Parameters | Returns | Description |
266
363
  |--------|------------|---------|-------------|
267
- | `verifyPin` | `pin` | `Promise<boolean>` | Verify PIN is correct |
268
- | `changePin` | `oldPin`, `newPin` | `Promise<string>` | Change PIN, re-encrypt data |
269
- | `getEncryptionStatus` | | `Object` | Get encryption state info |
270
- | `storePrivateKey` | `name`, `key`, `auth?` | `Promise<boolean>` | Store encrypted key |
271
- | `getPrivateKey` | `name`, `auth?` | `Promise<string>` | Retrieve decrypted key |
272
- | `listPrivateKeys` || `Promise<Array>` | List stored key names |
273
- | `deletePrivateKey` | `name` | `Promise<boolean>` | Delete stored key |
364
+ | `changePin` | `oldPin`, `newPin` | `Promise<boolean>` | Change encryption PIN (verifies old PIN first) |
365
+ | `storePrivateKey` | `keyName`, `privateKey`, `additionalAuth?` | `Promise<boolean>` | Store an encrypted private key |
366
+ | `getPrivateKey` | `keyName`, `additionalAuth?` | `Promise<string>` | Retrieve and decrypt a private key |
367
+
368
+ ```javascript
369
+ // PIN changere-wraps master key, does NOT re-encrypt documents
370
+ await db.changePin('oldPin', 'newPin');
371
+
372
+ // Private key vault with optional additional authentication data
373
+ await db.storePrivateKey('eth-wallet', '0xabc...', 'user@example.com');
374
+ const key = await db.getPrivateKey('eth-wallet', 'user@example.com');
375
+ ```
376
+
377
+ > **How it works:** LacertaDB uses a Master Key Wrapping architecture. Your PIN derives a Key Encryption Key (KEK) via PBKDF2 which wraps/unwraps a random master key. Changing the PIN only re-wraps the master key — existing encrypted documents remain untouched.
274
378
 
275
379
  </details>
276
380
 
@@ -279,12 +383,12 @@ User Data ← Deserialize ← Decompress ← Decrypt ← Retrieve
279
383
 
280
384
  | Method | Parameters | Returns | Description |
281
385
  |--------|------------|---------|-------------|
282
- | `getStats` | — | `Object` | Database statistics |
283
- | `updateSettings` | `settings` | `void` | Update database settings |
284
- | `export` | `format?`, `password?` | `Promise<string>` | Export database |
285
- | `import` | `data`, `format?`, `password?` | `Promise<Object>` | Import data |
286
- | `clearAll` | — | `Promise<void>` | Clear all collections |
287
- | `destroy` | — | `Promise<void>` | Destroy database |
386
+ | `getStats` | — | `Object` | Returns `{ name, totalSizeKB, totalDocuments, collections[] }` |
387
+ | `updateSettings` | `settings` | `void` | Update `{ sizeLimitKB, bufferLimitKB, freeSpaceEvery }` |
388
+ | `export` | `format?`, `password?` | `Promise<string>` | Export as `'json'` or `'encrypted'` (base64 string) |
389
+ | `import` | `data`, `format?`, `password?` | `Promise<Object>` | Import data, returns `{ collections, documents }` |
390
+ | `clearAll` | — | `Promise<void>` | Clear all collections and reset metadata |
391
+ | `destroy` | — | `void` | Destroy database instance and release resources |
288
392
 
289
393
  </details>
290
394
 
@@ -294,11 +398,13 @@ User Data ← Deserialize ← Decompress ← Decrypt ← Retrieve
294
398
  | Property | Type | Description |
295
399
  |----------|------|-------------|
296
400
  | `name` | `string` | Database name |
297
- | `isEncrypted` | `boolean` | Encryption status |
298
- | `metadata` | `DatabaseMetadata` | Size, document counts |
299
- | `settings` | `Settings` | Configuration |
401
+ | `isEncrypted` | `boolean` | Whether encryption is active |
402
+ | `encryption` | `SecureDatabaseEncryption \| null` | Encryption utility (null if unencrypted) |
403
+ | `metadata` | `DatabaseMetadata` | Size and document counts |
404
+ | `settings` | `Settings` | Configuration (size limits, cleanup interval) |
300
405
  | `quickStore` | `QuickStore` | Fast localStorage access |
301
406
  | `performanceMonitor` | `PerformanceMonitor` | Metrics collector |
407
+ | `collections` | `Map<string, Collection>` | Loaded collections |
302
408
 
303
409
  </details>
304
410
 
@@ -306,31 +412,58 @@ User Data ← Deserialize ← Decompress ← Decrypt ← Retrieve
306
412
 
307
413
  ### Collection
308
414
 
415
+ Collections are the primary interface for storing, querying, and managing documents.
416
+
309
417
  <details>
310
418
  <summary><strong>CRUD Operations</strong></summary>
311
419
 
312
420
  | Method | Parameters | Returns | Description |
313
421
  |--------|------------|---------|-------------|
314
- | `add` | `data`, `options?` | `Promise<string>` | Add document |
315
- | `get` | `docId`, `options?` | `Promise<Object>` | Get by ID |
422
+ | `add` | `data`, `options?` | `Promise<string>` | Add a document, returns its ID |
423
+ | `get` | `docId`, `options?` | `Promise<Object>` | Get document by ID |
316
424
  | `getAll` | `options?` | `Promise<Array>` | Get all documents |
317
- | `update` | `docId`, `updates`, `options?` | `Promise<string>` | Update document |
318
- | `delete` | `docId`, `options?` | `Promise<void>` | Delete document |
425
+ | `update` | `docId`, `updates`, `options?` | `Promise<string>` | Merge updates into document |
426
+ | `delete` | `docId`, `options?` | `Promise<void>` | Delete a document |
319
427
 
320
- **Add Options:**
428
+ **`add` Options:**
321
429
 
322
430
  | Option | Type | Default | Description |
323
431
  |--------|------|---------|-------------|
324
- | `id` | `string` | auto | Custom document ID |
325
- | `compressed` | `boolean` | `true` | Enable compression |
326
- | `permanent` | `boolean` | `false` | Protect from auto-cleanup |
327
- | `attachments` | `Array<File\|Blob>` | `[]` | Binary attachments |
432
+ | `id` | `string` | auto-generated | Custom document ID |
433
+ | `compressed` | `boolean` | `true` | Enable deflate compression |
434
+ | `permanent` | `boolean` | `false` | Protect from automatic cleanup |
435
+ | `encrypted` | `boolean` | `false` | Requires database-level encryption |
436
+ | `attachments` | `Array<File\|Blob\|Object>` | `[]` | Binary attachments stored via OPFS |
328
437
 
329
- **Get Options:**
438
+ **`get` Options:**
330
439
 
331
440
  | Option | Type | Default | Description |
332
441
  |--------|------|---------|-------------|
333
- | `includeAttachments` | `boolean` | `false` | Load binary attachments |
442
+ | `includeAttachments` | `boolean` | `false` | Load binary attachments from OPFS |
443
+
444
+ **`delete` Options:**
445
+
446
+ | Option | Type | Default | Description |
447
+ |--------|------|---------|-------------|
448
+ | `force` | `boolean` | `false` | Required to delete permanent documents |
449
+
450
+ ```javascript
451
+ // Add with options
452
+ const id = await users.add(
453
+ { name: 'Alice', email: 'alice@example.com' },
454
+ { compressed: true, permanent: true, id: 'user_alice' }
455
+ );
456
+
457
+ // Get with attachments
458
+ const doc = await users.get('user_alice', { includeAttachments: true });
459
+ // → { _id: 'user_alice', _created: 1702..., _modified: 1702..., name: 'Alice', ... }
460
+
461
+ // Update (shallow merge)
462
+ await users.update('user_alice', { age: 29, status: 'active' });
463
+
464
+ // Delete permanent document
465
+ await users.delete('user_alice', { force: true });
466
+ ```
334
467
 
335
468
  </details>
336
469
 
@@ -339,26 +472,31 @@ User Data ← Deserialize ← Decompress ← Decrypt ← Retrieve
339
472
 
340
473
  | Method | Parameters | Returns | Description |
341
474
  |--------|------------|---------|-------------|
342
- | `batchAdd` | `documents`, `options?` | `Promise<string[]>` | Add multiple |
343
- | `batchUpdate` | `updates` | `Promise<string[]>` | Update multiple |
344
- | `batchDelete` | `docIds` | `Promise<void>` | Delete multiple |
475
+ | `batchAdd` | `documents[]`, `options?` | `Promise<Array>` | Add multiple documents atomically |
476
+ | `batchUpdate` | `updates[]`, `options?` | `Promise<Array>` | Update multiple documents |
477
+ | `batchDelete` | `items[]` | `Promise<Array>` | Delete multiple documents |
478
+
479
+ Each method returns an array of `{ success: boolean, id: string, error?: string }`.
345
480
 
346
481
  ```javascript
347
- // Batch add
348
- const ids = await collection.batchAdd([
349
- { name: 'Alice' },
350
- { name: 'Bob' },
351
- { name: 'Charlie' }
482
+ // Batch add — all documents in a single IndexedDB transaction
483
+ const results = await collection.batchAdd([
484
+ { name: 'Alice', role: 'admin' },
485
+ { name: 'Bob', role: 'user' },
486
+ { name: 'Charlie', role: 'user' }
352
487
  ]);
353
488
 
354
489
  // Batch update
355
490
  await collection.batchUpdate([
356
- { id: 'doc1', updates: { status: 'active' } },
357
- { id: 'doc2', updates: { status: 'active' } }
491
+ { id: 'doc_1', data: { status: 'active' } },
492
+ { id: 'doc_2', data: { status: 'suspended' } }
358
493
  ]);
359
494
 
360
- // Batch delete
361
- await collection.batchDelete(['doc1', 'doc2', 'doc3']);
495
+ // Batch delete — strings or objects with options
496
+ await collection.batchDelete(['doc_1', 'doc_2']);
497
+ await collection.batchDelete([
498
+ { id: 'permanent_doc', options: { force: true } }
499
+ ]);
362
500
  ```
363
501
 
364
502
  </details>
@@ -368,23 +506,22 @@ await collection.batchDelete(['doc1', 'doc2', 'doc3']);
368
506
 
369
507
  | Method | Parameters | Returns | Description |
370
508
  |--------|------------|---------|-------------|
371
- | `query` | `filter`, `options?` | `Promise<Array>` | Query documents |
372
- | `aggregate` | `pipeline` | `Promise<Array>` | Run aggregation |
373
- | `count` | `filter?` | `Promise<number>` | Count documents |
509
+ | `query` | `filter`, `options?` | `Promise<Array>` | Query documents with filter and options |
510
+ | `aggregate` | `pipeline[]` | `Promise<Array>` | Run an aggregation pipeline |
374
511
 
375
512
  **Query Options:**
376
513
 
377
514
  | Option | Type | Description |
378
515
  |--------|------|-------------|
379
- | `sort` | `Object` | Sort specification `{ field: 1 }` or `{ field: -1 }` |
516
+ | `sort` | `Object` | Sort specification: `{ field: 1 }` (asc) or `{ field: -1 }` (desc) |
380
517
  | `skip` | `number` | Skip N documents |
381
- | `limit` | `number` | Limit results |
382
- | `projection` | `Object` | Field selection |
518
+ | `limit` | `number` | Limit result count |
519
+ | `projection` | `Object` | Field selection: `{ name: 1 }` (include) or `{ password: 0 }` (exclude) |
383
520
 
384
521
  ```javascript
385
522
  const results = await collection.query(
386
523
  { status: 'active', age: { $gte: 18 } },
387
- {
524
+ {
388
525
  sort: { createdAt: -1 },
389
526
  skip: 10,
390
527
  limit: 20,
@@ -393,105 +530,149 @@ const results = await collection.query(
393
530
  );
394
531
  ```
395
532
 
533
+ > **Index hint:** If a filter field matches an existing index, LacertaDB automatically uses it instead of scanning all documents.
534
+
535
+ </details>
536
+
537
+ <details>
538
+ <summary><strong>Index Management</strong></summary>
539
+
540
+ | Method | Parameters | Returns | Description |
541
+ |--------|------------|---------|-------------|
542
+ | `createIndex` | `fieldPath`, `options?` | `Promise<string>` | Create an index, returns its name |
543
+ | `dropIndex` | `indexName` | `void` | Remove an index |
544
+ | `getIndexes` | — | `Promise<Object>` | Get index stats (size, memory, type) |
545
+ | `verifyIndexes` | — | `Promise<Object>` | Check index integrity, auto-rebuild if needed |
546
+
547
+ See the [Indexing](#indexing) section for full details and options.
548
+
396
549
  </details>
397
550
 
398
551
  <details>
399
- <summary><strong>Indexing</strong></summary>
552
+ <summary><strong>Cache Configuration</strong></summary>
400
553
 
401
554
  | Method | Parameters | Returns | Description |
402
555
  |--------|------------|---------|-------------|
403
- | `createIndex` | `fieldPath`, `options?` | `Promise<string>` | Create index |
404
- | `dropIndex` | `indexName` | `Promise<void>` | Remove index |
405
- | `getIndexes` | — | `Promise<Object>` | List indexes with stats |
406
- | `verifyIndexes` | | `Promise<Object>` | Verify index integrity |
556
+ | `configureCacheStrategy` | `config` | `void` | Update cache type/size/TTL |
557
+ | `clearCache` | | `void` | Manually invalidate all cached queries |
558
+
559
+ See the [Caching](#caching) section for strategies and configuration.
407
560
 
408
561
  </details>
409
562
 
410
563
  <details>
411
- <summary><strong>Events</strong></summary>
564
+ <summary><strong>Lifecycle Events</strong></summary>
412
565
 
413
566
  | Method | Parameters | Returns | Description |
414
567
  |--------|------------|---------|-------------|
415
- | `on` | `event`, `callback` | `void` | Subscribe to event |
416
- | `off` | `event`, `callback?` | `void` | Unsubscribe |
568
+ | `on` | `event`, `callback` | `void` | Subscribe to a collection event |
569
+ | `off` | `event`, `callback` | `void` | Unsubscribe from an event |
417
570
 
418
571
  **Available Events:**
419
572
 
420
- | Event | Payload | Description |
421
- |-------|---------|-------------|
422
- | `beforeAdd` | `documentData` | Before document insert |
423
- | `afterAdd` | `document` | After document insert |
424
- | `beforeUpdate` | `{ docId, updates }` | Before update |
425
- | `afterUpdate` | `document` | After update |
426
- | `beforeDelete` | `docId` | Before deletion |
427
- | `afterDelete` | `docId` | After deletion |
428
- | `beforeGet` | `docId` | Before retrieval |
429
- | `afterGet` | `document` | After retrieval |
573
+ | Event | Callback Argument | Description |
574
+ |-------|-------------------|-------------|
575
+ | `beforeAdd` | `documentData` | Before a document is inserted |
576
+ | `afterAdd` | `Document` | After successful insert |
577
+ | `beforeUpdate` | `{ docId, updates }` | Before a document is updated |
578
+ | `afterUpdate` | `Document` | After successful update |
579
+ | `beforeDelete` | `docId` | Before a document is deleted |
580
+ | `afterDelete` | `docId` | After successful deletion |
581
+ | `beforeGet` | `docId` | Before a document is retrieved |
582
+ | `afterGet` | `Document` | After successful retrieval |
430
583
 
431
584
  ```javascript
585
+ // Audit logging
432
586
  collection.on('afterAdd', async (doc) => {
433
- console.log(`Document ${doc._id} created`);
434
- await notifyWebhook(doc);
587
+ console.log(`Created: ${doc._id} at ${new Date(doc._created).toISOString()}`);
588
+ });
589
+
590
+ // Validation hook
591
+ collection.on('beforeAdd', async (data) => {
592
+ if (!data.email) throw new Error('Email is required');
435
593
  });
436
594
  ```
437
595
 
596
+ > **Note:** Throwing an error in a `before*` hook aborts the operation.
597
+
598
+ </details>
599
+
600
+ <details>
601
+ <summary><strong>Other Methods</strong></summary>
602
+
603
+ | Method | Parameters | Returns | Description |
604
+ |--------|------------|---------|-------------|
605
+ | `clear` | `options?` | `Promise<void>` | Clear documents (`{ force: true }` to include permanent) |
606
+ | `destroy` | — | `void` | Release IndexedDB connection, clear timers and cache |
607
+
438
608
  </details>
439
609
 
440
610
  ---
441
611
 
442
612
  ## Query Operators
443
613
 
444
- LacertaDB supports **MongoDB-compatible** query operators.
614
+ LacertaDB supports **MongoDB-compatible** query operators. They can be used in `collection.query()`, `quickStore.query()`, event hooks, and aggregation `$match` stages.
445
615
 
446
- ### Comparison Operators
616
+ ### Comparison
447
617
 
448
618
  | Operator | Description | Example |
449
619
  |----------|-------------|---------|
450
- | `$eq` | Equal | `{ status: { $eq: 'active' } }` |
451
- | `$ne` | Not equal | `{ status: { $ne: 'deleted' } }` |
620
+ | `$eq` | Equal to | `{ status: { $eq: 'active' } }` |
621
+ | `$ne` | Not equal to | `{ status: { $ne: 'deleted' } }` |
452
622
  | `$gt` | Greater than | `{ age: { $gt: 18 } }` |
453
- | `$gte` | Greater or equal | `{ score: { $gte: 90 } }` |
623
+ | `$gte` | Greater than or equal | `{ score: { $gte: 90 } }` |
454
624
  | `$lt` | Less than | `{ price: { $lt: 100 } }` |
455
- | `$lte` | Less or equal | `{ qty: { $lte: 10 } }` |
456
- | `$in` | In array | `{ status: { $in: ['a', 'b'] } }` |
457
- | `$nin` | Not in array | `{ role: { $nin: ['guest'] } }` |
625
+ | `$lte` | Less than or equal | `{ qty: { $lte: 10 } }` |
626
+ | `$in` | Value in array | `{ status: { $in: ['active', 'pending'] } }` |
627
+ | `$nin` | Value not in array | `{ role: { $nin: ['guest'] } }` |
628
+
629
+ > **Shorthand:** `{ status: 'active' }` is equivalent to `{ status: { $eq: 'active' } }`.
630
+
631
+ ### Logical
632
+
633
+ | Operator | Description | Example |
634
+ |----------|-------------|---------|
635
+ | `$and` | All conditions must match | `{ $and: [{ a: 1 }, { b: 2 }] }` |
636
+ | `$or` | At least one condition must match | `{ $or: [{ a: 1 }, { b: 2 }] }` |
637
+ | `$not` | Inverts a condition | `{ $not: { status: 'deleted' } }` |
638
+ | `$nor` | None of the conditions must match | `{ $nor: [{ a: 1 }, { b: 2 }] }` |
458
639
 
459
- ### Logical Operators
640
+ ### Element
460
641
 
461
642
  | Operator | Description | Example |
462
643
  |----------|-------------|---------|
463
- | `$and` | Logical AND | `{ $and: [{ a: 1 }, { b: 2 }] }` |
464
- | `$or` | Logical OR | `{ $or: [{ a: 1 }, { b: 2 }] }` |
465
- | `$not` | Logical NOT | `{ $not: { status: 'deleted' } }` |
466
- | `$nor` | Neither | `{ $nor: [{ a: 1 }, { b: 2 }] }` |
644
+ | `$exists` | Field exists (or not) | `{ email: { $exists: true } }` |
645
+ | `$type` | JavaScript `typeof` check | `{ age: { $type: 'number' } }` |
467
646
 
468
- ### Element Operators
647
+ ### Array
469
648
 
470
649
  | Operator | Description | Example |
471
650
  |----------|-------------|---------|
472
- | `$exists` | Field exists | `{ email: { $exists: true } }` |
473
- | `$type` | Type check | `{ age: { $type: 'number' } }` |
651
+ | `$all` | Array contains all values | `{ tags: { $all: ['js', 'db'] } }` |
652
+ | `$elemMatch` | At least one element matches | `{ items: { $elemMatch: { qty: { $gt: 5 } } } }` |
653
+ | `$size` | Array has exact length | `{ tags: { $size: 3 } }` |
474
654
 
475
- ### Array Operators
655
+ ### String
476
656
 
477
657
  | Operator | Description | Example |
478
658
  |----------|-------------|---------|
479
- | `$all` | Contains all | `{ tags: { $all: ['a', 'b'] } }` |
480
- | `$elemMatch` | Element match | `{ items: { $elemMatch: { qty: { $gt: 5 } } } }` |
481
- | `$size` | Array size | `{ tags: { $size: 3 } }` |
659
+ | `$regex` | Regular expression match | `{ name: { $regex: '^Alice' } }` |
660
+ | `$text` | Case-insensitive substring search | `{ bio: { $text: 'developer' } }` |
482
661
 
483
- ### String Operators
662
+ ### Geospatial
484
663
 
485
664
  | Operator | Description | Example |
486
665
  |----------|-------------|---------|
487
- | `$regex` | Regex match | `{ name: { $regex: '^John' } }` |
488
- | `$text` | Text search | `{ bio: { $text: 'developer' } }` |
666
+ | `$near` | Find points near coordinates | `{ location: { $near: { coordinates: { lat, lng }, maxDistance: 10 } } }` |
667
+ | `$within` | Find points within bounds | `{ location: { $within: { minLat, maxLat, minLng, maxLng } } }` |
668
+
669
+ > **Geo queries require** a geo index on the field. See [Indexing](#indexing).
489
670
 
490
671
  <details>
491
- <summary><strong>📝 Complex Query Examples</strong></summary>
672
+ <summary><strong>Complex Query Examples</strong></summary>
492
673
 
493
674
  ```javascript
494
- // Find active users over 18 with verified email
675
+ // Active users over 18 with verified email
495
676
  const users = await collection.query({
496
677
  $and: [
497
678
  { status: 'active' },
@@ -500,7 +681,7 @@ const users = await collection.query({
500
681
  ]
501
682
  });
502
683
 
503
- // Find products in price range with specific tags
684
+ // Products in price range with specific tags
504
685
  const products = await collection.query({
505
686
  $and: [
506
687
  { price: { $gte: 10, $lte: 100 } },
@@ -509,7 +690,13 @@ const products = await collection.query({
509
690
  ]
510
691
  });
511
692
 
512
- // Text search with regex
693
+ // Dot notation for nested fields
694
+ const docs = await collection.query({
695
+ 'address.city': 'Zurich',
696
+ 'metadata.version': { $gte: 2 }
697
+ });
698
+
699
+ // Combined text and regex search
513
700
  const articles = await collection.query({
514
701
  $or: [
515
702
  { title: { $regex: 'blockchain' } },
@@ -524,30 +711,41 @@ const articles = await collection.query({
524
711
 
525
712
  ## Aggregation Pipeline
526
713
 
527
- Transform and analyze data using pipeline stages.
714
+ Transform and analyze data using a sequence of pipeline stages. Each stage receives the output of the previous stage.
715
+
716
+ ```javascript
717
+ const results = await collection.aggregate([
718
+ { $match: { ... } }, // Filter
719
+ { $group: { ... } }, // Group & accumulate
720
+ { $sort: { ... } }, // Order
721
+ { $limit: 10 } // Trim
722
+ ]);
723
+ ```
724
+
725
+ ### Stages
528
726
 
529
727
  | Stage | Description | Example |
530
728
  |-------|-------------|---------|
531
- | `$match` | Filter documents | `{ $match: { status: 'active' } }` |
532
- | `$project` | Shape output | `{ $project: { name: 1, email: 1 } }` |
533
- | `$sort` | Order results | `{ $sort: { date: -1 } }` |
534
- | `$limit` | Limit results | `{ $limit: 10 }` |
535
- | `$skip` | Skip results | `{ $skip: 20 }` |
536
- | `$group` | Group & aggregate | `{ $group: { _id: '$category', count: { $count: 1 } } }` |
537
- | `$lookup` | Join collections | See example below |
729
+ | `$match` | Filter documents (same syntax as `query`) | `{ $match: { status: 'active' } }` |
730
+ | `$project` | Include/exclude fields | `{ $project: { name: 1, email: 1 } }` |
731
+ | `$sort` | Order results (1 = asc, -1 = desc) | `{ $sort: { date: -1 } }` |
732
+ | `$limit` | Take first N results | `{ $limit: 10 }` |
733
+ | `$skip` | Skip first N results | `{ $skip: 20 }` |
734
+ | `$group` | Group by field and accumulate | See below |
735
+ | `$lookup` | Join with another collection | See below |
538
736
 
539
737
  ### Group Accumulators
540
738
 
541
- | Accumulator | Description |
542
- |-------------|-------------|
543
- | `$sum` | Sum values |
544
- | `$avg` | Average values |
545
- | `$min` | Minimum value |
546
- | `$max` | Maximum value |
547
- | `$count` | Count documents |
739
+ | Accumulator | Description | Example |
740
+ |-------------|-------------|---------|
741
+ | `$sum` | Sum of field values | `{ total: { $sum: '$amount' } }` |
742
+ | `$avg` | Average of field values | `{ avgPrice: { $avg: '$price' } }` |
743
+ | `$min` | Minimum value | `{ cheapest: { $min: '$price' } }` |
744
+ | `$max` | Maximum value | `{ mostExpensive: { $max: '$price' } }` |
745
+ | `$count` | Count of documents in group | `{ orderCount: { $count: 1 } }` |
548
746
 
549
747
  <details>
550
- <summary><strong>📊 Aggregation Examples</strong></summary>
748
+ <summary><strong>Aggregation Examples</strong></summary>
551
749
 
552
750
  ```javascript
553
751
  // Sales report by category
@@ -563,26 +761,20 @@ const report = await orders.aggregate([
563
761
  { $limit: 10 }
564
762
  ]);
565
763
 
566
- // Join users with their orders
764
+ // Join users with their orders ($lookup)
567
765
  const usersWithOrders = await users.aggregate([
568
766
  { $lookup: {
569
- from: 'orders',
570
- localField: '_id',
571
- foreignField: 'userId',
572
- as: 'orders'
767
+ from: 'orders', // Foreign collection name
768
+ localField: '_id', // Field in current collection
769
+ foreignField: 'userId', // Field in foreign collection
770
+ as: 'orders' // Output array field name
573
771
  }},
574
- { $project: {
575
- name: 1,
576
- email: 1,
577
- orderCount: { $size: '$orders' }
578
- }}
772
+ { $project: { name: 1, email: 1, orders: 1 } }
579
773
  ]);
580
774
 
581
775
  // Top customers this month
582
776
  const topCustomers = await orders.aggregate([
583
- { $match: {
584
- date: { $gte: startOfMonth }
585
- }},
777
+ { $match: { date: { $gte: startOfMonth } } },
586
778
  { $group: {
587
779
  _id: '$customerId',
588
780
  total: { $sum: '$amount' },
@@ -599,63 +791,83 @@ const topCustomers = await orders.aggregate([
599
791
 
600
792
  ## Indexing
601
793
 
602
- Indexes dramatically improve query performance for large collections.
794
+ Indexes dramatically improve query performance by avoiding full collection scans. LacertaDB supports four index types, each optimized for different query patterns.
603
795
 
604
796
  ### Index Types
605
797
 
606
- | Type | Best For | Example |
607
- |------|----------|---------|
608
- | `btree` | Range queries, sorting | `{ type: 'btree' }` |
609
- | `hash` | Exact match lookups | `{ type: 'hash' }` |
610
- | `text` | Full-text search | `{ type: 'text' }` |
611
- | `geo` | Location queries | `{ type: 'geo' }` |
798
+ | Type | Complexity | Best For | Query Operators |
799
+ |------|-----------|----------|-----------------|
800
+ | `btree` | O(log N) | Range queries, sorting, equality | `$eq`, `$gt`, `$gte`, `$lt`, `$lte` |
801
+ | `hash` | O(1) | Exact match, `$in` lookups | `$eq`, `$in` |
802
+ | `text` | O(tokens) | Full-text search (CJK-aware) | `$search` |
803
+ | `geo` | O(log N) | Location queries (QuadTree) | `$near`, `$within` |
612
804
 
613
805
  ### Creating Indexes
614
806
 
615
807
  ```javascript
616
- // B-Tree index (default)
808
+ // B-Tree index (default) — best for range queries
617
809
  await collection.createIndex('email', { unique: true });
810
+ await collection.createIndex('createdAt');
618
811
 
619
- // Hash index for fast lookups
812
+ // Hash index — fastest for exact-match lookups
620
813
  await collection.createIndex('userId', { type: 'hash' });
621
814
 
622
- // Text index for search
815
+ // Text index — full-text search with Intl.Segmenter for CJK support
623
816
  await collection.createIndex('content', { type: 'text' });
624
817
 
625
- // Geo index for locations
818
+ // Geo index QuadTree-backed spatial queries
626
819
  await collection.createIndex('location', { type: 'geo' });
627
820
 
628
- // Sparse index (skip null values)
821
+ // Sparse index skip documents where field is null/undefined
629
822
  await collection.createIndex('optionalField', { sparse: true });
823
+
824
+ // Hashed B-Tree — hash values before inserting into B-Tree
825
+ await collection.createIndex('sensitiveField', { hashed: true });
630
826
  ```
631
827
 
632
828
  ### Index Options
633
829
 
634
830
  | Option | Type | Default | Description |
635
831
  |--------|------|---------|-------------|
636
- | `name` | `string` | fieldPath | Custom index name |
637
- | `type` | `string` | `'btree'` | Index type |
638
- | `unique` | `boolean` | `false` | Enforce uniqueness |
639
- | `sparse` | `boolean` | `false` | Skip null/undefined |
832
+ | `name` | `string` | `fieldPath` | Custom index name |
833
+ | `type` | `'btree' \| 'hash' \| 'text' \| 'geo'` | `'btree'` | Index structure |
834
+ | `unique` | `boolean` | `false` | Reject duplicate values |
835
+ | `sparse` | `boolean` | `false` | Skip null/undefined fields |
836
+ | `hashed` | `boolean` | `false` | SHA-256 hash values before indexing |
837
+ | `collation` | `Object \| null` | `null` | Reserved for future locale-aware sorting |
838
+
839
+ ### Index Management
840
+
841
+ ```javascript
842
+ // Get index statistics
843
+ const stats = await collection.getIndexes();
844
+ // { email: { fieldPath: 'email', type: 'btree', unique: true, size: 1500, memoryUsage: 180000 } }
845
+
846
+ // Verify integrity (auto-rebuilds if corrupted)
847
+ const report = await collection.verifyIndexes();
848
+ // { email: { healthy: true, issues: [], repaired: 0 } }
849
+
850
+ // Drop an index
851
+ await collection.dropIndex('email');
852
+ ```
640
853
 
641
854
  <details>
642
- <summary><strong>🌍 Geospatial Queries</strong></summary>
855
+ <summary><strong>Geospatial Queries</strong></summary>
643
856
 
644
857
  ```javascript
645
- // Create geo index
646
858
  await places.createIndex('coordinates', { type: 'geo' });
647
859
 
648
- // Find nearby (within 10km)
860
+ // Find places within 10km of Zurich
649
861
  const nearby = await places.query({
650
862
  coordinates: {
651
863
  $near: {
652
864
  coordinates: { lat: 47.3769, lng: 8.5417 },
653
- maxDistance: 10 // kilometers
865
+ maxDistance: 10 // kilometers (Haversine distance)
654
866
  }
655
867
  }
656
868
  });
657
869
 
658
- // Find within bounds
870
+ // Find places within a bounding box
659
871
  const inArea = await places.query({
660
872
  coordinates: {
661
873
  $within: {
@@ -674,18 +886,39 @@ const inArea = await places.query({
674
886
 
675
887
  ## Encryption
676
888
 
677
- LacertaDB provides **AES-GCM-256** encryption with **PBKDF2** key derivation.
889
+ LacertaDB provides **AES-GCM-256** encryption with a **Master Key Wrapping** architecture powered entirely by the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API).
678
890
 
679
891
  ### Security Specifications
680
892
 
681
893
  | Parameter | Value |
682
894
  |-----------|-------|
683
- | Algorithm | AES-GCM-256 |
684
- | Key Derivation | PBKDF2 |
685
- | Iterations | 100,000 |
686
- | Hash | SHA-256 |
687
- | Salt Length | 32 bytes |
688
- | IV Length | 12 bytes |
895
+ | **Encryption Algorithm** | AES-GCM-256 |
896
+ | **Key Derivation** | PBKDF2 |
897
+ | **PBKDF2 Iterations** | 600,000 (OWASP 2024 recommendation) |
898
+ | **Hash Function** | SHA-256 |
899
+ | **Salt Length** | 32 bytes (256 bits) |
900
+ | **IV Length** | 12 bytes (96 bits, NIST SP 800-38D) |
901
+ | **HMAC** | HMAC-SHA-256 (32 bytes) on every encrypted document |
902
+ | **PIN Verification** | Constant-time comparison (timing-attack resistant) |
903
+
904
+ ### How Master Key Wrapping Works
905
+
906
+ Unlike simple password-derived encryption, LacertaDB separates the *data encryption key* from the *user's PIN*:
907
+
908
+ ```
909
+ User PIN → PBKDF2 (600k iterations) → KEK (Key Encryption Key)
910
+
911
+ Random Master Key (256-bit) ←── unwrap ──────┘
912
+ Random HMAC Key (256-bit) ←── unwrap ──────┘
913
+ │ │
914
+ ├── encrypts documents ├── signs encrypted documents
915
+ └── encrypts private keys └── verifies on read
916
+ ```
917
+
918
+ **Benefits:**
919
+ - **PIN change is instant** — only re-wraps the master key, no document re-encryption needed
920
+ - **Master key is cryptographically random** — not derived from a potentially weak PIN
921
+ - **Separate HMAC key** — tamper detection is independent of encryption
689
922
 
690
923
  ### Usage
691
924
 
@@ -693,83 +926,151 @@ LacertaDB provides **AES-GCM-256** encryption with **PBKDF2** key derivation.
693
926
  // Create encrypted database
694
927
  const db = await lacerta.getSecureDatabase('vault', 'mySecretPin123');
695
928
 
696
- // All documents are automatically encrypted
929
+ // All documents in all collections are automatically encrypted
697
930
  const secrets = await db.createCollection('secrets');
698
- await secrets.add({
931
+ await secrets.add({
699
932
  apiKey: 'sk-live-xxx',
700
933
  privateData: 'sensitive information'
701
934
  });
702
935
 
703
- // Verify PIN
704
- const isValid = await db.verifyPin('mySecretPin123'); // true
705
-
706
- // Change PIN (re-encrypts ALL data)
936
+ // Change PIN (instant — only re-wraps master key)
707
937
  await db.changePin('mySecretPin123', 'newStrongerPin!');
708
-
709
- // Check encryption status
710
- const status = db.getEncryptionStatus();
711
- // { isEncrypted: true, initialized: true, algorithm: 'AES-GCM-256', kdf: 'PBKDF2' }
712
938
  ```
713
939
 
714
940
  ### Private Key Vault
715
941
 
716
- Store cryptographic keys securely:
942
+ Store blockchain private keys, mnemonics, or other secrets with an additional authentication data layer:
717
943
 
718
944
  ```javascript
719
- // Store a private key
720
- await db.storePrivateKey('wallet-main', privateKeyString, 'optionalAuth');
945
+ // Store with optional additional authentication data (AAD)
946
+ await db.storePrivateKey('wallet-main', privateKeyString, 'user@example.com');
721
947
 
722
- // Retrieve
723
- const key = await db.getPrivateKey('wallet-main', 'optionalAuth');
948
+ // Retrieve — AAD must match exactly
949
+ const key = await db.getPrivateKey('wallet-main', 'user@example.com');
950
+ ```
951
+
952
+ > **AAD** (Additional Authentication Data) is bound to the ciphertext via AES-GCM. If the AAD doesn't match on decryption, the operation fails even with the correct master key. Use it to bind keys to a specific context (user email, device ID, etc.).
724
953
 
725
- // List stored keys
726
- const keys = await db.listPrivateKeys();
727
- // [{ name: 'wallet-main', createdAt: 1699..., updatedAt: null }]
954
+ ### Secure PIN Generation
955
+
956
+ ```javascript
957
+ import { SecureDatabaseEncryption } from '@pixagram/lacerta-db';
728
958
 
729
- // Delete
730
- await db.deletePrivateKey('wallet-main');
959
+ // Generate a cryptographically random, unbiased 6-digit PIN
960
+ const pin = SecureDatabaseEncryption.generateSecurePIN(6); // e.g., '839201'
961
+ const longPin = SecureDatabaseEncryption.generateSecurePIN(12); // e.g., '483920173856'
731
962
  ```
732
963
 
733
964
  ---
734
965
 
735
966
  ## Caching
736
967
 
737
- Configure caching to optimize read performance.
968
+ Each collection has an independent, configurable query cache that avoids redundant IndexedDB reads. The cache is automatically invalidated after any write operation (add, update, delete).
738
969
 
739
- ### Cache Strategies
970
+ ### Strategies
740
971
 
741
- | Strategy | Description | Best For |
742
- |----------|-------------|----------|
743
- | `lru` | Least Recently Used | General purpose |
744
- | `lfu` | Least Frequently Used | Hot data patterns |
745
- | `ttl` | Time To Live | Expiring data |
746
- | `none` | Disabled | Write-heavy workloads |
972
+ | Strategy | Eviction Policy | Best For |
973
+ |----------|----------------|----------|
974
+ | `lru` | Evicts least recently accessed item | General purpose, read-heavy workloads |
975
+ | `lfu` | Evicts least frequently accessed item | Hot/cold data with stable access patterns |
976
+ | `ttl` | Evicts after fixed time-to-live expires | Data that becomes stale after a known period |
977
+ | `none` | Caching disabled | Write-heavy workloads, memory-constrained |
747
978
 
748
979
  ### Configuration
749
980
 
750
981
  ```javascript
751
- // Configure cache per collection
752
982
  collection.configureCacheStrategy({
753
- type: 'lru', // 'lru' | 'lfu' | 'ttl' | 'none'
754
- maxSize: 200, // Max cached items
755
- ttl: 120000, // TTL in milliseconds
756
- enabled: true
983
+ type: 'lru', // 'lru' | 'lfu' | 'ttl' | 'none'
984
+ maxSize: 200, // Maximum number of cached query results
985
+ ttl: 120000, // Time-to-live in milliseconds (applies to LRU, LFU, and TTL)
986
+ enabled: true // Set to false to disable
757
987
  });
758
988
 
759
- // Clear cache manually
989
+ // Manually clear cache
760
990
  collection.clearCache();
761
991
  ```
762
992
 
993
+ > **Default:** Every collection starts with an LRU cache of 100 entries and a 60-second TTL.
994
+
995
+ ---
996
+
997
+ ## QuickStore
998
+
999
+ QuickStore provides **synchronous** key-value access backed by localStorage, ideal for user preferences, session tokens, feature flags, and other small data that needs to be available immediately without `await`.
1000
+
1001
+ ```javascript
1002
+ const quick = db.quickStore;
1003
+
1004
+ // Synchronous CRUD
1005
+ quick.add('user-pref', { theme: 'dark', language: 'en' });
1006
+ const prefs = quick.get('user-pref'); // null if not found
1007
+ quick.update('user-pref', { theme: 'light', language: 'en' });
1008
+ quick.delete('user-pref');
1009
+
1010
+ // Query (same MongoDB-style operators as Collection)
1011
+ const darkThemePrefs = quick.query({ theme: 'dark' });
1012
+
1013
+ // Get all documents
1014
+ const all = quick.getAll(); // [{ _id: 'user-pref', theme: 'light', ... }, ...]
1015
+
1016
+ // Size and cleanup
1017
+ console.log(quick.size); // number of stored items
1018
+ quick.clear(); // remove all QuickStore data
1019
+ ```
1020
+
1021
+ **Implementation details:**
1022
+ - Index is kept in memory (Set) to avoid parsing on every operation
1023
+ - Index is persisted to localStorage via `requestIdleCallback` (debounced)
1024
+ - `beforeunload` listener flushes pending index writes synchronously
1025
+ - Data is serialized via TurboSerial and base64-encoded
1026
+
1027
+ > **Capacity:** localStorage is typically limited to 5–10 MB. Use Collections (IndexedDB) for larger datasets.
1028
+
1029
+ ---
1030
+
1031
+ ## Binary Attachments
1032
+
1033
+ LacertaDB stores binary files (images, PDFs, videos, etc.) in the **Origin Private File System** (OPFS), separate from document data in IndexedDB.
1034
+
1035
+ ```javascript
1036
+ // Add document with file attachments
1037
+ const fileInput = document.querySelector('input[type="file"]');
1038
+ const docId = await collection.add(
1039
+ { title: 'Report Q4', author: 'Alice' },
1040
+ { attachments: Array.from(fileInput.files) }
1041
+ );
1042
+
1043
+ // Retrieve document with attachments
1044
+ const doc = await collection.get(docId, { includeAttachments: true });
1045
+ doc._attachments.forEach(att => {
1046
+ console.log(att.name, att.type, att.size);
1047
+ // att.data is a Uint8Array
1048
+ });
1049
+
1050
+ // Prepare attachments programmatically
1051
+ import { OPFSUtility } from '@pixagram/lacerta-db';
1052
+
1053
+ const attachment = await OPFSUtility.prepareAttachment(
1054
+ new Blob(['Hello'], { type: 'text/plain' }),
1055
+ 'greeting.txt'
1056
+ );
1057
+ await collection.add({ title: 'Test' }, { attachments: [attachment] });
1058
+ ```
1059
+
1060
+ > **Note:** OPFS support varies by browser. Safari has partial support. See [Browser Compatibility](#browser-compatibility).
1061
+
763
1062
  ---
764
1063
 
765
1064
  ## Migrations
766
1065
 
767
- Manage schema changes across versions.
1066
+ Manage schema changes across application versions. Migrations run per-document across all collections, with support for rollback.
768
1067
 
769
1068
  ```javascript
1069
+ import { MigrationManager } from '@pixagram/lacerta-db';
1070
+
770
1071
  const migration = new MigrationManager(db);
771
1072
 
772
- // Define migrations
1073
+ // Define forward and backward migrations
773
1074
  migration.addMigration({
774
1075
  version: '1.1.0',
775
1076
  name: 'Add user roles',
@@ -786,134 +1087,146 @@ migration.addMigration({
786
1087
 
787
1088
  migration.addMigration({
788
1089
  version: '1.2.0',
789
- name: 'Normalize email',
1090
+ name: 'Normalize emails',
790
1091
  up: async (doc) => ({
791
1092
  ...doc,
792
1093
  email: doc.email?.toLowerCase()
793
1094
  }),
794
- down: async (doc) => doc // No rollback needed
1095
+ down: async (doc) => doc
795
1096
  });
796
1097
 
797
- // Run migrations
1098
+ // Run all migrations up to target version
798
1099
  await migration.runMigrations('1.2.0');
799
1100
 
800
- // Rollback if needed
1101
+ // Rollback to a previous version
801
1102
  await migration.rollback('1.0.0');
1103
+
1104
+ // Check current version
1105
+ console.log(migration.currentVersion); // '1.2.0'
802
1106
  ```
803
1107
 
1108
+ > **How it works:** Migrations are applied in semver order. Each migration's `up` function receives a document and returns the transformed document (or `null` to skip). The current version is persisted in localStorage.
1109
+
804
1110
  ---
805
1111
 
806
1112
  ## Performance Monitoring
807
1113
 
808
- Built-in performance tracking and optimization.
1114
+ Built-in performance tracking with real-time metrics and optimization suggestions.
809
1115
 
810
1116
  ```javascript
811
- // Start monitoring
812
- lacerta.performanceMonitor.startMonitoring();
1117
+ const monitor = lacerta.performanceMonitor;
1118
+
1119
+ // Start collecting metrics
1120
+ monitor.startMonitoring();
1121
+
1122
+ // ... perform operations ...
813
1123
 
814
- // Get statistics
815
- const stats = lacerta.performanceMonitor.getStats();
816
- console.log(stats);
1124
+ // Get real-time statistics
1125
+ const stats = monitor.getStats();
817
1126
  // {
818
- // opsPerSec: 150,
819
- // avgLatency: '2.34',
820
- // cacheHitRate: '87.5',
821
- // memoryUsageMB: '45.20'
1127
+ // opsPerSec: 150, // Operations in the last second
1128
+ // avgLatency: '2.34', // Average operation latency in ms
1129
+ // cacheHitRate: '87.5', // Cache hit rate percentage
1130
+ // memoryUsageMB: '45.20' // JS heap usage (Chrome only)
822
1131
  // }
823
1132
 
824
- // Get optimization suggestions
825
- const tips = lacerta.performanceMonitor.getOptimizationTips();
1133
+ // Get automated optimization tips
1134
+ const tips = monitor.getOptimizationTips();
826
1135
  // ['Performance is optimal. No issues detected.']
1136
+ // or: ['High average latency detected. Consider enabling compression and indexing...']
1137
+ // or: ['Low cache hit rate. Consider increasing cache size or optimizing query patterns.']
827
1138
 
828
1139
  // Stop monitoring
829
- lacerta.performanceMonitor.stopMonitoring();
1140
+ monitor.stopMonitoring();
830
1141
  ```
831
1142
 
1143
+ > **Note:** `memoryUsageMB` relies on `performance.memory` which is only available in Chromium-based browsers.
1144
+
832
1145
  ---
833
1146
 
834
- ## Error Codes
835
-
836
- | Code | Description | Solution |
837
- |------|-------------|----------|
838
- | `DOCUMENT_NOT_FOUND` | Document doesn't exist | Verify document ID |
839
- | `COLLECTION_NOT_FOUND` | Collection doesn't exist | Create collection first |
840
- | `COLLECTION_EXISTS` | Collection already exists | Use getCollection instead |
841
- | `ENCRYPTION_NOT_INITIALIZED` | Encryption required but not set up | Use getSecureDatabase |
842
- | `ENCRYPTION_REQUIRED` | Document encrypted, db not unlocked | Unlock with correct PIN |
843
- | `INVALID_PIN` | Wrong PIN provided | Verify PIN |
844
- | `NOT_ENCRYPTED` | Operation requires encryption | Use encrypted database |
845
- | `PERMANENT_DOCUMENT_PROTECTION` | Cannot delete permanent document | Use force: true |
846
- | `QUOTA_EXCEEDED` | Storage limit reached | Free up space |
847
- | `MUTEX_TIMEOUT` | Operation timed out | Retry or check deadlock |
848
- | `TRANSACTION_FAILED` | IndexedDB transaction failed | Retry operation |
849
- | `INDEX_EXISTS` | Index already exists | Drop existing index first |
850
- | `UNIQUE_CONSTRAINT_VIOLATION` | Duplicate value in unique index | Use unique values |
851
-
852
- ### Error Handling
1147
+ ## Error Handling
1148
+
1149
+ All LacertaDB errors are instances of `LacertaDBError` with a machine-readable `code`, human-readable `message`, and ISO `timestamp`.
853
1150
 
854
1151
  ```javascript
855
- import { LacertaDBError } from '@pixagram/lacertadb';
1152
+ import { LacertaDBError } from '@pixagram/lacerta-db';
856
1153
 
857
1154
  try {
858
1155
  await collection.get('nonexistent');
859
1156
  } catch (error) {
860
1157
  if (error instanceof LacertaDBError) {
861
- console.log(error.code); // 'DOCUMENT_NOT_FOUND'
862
- console.log(error.message); // 'Document with id...'
863
- console.log(error.timestamp); // ISO date string
1158
+ console.log(error.code); // 'DOCUMENT_NOT_FOUND'
1159
+ console.log(error.message); // 'Document with id ...'
1160
+ console.log(error.timestamp); // '2025-01-15T12:00:00.000Z'
1161
+ console.log(error.originalError); // Underlying error (if any)
864
1162
  }
865
1163
  }
866
1164
  ```
867
1165
 
1166
+ ### Error Codes
1167
+
1168
+ | Code | Description | Common Cause |
1169
+ |------|-------------|--------------|
1170
+ | `DOCUMENT_NOT_FOUND` | Document does not exist | Invalid or deleted document ID |
1171
+ | `COLLECTION_NOT_FOUND` | Collection does not exist | Typo in collection name, or not yet created |
1172
+ | `COLLECTION_EXISTS` | Collection already exists | Use `getCollection` instead of `createCollection` |
1173
+ | `ENCRYPTION_NOT_INITIALIZED` | Document encryption requested without database encryption | Use `getSecureDatabase()` |
1174
+ | `PERMANENT_DOCUMENT_PROTECTION` | Cannot delete a permanent document | Pass `{ force: true }` to `delete()` |
1175
+ | `QUOTA_EXCEEDED` | localStorage storage limit reached | Clear QuickStore data or reduce usage |
1176
+ | `TRANSACTION_FAILED` | IndexedDB transaction failed after retries | Check for database corruption or concurrent access |
1177
+ | `DATABASE_OPEN_FAILED` | Failed to open IndexedDB connection | Browser may be in private mode or storage disabled |
1178
+ | `PACK_FAILED` | Document serialization/compression/encryption failed | Check data types and encryption state |
1179
+ | `IMPORT_PARSE_FAILED` | Import data could not be parsed | Corrupted or incompatible backup data |
1180
+ | `INVALID_FORMAT` | Unsupported export format | Use `'json'` or `'encrypted'` |
1181
+ | `ATTACHMENT_SAVE_FAILED` | OPFS write failed | OPFS not supported or storage full |
1182
+ | `SYNC_DECRYPT_NOT_SUPPORTED` | Called `unpackSync()` on encrypted document | Use async `unpack()` instead |
1183
+
868
1184
  ---
869
1185
 
870
1186
  ## Examples
871
1187
 
872
1188
  <details>
873
- <summary><strong>📱 User Management System</strong></summary>
1189
+ <summary><strong>User Management System</strong></summary>
874
1190
 
875
1191
  ```javascript
876
1192
  const lacerta = new LacertaDB();
877
1193
  const db = await lacerta.getSecureDatabase('app', 'adminPin123');
878
1194
 
879
- // Create collections
1195
+ // Create collections with indexes
880
1196
  const users = await db.createCollection('users');
881
1197
  const sessions = await db.createCollection('sessions');
882
1198
 
883
- // Create indexes
884
1199
  await users.createIndex('email', { unique: true });
885
- await users.createIndex('username', { unique: true });
886
- await sessions.createIndex('userId');
1200
+ await sessions.createIndex('userId', { type: 'hash' });
887
1201
  await sessions.createIndex('expiresAt');
888
1202
 
889
1203
  // Register user
890
1204
  async function registerUser(data) {
891
- const userId = await users.add({
1205
+ return await users.add({
892
1206
  ...data,
893
1207
  email: data.email.toLowerCase(),
894
1208
  createdAt: Date.now(),
895
1209
  status: 'pending'
896
1210
  });
897
- return userId;
898
1211
  }
899
1212
 
900
- // Login
901
- async function login(email, password) {
902
- const [user] = await users.query({
1213
+ // Login — create session
1214
+ async function login(email, passwordHash) {
1215
+ const [user] = await users.query({
903
1216
  email: email.toLowerCase(),
904
1217
  status: 'active'
905
1218
  });
906
-
907
- if (!user || !verifyPassword(password, user.passwordHash)) {
1219
+
1220
+ if (!user || user.passwordHash !== passwordHash) {
908
1221
  throw new Error('Invalid credentials');
909
1222
  }
910
-
1223
+
911
1224
  const sessionId = await sessions.add({
912
1225
  userId: user._id,
913
1226
  createdAt: Date.now(),
914
1227
  expiresAt: Date.now() + 24 * 60 * 60 * 1000
915
1228
  });
916
-
1229
+
917
1230
  return { user, sessionId };
918
1231
  }
919
1232
 
@@ -929,58 +1242,48 @@ async function cleanupSessions() {
929
1242
  </details>
930
1243
 
931
1244
  <details>
932
- <summary><strong>🛒 E-commerce Cart</strong></summary>
1245
+ <summary><strong>E-Commerce Cart</strong></summary>
933
1246
 
934
1247
  ```javascript
935
1248
  const db = await lacerta.getDatabase('shop');
936
1249
  const carts = await db.createCollection('carts');
937
1250
  const products = await db.createCollection('products');
938
1251
 
939
- // Add to cart
1252
+ await carts.createIndex('userId', { type: 'hash' });
1253
+
1254
+ // Add to cart (upsert pattern)
940
1255
  async function addToCart(userId, productId, quantity) {
941
- const [existing] = await carts.query({
942
- userId,
943
- productId
944
- });
945
-
1256
+ const [existing] = await carts.query({ userId, productId });
1257
+
946
1258
  if (existing) {
947
1259
  await carts.update(existing._id, {
948
1260
  quantity: existing.quantity + quantity,
949
1261
  updatedAt: Date.now()
950
1262
  });
951
1263
  } else {
952
- await carts.add({
953
- userId,
954
- productId,
955
- quantity,
956
- addedAt: Date.now()
957
- });
1264
+ await carts.add({ userId, productId, quantity, addedAt: Date.now() });
958
1265
  }
959
1266
  }
960
1267
 
961
- // Get cart with product details
1268
+ // Cart with product details
962
1269
  async function getCart(userId) {
963
- const cartItems = await carts.query({ userId });
964
-
1270
+ const items = await carts.query({ userId });
1271
+
965
1272
  const enriched = await Promise.all(
966
- cartItems.map(async (item) => {
1273
+ items.map(async (item) => {
967
1274
  const product = await products.get(item.productId);
968
- return {
969
- ...item,
970
- product,
971
- subtotal: product.price * item.quantity
972
- };
1275
+ return { ...item, product, subtotal: product.price * item.quantity };
973
1276
  })
974
1277
  );
975
-
1278
+
976
1279
  return {
977
1280
  items: enriched,
978
1281
  total: enriched.reduce((sum, i) => sum + i.subtotal, 0)
979
1282
  };
980
1283
  }
981
1284
 
982
- // Cart analytics
983
- async function getCartAnalytics() {
1285
+ // Cart analytics with aggregation
1286
+ async function getTopProducts() {
984
1287
  return await carts.aggregate([
985
1288
  { $group: {
986
1289
  _id: '$productId',
@@ -996,17 +1299,17 @@ async function getCartAnalytics() {
996
1299
  </details>
997
1300
 
998
1301
  <details>
999
- <summary><strong>📍 Location-Based Service</strong></summary>
1302
+ <summary><strong>Location-Based Service</strong></summary>
1000
1303
 
1001
1304
  ```javascript
1002
1305
  const db = await lacerta.getDatabase('geo');
1003
1306
  const places = await db.createCollection('places');
1004
1307
 
1005
- // Create geo index
1308
+ // Create geo and text indexes
1006
1309
  await places.createIndex('location', { type: 'geo' });
1007
1310
  await places.createIndex('name', { type: 'text' });
1008
1311
 
1009
- // Add place
1312
+ // Add a place
1010
1313
  async function addPlace(data) {
1011
1314
  return await places.add({
1012
1315
  name: data.name,
@@ -1017,7 +1320,7 @@ async function addPlace(data) {
1017
1320
  });
1018
1321
  }
1019
1322
 
1020
- // Find nearby restaurants
1323
+ // Find nearby restaurants within 5km
1021
1324
  async function findNearbyRestaurants(lat, lng, radiusKm = 5) {
1022
1325
  return await places.query({
1023
1326
  location: {
@@ -1032,7 +1335,7 @@ async function findNearbyRestaurants(lat, lng, radiusKm = 5) {
1032
1335
  });
1033
1336
  }
1034
1337
 
1035
- // Search places by name
1338
+ // Search places by name within a bounding box
1036
1339
  async function searchPlaces(query, bounds) {
1037
1340
  return await places.query({
1038
1341
  $and: [
@@ -1046,18 +1349,18 @@ async function searchPlaces(query, bounds) {
1046
1349
  </details>
1047
1350
 
1048
1351
  <details>
1049
- <summary><strong>🔐 Wallet Key Management</strong></summary>
1352
+ <summary><strong>Blockchain Wallet Key Management</strong></summary>
1050
1353
 
1051
1354
  ```javascript
1052
1355
  const db = await lacerta.getSecureDatabase('wallet', userPin);
1053
1356
 
1054
- // Store wallet keys
1357
+ // Store wallet keys with additional authentication
1055
1358
  async function storeWallet(walletName, privateKey, mnemonic) {
1056
- await db.storePrivateKey(`${walletName}-key`, privateKey);
1057
- await db.storePrivateKey(`${walletName}-mnemonic`, mnemonic);
1058
-
1059
- // Store metadata (not the actual keys)
1060
- const wallets = await db.getCollection('wallets');
1359
+ await db.storePrivateKey(`${walletName}-key`, privateKey, walletName);
1360
+ await db.storePrivateKey(`${walletName}-mnemonic`, mnemonic, walletName);
1361
+
1362
+ // Store public metadata (not the actual keys)
1363
+ const wallets = await db.createCollection('wallets').catch(() => db.getCollection('wallets'));
1061
1364
  await wallets.add({
1062
1365
  name: walletName,
1063
1366
  address: deriveAddress(privateKey),
@@ -1065,66 +1368,106 @@ async function storeWallet(walletName, privateKey, mnemonic) {
1065
1368
  }, { id: walletName, permanent: true });
1066
1369
  }
1067
1370
 
1068
- // Sign transaction
1371
+ // Sign a transaction
1069
1372
  async function signTransaction(walletName, tx) {
1070
- const privateKey = await db.getPrivateKey(`${walletName}-key`);
1373
+ const privateKey = await db.getPrivateKey(`${walletName}-key`, walletName);
1071
1374
  return signWithKey(tx, privateKey);
1072
1375
  }
1073
1376
 
1074
- // Export wallet (encrypted)
1075
- async function exportWallet(walletName, exportPassword) {
1076
- const privateKey = await db.getPrivateKey(`${walletName}-key`);
1077
- const mnemonic = await db.getPrivateKey(`${walletName}-mnemonic`);
1078
-
1079
- const wallets = await db.getCollection('wallets');
1080
- const metadata = await wallets.get(walletName);
1081
-
1377
+ // Export encrypted backup
1378
+ async function exportWallet(exportPassword) {
1082
1379
  return await db.export('encrypted', exportPassword);
1083
1380
  }
1381
+
1382
+ // Import wallet from backup
1383
+ async function importWallet(backupData, exportPassword) {
1384
+ return await db.import(backupData, 'encrypted', exportPassword);
1385
+ }
1084
1386
  ```
1085
1387
 
1086
1388
  </details>
1087
1389
 
1088
- ---
1390
+ <details>
1391
+ <summary><strong>Backup and Restore</strong></summary>
1089
1392
 
1090
- ## QuickStore
1393
+ ```javascript
1394
+ const lacerta = new LacertaDB();
1091
1395
 
1092
- For simple, fast key-value access using localStorage:
1396
+ // Full backup of all databases (optionally encrypted)
1397
+ const backup = await lacerta.createBackup('backupPassword123');
1093
1398
 
1094
- ```javascript
1095
- const quick = db.quickStore;
1399
+ // Save backup string (e.g., download as file or send to server)
1400
+ downloadAsFile(backup, 'lacertadb-backup.dat');
1096
1401
 
1097
- // CRUD operations
1098
- quick.add('user-pref', { theme: 'dark', language: 'en' });
1099
- const prefs = quick.get('user-pref');
1100
- quick.update('user-pref', { ...prefs, theme: 'light' });
1101
- quick.delete('user-pref');
1402
+ // Restore from backup
1403
+ const result = await lacerta.restoreBackup(backupString, 'backupPassword123');
1404
+ console.log(`Restored ${result.databases} databases, ${result.collections} collections, ${result.documents} documents`);
1405
+ ```
1406
+
1407
+ </details>
1102
1408
 
1103
- // Query (supports same operators as Collection)
1104
- const results = quick.query({ theme: 'dark' });
1409
+ ---
1105
1410
 
1106
- // Get all
1107
- const all = quick.getAll();
1411
+ ## Exports
1108
1412
 
1109
- // Clear
1110
- quick.clear();
1413
+ All public classes are available as named exports:
1111
1414
 
1112
- // Size
1113
- console.log(quick.size); // number of items
1415
+ ```javascript
1416
+ import {
1417
+ // Core
1418
+ LacertaDB, // Top-level manager
1419
+ Database, // Database instance
1420
+ Collection, // Collection with CRUD, queries, indexes
1421
+ Document, // Document container
1422
+
1423
+ // Storage
1424
+ QuickStore, // Synchronous localStorage key-value store
1425
+ OPFSUtility, // Binary attachment storage (OPFS)
1426
+ IndexedDBConnectionPool, // Connection pooling for IndexedDB
1427
+
1428
+ // Indexing
1429
+ IndexManager, // Index lifecycle manager
1430
+ BTreeIndex, // B-Tree index implementation
1431
+ TextIndex, // Full-text inverted index
1432
+ GeoIndex, // QuadTree-backed spatial index
1433
+
1434
+ // Caching
1435
+ CacheStrategy, // Cache factory and wrapper
1436
+ LRUCache, // Least Recently Used cache
1437
+ LFUCache, // Least Frequently Used cache
1438
+ TTLCache, // Time-To-Live cache
1439
+
1440
+ // Security
1441
+ SecureDatabaseEncryption, // Master key wrapping + AES-GCM + HMAC
1442
+ BrowserEncryptionUtility, // Standalone password-based AES-GCM encryption
1443
+ BrowserCompressionUtility,// CompressionStream wrapper with magic bytes
1444
+
1445
+ // Utilities
1446
+ AsyncMutex, // Promise-based mutual exclusion lock
1447
+ MigrationManager, // Schema version management
1448
+ PerformanceMonitor, // Metrics collection and optimization tips
1449
+ LacertaDBError // Custom error class with codes
1450
+ } from '@pixagram/lacerta-db';
1114
1451
  ```
1115
1452
 
1116
- > ⚠️ **Note:** QuickStore uses localStorage which has ~5-10MB limit. Use Collections for larger datasets.
1117
-
1118
1453
  ---
1119
1454
 
1120
1455
  ## Browser Compatibility
1121
1456
 
1122
- | Browser | IndexedDB | OPFS | CompressionStream |
1123
- |---------|-----------|------|-------------------|
1124
- | Chrome 86+ | | | |
1125
- | Firefox 111+ | ✅ | ✅ | ✅ |
1126
- | Safari 15.4+ | | ⚠️ Partial | |
1127
- | Edge 86+ | | | |
1457
+ LacertaDB requires a modern browser with IndexedDB, Web Crypto API, and CompressionStream support.
1458
+
1459
+ | Feature | Chrome | Firefox | Safari | Edge |
1460
+ |---------|--------|---------|--------|------|
1461
+ | IndexedDB | 24+ | 16+ | 10+ | 12+ |
1462
+ | Web Crypto API | 37+ | 34+ | 11+ | 12+ |
1463
+ | CompressionStream | 80+ | 113+ | 16.4+ | 80+ |
1464
+ | OPFS | 86+ | 111+ | 15.2+ (partial) | 86+ |
1465
+ | `Intl.Segmenter` (CJK text) | 87+ | ❌ (fallback used) | 15.4+ | 87+ |
1466
+ | `requestIdleCallback` | 47+ | 55+ | ❌ (polyfilled) | 12+ |
1467
+
1468
+ > **Minimum recommended:** Chrome/Edge 86+, Firefox 113+, Safari 16.4+
1469
+
1470
+ > **Graceful degradation:** When `CompressionStream` is unavailable, data is stored uncompressed with a raw marker byte. When `Intl.Segmenter` is unavailable, text tokenization falls back to regex-based word splitting. When `requestIdleCallback` is unavailable, `setTimeout(fn, 0)` is used.
1128
1471
 
1129
1472
  ---
1130
1473