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 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
- - [๐ŸŽ›๏ธ Advanced Behaviors](#๏ธ-advanced-behaviors)
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
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
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
- paranoid: true,
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
- uri: `s3://${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY}@${process.env.AWS_BUCKET}/databases/${process.env.DATABASE_NAME}`
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
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
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
- uri: "s3://BUCKET_NAME/databases/myapp"
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
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
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
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
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
- // All options are now at the root level
318
- timestamps: true, // Automatic createdAt/updatedAt
319
- behavior: "user-management", // How to handle large documents
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) for resource creation and partition validation. This powerful validation engine provides comprehensive rule support, excellent performance, and detailed error reporting for all your data validation needs.
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
- byUtmSource: { fields: { "utm.source": "string" } },
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 googleEvents = await analytics.list({
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
- async (data) => {
421
- // Auto-generate SKU
422
- data.sku = `${data.category.toUpperCase()}-${Date.now()}`;
423
- return data;
424
- }
425
- ],
426
- afterInsert: [
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 with streams:
367
+ Handle large datasets efficiently:
460
368
 
461
369
  ```javascript
462
- // Export all users to CSV
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
- records.push(user);
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 from stream
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
- s3db.js includes a powerful versioning system that automatically manages schema evolution and data migration:
531
-
532
- #### Enable Versioning
399
+ Automatically manages schema evolution and data migration:
533
400
 
534
401
  ```javascript
535
- // Enable versioning at database level
402
+ // Enable versioning
536
403
  const s3db = new S3db({
537
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
538
- versioningEnabled: true // Enable versioning for all resources
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 users in v0
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 - automatically creates v1
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", // New field
579
- profile: "object|optional" // New nested object
430
+ age: "number|optional"
580
431
  },
581
432
  versioningEnabled: true
582
433
  });
583
434
 
584
- // User1 now has _v: "v0" metadata
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); // "v1" - automatically migrated
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 v0Users = await users.list({
440
+ const version0Users = await users.list({
650
441
  partition: "byVersion",
651
- partitionValues: { _v: "v0" }
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
- s3db.js supports flexible ID generation strategies:
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
- const longUsers = await s3db.createResource({
682
- name: "long-users",
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 // Pass UUID function directly
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
- #### Custom ID Functions
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()}_${Math.random().toString(36).substr(2, 5)}`
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 functionality with plugins:
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 { CachePlugin, CostsPlugin } from 's3db.js';
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
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
758
- plugins: [CachePlugin, CostsPlugin]
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
- #### Cache Plugin
528
+ ### ๐Ÿ”„ Replicator System
763
529
 
764
- ```javascript
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
- // These operations are automatically cached:
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
- // Write operations automatically clear cache:
778
- await users.insert({...}); // Clears cache
779
- await users.update(id, {...}); // Clears cache
780
- await users.delete(id); // Clears cache
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
- #### Costs Plugin
784
-
545
+ **SQS Replicator** - Sends data to AWS SQS queues:
785
546
  ```javascript
786
- // Track AWS S3 costs in real-time
787
- const s3db = new S3db({
788
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
789
- plugins: [CostsPlugin]
790
- });
791
-
792
- // Monitor costs during operations
793
- await users.insert({ name: "John" });
794
- await users.get("user-123");
795
- await users.list();
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
- #### Custom Plugins
807
-
559
+ **BigQuery Replicator** - Sends data to Google BigQuery:
808
560
  ```javascript
809
- // Create custom plugin
810
- const MyCustomPlugin = {
811
- async setup(database) {
812
- this.database = database;
813
- console.log('Custom plugin setup');
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
- async start() {
817
- console.log('Custom plugin started');
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
- async stop() {
821
- console.log('Custom plugin stopped');
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
- // Use custom plugin
826
- const s3db = new S3db({
827
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
828
- plugins: [MyCustomPlugin, CachePlugin]
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
- ### ๐ŸŽ›๏ธ Advanced Behaviors
632
+ **Solution:** Install the missing dependency as shown above.
833
633
 
834
- Choose the right behavior strategy for your use case:
634
+ #### Example Usage
835
635
 
836
- #### Behavior Comparison
636
+ See `examples/e34-replicators.js` for a complete example using all four replicator types.
837
637
 
838
- | Behavior | Use Case | 2KB Limit | Data Loss | Performance |
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 |
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
- #### User Management Behavior (Default)
645
+ #### ๐Ÿ”„ Cache Plugin
646
+ Intelligent caching to reduce API calls and improve performance:
846
647
 
847
648
  ```javascript
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
- });
854
-
855
- // Listen for limit warnings
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
- // Operation continues despite warning
861
- await users.insert({
862
- name: "John",
863
- bio: "A".repeat(3000) // > 2KB
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
- #### Enforce Limits Behavior
665
+ #### ๐Ÿ’ฐ Costs Plugin
666
+ Track and monitor AWS S3 costs in real-time:
868
667
 
869
668
  ```javascript
870
- // Strict validation - throws error if limit exceeded
871
- const settings = await s3db.createResource({
872
- name: "settings",
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
- // Throws error if data > 2KB
878
- await settings.insert({
879
- key: "large_setting",
880
- value: "A".repeat(3000) // Throws: "S3 metadata size exceeds 2KB limit"
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
- #### Data Truncate Behavior
683
+ #### ๐Ÿ” Full-Text Search Plugin
684
+ Powerful text search with automatic indexing:
885
685
 
886
686
  ```javascript
887
- // Smart truncation - preserves structure, truncates content
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
- | `readable(options?)` | Create readable stream | `await users.readable({batchSize: 50})` |
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
+ | `