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.
- package/PLUGINS.md +2724 -0
- package/README.md +377 -492
- package/UNLICENSE +24 -0
- package/dist/s3db.cjs.js +30054 -18189
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.d.ts +373 -72
- package/dist/s3db.es.js +30040 -18186
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +29727 -17863
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +44 -69
- package/src/behaviors/body-only.js +110 -0
- package/src/behaviors/body-overflow.js +153 -0
- package/src/behaviors/enforce-limits.js +195 -0
- package/src/behaviors/index.js +39 -0
- package/src/behaviors/truncate-data.js +204 -0
- package/src/behaviors/user-managed.js +147 -0
- package/src/client.class.js +515 -0
- package/src/concerns/base62.js +61 -0
- package/src/concerns/calculator.js +204 -0
- package/src/concerns/crypto.js +142 -0
- package/src/concerns/id.js +8 -0
- package/src/concerns/index.js +5 -0
- package/src/concerns/try-fn.js +151 -0
- package/src/connection-string.class.js +75 -0
- package/src/database.class.js +599 -0
- package/src/errors.js +261 -0
- package/src/index.js +17 -0
- package/src/plugins/audit.plugin.js +442 -0
- package/src/plugins/cache/cache.class.js +53 -0
- package/src/plugins/cache/index.js +6 -0
- package/src/plugins/cache/memory-cache.class.js +164 -0
- package/src/plugins/cache/s3-cache.class.js +189 -0
- package/src/plugins/cache.plugin.js +275 -0
- package/src/plugins/consumers/index.js +24 -0
- package/src/plugins/consumers/rabbitmq-consumer.js +56 -0
- package/src/plugins/consumers/sqs-consumer.js +102 -0
- package/src/plugins/costs.plugin.js +81 -0
- package/src/plugins/fulltext.plugin.js +473 -0
- package/src/plugins/index.js +12 -0
- package/src/plugins/metrics.plugin.js +603 -0
- package/src/plugins/plugin.class.js +210 -0
- package/src/plugins/plugin.obj.js +13 -0
- package/src/plugins/queue-consumer.plugin.js +134 -0
- package/src/plugins/replicator.plugin.js +769 -0
- package/src/plugins/replicators/base-replicator.class.js +85 -0
- package/src/plugins/replicators/bigquery-replicator.class.js +328 -0
- package/src/plugins/replicators/index.js +44 -0
- package/src/plugins/replicators/postgres-replicator.class.js +427 -0
- package/src/plugins/replicators/s3db-replicator.class.js +352 -0
- package/src/plugins/replicators/sqs-replicator.class.js +427 -0
- package/src/resource.class.js +2626 -0
- package/src/s3db.d.ts +1263 -0
- package/src/schema.class.js +706 -0
- package/src/stream/index.js +16 -0
- package/src/stream/resource-ids-page-reader.class.js +10 -0
- package/src/stream/resource-ids-reader.class.js +63 -0
- package/src/stream/resource-reader.class.js +81 -0
- package/src/stream/resource-writer.class.js +92 -0
- 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
|
-
- [
|
|
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
|
-
####
|
|
203
|
+
#### replicator Dependencies
|
|
210
204
|
|
|
211
|
-
If you plan to use the
|
|
205
|
+
If you plan to use the replicator system with external services, install the corresponding dependencies:
|
|
212
206
|
|
|
213
207
|
```bash
|
|
214
|
-
# For SQS
|
|
208
|
+
# For SQS replicator (AWS SQS queues)
|
|
215
209
|
npm install @aws-sdk/client-sqs
|
|
216
210
|
|
|
217
|
-
# For BigQuery
|
|
211
|
+
# For BigQuery replicator (Google BigQuery)
|
|
218
212
|
npm install @google-cloud/bigquery
|
|
219
213
|
|
|
220
|
-
# For PostgreSQL
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
506
|
-
CostsPlugin,
|
|
507
|
-
new FullTextPlugin({ fields: ['name'
|
|
508
|
-
new MetricsPlugin(
|
|
509
|
-
new
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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(
|
|
510
|
+
new AuditPlugin() // π Audit logging
|
|
522
511
|
]
|
|
523
512
|
});
|
|
524
513
|
|
|
525
|
-
|
|
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
|
-
//
|
|
516
|
+
// All plugins work together seamlessly
|
|
693
517
|
await users.insert({ name: "John", email: "john@example.com" });
|
|
694
|
-
|
|
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
|
-
####
|
|
702
|
-
Powerful text search with automatic indexing:
|
|
521
|
+
#### Available Plugins
|
|
703
522
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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
|
-
|
|
717
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
995
|
+
beforeInsert: [
|
|
1409
996
|
async (data) => {
|
|
1410
|
-
console.log('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.
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
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
|
-
|
|
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()` |
|