s3db.js 6.1.0 β†’ 7.0.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.
Files changed (60) hide show
  1. package/PLUGINS.md +2724 -0
  2. package/README.md +377 -492
  3. package/UNLICENSE +24 -0
  4. package/dist/s3db.cjs.js +30054 -18189
  5. package/dist/s3db.cjs.min.js +1 -1
  6. package/dist/s3db.d.ts +373 -72
  7. package/dist/s3db.es.js +30040 -18186
  8. package/dist/s3db.es.min.js +1 -1
  9. package/dist/s3db.iife.js +29727 -17863
  10. package/dist/s3db.iife.min.js +1 -1
  11. package/package.json +44 -69
  12. package/src/behaviors/body-only.js +110 -0
  13. package/src/behaviors/body-overflow.js +153 -0
  14. package/src/behaviors/enforce-limits.js +195 -0
  15. package/src/behaviors/index.js +39 -0
  16. package/src/behaviors/truncate-data.js +204 -0
  17. package/src/behaviors/user-managed.js +147 -0
  18. package/src/client.class.js +515 -0
  19. package/src/concerns/base62.js +61 -0
  20. package/src/concerns/calculator.js +204 -0
  21. package/src/concerns/crypto.js +142 -0
  22. package/src/concerns/id.js +8 -0
  23. package/src/concerns/index.js +5 -0
  24. package/src/concerns/try-fn.js +151 -0
  25. package/src/connection-string.class.js +75 -0
  26. package/src/database.class.js +599 -0
  27. package/src/errors.js +261 -0
  28. package/src/index.js +17 -0
  29. package/src/plugins/audit.plugin.js +442 -0
  30. package/src/plugins/cache/cache.class.js +53 -0
  31. package/src/plugins/cache/index.js +6 -0
  32. package/src/plugins/cache/memory-cache.class.js +164 -0
  33. package/src/plugins/cache/s3-cache.class.js +189 -0
  34. package/src/plugins/cache.plugin.js +275 -0
  35. package/src/plugins/consumers/index.js +24 -0
  36. package/src/plugins/consumers/rabbitmq-consumer.js +56 -0
  37. package/src/plugins/consumers/sqs-consumer.js +102 -0
  38. package/src/plugins/costs.plugin.js +81 -0
  39. package/src/plugins/fulltext.plugin.js +473 -0
  40. package/src/plugins/index.js +12 -0
  41. package/src/plugins/metrics.plugin.js +603 -0
  42. package/src/plugins/plugin.class.js +210 -0
  43. package/src/plugins/plugin.obj.js +13 -0
  44. package/src/plugins/queue-consumer.plugin.js +134 -0
  45. package/src/plugins/replicator.plugin.js +769 -0
  46. package/src/plugins/replicators/base-replicator.class.js +85 -0
  47. package/src/plugins/replicators/bigquery-replicator.class.js +328 -0
  48. package/src/plugins/replicators/index.js +44 -0
  49. package/src/plugins/replicators/postgres-replicator.class.js +427 -0
  50. package/src/plugins/replicators/s3db-replicator.class.js +352 -0
  51. package/src/plugins/replicators/sqs-replicator.class.js +427 -0
  52. package/src/resource.class.js +2626 -0
  53. package/src/s3db.d.ts +1263 -0
  54. package/src/schema.class.js +706 -0
  55. package/src/stream/index.js +16 -0
  56. package/src/stream/resource-ids-page-reader.class.js +10 -0
  57. package/src/stream/resource-ids-reader.class.js +63 -0
  58. package/src/stream/resource-reader.class.js +81 -0
  59. package/src/stream/resource-writer.class.js +92 -0
  60. package/src/validator.class.js +97 -0
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,14 @@
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)
111
+ - [🧩 Resource Middlewares](#-resource-middlewares)
108
112
  - [πŸ“– 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
113
 
120
114
  ---
121
115
 
@@ -206,23 +200,22 @@ yarn add s3db.js
206
200
 
207
201
  Some features require additional dependencies to be installed manually:
208
202
 
209
- #### Replication Dependencies
203
+ #### replicator Dependencies
210
204
 
211
- If you plan to use the replication system with external services, install the corresponding dependencies:
205
+ If you plan to use the replicator system with external services, install the corresponding dependencies:
212
206
 
213
207
  ```bash
214
- # For SQS replication (AWS SQS queues)
208
+ # For SQS replicator (AWS SQS queues)
215
209
  npm install @aws-sdk/client-sqs
216
210
 
217
- # For BigQuery replication (Google BigQuery)
211
+ # For BigQuery replicator (Google BigQuery)
218
212
  npm install @google-cloud/bigquery
219
213
 
220
- # For PostgreSQL replication (PostgreSQL databases)
214
+ # For PostgreSQL replicator (PostgreSQL databases)
221
215
  npm install pg
222
216
  ```
223
217
 
224
218
  **Why manual installation?** These are marked as `peerDependencies` to keep the main package lightweight. Only install what you need!
225
- ```
226
219
 
227
220
  ### Environment Setup
228
221
 
@@ -310,7 +303,7 @@ const users = await s3db.createResource({
310
303
  password: "secret"
311
304
  },
312
305
  timestamps: true,
313
- behavior: "user-managed",
306
+ behavior: "user-managed",
314
307
  partitions: {
315
308
  byRegion: { fields: { region: "string" } }
316
309
  }
@@ -358,7 +351,7 @@ const products = await s3db.createResource({
358
351
  name: "products",
359
352
  attributes: { name: "string", price: "number" },
360
353
  hooks: {
361
- preInsert: [async (data) => {
354
+ beforeInsert: [async (data) => {
362
355
  data.sku = `${data.category.toUpperCase()}-${Date.now()}`;
363
356
  return data;
364
357
  }],
@@ -384,21 +377,6 @@ readableStream.on("end", () => console.log("βœ… Export completed"));
384
377
  const writableStream = await users.writable();
385
378
  importData.forEach(userData => writableStream.write(userData));
386
379
  writableStream.end();
387
- ```
388
- value: "string"
389
- },
390
- behavior: "enforce-limits" // Ensures data stays within 2KB
391
- });
392
-
393
- // Smart truncation - preserves structure, truncates content
394
- const summaries = await s3db.createResource({
395
- name: "summaries",
396
- attributes: {
397
- title: "string",
398
- description: "string"
399
- },
400
- behavior: "data-truncate" // Truncates to fit within limits
401
- });
402
380
  ```
403
381
 
404
382
  ### πŸ”„ Resource Versioning System
@@ -485,9 +463,25 @@ const timestampUsers = await s3db.createResource({
485
463
  });
486
464
  ```
