s3db.js 6.2.0 β†’ 7.0.1

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 +372 -469
  3. package/UNLICENSE +24 -0
  4. package/dist/s3db.cjs.js +12105 -19396
  5. package/dist/s3db.cjs.min.js +1 -1
  6. package/dist/s3db.d.ts +373 -72
  7. package/dist/s3db.es.js +12090 -19393
  8. package/dist/s3db.es.min.js +1 -1
  9. package/dist/s3db.iife.js +12103 -19398
  10. package/dist/s3db.iife.min.js +1 -1
  11. package/package.json +44 -38
  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 +159 -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
@@ -108,6 +108,7 @@
108
108
  - [πŸ“ Binary Content Management](#-binary-content-management)
109
109
  - [πŸ—‚οΈ Advanced Partitioning](#️-advanced-partitioning)
110
110
  - [🎣 Advanced Hooks System](#-advanced-hooks-system)
111
+ - [🧩 Resource Middlewares](#-resource-middlewares)
111
112
  - [πŸ“– API Reference](#-api-reference)
112
113
 
113
114
  ---
@@ -199,23 +200,22 @@ yarn add s3db.js
199
200
 
200
201
  Some features require additional dependencies to be installed manually:
201
202
 
202
- #### Replication Dependencies
203
+ #### replicator Dependencies
203
204
 
204
- 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:
205
206
 
206
207
  ```bash
207
- # For SQS replication (AWS SQS queues)
208
+ # For SQS replicator (AWS SQS queues)
208
209
  npm install @aws-sdk/client-sqs
209
210
 
210
- # For BigQuery replication (Google BigQuery)
211
+ # For BigQuery replicator (Google BigQuery)
211
212
  npm install @google-cloud/bigquery
212
213
 
213
- # For PostgreSQL replication (PostgreSQL databases)
214
+ # For PostgreSQL replicator (PostgreSQL databases)
214
215
  npm install pg
215
216
  ```
216
217
 
217
218
  **Why manual installation?** These are marked as `peerDependencies` to keep the main package lightweight. Only install what you need!
218
- ```
219
219
 
220
220
  ### Environment Setup
221
221
 
@@ -303,7 +303,7 @@ const users = await s3db.createResource({
303
303
  password: "secret"
304
304
  },
305
305
  timestamps: true,
306
- behavior: "user-managed",
306
+ behavior: "user-managed",
307
307
  partitions: {
308
308
  byRegion: { fields: { region: "string" } }
309
309
  }
@@ -351,7 +351,7 @@ const products = await s3db.createResource({
351
351
  name: "products",
352
352
  attributes: { name: "string", price: "number" },
353
353
  hooks: {
354
- preInsert: [async (data) => {
354
+ beforeInsert: [async (data) => {
355
355
  data.sku = `${data.category.toUpperCase()}-${Date.now()}`;
356
356
  return data;
357
357
  }],
@@ -377,21 +377,6 @@ readableStream.on("end", () => console.log("βœ… Export completed"));
377
377
  const writableStream = await users.writable();
378
378
  importData.forEach(userData => writableStream.write(userData));
379
379
  writableStream.end();
380
- ```
381
- value: "string"
382
- },
383
- behavior: "enforce-limits" // Ensures data stays within 2KB
384
- });
385
-
386
- // Smart truncation - preserves structure, truncates content
387
- const summaries = await s3db.createResource({
388
- name: "summaries",
389
- attributes: {
390
- title: "string",
391
- description: "string"
392
- },
393
- behavior: "data-truncate" // Truncates to fit within limits
394
- });
395
380
  ```
396
381
 
397
382
  ### πŸ”„ Resource Versioning System
@@ -478,9 +463,25 @@ const timestampUsers = await s3db.createResource({
478
463
  });
479
464
  ```
480
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
+
481
482
  ### πŸ”Œ Plugin System
482
483
 
483
- 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:
484
485
 
485
486
  ```javascript
486
487
  import {
@@ -488,444 +489,47 @@ import {
488
489
  CostsPlugin,
489
490
  FullTextPlugin,
490
491
  MetricsPlugin,
491
- ReplicationPlugin,
492
+ ReplicatorPlugin,
492
493
  AuditPlugin
493
494
  } from 's3db.js';
494
495
 
495
496
  const s3db = new S3db({
496
497
  connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
497
498
  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
- ]
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
+ }]
513
509
  }),
514
- new AuditPlugin({ enabled: true })
510
+ new AuditPlugin() // πŸ“ Audit logging
515
511
  ]
516
512
  });
517
513
 
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
526
- ```
527
-
528
- ### πŸ”„ Replicator System
529
-
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.
531
-
532
- #### Available Replicators
533
-
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
- }
543
- ```
544
-
545
- **SQS Replicator** - Sends data to AWS SQS queues:
546
- ```javascript
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
- }
557
- ```
558
-
559
- **BigQuery Replicator** - Sends data to Google BigQuery:
560
- ```javascript
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
- }
573
- },
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'
601
- },
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' }
611
- }
612
- }
613
- ```
614
-
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'
630
- ```
631
-
632
- **Solution:** Install the missing dependency as shown above.
633
-
634
- #### Example Usage
635
-
636
- See `examples/e34-replicators.js` for a complete example using all four replicator types.
637
-
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
- ```
644
-
645
- #### πŸ”„ Cache Plugin
646
- Intelligent caching to reduce API calls and improve performance:
647
-
648
- ```javascript
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
- })]
657
- });
658
-
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
663
- ```
664
-
665
- #### πŸ’° Costs Plugin
666
- Track and monitor AWS S3 costs in real-time:
667
-
668
- ```javascript
669
- const s3db = new S3db({
670
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
671
- plugins: [CostsPlugin]
672
- });
514
+ await s3db.connect();
673
515
 
