s3db.js 7.3.10 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -0
- package/dist/s3db.cjs.js +44 -2
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.d.ts +6 -0
- package/dist/s3db.es.js +44 -2
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +44 -2
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +1 -1
- package/src/database.class.js +2 -1
- package/src/resource.class.js +49 -1
- package/src/s3db.d.ts +6 -0
package/README.md
CHANGED
|
@@ -109,6 +109,7 @@
|
|
|
109
109
|
- [🗂️ Advanced Partitioning](#️-advanced-partitioning)
|
|
110
110
|
- [🎣 Advanced Hooks System](#-advanced-hooks-system)
|
|
111
111
|
- [🧩 Resource Middlewares](#-resource-middlewares)
|
|
112
|
+
- [🎧 Event Listeners Configuration](#-event-listeners-configuration)
|
|
112
113
|
- [📖 API Reference](#-api-reference)
|
|
113
114
|
|
|
114
115
|
---
|
|
@@ -1398,6 +1399,169 @@ await users.insert({ name: 'john', email: 'john@example.com' });
|
|
|
1398
1399
|
|
|
1399
1400
|
#### **Best Practices**
|
|
1400
1401
|
- Hooks are lightweight and ideal for observing or reacting to events.
|
|
1402
|
+
|
|
1403
|
+
---
|
|
1404
|
+
|
|
1405
|
+
### 🎧 Event Listeners Configuration
|
|
1406
|
+
|
|
1407
|
+
s3db.js resources extend Node.js EventEmitter, providing a powerful event system for real-time monitoring and notifications. You can configure event listeners in **two ways**: programmatically using `.on()` or declaratively in the resource configuration.
|
|
1408
|
+
|
|
1409
|
+
#### **Programmatic Event Listeners**
|
|
1410
|
+
Traditional EventEmitter pattern using `.on()`, `.once()`, or `.off()`:
|
|
1411
|
+
|
|
1412
|
+
```javascript
|
|
1413
|
+
const users = await s3db.createResource({
|
|
1414
|
+
name: "users",
|
|
1415
|
+
attributes: {
|
|
1416
|
+
name: "string|required",
|
|
1417
|
+
email: "string|required"
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
|
|
1421
|
+
// Single event listener
|
|
1422
|
+
users.on('insert', (event) => {
|
|
1423
|
+
console.log('User created:', event.name);
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
// Multiple listeners for the same event
|
|
1427
|
+
users.on('update', (event) => {
|
|
1428
|
+
console.log('Update detected:', event.id);
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
users.on('update', (event) => {
|
|
1432
|
+
if (event.$before.email !== event.$after.email) {
|
|
1433
|
+
console.log('Email changed!');
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
```
|
|
1437
|
+
|
|
1438
|
+
#### **Declarative Event Listeners**
|
|
1439
|
+
Configure event listeners directly in the resource configuration for cleaner, more maintainable code:
|
|
1440
|
+
|
|
1441
|
+
```javascript
|
|
1442
|
+
const users = await s3db.createResource({
|
|
1443
|
+
name: "users",
|
|
1444
|
+
attributes: {
|
|
1445
|
+
name: "string|required",
|
|
1446
|
+
email: "string|required"
|
|
1447
|
+
},
|
|
1448
|
+
events: {
|
|
1449
|
+
// Single event listener
|
|
1450
|
+
insert: (event) => {
|
|
1451
|
+
console.log('📝 User created:', {
|
|
1452
|
+
id: event.id,
|
|
1453
|
+
name: event.name,
|
|
1454
|
+
timestamp: new Date().toISOString()
|
|
1455
|
+
});
|
|
1456
|
+
},
|
|
1457
|
+
|
|
1458
|
+
// Multiple event listeners (array)
|
|
1459
|
+
update: [
|
|
1460
|
+
(event) => {
|
|
1461
|
+
console.log('⚠️ Update detected for user:', event.id);
|
|
1462
|
+
},
|
|
1463
|
+
(event) => {
|
|
1464
|
+
const changes = [];
|
|
1465
|
+
if (event.$before.name !== event.$after.name) {
|
|
1466
|
+
changes.push(`name: ${event.$before.name} → ${event.$after.name}`);
|
|
1467
|
+
}
|
|
1468
|
+
if (event.$before.email !== event.$after.email) {
|
|
1469
|
+
changes.push(`email: ${event.$before.email} → ${event.$after.email}`);
|
|
1470
|
+
}
|
|
1471
|
+
if (changes.length > 0) {
|
|
1472
|
+
console.log('📝 Changes:', changes.join(', '));
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
],
|
|
1476
|
+
|
|
1477
|
+
// Bulk operation listeners
|
|
1478
|
+
deleteMany: (count) => {
|
|
1479
|
+
console.log(`🗑️ Bulk delete: ${count} users deleted`);
|
|
1480
|
+
},
|
|
1481
|
+
|
|
1482
|
+
// Performance and monitoring
|
|
1483
|
+
list: (result) => {
|
|
1484
|
+
console.log(`📋 Listed ${result.count} users, ${result.errors} errors`);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
```
|
|
1489
|
+
|
|
1490
|
+
#### **Available Events**
|
|
1491
|
+
|
|
1492
|
+
| Event | Description | Data Passed |
|
|
1493
|
+
|-------|-------------|-------------|
|
|
1494
|
+
| `insert` | Single record inserted | Complete object with all fields |
|
|
1495
|
+
| `update` | Single record updated | Object with `$before` and `$after` states |
|
|
1496
|
+
| `delete` | Single record deleted | Object data before deletion |
|
|
1497
|
+
| `insertMany` | Bulk insert completed | Number of records inserted |
|
|
1498
|
+
| `deleteMany` | Bulk delete completed | Number of records deleted |
|
|
1499
|
+
| `list` | List operation completed | Result object with count and errors |
|
|
1500
|
+
| `count` | Count operation completed | Total count number |
|
|
1501
|
+
| `get` | Single record retrieved | Complete object data |
|
|
1502
|
+
| `getMany` | Multiple records retrieved | Count of records |
|
|
1503
|
+
|
|
1504
|
+
#### **Event Data Structure**
|
|
1505
|
+
|
|
1506
|
+
**Insert/Get Events:**
|
|
1507
|
+
```javascript
|
|
1508
|
+
{
|
|
1509
|
+
id: 'user-123',
|
|
1510
|
+
name: 'John Doe',
|
|
1511
|
+
email: 'john@example.com',
|
|
1512
|
+
createdAt: '2023-12-01T10:00:00.000Z',
|
|
1513
|
+
// ... all other fields
|
|
1514
|
+
}
|
|
1515
|
+
```
|
|
1516
|
+
|
|
1517
|
+
**Update Events:**
|
|
1518
|
+
```javascript
|
|
1519
|
+
{
|
|
1520
|
+
id: 'user-123',
|
|
1521
|
+
name: 'John Updated',
|
|
1522
|
+
email: 'john.new@example.com',
|
|
1523
|
+
$before: {
|
|
1524
|
+
name: 'John Doe',
|
|
1525
|
+
email: 'john@example.com',
|
|
1526
|
+
// ... previous state
|
|
1527
|
+
},
|
|
1528
|
+
$after: {
|
|
1529
|
+
name: 'John Updated',
|
|
1530
|
+
email: 'john.new@example.com',
|
|
1531
|
+
// ... current state
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
```
|
|
1535
|
+
|
|
1536
|
+
#### **Combining Both Approaches**
|
|
1537
|
+
You can use both declarative and programmatic event listeners together:
|
|
1538
|
+
|
|
1539
|
+
```javascript
|
|
1540
|
+
const users = await s3db.createResource({
|
|
1541
|
+
name: "users",
|
|
1542
|
+
attributes: { name: "string|required" },
|
|
1543
|
+
events: {
|
|
1544
|
+
insert: (event) => console.log('Config listener:', event.name)
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
// Add additional programmatic listeners
|
|
1549
|
+
users.on('insert', (event) => {
|
|
1550
|
+
console.log('Programmatic listener:', event.name);
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
await users.insert({ name: 'John' });
|
|
1554
|
+
// Output:
|
|
1555
|
+
// Config listener: John
|
|
1556
|
+
// Programmatic listener: John
|
|
1557
|
+
```
|
|
1558
|
+
|
|
1559
|
+
#### **Best Practices for Event Listeners**
|
|
1560
|
+
- **Declarative for core functionality**: Use the `events` config for essential listeners
|
|
1561
|
+
- **Programmatic for conditional/dynamic**: Use `.on()` for listeners that might change at runtime
|
|
1562
|
+
- **Error handling**: Listeners should handle their own errors to avoid breaking operations
|
|
1563
|
+
- **Performance**: Keep listeners lightweight; use async operations sparingly
|
|
1564
|
+
- **Debugging**: Event listeners are excellent for debugging and monitoring
|
|
1401
1565
|
- Middlewares are powerful and ideal for controlling or transforming operations.
|
|
1402
1566
|
- You can safely combine both for maximum flexibility.
|
|
1403
1567
|
|
package/dist/s3db.cjs.js
CHANGED
|
@@ -11074,6 +11074,7 @@ class Resource extends EventEmitter {
|
|
|
11074
11074
|
* @param {Function} [config.idGenerator] - Custom ID generator function
|
|
11075
11075
|
* @param {number} [config.idSize=22] - Size for auto-generated IDs
|
|
11076
11076
|
* @param {boolean} [config.versioningEnabled=false] - Enable versioning for this resource
|
|
11077
|
+
* @param {Object} [config.events={}] - Event listeners to automatically add
|
|
11077
11078
|
* @example
|
|
11078
11079
|
* const users = new Resource({
|
|
11079
11080
|
* name: 'users',
|
|
@@ -11095,6 +11096,14 @@ class Resource extends EventEmitter {
|
|
|
11095
11096
|
* beforeInsert: [async (data) => {
|
|
11096
11097
|
* return data;
|
|
11097
11098
|
* }]
|
|
11099
|
+
* },
|
|
11100
|
+
* events: {
|
|
11101
|
+
* insert: (ev) => console.log('Inserted:', ev.id),
|
|
11102
|
+
* update: [
|
|
11103
|
+
* (ev) => console.warn('Update detected'),
|
|
11104
|
+
* (ev) => console.log('Updated:', ev.id)
|
|
11105
|
+
* ],
|
|
11106
|
+
* delete: (ev) => console.log('Deleted:', ev.id)
|
|
11098
11107
|
* }
|
|
11099
11108
|
* });
|
|
11100
11109
|
*
|
|
@@ -11147,7 +11156,8 @@ class Resource extends EventEmitter {
|
|
|
11147
11156
|
hooks = {},
|
|
11148
11157
|
idGenerator: customIdGenerator,
|
|
11149
11158
|
idSize = 22,
|
|
11150
|
-
versioningEnabled = false
|
|
11159
|
+
versioningEnabled = false,
|
|
11160
|
+
events = {}
|
|
11151
11161
|
} = config;
|
|
11152
11162
|
this.name = name;
|
|
11153
11163
|
this.client = client;
|
|
@@ -11189,6 +11199,19 @@ class Resource extends EventEmitter {
|
|
|
11189
11199
|
}
|
|
11190
11200
|
}
|
|
11191
11201
|
}
|
|
11202
|
+
if (events && Object.keys(events).length > 0) {
|
|
11203
|
+
for (const [eventName, listeners] of Object.entries(events)) {
|
|
11204
|
+
if (Array.isArray(listeners)) {
|
|
11205
|
+
for (const listener of listeners) {
|
|
11206
|
+
if (typeof listener === "function") {
|
|
11207
|
+
this.on(eventName, listener);
|
|
11208
|
+
}
|
|
11209
|
+
}
|
|
11210
|
+
} else if (typeof listeners === "function") {
|
|
11211
|
+
this.on(eventName, listeners);
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
}
|
|
11192
11215
|
this._initMiddleware();
|
|
11193
11216
|
}
|
|
11194
11217
|
/**
|
|
@@ -13228,6 +13251,24 @@ function validateResourceConfig(config) {
|
|
|
13228
13251
|
}
|
|
13229
13252
|
}
|
|
13230
13253
|
}
|
|
13254
|
+
if (config.events !== void 0) {
|
|
13255
|
+
if (typeof config.events !== "object" || Array.isArray(config.events)) {
|
|
13256
|
+
errors.push("Resource 'events' must be an object");
|
|
13257
|
+
} else {
|
|
13258
|
+
for (const [eventName, listeners] of Object.entries(config.events)) {
|
|
13259
|
+
if (Array.isArray(listeners)) {
|
|
13260
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
13261
|
+
const listener = listeners[i];
|
|
13262
|
+
if (typeof listener !== "function") {
|
|
13263
|
+
errors.push(`Resource 'events.${eventName}[${i}]' must be a function`);
|
|
13264
|
+
}
|
|
13265
|
+
}
|
|
13266
|
+
} else if (typeof listeners !== "function") {
|
|
13267
|
+
errors.push(`Resource 'events.${eventName}' must be a function or array of functions`);
|
|
13268
|
+
}
|
|
13269
|
+
}
|
|
13270
|
+
}
|
|
13271
|
+
}
|
|
13231
13272
|
return {
|
|
13232
13273
|
isValid: errors.length === 0,
|
|
13233
13274
|
errors
|
|
@@ -13611,7 +13652,8 @@ class Database extends EventEmitter {
|
|
|
13611
13652
|
versioningEnabled: this.versioningEnabled,
|
|
13612
13653
|
map: config.map,
|
|
13613
13654
|
idGenerator: config.idGenerator,
|
|
13614
|
-
idSize: config.idSize
|
|
13655
|
+
idSize: config.idSize,
|
|
13656
|
+
events: config.events || {}
|
|
13615
13657
|
});
|
|
13616
13658
|
resource.database = this;
|
|
13617
13659
|
this.resources[name] = resource;
|