487
465
 
466
+ #### πŸ“ **Intelligent Data Compression**
467
+
468
+ s3db.js automatically compresses numeric data using **Base62 encoding** to maximize your S3 metadata space (2KB limit):
469
+
470
+ | Data Type | Original | Compressed | Space Saved |
471
+ |-----------|----------|------------|-------------|
472
+ | `10000` | `10000` (5 digits) | `2Bi` (3 digits) | **40%** |
473
+ | `123456789` | `123456789` (9 digits) | `8m0Kx` (5 digits) | **44%** |
474
+ | Large arrays | `[1,2,3,999999]` (13 chars) | `1,2,3,hBxM` (9 chars) | **31%** |
475
+
476
+ **Performance Benefits:**
477
+ - ⚑ **5x faster** encoding for large numbers vs Base36
478
+ - πŸ—œοΈ **41% compression** for typical numeric data
479
+ - πŸš€ **Space efficient** - more data fits in S3 metadata
480
+ - πŸ”„ **Automatic** - no configuration required
481
+
488
482
  ### πŸ”Œ Plugin System
489
483
 
490
- Extend functionality with powerful plugins. s3db.js supports multiple plugins working together seamlessly:
484
+ Extend s3db.js with powerful plugins for caching, monitoring, replication, search, and more:
491
485
 