674
- // Track costs automatically
516
+ // All plugins work together seamlessly
675
517
  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 } }
518
+ // βœ… Data cached, costs tracked, indexed for search, metrics recorded, replicated, and audited
681
519
  ```
682
520
 
683
- #### πŸ” Full-Text Search Plugin
684
- Powerful text search with automatic indexing:
521
+ #### Available Plugins
685
522
 
686
- ```javascript
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
- });
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
697
530
 
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
- ```
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)**
929
533
 
930
534
  ### πŸŽ›οΈ Resource Behaviors
931
535
 
@@ -937,7 +541,7 @@ Choose the right behavior strategy for your use case:
937
541
  |------------------|-------------|-----------|----------------|-------------------------|
938
542
  | `user-managed` | None | Possible | Warns | Dev/Test/Advanced users |
939
543
  | `enforce-limits` | Strict | No | Throws | Production |
940
- | `data-truncate` | Truncates | Yes | Warns | Content Mgmt |
544
+ | `truncate-data` | Truncates | Yes | Warns | Content Mgmt |
941
545
  | `body-overflow` | Truncates/Splits | Yes | Warns | Large objects |
942
546
  | `body-only` | Unlimited | No | No | Large JSON/Logs |
943
547
 
@@ -988,10 +592,10 @@ await users.insert({
988
592
  **Best Practices & Warnings:**
989
593
  - Exceeding S3 metadata limits will cause silent data loss or errors at the storage layer.
990
594
  - 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.
595
+ - For production, prefer `enforce-limits` or `truncate-data` to avoid data loss.
992
596
 
993
597
  **Migration Tips:**
994
- - 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`.
995
599
  - Review emitted warnings to identify resources at risk of exceeding S3 limits.
996
600
 
997
601
  #### Enforce Limits Behavior
@@ -1022,7 +626,7 @@ const summaries = await s3db.createResource({
1022
626
  description: "string",
1023
627
  content: "string"
1024
628
  },
1025
- behavior: "data-truncate"
629
+ behavior: "truncate-data"
1026
630
  });
1027
631
 
1028
632
  // Automatically truncates to fit within 2KB
@@ -1330,6 +934,7 @@ const usUsers = await users.list({
1330
934
 
1331
935
  // Note: The system automatically manages partition references internally
1332
936
  // Users should use standard list() method with partition parameters
937
+ ```
1333
938
 
1334
939
  #### Automatic Timestamp Partitions
1335
940
 
@@ -1387,14 +992,14 @@ const users = await s3db.createResource({
1387
992
  name: "users",
1388
993
  attributes: { name: "string", email: "string" },
1389
994
  hooks: {
1390
- preInsert: [
995
+ beforeInsert: [
1391
996
  async (data) => {
1392
- console.log('1. Pre-insert hook 1');
997
+ console.log('1. Before-insert hook 1');
1393
998
  data.timestamp = new Date().toISOString();
1394
999
  return data;
1395
1000
  },
1396
1001
  async (data) => {
1397
- console.log('2. Pre-insert hook 2');
1002
+ console.log('2. Before-insert hook 2');
1398
1003
  data.processed = true;
1399
1004
  return data;
1400
1005
  }
@@ -1412,7 +1017,7 @@ const users = await s3db.createResource({
1412
1017
  }
1413
1018
  });
1414
1019
 
1415
- // Execution order: preInsert hooks β†’ insert β†’ afterInsert hooks
1020
+ // Execution order: beforeInsert hooks β†’ insert β†’ afterInsert hooks
1416
1021
  ```
1417
1022
 
1418
1023
  #### Version-Specific Hooks
@@ -1424,7 +1029,7 @@ const users = await s3db.createResource({
1424
1029
  attributes: { name: "string", email: "string" },
1425
1030
  versioningEnabled: true,
1426
1031
  hooks: {
1427
- preInsert: [
1032
+ beforeInsert: [
1428
1033
  async (data) => {
1429
1034
  // Access resource context
1430
1035
  console.log('Current version:', this.version);
@@ -1446,19 +1051,19 @@ users.on('versionUpdated', ({ oldVersion, newVersion }) => {
1446
1051
  const users = await s3db.createResource({
1447
1052
  name: "users",
1448
1053
  attributes: { name: "string", email: "string" },
1449
- hooks: {
1450
- preInsert: [
1451
- async (data) => {
1452
- try {
1453
- // Validate external service
1454
- await validateEmail(data.email);
1455
- return data;
1456
- } catch (error) {
1457
- // Transform error or add context
1458
- 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
+ }
1459
1065
  }
1460
- }
1461
- ],
1066
+ ],
1462
1067
  afterInsert: [
1463
1068
  async (data) => {
1464
1069
  try {
@@ -1480,7 +1085,7 @@ const users = await s3db.createResource({
1480
1085
  name: "users",
1481
1086
  attributes: { name: "string", email: "string" },
1482
1087
  hooks: {
1483
- preInsert: [
1088
+ beforeInsert: [
1484
1089
  async function(data) {
1485
1090
  // 'this' is bound to the resource instance
1486
1091
  console.log('Resource name:', this.name);
@@ -1499,6 +1104,303 @@ const users = await s3db.createResource({
1499
1104
  });
1500
1105
  ```
1501
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
+
1502
1404
  ---
1503
1405
 
1504
1406
  ## πŸ“– API Reference
@@ -1585,4 +1487,5 @@ console.log(`Total users: ${allUsers.length}`);
1585
1487
 
1586
1488
  | Method | Description | Example |
1587
1489
  |--------|-------------|---------|
1588
- | `
1490
+ | `readable(options?)` | Create readable stream | `await users.readable()` |
1491
+ | `writable(options?)` | Create writable stream | `await users.writable()` |