s3db.js 13.1.0 → 13.2.2
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 +9 -9
- package/dist/s3db.cjs.js +1249 -271
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +1249 -271
- package/dist/s3db.es.js.map +1 -1
- package/package.json +2 -1
- package/src/clients/memory-client.class.js +16 -16
- package/src/clients/s3-client.class.js +17 -17
- package/src/concerns/error-classifier.js +204 -0
- package/src/database.class.js +9 -9
- package/src/plugins/backup.plugin.js +8 -8
- package/src/plugins/cache.plugin.js +3 -3
- package/src/plugins/concerns/plugin-dependencies.js +12 -0
- package/src/plugins/geo.plugin.js +2 -2
- package/src/plugins/ml.plugin.js +337 -137
- package/src/plugins/relation.plugin.js +1 -1
- package/src/plugins/replicator.plugin.js +16 -16
- package/src/plugins/s3-queue.plugin.js +5 -5
- package/src/plugins/scheduler.plugin.js +7 -7
- package/src/plugins/state-machine.errors.js +9 -1
- package/src/plugins/state-machine.plugin.js +671 -16
- package/src/plugins/ttl.plugin.js +4 -4
- package/src/plugins/vector.plugin.js +10 -10
- package/src/resource.class.js +189 -40
|
@@ -200,7 +200,7 @@ export class TTLPlugin extends Plugin {
|
|
|
200
200
|
console.log(`[TTLPlugin] Installed with ${Object.keys(this.resources).length} resources`);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
this.emit('installed', {
|
|
203
|
+
this.emit('db:plugin:installed', {
|
|
204
204
|
plugin: 'TTLPlugin',
|
|
205
205
|
resources: Object.keys(this.resources)
|
|
206
206
|
});
|
|
@@ -512,7 +512,7 @@ export class TTLPlugin extends Plugin {
|
|
|
512
512
|
this.stats.lastScanAt = new Date().toISOString();
|
|
513
513
|
this.stats.lastScanDuration = Date.now() - startTime;
|
|
514
514
|
|
|
515
|
-
this.emit('
|
|
515
|
+
this.emit('plg:ttl:scan-completed', {
|
|
516
516
|
granularity,
|
|
517
517
|
duration: this.stats.lastScanDuration,
|
|
518
518
|
cohorts
|
|
@@ -520,7 +520,7 @@ export class TTLPlugin extends Plugin {
|
|
|
520
520
|
} catch (error) {
|
|
521
521
|
console.error(`[TTLPlugin] Error in ${granularity} cleanup:`, error);
|
|
522
522
|
this.stats.totalErrors++;
|
|
523
|
-
this.emit('
|
|
523
|
+
this.emit('plg:ttl:cleanup-error', { granularity, error });
|
|
524
524
|
}
|
|
525
525
|
}
|
|
526
526
|
|
|
@@ -585,7 +585,7 @@ export class TTLPlugin extends Plugin {
|
|
|
585
585
|
await this.expirationIndex.delete(entry.id);
|
|
586
586
|
|
|
587
587
|
this.stats.totalExpired++;
|
|
588
|
-
this.emit('
|
|
588
|
+
this.emit('plg:ttl:record-expired', { resource: entry.resourceName, record });
|
|
589
589
|
} catch (error) {
|
|
590
590
|
console.error(`[TTLPlugin] Error processing expired entry:`, error);
|
|
591
591
|
this.stats.totalErrors++;
|
|
@@ -47,7 +47,7 @@ export class VectorPlugin extends Plugin {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async onInstall() {
|
|
50
|
-
this.emit('installed', { plugin: 'VectorPlugin' });
|
|
50
|
+
this.emit('db:plugin:installed', { plugin: 'VectorPlugin' });
|
|
51
51
|
|
|
52
52
|
// Validate vector storage for all resources
|
|
53
53
|
this.validateVectorStorage();
|
|
@@ -57,11 +57,11 @@ export class VectorPlugin extends Plugin {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
async onStart() {
|
|
60
|
-
this.emit('started', { plugin: 'VectorPlugin' });
|
|
60
|
+
this.emit('db:plugin:started', { plugin: 'VectorPlugin' });
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
async onStop() {
|
|
64
|
-
this.emit('stopped', { plugin: 'VectorPlugin' });
|
|
64
|
+
this.emit('db:plugin:stopped', { plugin: 'VectorPlugin' });
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
async onUninstall(options) {
|
|
@@ -78,7 +78,7 @@ export class VectorPlugin extends Plugin {
|
|
|
78
78
|
delete resource.distance;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
this.emit('uninstalled', { plugin: 'VectorPlugin' });
|
|
81
|
+
this.emit('db:plugin:uninstalled', { plugin: 'VectorPlugin' });
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
@@ -114,12 +114,12 @@ export class VectorPlugin extends Plugin {
|
|
|
114
114
|
recommendation: 'body-overflow'
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
-
this.emit('vector:storage-warning', warning);
|
|
117
|
+
this.emit('plg:vector:storage-warning', warning);
|
|
118
118
|
|
|
119
119
|
// Auto-fix if configured
|
|
120
120
|
if (this.config.autoFixBehavior) {
|
|
121
121
|
resource.behavior = 'body-overflow';
|
|
122
|
-
this.emit('vector:behavior-fixed', {
|
|
122
|
+
this.emit('plg:vector:behavior-fixed', {
|
|
123
123
|
resource: resource.name,
|
|
124
124
|
newBehavior: 'body-overflow'
|
|
125
125
|
});
|
|
@@ -163,7 +163,7 @@ export class VectorPlugin extends Plugin {
|
|
|
163
163
|
|
|
164
164
|
// Check if partition already exists
|
|
165
165
|
if (resource.config.partitions && resource.config.partitions[partitionName]) {
|
|
166
|
-
this.emit('vector:partition-exists', {
|
|
166
|
+
this.emit('plg:vector:partition-exists', {
|
|
167
167
|
resource: resource.name,
|
|
168
168
|
vectorField: vectorField.name,
|
|
169
169
|
partition: partitionName,
|
|
@@ -193,7 +193,7 @@ export class VectorPlugin extends Plugin {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
// Emit event
|
|
196
|
-
this.emit('vector:partition-created', {
|
|
196
|
+
this.emit('plg:vector:partition-created', {
|
|
197
197
|
resource: resource.name,
|
|
198
198
|
vectorField: vectorField.name,
|
|
199
199
|
partition: partitionName,
|
|
@@ -296,7 +296,7 @@ export class VectorPlugin extends Plugin {
|
|
|
296
296
|
return updates;
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
-
this.emit('vector:hooks-installed', {
|
|
299
|
+
this.emit('plg:vector:hooks-installed', {
|
|
300
300
|
resource: resource.name,
|
|
301
301
|
vectorField,
|
|
302
302
|
trackingField,
|
|
@@ -429,7 +429,7 @@ export class VectorPlugin extends Plugin {
|
|
|
429
429
|
|
|
430
430
|
// Emit event if field detected
|
|
431
431
|
if (vectorField && this.config.emitEvents) {
|
|
432
|
-
this.emit('vector:field-detected', {
|
|
432
|
+
this.emit('plg:vector:field-detected', {
|
|
433
433
|
resource: resource.name,
|
|
434
434
|
vectorField,
|
|
435
435
|
timestamp: Date.now()
|
package/src/resource.class.js
CHANGED
|
@@ -1021,6 +1021,24 @@ export class Resource extends AsyncEventEmitter {
|
|
|
1021
1021
|
return Buffer.byteLength(String(body), 'utf8');
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
1024
|
+
/**
|
|
1025
|
+
* Emit standardized events with optional ID-specific variant
|
|
1026
|
+
*
|
|
1027
|
+
* @private
|
|
1028
|
+
* @param {string} event - Event name
|
|
1029
|
+
* @param {Object} payload - Event payload
|
|
1030
|
+
* @param {string} [id] - Optional ID for ID-specific events
|
|
1031
|
+
*/
|
|
1032
|
+
_emitStandardized(event, payload, id = null) {
|
|
1033
|
+
// Emit standardized event
|
|
1034
|
+
this.emit(event, payload);
|
|
1035
|
+
|
|
1036
|
+
// Emit ID-specific event if ID provided
|
|
1037
|
+
if (id) {
|
|
1038
|
+
this.emit(`${event}:${id}`, payload);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1024
1042
|
/**
|
|
1025
1043
|
* Insert a new resource object
|
|
1026
1044
|
* @param {Object} attributes - Resource attributes
|
|
@@ -1191,24 +1209,24 @@ export class Resource extends AsyncEventEmitter {
|
|
|
1191
1209
|
}
|
|
1192
1210
|
|
|
1193
1211
|
// Execute other afterInsert hooks synchronously (excluding partition hook)
|
|
1194
|
-
const nonPartitionHooks = this.hooks.afterInsert.filter(hook =>
|
|
1212
|
+
const nonPartitionHooks = this.hooks.afterInsert.filter(hook =>
|
|
1195
1213
|
!hook.toString().includes('createPartitionReferences')
|
|
1196
1214
|
);
|
|
1197
1215
|
let finalResult = insertedObject;
|
|
1198
1216
|
for (const hook of nonPartitionHooks) {
|
|
1199
1217
|
finalResult = await hook(finalResult);
|
|
1200
1218
|
}
|
|
1201
|
-
|
|
1202
|
-
// Emit insert event
|
|
1203
|
-
this.
|
|
1219
|
+
|
|
1220
|
+
// Emit insert event with standardized naming
|
|
1221
|
+
this._emitStandardized('inserted', finalResult, finalResult?.id || insertedObject?.id);
|
|
1204
1222
|
return finalResult;
|
|
1205
1223
|
} else {
|
|
1206
1224
|
// Sync mode: execute all hooks including partition creation
|
|
1207
1225
|
const finalResult = await this.executeHooks('afterInsert', insertedObject);
|
|
1208
|
-
|
|
1209
|
-
// Emit insert event
|
|
1210
|
-
this.
|
|
1211
|
-
|
|
1226
|
+
|
|
1227
|
+
// Emit insert event with standardized naming
|
|
1228
|
+
this._emitStandardized('inserted', finalResult, finalResult?.id || insertedObject?.id);
|
|
1229
|
+
|
|
1212
1230
|
// Return the final object
|
|
1213
1231
|
return finalResult;
|
|
1214
1232
|
}
|
|
@@ -1304,7 +1322,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
1304
1322
|
// Execute afterGet hooks
|
|
1305
1323
|
data = await this.executeHooks('afterGet', data);
|
|
1306
1324
|
|
|
1307
|
-
this.
|
|
1325
|
+
this._emitStandardized("get", "fetched", data, data.id);
|
|
1308
1326
|
const value = data;
|
|
1309
1327
|
return value;
|
|
1310
1328
|
}
|
|
@@ -1579,28 +1597,28 @@ export class Resource extends AsyncEventEmitter {
|
|
|
1579
1597
|
}
|
|
1580
1598
|
|
|
1581
1599
|
// Execute other afterUpdate hooks synchronously (excluding partition hook)
|
|
1582
|
-
const nonPartitionHooks = this.hooks.afterUpdate.filter(hook =>
|
|
1600
|
+
const nonPartitionHooks = this.hooks.afterUpdate.filter(hook =>
|
|
1583
1601
|
!hook.toString().includes('handlePartitionReferenceUpdates')
|
|
1584
1602
|
);
|
|
1585
1603
|
let finalResult = updatedData;
|
|
1586
1604
|
for (const hook of nonPartitionHooks) {
|
|
1587
1605
|
finalResult = await hook(finalResult);
|
|
1588
1606
|
}
|
|
1589
|
-
|
|
1590
|
-
this.
|
|
1607
|
+
|
|
1608
|
+
this._emitStandardized('updated', {
|
|
1591
1609
|
...updatedData,
|
|
1592
1610
|
$before: { ...originalData },
|
|
1593
1611
|
$after: { ...finalResult }
|
|
1594
|
-
});
|
|
1612
|
+
}, updatedData.id);
|
|
1595
1613
|
return finalResult;
|
|
1596
1614
|
} else {
|
|
1597
1615
|
// Sync mode: execute all hooks including partition updates
|
|
1598
1616
|
const finalResult = await this.executeHooks('afterUpdate', updatedData);
|
|
1599
|
-
this.
|
|
1617
|
+
this._emitStandardized('updated', {
|
|
1600
1618
|
...updatedData,
|
|
1601
1619
|
$before: { ...originalData },
|
|
1602
1620
|
$after: { ...finalResult }
|
|
1603
|
-
});
|
|
1621
|
+
}, updatedData.id);
|
|
1604
1622
|
return finalResult;
|
|
1605
1623
|
}
|
|
1606
1624
|
}
|
|
@@ -2125,11 +2143,11 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2125
2143
|
finalResult = await hook(finalResult);
|
|
2126
2144
|
}
|
|
2127
2145
|
|
|
2128
|
-
this.
|
|
2146
|
+
this._emitStandardized('updated', {
|
|
2129
2147
|
...updatedData,
|
|
2130
2148
|
$before: { ...originalData },
|
|
2131
2149
|
$after: { ...finalResult }
|
|
2132
|
-
});
|
|
2150
|
+
}, updatedData.id);
|
|
2133
2151
|
|
|
2134
2152
|
return {
|
|
2135
2153
|
success: true,
|
|
@@ -2141,11 +2159,11 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2141
2159
|
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
2142
2160
|
const finalResult = await this.executeHooks('afterUpdate', updatedData);
|
|
2143
2161
|
|
|
2144
|
-
this.
|
|
2162
|
+
this._emitStandardized('updated', {
|
|
2145
2163
|
...updatedData,
|
|
2146
2164
|
$before: { ...originalData },
|
|
2147
2165
|
$after: { ...finalResult }
|
|
2148
|
-
});
|
|
2166
|
+
}, updatedData.id);
|
|
2149
2167
|
|
|
2150
2168
|
return {
|
|
2151
2169
|
success: true,
|
|
@@ -2182,13 +2200,13 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2182
2200
|
await this.executeHooks('beforeDelete', objectData);
|
|
2183
2201
|
const key = this.getResourceKey(id);
|
|
2184
2202
|
const [ok2, err2, response] = await tryFn(() => this.client.deleteObject(key));
|
|
2185
|
-
|
|
2203
|
+
|
|
2186
2204
|
// Always emit delete event for audit purposes, even if delete fails
|
|
2187
|
-
this.
|
|
2205
|
+
this._emitStandardized("delete", "deleted", {
|
|
2188
2206
|
...objectData,
|
|
2189
2207
|
$before: { ...objectData },
|
|
2190
2208
|
$after: null
|
|
2191
|
-
});
|
|
2209
|
+
}, id);
|
|
2192
2210
|
|
|
2193
2211
|
// If we had an error getting the object, throw it now (after emitting the event)
|
|
2194
2212
|
if (deleteError) {
|
|
@@ -2339,7 +2357,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2339
2357
|
// Execute afterCount hooks
|
|
2340
2358
|
await this.executeHooks('afterCount', { count, partition, partitionValues });
|
|
2341
2359
|
|
|
2342
|
-
this.
|
|
2360
|
+
this._emitStandardized("count", "counted", count);
|
|
2343
2361
|
return count;
|
|
2344
2362
|
}
|
|
2345
2363
|
|
|
@@ -2367,7 +2385,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2367
2385
|
return result;
|
|
2368
2386
|
});
|
|
2369
2387
|
|
|
2370
|
-
this.
|
|
2388
|
+
this._emitStandardized("insertMany", "inserted-many", objects.length);
|
|
2371
2389
|
return results;
|
|
2372
2390
|
}
|
|
2373
2391
|
|
|
@@ -2417,7 +2435,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2417
2435
|
// Execute afterDeleteMany hooks
|
|
2418
2436
|
await this.executeHooks('afterDeleteMany', { ids, results });
|
|
2419
2437
|
|
|
2420
|
-
this.
|
|
2438
|
+
this._emitStandardized("deleteMany", "deleted-many", ids.length);
|
|
2421
2439
|
return results;
|
|
2422
2440
|
}
|
|
2423
2441
|
|
|
@@ -2431,7 +2449,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2431
2449
|
const prefix = `resource=${this.name}/data`;
|
|
2432
2450
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
2433
2451
|
|
|
2434
|
-
this.
|
|
2452
|
+
this._emitStandardized("deleteAll", "deleted-all", {
|
|
2435
2453
|
version: this.version,
|
|
2436
2454
|
prefix,
|
|
2437
2455
|
deletedCount
|
|
@@ -2454,7 +2472,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2454
2472
|
const prefix = `resource=${this.name}`;
|
|
2455
2473
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
2456
2474
|
|
|
2457
|
-
this.
|
|
2475
|
+
this._emitStandardized("deleteAllData", "deleted-all-data", {
|
|
2458
2476
|
resource: this.name,
|
|
2459
2477
|
prefix,
|
|
2460
2478
|
deletedCount
|
|
@@ -2532,7 +2550,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2532
2550
|
const idPart = parts.find(part => part.startsWith('id='));
|
|
2533
2551
|
return idPart ? idPart.replace('id=', '') : null;
|
|
2534
2552
|
}).filter(Boolean);
|
|
2535
|
-
this.
|
|
2553
|
+
this._emitStandardized("listIds", "listed-ids", ids.length);
|
|
2536
2554
|
return ids;
|
|
2537
2555
|
}
|
|
2538
2556
|
|
|
@@ -2580,13 +2598,13 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2580
2598
|
const [ok, err, ids] = await tryFn(() => this.listIds({ limit, offset }));
|
|
2581
2599
|
if (!ok) throw err;
|
|
2582
2600
|
const results = await this.processListResults(ids, 'main');
|
|
2583
|
-
this.
|
|
2601
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
2584
2602
|
return results;
|
|
2585
2603
|
}
|
|
2586
2604
|
|
|
2587
2605
|
async listPartition({ partition, partitionValues, limit, offset = 0 }) {
|
|
2588
2606
|
if (!this.config.partitions?.[partition]) {
|
|
2589
|
-
this.
|
|
2607
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 0 });
|
|
2590
2608
|
return [];
|
|
2591
2609
|
}
|
|
2592
2610
|
const partitionDef = this.config.partitions[partition];
|
|
@@ -2596,7 +2614,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2596
2614
|
const ids = this.extractIdsFromKeys(keys).slice(offset);
|
|
2597
2615
|
const filteredIds = limit ? ids.slice(0, limit) : ids;
|
|
2598
2616
|
const results = await this.processPartitionResults(filteredIds, partition, partitionDef, keys);
|
|
2599
|
-
this.
|
|
2617
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: results.length, errors: 0 });
|
|
2600
2618
|
return results;
|
|
2601
2619
|
}
|
|
2602
2620
|
|
|
@@ -2652,7 +2670,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2652
2670
|
}
|
|
2653
2671
|
return this.handleResourceError(err, id, context);
|
|
2654
2672
|
});
|
|
2655
|
-
this.
|
|
2673
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
2656
2674
|
return results;
|
|
2657
2675
|
}
|
|
2658
2676
|
|
|
@@ -2725,11 +2743,11 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2725
2743
|
*/
|
|
2726
2744
|
handleListError(error, { partition, partitionValues }) {
|
|
2727
2745
|
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
2728
|
-
this.
|
|
2746
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
2729
2747
|
return [];
|
|
2730
2748
|
}
|
|
2731
2749
|
|
|
2732
|
-
this.
|
|
2750
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
2733
2751
|
return [];
|
|
2734
2752
|
}
|
|
2735
2753
|
|
|
@@ -2771,7 +2789,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2771
2789
|
// Execute afterGetMany hooks
|
|
2772
2790
|
const finalResults = await this.executeHooks('afterGetMany', results);
|
|
2773
2791
|
|
|
2774
|
-
this.
|
|
2792
|
+
this._emitStandardized("getMany", "fetched-many", ids.length);
|
|
2775
2793
|
return finalResults;
|
|
2776
2794
|
}
|
|
2777
2795
|
|
|
@@ -2862,7 +2880,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2862
2880
|
hasTotalItems: totalItems !== null
|
|
2863
2881
|
}
|
|
2864
2882
|
};
|
|
2865
|
-
this.
|
|
2883
|
+
this._emitStandardized("page", "paginated", result);
|
|
2866
2884
|
return result;
|
|
2867
2885
|
});
|
|
2868
2886
|
if (ok) return result;
|
|
@@ -2936,7 +2954,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2936
2954
|
contentType
|
|
2937
2955
|
}));
|
|
2938
2956
|
if (!ok2) throw err2;
|
|
2939
|
-
this.
|
|
2957
|
+
this._emitStandardized("setContent", "content-set", { id, contentType, contentLength: buffer.length }, id);
|
|
2940
2958
|
return updatedData;
|
|
2941
2959
|
}
|
|
2942
2960
|
|
|
@@ -2966,7 +2984,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
2966
2984
|
}
|
|
2967
2985
|
const buffer = Buffer.from(await response.Body.transformToByteArray());
|
|
2968
2986
|
const contentType = response.ContentType || null;
|
|
2969
|
-
this.
|
|
2987
|
+
this._emitStandardized("content", "content-fetched", { id, contentLength: buffer.length, contentType }, id);
|
|
2970
2988
|
return {
|
|
2971
2989
|
buffer,
|
|
2972
2990
|
contentType
|
|
@@ -3000,7 +3018,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
3000
3018
|
metadata: existingMetadata,
|
|
3001
3019
|
}));
|
|
3002
3020
|
if (!ok2) throw err2;
|
|
3003
|
-
this.
|
|
3021
|
+
this._emitStandardized("deleteContent", "content-deleted", id, id);
|
|
3004
3022
|
return response;
|
|
3005
3023
|
}
|
|
3006
3024
|
|
|
@@ -3410,7 +3428,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
3410
3428
|
data._partition = partitionName;
|
|
3411
3429
|
data._partitionValues = partitionValues;
|
|
3412
3430
|
|
|
3413
|
-
this.
|
|
3431
|
+
this._emitStandardized("getFromPartition", "partition-fetched", data, data.id);
|
|
3414
3432
|
return data;
|
|
3415
3433
|
}
|
|
3416
3434
|
|
|
@@ -3696,6 +3714,137 @@ export class Resource extends AsyncEventEmitter {
|
|
|
3696
3714
|
return out;
|
|
3697
3715
|
}
|
|
3698
3716
|
|
|
3717
|
+
// ============================================================================
|
|
3718
|
+
// STATE MACHINE METHODS
|
|
3719
|
+
// ============================================================================
|
|
3720
|
+
|
|
3721
|
+
/**
|
|
3722
|
+
* Send an event to trigger a state transition
|
|
3723
|
+
* @param {string} id - Entity ID
|
|
3724
|
+
* @param {string} event - Event name
|
|
3725
|
+
* @param {Object} [eventData] - Event data
|
|
3726
|
+
* @returns {Promise<Object>} Transition result
|
|
3727
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
3728
|
+
* @example
|
|
3729
|
+
* await orders.state('order-123', 'CONFIRM', { confirmedBy: 'user-456' });
|
|
3730
|
+
*/
|
|
3731
|
+
async state(id, event, eventData) {
|
|
3732
|
+
if (!this._stateMachine) {
|
|
3733
|
+
throw new Error(
|
|
3734
|
+
`No state machine configured for resource '${this.name}'. ` +
|
|
3735
|
+
`Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
return this._stateMachine.send(id, event, eventData);
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
/**
|
|
3742
|
+
* Get current state of an entity
|
|
3743
|
+
* @param {string} id - Entity ID
|
|
3744
|
+
* @returns {Promise<string>} Current state
|
|
3745
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
3746
|
+
* @example
|
|
3747
|
+
* const currentState = await orders.getState('order-123');
|
|
3748
|
+
*/
|
|
3749
|
+
async getState(id) {
|
|
3750
|
+
if (!this._stateMachine) {
|
|
3751
|
+
throw new Error(
|
|
3752
|
+
`No state machine configured for resource '${this.name}'. ` +
|
|
3753
|
+
`Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3756
|
+
return this._stateMachine.getState(id);
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
/**
|
|
3760
|
+
* Check if a transition is valid
|
|
3761
|
+
* @param {string} id - Entity ID
|
|
3762
|
+
* @param {string} event - Event name
|
|
3763
|
+
* @returns {Promise<boolean>} True if transition is valid
|
|
3764
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
3765
|
+
* @example
|
|
3766
|
+
* const canConfirm = await orders.canTransition('order-123', 'CONFIRM');
|
|
3767
|
+
*/
|
|
3768
|
+
async canTransition(id, event) {
|
|
3769
|
+
if (!this._stateMachine) {
|
|
3770
|
+
throw new Error(
|
|
3771
|
+
`No state machine configured for resource '${this.name}'. ` +
|
|
3772
|
+
`Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
3773
|
+
);
|
|
3774
|
+
}
|
|
3775
|
+
return this._stateMachine.canTransition(id, event);
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
/**
|
|
3779
|
+
* Get all valid events for the current state
|
|
3780
|
+
* @param {string} id - Entity ID
|
|
3781
|
+
* @returns {Promise<Array<string>>} Array of valid event names
|
|
3782
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
3783
|
+
* @example
|
|
3784
|
+
* const events = await orders.getValidEvents('order-123');
|
|
3785
|
+
* // Returns: ['SHIP', 'CANCEL']
|
|
3786
|
+
*/
|
|
3787
|
+
async getValidEvents(id) {
|
|
3788
|
+
if (!this._stateMachine) {
|
|
3789
|
+
throw new Error(
|
|
3790
|
+
`No state machine configured for resource '${this.name}'. ` +
|
|
3791
|
+
`Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
3792
|
+
);
|
|
3793
|
+
}
|
|
3794
|
+
return this._stateMachine.getValidEvents(id);
|
|
3795
|
+
}
|
|
3796
|
+
|
|
3797
|
+
/**
|
|
3798
|
+
* Initialize entity with initial state
|
|
3799
|
+
* @param {string} id - Entity ID
|
|
3800
|
+
* @param {Object} [context] - Initial context data
|
|
3801
|
+
* @returns {Promise<void>}
|
|
3802
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
3803
|
+
* @example
|
|
3804
|
+
* await orders.initializeState('order-456', { customerId: 'user-123' });
|
|
3805
|
+
*/
|
|
3806
|
+
async initializeState(id, context) {
|
|
3807
|
+
if (!this._stateMachine) {
|
|
3808
|
+
throw new Error(
|
|
3809
|
+
`No state machine configured for resource '${this.name}'. ` +
|
|
3810
|
+
`Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
3811
|
+
);
|
|
3812
|
+
}
|
|
3813
|
+
return this._stateMachine.initializeEntity(id, context);
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
/**
|
|
3817
|
+
* Get transition history for an entity
|
|
3818
|
+
* @param {string} id - Entity ID
|
|
3819
|
+
* @param {Object} [options] - Query options
|
|
3820
|
+
* @param {number} [options.limit=100] - Maximum number of transitions
|
|
3821
|
+
* @param {Date} [options.fromDate] - Filter from date
|
|
3822
|
+
* @param {Date} [options.toDate] - Filter to date
|
|
3823
|
+
* @returns {Promise<Array<Object>>} Transition history
|
|
3824
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
3825
|
+
* @example
|
|
3826
|
+
* const history = await orders.getStateHistory('order-123', { limit: 50 });
|
|
3827
|
+
*/
|
|
3828
|
+
async getStateHistory(id, options) {
|
|
3829
|
+
if (!this._stateMachine) {
|
|
3830
|
+
throw new Error(
|
|
3831
|
+
`No state machine configured for resource '${this.name}'. ` +
|
|
3832
|
+
`Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
3833
|
+
);
|
|
3834
|
+
}
|
|
3835
|
+
return this._stateMachine.getTransitionHistory(id, options);
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
/**
|
|
3839
|
+
* Internal method to attach state machine instance
|
|
3840
|
+
* This is called by StateMachinePlugin during initialization
|
|
3841
|
+
* @private
|
|
3842
|
+
* @param {Object} stateMachine - State machine instance
|
|
3843
|
+
*/
|
|
3844
|
+
_attachStateMachine(stateMachine) {
|
|
3845
|
+
this._stateMachine = stateMachine;
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3699
3848
|
}
|
|
3700
3849
|
|
|
3701
3850
|
/**
|