492
486
  ```javascript
493
487
  import {
@@ -495,457 +489,49 @@ import {
495
489
  CostsPlugin,
496
490
  FullTextPlugin,
497
491
  MetricsPlugin,
498
- ReplicationPlugin,
492
+ ReplicatorPlugin,
499
493
  AuditPlugin
500
494
  } from 's3db.js';
501
495
 
502
496
  const s3db = new S3db({
503
497
  connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
504
498
  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
- ]
499
+ new CachePlugin(), // πŸ’Ύ Intelligent caching
500
+ CostsPlugin, // πŸ’° Cost tracking
501
+ new FullTextPlugin({ fields: ['name'] }), // πŸ” Full-text search
502
+ new MetricsPlugin(), // πŸ“Š Performance monitoring
503
+ new ReplicatorPlugin({ // πŸ”„ Data replication
504
+ replicators: [{
505
+ driver: 's3db',
506
+ resources: ['users'],
507
+ config: { connectionString: "s3://backup-bucket/backup" }
508
+ }]
520
509
  }),
521
- new AuditPlugin({ enabled: true })
510
+ new AuditPlugin() // πŸ“ Audit logging
522
511
  ]
523
512
  });
524
513
 
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
533
- ```
534
-
535
- ### πŸ”„ Replicator System
536
-
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.
538
-
539
- #### Available Replicators
540
-
541
- **S3DB Replicator** - Replicates data to another s3db instance:
542
- ```javascript
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
- }
551
- ```
552
-
553
- **SQS Replicator** - Sends data to AWS SQS queues:
554
- ```javascript
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
- ```
566
-
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
- ```
585
-
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
- }
603
- ```
604
-
605
- #### Replicator Features
606
-
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
613
-
614
- #### Dependencies
615
-
616
- The replicators use optional peer dependencies. Install only what you need:
617
-
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
625
-
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
632
-
633
- # For PostgreSQL replicator
634
- npm install pg
635
- # or
636
- yarn add pg
637
- # or
638
- pnpm add pg
639
- ```
640
-
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.
642
-
643
- **Example error without dependency:**
644
- ```
645
- Error: Cannot find module '@aws-sdk/client-sqs'
646
- ```
647
-
648
- **Solution:** Install the missing dependency as shown above.
649
-
650
- #### Example Usage
651
-
652
- See `examples/e34-replicators.js` for a complete example using all four replicator types.
653
-
654
- **Prerequisites:** Make sure to install the required dependencies before running the example:
655
-
656
- ```bash
657
- # Install all replication dependencies for the full example
658
- npm install @aws-sdk/client-sqs @google-cloud/bigquery pg
659
- ```
660
-
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
- })]
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
681
- ```
682
-
683
- #### πŸ’° Costs Plugin
684
- Track and monitor AWS S3 costs in real-time:
685
-
686
- ```javascript
687
- const s3db = new S3db({
688
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
689
- plugins: [CostsPlugin]
690
- });
514
+ await s3db.connect();
691
515
 
692
- // Track costs automatically
516
+ // All plugins work together seamlessly
693
517
  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 } }
518
+ // βœ… Data cached, costs tracked, indexed for search, metrics recorded, replicated, and audited
699
519
  ```
700
520
 
