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 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;