s3db.js 6.0.0 โ 6.1.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 +626 -1210
- package/dist/s3db.cjs.js +3443 -502
- package/dist/s3db.cjs.min.js +1 -31
- package/dist/s3db.d.ts +891 -62
- package/dist/s3db.es.js +3441 -504
- package/dist/s3db.es.min.js +1 -31
- package/dist/s3db.iife.js +3443 -502
- package/dist/s3db.iife.min.js +1 -31
- package/package.json +33 -14
package/README.md
CHANGED
|
@@ -135,13 +135,17 @@ npm install s3db.js
|
|
|
135
135
|
import { S3db } from "s3db.js";
|
|
136
136
|
|
|
137
137
|
const s3db = new S3db({
|
|
138
|
-
|
|
138
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
await s3db.connect();
|
|
142
142
|
console.log("๐ Connected to S3 database!");
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
+
> **โน๏ธ 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.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
145
149
|
### 3. Create your first resource
|
|
146
150
|
|
|
147
151
|
```javascript
|
|
@@ -151,24 +155,9 @@ const users = await s3db.createResource({
|
|
|
151
155
|
name: "string|min:2|max:100",
|
|
152
156
|
email: "email|unique",
|
|
153
157
|
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" } }
|
|
158
|
+
isActive: "boolean"
|
|
161
159
|
},
|
|
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
|
-
}
|
|
160
|
+
timestamps: true
|
|
172
161
|
});
|
|
173
162
|
```
|
|
174
163
|
|
|
@@ -213,6 +202,28 @@ pnpm add s3db.js
|
|
|
213
202
|
yarn add s3db.js
|
|
214
203
|
```
|
|
215
204
|
|
|
205
|
+
### ๐ฆ Optional Dependencies
|
|
206
|
+
|
|
207
|
+
Some features require additional dependencies to be installed manually:
|
|
208
|
+
|
|
209
|
+
#### Replication Dependencies
|
|
210
|
+
|
|
211
|
+
If you plan to use the replication system with external services, install the corresponding dependencies:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# For SQS replication (AWS SQS queues)
|
|
215
|
+
npm install @aws-sdk/client-sqs
|
|
216
|
+
|
|
217
|
+
# For BigQuery replication (Google BigQuery)
|
|
218
|
+
npm install @google-cloud/bigquery
|
|
219
|
+
|
|
220
|
+
# For PostgreSQL replication (PostgreSQL databases)
|
|
221
|
+
npm install pg
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Why manual installation?** These are marked as `peerDependencies` to keep the main package lightweight. Only install what you need!
|
|
225
|
+
```
|
|
226
|
+
|
|
216
227
|
### Environment Setup
|
|
217
228
|
|
|
218
229
|
Create a `.env` file with your AWS credentials:
|
|
@@ -233,7 +244,7 @@ import dotenv from "dotenv";
|
|
|
233
244
|
dotenv.config();
|
|
234
245
|
|
|
235
246
|
const s3db = new S3db({
|
|
236
|
-
|
|
247
|
+
connectionString: `s3://${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY}@${process.env.AWS_BUCKET}/databases/${process.env.DATABASE_NAME}`
|
|
237
248
|
});
|
|
238
249
|
```
|
|
239
250
|
|
|
@@ -245,7 +256,7 @@ const s3db = new S3db({
|
|
|
245
256
|
#### 1. Access Keys (Development)
|
|
246
257
|
```javascript
|
|
247
258
|
const s3db = new S3db({
|
|
248
|
-
|
|
259
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
|
|
249
260
|
});
|
|
250
261
|
```
|
|
251
262
|
|
|
@@ -253,14 +264,14 @@ const s3db = new S3db({
|
|
|
253
264
|
```javascript
|
|
254
265
|
// No credentials needed - uses IAM role permissions
|
|
255
266
|
const s3db = new S3db({
|
|
256
|
-
|
|
267
|
+
connectionString: "s3://BUCKET_NAME/databases/myapp"
|
|
257
268
|
});
|
|
258
269
|
```
|
|
259
270
|
|
|
260
271
|
#### 3. S3-Compatible Services (MinIO, etc.)
|
|
261
272
|
```javascript
|
|
262
273
|
const s3db = new S3db({
|
|
263
|
-
|
|
274
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
264
275
|
endpoint: "http://localhost:9000"
|
|
265
276
|
});
|
|
266
277
|
```
|
|
@@ -276,92 +287,43 @@ A logical container for your resources, stored in a specific S3 prefix.
|
|
|
276
287
|
|
|
277
288
|
```javascript
|
|
278
289
|
const s3db = new S3db({
|
|
279
|
-
|
|
290
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
|
|
280
291
|
});
|
|
281
|
-
// Creates/connects to: s3://bucket/databases/myapp/
|
|
282
292
|
```
|
|
283
293
|
|
|
284
294
|
### ๐ Resources (Collections)
|
|
285
295
|
Resources define the structure of your documents, similar to tables in traditional databases.
|
|
286
296
|
|
|
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
297
|
```javascript
|
|
292
298
|
const users = await s3db.createResource({
|
|
293
299
|
name: "users",
|
|
294
300
|
attributes: {
|
|
295
|
-
// Basic types
|
|
296
301
|
name: "string|min:2|max:100",
|
|
297
302
|
email: "email|unique",
|
|
298
303
|
age: "number|integer|positive",
|
|
299
304
|
isActive: "boolean",
|
|
300
|
-
|
|
301
|
-
// Nested objects
|
|
302
305
|
profile: {
|
|
303
306
|
bio: "string|optional",
|
|
304
|
-
avatar: "url|optional"
|
|
305
|
-
preferences: {
|
|
306
|
-
theme: "string|enum:light,dark|default:light",
|
|
307
|
-
notifications: "boolean|default:true"
|
|
308
|
-
}
|
|
307
|
+
avatar: "url|optional"
|
|
309
308
|
},
|
|
310
|
-
|
|
311
|
-
// Arrays
|
|
312
309
|
tags: "array|items:string|unique",
|
|
313
|
-
|
|
314
|
-
// Encrypted fields
|
|
315
310
|
password: "secret"
|
|
316
311
|
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
partitions: { // Organize data for efficient queries
|
|
312
|
+
timestamps: true,
|
|
313
|
+
behavior: "user-managed",
|
|
314
|
+
partitions: {
|
|
321
315
|
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
316
|
}
|
|
333
317
|
});
|
|
334
318
|
```
|
|
335
319
|
|
|
336
320
|
### ๐ 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
|
|
321
|
+
Built-in validation using [@icebob/fastest-validator](https://github.com/icebob/fastest-validator) with comprehensive rule support and excellent performance.
|
|
358
322
|
|
|
359
323
|
---
|
|
360
324
|
|
|
361
325
|
## โก Advanced Features
|
|
362
326
|
|
|
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
327
|
### ๐ฆ Partitions
|
|
366
328
|
|
|
367
329
|
Organize data efficiently with partitions for faster queries:
|
|
@@ -372,32 +334,16 @@ const analytics = await s3db.createResource({
|
|
|
372
334
|
attributes: {
|
|
373
335
|
userId: "string",
|
|
374
336
|
event: "string",
|
|
375
|
-
timestamp: "date"
|
|
376
|
-
utm: {
|
|
377
|
-
source: "string",
|
|
378
|
-
medium: "string",
|
|
379
|
-
campaign: "string"
|
|
380
|
-
}
|
|
337
|
+
timestamp: "date"
|
|
381
338
|
},
|
|
382
339
|
partitions: {
|
|
383
340
|
byDate: { fields: { timestamp: "date|maxlength:10" } },
|
|
384
|
-
|
|
385
|
-
byUserAndDate: {
|
|
386
|
-
fields: {
|
|
387
|
-
userId: "string",
|
|
388
|
-
timestamp: "date|maxlength:10"
|
|
389
|
-
}
|
|
390
|
-
}
|
|
341
|
+
byUserAndDate: { fields: { userId: "string", timestamp: "date|maxlength:10" } }
|
|
391
342
|
}
|
|
392
343
|
});
|
|
393
344
|
|
|
394
345
|
// Query by partition for better performance
|
|
395
|
-
const
|
|
396
|
-
partition: "byUtmSource",
|
|
397
|
-
partitionValues: { "utm.source": "google" }
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
const todayEvents = await analytics.count({
|
|
346
|
+
const todayEvents = await analytics.list({
|
|
401
347
|
partition: "byDate",
|
|
402
348
|
partitionValues: { timestamp: "2024-01-15" }
|
|
403
349
|
});
|
|
@@ -410,105 +356,35 @@ Add custom logic with pre/post operation hooks:
|
|
|
410
356
|
```javascript
|
|
411
357
|
const products = await s3db.createResource({
|
|
412
358
|
name: "products",
|
|
413
|
-
attributes: {
|
|
414
|
-
name: "string",
|
|
415
|
-
price: "number",
|
|
416
|
-
category: "string"
|
|
417
|
-
},
|
|
359
|
+
attributes: { name: "string", price: "number" },
|
|
418
360
|
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
|
|
361
|
+
preInsert: [async (data) => {
|
|
362
|
+
data.sku = `${data.category.toUpperCase()}-${Date.now()}`;
|
|
363
|
+
return data;
|
|
364
|
+
}],
|
|
365
|
+
afterInsert: [async (data) => {
|
|
366
|
+
console.log(`๐ฆ Product ${data.name} created`);
|
|
367
|
+
}]
|
|
368
|
+
}
|
|
454
369
|
});
|
|
455
370
|
```
|
|
456
371
|
|
|
457
372
|
### ๐ Streaming API
|
|
458
373
|
|
|
459
|
-
Handle large datasets efficiently
|
|
374
|
+
Handle large datasets efficiently:
|
|
460
375
|
|
|
461
376
|
```javascript
|
|
462
|
-
// Export
|
|
377
|
+
// Export to CSV
|
|
463
378
|
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
379
|
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
|
-
});
|
|
380
|
+
readableStream.on("data", (user) => records.push(user));
|
|
381
|
+
readableStream.on("end", () => console.log("โ
Export completed"));
|
|
482
382
|
|
|
483
|
-
// Bulk import
|
|
383
|
+
// Bulk import
|
|
484
384
|
const writableStream = await users.writable();
|
|
485
|
-
importData.forEach(userData =>
|
|
486
|
-
writableStream.write(userData);
|
|
487
|
-
});
|
|
385
|
+
importData.forEach(userData => writableStream.write(userData));
|
|
488
386
|
writableStream.end();
|
|
489
387
|
```
|
|
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
388
|
value: "string"
|
|
513
389
|
},
|
|
514
390
|
behavior: "enforce-limits" // Ensures data stays within 2KB
|
|
@@ -527,33 +403,16 @@ const summaries = await s3db.createResource({
|
|
|
527
403
|
|
|
528
404
|
### ๐ Resource Versioning System
|
|
529
405
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
#### Enable Versioning
|
|
406
|
+
Automatically manages schema evolution and data migration:
|
|
533
407
|
|
|
534
408
|
```javascript
|
|
535
|
-
// Enable versioning
|
|
409
|
+
// Enable versioning
|
|
536
410
|
const s3db = new S3db({
|
|
537
|
-
|
|
538
|
-
versioningEnabled: true
|
|
411
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
412
|
+
versioningEnabled: true
|
|
539
413
|
});
|
|
540
414
|
|
|
541
415
|
// 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
416
|
const users = await s3db.createResource({
|
|
558
417
|
name: "users",
|
|
559
418
|
attributes: {
|
|
@@ -563,114 +422,39 @@ const users = await s3db.createResource({
|
|
|
563
422
|
versioningEnabled: true
|
|
564
423
|
});
|
|
565
424
|
|
|
566
|
-
// Insert
|
|
425
|
+
// Insert in version 0
|
|
567
426
|
const user1 = await users.insert({
|
|
568
427
|
name: "John Doe",
|
|
569
428
|
email: "john@example.com"
|
|
570
429
|
});
|
|
571
430
|
|
|
572
|
-
// Update schema -
|
|
431
|
+
// Update schema - creates version 1
|
|
573
432
|
const updatedUsers = await s3db.createResource({
|
|
574
433
|
name: "users",
|
|
575
434
|
attributes: {
|
|
576
435
|
name: "string|required",
|
|
577
436
|
email: "string|required",
|
|
578
|
-
age: "number|optional"
|
|
579
|
-
profile: "object|optional" // New nested object
|
|
437
|
+
age: "number|optional"
|
|
580
438
|
},
|
|
581
439
|
versioningEnabled: true
|
|
582
440
|
});
|
|
583
441
|
|
|
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
|
|
442
|
+
// Automatic migration
|
|
598
443
|
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" } }
|
|
444
|
+
console.log(migratedUser._v); // "1" - automatically migrated
|
|
647
445
|
|
|
648
446
|
// Query by version
|
|
649
|
-
const
|
|
447
|
+
const version0Users = await users.list({
|
|
650
448
|
partition: "byVersion",
|
|
651
|
-
partitionValues: { _v: "
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
const v1Users = await users.list({
|
|
655
|
-
partition: "byVersion",
|
|
656
|
-
partitionValues: { _v: "v1" }
|
|
449
|
+
partitionValues: { _v: "0" }
|
|
657
450
|
});
|
|
658
451
|
```
|
|
659
452
|
|
|
660
453
|
### ๐ Custom ID Generation
|
|
661
454
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
#### Built-in ID Sizes
|
|
455
|
+
Flexible ID generation strategies:
|
|
665
456
|
|
|
666
457
|
```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
458
|
// Custom size IDs
|
|
675
459
|
const shortUsers = await s3db.createResource({
|
|
676
460
|
name: "short-users",
|
|
@@ -678,23 +462,12 @@ const shortUsers = await s3db.createResource({
|
|
|
678
462
|
idSize: 8 // Generate 8-character IDs
|
|
679
463
|
});
|
|
680
464
|
|
|
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)
|
|
465
|
+
// UUID support
|
|
466
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
694
467
|
const uuidUsers = await s3db.createResource({
|
|
695
468
|
name: "uuid-users",
|
|
696
469
|
attributes: { name: "string|required" },
|
|
697
|
-
idGenerator: uuidv4
|
|
470
|
+
idGenerator: uuidv4
|
|
698
471
|
});
|
|
699
472
|
|
|
700
473
|
// UUID v1 (time-based)
|
|
@@ -703,185 +476,560 @@ const timeUsers = await s3db.createResource({
|
|
|
703
476
|
attributes: { name: "string|required" },
|
|
704
477
|
idGenerator: uuidv1
|
|
705
478
|
});
|
|
706
|
-
```
|
|
707
479
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
```javascript
|
|
711
|
-
// Timestamp-based IDs
|
|
480
|
+
// Custom ID function
|
|
712
481
|
const timestampUsers = await s3db.createResource({
|
|
713
482
|
name: "timestamp-users",
|
|
714
483
|
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()}`
|
|
484
|
+
idGenerator: () => `user_${Date.now()}`
|
|
731
485
|
});
|
|
732
486
|
```
|
|
733
487
|
|
|
734
|
-
|
|
488
|
+
### ๐ Plugin System
|
|
489
|
+
|
|
490
|
+
Extend functionality with powerful plugins. s3db.js supports multiple plugins working together seamlessly:
|
|
735
491
|
|
|
736
492
|
```javascript
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
493
|
+
import {
|
|
494
|
+
CachePlugin,
|
|
495
|
+
CostsPlugin,
|
|
496
|
+
FullTextPlugin,
|
|
497
|
+
MetricsPlugin,
|
|
498
|
+
ReplicationPlugin,
|
|
499
|
+
AuditPlugin
|
|
500
|
+
} from 's3db.js';
|
|
501
|
+
|
|
502
|
+
const s3db = new S3db({
|
|
503
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
504
|
+
plugins: [
|
|
505
|
+
new CachePlugin({ enabled: true }), // CachePlugin needs instantiation
|
|
506
|
+
CostsPlugin, // CostsPlugin is a static object
|
|
507
|
+
new FullTextPlugin({ fields: ['name', 'description'] }),
|
|
508
|
+
new MetricsPlugin({ enabled: true }),
|
|
509
|
+
new ReplicationPlugin({
|
|
510
|
+
enabled: true,
|
|
511
|
+
replicators: [
|
|
512
|
+
{
|
|
513
|
+
driver: 's3db',
|
|
514
|
+
resources: ['users', 'products'],
|
|
515
|
+
config: {
|
|
516
|
+
connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
]
|
|
520
|
+
}),
|
|
521
|
+
new AuditPlugin({ enabled: true })
|
|
522
|
+
]
|
|
743
523
|
});
|
|
524
|
+
|
|
525
|
+
// All plugins work together seamlessly
|
|
526
|
+
await users.insert({ name: "John", email: "john@example.com" });
|
|
527
|
+
// - Cache: Caches the operation
|
|
528
|
+
// - Costs: Tracks S3 costs
|
|
529
|
+
// - FullText: Indexes the data for search
|
|
530
|
+
// - Metrics: Records performance metrics
|
|
531
|
+
// - Replication: Syncs to configured replicators
|
|
532
|
+
// - Audit: Logs the operation
|
|
744
533
|
```
|
|
745
534
|
|
|
746
|
-
###
|
|
535
|
+
### ๐ Replicator System
|
|
747
536
|
|
|
748
|
-
|
|
537
|
+
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.
|
|
749
538
|
|
|
750
|
-
####
|
|
539
|
+
#### Available Replicators
|
|
751
540
|
|
|
541
|
+
**S3DB Replicator** - Replicates data to another s3db instance:
|
|
752
542
|
```javascript
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
}
|
|
543
|
+
{
|
|
544
|
+
driver: 's3db',
|
|
545
|
+
resources: ['users', 'products'], // <-- root level
|
|
546
|
+
config: {
|
|
547
|
+
connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup",
|
|
548
|
+
region: 'us-west-2'
|
|
549
|
+
}
|
|
550
|
+
}
|
|
760
551
|
```
|
|
761
552
|
|
|
762
|
-
|
|
763
|
-
|
|
553
|
+
**SQS Replicator** - Sends data to AWS SQS queues:
|
|
764
554
|
```javascript
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
555
|
+
{
|
|
556
|
+
driver: 'sqs',
|
|
557
|
+
resources: ['orders'],
|
|
558
|
+
config: {
|
|
559
|
+
queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
|
|
560
|
+
region: 'us-east-1',
|
|
561
|
+
messageGroupId: 's3db-replication', // For FIFO queues
|
|
562
|
+
deduplicationId: true // Enable deduplication
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
```
|
|
770
566
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
567
|
+
**BigQuery Replicator** - Sends data to Google BigQuery:
|
|
568
|
+
```javascript
|
|
569
|
+
{
|
|
570
|
+
driver: 'bigquery',
|
|
571
|
+
resources: ['users', 'orders'],
|
|
572
|
+
config: {
|
|
573
|
+
projectId: 'my-project',
|
|
574
|
+
datasetId: 'analytics',
|
|
575
|
+
tableId: 's3db_replication',
|
|
576
|
+
location: 'US',
|
|
577
|
+
credentials: {
|
|
578
|
+
// Your Google Cloud service account credentials
|
|
579
|
+
client_email: 'service-account@project.iam.gserviceaccount.com',
|
|
580
|
+
private_key: '-----BEGIN PRIVATE KEY-----\n...'
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
```
|
|
776
585
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
586
|
+
**PostgreSQL Replicator** - Sends data to PostgreSQL databases:
|
|
587
|
+
```javascript
|
|
588
|
+
{
|
|
589
|
+
driver: 'postgres',
|
|
590
|
+
resources: ['users'],
|
|
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
|
+
tableName: 's3db_replication',
|
|
600
|
+
ssl: false
|
|
601
|
+
}
|
|
602
|
+
}
|
|
781
603
|
```
|
|
782
604
|
|
|
783
|
-
####
|
|
605
|
+
#### Replicator Features
|
|
784
606
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
607
|
+
- **Resource Filtering**: Each replicator can be configured to handle specific resources only
|
|
608
|
+
- **Event Emission**: All replicators emit events for monitoring and debugging
|
|
609
|
+
- **Connection Testing**: Test connections to replicators before use
|
|
610
|
+
- **Batch Operations**: Support for batch replication operations
|
|
611
|
+
- **Error Handling**: Comprehensive error handling and retry logic
|
|
612
|
+
- **Status Monitoring**: Get detailed status and statistics for each replicator
|
|
791
613
|
|
|
792
|
-
|
|
793
|
-
await users.insert({ name: "John" });
|
|
794
|
-
await users.get("user-123");
|
|
795
|
-
await users.list();
|
|
614
|
+
#### Dependencies
|
|
796
615
|
|
|
797
|
-
|
|
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
|
-
// }
|
|
804
|
-
```
|
|
616
|
+
The replicators use optional peer dependencies. Install only what you need:
|
|
805
617
|
|
|
806
|
-
|
|
618
|
+
```bash
|
|
619
|
+
# For SQS replicator
|
|
620
|
+
npm install @aws-sdk/client-sqs
|
|
621
|
+
# or
|
|
622
|
+
yarn add @aws-sdk/client-sqs
|
|
623
|
+
# or
|
|
624
|
+
pnpm add @aws-sdk/client-sqs
|
|
807
625
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
},
|
|
815
|
-
|
|
816
|
-
async start() {
|
|
817
|
-
console.log('Custom plugin started');
|
|
818
|
-
},
|
|
819
|
-
|
|
820
|
-
async stop() {
|
|
821
|
-
console.log('Custom plugin stopped');
|
|
822
|
-
}
|
|
823
|
-
};
|
|
626
|
+
# For BigQuery replicator
|
|
627
|
+
npm install @google-cloud/bigquery
|
|
628
|
+
# or
|
|
629
|
+
yarn add @google-cloud/bigquery
|
|
630
|
+
# or
|
|
631
|
+
pnpm add @google-cloud/bigquery
|
|
824
632
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
633
|
+
# For PostgreSQL replicator
|
|
634
|
+
npm install pg
|
|
635
|
+
# or
|
|
636
|
+
yarn add pg
|
|
637
|
+
# or
|
|
638
|
+
pnpm add pg
|
|
830
639
|
```
|
|
831
640
|
|
|
832
|
-
|
|
641
|
+
**โ ๏ธ 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.
|
|
833
642
|
|
|
834
|
-
|
|
643
|
+
**Example error without dependency:**
|
|
644
|
+
```
|
|
645
|
+
Error: Cannot find module '@aws-sdk/client-sqs'
|
|
646
|
+
```
|
|
835
647
|
|
|
836
|
-
|
|
648
|
+
**Solution:** Install the missing dependency as shown above.
|
|
837
649
|
|
|
838
|
-
|
|
839
|
-
|----------|----------|------------|-----------|-------------|
|
|
840
|
-
| `user-management` | Development/Testing | Warns | No | High |
|
|
841
|
-
| `enforce-limits` | Production/Strict | Throws Error | No | High |
|
|
842
|
-
| `data-truncate` | Content Management | Truncates | Yes | High |
|
|
843
|
-
| `body-overflow` | Large Documents | Uses S3 Body | No | Medium |
|
|
650
|
+
#### Example Usage
|
|
844
651
|
|
|
845
|
-
|
|
652
|
+
See `examples/e34-replicators.js` for a complete example using all four replicator types.
|
|
846
653
|
|
|
847
|
-
|
|
848
|
-
// Flexible behavior - warns but doesn't block
|
|
849
|
-
const users = await s3db.createResource({
|
|
850
|
-
name: "users",
|
|
851
|
-
attributes: { name: "string", bio: "string" },
|
|
852
|
-
behavior: "user-management" // Default
|
|
853
|
-
});
|
|
654
|
+
**Prerequisites:** Make sure to install the required dependencies before running the example:
|
|
854
655
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
656
|
+
```bash
|
|
657
|
+
# Install all replication dependencies for the full example
|
|
658
|
+
npm install @aws-sdk/client-sqs @google-cloud/bigquery pg
|
|
659
|
+
```
|
|
859
660
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
661
|
+
#### ๐ Cache Plugin
|
|
662
|
+
|
|
663
|
+
#### ๐ Cache Plugin
|
|
664
|
+
Intelligent caching to reduce API calls and improve performance:
|
|
665
|
+
|
|
666
|
+
```javascript
|
|
667
|
+
const s3db = new S3db({
|
|
668
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
669
|
+
plugins: [new CachePlugin({
|
|
670
|
+
enabled: true,
|
|
671
|
+
ttl: 300000, // 5 minutes cache
|
|
672
|
+
maxSize: 1000, // Max 1000 items in cache
|
|
673
|
+
driverType: 'memory' // 'memory' or 's3'
|
|
674
|
+
})]
|
|
864
675
|
});
|
|
676
|
+
|
|
677
|
+
// Automatic caching for reads
|
|
678
|
+
await users.count(); // Cached for 5 minutes
|
|
679
|
+
await users.list(); // Cached for 5 minutes
|
|
680
|
+
await users.insert({...}); // Automatically clears cache
|
|
865
681
|
```
|
|
866
682
|
|
|
867
|
-
####
|
|
683
|
+
#### ๐ฐ Costs Plugin
|
|
684
|
+
Track and monitor AWS S3 costs in real-time:
|
|
868
685
|
|
|
869
686
|
```javascript
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
attributes: { key: "string", value: "string" },
|
|
874
|
-
behavior: "enforce-limits"
|
|
687
|
+
const s3db = new S3db({
|
|
688
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
689
|
+
plugins: [CostsPlugin]
|
|
875
690
|
});
|
|
876
691
|
|
|
877
|
-
//
|
|
878
|
-
await
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
692
|
+
// Track costs automatically
|
|
693
|
+
await users.insert({ name: "John", email: "john@example.com" });
|
|
694
|
+
await users.list();
|
|
695
|
+
|
|
696
|
+
// Get cost information
|
|
697
|
+
console.log(s3db.client.costs);
|
|
698
|
+
// { total: 0.000009, requests: { total: 3, get: 1, put: 1, list: 1 } }
|
|
882
699
|
```
|
|
883
700
|
|
|
884
|
-
####
|
|
701
|
+
#### ๐ Full-Text Search Plugin
|
|
702
|
+
Powerful text search with automatic indexing:
|
|
703
|
+
|
|
704
|
+
```javascript
|
|
705
|
+
const s3db = new S3db({
|
|
706
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
707
|
+
plugins: [new FullTextPlugin({
|
|
708
|
+
enabled: true,
|
|
709
|
+
fields: ['name', 'description', 'content'], // Fields to index
|
|
710
|
+
minWordLength: 3,
|
|
711
|
+
maxResults: 50,
|
|
712
|
+
language: 'en-US'
|
|
713
|
+
})]
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Create resource with searchable fields
|
|
717
|
+
const products = await s3db.createResource({
|
|
718
|
+
name: "products",
|
|
719
|
+
attributes: {
|
|
720
|
+
name: "string|required",
|
|
721
|
+
description: "string",
|
|
722
|
+
content: "string"
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// Insert data (automatically indexed)
|
|
727
|
+
await products.insert({
|
|
728
|
+
name: "JavaScript Book",
|
|
729
|
+
description: "Learn JavaScript programming",
|
|
730
|
+
content: "Comprehensive guide to modern JavaScript"
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// Search across all indexed fields
|
|
734
|
+
const results = await s3db.plugins.fulltext.searchRecords('products', 'javascript');
|
|
735
|
+
console.log(results); // Returns products with search scores
|
|
736
|
+
|
|
737
|
+
// Example of search results:
|
|
738
|
+
// [
|
|
739
|
+
// {
|
|
740
|
+
// id: "prod-123",
|
|
741
|
+
// name: "JavaScript Book",
|
|
742
|
+
// description: "Learn JavaScript programming",
|
|
743
|
+
// content: "Comprehensive guide to modern JavaScript",
|
|
744
|
+
// _searchScore: 0.85,
|
|
745
|
+
// _matchedFields: ["name", "description", "content"],
|
|
746
|
+
// _matchedWords: ["javascript"]
|
|
747
|
+
// },
|
|
748
|
+
// {
|
|
749
|
+
// id: "prod-456",
|
|
750
|
+
// name: "Web Development Guide",
|
|
751
|
+
// description: "Includes JavaScript, HTML, and CSS",
|
|
752
|
+
// content: "Complete web development with JavaScript",
|
|
753
|
+
// _searchScore: 0.72,
|
|
754
|
+
// _matchedFields: ["description", "content"],
|
|
755
|
+
// _matchedWords: ["javascript"]
|
|
756
|
+
// }
|
|
757
|
+
// ]
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
#### ๐ Metrics Plugin
|
|
761
|
+
Monitor performance and usage metrics:
|
|
762
|
+
|
|
763
|
+
```javascript
|
|
764
|
+
const s3db = new S3db({
|
|
765
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
766
|
+
plugins: [new MetricsPlugin({
|
|
767
|
+
enabled: true,
|
|
768
|
+
collectPerformance: true,
|
|
769
|
+
collectErrors: true,
|
|
770
|
+
collectUsage: true,
|
|
771
|
+
flushInterval: 60000 // Flush every minute
|
|
772
|
+
})]
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// Metrics are collected automatically
|
|
776
|
+
await users.insert({ name: "John" });
|
|
777
|
+
await users.list();
|
|
778
|
+
|
|
779
|
+
// Get metrics
|
|
780
|
+
const metrics = await s3db.plugins.metrics.getMetrics();
|
|
781
|
+
console.log(metrics); // Performance and usage data
|
|
782
|
+
|
|
783
|
+
// Example of metrics object:
|
|
784
|
+
// {
|
|
785
|
+
// performance: {
|
|
786
|
+
// averageResponseTime: 245, // milliseconds
|
|
787
|
+
// totalRequests: 1250,
|
|
788
|
+
// requestsPerSecond: 12.5,
|
|
789
|
+
// slowestOperations: [
|
|
790
|
+
// { operation: "list", resource: "users", avgTime: 450, count: 50 },
|
|
791
|
+
// { operation: "get", resource: "products", avgTime: 320, count: 200 }
|
|
792
|
+
// ]
|
|
793
|
+
// },
|
|
794
|
+
// usage: {
|
|
795
|
+
// resources: {
|
|
796
|
+
// users: { inserts: 150, updates: 75, deletes: 10, reads: 800 },
|
|
797
|
+
// products: { inserts: 300, updates: 120, deletes: 25, reads: 1200 }
|
|
798
|
+
// },
|
|
799
|
+
// totalOperations: 2680,
|
|
800
|
+
// mostActiveResource: "products",
|
|
801
|
+
// peakUsageHour: "14:00"
|
|
802
|
+
// },
|
|
803
|
+
// errors: {
|
|
804
|
+
// total: 15,
|
|
805
|
+
// byType: {
|
|
806
|
+
// "ValidationError": 8,
|
|
807
|
+
// "NotFoundError": 5,
|
|
808
|
+
// "PermissionError": 2
|
|
809
|
+
// },
|
|
810
|
+
// byResource: {
|
|
811
|
+
// users: 10,
|
|
812
|
+
// products: 5
|
|
813
|
+
// }
|
|
814
|
+
// },
|
|
815
|
+
// cache: {
|
|
816
|
+
// hitRate: 0.78, // 78% cache hit rate
|
|
817
|
+
// totalHits: 980,
|
|
818
|
+
// totalMisses: 270,
|
|
819
|
+
// averageCacheTime: 120 // milliseconds
|
|
820
|
+
// }
|
|
821
|
+
// }
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
#### ๐ Replication Plugin
|
|
825
|
+
Replicate data to other buckets or regions:
|
|
826
|
+
|
|
827
|
+
```javascript
|
|
828
|
+
const s3db = new S3db({
|
|
829
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
830
|
+
plugins: [new ReplicationPlugin({
|
|
831
|
+
enabled: true,
|
|
832
|
+
replicators: [
|
|
833
|
+
{
|
|
834
|
+
driver: 's3db',
|
|
835
|
+
resources: ['users', 'products'],
|
|
836
|
+
config: {
|
|
837
|
+
connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
driver: 'sqs',
|
|
842
|
+
resources: ['orders', 'users', 'products'],
|
|
843
|
+
config: {
|
|
844
|
+
// Resource-specific queues
|
|
845
|
+
queues: {
|
|
846
|
+
users: 'https://sqs.us-east-1.amazonaws.com/123456789012/users-events.fifo',
|
|
847
|
+
orders: 'https://sqs.us-east-1.amazonaws.com/123456789012/orders-events.fifo',
|
|
848
|
+
products: 'https://sqs.us-east-1.amazonaws.com/123456789012/products-events.fifo'
|
|
849
|
+
},
|
|
850
|
+
// Fallback queue for unspecified resources
|
|
851
|
+
defaultQueueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/default-events.fifo',
|
|
852
|
+
messageGroupId: 's3db-replication', // For FIFO queues
|
|
853
|
+
deduplicationId: true // Enable deduplication
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
driver: 'bigquery',
|
|
858
|
+
resources: ['users', 'orders'],
|
|
859
|
+
config: {
|
|
860
|
+
projectId: 'my-project',
|
|
861
|
+
datasetId: 'analytics',
|
|
862
|
+
tableId: 's3db_replication'
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
driver: 'postgres',
|
|
867
|
+
resources: ['users'],
|
|
868
|
+
config: {
|
|
869
|
+
connectionString: 'postgresql://user:pass@localhost:5432/analytics',
|
|
870
|
+
tableName: 's3db_replication'
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
],
|
|
874
|
+
syncInterval: 300000 // Sync every 5 minutes
|
|
875
|
+
})]
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
// Data is automatically replicated to all configured targets
|
|
879
|
+
await users.insert({ name: "John" }); // Synced to all replicators
|
|
880
|
+
|
|
881
|
+
**SQS Message Structure:**
|
|
882
|
+
|
|
883
|
+
The SQS replicator sends standardized messages with the following structure:
|
|
884
|
+
|
|
885
|
+
```javascript
|
|
886
|
+
// INSERT operation
|
|
887
|
+
{
|
|
888
|
+
resource: "users",
|
|
889
|
+
action: "insert",
|
|
890
|
+
data: { _v: 0, id: "user-001", name: "John", email: "john@example.com" },
|
|
891
|
+
timestamp: "2024-01-01T10:00:00.000Z",
|
|
892
|
+
source: "s3db-replication"
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// UPDATE operation (includes before/after data)
|
|
896
|
+
{
|
|
897
|
+
resource: "users",
|
|
898
|
+
action: "update",
|
|
899
|
+
before: { _v: 0, id: "user-001", name: "John", age: 30 },
|
|
900
|
+
data: { _v: 1, id: "user-001", name: "John", age: 31 },
|
|
901
|
+
timestamp: "2024-01-01T10:05:00.000Z",
|
|
902
|
+
source: "s3db-replication"
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// DELETE operation
|
|
906
|
+
{
|
|
907
|
+
resource: "users",
|
|
908
|
+
action: "delete",
|
|
909
|
+
data: { _v: 1, id: "user-001", name: "John", age: 31 },
|
|
910
|
+
timestamp: "2024-01-01T10:10:00.000Z",
|
|
911
|
+
source: "s3db-replication"
|
|
912
|
+
}
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
**Queue Routing:**
|
|
916
|
+
- Each resource can have its own dedicated queue
|
|
917
|
+
- Unspecified resources use the default queue
|
|
918
|
+
- FIFO queues supported with deduplication
|
|
919
|
+
- Messages are automatically routed to the appropriate queue
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
#### ๐ Audit Plugin
|
|
923
|
+
Log all operations for compliance and traceability:
|
|
924
|
+
|
|
925
|
+
```javascript
|
|
926
|
+
const s3db = new S3db({
|
|
927
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
928
|
+
plugins: [new AuditPlugin({
|
|
929
|
+
enabled: true,
|
|
930
|
+
trackOperations: ['insert', 'update', 'delete', 'get'],
|
|
931
|
+
includeData: false, // Don't log sensitive data
|
|
932
|
+
retentionDays: 90
|
|
933
|
+
})]
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
// All operations are logged
|
|
937
|
+
await users.insert({ name: "John" });
|
|
938
|
+
await users.update(userId, { age: 31 });
|
|
939
|
+
|
|
940
|
+
// Get audit logs
|
|
941
|
+
const logs = await s3db.plugins.audit.getAuditLogs({
|
|
942
|
+
resourceName: 'users',
|
|
943
|
+
operation: 'insert'
|
|
944
|
+
});
|
|
945
|
+
console.log(logs); // Audit trail
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
### ๐๏ธ Advanced Behaviors
|
|
949
|
+
|
|
950
|
+
Choose the right behavior strategy for your use case:
|
|
951
|
+
|
|
952
|
+
#### Behavior Comparison
|
|
953
|
+
|
|
954
|
+
| Behavior | Enforcement | Data Loss | Event Emission | Use Case |
|
|
955
|
+
|------------------|-------------|-----------|----------------|-------------------------|
|
|
956
|
+
| `user-managed` | None | Possible | Warns | Dev/Test/Advanced users |
|
|
957
|
+
| `enforce-limits` | Strict | No | Throws | Production |
|
|
958
|
+
| `data-truncate` | Truncates | Yes | Warns | Content Mgmt |
|
|
959
|
+
| `body-overflow` | Truncates/Splits | Yes | Warns | Large objects |
|
|
960
|
+
| `body-only` | Unlimited | No | No | Large JSON/Logs |
|
|
961
|
+
|
|
962
|
+
#### User Managed Behavior (Default)
|
|
963
|
+
|
|
964
|
+
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.
|
|
965
|
+
|
|
966
|
+
**Purpose & Use Cases:**
|
|
967
|
+
- For development, testing, or advanced users who want full control over resource metadata and body size.
|
|
968
|
+
- Useful when you want to handle S3 metadata limits yourself, or implement custom logic for warnings.
|
|
969
|
+
- Not recommended for production unless you have custom enforcement or validation in place.
|
|
970
|
+
|
|
971
|
+
**How It Works:**
|
|
972
|
+
- Emits an `exceedsLimit` event (with details) when a resource's metadata size exceeds the S3 2KB limit.
|
|
973
|
+
- Does NOT block, truncate, or modify dataโoperations always proceed.
|
|
974
|
+
- No automatic enforcement of any limits; user is responsible for handling warnings and data integrity.
|
|
975
|
+
|
|
976
|
+
**Event Emission:**
|
|
977
|
+
- Event: `exceedsLimit`
|
|
978
|
+
- Payload:
|
|
979
|
+
- `operation`: 'insert' | 'update' | 'upsert'
|
|
980
|
+
- `id` (for update/upsert): resource id
|
|
981
|
+
- `totalSize`: total metadata size in bytes
|
|
982
|
+
- `limit`: S3 metadata limit (2048 bytes)
|
|
983
|
+
- `excess`: number of bytes over the limit
|
|
984
|
+
- `data`: the offending data object
|
|
985
|
+
|
|
986
|
+
```javascript
|
|
987
|
+
// Flexible behavior - warns but doesn't block
|
|
988
|
+
const users = await s3db.createResource({
|
|
989
|
+
name: "users",
|
|
990
|
+
attributes: { name: "string", bio: "string" },
|
|
991
|
+
behavior: "user-managed" // Default
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
// Listen for limit warnings
|
|
995
|
+
users.on('exceedsLimit', (data) => {
|
|
996
|
+
console.warn(`Data exceeds 2KB limit by ${data.excess} bytes`, data);
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
// Operation continues despite warning
|
|
1000
|
+
await users.insert({
|
|
1001
|
+
name: "John",
|
|
1002
|
+
bio: "A".repeat(3000) // > 2KB
|
|
1003
|
+
});
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
**Best Practices & Warnings:**
|
|
1007
|
+
- Exceeding S3 metadata limits will cause silent data loss or errors at the storage layer.
|
|
1008
|
+
- Use this behavior only if you have custom logic to handle warnings and enforce limits.
|
|
1009
|
+
- For production, prefer `enforce-limits` or `data-truncate` to avoid data loss.
|
|
1010
|
+
|
|
1011
|
+
**Migration Tips:**
|
|
1012
|
+
- To migrate to a stricter behavior, change the resource's behavior to `enforce-limits` or `data-truncate`.
|
|
1013
|
+
- Review emitted warnings to identify resources at risk of exceeding S3 limits.
|
|
1014
|
+
|
|
1015
|
+
#### Enforce Limits Behavior
|
|
1016
|
+
|
|
1017
|
+
```javascript
|
|
1018
|
+
// Strict validation - throws error if limit exceeded
|
|
1019
|
+
const settings = await s3db.createResource({
|
|
1020
|
+
name: "settings",
|
|
1021
|
+
attributes: { key: "string", value: "string" },
|
|
1022
|
+
behavior: "enforce-limits"
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
// Throws error if data > 2KB
|
|
1026
|
+
await settings.insert({
|
|
1027
|
+
key: "large_setting",
|
|
1028
|
+
value: "A".repeat(3000) // Throws: "S3 metadata size exceeds 2KB limit"
|
|
1029
|
+
});
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
#### Data Truncate Behavior
|
|
885
1033
|
|
|
886
1034
|
```javascript
|
|
887
1035
|
// Smart truncation - preserves structure, truncates content
|
|
@@ -934,6 +1082,52 @@ console.log(retrieved.content.length); // 5000 (full content preserved)
|
|
|
934
1082
|
console.log(retrieved._hasContent); // true (indicates body usage)
|
|
935
1083
|
```
|
|
936
1084
|
|
|
1085
|
+
#### Body Only Behavior
|
|
1086
|
+
|
|
1087
|
+
```javascript
|
|
1088
|
+
// Store all data in S3 object body as JSON, keeping only version in metadata
|
|
1089
|
+
const documents = await s3db.createResource({
|
|
1090
|
+
name: "documents",
|
|
1091
|
+
attributes: {
|
|
1092
|
+
title: "string",
|
|
1093
|
+
content: "string", // Can be extremely large
|
|
1094
|
+
metadata: "object"
|
|
1095
|
+
},
|
|
1096
|
+
behavior: "body-only"
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
// Store large documents without any size limits
|
|
1100
|
+
const document = await documents.insert({
|
|
1101
|
+
title: "Large Document",
|
|
1102
|
+
content: "A".repeat(100000), // 100KB content
|
|
1103
|
+
metadata: {
|
|
1104
|
+
author: "John Doe",
|
|
1105
|
+
tags: ["large", "document"],
|
|
1106
|
+
version: "1.0"
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
// All data is stored in the S3 object body
|
|
1111
|
+
const retrieved = await documents.get(document.id);
|
|
1112
|
+
console.log(retrieved.content.length); // 100000 (full content preserved)
|
|
1113
|
+
console.log(retrieved.metadata.author); // "John Doe"
|
|
1114
|
+
console.log(retrieved._hasContent); // true (indicates body usage)
|
|
1115
|
+
|
|
1116
|
+
// Perfect for storing large JSON documents, logs, or any large content
|
|
1117
|
+
const logEntry = await documents.insert({
|
|
1118
|
+
title: "Application Log",
|
|
1119
|
+
content: JSON.stringify({
|
|
1120
|
+
timestamp: new Date().toISOString(),
|
|
1121
|
+
level: "INFO",
|
|
1122
|
+
message: "Application started",
|
|
1123
|
+
details: {
|
|
1124
|
+
// ... large log details
|
|
1125
|
+
}
|
|
1126
|
+
}),
|
|
1127
|
+
metadata: { source: "api-server", environment: "production" }
|
|
1128
|
+
});
|
|
1129
|
+
```
|
|
1130
|
+
|
|
937
1131
|
### ๐ Advanced Streaming API
|
|
938
1132
|
|
|
939
1133
|
Handle large datasets efficiently with advanced streaming capabilities:
|
|
@@ -1409,782 +1603,4 @@ console.log(`Total users: ${allUsers.length}`);
|
|
|
1409
1603
|
|
|
1410
1604
|
| Method | Description | Example |
|
|
1411
1605
|
|--------|-------------|---------|
|
|
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>
|
|
1606
|
+
| `
|