701
- #### πŸ” Full-Text Search Plugin
702
- Powerful text search with automatic indexing:
521
+ #### Available Plugins
703
522
 
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
- });
523
+ - **πŸ’Ύ Cache Plugin** - Intelligent caching (memory/S3) for performance
524
+ - **πŸ’° Costs Plugin** - Real-time AWS S3 cost tracking
525
+ - **πŸ” FullText Plugin** - Advanced search with automatic indexing
526
+ - **πŸ“Š Metrics Plugin** - Performance monitoring and analytics
527
+ - **πŸ”„ Replicator Plugin** - Multi-target replication (S3DB, SQS, BigQuery, PostgreSQL)
528
+ - **πŸ“ Audit Plugin** - Comprehensive audit logging for compliance
529
+ - **πŸ“¬ Queue Consumer Plugin** - Message consumption from SQS/RabbitMQ
715
530
 
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
- ```
531
+ **πŸ“– For detailed documentation, configuration options, and advanced examples, see:**
532
+ **[πŸ“‹ Complete Plugin Documentation](https://github.com/forattini-dev/s3db.js/blob/main/PLUGINS.md)**
759
533
 
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
534
+ ### πŸŽ›οΈ Resource Behaviors
949
535
 
950
536
  Choose the right behavior strategy for your use case:
951
537
 
@@ -955,7 +541,7 @@ Choose the right behavior strategy for your use case:
955
541
  |------------------|-------------|-----------|----------------|-------------------------|
956
542
  | `user-managed` | None | Possible | Warns | Dev/Test/Advanced users |
957
543
  | `enforce-limits` | Strict | No | Throws | Production |
958
- | `data-truncate` | Truncates | Yes | Warns | Content Mgmt |
544
+ | `truncate-data` | Truncates | Yes | Warns | Content Mgmt |
959
545
  | `body-overflow` | Truncates/Splits | Yes | Warns | Large objects |
960
546
  | `body-only` | Unlimited | No | No | Large JSON/Logs |
961
547
 
@@ -1006,10 +592,10 @@ await users.insert({
1006
592
  **Best Practices & Warnings:**
1007
593
  - Exceeding S3 metadata limits will cause silent data loss or errors at the storage layer.
1008
594
  - 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.
595
+ - For production, prefer `enforce-limits` or `truncate-data` to avoid data loss.
1010
596
 
1011
597
  **Migration Tips:**
1012
- - To migrate to a stricter behavior, change the resource's behavior to `enforce-limits` or `data-truncate`.
598
+ - To migrate to a stricter behavior, change the resource's behavior to `enforce-limits` or `truncate-data`.
1013
599
  - Review emitted warnings to identify resources at risk of exceeding S3 limits.
1014
600
 
1015
601
  #### Enforce Limits Behavior
@@ -1040,7 +626,7 @@ const summaries = await s3db.createResource({
1040
626
  description: "string",
1041
627
  content: "string"
1042
628
  },
1043
- behavior: "data-truncate"
629
+ behavior: "truncate-data"
1044
630
  });
1045
631
 
1046
632
  // Automatically truncates to fit within 2KB
@@ -1348,6 +934,7 @@ const usUsers = await users.list({
1348
934
 
1349
935
  // Note: The system automatically manages partition references internally
1350
936
  // Users should use standard list() method with partition parameters
937
+ ```
1351
938
 
1352
939
  #### Automatic Timestamp Partitions
1353
940
 
