s3db.js 6.0.0 โ 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +620 -1222
- package/dist/s3db.cjs.js +3638 -502
- package/dist/s3db.cjs.min.js +1 -31
- package/dist/s3db.d.ts +891 -62
- package/dist/s3db.es.js +3636 -504
- package/dist/s3db.es.min.js +1 -31
- package/dist/s3db.iife.js +3638 -502
- package/dist/s3db.iife.min.js +1 -31
- package/package.json +33 -14
package/README.md
CHANGED
|
@@ -93,6 +93,8 @@
|
|
|
93
93
|
|
|
94
94
|
## ๐ Table of Contents
|
|
95
95
|
|
|
96
|
+
- [๐ What is s3db.js?](#-what-is-s3dbjs)
|
|
97
|
+
- [โจ Key Features](#-key-features)
|
|
96
98
|
- [๐ Quick Start](#-quick-start)
|
|
97
99
|
- [๐พ Installation](#-installation)
|
|
98
100
|
- [๐ฏ Core Concepts](#-core-concepts)
|
|
@@ -100,22 +102,13 @@
|
|
|
100
102
|
- [๐ Resource Versioning System](#-resource-versioning-system)
|
|
101
103
|
- [๐ Custom ID Generation](#-custom-id-generation)
|
|
102
104
|
- [๐ Plugin System](#-plugin-system)
|
|
103
|
-
- [
|
|
105
|
+
- [๐ Replicator System](#-replicator-system)
|
|
106
|
+
- [๐๏ธ Resource Behaviors](#๏ธ-resource-behaviors)
|
|
104
107
|
- [๐ Advanced Streaming API](#-advanced-streaming-api)
|
|
105
108
|
- [๐ Binary Content Management](#-binary-content-management)
|
|
106
109
|
- [๐๏ธ Advanced Partitioning](#๏ธ-advanced-partitioning)
|
|
107
110
|
- [๐ฃ Advanced Hooks System](#-advanced-hooks-system)
|
|
108
111
|
- [๐ API Reference](#-api-reference)
|
|
109
|
-
- [๐จ Examples](#-examples)
|
|
110
|
-
- [๐ Security](#-security)
|
|
111
|
-
- [โ๏ธ Advanced Configuration Options](#๏ธ-advanced-configuration-options)
|
|
112
|
-
- [๐ก Events and Emitters](#-events-and-emitters)
|
|
113
|
-
- [๐ง Troubleshooting](#-troubleshooting)
|
|
114
|
-
- [๐ฐ Cost Analysis](#-cost-analysis)
|
|
115
|
-
- [๐จ Best Practices](#-best-practices)
|
|
116
|
-
- [๐งช Testing](#-testing)
|
|
117
|
-
- [๐ค Contributing](#-contributing)
|
|
118
|
-
- [๐ License](#-license)
|
|
119
112
|
|
|
120
113
|
---
|
|
121
114
|
|
|
@@ -135,13 +128,17 @@ npm install s3db.js
|
|
|
135
128
|
import { S3db } from "s3db.js";
|
|
136
129
|
|
|
137
130
|
const s3db = new S3db({
|
|
138
|
-
|
|
131
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
|
|
139
132
|
});
|
|
140
133
|
|
|
141
134
|
await s3db.connect();
|
|
142
135
|
console.log("๐ Connected to S3 database!");
|
|
143
136
|
```
|
|
144
137
|
|
|
138
|
+
> **โน๏ธ Note:** You do **not** need to provide `ACCESS_KEY` and `SECRET_KEY` in the connection string if your environment already has S3 permissions (e.g., via IAM Role on EKS, EC2, Lambda, or other compatible clouds). s3db.js will use the default AWS credential provider chain, so credentials can be omitted for role-based or environment-based authentication. This also applies to S3-compatible clouds (MinIO, DigitalOcean Spaces, etc.) if they support such mechanisms.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
145
142
|
### 3. Create your first resource
|
|
146
143
|
|
|
147
144
|
```javascript
|
|
@@ -151,24 +148,9 @@ const users = await s3db.createResource({
|
|
|
151
148
|
name: "string|min:2|max:100",
|
|
152
149
|
email: "email|unique",
|
|
153
150
|
age: "number|integer|positive",
|
|
154
|
-
isActive: "boolean"
|
|
155
|
-
createdAt: "date"
|
|
156
|
-
},
|
|
157
|
-
timestamps: true,
|
|
158
|
-
behavior: "user-management",
|
|
159
|
-
partitions: {
|
|
160
|
-
byRegion: { fields: { region: "string" } }
|
|
151
|
+
isActive: "boolean"
|
|
161
152
|
},
|
|
162
|
-
|
|
163
|
-
autoDecrypt: true,
|
|
164
|
-
cache: false,
|
|
165
|
-
parallelism: 10,
|
|
166
|
-
hooks: {
|
|
167
|
-
preInsert: [async (data) => {
|
|
168
|
-
console.log("Pre-insert:", data);
|
|
169
|
-
return data;
|
|
170
|
-
}]
|
|
171
|
-
}
|
|
153
|
+
timestamps: true
|
|
172
154
|
});
|
|
173
155
|
```
|
|
174
156
|
|
|
@@ -213,6 +195,28 @@ pnpm add s3db.js
|
|
|
213
195
|
yarn add s3db.js
|
|
214
196
|
```
|
|
215
197
|
|
|
198
|
+
### ๐ฆ Optional Dependencies
|
|
199
|
+
|
|
200
|
+
Some features require additional dependencies to be installed manually:
|
|
201
|
+
|
|
202
|
+
#### Replication Dependencies
|
|
203
|
+
|
|
204
|
+
If you plan to use the replication system with external services, install the corresponding dependencies:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# For SQS replication (AWS SQS queues)
|
|
208
|
+
npm install @aws-sdk/client-sqs
|
|
209
|
+
|
|
210
|
+
# For BigQuery replication (Google BigQuery)
|
|
211
|
+
npm install @google-cloud/bigquery
|
|
212
|
+
|
|
213
|
+
# For PostgreSQL replication (PostgreSQL databases)
|
|
214
|
+
npm install pg
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Why manual installation?** These are marked as `peerDependencies` to keep the main package lightweight. Only install what you need!
|
|
218
|
+
```
|
|
219
|
+
|
|
216
220
|
### Environment Setup
|
|
217
221
|
|
|
218
222
|
Create a `.env` file with your AWS credentials:
|
|
@@ -233,7 +237,7 @@ import dotenv from "dotenv";
|
|
|
233
237
|
dotenv.config();
|
|
234
238
|
|
|
235
239
|
const s3db = new S3db({
|
|
236
|
-
|
|
240
|
+
connectionString: `s3://${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY}@${process.env.AWS_BUCKET}/databases/${process.env.DATABASE_NAME}`
|
|
237
241
|
});
|
|
238
242
|
```
|
|
239
243
|
|
|
@@ -245,7 +249,7 @@ const s3db = new S3db({
|
|
|
245
249
|
#### 1. Access Keys (Development)
|
|
246
250
|
```javascript
|
|
247
251
|
const s3db = new S3db({
|
|
248
|
-
|
|
252
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
|
|
249
253
|
});
|
|
250
254
|
```
|
|
251
255
|
|
|
@@ -253,14 +257,14 @@ const s3db = new S3db({
|
|
|
253
257
|
```javascript
|
|
254
258
|
// No credentials needed - uses IAM role permissions
|
|
255
259
|
const s3db = new S3db({
|
|
256
|
-
|
|
260
|
+
connectionString: "s3://BUCKET_NAME/databases/myapp"
|
|
257
261
|
});
|
|
258
262
|
```
|
|
259
263
|
|
|
260
264
|
#### 3. S3-Compatible Services (MinIO, etc.)
|
|
261
265
|
```javascript
|
|
262
266
|
const s3db = new S3db({
|
|
263
|
-
|
|
267
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
264
268
|
endpoint: "http://localhost:9000"
|
|
265
269
|
});
|
|
266
270
|
```
|
|
@@ -276,92 +280,43 @@ A logical container for your resources, stored in a specific S3 prefix.
|
|
|
276
280
|
|
|
277
281
|
```javascript
|
|
278
282
|
const s3db = new S3db({
|
|
279
|
-
|
|
283
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
|
|
280
284
|
});
|
|
281
|
-
// Creates/connects to: s3://bucket/databases/myapp/
|
|
282
285
|
```
|
|
283
286
|
|
|
284
287
|
### ๐ Resources (Collections)
|
|
285
288
|
Resources define the structure of your documents, similar to tables in traditional databases.
|
|
286
289
|
|
|
287
|
-
#### New Configuration Structure
|
|
288
|
-
|
|
289
|
-
The Resource class now uses a unified configuration object where all options are passed directly in the config object:
|
|
290
|
-
|
|
291
290
|
```javascript
|
|
292
291
|
const users = await s3db.createResource({
|
|
293
292
|
name: "users",
|
|
294
293
|
attributes: {
|
|
295
|
-
// Basic types
|
|
296
294
|
name: "string|min:2|max:100",
|
|
297
295
|
email: "email|unique",
|
|
298
296
|
age: "number|integer|positive",
|
|
299
297
|
isActive: "boolean",
|
|
300
|
-
|
|
301
|
-
// Nested objects
|
|
302
298
|
profile: {
|
|
303
299
|
bio: "string|optional",
|
|
304
|
-
avatar: "url|optional"
|
|
305
|
-
preferences: {
|
|
306
|
-
theme: "string|enum:light,dark|default:light",
|
|
307
|
-
notifications: "boolean|default:true"
|
|
308
|
-
}
|
|
300
|
+
avatar: "url|optional"
|
|
309
301
|
},
|
|
310
|
-
|
|
311
|
-
// Arrays
|
|
312
302
|
tags: "array|items:string|unique",
|
|
313
|
-
|
|
314
|
-
// Encrypted fields
|
|
315
303
|
password: "secret"
|
|
316
304
|
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
partitions: { // Organize data for efficient queries
|
|
305
|
+
timestamps: true,
|
|
306
|
+
behavior: "user-managed",
|
|
307
|
+
partitions: {
|
|
321
308
|
byRegion: { fields: { region: "string" } }
|
|
322
|
-
},
|
|
323
|
-
paranoid: true, // Security flag for dangerous operations
|
|
324
|
-
autoDecrypt: true, // Auto-decrypt secret fields
|
|
325
|
-
cache: false, // Enable caching
|
|
326
|
-
parallelism: 10, // Parallelism for bulk operations
|
|
327
|
-
hooks: { // Custom hooks
|
|
328
|
-
preInsert: [async (data) => {
|
|
329
|
-
console.log("Pre-insert:", data);
|
|
330
|
-
return data;
|
|
331
|
-
}]
|
|
332
309
|
}
|
|
333
310
|
});
|
|
334
311
|
```
|
|
335
312
|
|
|
336
313
|
### ๐ Schema Validation
|
|
337
|
-
Built-in validation using [@icebob/fastest-validator](https://github.com/icebob/fastest-validator)
|
|
338
|
-
|
|
339
|
-
```javascript
|
|
340
|
-
const product = await products.insert({
|
|
341
|
-
name: "Wireless Headphones",
|
|
342
|
-
price: 99.99,
|
|
343
|
-
category: "electronics",
|
|
344
|
-
features: ["bluetooth", "noise-cancellation"],
|
|
345
|
-
specifications: {
|
|
346
|
-
battery: "30 hours",
|
|
347
|
-
connectivity: "Bluetooth 5.0"
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
**Validation Features powered by fastest-validator:**
|
|
353
|
-
- โ
**Comprehensive Rules** - String, number, array, object, date validation
|
|
354
|
-
- โ
**Nested Objects** - Deep validation for complex data structures
|
|
355
|
-
- โ
**Custom Rules** - Extend with your own validation logic
|
|
356
|
-
- โ
**Performance** - Optimized validation engine for speed
|
|
357
|
-
- โ
**Error Messages** - Detailed validation error reporting
|
|
314
|
+
Built-in validation using [@icebob/fastest-validator](https://github.com/icebob/fastest-validator) with comprehensive rule support and excellent performance.
|
|
358
315
|
|
|
359
316
|
---
|
|
360
317
|
|
|
361
318
|
## โก Advanced Features
|
|
362
319
|
|
|
363
|
-
s3db.js leverages [@icebob/fastest-validator](https://github.com/icebob/fastest-validator) as its core validation engine for both resource schemas and partition field validation, ensuring high-performance data validation with comprehensive rule support.
|
|
364
|
-
|
|
365
320
|
### ๐ฆ Partitions
|
|
366
321
|
|
|
367
322
|
Organize data efficiently with partitions for faster queries:
|
|
@@ -372,32 +327,16 @@ const analytics = await s3db.createResource({
|
|
|
372
327
|
attributes: {
|
|
373
328
|
userId: "string",
|
|
374
329
|
event: "string",
|
|
375
|
-
timestamp: "date"
|
|
376
|
-
utm: {
|
|
377
|
-
source: "string",
|
|
378
|
-
medium: "string",
|
|
379
|
-
campaign: "string"
|
|
380
|
-
}
|
|
330
|
+
timestamp: "date"
|
|
381
331
|
},
|
|
382
332
|
partitions: {
|
|
383
333
|
byDate: { fields: { timestamp: "date|maxlength:10" } },
|
|
384
|
-
|
|
385
|
-
byUserAndDate: {
|
|
386
|
-
fields: {
|
|
387
|
-
userId: "string",
|
|
388
|
-
timestamp: "date|maxlength:10"
|
|
389
|
-
}
|
|
390
|
-
}
|
|
334
|
+
byUserAndDate: { fields: { userId: "string", timestamp: "date|maxlength:10" } }
|
|
391
335
|
}
|
|
392
336
|
});
|
|
393
337
|
|
|
394
338
|
// Query by partition for better performance
|
|
395
|
-
const
|
|
396
|
-
partition: "byUtmSource",
|
|
397
|
-
partitionValues: { "utm.source": "google" }
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
const todayEvents = await analytics.count({
|
|
339
|
+
const todayEvents = await analytics.list({
|
|
401
340
|
partition: "byDate",
|
|
402
341
|
partitionValues: { timestamp: "2024-01-15" }
|
|
403
342
|
});
|
|
@@ -410,105 +349,35 @@ Add custom logic with pre/post operation hooks:
|
|
|
410
349
|
```javascript
|
|
411
350
|
const products = await s3db.createResource({
|
|
412
351
|
name: "products",
|
|
413
|
-
attributes: {
|
|
414
|
-
name: "string",
|
|
415
|
-
price: "number",
|
|
416
|
-
category: "string"
|
|
417
|
-
},
|
|
352
|
+
attributes: { name: "string", price: "number" },
|
|
418
353
|
hooks: {
|
|
419
|
-
preInsert: [
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
]
|
|
426
|
-
|
|
427
|
-
async (data) => {
|
|
428
|
-
console.log(`๐ฆ Product ${data.name} created with SKU: ${data.sku}`);
|
|
429
|
-
// Send notification, update cache, etc.
|
|
430
|
-
}
|
|
431
|
-
],
|
|
432
|
-
preUpdate: [
|
|
433
|
-
async (id, data) => {
|
|
434
|
-
// Log price changes
|
|
435
|
-
if (data.price) {
|
|
436
|
-
console.log(`๐ฐ Price update for ${id}: $${data.price}`);
|
|
437
|
-
}
|
|
438
|
-
return data;
|
|
439
|
-
}
|
|
440
|
-
]
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
// Optional: Security settings (default: true)
|
|
444
|
-
paranoid: true,
|
|
445
|
-
|
|
446
|
-
// Optional: Schema options (default: false)
|
|
447
|
-
allNestedObjectsOptional: false,
|
|
448
|
-
|
|
449
|
-
// Optional: Encryption settings (default: true)
|
|
450
|
-
autoDecrypt: true,
|
|
451
|
-
|
|
452
|
-
// Optional: Caching (default: false)
|
|
453
|
-
cache: false
|
|
354
|
+
preInsert: [async (data) => {
|
|
355
|
+
data.sku = `${data.category.toUpperCase()}-${Date.now()}`;
|
|
356
|
+
return data;
|
|
357
|
+
}],
|
|
358
|
+
afterInsert: [async (data) => {
|
|
359
|
+
console.log(`๐ฆ Product ${data.name} created`);
|
|
360
|
+
}]
|
|
361
|
+
}
|
|
454
362
|
});
|
|
455
363
|
```
|
|
456
364
|
|
|
457
365
|
### ๐ Streaming API
|
|
458
366
|
|
|
459
|
-
Handle large datasets efficiently
|
|
367
|
+
Handle large datasets efficiently:
|
|
460
368
|
|
|
461
369
|
```javascript
|
|
462
|
-
// Export
|
|
370
|
+
// Export to CSV
|
|
463
371
|
const readableStream = await users.readable();
|
|
464
|
-
const csvWriter = createObjectCsvWriter({
|
|
465
|
-
path: "users_export.csv",
|
|
466
|
-
header: [
|
|
467
|
-
{ id: "id", title: "ID" },
|
|
468
|
-
{ id: "name", title: "Name" },
|
|
469
|
-
{ id: "email", title: "Email" }
|
|
470
|
-
]
|
|
471
|
-
});
|
|
472
|
-
|
|
473
372
|
const records = [];
|
|
474
|
-
readableStream.on("data", (user) =>
|
|
475
|
-
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
readableStream.on("end", async () => {
|
|
479
|
-
await csvWriter.writeRecords(records);
|
|
480
|
-
console.log("โ
Export completed: users_export.csv");
|
|
481
|
-
});
|
|
373
|
+
readableStream.on("data", (user) => records.push(user));
|
|
374
|
+
readableStream.on("end", () => console.log("โ
Export completed"));
|
|
482
375
|
|
|
483
|
-
// Bulk import
|
|
376
|
+
// Bulk import
|
|
484
377
|
const writableStream = await users.writable();
|
|
485
|
-
importData.forEach(userData =>
|
|
486
|
-
writableStream.write(userData);
|
|
487
|
-
});
|
|
378
|
+
importData.forEach(userData => writableStream.write(userData));
|
|
488
379
|
writableStream.end();
|
|
489
380
|
```
|
|
490
|
-
|
|
491
|
-
### ๐ก๏ธ Document Behaviors
|
|
492
|
-
|
|
493
|
-
Handle documents that exceed S3's 2KB metadata limit:
|
|
494
|
-
|
|
495
|
-
```javascript
|
|
496
|
-
// Preserve all data by storing overflow in S3 body
|
|
497
|
-
const blogs = await s3db.createResource({
|
|
498
|
-
name: "blogs",
|
|
499
|
-
attributes: {
|
|
500
|
-
title: "string",
|
|
501
|
-
content: "string", // Can be very large
|
|
502
|
-
author: "string"
|
|
503
|
-
},
|
|
504
|
-
behavior: "body-overflow" // Handles large content automatically
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// Strict validation - throws error if limit exceeded
|
|
508
|
-
const settings = await s3db.createResource({
|
|
509
|
-
name: "settings",
|
|
510
|
-
attributes: {
|
|
511
|
-
key: "string",
|
|
512
381
|
value: "string"
|
|
513
382
|
},
|
|
514
383
|
behavior: "enforce-limits" // Ensures data stays within 2KB
|
|
@@ -527,33 +396,16 @@ const summaries = await s3db.createResource({
|
|
|
527
396
|
|
|
528
397
|
### ๐ Resource Versioning System
|
|
529
398
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
#### Enable Versioning
|
|
399
|
+
Automatically manages schema evolution and data migration:
|
|
533
400
|
|
|
534
401
|
```javascript
|
|
535
|
-
// Enable versioning
|
|
402
|
+
// Enable versioning
|
|
536
403
|
const s3db = new S3db({
|
|
537
|
-
|
|
538
|
-
versioningEnabled: true
|
|
404
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
405
|
+
versioningEnabled: true
|
|
539
406
|
});
|
|
540
407
|
|
|
541
408
|
// Create versioned resource
|
|
542
|
-
const users = await s3db.createResource({
|
|
543
|
-
name: "users",
|
|
544
|
-
attributes: {
|
|
545
|
-
name: "string|required",
|
|
546
|
-
email: "string|required",
|
|
547
|
-
status: "string|required"
|
|
548
|
-
},
|
|
549
|
-
versioningEnabled: true // Enable for this specific resource
|
|
550
|
-
});
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
#### Automatic Version Management
|
|
554
|
-
|
|
555
|
-
```javascript
|
|
556
|
-
// Initial version (v0) - basic user data
|
|
557
409
|
const users = await s3db.createResource({
|
|
558
410
|
name: "users",
|
|
559
411
|
attributes: {
|
|
@@ -563,114 +415,39 @@ const users = await s3db.createResource({
|
|
|
563
415
|
versioningEnabled: true
|
|
564
416
|
});
|
|
565
417
|
|
|
566
|
-
// Insert
|
|
418
|
+
// Insert in version 0
|
|
567
419
|
const user1 = await users.insert({
|
|
568
420
|
name: "John Doe",
|
|
569
421
|
email: "john@example.com"
|
|
570
422
|
});
|
|
571
423
|
|
|
572
|
-
// Update schema -
|
|
424
|
+
// Update schema - creates version 1
|
|
573
425
|
const updatedUsers = await s3db.createResource({
|
|
574
426
|
name: "users",
|
|
575
427
|
attributes: {
|
|
576
428
|
name: "string|required",
|
|
577
429
|
email: "string|required",
|
|
578
|
-
age: "number|optional"
|
|
579
|
-
profile: "object|optional" // New nested object
|
|
430
|
+
age: "number|optional"
|
|
580
431
|
},
|
|
581
432
|
versioningEnabled: true
|
|
582
433
|
});
|
|
583
434
|
|
|
584
|
-
//
|
|
585
|
-
// New users will have _v: "v1" metadata
|
|
586
|
-
const user2 = await updatedUsers.insert({
|
|
587
|
-
name: "Jane Smith",
|
|
588
|
-
email: "jane@example.com",
|
|
589
|
-
age: 30,
|
|
590
|
-
profile: { bio: "Software developer" }
|
|
591
|
-
});
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
#### Automatic Data Migration
|
|
595
|
-
|
|
596
|
-
```javascript
|
|
597
|
-
// Get user from old version - automatically migrated
|
|
435
|
+
// Automatic migration
|
|
598
436
|
const migratedUser = await updatedUsers.get(user1.id);
|
|
599
|
-
console.log(migratedUser._v); // "
|
|
600
|
-
console.log(migratedUser.age); // undefined (new field)
|
|
601
|
-
console.log(migratedUser.profile); // undefined (new field)
|
|
602
|
-
|
|
603
|
-
// Update user - migrates to current version
|
|
604
|
-
const updatedUser = await updatedUsers.update(user1.id, {
|
|
605
|
-
name: "John Doe",
|
|
606
|
-
email: "john@example.com",
|
|
607
|
-
age: 35, // Add new field
|
|
608
|
-
profile: { bio: "Updated bio" }
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
console.log(updatedUser._v); // "v1" - now on current version
|
|
612
|
-
console.log(updatedUser.age); // 35
|
|
613
|
-
console.log(updatedUser.profile); // { bio: "Updated bio" }
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
#### Historical Data Preservation
|
|
617
|
-
|
|
618
|
-
```javascript
|
|
619
|
-
// When versioning is enabled, old versions are preserved
|
|
620
|
-
// Historical data is stored in: ./resource=users/historical/id=user1
|
|
621
|
-
|
|
622
|
-
// The system automatically:
|
|
623
|
-
// 1. Detects schema changes via hash comparison
|
|
624
|
-
// 2. Increments version number (v0 โ v1 โ v2...)
|
|
625
|
-
// 3. Preserves old data in historical storage
|
|
626
|
-
// 4. Migrates data when accessed or updated
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
#### Version Partitions
|
|
630
|
-
|
|
631
|
-
```javascript
|
|
632
|
-
// Automatic version partition is created when versioning is enabled
|
|
633
|
-
const users = await s3db.createResource({
|
|
634
|
-
name: "users",
|
|
635
|
-
attributes: {
|
|
636
|
-
name: "string|required",
|
|
637
|
-
email: "string|required"
|
|
638
|
-
},
|
|
639
|
-
partitions: {
|
|
640
|
-
byStatus: { fields: { status: "string" } }
|
|
641
|
-
},
|
|
642
|
-
versioningEnabled: true
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
// Automatically adds: byVersion: { fields: { _v: "string" } }
|
|
646
|
-
console.log(users.config.partitions.byVersion); // { fields: { _v: "string" } }
|
|
437
|
+
console.log(migratedUser._v); // "1" - automatically migrated
|
|
647
438
|
|
|
648
439
|
// Query by version
|
|
649
|
-
const
|
|
440
|
+
const version0Users = await users.list({
|
|
650
441
|
partition: "byVersion",
|
|
651
|
-
partitionValues: { _v: "
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
const v1Users = await users.list({
|
|
655
|
-
partition: "byVersion",
|
|
656
|
-
partitionValues: { _v: "v1" }
|
|
442
|
+
partitionValues: { _v: "0" }
|
|
657
443
|
});
|
|
658
444
|
```
|
|
659
445
|
|
|
660
446
|
### ๐ Custom ID Generation
|
|
661
447
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
#### Built-in ID Sizes
|
|
448
|
+
Flexible ID generation strategies:
|
|
665
449
|
|
|
666
450
|
```javascript
|
|
667
|
-
// Default 22-character IDs
|
|
668
|
-
const defaultUsers = await s3db.createResource({
|
|
669
|
-
name: "users",
|
|
670
|
-
attributes: { name: "string|required" }
|
|
671
|
-
// Uses default 22-character nanoid
|
|
672
|
-
});
|
|
673
|
-
|
|
674
451
|
// Custom size IDs
|
|
675
452
|
const shortUsers = await s3db.createResource({
|
|
676
453
|
name: "short-users",
|
|
@@ -678,23 +455,12 @@ const shortUsers = await s3db.createResource({
|
|
|
678
455
|
idSize: 8 // Generate 8-character IDs
|
|
679
456
|
});
|
|
680
457
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
attributes: { name: "string|required" },
|
|
684
|
-
idSize: 32 // Generate 32-character IDs
|
|
685
|
-
});
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
#### UUID Support
|
|
689
|
-
|
|
690
|
-
```javascript
|
|
691
|
-
import { v4 as uuidv4, v1 as uuidv1 } from 'uuid';
|
|
692
|
-
|
|
693
|
-
// UUID v4 (random)
|
|
458
|
+
// UUID support
|
|
459
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
694
460
|
const uuidUsers = await s3db.createResource({
|
|
695
461
|
name: "uuid-users",
|
|
696
462
|
attributes: { name: "string|required" },
|
|
697
|
-
idGenerator: uuidv4
|
|
463
|
+
idGenerator: uuidv4
|
|
698
464
|
});
|
|
699
465
|
|
|
700
466
|
// UUID v1 (time-based)
|
|
@@ -703,188 +469,552 @@ const timeUsers = await s3db.createResource({
|
|
|
703
469
|
attributes: { name: "string|required" },
|
|
704
470
|
idGenerator: uuidv1
|
|
705
471
|
});
|
|
706
|
-
```
|
|
707
472
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
```javascript
|
|
711
|
-
// Timestamp-based IDs
|
|
473
|
+
// Custom ID function
|
|
712
474
|
const timestampUsers = await s3db.createResource({
|
|
713
475
|
name: "timestamp-users",
|
|
714
476
|
attributes: { name: "string|required" },
|
|
715
|
-
idGenerator: () => `user_${Date.now()}
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
// Sequential IDs
|
|
719
|
-
let counter = 0;
|
|
720
|
-
const sequentialUsers = await s3db.createResource({
|
|
721
|
-
name: "sequential-users",
|
|
722
|
-
attributes: { name: "string|required" },
|
|
723
|
-
idGenerator: () => `USER_${String(++counter).padStart(6, '0')}`
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
// Prefixed IDs
|
|
727
|
-
const prefixedUsers = await s3db.createResource({
|
|
728
|
-
name: "prefixed-users",
|
|
729
|
-
attributes: { name: "string|required" },
|
|
730
|
-
idGenerator: () => `CUSTOM_${Math.random().toString(36).substr(2, 10).toUpperCase()}`
|
|
731
|
-
});
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
#### ID Generator Priority
|
|
735
|
-
|
|
736
|
-
```javascript
|
|
737
|
-
// Priority order: idGenerator function > idGenerator number > idSize > default
|
|
738
|
-
const users = await s3db.createResource({
|
|
739
|
-
name: "users",
|
|
740
|
-
attributes: { name: "string|required" },
|
|
741
|
-
idGenerator: () => "custom-id", // This takes precedence
|
|
742
|
-
idSize: 16 // This is ignored
|
|
477
|
+
idGenerator: () => `user_${Date.now()}`
|
|
743
478
|
});
|
|
744
479
|
```
|
|
745
480
|
|
|
746
481
|
### ๐ Plugin System
|
|
747
482
|
|
|
748
|
-
Extend s3db.js
|
|
749
|
-
|
|
750
|
-
#### Built-in Plugins
|
|
483
|
+
Extend functionality with powerful plugins. s3db.js supports multiple plugins working together seamlessly:
|
|
751
484
|
|
|
752
485
|
```javascript
|
|
753
|
-
import {
|
|
486
|
+
import {
|
|
487
|
+
CachePlugin,
|
|
488
|
+
CostsPlugin,
|
|
489
|
+
FullTextPlugin,
|
|
490
|
+
MetricsPlugin,
|
|
491
|
+
ReplicationPlugin,
|
|
492
|
+
AuditPlugin
|
|
493
|
+
} from 's3db.js';
|
|
754
494
|
|
|
755
|
-
// Enable caching and cost tracking
|
|
756
495
|
const s3db = new S3db({
|
|
757
|
-
|
|
758
|
-
plugins: [
|
|
496
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
497
|
+
plugins: [
|
|
498
|
+
new CachePlugin({ enabled: true }), // CachePlugin needs instantiation
|
|
499
|
+
CostsPlugin, // CostsPlugin is a static object
|
|
500
|
+
new FullTextPlugin({ fields: ['name', 'description'] }),
|
|
501
|
+
new MetricsPlugin({ enabled: true }),
|
|
502
|
+
new ReplicationPlugin({
|
|
503
|
+
enabled: true,
|
|
504
|
+
replicators: [
|
|
505
|
+
{
|
|
506
|
+
driver: 's3db',
|
|
507
|
+
resources: ['users', 'products'],
|
|
508
|
+
config: {
|
|
509
|
+
connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
]
|
|
513
|
+
}),
|
|
514
|
+
new AuditPlugin({ enabled: true })
|
|
515
|
+
]
|
|
759
516
|
});
|
|
517
|
+
|
|
518
|
+
// All plugins work together seamlessly
|
|
519
|
+
await users.insert({ name: "John", email: "john@example.com" });
|
|
520
|
+
// - Cache: Caches the operation
|
|
521
|
+
// - Costs: Tracks S3 costs
|
|
522
|
+
// - FullText: Indexes the data for search
|
|
523
|
+
// - Metrics: Records performance metrics
|
|
524
|
+
// - Replication: Syncs to configured replicators
|
|
525
|
+
// - Audit: Logs the operation
|
|
760
526
|
```
|
|
761
527
|
|
|
762
|
-
|
|
528
|
+
### ๐ Replicator System
|
|
763
529
|
|
|
764
|
-
|
|
765
|
-
// Automatic caching for read operations
|
|
766
|
-
const users = await s3db.createResource({
|
|
767
|
-
name: "users",
|
|
768
|
-
attributes: { name: "string|required" }
|
|
769
|
-
});
|
|
530
|
+
The Replication Plugin now supports a flexible driver-based system for replicating data to different targets. Each replicator driver handles a specific type of target system.
|
|
770
531
|
|
|
771
|
-
|
|
772
|
-
await users.count(); // Cached count
|
|
773
|
-
await users.list(); // Cached list
|
|
774
|
-
await users.getMany([...]); // Cached bulk get
|
|
775
|
-
await users.page({...}); // Cached pagination
|
|
532
|
+
#### Available Replicators
|
|
776
533
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
534
|
+
**S3DB Replicator** - Replicates data to another s3db instance:
|
|
535
|
+
```javascript
|
|
536
|
+
{
|
|
537
|
+
driver: 's3db',
|
|
538
|
+
resources: ['users', 'products'], // <-- root level
|
|
539
|
+
config: {
|
|
540
|
+
connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup",
|
|
541
|
+
}
|
|
542
|
+
}
|
|
781
543
|
```
|
|
782
544
|
|
|
783
|
-
|
|
784
|
-
|
|
545
|
+
**SQS Replicator** - Sends data to AWS SQS queues:
|
|
785
546
|
```javascript
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
//
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
// Check current costs
|
|
798
|
-
console.log(s3db.client.costs);
|
|
799
|
-
// {
|
|
800
|
-
// total: 0.000009,
|
|
801
|
-
// requests: { total: 3, put: 1, get: 2 },
|
|
802
|
-
// events: { PutObjectCommand: 1, GetObjectCommand: 1, HeadObjectCommand: 1 }
|
|
803
|
-
// }
|
|
547
|
+
{
|
|
548
|
+
driver: 'sqs',
|
|
549
|
+
resources: ['orders'],
|
|
550
|
+
config: {
|
|
551
|
+
queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
|
|
552
|
+
region: 'us-east-1',
|
|
553
|
+
messageGroupId: 's3db-replication', // For FIFO queues
|
|
554
|
+
deduplicationId: true // Enable deduplication
|
|
555
|
+
}
|
|
556
|
+
}
|
|
804
557
|
```
|
|
805
558
|
|
|
806
|
-
|
|
807
|
-
|
|
559
|
+
**BigQuery Replicator** - Sends data to Google BigQuery:
|
|
808
560
|
```javascript
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
561
|
+
{
|
|
562
|
+
driver: 'bigquery',
|
|
563
|
+
config: {
|
|
564
|
+
projectId: 'my-project',
|
|
565
|
+
datasetId: 'analytics',
|
|
566
|
+
location: 'US',
|
|
567
|
+
logTable: 's3db_replication_log',
|
|
568
|
+
credentials: {
|
|
569
|
+
// Your Google Cloud service account credentials
|
|
570
|
+
client_email: 'service-account@project.iam.gserviceaccount.com',
|
|
571
|
+
private_key: '-----BEGIN PRIVATE KEY-----\n...'
|
|
572
|
+
}
|
|
814
573
|
},
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
574
|
+
resources: {
|
|
575
|
+
users: [
|
|
576
|
+
{ actions: ['insert', 'update', 'delete'], table: 'users_table' },
|
|
577
|
+
],
|
|
578
|
+
orders: [
|
|
579
|
+
{ actions: ['insert'], table: 'orders_table' },
|
|
580
|
+
{ actions: ['insert'], table: 'orders_analytics' }, // Also replicate to analytics table
|
|
581
|
+
],
|
|
582
|
+
products: 'products_table' // Short form: equivalent to { actions: ['insert'], table: 'products_table' }
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**PostgreSQL Replicator** - Sends data to PostgreSQL databases:
|
|
588
|
+
```javascript
|
|
589
|
+
{
|
|
590
|
+
driver: 'postgres',
|
|
591
|
+
config: {
|
|
592
|
+
connectionString: 'postgresql://user:pass@localhost:5432/analytics',
|
|
593
|
+
// OR individual parameters:
|
|
594
|
+
// host: 'localhost',
|
|
595
|
+
// port: 5432,
|
|
596
|
+
// database: 'analytics',
|
|
597
|
+
// user: 'user',
|
|
598
|
+
// password: 'pass',
|
|
599
|
+
ssl: false,
|
|
600
|
+
logTable: 's3db_replication_log'
|
|
818
601
|
},
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
602
|
+
resources: {
|
|
603
|
+
users: [
|
|
604
|
+
{ actions: ['insert', 'update', 'delete'], table: 'users_table' },
|
|
605
|
+
],
|
|
606
|
+
orders: [
|
|
607
|
+
{ actions: ['insert'], table: 'orders_table' },
|
|
608
|
+
{ actions: ['insert'], table: 'orders_analytics' }, // Also replicate to analytics table
|
|
609
|
+
],
|
|
610
|
+
products: 'products_table' // Short form: equivalent to { actions: ['insert'], table: 'products_table' }
|
|
822
611
|
}
|
|
823
|
-
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
824
614
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
615
|
+
#### Replicator Features
|
|
616
|
+
|
|
617
|
+
- **Resource Filtering**: Each replicator can be configured to handle specific resources only
|
|
618
|
+
- **Event Emission**: All replicators emit events for monitoring and debugging
|
|
619
|
+
- **Connection Testing**: Test connections to replicators before use
|
|
620
|
+
- **Batch Operations**: Support for batch replication operations
|
|
621
|
+
- **Error Handling**: Comprehensive error handling and retry logic
|
|
622
|
+
- **Status Monitoring**: Get detailed status and statistics for each replicator
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
**โ ๏ธ Important:** These dependencies are marked as `peerDependencies` in the package.json, which means they are not automatically installed with s3db.js. You must install them manually if you plan to use the corresponding replicators. If you don't install the required dependency, the replicator will throw an error when trying to initialize.
|
|
626
|
+
|
|
627
|
+
**Example error without dependency:**
|
|
628
|
+
```
|
|
629
|
+
Error: Cannot find module '@aws-sdk/client-sqs'
|
|
830
630
|
```
|
|
831
631
|
|
|
832
|
-
|
|
632
|
+
**Solution:** Install the missing dependency as shown above.
|
|
833
633
|
|
|
834
|
-
|
|
634
|
+
#### Example Usage
|
|
835
635
|
|
|
836
|
-
|
|
636
|
+
See `examples/e34-replicators.js` for a complete example using all four replicator types.
|
|
837
637
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
638
|
+
**Prerequisites:** Make sure to install the required dependencies before running the example:
|
|
639
|
+
|
|
640
|
+
```bash
|
|
641
|
+
# Install all replication dependencies for the full example
|
|
642
|
+
npm install @aws-sdk/client-sqs @google-cloud/bigquery pg
|
|
643
|
+
```
|
|
844
644
|
|
|
845
|
-
####
|
|
645
|
+
#### ๐ Cache Plugin
|
|
646
|
+
Intelligent caching to reduce API calls and improve performance:
|
|
846
647
|
|
|
847
648
|
```javascript
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
users.on('exceedsLimit', (data) => {
|
|
857
|
-
console.warn(`Data exceeds 2KB limit by ${data.excess} bytes`);
|
|
649
|
+
const s3db = new S3db({
|
|
650
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
651
|
+
plugins: [new CachePlugin({
|
|
652
|
+
enabled: true,
|
|
653
|
+
ttl: 300000, // 5 minutes cache
|
|
654
|
+
maxSize: 1000, // Max 1000 items in cache
|
|
655
|
+
driverType: 'memory' // 'memory' or 's3'
|
|
656
|
+
})]
|
|
858
657
|
});
|
|
859
658
|
|
|
860
|
-
//
|
|
861
|
-
await users.
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
});
|
|
659
|
+
// Automatic caching for reads
|
|
660
|
+
await users.count(); // Cached for 5 minutes
|
|
661
|
+
await users.list(); // Cached for 5 minutes
|
|
662
|
+
await users.insert({...}); // Automatically clears cache
|
|
865
663
|
```
|
|
866
664
|
|
|
867
|
-
####
|
|
665
|
+
#### ๐ฐ Costs Plugin
|
|
666
|
+
Track and monitor AWS S3 costs in real-time:
|
|
868
667
|
|
|
869
668
|
```javascript
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
attributes: { key: "string", value: "string" },
|
|
874
|
-
behavior: "enforce-limits"
|
|
669
|
+
const s3db = new S3db({
|
|
670
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
671
|
+
plugins: [CostsPlugin]
|
|
875
672
|
});
|
|
876
673
|
|
|
877
|
-
//
|
|
878
|
-
await
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
674
|
+
// Track costs automatically
|
|
675
|
+
await users.insert({ name: "John", email: "john@example.com" });
|
|
676
|
+
await users.list();
|
|
677
|
+
|
|
678
|
+
// Get cost information
|
|
679
|
+
console.log(s3db.client.costs);
|
|
680
|
+
// { total: 0.000009, requests: { total: 3, get: 1, put: 1, list: 1 } }
|
|
882
681
|
```
|
|
883
682
|
|
|
884
|
-
####
|
|
683
|
+
#### ๐ Full-Text Search Plugin
|
|
684
|
+
Powerful text search with automatic indexing:
|
|
885
685
|
|
|
886
686
|
```javascript
|
|
887
|
-
|
|
687
|
+
const s3db = new S3db({
|
|
688
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
689
|
+
plugins: [new FullTextPlugin({
|
|
690
|
+
enabled: true,
|
|
691
|
+
fields: ['name', 'description', 'content'], // Fields to index
|
|
692
|
+
minWordLength: 3,
|
|
693
|
+
maxResults: 50,
|
|
694
|
+
language: 'en-US'
|
|
695
|
+
})]
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// Create resource with searchable fields
|
|
699
|
+
const products = await s3db.createResource({
|
|
700
|
+
name: "products",
|
|
701
|
+
attributes: {
|
|
702
|
+
name: "string|required",
|
|
703
|
+
description: "string",
|
|
704
|
+
content: "string"
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// Insert data (automatically indexed)
|
|
709
|
+
await products.insert({
|
|
710
|
+
name: "JavaScript Book",
|
|
711
|
+
description: "Learn JavaScript programming",
|
|
712
|
+
content: "Comprehensive guide to modern JavaScript"
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// Search across all indexed fields
|
|
716
|
+
const results = await s3db.plugins.fulltext.searchRecords('products', 'javascript');
|
|
717
|
+
console.log(results); // Returns products with search scores
|
|
718
|
+
|
|
719
|
+
// Example of search results:
|
|
720
|
+
// [
|
|
721
|
+
// {
|
|
722
|
+
// id: "prod-123",
|
|
723
|
+
// name: "JavaScript Book",
|
|
724
|
+
// description: "Learn JavaScript programming",
|
|
725
|
+
// content: "Comprehensive guide to modern JavaScript",
|
|
726
|
+
// _searchScore: 0.85,
|
|
727
|
+
// _matchedFields: ["name", "description", "content"],
|
|
728
|
+
// _matchedWords: ["javascript"]
|
|
729
|
+
// },
|
|
730
|
+
// {
|
|
731
|
+
// id: "prod-456",
|
|
732
|
+
// name: "Web Development Guide",
|
|
733
|
+
// description: "Includes JavaScript, HTML, and CSS",
|
|
734
|
+
// content: "Complete web development with JavaScript",
|
|
735
|
+
// _searchScore: 0.72,
|
|
736
|
+
// _matchedFields: ["description", "content"],
|
|
737
|
+
// _matchedWords: ["javascript"]
|
|
738
|
+
// }
|
|
739
|
+
// ]
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
#### ๐ Metrics Plugin
|
|
743
|
+
Monitor performance and usage metrics:
|
|
744
|
+
|
|
745
|
+
```javascript
|
|
746
|
+
const s3db = new S3db({
|
|
747
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
748
|
+
plugins: [new MetricsPlugin({
|
|
749
|
+
enabled: true,
|
|
750
|
+
collectPerformance: true,
|
|
751
|
+
collectErrors: true,
|
|
752
|
+
collectUsage: true,
|
|
753
|
+
flushInterval: 60000 // Flush every minute
|
|
754
|
+
})]
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
// Metrics are collected automatically
|
|
758
|
+
await users.insert({ name: "John" });
|
|
759
|
+
await users.list();
|
|
760
|
+
|
|
761
|
+
// Get metrics
|
|
762
|
+
const metrics = await s3db.plugins.metrics.getMetrics();
|
|
763
|
+
console.log(metrics); // Performance and usage data
|
|
764
|
+
|
|
765
|
+
// Example of metrics object:
|
|
766
|
+
// {
|
|
767
|
+
// performance: {
|
|
768
|
+
// averageResponseTime: 245, // milliseconds
|
|
769
|
+
// totalRequests: 1250,
|
|
770
|
+
// requestsPerSecond: 12.5,
|
|
771
|
+
// slowestOperations: [
|
|
772
|
+
// { operation: "list", resource: "users", avgTime: 450, count: 50 },
|
|
773
|
+
// { operation: "get", resource: "products", avgTime: 320, count: 200 }
|
|
774
|
+
// ]
|
|
775
|
+
// },
|
|
776
|
+
// usage: {
|
|
777
|
+
// resources: {
|
|
778
|
+
// users: { inserts: 150, updates: 75, deletes: 10, reads: 800 },
|
|
779
|
+
// products: { inserts: 300, updates: 120, deletes: 25, reads: 1200 }
|
|
780
|
+
// },
|
|
781
|
+
// totalOperations: 2680,
|
|
782
|
+
// mostActiveResource: "products",
|
|
783
|
+
// peakUsageHour: "14:00"
|
|
784
|
+
// },
|
|
785
|
+
// errors: {
|
|
786
|
+
// total: 15,
|
|
787
|
+
// byType: {
|
|
788
|
+
// "ValidationError": 8,
|
|
789
|
+
// "NotFoundError": 5,
|
|
790
|
+
// "PermissionError": 2
|
|
791
|
+
// },
|
|
792
|
+
// byResource: {
|
|
793
|
+
// users: 10,
|
|
794
|
+
// products: 5
|
|
795
|
+
// }
|
|
796
|
+
// },
|
|
797
|
+
// cache: {
|
|
798
|
+
// hitRate: 0.78, // 78% cache hit rate
|
|
799
|
+
// totalHits: 980,
|
|
800
|
+
// totalMisses: 270,
|
|
801
|
+
// averageCacheTime: 120 // milliseconds
|
|
802
|
+
// }
|
|
803
|
+
// }
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
#### ๐ Replication Plugin
|
|
807
|
+
Replicate data to other buckets or regions:
|
|
808
|
+
|
|
809
|
+
```javascript
|
|
810
|
+
const s3db = new S3db({
|
|
811
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
812
|
+
plugins: [new ReplicationPlugin({
|
|
813
|
+
enabled: true,
|
|
814
|
+
replicators: [
|
|
815
|
+
{
|
|
816
|
+
driver: 's3db',
|
|
817
|
+
resources: ['users', 'products'],
|
|
818
|
+
config: {
|
|
819
|
+
connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
driver: 'sqs',
|
|
824
|
+
resources: ['orders', 'users', 'products'],
|
|
825
|
+
config: {
|
|
826
|
+
// Resource-specific queues
|
|
827
|
+
queues: {
|
|
828
|
+
users: 'https://sqs.us-east-1.amazonaws.com/123456789012/users-events.fifo',
|
|
829
|
+
orders: 'https://sqs.us-east-1.amazonaws.com/123456789012/orders-events.fifo',
|
|
830
|
+
products: 'https://sqs.us-east-1.amazonaws.com/123456789012/products-events.fifo'
|
|
831
|
+
},
|
|
832
|
+
// Fallback queue for unspecified resources
|
|
833
|
+
defaultQueueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/default-events.fifo',
|
|
834
|
+
messageGroupId: 's3db-replication', // For FIFO queues
|
|
835
|
+
deduplicationId: true // Enable deduplication
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
driver: 'bigquery',
|
|
840
|
+
resources: ['users', 'orders'],
|
|
841
|
+
config: {
|
|
842
|
+
projectId: 'my-project',
|
|
843
|
+
datasetId: 'analytics',
|
|
844
|
+
tableId: 's3db_replication'
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
driver: 'postgres',
|
|
849
|
+
resources: ['users'],
|
|
850
|
+
config: {
|
|
851
|
+
connectionString: 'postgresql://user:pass@localhost:5432/analytics',
|
|
852
|
+
tableName: 's3db_replication'
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
],
|
|
856
|
+
syncInterval: 300000 // Sync every 5 minutes
|
|
857
|
+
})]
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// Data is automatically replicated to all configured targets
|
|
861
|
+
await users.insert({ name: "John" }); // Synced to all replicators
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
**SQS Message Structure:**
|
|
865
|
+
|
|
866
|
+
The SQS replicator sends standardized messages with the following structure:
|
|
867
|
+
|
|
868
|
+
```javascript
|
|
869
|
+
// INSERT operation
|
|
870
|
+
{
|
|
871
|
+
resource: "users",
|
|
872
|
+
action: "insert",
|
|
873
|
+
data: { _v: 0, id: "user-001", name: "John", email: "john@example.com" },
|
|
874
|
+
timestamp: "2024-01-01T10:00:00.000Z",
|
|
875
|
+
source: "s3db-replication"
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// UPDATE operation (includes before/after data)
|
|
879
|
+
{
|
|
880
|
+
resource: "users",
|
|
881
|
+
action: "update",
|
|
882
|
+
before: { _v: 0, id: "user-001", name: "John", age: 30 },
|
|
883
|
+
data: { _v: 1, id: "user-001", name: "John", age: 31 },
|
|
884
|
+
timestamp: "2024-01-01T10:05:00.000Z",
|
|
885
|
+
source: "s3db-replication"
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// DELETE operation
|
|
889
|
+
{
|
|
890
|
+
resource: "users",
|
|
891
|
+
action: "delete",
|
|
892
|
+
data: { _v: 1, id: "user-001", name: "John", age: 31 },
|
|
893
|
+
timestamp: "2024-01-01T10:10:00.000Z",
|
|
894
|
+
source: "s3db-replication"
|
|
895
|
+
}
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
**Queue Routing:**
|
|
899
|
+
- Each resource can have its own dedicated queue
|
|
900
|
+
- Unspecified resources use the default queue
|
|
901
|
+
- FIFO queues supported with deduplication
|
|
902
|
+
- Messages are automatically routed to the appropriate queue
|
|
903
|
+
|
|
904
|
+
#### ๐ Audit Plugin
|
|
905
|
+
Log all operations for compliance and traceability:
|
|
906
|
+
|
|
907
|
+
```javascript
|
|
908
|
+
const s3db = new S3db({
|
|
909
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
910
|
+
plugins: [new AuditPlugin({
|
|
911
|
+
enabled: true,
|
|
912
|
+
trackOperations: ['insert', 'update', 'delete', 'get'],
|
|
913
|
+
includeData: false, // Don't log sensitive data
|
|
914
|
+
retentionDays: 90
|
|
915
|
+
})]
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
// All operations are logged
|
|
919
|
+
await users.insert({ name: "John" });
|
|
920
|
+
await users.update(userId, { age: 31 });
|
|
921
|
+
|
|
922
|
+
// Get audit logs
|
|
923
|
+
const logs = await s3db.plugins.audit.getAuditLogs({
|
|
924
|
+
resourceName: 'users',
|
|
925
|
+
operation: 'insert'
|
|
926
|
+
});
|
|
927
|
+
console.log(logs); // Audit trail
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
### ๐๏ธ Resource Behaviors
|
|
931
|
+
|
|
932
|
+
Choose the right behavior strategy for your use case:
|
|
933
|
+
|
|
934
|
+
#### Behavior Comparison
|
|
935
|
+
|
|
936
|
+
| Behavior | Enforcement | Data Loss | Event Emission | Use Case |
|
|
937
|
+
|------------------|-------------|-----------|----------------|-------------------------|
|
|
938
|
+
| `user-managed` | None | Possible | Warns | Dev/Test/Advanced users |
|
|
939
|
+
| `enforce-limits` | Strict | No | Throws | Production |
|
|
940
|
+
| `data-truncate` | Truncates | Yes | Warns | Content Mgmt |
|
|
941
|
+
| `body-overflow` | Truncates/Splits | Yes | Warns | Large objects |
|
|
942
|
+
| `body-only` | Unlimited | No | No | Large JSON/Logs |
|
|
943
|
+
|
|
944
|
+
#### User Managed Behavior (Default)
|
|
945
|
+
|
|
946
|
+
The `user-managed` behavior is the default for s3db resources. It provides no automatic enforcement of S3 metadata or body size limits, and does not modify or truncate data. Instead, it emits warnings via the `exceedsLimit` event when S3 metadata limits are exceeded, but allows all operations to proceed.
|
|
947
|
+
|
|
948
|
+
**Purpose & Use Cases:**
|
|
949
|
+
- For development, testing, or advanced users who want full control over resource metadata and body size.
|
|
950
|
+
- Useful when you want to handle S3 metadata limits yourself, or implement custom logic for warnings.
|
|
951
|
+
- Not recommended for production unless you have custom enforcement or validation in place.
|
|
952
|
+
|
|
953
|
+
**How It Works:**
|
|
954
|
+
- Emits an `exceedsLimit` event (with details) when a resource's metadata size exceeds the S3 2KB limit.
|
|
955
|
+
- Does NOT block, truncate, or modify dataโoperations always proceed.
|
|
956
|
+
- No automatic enforcement of any limits; user is responsible for handling warnings and data integrity.
|
|
957
|
+
|
|
958
|
+
**Event Emission:**
|
|
959
|
+
- Event: `exceedsLimit`
|
|
960
|
+
- Payload:
|
|
961
|
+
- `operation`: 'insert' | 'update' | 'upsert'
|
|
962
|
+
- `id` (for update/upsert): resource id
|
|
963
|
+
- `totalSize`: total metadata size in bytes
|
|
964
|
+
- `limit`: S3 metadata limit (2048 bytes)
|
|
965
|
+
- `excess`: number of bytes over the limit
|
|
966
|
+
- `data`: the offending data object
|
|
967
|
+
|
|
968
|
+
```javascript
|
|
969
|
+
// Flexible behavior - warns but doesn't block
|
|
970
|
+
const users = await s3db.createResource({
|
|
971
|
+
name: "users",
|
|
972
|
+
attributes: { name: "string", bio: "string" },
|
|
973
|
+
behavior: "user-managed" // Default
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
// Listen for limit warnings
|
|
977
|
+
users.on('exceedsLimit', (data) => {
|
|
978
|
+
console.warn(`Data exceeds 2KB limit by ${data.excess} bytes`, data);
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// Operation continues despite warning
|
|
982
|
+
await users.insert({
|
|
983
|
+
name: "John",
|
|
984
|
+
bio: "A".repeat(3000) // > 2KB
|
|
985
|
+
});
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
**Best Practices & Warnings:**
|
|
989
|
+
- Exceeding S3 metadata limits will cause silent data loss or errors at the storage layer.
|
|
990
|
+
- Use this behavior only if you have custom logic to handle warnings and enforce limits.
|
|
991
|
+
- For production, prefer `enforce-limits` or `data-truncate` to avoid data loss.
|
|
992
|
+
|
|
993
|
+
**Migration Tips:**
|
|
994
|
+
- To migrate to a stricter behavior, change the resource's behavior to `enforce-limits` or `data-truncate`.
|
|
995
|
+
- Review emitted warnings to identify resources at risk of exceeding S3 limits.
|
|
996
|
+
|
|
997
|
+
#### Enforce Limits Behavior
|
|
998
|
+
|
|
999
|
+
```javascript
|
|
1000
|
+
// Strict validation - throws error if limit exceeded
|
|
1001
|
+
const settings = await s3db.createResource({
|
|
1002
|
+
name: "settings",
|
|
1003
|
+
attributes: { key: "string", value: "string" },
|
|
1004
|
+
behavior: "enforce-limits"
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// Throws error if data > 2KB
|
|
1008
|
+
await settings.insert({
|
|
1009
|
+
key: "large_setting",
|
|
1010
|
+
value: "A".repeat(3000) // Throws: "S3 metadata size exceeds 2KB limit"
|
|
1011
|
+
});
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
#### Data Truncate Behavior
|
|
1015
|
+
|
|
1016
|
+
```javascript
|
|
1017
|
+
// Smart truncation - preserves structure, truncates content
|
|
888
1018
|
const summaries = await s3db.createResource({
|
|
889
1019
|
name: "summaries",
|
|
890
1020
|
attributes: {
|
|
@@ -934,6 +1064,52 @@ console.log(retrieved.content.length); // 5000 (full content preserved)
|
|
|
934
1064
|
console.log(retrieved._hasContent); // true (indicates body usage)
|
|
935
1065
|
```
|
|
936
1066
|
|
|
1067
|
+
#### Body Only Behavior
|
|
1068
|
+
|
|
1069
|
+
```javascript
|
|
1070
|
+
// Store all data in S3 object body as JSON, keeping only version in metadata
|
|
1071
|
+
const documents = await s3db.createResource({
|
|
1072
|
+
name: "documents",
|
|
1073
|
+
attributes: {
|
|
1074
|
+
title: "string",
|
|
1075
|
+
content: "string", // Can be extremely large
|
|
1076
|
+
metadata: "object"
|
|
1077
|
+
},
|
|
1078
|
+
behavior: "body-only"
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
// Store large documents without any size limits
|
|
1082
|
+
const document = await documents.insert({
|
|
1083
|
+
title: "Large Document",
|
|
1084
|
+
content: "A".repeat(100000), // 100KB content
|
|
1085
|
+
metadata: {
|
|
1086
|
+
author: "John Doe",
|
|
1087
|
+
tags: ["large", "document"],
|
|
1088
|
+
version: "1.0"
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
// All data is stored in the S3 object body
|
|
1093
|
+
const retrieved = await documents.get(document.id);
|
|
1094
|
+
console.log(retrieved.content.length); // 100000 (full content preserved)
|
|
1095
|
+
console.log(retrieved.metadata.author); // "John Doe"
|
|
1096
|
+
console.log(retrieved._hasContent); // true (indicates body usage)
|
|
1097
|
+
|
|
1098
|
+
// Perfect for storing large JSON documents, logs, or any large content
|
|
1099
|
+
const logEntry = await documents.insert({
|
|
1100
|
+
title: "Application Log",
|
|
1101
|
+
content: JSON.stringify({
|
|
1102
|
+
timestamp: new Date().toISOString(),
|
|
1103
|
+
level: "INFO",
|
|
1104
|
+
message: "Application started",
|
|
1105
|
+
details: {
|
|
1106
|
+
// ... large log details
|
|
1107
|
+
}
|
|
1108
|
+
}),
|
|
1109
|
+
metadata: { source: "api-server", environment: "production" }
|
|
1110
|
+
});
|
|
1111
|
+
```
|
|
1112
|
+
|
|
937
1113
|
### ๐ Advanced Streaming API
|
|
938
1114
|
|
|
939
1115
|
Handle large datasets efficiently with advanced streaming capabilities:
|
|
@@ -1409,782 +1585,4 @@ console.log(`Total users: ${allUsers.length}`);
|
|
|
1409
1585
|
|
|
1410
1586
|
| Method | Description | Example |
|
|
1411
1587
|
|--------|-------------|---------|
|
|
1412
|
-
| `
|
|
1413
|
-
| `writable(options?)` | Create writable stream | `await users.writable({batchSize: 25})` |
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
---
|
|
1418
|
-
|
|
1419
|
-
## ๐จ Examples
|
|
1420
|
-
|
|
1421
|
-
### ๐ Blog Platform
|
|
1422
|
-
|
|
1423
|
-
```javascript
|
|
1424
|
-
// Create blog posts with body-overflow behavior for long content
|
|
1425
|
-
const posts = await s3db.createResource({
|
|
1426
|
-
name: "posts",
|
|
1427
|
-
attributes: {
|
|
1428
|
-
title: "string|min:5|max:200",
|
|
1429
|
-
content: "string",
|
|
1430
|
-
author: "string",
|
|
1431
|
-
tags: "array|items:string",
|
|
1432
|
-
published: "boolean|default:false",
|
|
1433
|
-
publishedAt: "date|optional"
|
|
1434
|
-
},
|
|
1435
|
-
behavior: "body-overflow", // Handle long content
|
|
1436
|
-
timestamps: true,
|
|
1437
|
-
partitions: {
|
|
1438
|
-
byAuthor: { fields: { author: "string" } },
|
|
1439
|
-
byTag: { fields: { "tags.0": "string" } }
|
|
1440
|
-
}
|
|
1441
|
-
});
|
|
1442
|
-
|
|
1443
|
-
// Create a blog post
|
|
1444
|
-
const post = await posts.insert({
|
|
1445
|
-
title: "Getting Started with s3db.js",
|
|
1446
|
-
content: "This is a comprehensive guide to using s3db.js for your next project...",
|
|
1447
|
-
author: "john_doe",
|
|
1448
|
-
tags: ["tutorial", "database", "s3"],
|
|
1449
|
-
published: true,
|
|
1450
|
-
publishedAt: new Date()
|
|
1451
|
-
});
|
|
1452
|
-
|
|
1453
|
-
// Query posts by author
|
|
1454
|
-
const johnsPosts = await posts.list({
|
|
1455
|
-
partition: "byAuthor",
|
|
1456
|
-
partitionValues: { author: "john_doe" }
|
|
1457
|
-
});
|
|
1458
|
-
|
|
1459
|
-
// Get all posts (simple approach)
|
|
1460
|
-
const allPosts = await posts.getAll();
|
|
1461
|
-
console.log(`Total posts: ${allPosts.length}`);
|
|
1462
|
-
|
|
1463
|
-
// Get posts with pagination (advanced approach)
|
|
1464
|
-
const firstPage = await posts.list({ limit: 10, offset: 0 });
|
|
1465
|
-
const secondPage = await posts.list({ limit: 10, offset: 10 });
|
|
1466
|
-
```
|
|
1467
|
-
|
|
1468
|
-
### ๐ E-commerce Store
|
|
1469
|
-
|
|
1470
|
-
```javascript
|
|
1471
|
-
// Products with detailed specifications
|
|
1472
|
-
const products = await s3db.createResource({
|
|
1473
|
-
name: "products",
|
|
1474
|
-
attributes: {
|
|
1475
|
-
name: "string|min:2|max:200",
|
|
1476
|
-
description: "string",
|
|
1477
|
-
price: "number|positive",
|
|
1478
|
-
category: "string",
|
|
1479
|
-
inventory: {
|
|
1480
|
-
stock: "number|integer|min:0",
|
|
1481
|
-
reserved: "number|integer|min:0|default:0"
|
|
1482
|
-
},
|
|
1483
|
-
specifications: "object|optional",
|
|
1484
|
-
images: "array|items:url"
|
|
1485
|
-
},
|
|
1486
|
-
behavior: "body-overflow",
|
|
1487
|
-
timestamps: true,
|
|
1488
|
-
partitions: {
|
|
1489
|
-
byCategory: { fields: { category: "string" } }
|
|
1490
|
-
}
|
|
1491
|
-
});
|
|
1492
|
-
|
|
1493
|
-
// Orders with customer information
|
|
1494
|
-
const orders = await s3db.createResource({
|
|
1495
|
-
name: "orders",
|
|
1496
|
-
attributes: {
|
|
1497
|
-
customerId: "string",
|
|
1498
|
-
items: "array|items:object",
|
|
1499
|
-
total: "number|positive",
|
|
1500
|
-
status: "string|enum:pending,processing,shipped,delivered",
|
|
1501
|
-
shipping: {
|
|
1502
|
-
address: "string",
|
|
1503
|
-
city: "string",
|
|
1504
|
-
country: "string",
|
|
1505
|
-
zipCode: "string"
|
|
1506
|
-
}
|
|
1507
|
-
},
|
|
1508
|
-
behavior: "enforce-limits",
|
|
1509
|
-
timestamps: true
|
|
1510
|
-
});
|
|
1511
|
-
|
|
1512
|
-
// Create a product
|
|
1513
|
-
const product = await products.insert({
|
|
1514
|
-
name: "Premium Wireless Headphones",
|
|
1515
|
-
description: "High-quality audio with active noise cancellation",
|
|
1516
|
-
price: 299.99,
|
|
1517
|
-
category: "electronics",
|
|
1518
|
-
inventory: { stock: 50 },
|
|
1519
|
-
specifications: {
|
|
1520
|
-
brand: "AudioTech",
|
|
1521
|
-
model: "AT-WH1000",
|
|
1522
|
-
features: ["ANC", "Bluetooth 5.0", "30h battery"]
|
|
1523
|
-
},
|
|
1524
|
-
images: ["https://example.com/headphones-1.jpg"]
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
// Get all products (simple listing)
|
|
1528
|
-
const allProducts = await products.getAll();
|
|
1529
|
-
console.log(`Total products: ${allProducts.length}`);
|
|
1530
|
-
|
|
1531
|
-
// Get products by category (partitioned listing)
|
|
1532
|
-
const electronics = await products.list({
|
|
1533
|
-
partition: "byCategory",
|
|
1534
|
-
partitionValues: { category: "electronics" }
|
|
1535
|
-
});
|
|
1536
|
-
|
|
1537
|
-
// Create an order
|
|
1538
|
-
const order = await orders.insert({
|
|
1539
|
-
customerId: "customer-123",
|
|
1540
|
-
items: [
|
|
1541
|
-
{ productId: product.id, quantity: 1, price: 299.99 }
|
|
1542
|
-
],
|
|
1543
|
-
total: 299.99,
|
|
1544
|
-
status: "pending",
|
|
1545
|
-
shipping: {
|
|
1546
|
-
address: "123 Main St",
|
|
1547
|
-
city: "New York",
|
|
1548
|
-
country: "USA",
|
|
1549
|
-
zipCode: "10001"
|
|
1550
|
-
}
|
|
1551
|
-
});
|
|
1552
|
-
```
|
|
1553
|
-
|
|
1554
|
-
### ๐ฅ User Management System
|
|
1555
|
-
|
|
1556
|
-
```javascript
|
|
1557
|
-
// Users with authentication
|
|
1558
|
-
const users = await s3db.createResource({
|
|
1559
|
-
name: "users",
|
|
1560
|
-
attributes: {
|
|
1561
|
-
username: "string|min:3|max:50|unique",
|
|
1562
|
-
email: "email|unique",
|
|
1563
|
-
password: "secret", // Automatically encrypted
|
|
1564
|
-
role: "string|enum:user,admin,moderator|default:user",
|
|
1565
|
-
profile: {
|
|
1566
|
-
firstName: "string",
|
|
1567
|
-
lastName: "string",
|
|
1568
|
-
avatar: "url|optional",
|
|
1569
|
-
bio: "string|max:500|optional"
|
|
1570
|
-
},
|
|
1571
|
-
preferences: {
|
|
1572
|
-
theme: "string|enum:light,dark|default:light",
|
|
1573
|
-
language: "string|default:en",
|
|
1574
|
-
notifications: "boolean|default:true"
|
|
1575
|
-
},
|
|
1576
|
-
lastLogin: "date|optional"
|
|
1577
|
-
},
|
|
1578
|
-
behavior: "enforce-limits",
|
|
1579
|
-
timestamps: true,
|
|
1580
|
-
hooks: {
|
|
1581
|
-
preInsert: [async (data) => {
|
|
1582
|
-
// Auto-generate secure password if not provided
|
|
1583
|
-
if (!data.password) {
|
|
1584
|
-
data.password = generateSecurePassword();
|
|
1585
|
-
}
|
|
1586
|
-
return data;
|
|
1587
|
-
}],
|
|
1588
|
-
afterInsert: [async (data) => {
|
|
1589
|
-
console.log(`Welcome ${data.username}! ๐`);
|
|
1590
|
-
}]
|
|
1591
|
-
}
|
|
1592
|
-
});
|
|
1593
|
-
|
|
1594
|
-
// Register a new user
|
|
1595
|
-
const user = await users.insert({
|
|
1596
|
-
username: "jane_smith",
|
|
1597
|
-
email: "jane@example.com",
|
|
1598
|
-
profile: {
|
|
1599
|
-
firstName: "Jane",
|
|
1600
|
-
lastName: "Smith"
|
|
1601
|
-
},
|
|
1602
|
-
preferences: {
|
|
1603
|
-
theme: "dark",
|
|
1604
|
-
notifications: true
|
|
1605
|
-
}
|
|
1606
|
-
});
|
|
1607
|
-
|
|
1608
|
-
// Password was auto-generated and encrypted
|
|
1609
|
-
console.log("Generated password:", user.password);
|
|
1610
|
-
```
|
|
1611
|
-
|
|
1612
|
-
---
|
|
1613
|
-
|
|
1614
|
-
## ๐ Security
|
|
1615
|
-
|
|
1616
|
-
### ๐ Field-Level Encryption
|
|
1617
|
-
|
|
1618
|
-
Sensitive data is automatically encrypted using the `"secret"` type:
|
|
1619
|
-
|
|
1620
|
-
```javascript
|
|
1621
|
-
const users = await s3db.createResource({
|
|
1622
|
-
name: "users",
|
|
1623
|
-
attributes: {
|
|
1624
|
-
email: "email",
|
|
1625
|
-
password: "secret", // ๐ Encrypted
|
|
1626
|
-
apiKey: "secret", // ๐ Encrypted
|
|
1627
|
-
creditCard: "secret" // ๐ Encrypted
|
|
1628
|
-
}
|
|
1629
|
-
});
|
|
1630
|
-
|
|
1631
|
-
const user = await users.insert({
|
|
1632
|
-
email: "john@example.com",
|
|
1633
|
-
password: "my_secure_password",
|
|
1634
|
-
apiKey: "sk_live_123456789",
|
|
1635
|
-
creditCard: "4111111111111111"
|
|
1636
|
-
});
|
|
1637
|
-
|
|
1638
|
-
// Data is automatically decrypted when retrieved
|
|
1639
|
-
const retrieved = await users.get(user.id);
|
|
1640
|
-
console.log(retrieved.password); // "my_secure_password" โ
|
|
1641
|
-
```
|
|
1642
|
-
|
|
1643
|
-
### ๐ฒ Auto-Generated Secure Passwords
|
|
1644
|
-
|
|
1645
|
-
s3db.js automatically generates secure passwords for `secret` fields when not provided:
|
|
1646
|
-
|
|
1647
|
-
```javascript
|
|
1648
|
-
const accounts = await s3db.createResource({
|
|
1649
|
-
name: "accounts",
|
|
1650
|
-
attributes: {
|
|
1651
|
-
name: "string",
|
|
1652
|
-
password: "secret", // Auto-generated if not provided
|
|
1653
|
-
apiKey: "secret" // Auto-generated if not provided
|
|
1654
|
-
}
|
|
1655
|
-
});
|
|
1656
|
-
|
|
1657
|
-
const account = await accounts.insert({
|
|
1658
|
-
name: "Service Account"
|
|
1659
|
-
// password and apiKey will be auto-generated
|
|
1660
|
-
});
|
|
1661
|
-
|
|
1662
|
-
console.log(account.password); // "Ax7Kp9mN2qR3" (12-char secure password)
|
|
1663
|
-
console.log(account.apiKey); // "Bc8Lq0nO3sS4" (12-char secure key)
|
|
1664
|
-
```
|
|
1665
|
-
|
|
1666
|
-
**Features:**
|
|
1667
|
-
- ๐ฏ **12-character passwords** with cryptographically secure randomness
|
|
1668
|
-
- ๐ซ **No confusing characters** (excludes 0, O, 1, l, I)
|
|
1669
|
-
- ๐ **Unique every time** using nanoid generation
|
|
1670
|
-
- ๐ก๏ธ **Custom passwords supported** when explicitly provided
|
|
1671
|
-
|
|
1672
|
-
### ๐ Custom Encryption Keys
|
|
1673
|
-
|
|
1674
|
-
```javascript
|
|
1675
|
-
import fs from "fs";
|
|
1676
|
-
|
|
1677
|
-
const s3db = new S3db({
|
|
1678
|
-
uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
1679
|
-
passphrase: fs.readFileSync("./private-key.pem") // Custom encryption key
|
|
1680
|
-
});
|
|
1681
|
-
```
|
|
1682
|
-
|
|
1683
|
-
### โ๏ธ Advanced Configuration Options
|
|
1684
|
-
|
|
1685
|
-
#### Database Configuration
|
|
1686
|
-
|
|
1687
|
-
```javascript
|
|
1688
|
-
const s3db = new S3db({
|
|
1689
|
-
uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
1690
|
-
|
|
1691
|
-
// Versioning
|
|
1692
|
-
versioningEnabled: true, // Enable versioning for all resources
|
|
1693
|
-
|
|
1694
|
-
// Performance
|
|
1695
|
-
parallelism: 25, // Concurrent operations (default: 10)
|
|
1696
|
-
|
|
1697
|
-
// Plugins
|
|
1698
|
-
plugins: [CachePlugin, CostsPlugin], // Enable plugins
|
|
1699
|
-
|
|
1700
|
-
// Security
|
|
1701
|
-
passphrase: "custom-secret-key", // Encryption key
|
|
1702
|
-
|
|
1703
|
-
// Debugging
|
|
1704
|
-
verbose: true, // Enable verbose logging
|
|
1705
|
-
});
|
|
1706
|
-
```
|
|
1707
|
-
|
|
1708
|
-
#### Resource Configuration
|
|
1709
|
-
|
|
1710
|
-
```javascript
|
|
1711
|
-
const users = await s3db.createResource({
|
|
1712
|
-
name: "users",
|
|
1713
|
-
attributes: { name: "string", email: "string" },
|
|
1714
|
-
|
|
1715
|
-
// ID Generation
|
|
1716
|
-
idGenerator: uuidv4, // Custom ID generator function
|
|
1717
|
-
idSize: 16, // Custom ID size (if no idGenerator)
|
|
1718
|
-
|
|
1719
|
-
// Versioning
|
|
1720
|
-
versioningEnabled: true, // Enable for this resource
|
|
1721
|
-
|
|
1722
|
-
// Behavior Strategy
|
|
1723
|
-
behavior: "body-overflow", // How to handle large data
|
|
1724
|
-
|
|
1725
|
-
// Schema Options
|
|
1726
|
-
allNestedObjectsOptional: true, // Make nested objects optional
|
|
1727
|
-
autoDecrypt: true, // Auto-decrypt secret fields
|
|
1728
|
-
|
|
1729
|
-
// Security
|
|
1730
|
-
paranoid: true, // Security flag for dangerous operations
|
|
1731
|
-
|
|
1732
|
-
// Performance
|
|
1733
|
-
cache: false, // Enable caching for this resource
|
|
1734
|
-
parallelism: 10, // Resource-specific parallelism
|
|
1735
|
-
});
|
|
1736
|
-
```
|
|
1737
|
-
|
|
1738
|
-
---
|
|
1739
|
-
|
|
1740
|
-
## ๐ฐ Cost Analysis
|
|
1741
|
-
|
|
1742
|
-
### ๐ Understanding S3 Costs
|
|
1743
|
-
|
|
1744
|
-
s3db.js is incredibly cost-effective because it uses S3 metadata instead of file storage:
|
|
1745
|
-
|
|
1746
|
-
| Operation | AWS Cost | s3db.js Usage |
|
|
1747
|
-
|-----------|----------|---------------|
|
|
1748
|
-
| **PUT Requests** | $0.0005 per 1,000 | Document inserts/updates |
|
|
1749
|
-
| **GET Requests** | $0.0004 per 1,000 | Document retrievals |
|
|
1750
|
-
| **Storage** | $0.023 per GB | ~$0 (uses 0-byte files) |
|
|
1751
|
-
| **Data Transfer** | $0.09 per GB | Minimal (metadata only) |
|
|
1752
|
-
|
|
1753
|
-
### ๐ก Cost Examples
|
|
1754
|
-
|
|
1755
|
-
<details>
|
|
1756
|
-
<summary><strong>๐ Small Application (1,000 users)</strong></summary>
|
|
1757
|
-
|
|
1758
|
-
```javascript
|
|
1759
|
-
// One-time setup cost
|
|
1760
|
-
const setupCost = 0.0005; // 1,000 PUT requests = $0.0005
|
|
1761
|
-
|
|
1762
|
-
// Monthly operations (10 reads per user)
|
|
1763
|
-
const monthlyReads = 0.004; // 10,000 GET requests = $0.004
|
|
1764
|
-
const monthlyUpdates = 0.0005; // 1,000 PUT requests = $0.0005
|
|
1765
|
-
|
|
1766
|
-
const totalMonthlyCost = monthlyReads + monthlyUpdates;
|
|
1767
|
-
console.log(`Monthly cost: $${totalMonthlyCost.toFixed(4)}`); // $0.0045
|
|
1768
|
-
```
|
|
1769
|
-
|
|
1770
|
-
</details>
|
|
1771
|
-
|
|
1772
|
-
<details>
|
|
1773
|
-
<summary><strong>๐ Large Application (1,000,000 users)</strong></summary>
|
|
1774
|
-
|
|
1775
|
-
```javascript
|
|
1776
|
-
// One-time setup cost
|
|
1777
|
-
const setupCost = 0.50; // 1,000,000 PUT requests = $0.50
|
|
1778
|
-
|
|
1779
|
-
// Monthly operations (10 reads per user)
|
|
1780
|
-
const monthlyReads = 4.00; // 10,000,000 GET requests = $4.00
|
|
1781
|
-
const monthlyUpdates = 0.50; // 1,000,000 PUT requests = $0.50
|
|
1782
|
-
|
|
1783
|
-
const totalMonthlyCost = monthlyReads + monthlyUpdates;
|
|
1784
|
-
console.log(`Monthly cost: $${totalMonthlyCost.toFixed(2)}`); // $4.50
|
|
1785
|
-
```
|
|
1786
|
-
|
|
1787
|
-
</details>
|
|
1788
|
-
|
|
1789
|
-
### ๐ Cost Tracking
|
|
1790
|
-
|
|
1791
|
-
Monitor your expenses with the built-in cost tracking plugin:
|
|
1792
|
-
|
|
1793
|
-
```javascript
|
|
1794
|
-
import { CostsPlugin } from "s3db.js";
|
|
1795
|
-
|
|
1796
|
-
const s3db = new S3db({
|
|
1797
|
-
uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
1798
|
-
plugins: [CostsPlugin]
|
|
1799
|
-
});
|
|
1800
|
-
|
|
1801
|
-
// After operations
|
|
1802
|
-
console.log("๐ฐ Total cost:", s3db.client.costs.total.toFixed(4), "USD");
|
|
1803
|
-
console.log("๐ Requests made:", s3db.client.costs.requests.total);
|
|
1804
|
-
console.log("๐ Cost breakdown:", s3db.client.costs.breakdown);
|
|
1805
|
-
```
|
|
1806
|
-
|
|
1807
|
-
---
|
|
1808
|
-
|
|
1809
|
-
## ๐จ Best Practices
|
|
1810
|
-
|
|
1811
|
-
### โ
Do's
|
|
1812
|
-
|
|
1813
|
-
#### **๐ฏ Design for Document Storage**
|
|
1814
|
-
```javascript
|
|
1815
|
-
// โ
Good: Well-structured documents
|
|
1816
|
-
const user = {
|
|
1817
|
-
id: "user-123",
|
|
1818
|
-
name: "John Doe",
|
|
1819
|
-
profile: {
|
|
1820
|
-
bio: "Software developer",
|
|
1821
|
-
preferences: { theme: "dark" }
|
|
1822
|
-
}
|
|
1823
|
-
};
|
|
1824
|
-
```
|
|
1825
|
-
|
|
1826
|
-
#### **๐ Use Sequential IDs for Performance**
|
|
1827
|
-
```javascript
|
|
1828
|
-
// โ
Best: Sequential IDs
|
|
1829
|
-
const productIds = ["00001", "00002", "00003"];
|
|
1830
|
-
|
|
1831
|
-
// โ
Good: UUIDs with sufficient entropy
|
|
1832
|
-
const userIds = ["a1b2c3d4", "e5f6g7h8", "i9j0k1l2"];
|
|
1833
|
-
```
|
|
1834
|
-
|
|
1835
|
-
#### **๐ Leverage Streaming for Large Operations**
|
|
1836
|
-
```javascript
|
|
1837
|
-
// โ
Good: Process large datasets with streams
|
|
1838
|
-
const stream = await users.readable();
|
|
1839
|
-
stream.on("data", (user) => {
|
|
1840
|
-
// Process each user individually
|
|
1841
|
-
});
|
|
1842
|
-
```
|
|
1843
|
-
|
|
1844
|
-
#### **๐๏ธ Choose the Right Behavior Strategy**
|
|
1845
|
-
```javascript
|
|
1846
|
-
// โ
Development: Flexible with warnings
|
|
1847
|
-
{ behavior: "user-management" }
|
|
1848
|
-
|
|
1849
|
-
// โ
Production: Strict validation
|
|
1850
|
-
{ behavior: "enforce-limits" }
|
|
1851
|
-
|
|
1852
|
-
// โ
Content: Preserve all data
|
|
1853
|
-
{ behavior: "body-overflow" }
|
|
1854
|
-
```
|
|
1855
|
-
|
|
1856
|
-
### โ Don'ts
|
|
1857
|
-
|
|
1858
|
-
#### **๐ซ Avoid Large Arrays in Documents**
|
|
1859
|
-
```javascript
|
|
1860
|
-
// โ Bad: Large arrays can exceed 2KB limit
|
|
1861
|
-
const user = {
|
|
1862
|
-
name: "John",
|
|
1863
|
-
purchaseHistory: [/* hundreds of orders */]
|
|
1864
|
-
};
|
|
1865
|
-
|
|
1866
|
-
// โ
Better: Use separate resource with references
|
|
1867
|
-
const user = { name: "John", id: "user-123" };
|
|
1868
|
-
const orders = [
|
|
1869
|
-
{ userId: "user-123", product: "...", date: "..." },
|
|
1870
|
-
// Store orders separately
|
|
1871
|
-
];
|
|
1872
|
-
```
|
|
1873
|
-
|
|
1874
|
-
#### **๐ซ Don't Load Everything at Once**
|
|
1875
|
-
```javascript
|
|
1876
|
-
// โ Bad: Memory intensive
|
|
1877
|
-
const allUsers = await users.getAll();
|
|
1878
|
-
|
|
1879
|
-
// โ
Better: Use pagination or streaming
|
|
1880
|
-
const page = await users.page({ offset: 0, size: 100 });
|
|
1881
|
-
```
|
|
1882
|
-
|
|
1883
|
-
### ๐ฏ Performance Tips
|
|
1884
|
-
|
|
1885
|
-
1. **Enable caching** for frequently accessed data:
|
|
1886
|
-
```javascript
|
|
1887
|
-
const s3db = new S3db({
|
|
1888
|
-
uri: "s3://...",
|
|
1889
|
-
cache: true,
|
|
1890
|
-
ttl: 3600 // 1 hour
|
|
1891
|
-
});
|
|
1892
|
-
```
|
|
1893
|
-
|
|
1894
|
-
2. **Adjust parallelism** for bulk operations:
|
|
1895
|
-
```javascript
|
|
1896
|
-
const s3db = new S3db({
|
|
1897
|
-
uri: "s3://...",
|
|
1898
|
-
parallelism: 25 // Handle 25 concurrent operations
|
|
1899
|
-
});
|
|
1900
|
-
```
|
|
1901
|
-
|
|
1902
|
-
3. **Use partitions** for efficient queries:
|
|
1903
|
-
```javascript
|
|
1904
|
-
// Query specific partitions instead of scanning all data
|
|
1905
|
-
const results = await users.list({
|
|
1906
|
-
partition: "byRegion",
|
|
1907
|
-
partitionValues: { region: "us-east" }
|
|
1908
|
-
});
|
|
1909
|
-
```
|
|
1910
|
-
|
|
1911
|
-
### ๐ง Troubleshooting
|
|
1912
|
-
|
|
1913
|
-
#### Common Issues and Solutions
|
|
1914
|
-
|
|
1915
|
-
**1. Data Exceeds 2KB Limit**
|
|
1916
|
-
```javascript
|
|
1917
|
-
// Problem: "S3 metadata size exceeds 2KB limit"
|
|
1918
|
-
// Solution: Use appropriate behavior strategy
|
|
1919
|
-
const users = await s3db.createResource({
|
|
1920
|
-
name: "users",
|
|
1921
|
-
attributes: { name: "string", bio: "string" },
|
|
1922
|
-
behavior: "body-overflow" // Handles large data automatically
|
|
1923
|
-
});
|
|
1924
|
-
```
|
|
1925
|
-
|
|
1926
|
-
**2. Version Conflicts**
|
|
1927
|
-
```javascript
|
|
1928
|
-
// Problem: Objects not migrating between versions
|
|
1929
|
-
// Solution: Ensure versioning is enabled and update objects
|
|
1930
|
-
const users = await s3db.createResource({
|
|
1931
|
-
name: "users",
|
|
1932
|
-
versioningEnabled: true
|
|
1933
|
-
});
|
|
1934
|
-
|
|
1935
|
-
// Force migration by updating the object
|
|
1936
|
-
await users.update(userId, { ...existingData, newField: "value" });
|
|
1937
|
-
```
|
|
1938
|
-
|
|
1939
|
-
**3. Partition Validation Errors**
|
|
1940
|
-
```javascript
|
|
1941
|
-
// Problem: "Partition uses field that does not exist"
|
|
1942
|
-
// Solution: Ensure partition fields match attributes
|
|
1943
|
-
const users = await s3db.createResource({
|
|
1944
|
-
name: "users",
|
|
1945
|
-
attributes: {
|
|
1946
|
-
name: "string",
|
|
1947
|
-
email: "string",
|
|
1948
|
-
profile: { country: "string" }
|
|
1949
|
-
},
|
|
1950
|
-
partitions: {
|
|
1951
|
-
byEmail: { fields: { email: "string" } }, // โ
Valid
|
|
1952
|
-
byCountry: { fields: { "profile.country": "string" } } // โ
Valid
|
|
1953
|
-
// byInvalid: { fields: { invalid: "string" } } // โ Invalid
|
|
1954
|
-
}
|
|
1955
|
-
});
|
|
1956
|
-
|
|
1957
|
-
// Use standard list() method with partition parameters
|
|
1958
|
-
const results = await users.list({
|
|
1959
|
-
partition: "byEmail",
|
|
1960
|
-
partitionValues: { email: "user@example.com" }
|
|
1961
|
-
});
|
|
1962
|
-
```
|
|
1963
|
-
|
|
1964
|
-
**4. ID Generation Issues**
|
|
1965
|
-
```javascript
|
|
1966
|
-
// Problem: Custom ID generator not working
|
|
1967
|
-
// Solution: Check priority order and function signature
|
|
1968
|
-
const users = await s3db.createResource({
|
|
1969
|
-
name: "users",
|
|
1970
|
-
attributes: { name: "string" },
|
|
1971
|
-
idGenerator: () => `user_${Date.now()}`, // Must return string
|
|
1972
|
-
// idSize: 16 // This is ignored when idGenerator is provided
|
|
1973
|
-
});
|
|
1974
|
-
```
|
|
1975
|
-
|
|
1976
|
-
**5. Plugin Setup Issues**
|
|
1977
|
-
```javascript
|
|
1978
|
-
// Problem: Plugins not working
|
|
1979
|
-
// Solution: Ensure proper import and setup
|
|
1980
|
-
import { CachePlugin, CostsPlugin } from 's3db.js';
|
|
1981
|
-
|
|
1982
|
-
const s3db = new S3db({
|
|
1983
|
-
uri: "s3://...",
|
|
1984
|
-
plugins: [CachePlugin, CostsPlugin] // Array of plugin classes/functions
|
|
1985
|
-
});
|
|
1986
|
-
|
|
1987
|
-
await s3db.connect(); // Plugins are initialized during connect
|
|
1988
|
-
```
|
|
1989
|
-
|
|
1990
|
-
**6. Streaming Performance Issues**
|
|
1991
|
-
```javascript
|
|
1992
|
-
// Problem: Streams too slow or memory intensive
|
|
1993
|
-
// Solution: Adjust batch size and concurrency
|
|
1994
|
-
const stream = await users.readable({
|
|
1995
|
-
batchSize: 10, // Smaller batches for memory
|
|
1996
|
-
concurrency: 5 // Fewer concurrent operations
|
|
1997
|
-
});
|
|
1998
|
-
```
|
|
1999
|
-
|
|
2000
|
-
**7. Hook Execution Problems**
|
|
2001
|
-
```javascript
|
|
2002
|
-
// Problem: Hooks not executing or context issues
|
|
2003
|
-
// Solution: Use proper function binding and error handling
|
|
2004
|
-
const users = await s3db.createResource({
|
|
2005
|
-
name: "users",
|
|
2006
|
-
attributes: { name: "string" },
|
|
2007
|
-
hooks: {
|
|
2008
|
-
preInsert: [
|
|
2009
|
-
async function(data) { // Use function() for proper 'this' binding
|
|
2010
|
-
console.log('Resource name:', this.name);
|
|
2011
|
-
return data;
|
|
2012
|
-
}
|
|
2013
|
-
]
|
|
2014
|
-
}
|
|
2015
|
-
});
|
|
2016
|
-
```
|
|
2017
|
-
|
|
2018
|
-
### ๐ก Events and Emitters
|
|
2019
|
-
|
|
2020
|
-
s3db.js uses Node.js EventEmitter for real-time notifications:
|
|
2021
|
-
|
|
2022
|
-
#### Resource Events
|
|
2023
|
-
|
|
2024
|
-
```javascript
|
|
2025
|
-
const users = await s3db.createResource({
|
|
2026
|
-
name: "users",
|
|
2027
|
-
attributes: { name: "string", email: "string" }
|
|
2028
|
-
});
|
|
2029
|
-
|
|
2030
|
-
// Listen for resource operations
|
|
2031
|
-
users.on('insert', (data) => {
|
|
2032
|
-
console.log('User inserted:', data.name);
|
|
2033
|
-
});
|
|
2034
|
-
|
|
2035
|
-
users.on('update', (oldData, newData) => {
|
|
2036
|
-
console.log('User updated:', newData.name);
|
|
2037
|
-
});
|
|
2038
|
-
|
|
2039
|
-
users.on('delete', (id) => {
|
|
2040
|
-
console.log('User deleted:', id);
|
|
2041
|
-
});
|
|
2042
|
-
|
|
2043
|
-
users.on('get', (data) => {
|
|
2044
|
-
console.log('User retrieved:', data.name);
|
|
2045
|
-
});
|
|
2046
|
-
```
|
|
2047
|
-
|
|
2048
|
-
#### Versioning Events
|
|
2049
|
-
|
|
2050
|
-
```javascript
|
|
2051
|
-
// Listen for version changes
|
|
2052
|
-
users.on('versionUpdated', ({ oldVersion, newVersion }) => {
|
|
2053
|
-
console.log(`Resource updated from ${oldVersion} to ${newVersion}`);
|
|
2054
|
-
});
|
|
2055
|
-
```
|
|
2056
|
-
|
|
2057
|
-
#### Behavior Events
|
|
2058
|
-
|
|
2059
|
-
```javascript
|
|
2060
|
-
// Listen for data limit warnings
|
|
2061
|
-
users.on('exceedsLimit', (data) => {
|
|
2062
|
-
console.warn(`Data exceeds 2KB limit by ${data.excess} bytes`);
|
|
2063
|
-
console.log('Operation:', data.operation);
|
|
2064
|
-
console.log('Resource ID:', data.id);
|
|
2065
|
-
});
|
|
2066
|
-
```
|
|
2067
|
-
|
|
2068
|
-
#### Database Events
|
|
2069
|
-
|
|
2070
|
-
```javascript
|
|
2071
|
-
// Listen for database-level events
|
|
2072
|
-
s3db.on('s3db.resourceCreated', (resourceName) => {
|
|
2073
|
-
console.log(`Resource created: ${resourceName}`);
|
|
2074
|
-
});
|
|
2075
|
-
|
|
2076
|
-
s3db.on('s3db.resourceUpdated', (resourceName) => {
|
|
2077
|
-
console.log(`Resource updated: ${resourceName}`);
|
|
2078
|
-
});
|
|
2079
|
-
|
|
2080
|
-
s3db.on('metadataUploaded', (metadata) => {
|
|
2081
|
-
console.log('Database metadata updated');
|
|
2082
|
-
});
|
|
2083
|
-
```
|
|
2084
|
-
|
|
2085
|
-
#### Plugin Events
|
|
2086
|
-
|
|
2087
|
-
```javascript
|
|
2088
|
-
// Listen for plugin-specific events
|
|
2089
|
-
s3db.on('cache.hit', (key) => {
|
|
2090
|
-
console.log('Cache hit:', key);
|
|
2091
|
-
});
|
|
2092
|
-
|
|
2093
|
-
s3db.on('cache.miss', (key) => {
|
|
2094
|
-
console.log('Cache miss:', key);
|
|
2095
|
-
});
|
|
2096
|
-
```
|
|
2097
|
-
|
|
2098
|
-
---
|
|
2099
|
-
|
|
2100
|
-
## ๐งช Testing
|
|
2101
|
-
|
|
2102
|
-
s3db.js includes a comprehensive test suite. Run tests with:
|
|
2103
|
-
|
|
2104
|
-
```bash
|
|
2105
|
-
# Run all tests
|
|
2106
|
-
npm test
|
|
2107
|
-
|
|
2108
|
-
# Run specific test file
|
|
2109
|
-
npm test -- --testNamePattern="Resource"
|
|
2110
|
-
|
|
2111
|
-
# Run with coverage
|
|
2112
|
-
npm run test:coverage
|
|
2113
|
-
```
|
|
2114
|
-
|
|
2115
|
-
### Test Coverage
|
|
2116
|
-
|
|
2117
|
-
- โ
**Unit Tests** - Individual component testing
|
|
2118
|
-
- โ
**Integration Tests** - End-to-end workflows
|
|
2119
|
-
- โ
**Behavior Tests** - Document handling strategies
|
|
2120
|
-
- โ
**Performance Tests** - Large dataset operations
|
|
2121
|
-
- โ
**Security Tests** - Encryption and validation
|
|
2122
|
-
|
|
2123
|
-
---
|
|
2124
|
-
|
|
2125
|
-
## ๐ค Contributing
|
|
2126
|
-
|
|
2127
|
-
We'd love your help making s3db.js even better! Here's how you can contribute:
|
|
2128
|
-
|
|
2129
|
-
### ๐ ๏ธ Development Setup
|
|
2130
|
-
|
|
2131
|
-
```bash
|
|
2132
|
-
# Clone the repository
|
|
2133
|
-
git clone https://github.com/forattini-dev/s3db.js.git
|
|
2134
|
-
cd s3db.js
|
|
2135
|
-
|
|
2136
|
-
# Install dependencies
|
|
2137
|
-
npm install
|
|
2138
|
-
|
|
2139
|
-
# Run tests
|
|
2140
|
-
npm test
|
|
2141
|
-
|
|
2142
|
-
# Start development server
|
|
2143
|
-
npm run dev
|
|
2144
|
-
```
|
|
2145
|
-
|
|
2146
|
-
### ๐ Contribution Guidelines
|
|
2147
|
-
|
|
2148
|
-
1. **๐ด Fork** the repository
|
|
2149
|
-
2. **๐ฟ Create** a feature branch (`git checkout -b feature/amazing-feature`)
|
|
2150
|
-
3. **โจ Make** your changes with tests
|
|
2151
|
-
4. **โ
Ensure** all tests pass (`npm test`)
|
|
2152
|
-
5. **๐ Commit** your changes (`git commit -m 'Add amazing feature'`)
|
|
2153
|
-
6. **๐ Push** to your branch (`git push origin feature/amazing-feature`)
|
|
2154
|
-
7. **๐ Open** a Pull Request
|
|
2155
|
-
|
|
2156
|
-
### ๐ Bug Reports
|
|
2157
|
-
|
|
2158
|
-
Found a bug? Please open an issue with:
|
|
2159
|
-
- Clear description of the problem
|
|
2160
|
-
- Steps to reproduce
|
|
2161
|
-
- Expected vs actual behavior
|
|
2162
|
-
- Your environment details
|
|
2163
|
-
|
|
2164
|
-
### ๐ก Feature Requests
|
|
2165
|
-
|
|
2166
|
-
Have an idea? We'd love to hear it! Open an issue describing:
|
|
2167
|
-
- The problem you're trying to solve
|
|
2168
|
-
- Your proposed solution
|
|
2169
|
-
- Any alternatives you've considered
|
|
2170
|
-
|
|
2171
|
-
---
|
|
2172
|
-
|
|
2173
|
-
## ๐ License
|
|
2174
|
-
|
|
2175
|
-
This project is licensed under the **Unlicense** - see the [LICENSE](LICENSE) file for details.
|
|
2176
|
-
|
|
2177
|
-
This means you can use, modify, and distribute this software for any purpose without any restrictions. It's truly free and open source! ๐
|
|
2178
|
-
|
|
2179
|
-
---
|
|
2180
|
-
|
|
2181
|
-
<p align="center">
|
|
2182
|
-
<strong>Made with โค๏ธ by developers, for developers</strong><br>
|
|
2183
|
-
<a href="https://github.com/forattini-dev/s3db.js">โญ Star us on GitHub</a> โข
|
|
2184
|
-
<a href="https://www.npmjs.com/package/s3db.js">๐ฆ View on NPM</a> โข
|
|
2185
|
-
<a href="https://github.com/forattini-dev/s3db.js/issues">๐ Report Issues</a>
|
|
2186
|
-
</p>
|
|
2187
|
-
|
|
2188
|
-
<p align="center">
|
|
2189
|
-
<sub>Built with Node.js โข Powered by AWS S3 โข Streaming Ready</sub>
|
|
2190
|
-
</p>
|
|
1588
|
+
| `
|