@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/dist/browser.min.js +21 -4
- package/dist/index.min.js +21 -4
- package/index.js +110 -52
- package/package.json +1 -1
- package/readme.md +763 -420
- package/.idea/lacerta-db.iml +0 -8
- package/.idea/modules.xml +0 -8
- package/.idea/php.xml +0 -19
- package/.idea/workspace.xml +0 -61
- package/index (Copy).js +0 -3882
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
|
|
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.
|
|
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-
|
|
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 **
|
|
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
|
|
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
|
|
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
|
-
###
|
|
62
|
-
- **IndexedDB** backend with connection pooling
|
|
63
|
-
- **OPFS** for binary attachments
|
|
64
|
-
- **localStorage** QuickStore for fast
|
|
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
|
-
###
|
|
71
|
-
- **AES-GCM-256** encryption
|
|
72
|
-
- **PBKDF2** key derivation (
|
|
73
|
-
- **
|
|
74
|
-
-
|
|
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
|
-
###
|
|
82
|
-
- **MongoDB-style** query syntax
|
|
83
|
-
- **
|
|
84
|
-
- **
|
|
85
|
-
-
|
|
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
|
-
###
|
|
91
|
-
- **
|
|
92
|
-
- **
|
|
93
|
-
- **
|
|
94
|
-
-
|
|
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/
|
|
118
|
+
npm install @pixagram/lacerta-db
|
|
106
119
|
```
|
|
107
120
|
|
|
108
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 **
|
|
238
|
+
LacertaDB follows a **layered architecture** where each component encapsulates complexity while exposing a simple interface.
|
|
168
239
|
|
|
169
240
|
```
|
|
170
|
-
|
|
171
|
-
│
|
|
172
|
-
│
|
|
173
|
-
│ │
|
|
174
|
-
│ │
|
|
175
|
-
│ │ │
|
|
176
|
-
│ │ │
|
|
177
|
-
│ │ │ │
|
|
178
|
-
│ │ │ │ • Data (serialized)
|
|
179
|
-
│ │ │ │ • Metadata (_id, _created, _modified)
|
|
180
|
-
│ │ │ │ • Attachments (OPFS references) │ │ │ │
|
|
181
|
-
│ │ │
|
|
182
|
-
│ │ │
|
|
183
|
-
│ │ │ │ IndexManager│ │CacheStrategy│ │
|
|
184
|
-
│ │ │
|
|
185
|
-
│ │
|
|
186
|
-
│ │
|
|
187
|
-
│ │ │
|
|
188
|
-
│ │
|
|
189
|
-
│
|
|
190
|
-
│
|
|
191
|
-
│ │
|
|
192
|
-
│ │
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
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
|
-
|
|
286
|
+
### Data Flow
|
|
210
287
|
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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 (
|
|
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>` |
|
|
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` |
|
|
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
|
|
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
|
-
| `
|
|
268
|
-
| `
|
|
269
|
-
| `
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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 change — re-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` |
|
|
283
|
-
| `updateSettings` | `settings` | `void` | Update
|
|
284
|
-
| `export` | `format?`, `password?` | `Promise<string>` | Export
|
|
285
|
-
| `import` | `data`, `format?`, `password?` | `Promise<Object>` | Import data |
|
|
286
|
-
| `clearAll` | — | `Promise<void>` | Clear all collections |
|
|
287
|
-
| `destroy` | — | `
|
|
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` |
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
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>` |
|
|
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
|
-
|
|
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
|
|
327
|
-
| `
|
|
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
|
-
|
|
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<
|
|
343
|
-
| `batchUpdate` | `updates` | `Promise<
|
|
344
|
-
| `batchDelete` | `
|
|
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
|
|
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: '
|
|
357
|
-
{ id: '
|
|
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(['
|
|
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
|
|
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>
|
|
552
|
+
<summary><strong>Cache Configuration</strong></summary>
|
|
400
553
|
|
|
401
554
|
| Method | Parameters | Returns | Description |
|
|
402
555
|
|--------|------------|---------|-------------|
|
|
403
|
-
| `
|
|
404
|
-
| `
|
|
405
|
-
|
|
406
|
-
|
|
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
|
|
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 |
|
|
421
|
-
|
|
422
|
-
| `beforeAdd` | `documentData` | Before document
|
|
423
|
-
| `afterAdd` | `
|
|
424
|
-
| `beforeUpdate` | `{ docId, updates }` | Before
|
|
425
|
-
| `afterUpdate` | `
|
|
426
|
-
| `beforeDelete` | `docId` | Before
|
|
427
|
-
| `afterDelete` | `docId` | After deletion |
|
|
428
|
-
| `beforeGet` | `docId` | Before
|
|
429
|
-
| `afterGet` | `
|
|
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(`
|
|
434
|
-
|
|
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
|
|
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` |
|
|
457
|
-
| `$nin` |
|
|
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
|
-
###
|
|
640
|
+
### Element
|
|
460
641
|
|
|
461
642
|
| Operator | Description | Example |
|
|
462
643
|
|----------|-------------|---------|
|
|
463
|
-
| `$
|
|
464
|
-
| `$
|
|
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
|
-
###
|
|
647
|
+
### Array
|
|
469
648
|
|
|
470
649
|
| Operator | Description | Example |
|
|
471
650
|
|----------|-------------|---------|
|
|
472
|
-
| `$
|
|
473
|
-
| `$
|
|
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
|
-
###
|
|
655
|
+
### String
|
|
476
656
|
|
|
477
657
|
| Operator | Description | Example |
|
|
478
658
|
|----------|-------------|---------|
|
|
479
|
-
| `$
|
|
480
|
-
| `$
|
|
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
|
-
###
|
|
662
|
+
### Geospatial
|
|
484
663
|
|
|
485
664
|
| Operator | Description | Example |
|
|
486
665
|
|----------|-------------|---------|
|
|
487
|
-
| `$
|
|
488
|
-
| `$
|
|
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
|
|
672
|
+
<summary><strong>Complex Query Examples</strong></summary>
|
|
492
673
|
|
|
493
674
|
```javascript
|
|
494
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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` |
|
|
533
|
-
| `$sort` | Order results | `{ $sort: { date: -1 } }` |
|
|
534
|
-
| `$limit` |
|
|
535
|
-
| `$skip` | Skip results | `{ $skip: 20 }` |
|
|
536
|
-
| `$group` | Group
|
|
537
|
-
| `$lookup` | Join
|
|
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
|
|
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
|
|
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 |
|
|
607
|
-
|
|
608
|
-
| `btree` | Range queries, sorting |
|
|
609
|
-
| `hash` | Exact match lookups |
|
|
610
|
-
| `text` | Full-text search | `
|
|
611
|
-
| `geo` | Location queries |
|
|
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
|
|
812
|
+
// Hash index — fastest for exact-match lookups
|
|
620
813
|
await collection.createIndex('userId', { type: 'hash' });
|
|
621
814
|
|
|
622
|
-
// Text index for
|
|
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
|
|
818
|
+
// Geo index — QuadTree-backed spatial queries
|
|
626
819
|
await collection.createIndex('location', { type: 'geo' });
|
|
627
820
|
|
|
628
|
-
// Sparse index
|
|
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` | `
|
|
638
|
-
| `unique` | `boolean` | `false` |
|
|
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
|
|
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
|
|
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
|
|
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 **
|
|
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 |
|
|
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
|
-
//
|
|
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
|
|
942
|
+
Store blockchain private keys, mnemonics, or other secrets with an additional authentication data layer:
|
|
717
943
|
|
|
718
944
|
```javascript
|
|
719
|
-
// Store
|
|
720
|
-
await db.storePrivateKey('wallet-main', privateKeyString, '
|
|
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', '
|
|
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
|
-
|
|
726
|
-
|
|
727
|
-
|
|
954
|
+
### Secure PIN Generation
|
|
955
|
+
|
|
956
|
+
```javascript
|
|
957
|
+
import { SecureDatabaseEncryption } from '@pixagram/lacerta-db';
|
|
728
958
|
|
|
729
|
-
//
|
|
730
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
970
|
+
### Strategies
|
|
740
971
|
|
|
741
|
-
| Strategy |
|
|
742
|
-
|
|
743
|
-
| `lru` |
|
|
744
|
-
| `lfu` |
|
|
745
|
-
| `ttl` |
|
|
746
|
-
| `none` |
|
|
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',
|
|
754
|
-
maxSize: 200,
|
|
755
|
-
ttl: 120000,
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
812
|
-
|
|
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 =
|
|
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
|
|
825
|
-
const tips =
|
|
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
|
-
|
|
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
|
|
835
|
-
|
|
836
|
-
|
|
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/
|
|
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);
|
|
862
|
-
console.log(error.message);
|
|
863
|
-
console.log(error.timestamp);
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1268
|
+
// Cart with product details
|
|
962
1269
|
async function getCart(userId) {
|
|
963
|
-
const
|
|
964
|
-
|
|
1270
|
+
const items = await carts.query({ userId });
|
|
1271
|
+
|
|
965
1272
|
const enriched = await Promise.all(
|
|
966
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1075
|
-
async function exportWallet(
|
|
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
|
-
|
|
1393
|
+
```javascript
|
|
1394
|
+
const lacerta = new LacertaDB();
|
|
1091
1395
|
|
|
1092
|
-
|
|
1396
|
+
// Full backup of all databases (optionally encrypted)
|
|
1397
|
+
const backup = await lacerta.createBackup('backupPassword123');
|
|
1093
1398
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1399
|
+
// Save backup string (e.g., download as file or send to server)
|
|
1400
|
+
downloadAsFile(backup, 'lacertadb-backup.dat');
|
|
1096
1401
|
|
|
1097
|
-
//
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
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
|
-
|
|
1104
|
-
const results = quick.query({ theme: 'dark' });
|
|
1409
|
+
---
|
|
1105
1410
|
|
|
1106
|
-
|
|
1107
|
-
const all = quick.getAll();
|
|
1411
|
+
## Exports
|
|
1108
1412
|
|
|
1109
|
-
|
|
1110
|
-
quick.clear();
|
|
1413
|
+
All public classes are available as named exports:
|
|
1111
1414
|
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
-
|
|
1123
|
-
|
|
1124
|
-
| Chrome
|
|
1125
|
-
|
|
1126
|
-
|
|
|
1127
|
-
|
|
|
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
|
|