@@ -1405,14 +992,14 @@ const users = await s3db.createResource({
1405
992
  name: "users",
1406
993
  attributes: { name: "string", email: "string" },
1407
994
  hooks: {
1408
- preInsert: [
995
+ beforeInsert: [
1409
996
  async (data) => {
1410
- console.log('1. Pre-insert hook 1');
997
+ console.log('1. Before-insert hook 1');
1411
998
  data.timestamp = new Date().toISOString();
1412
999
  return data;
1413
1000
  },
1414
1001
  async (data) => {
1415
- console.log('2. Pre-insert hook 2');
1002
+ console.log('2. Before-insert hook 2');
1416
1003
  data.processed = true;
1417
1004
  return data;
1418
1005
  }
@@ -1430,7 +1017,7 @@ const users = await s3db.createResource({
1430
1017
  }
1431
1018
  });
1432
1019
 
1433
- // Execution order: preInsert hooks β†’ insert β†’ afterInsert hooks
1020
+ // Execution order: beforeInsert hooks β†’ insert β†’ afterInsert hooks
1434
1021
  ```
1435
1022
 
1436
1023
  #### Version-Specific Hooks
@@ -1442,7 +1029,7 @@ const users = await s3db.createResource({
1442
1029
  attributes: { name: "string", email: "string" },
1443
1030
  versioningEnabled: true,
1444
1031
  hooks: {
1445
- preInsert: [
1032
+ beforeInsert: [
1446
1033
  async (data) => {
1447
1034
  // Access resource context
1448
1035
  console.log('Current version:', this.version);
@@ -1464,19 +1051,19 @@ users.on('versionUpdated', ({ oldVersion, newVersion }) => {
1464
1051
  const users = await s3db.createResource({
1465
1052
  name: "users",
1466
1053
  attributes: { name: "string", email: "string" },
1467
- hooks: {
1468
- preInsert: [
1469
- async (data) => {
1470
- try {
1471
- // Validate external service
1472
- await validateEmail(data.email);
1473
- return data;
1474
- } catch (error) {
1475
- // Transform error or add context
1476
- throw new Error(`Email validation failed: ${error.message}`);
1054
+ hooks: {
1055
+ beforeInsert: [
1056
+ async (data) => {
1057
+ try {
1058
+ // Validate external service
1059
+ await validateEmail(data.email);
1060
+ return data;
1061
+ } catch (error) {
1062
+ // Transform error or add context
1063
+ throw new Error(`Email validation failed: ${error.message}`);
1064
+ }
1477
1065
  }
1478
- }
1479
- ],
1066
+ ],
1480
1067
  afterInsert: [
1481
1068
  async (data) => {
1482
1069
  try {
@@ -1498,7 +1085,7 @@ const users = await s3db.createResource({
1498
1085
  name: "users",
1499
1086
  attributes: { name: "string", email: "string" },
1500
1087
  hooks: {
1501
- preInsert: [
1088
+ beforeInsert: [
1502
1089
  async function(data) {
1503
1090
  // 'this' is bound to the resource instance
1504
1091
  console.log('Resource name:', this.name);
@@ -1517,6 +1104,303 @@ const users = await s3db.createResource({
1517
1104
  });
1518
1105
  ```
1519
1106
 
1107
+ ### 🧩 Resource Middlewares
1108
+
1109
+ The Resource class supports a powerful middleware system, similar to Express/Koa, allowing you to intercept, modify, or extend the behavior of core methods like `insert`, `get`, `update`, `delete`, `list`, and more.
1110
+
1111
+ **Supported methods for middleware:**
1112
+ - `get`
1113
+ - `list`
1114
+ - `listIds`
1115
+ - `getAll`
1116
+ - `count`
1117
+ - `page`
1118
+ - `insert`
1119
+ - `update`
1120
+ - `delete`
1121
+ - `deleteMany`
1122
+ - `exists`
1123
+ - `getMany`
1124
+
1125
+ #### Middleware Signature
1126
+ ```js
1127
+ async function middleware(ctx, next) {
1128
+ // ctx.resource: Resource instance
1129
+ // ctx.args: arguments array (for the method)
1130
+ // ctx.method: method name (e.g., 'insert')
1131
+ // next(): calls the next middleware or the original method
1132
+ }
1133
+ ```
1134
+
1135
+ #### Example: Logging Middleware for Insert
1136
+ ```js
1137
+ const users = await s3db.createResource({
1138
+ name: "users",
1139
+ attributes: { name: "string", email: "string" }
1140
+ });
1141
+
1142
+ users.useMiddleware('insert', async (ctx, next) => {
1143
+ console.log('Before insert:', ctx.args[0]);
1144
+ // You can modify ctx.args if needed
1145
+ ctx.args[0].name = ctx.args[0].name.toUpperCase();
1146
+ const result = await next();
1147
+ console.log('After insert:', result);
1148
+ return result;
1149
+ });
1150
+
1151
+ await users.insert({ name: "john", email: "john@example.com" });
1152
+ // Output:
1153
+ // Before insert: { name: 'john', email: 'john@example.com' }
1154
+ // After insert: { id: '...', name: 'JOHN', email: 'john@example.com', ... }
1155
+ ```
1156
+
1157
+ #### Example: Validation or Metrics Middleware
1158
+ ```js
1159
+ users.useMiddleware('update', async (ctx, next) => {
1160
+ if (!ctx.args[1].email) throw new Error('Email is required for update!');
1161
+ const start = Date.now();
1162
+ const result = await next();
1163
+ const duration = Date.now() - start;
1164
+ console.log(`Update took ${duration}ms`);
1165
+ return result;
1166
+ });
1167
+ ```
1168
+
1169
+ #### πŸ”’ Complete Example: Authentication & Audit Middleware
1170
+
1171
+ Here's a practical example showing how to implement authentication and audit logging with middleware:
1172
+
1173
+ ```js
1174
+ import { S3db } from 's3db.js';
1175
+
1176
+ // Create database and resources
1177
+ const database = new S3db({ connectionString: 's3://my-bucket/my-app' });
1178
+ await database.connect();
1179
+
1180
+ const orders = await database.createResource({
1181
+ name: 'orders',
1182
+ attributes: {
1183
+ id: 'string|required',
1184
+ customerId: 'string|required',
1185
+ amount: 'number|required',
1186
+ status: 'string|required'
1187
+ }
1188
+ });
1189
+
1190
+ // Authentication middleware - runs on all operations
1191
+ ['insert', 'update', 'delete', 'get'].forEach(method => {
1192
+ orders.useMiddleware(method, async (ctx, next) => {
1193
+ // Extract user from context (e.g., from JWT token)
1194
+ const user = ctx.user || ctx.args.find(arg => arg?.userId);
1195
+
1196
+ if (!user || !user.userId) {
1197
+ throw new Error(`Authentication required for ${method} operation`);
1198
+ }
1199
+
1200
+ // Add user info to context for other middlewares
1201
+ ctx.authenticatedUser = user;
1202
+
1203
+ return await next();
1204
+ });
1205
+ });
1206
+
1207
+ // Audit logging middleware - tracks all changes
1208
+ ['insert', 'update', 'delete'].forEach(method => {
1209
+ orders.useMiddleware(method, async (ctx, next) => {
1210
+ const startTime = Date.now();
1211
+ const user = ctx.authenticatedUser;
1212
+
1213
+ try {
1214
+ const result = await next();
1215
+
1216
+ // Log successful operation
1217
+ console.log(`[AUDIT] ${method.toUpperCase()}`, {
1218
+ resource: 'orders',
1219
+ userId: user.userId,
1220
+ method,
1221
+ args: ctx.args,
1222
+ duration: Date.now() - startTime,
1223
+ timestamp: new Date().toISOString(),
1224
+ success: true
1225
+ });
1226
+
1227
+ return result;
1228
+ } catch (error) {
1229
+ // Log failed operation
1230
+ console.log(`[AUDIT] ${method.toUpperCase()} FAILED`, {
1231
+ resource: 'orders',
1232
+ userId: user.userId,
1233
+ method,
1234
+ error: error.message,
1235
+ duration: Date.now() - startTime,
1236
+ timestamp: new Date().toISOString(),
1237
+ success: false
1238
+ });
1239
+
1240
+ throw error;
1241
+ }
1242
+ });
1243
+ });
1244
+
1245
+ // Permission middleware for sensitive operations
1246
+ orders.useMiddleware('delete', async (ctx, next) => {
1247
+ const user = ctx.authenticatedUser;
1248
+
1249
+ if (user.role !== 'admin') {
1250
+ throw new Error('Only admins can delete orders');
1251
+ }
1252
+
1253
+ return await next();
1254
+ });
1255
+
1256
+ // Usage examples
1257
+ try {
1258
+ // This will require authentication and log the operation
1259
+ const order = await orders.insert(
1260
+ {
1261
+ id: 'order-123',
1262
+ customerId: 'cust-456',
1263
+ amount: 99.99,
1264
+ status: 'pending'
1265
+ },
1266
+ { user: { userId: 'user-789', role: 'customer' } }
1267
+ );
1268
+
1269
+ // This will fail - only admins can delete
1270
+ await orders.delete('order-123', {
1271
+ user: { userId: 'user-789', role: 'customer' }
1272
+ });
1273
+
1274
+ } catch (error) {
1275
+ console.error('Operation failed:', error.message);
1276
+ }
1277
+
1278
+ /*
1279
+ Expected output:
1280
+ [AUDIT] INSERT {
1281
+ resource: 'orders',
1282
+ userId: 'user-789',
1283
+ method: 'insert',
1284
+ args: [{ id: 'order-123', customerId: 'cust-456', amount: 99.99, status: 'pending' }],
1285
+ duration: 245,
1286
+ timestamp: '2024-01-15T10:30:45.123Z',
1287
+ success: true
1288
+ }
1289
+
1290
+ Operation failed: Only admins can delete orders
1291
+ [AUDIT] DELETE FAILED {
1292
+ resource: 'orders',
1293
+ userId: 'user-789',
1294
+ method: 'delete',
1295
+ error: 'Only admins can delete orders',
1296
+ duration: 12,
1297
+ timestamp: '2024-01-15T10:30:45.456Z',
1298
+ success: false
1299
+ }
1300
+ */
1301
+ ```
1302
+
1303
+ **Key Benefits of This Approach:**
1304
+ - πŸ” **Centralized Authentication**: One middleware handles auth for all operations
1305
+ - πŸ“Š **Comprehensive Auditing**: All operations are logged with timing and user info
1306
+ - πŸ›‘οΈ **Granular Permissions**: Different rules for different operations
1307
+ - ⚑ **Performance Tracking**: Built-in timing for operation monitoring
1308
+ - πŸ”§ **Easy to Maintain**: Add/remove middlewares without changing business logic
1309
+
1310
+ - **Chaining:** You can add multiple middlewares for the same method; they run in registration order.
1311
+ - **Control:** You can short-circuit the chain by not calling `next()`, or modify arguments/results as needed.
1312
+
1313
+ This system is ideal for cross-cutting concerns like logging, access control, custom validation, metrics, or request shaping.
1314
+
1315
+ ---
1316
+
1317
+ ### 🧩 Hooks vs Middlewares: Differences, Usage, and Coexistence
1318
+
1319
+ s3db.js supports **both hooks and middlewares** for resources. They are complementary tools for customizing and extending resource behavior.
1320
+
1321
+ #### **What are Hooks?**
1322
+ - Hooks are functions that run **before or after** specific operations (e.g., `beforeInsert`, `afterUpdate`).
1323
+ - They are ideal for **side effects**: logging, notifications, analytics, validation, etc.
1324
+ - Hooks **cannot block or replace** the original operationβ€”they can only observe or modify the data passed to them.
1325
+ - Hooks are registered with `addHook(hookName, fn)` or via the `hooks` config.
1326
+
1327
+ > **πŸ“ Note:** Don't confuse hooks with **events**. Hooks are lifecycle functions (`beforeInsert`, `afterUpdate`, etc.) while events are actual EventEmitter events (`exceedsLimit`, `truncate`, `overflow`) that you listen to with `.on(eventName, handler)`.
1328
+
1329
+ **Example:**
1330
+ ```js
1331
+ users.addHook('afterInsert', async (data) => {
1332
+ await sendWelcomeEmail(data.email);
1333
+ return data;
1334
+ });
1335
+ ```
1336
+
1337
+ #### **What are Middlewares?**
1338
+ - Middlewares are functions that **wrap** the entire method call (like Express/Koa middlewares).
1339
+ - They can **intercept, modify, block, or replace** the operation.
1340
+ - Middlewares can transform arguments, short-circuit the call, or modify the result.
1341
+ - Middlewares are registered with `useMiddleware(method, fn)`.
1342
+
1343
+ **Example:**
1344
+ ```js
1345
+ users.useMiddleware('insert', async (ctx, next) => {
1346
+ if (!ctx.args[0].email) throw new Error('Email required');
1347
+ ctx.args[0].name = ctx.args[0].name.toUpperCase();
1348
+ const result = await next();
1349
+ return result;
1350
+ });
1351
+ ```
1352
+
1353
+ #### **Key Differences**
1354
+ | Feature | Hooks | Middlewares |
1355
+ |----------------|------------------------------|------------------------------|
1356
+ | Placement | Before/after operation | Wraps the entire method |
1357
+ | Control | Cannot block/replace op | Can block/replace op |
1358
+ | Use case | Side effects, logging, etc. | Access control, transform |
1359
+ | Registration | `addHook(hookName, fn)` | `useMiddleware(method, fn)` |
1360
+ | Data access | Receives data only | Full context (args, method) |
1361
+ | Chaining | Runs in order, always passes | Runs in order, can short-circuit |
1362
+
1363
+ #### **How They Work Together**
1364
+ Hooks and middlewares can be used **together** on the same resource and method. The order of execution is:
1365
+
1366
+ 1. **Middlewares** (before the operation)
1367
+ 2. **Hooks** (`beforeX`)
1368
+ 3. **Original operation**
1369
+ 4. **Hooks** (`afterX`)
1370
+ 5. **Middlewares** (after the operation, as the call stack unwinds)
1371
+
1372
+ **Example: Using Both**
1373
+ ```js
1374
+ // Middleware: transforms input and checks permissions
1375
+ users.useMiddleware('insert', async (ctx, next) => {
1376
+ if (!userHasPermission(ctx.args[0])) throw new Error('Unauthorized');
1377
+ ctx.args[0].name = ctx.args[0].name.toUpperCase();
1378
+ const result = await next();
1379
+ return result;
1380
+ });
1381
+
1382
+ // Hook: sends notification after insert
1383
+ users.addHook('afterInsert', async (data) => {
1384
+ await sendWelcomeEmail(data.email);
1385
+ return data;
1386
+ });
1387
+
1388
+ await users.insert({ name: 'john', email: 'john@example.com' });
1389
+ // Output:
1390
+ // Middleware runs (transforms/checks)
1391
+ // Hook runs (sends email)
1392
+ ```
1393
+
1394
+ #### **When to Use Each**
1395
+ - Use **hooks** for: logging, analytics, notifications, validation, side effects.
1396
+ - Use **middlewares** for: access control, input/output transformation, caching, rate limiting, blocking or replacing operations.
1397
+ - Use **both** for advanced scenarios: e.g., middleware for access control + hook for analytics.
1398
+
1399
+ #### **Best Practices**
1400
+ - Hooks are lightweight and ideal for observing or reacting to events.
1401
+ - Middlewares are powerful and ideal for controlling or transforming operations.
1402
+ - You can safely combine both for maximum flexibility.
1403
+
1520
1404
  ---
1521
1405
 
1522
1406
  ## πŸ“– API Reference
@@ -1603,4 +1487,5 @@ console.log(`Total users: ${allUsers.length}`);
1603
1487
 
1604
1488
  | Method | Description | Example |
1605
1489
  |--------|-------------|---------|
1606
- | `
1490
+ | `readable(options?)` | Create readable stream | `await users.readable()` |
1491
+ | `writable(options?)` | Create writable stream | `await users.writable()` |