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
package/dist/s3db.cjs.js
CHANGED
|
@@ -2567,6 +2567,18 @@ const PLUGIN_DEPENDENCIES = {
|
|
|
2567
2567
|
npmUrl: "https://www.npmjs.com/package/@hono/swagger-ui"
|
|
2568
2568
|
}
|
|
2569
2569
|
}
|
|
2570
|
+
},
|
|
2571
|
+
"ml-plugin": {
|
|
2572
|
+
name: "ML Plugin",
|
|
2573
|
+
docsUrl: "https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/ml-plugin.md",
|
|
2574
|
+
dependencies: {
|
|
2575
|
+
"@tensorflow/tfjs-node": {
|
|
2576
|
+
version: "^4.0.0",
|
|
2577
|
+
description: "TensorFlow.js for Node.js with native bindings",
|
|
2578
|
+
installCommand: "pnpm add @tensorflow/tfjs-node",
|
|
2579
|
+
npmUrl: "https://www.npmjs.com/package/@tensorflow/tfjs-node"
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2570
2582
|
}
|
|
2571
2583
|
};
|
|
2572
2584
|
function isVersionCompatible(actual, required) {
|
|
@@ -6616,7 +6628,7 @@ class BackupPlugin extends Plugin {
|
|
|
6616
6628
|
const storageInfo = this.driver.getStorageInfo();
|
|
6617
6629
|
console.log(`[BackupPlugin] Initialized with driver: ${storageInfo.type}`);
|
|
6618
6630
|
}
|
|
6619
|
-
this.emit("initialized", {
|
|
6631
|
+
this.emit("db:plugin:initialized", {
|
|
6620
6632
|
driver: this.driver.getType(),
|
|
6621
6633
|
config: this.driver.getStorageInfo()
|
|
6622
6634
|
});
|
|
@@ -6664,7 +6676,7 @@ class BackupPlugin extends Plugin {
|
|
|
6664
6676
|
if (this.config.onBackupStart) {
|
|
6665
6677
|
await this._executeHook(this.config.onBackupStart, type, { backupId });
|
|
6666
6678
|
}
|
|
6667
|
-
this.emit("
|
|
6679
|
+
this.emit("plg:backup:start", { id: backupId, type });
|
|
6668
6680
|
const metadata = await this._createBackupMetadata(backupId, type);
|
|
6669
6681
|
const tempBackupDir = path$1.join(this.config.tempDir, backupId);
|
|
6670
6682
|
await promises.mkdir(tempBackupDir, { recursive: true });
|
|
@@ -6697,7 +6709,7 @@ class BackupPlugin extends Plugin {
|
|
|
6697
6709
|
const stats = { backupId, type, size: totalSize, duration, driverInfo: uploadResult };
|
|
6698
6710
|
await this._executeHook(this.config.onBackupComplete, type, stats);
|
|
6699
6711
|
}
|
|
6700
|
-
this.emit("
|
|
6712
|
+
this.emit("plg:backup:complete", {
|
|
6701
6713
|
id: backupId,
|
|
6702
6714
|
type,
|
|
6703
6715
|
size: totalSize,
|
|
@@ -6725,7 +6737,7 @@ class BackupPlugin extends Plugin {
|
|
|
6725
6737
|
error: error.message,
|
|
6726
6738
|
duration: Date.now() - startTime
|
|
6727
6739
|
});
|
|
6728
|
-
this.emit("
|
|
6740
|
+
this.emit("plg:backup:error", { id: backupId, type, error: error.message });
|
|
6729
6741
|
throw error;
|
|
6730
6742
|
} finally {
|
|
6731
6743
|
this.activeBackups.delete(backupId);
|
|
@@ -6946,7 +6958,7 @@ class BackupPlugin extends Plugin {
|
|
|
6946
6958
|
if (this.config.onRestoreStart) {
|
|
6947
6959
|
await this._executeHook(this.config.onRestoreStart, backupId, options);
|
|
6948
6960
|
}
|
|
6949
|
-
this.emit("
|
|
6961
|
+
this.emit("plg:backup:restore-start", { id: backupId, options });
|
|
6950
6962
|
const backup = await this.getBackupStatus(backupId);
|
|
6951
6963
|
if (!backup) {
|
|
6952
6964
|
throw new Error(`Backup '${backupId}' not found`);
|
|
@@ -6969,7 +6981,7 @@ class BackupPlugin extends Plugin {
|
|
|
6969
6981
|
if (this.config.onRestoreComplete) {
|
|
6970
6982
|
await this._executeHook(this.config.onRestoreComplete, backupId, { restored: restoredResources });
|
|
6971
6983
|
}
|
|
6972
|
-
this.emit("
|
|
6984
|
+
this.emit("plg:backup:restore-complete", {
|
|
6973
6985
|
id: backupId,
|
|
6974
6986
|
restored: restoredResources
|
|
6975
6987
|
});
|
|
@@ -6984,7 +6996,7 @@ class BackupPlugin extends Plugin {
|
|
|
6984
6996
|
if (this.config.onRestoreError) {
|
|
6985
6997
|
await this._executeHook(this.config.onRestoreError, backupId, { error });
|
|
6986
6998
|
}
|
|
6987
|
-
this.emit("
|
|
6999
|
+
this.emit("plg:backup:restore-error", { id: backupId, error: error.message });
|
|
6988
7000
|
throw error;
|
|
6989
7001
|
}
|
|
6990
7002
|
}
|
|
@@ -7234,7 +7246,7 @@ class BackupPlugin extends Plugin {
|
|
|
7234
7246
|
}
|
|
7235
7247
|
async stop() {
|
|
7236
7248
|
for (const backupId of this.activeBackups) {
|
|
7237
|
-
this.emit("
|
|
7249
|
+
this.emit("plg:backup:cancelled", { id: backupId });
|
|
7238
7250
|
}
|
|
7239
7251
|
this.activeBackups.clear();
|
|
7240
7252
|
if (this.driver) {
|
|
@@ -8758,7 +8770,7 @@ class CachePlugin extends Plugin {
|
|
|
8758
8770
|
const specificKey = await this.generateCacheKey(resource, method, { id: data.id });
|
|
8759
8771
|
const [ok2, err2] = await this.clearCacheWithRetry(resource.cache, specificKey);
|
|
8760
8772
|
if (!ok2) {
|
|
8761
|
-
this.emit("
|
|
8773
|
+
this.emit("plg:cache:clear-error", {
|
|
8762
8774
|
resource: resource.name,
|
|
8763
8775
|
method,
|
|
8764
8776
|
id: data.id,
|
|
@@ -8776,7 +8788,7 @@ class CachePlugin extends Plugin {
|
|
|
8776
8788
|
const partitionKeyPrefix = path$1.join(keyPrefix, `partition=${partitionName}`);
|
|
8777
8789
|
const [ok2, err2] = await this.clearCacheWithRetry(resource.cache, partitionKeyPrefix);
|
|
8778
8790
|
if (!ok2) {
|
|
8779
|
-
this.emit("
|
|
8791
|
+
this.emit("plg:cache:clear-error", {
|
|
8780
8792
|
resource: resource.name,
|
|
8781
8793
|
partition: partitionName,
|
|
8782
8794
|
error: err2.message
|
|
@@ -8791,7 +8803,7 @@ class CachePlugin extends Plugin {
|
|
|
8791
8803
|
}
|
|
8792
8804
|
const [ok, err] = await this.clearCacheWithRetry(resource.cache, keyPrefix);
|
|
8793
8805
|
if (!ok) {
|
|
8794
|
-
this.emit("
|
|
8806
|
+
this.emit("plg:cache:clear-error", {
|
|
8795
8807
|
resource: resource.name,
|
|
8796
8808
|
type: "broad",
|
|
8797
8809
|
error: err.message
|
|
@@ -12691,7 +12703,7 @@ class GeoPlugin extends Plugin {
|
|
|
12691
12703
|
if (this.verbose) {
|
|
12692
12704
|
console.log(`[GeoPlugin] Installed with ${Object.keys(this.resources).length} resources`);
|
|
12693
12705
|
}
|
|
12694
|
-
this.emit("installed", {
|
|
12706
|
+
this.emit("db:plugin:installed", {
|
|
12695
12707
|
plugin: "GeoPlugin",
|
|
12696
12708
|
resources: Object.keys(this.resources)
|
|
12697
12709
|
});
|
|
@@ -13258,7 +13270,7 @@ class GeoPlugin extends Plugin {
|
|
|
13258
13270
|
if (this.verbose) {
|
|
13259
13271
|
console.log("[GeoPlugin] Uninstalled");
|
|
13260
13272
|
}
|
|
13261
|
-
this.emit("uninstalled", {
|
|
13273
|
+
this.emit("db:plugin:uninstalled", {
|
|
13262
13274
|
plugin: "GeoPlugin"
|
|
13263
13275
|
});
|
|
13264
13276
|
await super.uninstall();
|
|
@@ -15260,7 +15272,7 @@ class MLPlugin extends Plugin {
|
|
|
15260
15272
|
enableVersioning: options.enableVersioning !== false
|
|
15261
15273
|
// Default true
|
|
15262
15274
|
};
|
|
15263
|
-
requirePluginDependency("
|
|
15275
|
+
requirePluginDependency("ml-plugin");
|
|
15264
15276
|
this.models = {};
|
|
15265
15277
|
this.modelVersions = /* @__PURE__ */ new Map();
|
|
15266
15278
|
this.modelCache = /* @__PURE__ */ new Map();
|
|
@@ -15298,7 +15310,7 @@ class MLPlugin extends Plugin {
|
|
|
15298
15310
|
if (this.config.verbose) {
|
|
15299
15311
|
console.log(`[MLPlugin] Installed with ${Object.keys(this.models).length} models`);
|
|
15300
15312
|
}
|
|
15301
|
-
this.emit("installed", {
|
|
15313
|
+
this.emit("db:plugin:installed", {
|
|
15302
15314
|
plugin: "MLPlugin",
|
|
15303
15315
|
models: Object.keys(this.models)
|
|
15304
15316
|
});
|
|
@@ -15649,6 +15661,22 @@ class MLPlugin extends Plugin {
|
|
|
15649
15661
|
}
|
|
15650
15662
|
data = allData;
|
|
15651
15663
|
}
|
|
15664
|
+
if (modelConfig.filter && typeof modelConfig.filter === "function") {
|
|
15665
|
+
if (this.config.verbose) {
|
|
15666
|
+
console.log(`[MLPlugin] Applying custom filter function...`);
|
|
15667
|
+
}
|
|
15668
|
+
const originalLength = data.length;
|
|
15669
|
+
data = data.filter(modelConfig.filter);
|
|
15670
|
+
if (this.config.verbose) {
|
|
15671
|
+
console.log(`[MLPlugin] Filter reduced dataset from ${originalLength} to ${data.length} samples`);
|
|
15672
|
+
}
|
|
15673
|
+
}
|
|
15674
|
+
if (modelConfig.map && typeof modelConfig.map === "function") {
|
|
15675
|
+
if (this.config.verbose) {
|
|
15676
|
+
console.log(`[MLPlugin] Applying custom map function...`);
|
|
15677
|
+
}
|
|
15678
|
+
data = data.map(modelConfig.map);
|
|
15679
|
+
}
|
|
15652
15680
|
if (!data || data.length < this.config.minTrainingSamples) {
|
|
15653
15681
|
throw new TrainingError(
|
|
15654
15682
|
`Insufficient training data: ${data?.length || 0} samples (minimum: ${this.config.minTrainingSamples})`,
|
|
@@ -15671,7 +15699,7 @@ class MLPlugin extends Plugin {
|
|
|
15671
15699
|
if (this.config.verbose) {
|
|
15672
15700
|
console.log(`[MLPlugin] Training completed for "${modelName}":`, result);
|
|
15673
15701
|
}
|
|
15674
|
-
this.emit("
|
|
15702
|
+
this.emit("plg:ml:model-trained", {
|
|
15675
15703
|
modelName,
|
|
15676
15704
|
type: modelConfig.type,
|
|
15677
15705
|
result
|
|
@@ -15707,7 +15735,7 @@ class MLPlugin extends Plugin {
|
|
|
15707
15735
|
try {
|
|
15708
15736
|
const result = await model.predict(input);
|
|
15709
15737
|
this.stats.totalPredictions++;
|
|
15710
|
-
this.emit("prediction", {
|
|
15738
|
+
this.emit("plg:ml:prediction", {
|
|
15711
15739
|
modelName,
|
|
15712
15740
|
input,
|
|
15713
15741
|
result
|
|
@@ -15822,7 +15850,11 @@ class MLPlugin extends Plugin {
|
|
|
15822
15850
|
async _initializeVersioning(modelName) {
|
|
15823
15851
|
try {
|
|
15824
15852
|
const storage = this.getStorage();
|
|
15825
|
-
const
|
|
15853
|
+
const modelConfig = this.config.models[modelName];
|
|
15854
|
+
const resourceName = modelConfig.resource;
|
|
15855
|
+
const [ok, err, versionInfo] = await tryFn(
|
|
15856
|
+
() => storage.get(storage.getPluginKey(resourceName, "metadata", modelName, "versions"))
|
|
15857
|
+
);
|
|
15826
15858
|
if (ok && versionInfo) {
|
|
15827
15859
|
this.modelVersions.set(modelName, {
|
|
15828
15860
|
currentVersion: versionInfo.currentVersion || 1,
|
|
@@ -15861,16 +15893,22 @@ class MLPlugin extends Plugin {
|
|
|
15861
15893
|
async _updateVersionInfo(modelName, version) {
|
|
15862
15894
|
try {
|
|
15863
15895
|
const storage = this.getStorage();
|
|
15896
|
+
const modelConfig = this.config.models[modelName];
|
|
15897
|
+
const resourceName = modelConfig.resource;
|
|
15864
15898
|
const versionInfo = this.modelVersions.get(modelName) || { currentVersion: 1, latestVersion: 0 };
|
|
15865
15899
|
versionInfo.latestVersion = Math.max(versionInfo.latestVersion, version);
|
|
15866
15900
|
versionInfo.currentVersion = version;
|
|
15867
15901
|
this.modelVersions.set(modelName, versionInfo);
|
|
15868
|
-
await storage.
|
|
15869
|
-
modelName,
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15902
|
+
await storage.set(
|
|
15903
|
+
storage.getPluginKey(resourceName, "metadata", modelName, "versions"),
|
|
15904
|
+
{
|
|
15905
|
+
modelName,
|
|
15906
|
+
currentVersion: versionInfo.currentVersion,
|
|
15907
|
+
latestVersion: versionInfo.latestVersion,
|
|
15908
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15909
|
+
},
|
|
15910
|
+
{ behavior: "body-overflow" }
|
|
15911
|
+
);
|
|
15874
15912
|
if (this.config.verbose) {
|
|
15875
15913
|
console.log(`[MLPlugin] Updated version info for "${modelName}": current=v${versionInfo.currentVersion}, latest=v${versionInfo.latestVersion}`);
|
|
15876
15914
|
}
|
|
@@ -15885,6 +15923,8 @@ class MLPlugin extends Plugin {
|
|
|
15885
15923
|
async _saveModel(modelName) {
|
|
15886
15924
|
try {
|
|
15887
15925
|
const storage = this.getStorage();
|
|
15926
|
+
const modelConfig = this.config.models[modelName];
|
|
15927
|
+
const resourceName = modelConfig.resource;
|
|
15888
15928
|
const exportedModel = await this.models[modelName].export();
|
|
15889
15929
|
if (!exportedModel) {
|
|
15890
15930
|
if (this.config.verbose) {
|
|
@@ -15896,37 +15936,52 @@ class MLPlugin extends Plugin {
|
|
|
15896
15936
|
if (enableVersioning) {
|
|
15897
15937
|
const version = this._getNextVersion(modelName);
|
|
15898
15938
|
const modelStats = this.models[modelName].getStats();
|
|
15899
|
-
await storage.
|
|
15900
|
-
modelName,
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
15939
|
+
await storage.set(
|
|
15940
|
+
storage.getPluginKey(resourceName, "models", modelName, `v${version}`),
|
|
15941
|
+
{
|
|
15942
|
+
modelName,
|
|
15943
|
+
version,
|
|
15944
|
+
type: "model",
|
|
15945
|
+
modelData: exportedModel,
|
|
15946
|
+
// TensorFlow.js model object (will go to body)
|
|
15947
|
+
metrics: {
|
|
15948
|
+
loss: modelStats.loss,
|
|
15949
|
+
accuracy: modelStats.accuracy,
|
|
15950
|
+
samples: modelStats.samples
|
|
15951
|
+
},
|
|
15952
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15953
|
+
},
|
|
15954
|
+
{ behavior: "body-only" }
|
|
15955
|
+
// Large binary data goes to S3 body
|
|
15956
|
+
);
|
|
15911
15957
|
await this._updateVersionInfo(modelName, version);
|
|
15912
|
-
await storage.
|
|
15913
|
-
modelName,
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15958
|
+
await storage.set(
|
|
15959
|
+
storage.getPluginKey(resourceName, "metadata", modelName, "active"),
|
|
15960
|
+
{
|
|
15961
|
+
modelName,
|
|
15962
|
+
version,
|
|
15963
|
+
type: "reference",
|
|
15964
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15965
|
+
},
|
|
15966
|
+
{ behavior: "body-overflow" }
|
|
15967
|
+
// Small metadata
|
|
15968
|
+
);
|
|
15918
15969
|
if (this.config.verbose) {
|
|
15919
|
-
console.log(`[MLPlugin] Saved model "${modelName}" v${version} to
|
|
15970
|
+
console.log(`[MLPlugin] Saved model "${modelName}" v${version} to S3 (resource=${resourceName}/plugin=ml/models/${modelName}/v${version})`);
|
|
15920
15971
|
}
|
|
15921
15972
|
} else {
|
|
15922
|
-
await storage.
|
|
15923
|
-
modelName,
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
|
|
15973
|
+
await storage.set(
|
|
15974
|
+
storage.getPluginKey(resourceName, "models", modelName, "latest"),
|
|
15975
|
+
{
|
|
15976
|
+
modelName,
|
|
15977
|
+
type: "model",
|
|
15978
|
+
modelData: exportedModel,
|
|
15979
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15980
|
+
},
|
|
15981
|
+
{ behavior: "body-only" }
|
|
15982
|
+
);
|
|
15928
15983
|
if (this.config.verbose) {
|
|
15929
|
-
console.log(`[MLPlugin] Saved model "${modelName}" to
|
|
15984
|
+
console.log(`[MLPlugin] Saved model "${modelName}" to S3 (resource=${resourceName}/plugin=ml/models/${modelName}/latest)`);
|
|
15930
15985
|
}
|
|
15931
15986
|
}
|
|
15932
15987
|
} catch (error) {
|
|
@@ -15934,7 +15989,7 @@ class MLPlugin extends Plugin {
|
|
|
15934
15989
|
}
|
|
15935
15990
|
}
|
|
15936
15991
|
/**
|
|
15937
|
-
* Save intermediate training data to plugin storage (incremental)
|
|
15992
|
+
* Save intermediate training data to plugin storage (incremental - only new samples)
|
|
15938
15993
|
* @private
|
|
15939
15994
|
*/
|
|
15940
15995
|
async _saveTrainingData(modelName, rawData) {
|
|
@@ -15942,64 +15997,106 @@ class MLPlugin extends Plugin {
|
|
|
15942
15997
|
const storage = this.getStorage();
|
|
15943
15998
|
const model = this.models[modelName];
|
|
15944
15999
|
const modelConfig = this.config.models[modelName];
|
|
16000
|
+
const resourceName = modelConfig.resource;
|
|
15945
16001
|
const modelStats = model.getStats();
|
|
15946
16002
|
const enableVersioning = this.config.enableVersioning;
|
|
15947
|
-
const
|
|
15948
|
-
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
target: item[modelConfig.target]
|
|
15960
|
-
};
|
|
15961
|
-
}),
|
|
15962
|
-
metrics: {
|
|
15963
|
-
loss: modelStats.loss,
|
|
15964
|
-
accuracy: modelStats.accuracy,
|
|
15965
|
-
r2: modelStats.r2
|
|
15966
|
-
},
|
|
15967
|
-
trainedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15968
|
-
};
|
|
16003
|
+
const processedData = rawData.map((item) => {
|
|
16004
|
+
const features = {};
|
|
16005
|
+
modelConfig.features.forEach((feature) => {
|
|
16006
|
+
features[feature] = item[feature];
|
|
16007
|
+
});
|
|
16008
|
+
return {
|
|
16009
|
+
id: item.id || `${Date.now()}_${Math.random()}`,
|
|
16010
|
+
// Use record ID or generate
|
|
16011
|
+
features,
|
|
16012
|
+
target: item[modelConfig.target]
|
|
16013
|
+
};
|
|
16014
|
+
});
|
|
15969
16015
|
if (enableVersioning) {
|
|
15970
|
-
const
|
|
16016
|
+
const version = this._getNextVersion(modelName);
|
|
16017
|
+
const [ok, err, existing] = await tryFn(
|
|
16018
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
|
|
16019
|
+
);
|
|
15971
16020
|
let history = [];
|
|
16021
|
+
let previousSampleIds = /* @__PURE__ */ new Set();
|
|
15972
16022
|
if (ok && existing && existing.history) {
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
16023
|
+
history = existing.history;
|
|
16024
|
+
history.forEach((entry) => {
|
|
16025
|
+
if (entry.sampleIds) {
|
|
16026
|
+
entry.sampleIds.forEach((id) => previousSampleIds.add(id));
|
|
16027
|
+
}
|
|
16028
|
+
});
|
|
15978
16029
|
}
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
16030
|
+
const currentSampleIds = new Set(processedData.map((d) => d.id));
|
|
16031
|
+
const newSamples = processedData.filter((d) => !previousSampleIds.has(d.id));
|
|
16032
|
+
const newSampleIds = newSamples.map((d) => d.id);
|
|
16033
|
+
if (newSamples.length > 0) {
|
|
16034
|
+
await storage.set(
|
|
16035
|
+
storage.getPluginKey(resourceName, "training", "data", modelName, `v${version}`),
|
|
16036
|
+
{
|
|
16037
|
+
modelName,
|
|
16038
|
+
version,
|
|
16039
|
+
samples: newSamples,
|
|
16040
|
+
// Only new samples
|
|
16041
|
+
features: modelConfig.features,
|
|
16042
|
+
target: modelConfig.target,
|
|
16043
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16044
|
+
},
|
|
16045
|
+
{ behavior: "body-only" }
|
|
16046
|
+
// Dataset goes to S3 body
|
|
16047
|
+
);
|
|
16048
|
+
}
|
|
16049
|
+
const historyEntry = {
|
|
16050
|
+
version,
|
|
16051
|
+
totalSamples: processedData.length,
|
|
16052
|
+
// Total cumulative
|
|
16053
|
+
newSamples: newSamples.length,
|
|
16054
|
+
// Only new in this version
|
|
16055
|
+
sampleIds: Array.from(currentSampleIds),
|
|
16056
|
+
// All IDs for this version
|
|
16057
|
+
newSampleIds,
|
|
16058
|
+
// IDs of new samples
|
|
16059
|
+
storageKey: newSamples.length > 0 ? `training/data/${modelName}/v${version}` : null,
|
|
16060
|
+
metrics: {
|
|
16061
|
+
loss: modelStats.loss,
|
|
16062
|
+
accuracy: modelStats.accuracy,
|
|
16063
|
+
r2: modelStats.r2
|
|
16064
|
+
},
|
|
16065
|
+
trainedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16066
|
+
};
|
|
16067
|
+
history.push(historyEntry);
|
|
16068
|
+
await storage.set(
|
|
16069
|
+
storage.getPluginKey(resourceName, "training", "history", modelName),
|
|
16070
|
+
{
|
|
16071
|
+
modelName,
|
|
16072
|
+
type: "training_history",
|
|
16073
|
+
totalTrainings: history.length,
|
|
16074
|
+
latestVersion: version,
|
|
16075
|
+
history,
|
|
16076
|
+
// Array of metadata entries (not full data)
|
|
16077
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16078
|
+
},
|
|
16079
|
+
{ behavior: "body-overflow" }
|
|
16080
|
+
// History metadata
|
|
16081
|
+
);
|
|
15988
16082
|
if (this.config.verbose) {
|
|
15989
|
-
console.log(`[MLPlugin]
|
|
16083
|
+
console.log(`[MLPlugin] Saved training data for "${modelName}" v${version}: ${newSamples.length} new samples (total: ${processedData.length}, storage: resource=${resourceName}/plugin=ml/training/data/${modelName}/v${version})`);
|
|
15990
16084
|
}
|
|
15991
16085
|
} else {
|
|
15992
|
-
await storage.
|
|
15993
|
-
modelName,
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16086
|
+
await storage.set(
|
|
16087
|
+
storage.getPluginKey(resourceName, "training", "data", modelName, "latest"),
|
|
16088
|
+
{
|
|
16089
|
+
modelName,
|
|
16090
|
+
type: "training_data",
|
|
16091
|
+
samples: processedData,
|
|
16092
|
+
features: modelConfig.features,
|
|
16093
|
+
target: modelConfig.target,
|
|
16094
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16095
|
+
},
|
|
16096
|
+
{ behavior: "body-only" }
|
|
16097
|
+
);
|
|
16001
16098
|
if (this.config.verbose) {
|
|
16002
|
-
console.log(`[MLPlugin] Saved training data for "${modelName}" (${
|
|
16099
|
+
console.log(`[MLPlugin] Saved training data for "${modelName}" (${processedData.length} samples) to S3 (resource=${resourceName}/plugin=ml/training/data/${modelName}/latest)`);
|
|
16003
16100
|
}
|
|
16004
16101
|
}
|
|
16005
16102
|
} catch (error) {
|
|
@@ -16013,17 +16110,22 @@ class MLPlugin extends Plugin {
|
|
|
16013
16110
|
async _loadModel(modelName) {
|
|
16014
16111
|
try {
|
|
16015
16112
|
const storage = this.getStorage();
|
|
16113
|
+
const modelConfig = this.config.models[modelName];
|
|
16114
|
+
const resourceName = modelConfig.resource;
|
|
16016
16115
|
const enableVersioning = this.config.enableVersioning;
|
|
16017
16116
|
if (enableVersioning) {
|
|
16018
|
-
const [okRef, errRef, activeRef] = await tryFn(
|
|
16117
|
+
const [okRef, errRef, activeRef] = await tryFn(
|
|
16118
|
+
() => storage.get(storage.getPluginKey(resourceName, "metadata", modelName, "active"))
|
|
16119
|
+
);
|
|
16019
16120
|
if (okRef && activeRef && activeRef.version) {
|
|
16020
16121
|
const version = activeRef.version;
|
|
16021
|
-
const [ok, err, versionData] = await tryFn(
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
|
|
16122
|
+
const [ok, err, versionData] = await tryFn(
|
|
16123
|
+
() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`))
|
|
16124
|
+
);
|
|
16125
|
+
if (ok && versionData && versionData.modelData) {
|
|
16126
|
+
await this.models[modelName].import(versionData.modelData);
|
|
16025
16127
|
if (this.config.verbose) {
|
|
16026
|
-
console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (active) from
|
|
16128
|
+
console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (active) from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/v${version})`);
|
|
16027
16129
|
}
|
|
16028
16130
|
return;
|
|
16029
16131
|
}
|
|
@@ -16031,12 +16133,13 @@ class MLPlugin extends Plugin {
|
|
|
16031
16133
|
const versionInfo = this.modelVersions.get(modelName);
|
|
16032
16134
|
if (versionInfo && versionInfo.latestVersion > 0) {
|
|
16033
16135
|
const version = versionInfo.latestVersion;
|
|
16034
|
-
const [ok, err, versionData] = await tryFn(
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16136
|
+
const [ok, err, versionData] = await tryFn(
|
|
16137
|
+
() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`))
|
|
16138
|
+
);
|
|
16139
|
+
if (ok && versionData && versionData.modelData) {
|
|
16140
|
+
await this.models[modelName].import(versionData.modelData);
|
|
16038
16141
|
if (this.config.verbose) {
|
|
16039
|
-
console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (latest) from
|
|
16142
|
+
console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (latest) from S3`);
|
|
16040
16143
|
}
|
|
16041
16144
|
return;
|
|
16042
16145
|
}
|
|
@@ -16045,17 +16148,18 @@ class MLPlugin extends Plugin {
|
|
|
16045
16148
|
console.log(`[MLPlugin] No saved model versions found for "${modelName}"`);
|
|
16046
16149
|
}
|
|
16047
16150
|
} else {
|
|
16048
|
-
const [ok, err, record] = await tryFn(
|
|
16049
|
-
|
|
16151
|
+
const [ok, err, record] = await tryFn(
|
|
16152
|
+
() => storage.get(storage.getPluginKey(resourceName, "models", modelName, "latest"))
|
|
16153
|
+
);
|
|
16154
|
+
if (!ok || !record || !record.modelData) {
|
|
16050
16155
|
if (this.config.verbose) {
|
|
16051
16156
|
console.log(`[MLPlugin] No saved model found for "${modelName}"`);
|
|
16052
16157
|
}
|
|
16053
16158
|
return;
|
|
16054
16159
|
}
|
|
16055
|
-
|
|
16056
|
-
await this.models[modelName].import(modelData);
|
|
16160
|
+
await this.models[modelName].import(record.modelData);
|
|
16057
16161
|
if (this.config.verbose) {
|
|
16058
|
-
console.log(`[MLPlugin] Loaded model "${modelName}" from
|
|
16162
|
+
console.log(`[MLPlugin] Loaded model "${modelName}" from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/latest)`);
|
|
16059
16163
|
}
|
|
16060
16164
|
}
|
|
16061
16165
|
} catch (error) {
|
|
@@ -16063,27 +16167,68 @@ class MLPlugin extends Plugin {
|
|
|
16063
16167
|
}
|
|
16064
16168
|
}
|
|
16065
16169
|
/**
|
|
16066
|
-
* Load training data from plugin storage
|
|
16170
|
+
* Load training data from plugin storage (reconstructs specific version from incremental data)
|
|
16067
16171
|
* @param {string} modelName - Model name
|
|
16172
|
+
* @param {number} version - Version number (optional, defaults to latest)
|
|
16068
16173
|
* @returns {Object|null} Training data or null if not found
|
|
16069
16174
|
*/
|
|
16070
|
-
async getTrainingData(modelName) {
|
|
16175
|
+
async getTrainingData(modelName, version = null) {
|
|
16071
16176
|
try {
|
|
16072
16177
|
const storage = this.getStorage();
|
|
16073
|
-
const
|
|
16074
|
-
|
|
16178
|
+
const modelConfig = this.config.models[modelName];
|
|
16179
|
+
const resourceName = modelConfig.resource;
|
|
16180
|
+
const enableVersioning = this.config.enableVersioning;
|
|
16181
|
+
if (!enableVersioning) {
|
|
16182
|
+
const [ok, err, record] = await tryFn(
|
|
16183
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "data", modelName, "latest"))
|
|
16184
|
+
);
|
|
16185
|
+
if (!ok || !record) {
|
|
16186
|
+
if (this.config.verbose) {
|
|
16187
|
+
console.log(`[MLPlugin] No saved training data found for "${modelName}"`);
|
|
16188
|
+
}
|
|
16189
|
+
return null;
|
|
16190
|
+
}
|
|
16191
|
+
return {
|
|
16192
|
+
modelName: record.modelName,
|
|
16193
|
+
samples: record.samples,
|
|
16194
|
+
features: record.features,
|
|
16195
|
+
target: record.target,
|
|
16196
|
+
data: record.samples,
|
|
16197
|
+
savedAt: record.savedAt
|
|
16198
|
+
};
|
|
16199
|
+
}
|
|
16200
|
+
const [okHistory, errHistory, historyData] = await tryFn(
|
|
16201
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
|
|
16202
|
+
);
|
|
16203
|
+
if (!okHistory || !historyData || !historyData.history) {
|
|
16075
16204
|
if (this.config.verbose) {
|
|
16076
|
-
console.log(`[MLPlugin] No
|
|
16205
|
+
console.log(`[MLPlugin] No training history found for "${modelName}"`);
|
|
16077
16206
|
}
|
|
16078
16207
|
return null;
|
|
16079
16208
|
}
|
|
16209
|
+
const targetVersion = version || historyData.latestVersion;
|
|
16210
|
+
const reconstructedSamples = [];
|
|
16211
|
+
for (const entry of historyData.history) {
|
|
16212
|
+
if (entry.version > targetVersion) break;
|
|
16213
|
+
if (entry.storageKey && entry.newSamples > 0) {
|
|
16214
|
+
const [ok, err, versionData] = await tryFn(
|
|
16215
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "data", modelName, `v${entry.version}`))
|
|
16216
|
+
);
|
|
16217
|
+
if (ok && versionData && versionData.samples) {
|
|
16218
|
+
reconstructedSamples.push(...versionData.samples);
|
|
16219
|
+
}
|
|
16220
|
+
}
|
|
16221
|
+
}
|
|
16222
|
+
const targetEntry = historyData.history.find((e) => e.version === targetVersion);
|
|
16080
16223
|
return {
|
|
16081
|
-
modelName
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16224
|
+
modelName,
|
|
16225
|
+
version: targetVersion,
|
|
16226
|
+
samples: reconstructedSamples,
|
|
16227
|
+
totalSamples: reconstructedSamples.length,
|
|
16228
|
+
features: modelConfig.features,
|
|
16229
|
+
target: modelConfig.target,
|
|
16230
|
+
metrics: targetEntry?.metrics,
|
|
16231
|
+
savedAt: targetEntry?.trainedAt
|
|
16087
16232
|
};
|
|
16088
16233
|
} catch (error) {
|
|
16089
16234
|
console.error(`[MLPlugin] Failed to load training data for "${modelName}":`, error.message);
|
|
@@ -16091,15 +16236,29 @@ class MLPlugin extends Plugin {
|
|
|
16091
16236
|
}
|
|
16092
16237
|
}
|
|
16093
16238
|
/**
|
|
16094
|
-
* Delete model from plugin storage
|
|
16239
|
+
* Delete model from plugin storage (all versions)
|
|
16095
16240
|
* @private
|
|
16096
16241
|
*/
|
|
16097
16242
|
async _deleteModel(modelName) {
|
|
16098
16243
|
try {
|
|
16099
16244
|
const storage = this.getStorage();
|
|
16100
|
-
|
|
16245
|
+
const modelConfig = this.config.models[modelName];
|
|
16246
|
+
const resourceName = modelConfig.resource;
|
|
16247
|
+
const enableVersioning = this.config.enableVersioning;
|
|
16248
|
+
if (enableVersioning) {
|
|
16249
|
+
const versionInfo = this.modelVersions.get(modelName);
|
|
16250
|
+
if (versionInfo && versionInfo.latestVersion > 0) {
|
|
16251
|
+
for (let v = 1; v <= versionInfo.latestVersion; v++) {
|
|
16252
|
+
await storage.delete(storage.getPluginKey(resourceName, "models", modelName, `v${v}`));
|
|
16253
|
+
}
|
|
16254
|
+
}
|
|
16255
|
+
await storage.delete(storage.getPluginKey(resourceName, "metadata", modelName, "active"));
|
|
16256
|
+
await storage.delete(storage.getPluginKey(resourceName, "metadata", modelName, "versions"));
|
|
16257
|
+
} else {
|
|
16258
|
+
await storage.delete(storage.getPluginKey(resourceName, "models", modelName, "latest"));
|
|
16259
|
+
}
|
|
16101
16260
|
if (this.config.verbose) {
|
|
16102
|
-
console.log(`[MLPlugin] Deleted model "${modelName}" from plugin
|
|
16261
|
+
console.log(`[MLPlugin] Deleted model "${modelName}" from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/)`);
|
|
16103
16262
|
}
|
|
16104
16263
|
} catch (error) {
|
|
16105
16264
|
if (this.config.verbose) {
|
|
@@ -16108,15 +16267,32 @@ class MLPlugin extends Plugin {
|
|
|
16108
16267
|
}
|
|
16109
16268
|
}
|
|
16110
16269
|
/**
|
|
16111
|
-
* Delete training data from plugin storage
|
|
16270
|
+
* Delete training data from plugin storage (all versions)
|
|
16112
16271
|
* @private
|
|
16113
16272
|
*/
|
|
16114
16273
|
async _deleteTrainingData(modelName) {
|
|
16115
16274
|
try {
|
|
16116
16275
|
const storage = this.getStorage();
|
|
16117
|
-
|
|
16276
|
+
const modelConfig = this.config.models[modelName];
|
|
16277
|
+
const resourceName = modelConfig.resource;
|
|
16278
|
+
const enableVersioning = this.config.enableVersioning;
|
|
16279
|
+
if (enableVersioning) {
|
|
16280
|
+
const [ok, err, historyData] = await tryFn(
|
|
16281
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
|
|
16282
|
+
);
|
|
16283
|
+
if (ok && historyData && historyData.history) {
|
|
16284
|
+
for (const entry of historyData.history) {
|
|
16285
|
+
if (entry.storageKey) {
|
|
16286
|
+
await storage.delete(storage.getPluginKey(resourceName, "training", "data", modelName, `v${entry.version}`));
|
|
16287
|
+
}
|
|
16288
|
+
}
|
|
16289
|
+
}
|
|
16290
|
+
await storage.delete(storage.getPluginKey(resourceName, "training", "history", modelName));
|
|
16291
|
+
} else {
|
|
16292
|
+
await storage.delete(storage.getPluginKey(resourceName, "training", "data", modelName, "latest"));
|
|
16293
|
+
}
|
|
16118
16294
|
if (this.config.verbose) {
|
|
16119
|
-
console.log(`[MLPlugin] Deleted training data for "${modelName}" from plugin
|
|
16295
|
+
console.log(`[MLPlugin] Deleted training data for "${modelName}" from S3 (resource=${resourceName}/plugin=ml/training/)`);
|
|
16120
16296
|
}
|
|
16121
16297
|
} catch (error) {
|
|
16122
16298
|
if (this.config.verbose) {
|
|
@@ -16135,17 +16311,18 @@ class MLPlugin extends Plugin {
|
|
|
16135
16311
|
}
|
|
16136
16312
|
try {
|
|
16137
16313
|
const storage = this.getStorage();
|
|
16314
|
+
const modelConfig = this.config.models[modelName];
|
|
16315
|
+
const resourceName = modelConfig.resource;
|
|
16138
16316
|
const versionInfo = this.modelVersions.get(modelName) || { latestVersion: 0 };
|
|
16139
16317
|
const versions = [];
|
|
16140
16318
|
for (let v = 1; v <= versionInfo.latestVersion; v++) {
|
|
16141
|
-
const [ok, err, versionData] = await tryFn(() => storage.get(`
|
|
16319
|
+
const [ok, err, versionData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${v}`)));
|
|
16142
16320
|
if (ok && versionData) {
|
|
16143
|
-
const metrics = versionData.metrics ? JSON.parse(versionData.metrics) : {};
|
|
16144
16321
|
versions.push({
|
|
16145
16322
|
version: v,
|
|
16146
16323
|
savedAt: versionData.savedAt,
|
|
16147
16324
|
isCurrent: v === versionInfo.currentVersion,
|
|
16148
|
-
metrics
|
|
16325
|
+
metrics: versionData.metrics
|
|
16149
16326
|
});
|
|
16150
16327
|
}
|
|
16151
16328
|
}
|
|
@@ -16169,12 +16346,16 @@ class MLPlugin extends Plugin {
|
|
|
16169
16346
|
}
|
|
16170
16347
|
try {
|
|
16171
16348
|
const storage = this.getStorage();
|
|
16172
|
-
const
|
|
16349
|
+
const modelConfig = this.config.models[modelName];
|
|
16350
|
+
const resourceName = modelConfig.resource;
|
|
16351
|
+
const [ok, err, versionData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`)));
|
|
16173
16352
|
if (!ok || !versionData) {
|
|
16174
16353
|
throw new MLError(`Version ${version} not found for model "${modelName}"`, { modelName, version });
|
|
16175
16354
|
}
|
|
16176
|
-
|
|
16177
|
-
|
|
16355
|
+
if (!versionData.modelData) {
|
|
16356
|
+
throw new MLError(`Model data not found in version ${version}`, { modelName, version });
|
|
16357
|
+
}
|
|
16358
|
+
await this.models[modelName].import(versionData.modelData);
|
|
16178
16359
|
const versionInfo = this.modelVersions.get(modelName);
|
|
16179
16360
|
if (versionInfo) {
|
|
16180
16361
|
versionInfo.currentVersion = version;
|
|
@@ -16202,10 +16383,12 @@ class MLPlugin extends Plugin {
|
|
|
16202
16383
|
if (!this.config.enableVersioning) {
|
|
16203
16384
|
throw new MLError("Versioning is not enabled", { modelName });
|
|
16204
16385
|
}
|
|
16386
|
+
const modelConfig = this.config.models[modelName];
|
|
16387
|
+
const resourceName = modelConfig.resource;
|
|
16205
16388
|
await this.loadModelVersion(modelName, version);
|
|
16206
16389
|
await this._updateVersionInfo(modelName, version);
|
|
16207
16390
|
const storage = this.getStorage();
|
|
16208
|
-
await storage.
|
|
16391
|
+
await storage.set(storage.getPluginKey(resourceName, "metadata", modelName, "active"), {
|
|
16209
16392
|
modelName,
|
|
16210
16393
|
version,
|
|
16211
16394
|
type: "reference",
|
|
@@ -16227,7 +16410,9 @@ class MLPlugin extends Plugin {
|
|
|
16227
16410
|
}
|
|
16228
16411
|
try {
|
|
16229
16412
|
const storage = this.getStorage();
|
|
16230
|
-
const
|
|
16413
|
+
const modelConfig = this.config.models[modelName];
|
|
16414
|
+
const resourceName = modelConfig.resource;
|
|
16415
|
+
const [ok, err, historyData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName)));
|
|
16231
16416
|
if (!ok || !historyData) {
|
|
16232
16417
|
return null;
|
|
16233
16418
|
}
|
|
@@ -16256,8 +16441,10 @@ class MLPlugin extends Plugin {
|
|
|
16256
16441
|
}
|
|
16257
16442
|
try {
|
|
16258
16443
|
const storage = this.getStorage();
|
|
16259
|
-
const
|
|
16260
|
-
const
|
|
16444
|
+
const modelConfig = this.config.models[modelName];
|
|
16445
|
+
const resourceName = modelConfig.resource;
|
|
16446
|
+
const [ok1, err1, v1Data] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version1}`)));
|
|
16447
|
+
const [ok2, err2, v2Data] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version2}`)));
|
|
16261
16448
|
if (!ok1 || !v1Data) {
|
|
16262
16449
|
throw new MLError(`Version ${version1} not found`, { modelName, version: version1 });
|
|
16263
16450
|
}
|
|
@@ -16713,7 +16900,7 @@ class RelationPlugin extends Plugin {
|
|
|
16713
16900
|
if (this.verbose) {
|
|
16714
16901
|
console.log(`[RelationPlugin] Installed with ${Object.keys(this.relations).length} resources`);
|
|
16715
16902
|
}
|
|
16716
|
-
this.emit("installed", {
|
|
16903
|
+
this.emit("db:plugin:installed", {
|
|
16717
16904
|
plugin: "RelationPlugin",
|
|
16718
16905
|
resources: Object.keys(this.relations)
|
|
16719
16906
|
});
|
|
@@ -20288,7 +20475,7 @@ class S3Client extends EventEmitter {
|
|
|
20288
20475
|
return client;
|
|
20289
20476
|
}
|
|
20290
20477
|
async sendCommand(command) {
|
|
20291
|
-
this.emit("
|
|
20478
|
+
this.emit("cl:request", command.constructor.name, command.input);
|
|
20292
20479
|
const [ok, err, response] = await tryFn(() => this.client.send(command));
|
|
20293
20480
|
if (!ok) {
|
|
20294
20481
|
const bucket = this.config.bucket;
|
|
@@ -20300,7 +20487,7 @@ class S3Client extends EventEmitter {
|
|
|
20300
20487
|
commandInput: command.input
|
|
20301
20488
|
});
|
|
20302
20489
|
}
|
|
20303
|
-
this.emit("
|
|
20490
|
+
this.emit("cl:response", command.constructor.name, response, command.input);
|
|
20304
20491
|
return response;
|
|
20305
20492
|
}
|
|
20306
20493
|
async putObject({ key, metadata, contentType, body, contentEncoding, contentLength, ifMatch }) {
|
|
@@ -20325,7 +20512,7 @@ class S3Client extends EventEmitter {
|
|
|
20325
20512
|
if (contentLength !== void 0) options.ContentLength = contentLength;
|
|
20326
20513
|
if (ifMatch !== void 0) options.IfMatch = ifMatch;
|
|
20327
20514
|
const [ok, err, response] = await tryFn(() => this.sendCommand(new clientS3.PutObjectCommand(options)));
|
|
20328
|
-
this.emit("
|
|
20515
|
+
this.emit("cl:PutObject", err || response, { key, metadata, contentType, body, contentEncoding, contentLength });
|
|
20329
20516
|
if (!ok) {
|
|
20330
20517
|
throw mapAwsError(err, {
|
|
20331
20518
|
bucket: this.config.bucket,
|
|
@@ -20353,7 +20540,7 @@ class S3Client extends EventEmitter {
|
|
|
20353
20540
|
}
|
|
20354
20541
|
return res;
|
|
20355
20542
|
});
|
|
20356
|
-
this.emit("
|
|
20543
|
+
this.emit("cl:GetObject", err || response, { key });
|
|
20357
20544
|
if (!ok) {
|
|
20358
20545
|
throw mapAwsError(err, {
|
|
20359
20546
|
bucket: this.config.bucket,
|
|
@@ -20381,7 +20568,7 @@ class S3Client extends EventEmitter {
|
|
|
20381
20568
|
}
|
|
20382
20569
|
return res;
|
|
20383
20570
|
});
|
|
20384
|
-
this.emit("
|
|
20571
|
+
this.emit("cl:HeadObject", err || response, { key });
|
|
20385
20572
|
if (!ok) {
|
|
20386
20573
|
throw mapAwsError(err, {
|
|
20387
20574
|
bucket: this.config.bucket,
|
|
@@ -20414,7 +20601,7 @@ class S3Client extends EventEmitter {
|
|
|
20414
20601
|
options.ContentType = contentType;
|
|
20415
20602
|
}
|
|
20416
20603
|
const [ok, err, response] = await tryFn(() => this.sendCommand(new clientS3.CopyObjectCommand(options)));
|
|
20417
|
-
this.emit("
|
|
20604
|
+
this.emit("cl:CopyObject", err || response, { from, to, metadataDirective });
|
|
20418
20605
|
if (!ok) {
|
|
20419
20606
|
throw mapAwsError(err, {
|
|
20420
20607
|
bucket: this.config.bucket,
|
|
@@ -20439,7 +20626,7 @@ class S3Client extends EventEmitter {
|
|
|
20439
20626
|
Key: keyPrefix ? path$1.join(keyPrefix, key) : key
|
|
20440
20627
|
};
|
|
20441
20628
|
const [ok, err, response] = await tryFn(() => this.sendCommand(new clientS3.DeleteObjectCommand(options)));
|
|
20442
|
-
this.emit("
|
|
20629
|
+
this.emit("cl:DeleteObject", err || response, { key });
|
|
20443
20630
|
if (!ok) {
|
|
20444
20631
|
throw mapAwsError(err, {
|
|
20445
20632
|
bucket: this.config.bucket,
|
|
@@ -20479,7 +20666,7 @@ class S3Client extends EventEmitter {
|
|
|
20479
20666
|
deleted: results,
|
|
20480
20667
|
notFound: errors
|
|
20481
20668
|
};
|
|
20482
|
-
this.emit("
|
|
20669
|
+
this.emit("cl:DeleteObjects", report, keys);
|
|
20483
20670
|
return report;
|
|
20484
20671
|
}
|
|
20485
20672
|
/**
|
|
@@ -20509,7 +20696,7 @@ class S3Client extends EventEmitter {
|
|
|
20509
20696
|
const deleteResponse = await this.client.send(deleteCommand);
|
|
20510
20697
|
const deletedCount = deleteResponse.Deleted ? deleteResponse.Deleted.length : 0;
|
|
20511
20698
|
totalDeleted += deletedCount;
|
|
20512
|
-
this.emit("
|
|
20699
|
+
this.emit("cl:DeleteAll", {
|
|
20513
20700
|
prefix,
|
|
20514
20701
|
batch: deletedCount,
|
|
20515
20702
|
total: totalDeleted
|
|
@@ -20517,7 +20704,7 @@ class S3Client extends EventEmitter {
|
|
|
20517
20704
|
}
|
|
20518
20705
|
continuationToken = listResponse.IsTruncated ? listResponse.NextContinuationToken : void 0;
|
|
20519
20706
|
} while (continuationToken);
|
|
20520
|
-
this.emit("
|
|
20707
|
+
this.emit("cl:DeleteAllComplete", {
|
|
20521
20708
|
prefix,
|
|
20522
20709
|
totalDeleted
|
|
20523
20710
|
});
|
|
@@ -20548,7 +20735,7 @@ class S3Client extends EventEmitter {
|
|
|
20548
20735
|
if (!ok) {
|
|
20549
20736
|
throw new UnknownError("Unknown error in listObjects", { prefix, bucket: this.config.bucket, original: err });
|
|
20550
20737
|
}
|
|
20551
|
-
this.emit("
|
|
20738
|
+
this.emit("cl:ListObjects", response, options);
|
|
20552
20739
|
return response;
|
|
20553
20740
|
}
|
|
20554
20741
|
async count({ prefix } = {}) {
|
|
@@ -20565,7 +20752,7 @@ class S3Client extends EventEmitter {
|
|
|
20565
20752
|
truncated = response.IsTruncated || false;
|
|
20566
20753
|
continuationToken = response.NextContinuationToken;
|
|
20567
20754
|
}
|
|
20568
|
-
this.emit("
|
|
20755
|
+
this.emit("cl:Count", count, { prefix });
|
|
20569
20756
|
return count;
|
|
20570
20757
|
}
|
|
20571
20758
|
async getAllKeys({ prefix } = {}) {
|
|
@@ -20587,7 +20774,7 @@ class S3Client extends EventEmitter {
|
|
|
20587
20774
|
if (this.config.keyPrefix) {
|
|
20588
20775
|
keys = keys.map((x) => x.replace(this.config.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace(`/`, "") : x);
|
|
20589
20776
|
}
|
|
20590
|
-
this.emit("
|
|
20777
|
+
this.emit("cl:GetAllKeys", keys, { prefix });
|
|
20591
20778
|
return keys;
|
|
20592
20779
|
}
|
|
20593
20780
|
async getContinuationTokenAfterOffset(params = {}) {
|
|
@@ -20616,7 +20803,7 @@ class S3Client extends EventEmitter {
|
|
|
20616
20803
|
break;
|
|
20617
20804
|
}
|
|
20618
20805
|
}
|
|
20619
|
-
this.emit("
|
|
20806
|
+
this.emit("cl:GetContinuationTokenAfterOffset", continuationToken || null, params);
|
|
20620
20807
|
return continuationToken || null;
|
|
20621
20808
|
}
|
|
20622
20809
|
async getKeysPage(params = {}) {
|
|
@@ -20634,7 +20821,7 @@ class S3Client extends EventEmitter {
|
|
|
20634
20821
|
offset
|
|
20635
20822
|
});
|
|
20636
20823
|
if (!continuationToken) {
|
|
20637
|
-
this.emit("
|
|
20824
|
+
this.emit("cl:GetKeysPage", [], params);
|
|
20638
20825
|
return [];
|
|
20639
20826
|
}
|
|
20640
20827
|
}
|
|
@@ -20657,7 +20844,7 @@ class S3Client extends EventEmitter {
|
|
|
20657
20844
|
if (this.config.keyPrefix) {
|
|
20658
20845
|
keys = keys.map((x) => x.replace(this.config.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace(`/`, "") : x);
|
|
20659
20846
|
}
|
|
20660
|
-
this.emit("
|
|
20847
|
+
this.emit("cl:GetKeysPage", keys, params);
|
|
20661
20848
|
return keys;
|
|
20662
20849
|
}
|
|
20663
20850
|
async moveAllObjects({ prefixFrom, prefixTo }) {
|
|
@@ -20675,7 +20862,7 @@ class S3Client extends EventEmitter {
|
|
|
20675
20862
|
}
|
|
20676
20863
|
return to;
|
|
20677
20864
|
});
|
|
20678
|
-
this.emit("
|
|
20865
|
+
this.emit("cl:MoveAllObjects", { results, errors }, { prefixFrom, prefixTo });
|
|
20679
20866
|
if (errors.length > 0) {
|
|
20680
20867
|
throw new UnknownError("Some objects could not be moved", {
|
|
20681
20868
|
bucket: this.config.bucket,
|
|
@@ -23530,6 +23717,20 @@ ${errorDetails}`,
|
|
|
23530
23717
|
if (typeof body === "object") return Buffer.byteLength(JSON.stringify(body), "utf8");
|
|
23531
23718
|
return Buffer.byteLength(String(body), "utf8");
|
|
23532
23719
|
}
|
|
23720
|
+
/**
|
|
23721
|
+
* Emit standardized events with optional ID-specific variant
|
|
23722
|
+
*
|
|
23723
|
+
* @private
|
|
23724
|
+
* @param {string} event - Event name
|
|
23725
|
+
* @param {Object} payload - Event payload
|
|
23726
|
+
* @param {string} [id] - Optional ID for ID-specific events
|
|
23727
|
+
*/
|
|
23728
|
+
_emitStandardized(event, payload, id = null) {
|
|
23729
|
+
this.emit(event, payload);
|
|
23730
|
+
if (id) {
|
|
23731
|
+
this.emit(`${event}:${id}`, payload);
|
|
23732
|
+
}
|
|
23733
|
+
}
|
|
23533
23734
|
/**
|
|
23534
23735
|
* Insert a new resource object
|
|
23535
23736
|
* @param {Object} attributes - Resource attributes
|
|
@@ -23670,11 +23871,11 @@ ${errorDetails}`,
|
|
|
23670
23871
|
for (const hook of nonPartitionHooks) {
|
|
23671
23872
|
finalResult = await hook(finalResult);
|
|
23672
23873
|
}
|
|
23673
|
-
this.
|
|
23874
|
+
this._emitStandardized("inserted", finalResult, finalResult?.id || insertedObject?.id);
|
|
23674
23875
|
return finalResult;
|
|
23675
23876
|
} else {
|
|
23676
23877
|
const finalResult = await this.executeHooks("afterInsert", insertedObject);
|
|
23677
|
-
this.
|
|
23878
|
+
this._emitStandardized("inserted", finalResult, finalResult?.id || insertedObject?.id);
|
|
23678
23879
|
return finalResult;
|
|
23679
23880
|
}
|
|
23680
23881
|
}
|
|
@@ -23738,7 +23939,7 @@ ${errorDetails}`,
|
|
|
23738
23939
|
data = await this.applyVersionMapping(data, objectVersion, this.version);
|
|
23739
23940
|
}
|
|
23740
23941
|
data = await this.executeHooks("afterGet", data);
|
|
23741
|
-
this.
|
|
23942
|
+
this._emitStandardized("get", "fetched", data, data.id);
|
|
23742
23943
|
const value = data;
|
|
23743
23944
|
return value;
|
|
23744
23945
|
}
|
|
@@ -23989,19 +24190,19 @@ ${errorDetails}`,
|
|
|
23989
24190
|
for (const hook of nonPartitionHooks) {
|
|
23990
24191
|
finalResult = await hook(finalResult);
|
|
23991
24192
|
}
|
|
23992
|
-
this.
|
|
24193
|
+
this._emitStandardized("updated", {
|
|
23993
24194
|
...updatedData,
|
|
23994
24195
|
$before: { ...originalData },
|
|
23995
24196
|
$after: { ...finalResult }
|
|
23996
|
-
});
|
|
24197
|
+
}, updatedData.id);
|
|
23997
24198
|
return finalResult;
|
|
23998
24199
|
} else {
|
|
23999
24200
|
const finalResult = await this.executeHooks("afterUpdate", updatedData);
|
|
24000
|
-
this.
|
|
24201
|
+
this._emitStandardized("updated", {
|
|
24001
24202
|
...updatedData,
|
|
24002
24203
|
$before: { ...originalData },
|
|
24003
24204
|
$after: { ...finalResult }
|
|
24004
|
-
});
|
|
24205
|
+
}, updatedData.id);
|
|
24005
24206
|
return finalResult;
|
|
24006
24207
|
}
|
|
24007
24208
|
}
|
|
@@ -24400,11 +24601,11 @@ ${errorDetails}`,
|
|
|
24400
24601
|
for (const hook of nonPartitionHooks) {
|
|
24401
24602
|
finalResult = await hook(finalResult);
|
|
24402
24603
|
}
|
|
24403
|
-
this.
|
|
24604
|
+
this._emitStandardized("updated", {
|
|
24404
24605
|
...updatedData,
|
|
24405
24606
|
$before: { ...originalData },
|
|
24406
24607
|
$after: { ...finalResult }
|
|
24407
|
-
});
|
|
24608
|
+
}, updatedData.id);
|
|
24408
24609
|
return {
|
|
24409
24610
|
success: true,
|
|
24410
24611
|
data: finalResult,
|
|
@@ -24413,11 +24614,11 @@ ${errorDetails}`,
|
|
|
24413
24614
|
} else {
|
|
24414
24615
|
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
24415
24616
|
const finalResult = await this.executeHooks("afterUpdate", updatedData);
|
|
24416
|
-
this.
|
|
24617
|
+
this._emitStandardized("updated", {
|
|
24417
24618
|
...updatedData,
|
|
24418
24619
|
$before: { ...originalData },
|
|
24419
24620
|
$after: { ...finalResult }
|
|
24420
|
-
});
|
|
24621
|
+
}, updatedData.id);
|
|
24421
24622
|
return {
|
|
24422
24623
|
success: true,
|
|
24423
24624
|
data: finalResult,
|
|
@@ -24448,11 +24649,11 @@ ${errorDetails}`,
|
|
|
24448
24649
|
await this.executeHooks("beforeDelete", objectData);
|
|
24449
24650
|
const key = this.getResourceKey(id);
|
|
24450
24651
|
const [ok2, err2, response] = await tryFn(() => this.client.deleteObject(key));
|
|
24451
|
-
this.
|
|
24652
|
+
this._emitStandardized("delete", "deleted", {
|
|
24452
24653
|
...objectData,
|
|
24453
24654
|
$before: { ...objectData },
|
|
24454
24655
|
$after: null
|
|
24455
|
-
});
|
|
24656
|
+
}, id);
|
|
24456
24657
|
if (deleteError) {
|
|
24457
24658
|
throw mapAwsError(deleteError, {
|
|
24458
24659
|
bucket: this.client.config.bucket,
|
|
@@ -24576,7 +24777,7 @@ ${errorDetails}`,
|
|
|
24576
24777
|
}
|
|
24577
24778
|
const count = await this.client.count({ prefix });
|
|
24578
24779
|
await this.executeHooks("afterCount", { count, partition, partitionValues });
|
|
24579
|
-
this.
|
|
24780
|
+
this._emitStandardized("count", "counted", count);
|
|
24580
24781
|
return count;
|
|
24581
24782
|
}
|
|
24582
24783
|
/**
|
|
@@ -24599,7 +24800,7 @@ ${errorDetails}`,
|
|
|
24599
24800
|
const result = await this.insert(attributes);
|
|
24600
24801
|
return result;
|
|
24601
24802
|
});
|
|
24602
|
-
this.
|
|
24803
|
+
this._emitStandardized("insertMany", "inserted-many", objects.length);
|
|
24603
24804
|
return results;
|
|
24604
24805
|
}
|
|
24605
24806
|
/**
|
|
@@ -24634,7 +24835,7 @@ ${errorDetails}`,
|
|
|
24634
24835
|
return response;
|
|
24635
24836
|
});
|
|
24636
24837
|
await this.executeHooks("afterDeleteMany", { ids, results });
|
|
24637
|
-
this.
|
|
24838
|
+
this._emitStandardized("deleteMany", "deleted-many", ids.length);
|
|
24638
24839
|
return results;
|
|
24639
24840
|
}
|
|
24640
24841
|
async deleteAll() {
|
|
@@ -24643,7 +24844,7 @@ ${errorDetails}`,
|
|
|
24643
24844
|
}
|
|
24644
24845
|
const prefix = `resource=${this.name}/data`;
|
|
24645
24846
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24646
|
-
this.
|
|
24847
|
+
this._emitStandardized("deleteAll", "deleted-all", {
|
|
24647
24848
|
version: this.version,
|
|
24648
24849
|
prefix,
|
|
24649
24850
|
deletedCount
|
|
@@ -24660,7 +24861,7 @@ ${errorDetails}`,
|
|
|
24660
24861
|
}
|
|
24661
24862
|
const prefix = `resource=${this.name}`;
|
|
24662
24863
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24663
|
-
this.
|
|
24864
|
+
this._emitStandardized("deleteAllData", "deleted-all-data", {
|
|
24664
24865
|
resource: this.name,
|
|
24665
24866
|
prefix,
|
|
24666
24867
|
deletedCount
|
|
@@ -24730,7 +24931,7 @@ ${errorDetails}`,
|
|
|
24730
24931
|
const idPart = parts.find((part) => part.startsWith("id="));
|
|
24731
24932
|
return idPart ? idPart.replace("id=", "") : null;
|
|
24732
24933
|
}).filter(Boolean);
|
|
24733
|
-
this.
|
|
24934
|
+
this._emitStandardized("listIds", "listed-ids", ids.length);
|
|
24734
24935
|
return ids;
|
|
24735
24936
|
}
|
|
24736
24937
|
/**
|
|
@@ -24772,12 +24973,12 @@ ${errorDetails}`,
|
|
|
24772
24973
|
const [ok, err, ids] = await tryFn(() => this.listIds({ limit, offset }));
|
|
24773
24974
|
if (!ok) throw err;
|
|
24774
24975
|
const results = await this.processListResults(ids, "main");
|
|
24775
|
-
this.
|
|
24976
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
24776
24977
|
return results;
|
|
24777
24978
|
}
|
|
24778
24979
|
async listPartition({ partition, partitionValues, limit, offset = 0 }) {
|
|
24779
24980
|
if (!this.config.partitions?.[partition]) {
|
|
24780
|
-
this.
|
|
24981
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 0 });
|
|
24781
24982
|
return [];
|
|
24782
24983
|
}
|
|
24783
24984
|
const partitionDef = this.config.partitions[partition];
|
|
@@ -24787,7 +24988,7 @@ ${errorDetails}`,
|
|
|
24787
24988
|
const ids = this.extractIdsFromKeys(keys).slice(offset);
|
|
24788
24989
|
const filteredIds = limit ? ids.slice(0, limit) : ids;
|
|
24789
24990
|
const results = await this.processPartitionResults(filteredIds, partition, partitionDef, keys);
|
|
24790
|
-
this.
|
|
24991
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: results.length, errors: 0 });
|
|
24791
24992
|
return results;
|
|
24792
24993
|
}
|
|
24793
24994
|
/**
|
|
@@ -24832,7 +25033,7 @@ ${errorDetails}`,
|
|
|
24832
25033
|
}
|
|
24833
25034
|
return this.handleResourceError(err, id, context);
|
|
24834
25035
|
});
|
|
24835
|
-
this.
|
|
25036
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
24836
25037
|
return results;
|
|
24837
25038
|
}
|
|
24838
25039
|
/**
|
|
@@ -24895,10 +25096,10 @@ ${errorDetails}`,
|
|
|
24895
25096
|
*/
|
|
24896
25097
|
handleListError(error, { partition, partitionValues }) {
|
|
24897
25098
|
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
24898
|
-
this.
|
|
25099
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
24899
25100
|
return [];
|
|
24900
25101
|
}
|
|
24901
|
-
this.
|
|
25102
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
24902
25103
|
return [];
|
|
24903
25104
|
}
|
|
24904
25105
|
/**
|
|
@@ -24931,7 +25132,7 @@ ${errorDetails}`,
|
|
|
24931
25132
|
throw err;
|
|
24932
25133
|
});
|
|
24933
25134
|
const finalResults = await this.executeHooks("afterGetMany", results);
|
|
24934
|
-
this.
|
|
25135
|
+
this._emitStandardized("getMany", "fetched-many", ids.length);
|
|
24935
25136
|
return finalResults;
|
|
24936
25137
|
}
|
|
24937
25138
|
/**
|
|
@@ -25017,7 +25218,7 @@ ${errorDetails}`,
|
|
|
25017
25218
|
hasTotalItems: totalItems !== null
|
|
25018
25219
|
}
|
|
25019
25220
|
};
|
|
25020
|
-
this.
|
|
25221
|
+
this._emitStandardized("page", "paginated", result2);
|
|
25021
25222
|
return result2;
|
|
25022
25223
|
});
|
|
25023
25224
|
if (ok) return result;
|
|
@@ -25087,7 +25288,7 @@ ${errorDetails}`,
|
|
|
25087
25288
|
contentType
|
|
25088
25289
|
}));
|
|
25089
25290
|
if (!ok2) throw err2;
|
|
25090
|
-
this.
|
|
25291
|
+
this._emitStandardized("setContent", "content-set", { id, contentType, contentLength: buffer.length }, id);
|
|
25091
25292
|
return updatedData;
|
|
25092
25293
|
}
|
|
25093
25294
|
/**
|
|
@@ -25116,7 +25317,7 @@ ${errorDetails}`,
|
|
|
25116
25317
|
}
|
|
25117
25318
|
const buffer = Buffer.from(await response.Body.transformToByteArray());
|
|
25118
25319
|
const contentType = response.ContentType || null;
|
|
25119
|
-
this.
|
|
25320
|
+
this._emitStandardized("content", "content-fetched", { id, contentLength: buffer.length, contentType }, id);
|
|
25120
25321
|
return {
|
|
25121
25322
|
buffer,
|
|
25122
25323
|
contentType
|
|
@@ -25148,7 +25349,7 @@ ${errorDetails}`,
|
|
|
25148
25349
|
metadata: existingMetadata
|
|
25149
25350
|
}));
|
|
25150
25351
|
if (!ok2) throw err2;
|
|
25151
|
-
this.
|
|
25352
|
+
this._emitStandardized("deleteContent", "content-deleted", id, id);
|
|
25152
25353
|
return response;
|
|
25153
25354
|
}
|
|
25154
25355
|
/**
|
|
@@ -25460,7 +25661,7 @@ ${errorDetails}`,
|
|
|
25460
25661
|
const data = await this.get(id);
|
|
25461
25662
|
data._partition = partitionName;
|
|
25462
25663
|
data._partitionValues = partitionValues;
|
|
25463
|
-
this.
|
|
25664
|
+
this._emitStandardized("getFromPartition", "partition-fetched", data, data.id);
|
|
25464
25665
|
return data;
|
|
25465
25666
|
}
|
|
25466
25667
|
/**
|
|
@@ -25709,6 +25910,123 @@ ${errorDetails}`,
|
|
|
25709
25910
|
}
|
|
25710
25911
|
return out;
|
|
25711
25912
|
}
|
|
25913
|
+
// ============================================================================
|
|
25914
|
+
// STATE MACHINE METHODS
|
|
25915
|
+
// ============================================================================
|
|
25916
|
+
/**
|
|
25917
|
+
* Send an event to trigger a state transition
|
|
25918
|
+
* @param {string} id - Entity ID
|
|
25919
|
+
* @param {string} event - Event name
|
|
25920
|
+
* @param {Object} [eventData] - Event data
|
|
25921
|
+
* @returns {Promise<Object>} Transition result
|
|
25922
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25923
|
+
* @example
|
|
25924
|
+
* await orders.state('order-123', 'CONFIRM', { confirmedBy: 'user-456' });
|
|
25925
|
+
*/
|
|
25926
|
+
async state(id, event, eventData) {
|
|
25927
|
+
if (!this._stateMachine) {
|
|
25928
|
+
throw new Error(
|
|
25929
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25930
|
+
);
|
|
25931
|
+
}
|
|
25932
|
+
return this._stateMachine.send(id, event, eventData);
|
|
25933
|
+
}
|
|
25934
|
+
/**
|
|
25935
|
+
* Get current state of an entity
|
|
25936
|
+
* @param {string} id - Entity ID
|
|
25937
|
+
* @returns {Promise<string>} Current state
|
|
25938
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25939
|
+
* @example
|
|
25940
|
+
* const currentState = await orders.getState('order-123');
|
|
25941
|
+
*/
|
|
25942
|
+
async getState(id) {
|
|
25943
|
+
if (!this._stateMachine) {
|
|
25944
|
+
throw new Error(
|
|
25945
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25946
|
+
);
|
|
25947
|
+
}
|
|
25948
|
+
return this._stateMachine.getState(id);
|
|
25949
|
+
}
|
|
25950
|
+
/**
|
|
25951
|
+
* Check if a transition is valid
|
|
25952
|
+
* @param {string} id - Entity ID
|
|
25953
|
+
* @param {string} event - Event name
|
|
25954
|
+
* @returns {Promise<boolean>} True if transition is valid
|
|
25955
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25956
|
+
* @example
|
|
25957
|
+
* const canConfirm = await orders.canTransition('order-123', 'CONFIRM');
|
|
25958
|
+
*/
|
|
25959
|
+
async canTransition(id, event) {
|
|
25960
|
+
if (!this._stateMachine) {
|
|
25961
|
+
throw new Error(
|
|
25962
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25963
|
+
);
|
|
25964
|
+
}
|
|
25965
|
+
return this._stateMachine.canTransition(id, event);
|
|
25966
|
+
}
|
|
25967
|
+
/**
|
|
25968
|
+
* Get all valid events for the current state
|
|
25969
|
+
* @param {string} id - Entity ID
|
|
25970
|
+
* @returns {Promise<Array<string>>} Array of valid event names
|
|
25971
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25972
|
+
* @example
|
|
25973
|
+
* const events = await orders.getValidEvents('order-123');
|
|
25974
|
+
* // Returns: ['SHIP', 'CANCEL']
|
|
25975
|
+
*/
|
|
25976
|
+
async getValidEvents(id) {
|
|
25977
|
+
if (!this._stateMachine) {
|
|
25978
|
+
throw new Error(
|
|
25979
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25980
|
+
);
|
|
25981
|
+
}
|
|
25982
|
+
return this._stateMachine.getValidEvents(id);
|
|
25983
|
+
}
|
|
25984
|
+
/**
|
|
25985
|
+
* Initialize entity with initial state
|
|
25986
|
+
* @param {string} id - Entity ID
|
|
25987
|
+
* @param {Object} [context] - Initial context data
|
|
25988
|
+
* @returns {Promise<void>}
|
|
25989
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25990
|
+
* @example
|
|
25991
|
+
* await orders.initializeState('order-456', { customerId: 'user-123' });
|
|
25992
|
+
*/
|
|
25993
|
+
async initializeState(id, context) {
|
|
25994
|
+
if (!this._stateMachine) {
|
|
25995
|
+
throw new Error(
|
|
25996
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25997
|
+
);
|
|
25998
|
+
}
|
|
25999
|
+
return this._stateMachine.initializeEntity(id, context);
|
|
26000
|
+
}
|
|
26001
|
+
/**
|
|
26002
|
+
* Get transition history for an entity
|
|
26003
|
+
* @param {string} id - Entity ID
|
|
26004
|
+
* @param {Object} [options] - Query options
|
|
26005
|
+
* @param {number} [options.limit=100] - Maximum number of transitions
|
|
26006
|
+
* @param {Date} [options.fromDate] - Filter from date
|
|
26007
|
+
* @param {Date} [options.toDate] - Filter to date
|
|
26008
|
+
* @returns {Promise<Array<Object>>} Transition history
|
|
26009
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
26010
|
+
* @example
|
|
26011
|
+
* const history = await orders.getStateHistory('order-123', { limit: 50 });
|
|
26012
|
+
*/
|
|
26013
|
+
async getStateHistory(id, options) {
|
|
26014
|
+
if (!this._stateMachine) {
|
|
26015
|
+
throw new Error(
|
|
26016
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
26017
|
+
);
|
|
26018
|
+
}
|
|
26019
|
+
return this._stateMachine.getTransitionHistory(id, options);
|
|
26020
|
+
}
|
|
26021
|
+
/**
|
|
26022
|
+
* Internal method to attach state machine instance
|
|
26023
|
+
* This is called by StateMachinePlugin during initialization
|
|
26024
|
+
* @private
|
|
26025
|
+
* @param {Object} stateMachine - State machine instance
|
|
26026
|
+
*/
|
|
26027
|
+
_attachStateMachine(stateMachine) {
|
|
26028
|
+
this._stateMachine = stateMachine;
|
|
26029
|
+
}
|
|
25712
26030
|
}
|
|
25713
26031
|
function validateResourceConfig(config) {
|
|
25714
26032
|
const errors = [];
|
|
@@ -25869,7 +26187,7 @@ class Database extends EventEmitter {
|
|
|
25869
26187
|
})();
|
|
25870
26188
|
this.version = "1";
|
|
25871
26189
|
this.s3dbVersion = (() => {
|
|
25872
|
-
const [ok, err, version] = tryFn(() => true ? "13.
|
|
26190
|
+
const [ok, err, version] = tryFn(() => true ? "13.2.2" : "latest");
|
|
25873
26191
|
return ok ? version : "latest";
|
|
25874
26192
|
})();
|
|
25875
26193
|
this._resourcesMap = {};
|
|
@@ -26039,12 +26357,12 @@ class Database extends EventEmitter {
|
|
|
26039
26357
|
}
|
|
26040
26358
|
}
|
|
26041
26359
|
if (definitionChanges.length > 0) {
|
|
26042
|
-
this.emit("
|
|
26360
|
+
this.emit("db:resource-definitions-changed", {
|
|
26043
26361
|
changes: definitionChanges,
|
|
26044
26362
|
metadata: this.savedMetadata
|
|
26045
26363
|
});
|
|
26046
26364
|
}
|
|
26047
|
-
this.emit("connected", /* @__PURE__ */ new Date());
|
|
26365
|
+
this.emit("db:connected", /* @__PURE__ */ new Date());
|
|
26048
26366
|
}
|
|
26049
26367
|
/**
|
|
26050
26368
|
* Detect changes in resource definitions compared to saved metadata
|
|
@@ -26258,7 +26576,7 @@ class Database extends EventEmitter {
|
|
|
26258
26576
|
if (index > -1) {
|
|
26259
26577
|
this.pluginList.splice(index, 1);
|
|
26260
26578
|
}
|
|
26261
|
-
this.emit("plugin
|
|
26579
|
+
this.emit("db:plugin:uninstalled", { name: pluginName, plugin });
|
|
26262
26580
|
}
|
|
26263
26581
|
async uploadMetadataFile() {
|
|
26264
26582
|
const metadata = {
|
|
@@ -26317,7 +26635,7 @@ class Database extends EventEmitter {
|
|
|
26317
26635
|
contentType: "application/json"
|
|
26318
26636
|
});
|
|
26319
26637
|
this.savedMetadata = metadata;
|
|
26320
|
-
this.emit("
|
|
26638
|
+
this.emit("db:metadata-uploaded", metadata);
|
|
26321
26639
|
}
|
|
26322
26640
|
blankMetadataStructure() {
|
|
26323
26641
|
return {
|
|
@@ -26574,7 +26892,7 @@ class Database extends EventEmitter {
|
|
|
26574
26892
|
body: JSON.stringify(metadata, null, 2),
|
|
26575
26893
|
contentType: "application/json"
|
|
26576
26894
|
});
|
|
26577
|
-
this.emit("
|
|
26895
|
+
this.emit("db:metadata-healed", { healingLog, metadata });
|
|
26578
26896
|
if (this.verbose) {
|
|
26579
26897
|
console.warn("S3DB: Successfully uploaded healed metadata");
|
|
26580
26898
|
}
|
|
@@ -26714,7 +27032,7 @@ class Database extends EventEmitter {
|
|
|
26714
27032
|
if (!existingVersionData || existingVersionData.hash !== newHash) {
|
|
26715
27033
|
await this.uploadMetadataFile();
|
|
26716
27034
|
}
|
|
26717
|
-
this.emit("
|
|
27035
|
+
this.emit("db:resource:updated", name);
|
|
26718
27036
|
return existingResource;
|
|
26719
27037
|
}
|
|
26720
27038
|
const existingMetadata = this.savedMetadata?.resources?.[name];
|
|
@@ -26751,7 +27069,7 @@ class Database extends EventEmitter {
|
|
|
26751
27069
|
this._applyMiddlewares(resource, middlewares);
|
|
26752
27070
|
}
|
|
26753
27071
|
await this.uploadMetadataFile();
|
|
26754
|
-
this.emit("
|
|
27072
|
+
this.emit("db:resource:created", name);
|
|
26755
27073
|
return resource;
|
|
26756
27074
|
}
|
|
26757
27075
|
/**
|
|
@@ -26915,7 +27233,7 @@ class Database extends EventEmitter {
|
|
|
26915
27233
|
if (this.client && typeof this.client.removeAllListeners === "function") {
|
|
26916
27234
|
this.client.removeAllListeners();
|
|
26917
27235
|
}
|
|
26918
|
-
await this.emit("disconnected", /* @__PURE__ */ new Date());
|
|
27236
|
+
await this.emit("db:disconnected", /* @__PURE__ */ new Date());
|
|
26919
27237
|
this.removeAllListeners();
|
|
26920
27238
|
if (this._exitListener && typeof process !== "undefined") {
|
|
26921
27239
|
process.off("exit", this._exitListener);
|
|
@@ -27027,7 +27345,7 @@ class Database extends EventEmitter {
|
|
|
27027
27345
|
for (const hook of hooks) {
|
|
27028
27346
|
const [ok, error] = await tryFn(() => hook({ database: this, ...context }));
|
|
27029
27347
|
if (!ok) {
|
|
27030
|
-
this.emit("
|
|
27348
|
+
this.emit("db:hook-error", { event, error, context });
|
|
27031
27349
|
if (this.strictHooks) {
|
|
27032
27350
|
throw new DatabaseError(`Hook execution failed for event '${event}': ${error.message}`, {
|
|
27033
27351
|
event,
|
|
@@ -28603,7 +28921,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28603
28921
|
if (this.config.verbose) {
|
|
28604
28922
|
console.warn(`[ReplicatorPlugin] Insert event failed for resource ${resource.name}: ${error.message}`);
|
|
28605
28923
|
}
|
|
28606
|
-
this.emit("error", { operation: "insert", error: error.message, resource: resource.name });
|
|
28924
|
+
this.emit("plg:replicator:error", { operation: "insert", error: error.message, resource: resource.name });
|
|
28607
28925
|
}
|
|
28608
28926
|
};
|
|
28609
28927
|
const updateHandler = async (data, beforeData) => {
|
|
@@ -28616,7 +28934,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28616
28934
|
if (this.config.verbose) {
|
|
28617
28935
|
console.warn(`[ReplicatorPlugin] Update event failed for resource ${resource.name}: ${error.message}`);
|
|
28618
28936
|
}
|
|
28619
|
-
this.emit("error", { operation: "update", error: error.message, resource: resource.name });
|
|
28937
|
+
this.emit("plg:replicator:error", { operation: "update", error: error.message, resource: resource.name });
|
|
28620
28938
|
}
|
|
28621
28939
|
};
|
|
28622
28940
|
const deleteHandler = async (data) => {
|
|
@@ -28627,7 +28945,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28627
28945
|
if (this.config.verbose) {
|
|
28628
28946
|
console.warn(`[ReplicatorPlugin] Delete event failed for resource ${resource.name}: ${error.message}`);
|
|
28629
28947
|
}
|
|
28630
|
-
this.emit("error", { operation: "delete", error: error.message, resource: resource.name });
|
|
28948
|
+
this.emit("plg:replicator:error", { operation: "delete", error: error.message, resource: resource.name });
|
|
28631
28949
|
}
|
|
28632
28950
|
};
|
|
28633
28951
|
this.eventHandlers.set(resource.name, {
|
|
@@ -28748,7 +29066,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28748
29066
|
if (this.config.verbose) {
|
|
28749
29067
|
console.warn(`[ReplicatorPlugin] Failed to log error for ${resourceName}: ${logError.message}`);
|
|
28750
29068
|
}
|
|
28751
|
-
this.emit("
|
|
29069
|
+
this.emit("plg:replicator:log-error", {
|
|
28752
29070
|
replicator: replicator.name || replicator.id,
|
|
28753
29071
|
resourceName,
|
|
28754
29072
|
operation,
|
|
@@ -28773,7 +29091,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28773
29091
|
() => replicator.replicate(resourceName, operation, data, recordId, beforeData),
|
|
28774
29092
|
this.config.maxRetries
|
|
28775
29093
|
);
|
|
28776
|
-
this.emit("replicated", {
|
|
29094
|
+
this.emit("plg:replicator:replicated", {
|
|
28777
29095
|
replicator: replicator.name || replicator.id,
|
|
28778
29096
|
resourceName,
|
|
28779
29097
|
operation,
|
|
@@ -28789,7 +29107,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28789
29107
|
if (this.config.verbose) {
|
|
28790
29108
|
console.warn(`[ReplicatorPlugin] Replication failed for ${replicator.name || replicator.id} on ${resourceName}: ${error.message}`);
|
|
28791
29109
|
}
|
|
28792
|
-
this.emit("
|
|
29110
|
+
this.emit("plg:replicator:error", {
|
|
28793
29111
|
replicator: replicator.name || replicator.id,
|
|
28794
29112
|
resourceName,
|
|
28795
29113
|
operation,
|
|
@@ -28821,7 +29139,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28821
29139
|
if (this.config.verbose) {
|
|
28822
29140
|
console.warn(`[ReplicatorPlugin] Replicator item processing failed for ${replicator.name || replicator.id} on ${item.resourceName}: ${err.message}`);
|
|
28823
29141
|
}
|
|
28824
|
-
this.emit("
|
|
29142
|
+
this.emit("plg:replicator:error", {
|
|
28825
29143
|
replicator: replicator.name || replicator.id,
|
|
28826
29144
|
resourceName: item.resourceName,
|
|
28827
29145
|
operation: item.operation,
|
|
@@ -28833,7 +29151,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28833
29151
|
}
|
|
28834
29152
|
return { success: false, error: err.message };
|
|
28835
29153
|
}
|
|
28836
|
-
this.emit("replicated", {
|
|
29154
|
+
this.emit("plg:replicator:replicated", {
|
|
28837
29155
|
replicator: replicator.name || replicator.id,
|
|
28838
29156
|
resourceName: item.resourceName,
|
|
28839
29157
|
operation: item.operation,
|
|
@@ -28849,7 +29167,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28849
29167
|
if (this.config.verbose) {
|
|
28850
29168
|
console.warn(`[ReplicatorPlugin] Wrapper processing failed for ${replicator.name || replicator.id} on ${item.resourceName}: ${wrapperError.message}`);
|
|
28851
29169
|
}
|
|
28852
|
-
this.emit("
|
|
29170
|
+
this.emit("plg:replicator:error", {
|
|
28853
29171
|
replicator: replicator.name || replicator.id,
|
|
28854
29172
|
resourceName: item.resourceName,
|
|
28855
29173
|
operation: item.operation,
|
|
@@ -28867,7 +29185,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28867
29185
|
async logReplicator(item) {
|
|
28868
29186
|
const logRes = this.replicatorLog || this.database.resources[normalizeResourceName(this.config.replicatorLogResource)];
|
|
28869
29187
|
if (!logRes) {
|
|
28870
|
-
this.emit("replicator
|
|
29188
|
+
this.emit("plg:replicator:log-failed", { error: "replicator log resource not found", item });
|
|
28871
29189
|
return;
|
|
28872
29190
|
}
|
|
28873
29191
|
const logItem = {
|
|
@@ -28885,7 +29203,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28885
29203
|
if (this.config.verbose) {
|
|
28886
29204
|
console.warn(`[ReplicatorPlugin] Failed to log replicator item: ${err.message}`);
|
|
28887
29205
|
}
|
|
28888
|
-
this.emit("replicator
|
|
29206
|
+
this.emit("plg:replicator:log-failed", { error: err, item });
|
|
28889
29207
|
}
|
|
28890
29208
|
}
|
|
28891
29209
|
async updateReplicatorLog(logId, updates) {
|
|
@@ -28897,7 +29215,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28897
29215
|
});
|
|
28898
29216
|
});
|
|
28899
29217
|
if (!ok) {
|
|
28900
|
-
this.emit("replicator
|
|
29218
|
+
this.emit("plg:replicator:update-log-failed", { error: err.message, logId, updates });
|
|
28901
29219
|
}
|
|
28902
29220
|
}
|
|
28903
29221
|
// Utility methods
|
|
@@ -28981,7 +29299,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28981
29299
|
for (const resourceName in this.database.resources) {
|
|
28982
29300
|
if (normalizeResourceName(resourceName) === normalizeResourceName("plg_replicator_logs")) continue;
|
|
28983
29301
|
if (replicator.shouldReplicateResource(resourceName)) {
|
|
28984
|
-
this.emit("replicator
|
|
29302
|
+
this.emit("plg:replicator:sync-resource", { resourceName, replicatorId });
|
|
28985
29303
|
const resource = this.database.resources[resourceName];
|
|
28986
29304
|
let offset = 0;
|
|
28987
29305
|
const pageSize = this.config.batchSize || 100;
|
|
@@ -28997,7 +29315,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28997
29315
|
}
|
|
28998
29316
|
}
|
|
28999
29317
|
}
|
|
29000
|
-
this.emit("replicator
|
|
29318
|
+
this.emit("plg:replicator:sync-completed", { replicatorId, stats: this.stats });
|
|
29001
29319
|
}
|
|
29002
29320
|
async stop() {
|
|
29003
29321
|
const [ok, error] = await tryFn(async () => {
|
|
@@ -29012,7 +29330,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
29012
29330
|
if (this.config.verbose) {
|
|
29013
29331
|
console.warn(`[ReplicatorPlugin] Failed to stop replicator ${replicator.name || replicator.id}: ${replicatorError.message}`);
|
|
29014
29332
|
}
|
|
29015
|
-
this.emit("
|
|
29333
|
+
this.emit("plg:replicator:stop-error", {
|
|
29016
29334
|
replicator: replicator.name || replicator.id || "unknown",
|
|
29017
29335
|
driver: replicator.driver || "unknown",
|
|
29018
29336
|
error: replicatorError.message
|
|
@@ -29043,7 +29361,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
29043
29361
|
if (this.config.verbose) {
|
|
29044
29362
|
console.warn(`[ReplicatorPlugin] Failed to stop plugin: ${error.message}`);
|
|
29045
29363
|
}
|
|
29046
|
-
this.emit("
|
|
29364
|
+
this.emit("plg:replicator:plugin-stop-error", {
|
|
29047
29365
|
error: error.message
|
|
29048
29366
|
});
|
|
29049
29367
|
}
|
|
@@ -29200,7 +29518,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29200
29518
|
if (this.config.verbose) {
|
|
29201
29519
|
console.log(`[S3QueuePlugin] Started ${concurrency} workers`);
|
|
29202
29520
|
}
|
|
29203
|
-
this.emit("workers
|
|
29521
|
+
this.emit("plg:s3-queue:workers-started", { concurrency, workerId: this.workerId });
|
|
29204
29522
|
}
|
|
29205
29523
|
async stopProcessing() {
|
|
29206
29524
|
if (!this.isRunning) return;
|
|
@@ -29215,7 +29533,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29215
29533
|
if (this.config.verbose) {
|
|
29216
29534
|
console.log("[S3QueuePlugin] Stopped all workers");
|
|
29217
29535
|
}
|
|
29218
|
-
this.emit("workers
|
|
29536
|
+
this.emit("plg:s3-queue:workers-stopped", { workerId: this.workerId });
|
|
29219
29537
|
}
|
|
29220
29538
|
createWorker(handler, workerIndex) {
|
|
29221
29539
|
return (async () => {
|
|
@@ -29383,7 +29701,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29383
29701
|
});
|
|
29384
29702
|
await this.completeMessage(message.queueId, result);
|
|
29385
29703
|
const duration = Date.now() - startTime;
|
|
29386
|
-
this.emit("message
|
|
29704
|
+
this.emit("plg:s3-queue:message-completed", {
|
|
29387
29705
|
queueId: message.queueId,
|
|
29388
29706
|
originalId: message.record.id,
|
|
29389
29707
|
duration,
|
|
@@ -29396,7 +29714,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29396
29714
|
const shouldRetry = message.attempts < message.maxAttempts;
|
|
29397
29715
|
if (shouldRetry) {
|
|
29398
29716
|
await this.retryMessage(message.queueId, message.attempts, error.message);
|
|
29399
|
-
this.emit("message
|
|
29717
|
+
this.emit("plg:s3-queue:message-retry", {
|
|
29400
29718
|
queueId: message.queueId,
|
|
29401
29719
|
originalId: message.record.id,
|
|
29402
29720
|
attempts: message.attempts,
|
|
@@ -29404,7 +29722,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29404
29722
|
});
|
|
29405
29723
|
} else {
|
|
29406
29724
|
await this.moveToDeadLetter(message.queueId, message.record, error.message);
|
|
29407
|
-
this.emit("message
|
|
29725
|
+
this.emit("plg:s3-queue:message-dead", {
|
|
29408
29726
|
queueId: message.queueId,
|
|
29409
29727
|
originalId: message.record.id,
|
|
29410
29728
|
error: error.message
|
|
@@ -29636,7 +29954,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29636
29954
|
});
|
|
29637
29955
|
}
|
|
29638
29956
|
await this._startScheduling();
|
|
29639
|
-
this.emit("initialized", { jobs: this.jobs.size });
|
|
29957
|
+
this.emit("db:plugin:initialized", { jobs: this.jobs.size });
|
|
29640
29958
|
}
|
|
29641
29959
|
async _createJobHistoryResource() {
|
|
29642
29960
|
const [ok] = await tryFn(() => this.database.createResource({
|
|
@@ -29774,7 +30092,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29774
30092
|
if (this.config.onJobStart) {
|
|
29775
30093
|
await this._executeHook(this.config.onJobStart, jobName, context);
|
|
29776
30094
|
}
|
|
29777
|
-
this.emit("
|
|
30095
|
+
this.emit("plg:scheduler:job-start", { jobName, executionId, startTime });
|
|
29778
30096
|
let attempt = 0;
|
|
29779
30097
|
let lastError = null;
|
|
29780
30098
|
let result = null;
|
|
@@ -29841,7 +30159,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29841
30159
|
} else if (status !== "success" && this.config.onJobError) {
|
|
29842
30160
|
await this._executeHook(this.config.onJobError, jobName, lastError, attempt);
|
|
29843
30161
|
}
|
|
29844
|
-
this.emit("
|
|
30162
|
+
this.emit("plg:scheduler:job-complete", {
|
|
29845
30163
|
jobName,
|
|
29846
30164
|
executionId,
|
|
29847
30165
|
status,
|
|
@@ -29927,7 +30245,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29927
30245
|
}
|
|
29928
30246
|
job.enabled = true;
|
|
29929
30247
|
this._scheduleNextExecution(jobName);
|
|
29930
|
-
this.emit("
|
|
30248
|
+
this.emit("plg:scheduler:job-enabled", { jobName });
|
|
29931
30249
|
}
|
|
29932
30250
|
/**
|
|
29933
30251
|
* Disable a job
|
|
@@ -29948,7 +30266,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29948
30266
|
clearTimeout(timer);
|
|
29949
30267
|
this.timers.delete(jobName);
|
|
29950
30268
|
}
|
|
29951
|
-
this.emit("
|
|
30269
|
+
this.emit("plg:scheduler:job-disabled", { jobName });
|
|
29952
30270
|
}
|
|
29953
30271
|
/**
|
|
29954
30272
|
* Get job status and statistics
|
|
@@ -30086,7 +30404,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
30086
30404
|
if (job.enabled) {
|
|
30087
30405
|
this._scheduleNextExecution(jobName);
|
|
30088
30406
|
}
|
|
30089
|
-
this.emit("
|
|
30407
|
+
this.emit("plg:scheduler:job-added", { jobName });
|
|
30090
30408
|
}
|
|
30091
30409
|
/**
|
|
30092
30410
|
* Remove a job
|
|
@@ -30109,7 +30427,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
30109
30427
|
this.jobs.delete(jobName);
|
|
30110
30428
|
this.statistics.delete(jobName);
|
|
30111
30429
|
this.activeJobs.delete(jobName);
|
|
30112
|
-
this.emit("
|
|
30430
|
+
this.emit("plg:scheduler:job-removed", { jobName });
|
|
30113
30431
|
}
|
|
30114
30432
|
/**
|
|
30115
30433
|
* Get plugin instance by name (for job actions that need other plugins)
|
|
@@ -30150,9 +30468,14 @@ class SchedulerPlugin extends Plugin {
|
|
|
30150
30468
|
}
|
|
30151
30469
|
}
|
|
30152
30470
|
|
|
30471
|
+
var scheduler_plugin = /*#__PURE__*/Object.freeze({
|
|
30472
|
+
__proto__: null,
|
|
30473
|
+
SchedulerPlugin: SchedulerPlugin
|
|
30474
|
+
});
|
|
30475
|
+
|
|
30153
30476
|
class StateMachineError extends S3dbError {
|
|
30154
30477
|
constructor(message, details = {}) {
|
|
30155
|
-
const { currentState, targetState, resourceName, operation = "unknown", ...rest } = details;
|
|
30478
|
+
const { currentState, targetState, resourceName, operation = "unknown", retriable, ...rest } = details;
|
|
30156
30479
|
let description = details.description;
|
|
30157
30480
|
if (!description) {
|
|
30158
30481
|
description = `
|
|
@@ -30177,6 +30500,158 @@ Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/state-mach
|
|
|
30177
30500
|
`.trim();
|
|
30178
30501
|
}
|
|
30179
30502
|
super(message, { ...rest, currentState, targetState, resourceName, operation, description });
|
|
30503
|
+
if (retriable !== void 0) {
|
|
30504
|
+
this.retriable = retriable;
|
|
30505
|
+
}
|
|
30506
|
+
}
|
|
30507
|
+
}
|
|
30508
|
+
|
|
30509
|
+
const RETRIABLE = "RETRIABLE";
|
|
30510
|
+
const NON_RETRIABLE = "NON_RETRIABLE";
|
|
30511
|
+
const RETRIABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
30512
|
+
"ECONNREFUSED",
|
|
30513
|
+
"ETIMEDOUT",
|
|
30514
|
+
"ECONNRESET",
|
|
30515
|
+
"EPIPE",
|
|
30516
|
+
"ENOTFOUND",
|
|
30517
|
+
"NetworkError",
|
|
30518
|
+
"NETWORK_ERROR",
|
|
30519
|
+
"TimeoutError",
|
|
30520
|
+
"TIMEOUT"
|
|
30521
|
+
]);
|
|
30522
|
+
const RETRIABLE_AWS_CODES = /* @__PURE__ */ new Set([
|
|
30523
|
+
"ThrottlingException",
|
|
30524
|
+
"TooManyRequestsException",
|
|
30525
|
+
"RequestLimitExceeded",
|
|
30526
|
+
"ProvisionedThroughputExceededException",
|
|
30527
|
+
"RequestThrottledException",
|
|
30528
|
+
"SlowDown",
|
|
30529
|
+
"ServiceUnavailable"
|
|
30530
|
+
]);
|
|
30531
|
+
const RETRIABLE_AWS_CONFLICTS = /* @__PURE__ */ new Set([
|
|
30532
|
+
"ConditionalCheckFailedException",
|
|
30533
|
+
"TransactionConflictException"
|
|
30534
|
+
]);
|
|
30535
|
+
const RETRIABLE_STATUS_CODES = /* @__PURE__ */ new Set([
|
|
30536
|
+
429,
|
|
30537
|
+
// Too Many Requests
|
|
30538
|
+
500,
|
|
30539
|
+
// Internal Server Error
|
|
30540
|
+
502,
|
|
30541
|
+
// Bad Gateway
|
|
30542
|
+
503,
|
|
30543
|
+
// Service Unavailable
|
|
30544
|
+
504,
|
|
30545
|
+
// Gateway Timeout
|
|
30546
|
+
507,
|
|
30547
|
+
// Insufficient Storage
|
|
30548
|
+
509
|
|
30549
|
+
// Bandwidth Limit Exceeded
|
|
30550
|
+
]);
|
|
30551
|
+
const NON_RETRIABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
30552
|
+
"ValidationError",
|
|
30553
|
+
"StateMachineError",
|
|
30554
|
+
"SchemaError",
|
|
30555
|
+
"AuthenticationError",
|
|
30556
|
+
"PermissionError",
|
|
30557
|
+
"BusinessLogicError",
|
|
30558
|
+
"InvalidStateTransition"
|
|
30559
|
+
]);
|
|
30560
|
+
const NON_RETRIABLE_STATUS_CODES = /* @__PURE__ */ new Set([
|
|
30561
|
+
400,
|
|
30562
|
+
// Bad Request
|
|
30563
|
+
401,
|
|
30564
|
+
// Unauthorized
|
|
30565
|
+
403,
|
|
30566
|
+
// Forbidden
|
|
30567
|
+
404,
|
|
30568
|
+
// Not Found
|
|
30569
|
+
405,
|
|
30570
|
+
// Method Not Allowed
|
|
30571
|
+
406,
|
|
30572
|
+
// Not Acceptable
|
|
30573
|
+
409,
|
|
30574
|
+
// Conflict
|
|
30575
|
+
410,
|
|
30576
|
+
// Gone
|
|
30577
|
+
422
|
|
30578
|
+
// Unprocessable Entity
|
|
30579
|
+
]);
|
|
30580
|
+
class ErrorClassifier {
|
|
30581
|
+
/**
|
|
30582
|
+
* Classify an error as RETRIABLE or NON_RETRIABLE
|
|
30583
|
+
*
|
|
30584
|
+
* @param {Error} error - The error to classify
|
|
30585
|
+
* @param {Object} options - Classification options
|
|
30586
|
+
* @param {Array<string>} options.retryableErrors - Custom retriable error names/codes
|
|
30587
|
+
* @param {Array<string>} options.nonRetriableErrors - Custom non-retriable error names/codes
|
|
30588
|
+
* @returns {string} 'RETRIABLE' or 'NON_RETRIABLE'
|
|
30589
|
+
*/
|
|
30590
|
+
static classify(error, options = {}) {
|
|
30591
|
+
if (!error) return NON_RETRIABLE;
|
|
30592
|
+
const {
|
|
30593
|
+
retryableErrors = [],
|
|
30594
|
+
nonRetriableErrors = []
|
|
30595
|
+
} = options;
|
|
30596
|
+
if (retryableErrors.length > 0) {
|
|
30597
|
+
const isCustomRetriable = retryableErrors.some(
|
|
30598
|
+
(errType) => error.code === errType || error.name === errType || error.message?.includes(errType)
|
|
30599
|
+
);
|
|
30600
|
+
if (isCustomRetriable) return RETRIABLE;
|
|
30601
|
+
}
|
|
30602
|
+
if (nonRetriableErrors.length > 0) {
|
|
30603
|
+
const isCustomNonRetriable = nonRetriableErrors.some(
|
|
30604
|
+
(errType) => error.code === errType || error.name === errType || error.message?.includes(errType)
|
|
30605
|
+
);
|
|
30606
|
+
if (isCustomNonRetriable) return NON_RETRIABLE;
|
|
30607
|
+
}
|
|
30608
|
+
if (error.retriable === false) return NON_RETRIABLE;
|
|
30609
|
+
if (error.retriable === true) return RETRIABLE;
|
|
30610
|
+
if (NON_RETRIABLE_ERROR_NAMES.has(error.name)) {
|
|
30611
|
+
return NON_RETRIABLE;
|
|
30612
|
+
}
|
|
30613
|
+
if (error.statusCode && NON_RETRIABLE_STATUS_CODES.has(error.statusCode)) {
|
|
30614
|
+
return NON_RETRIABLE;
|
|
30615
|
+
}
|
|
30616
|
+
if (error.code && RETRIABLE_NETWORK_CODES.has(error.code)) {
|
|
30617
|
+
return RETRIABLE;
|
|
30618
|
+
}
|
|
30619
|
+
if (error.code && RETRIABLE_AWS_CODES.has(error.code)) {
|
|
30620
|
+
return RETRIABLE;
|
|
30621
|
+
}
|
|
30622
|
+
if (error.code && RETRIABLE_AWS_CONFLICTS.has(error.code)) {
|
|
30623
|
+
return RETRIABLE;
|
|
30624
|
+
}
|
|
30625
|
+
if (error.statusCode && RETRIABLE_STATUS_CODES.has(error.statusCode)) {
|
|
30626
|
+
return RETRIABLE;
|
|
30627
|
+
}
|
|
30628
|
+
if (error.message && typeof error.message === "string") {
|
|
30629
|
+
const lowerMessage = error.message.toLowerCase();
|
|
30630
|
+
if (lowerMessage.includes("timeout") || lowerMessage.includes("timed out") || lowerMessage.includes("network") || lowerMessage.includes("connection")) {
|
|
30631
|
+
return RETRIABLE;
|
|
30632
|
+
}
|
|
30633
|
+
}
|
|
30634
|
+
return RETRIABLE;
|
|
30635
|
+
}
|
|
30636
|
+
/**
|
|
30637
|
+
* Check if an error is retriable
|
|
30638
|
+
*
|
|
30639
|
+
* @param {Error} error - The error to check
|
|
30640
|
+
* @param {Object} options - Classification options
|
|
30641
|
+
* @returns {boolean} true if retriable
|
|
30642
|
+
*/
|
|
30643
|
+
static isRetriable(error, options = {}) {
|
|
30644
|
+
return this.classify(error, options) === RETRIABLE;
|
|
30645
|
+
}
|
|
30646
|
+
/**
|
|
30647
|
+
* Check if an error is non-retriable
|
|
30648
|
+
*
|
|
30649
|
+
* @param {Error} error - The error to check
|
|
30650
|
+
* @param {Object} options - Classification options
|
|
30651
|
+
* @returns {boolean} true if non-retriable
|
|
30652
|
+
*/
|
|
30653
|
+
static isNonRetriable(error, options = {}) {
|
|
30654
|
+
return this.classify(error, options) === NON_RETRIABLE;
|
|
30180
30655
|
}
|
|
30181
30656
|
}
|
|
30182
30657
|
|
|
@@ -30197,11 +30672,23 @@ class StateMachinePlugin extends Plugin {
|
|
|
30197
30672
|
workerId: options.workerId || "default",
|
|
30198
30673
|
lockTimeout: options.lockTimeout || 1e3,
|
|
30199
30674
|
// Wait up to 1s for lock
|
|
30200
|
-
lockTTL: options.lockTTL || 5
|
|
30675
|
+
lockTTL: options.lockTTL || 5,
|
|
30201
30676
|
// Lock expires after 5s (prevent deadlock)
|
|
30677
|
+
// Global retry configuration for action execution
|
|
30678
|
+
retryConfig: options.retryConfig || null,
|
|
30679
|
+
// Trigger system configuration
|
|
30680
|
+
enableScheduler: options.enableScheduler || false,
|
|
30681
|
+
schedulerConfig: options.schedulerConfig || {},
|
|
30682
|
+
enableDateTriggers: options.enableDateTriggers !== false,
|
|
30683
|
+
enableFunctionTriggers: options.enableFunctionTriggers !== false,
|
|
30684
|
+
enableEventTriggers: options.enableEventTriggers !== false,
|
|
30685
|
+
triggerCheckInterval: options.triggerCheckInterval || 6e4
|
|
30686
|
+
// Check triggers every 60s by default
|
|
30202
30687
|
};
|
|
30203
30688
|
this.database = null;
|
|
30204
30689
|
this.machines = /* @__PURE__ */ new Map();
|
|
30690
|
+
this.triggerIntervals = [];
|
|
30691
|
+
this.schedulerPlugin = null;
|
|
30205
30692
|
this._validateConfiguration();
|
|
30206
30693
|
}
|
|
30207
30694
|
_validateConfiguration() {
|
|
@@ -30250,7 +30737,9 @@ class StateMachinePlugin extends Plugin {
|
|
|
30250
30737
|
// entityId -> currentState
|
|
30251
30738
|
});
|
|
30252
30739
|
}
|
|
30253
|
-
|
|
30740
|
+
await this._attachStateMachinesToResources();
|
|
30741
|
+
await this._setupTriggers();
|
|
30742
|
+
this.emit("db:plugin:initialized", { machines: Array.from(this.machines.keys()) });
|
|
30254
30743
|
}
|
|
30255
30744
|
async _createStateResources() {
|
|
30256
30745
|
const [logOk] = await tryFn(() => this.database.createResource({
|
|
@@ -30281,6 +30770,8 @@ class StateMachinePlugin extends Plugin {
|
|
|
30281
30770
|
currentState: "string|required",
|
|
30282
30771
|
context: "json|default:{}",
|
|
30283
30772
|
lastTransition: "string|default:null",
|
|
30773
|
+
triggerCounts: "json|default:{}",
|
|
30774
|
+
// Track trigger execution counts
|
|
30284
30775
|
updatedAt: "string|required"
|
|
30285
30776
|
},
|
|
30286
30777
|
behavior: "body-overflow"
|
|
@@ -30344,7 +30835,7 @@ class StateMachinePlugin extends Plugin {
|
|
|
30344
30835
|
if (targetStateConfig && targetStateConfig.entry) {
|
|
30345
30836
|
await this._executeAction(targetStateConfig.entry, context, event, machineId, entityId);
|
|
30346
30837
|
}
|
|
30347
|
-
this.emit("transition", {
|
|
30838
|
+
this.emit("plg:state-machine:transition", {
|
|
30348
30839
|
machineId,
|
|
30349
30840
|
entityId,
|
|
30350
30841
|
from: currentState,
|
|
@@ -30370,14 +30861,97 @@ class StateMachinePlugin extends Plugin {
|
|
|
30370
30861
|
}
|
|
30371
30862
|
return;
|
|
30372
30863
|
}
|
|
30373
|
-
const
|
|
30374
|
-
|
|
30375
|
-
|
|
30376
|
-
|
|
30377
|
-
|
|
30378
|
-
|
|
30864
|
+
const machine = this.machines.get(machineId);
|
|
30865
|
+
const currentState = await this.getState(machineId, entityId);
|
|
30866
|
+
const stateConfig = machine?.config?.states?.[currentState];
|
|
30867
|
+
const retryConfig = {
|
|
30868
|
+
...this.config.retryConfig || {},
|
|
30869
|
+
...machine?.config?.retryConfig || {},
|
|
30870
|
+
...stateConfig?.retryConfig || {}
|
|
30871
|
+
};
|
|
30872
|
+
const maxAttempts = retryConfig.maxAttempts ?? 0;
|
|
30873
|
+
const retryEnabled = maxAttempts > 0;
|
|
30874
|
+
let attempt = 0;
|
|
30875
|
+
while (attempt <= maxAttempts) {
|
|
30876
|
+
try {
|
|
30877
|
+
const result = await action(context, event, { database: this.database, machineId, entityId });
|
|
30878
|
+
if (attempt > 0) {
|
|
30879
|
+
this.emit("plg:state-machine:action-retry-success", {
|
|
30880
|
+
machineId,
|
|
30881
|
+
entityId,
|
|
30882
|
+
action: actionName,
|
|
30883
|
+
attempts: attempt + 1,
|
|
30884
|
+
state: currentState
|
|
30885
|
+
});
|
|
30886
|
+
if (this.config.verbose) {
|
|
30887
|
+
console.log(`[StateMachinePlugin] Action '${actionName}' succeeded after ${attempt + 1} attempts`);
|
|
30888
|
+
}
|
|
30889
|
+
}
|
|
30890
|
+
return result;
|
|
30891
|
+
} catch (error) {
|
|
30892
|
+
if (!retryEnabled) {
|
|
30893
|
+
if (this.config.verbose) {
|
|
30894
|
+
console.error(`[StateMachinePlugin] Action '${actionName}' failed:`, error.message);
|
|
30895
|
+
}
|
|
30896
|
+
this.emit("plg:state-machine:action-error", { actionName, error: error.message, machineId, entityId });
|
|
30897
|
+
return;
|
|
30898
|
+
}
|
|
30899
|
+
const classification = ErrorClassifier.classify(error, {
|
|
30900
|
+
retryableErrors: retryConfig.retryableErrors,
|
|
30901
|
+
nonRetriableErrors: retryConfig.nonRetriableErrors
|
|
30902
|
+
});
|
|
30903
|
+
if (classification === "NON_RETRIABLE") {
|
|
30904
|
+
this.emit("plg:state-machine:action-error-non-retriable", {
|
|
30905
|
+
machineId,
|
|
30906
|
+
entityId,
|
|
30907
|
+
action: actionName,
|
|
30908
|
+
error: error.message,
|
|
30909
|
+
state: currentState
|
|
30910
|
+
});
|
|
30911
|
+
if (this.config.verbose) {
|
|
30912
|
+
console.error(`[StateMachinePlugin] Action '${actionName}' failed with non-retriable error:`, error.message);
|
|
30913
|
+
}
|
|
30914
|
+
throw error;
|
|
30915
|
+
}
|
|
30916
|
+
if (attempt >= maxAttempts) {
|
|
30917
|
+
this.emit("plg:state-machine:action-retry-exhausted", {
|
|
30918
|
+
machineId,
|
|
30919
|
+
entityId,
|
|
30920
|
+
action: actionName,
|
|
30921
|
+
attempts: attempt + 1,
|
|
30922
|
+
error: error.message,
|
|
30923
|
+
state: currentState
|
|
30924
|
+
});
|
|
30925
|
+
if (this.config.verbose) {
|
|
30926
|
+
console.error(`[StateMachinePlugin] Action '${actionName}' failed after ${attempt + 1} attempts:`, error.message);
|
|
30927
|
+
}
|
|
30928
|
+
throw error;
|
|
30929
|
+
}
|
|
30930
|
+
attempt++;
|
|
30931
|
+
const delay = this._calculateBackoff(attempt, retryConfig);
|
|
30932
|
+
if (retryConfig.onRetry) {
|
|
30933
|
+
try {
|
|
30934
|
+
await retryConfig.onRetry(attempt, error, context);
|
|
30935
|
+
} catch (hookError) {
|
|
30936
|
+
if (this.config.verbose) {
|
|
30937
|
+
console.warn(`[StateMachinePlugin] onRetry hook failed:`, hookError.message);
|
|
30938
|
+
}
|
|
30939
|
+
}
|
|
30940
|
+
}
|
|
30941
|
+
this.emit("plg:state-machine:action-retry-attempt", {
|
|
30942
|
+
machineId,
|
|
30943
|
+
entityId,
|
|
30944
|
+
action: actionName,
|
|
30945
|
+
attempt,
|
|
30946
|
+
delay,
|
|
30947
|
+
error: error.message,
|
|
30948
|
+
state: currentState
|
|
30949
|
+
});
|
|
30950
|
+
if (this.config.verbose) {
|
|
30951
|
+
console.warn(`[StateMachinePlugin] Action '${actionName}' failed (attempt ${attempt + 1}/${maxAttempts + 1}), retrying in ${delay}ms:`, error.message);
|
|
30952
|
+
}
|
|
30953
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
30379
30954
|
}
|
|
30380
|
-
this.emit("action_error", { actionName, error: error.message, machineId, entityId });
|
|
30381
30955
|
}
|
|
30382
30956
|
}
|
|
30383
30957
|
async _transition(machineId, entityId, fromState, toState, event, context) {
|
|
@@ -30475,6 +31049,27 @@ class StateMachinePlugin extends Plugin {
|
|
|
30475
31049
|
console.warn(`[StateMachinePlugin] Failed to release lock '${lockName}':`, err.message);
|
|
30476
31050
|
}
|
|
30477
31051
|
}
|
|
31052
|
+
/**
|
|
31053
|
+
* Calculate backoff delay for retry attempts
|
|
31054
|
+
* @private
|
|
31055
|
+
*/
|
|
31056
|
+
_calculateBackoff(attempt, retryConfig) {
|
|
31057
|
+
const {
|
|
31058
|
+
backoffStrategy = "exponential",
|
|
31059
|
+
baseDelay = 1e3,
|
|
31060
|
+
maxDelay = 3e4
|
|
31061
|
+
} = retryConfig || {};
|
|
31062
|
+
let delay;
|
|
31063
|
+
if (backoffStrategy === "exponential") {
|
|
31064
|
+
delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
31065
|
+
} else if (backoffStrategy === "linear") {
|
|
31066
|
+
delay = Math.min(baseDelay * attempt, maxDelay);
|
|
31067
|
+
} else {
|
|
31068
|
+
delay = baseDelay;
|
|
31069
|
+
}
|
|
31070
|
+
const jitter = delay * 0.2 * (Math.random() - 0.5);
|
|
31071
|
+
return Math.round(delay + jitter);
|
|
31072
|
+
}
|
|
30478
31073
|
/**
|
|
30479
31074
|
* Get current state for an entity
|
|
30480
31075
|
*/
|
|
@@ -30604,7 +31199,7 @@ class StateMachinePlugin extends Plugin {
|
|
|
30604
31199
|
if (initialStateConfig && initialStateConfig.entry) {
|
|
30605
31200
|
await this._executeAction(initialStateConfig.entry, context, "INIT", machineId, entityId);
|
|
30606
31201
|
}
|
|
30607
|
-
this.emit("
|
|
31202
|
+
this.emit("plg:state-machine:entity-initialized", { machineId, entityId, initialState });
|
|
30608
31203
|
return initialState;
|
|
30609
31204
|
}
|
|
30610
31205
|
/**
|
|
@@ -30661,12 +31256,395 @@ class StateMachinePlugin extends Plugin {
|
|
|
30661
31256
|
`;
|
|
30662
31257
|
return dot;
|
|
30663
31258
|
}
|
|
31259
|
+
/**
|
|
31260
|
+
* Get all entities currently in a specific state
|
|
31261
|
+
* @private
|
|
31262
|
+
*/
|
|
31263
|
+
async _getEntitiesInState(machineId, stateName) {
|
|
31264
|
+
if (!this.config.persistTransitions) {
|
|
31265
|
+
const machine = this.machines.get(machineId);
|
|
31266
|
+
if (!machine) return [];
|
|
31267
|
+
const entities = [];
|
|
31268
|
+
for (const [entityId, currentState] of machine.currentStates) {
|
|
31269
|
+
if (currentState === stateName) {
|
|
31270
|
+
entities.push({ entityId, currentState, context: {}, triggerCounts: {} });
|
|
31271
|
+
}
|
|
31272
|
+
}
|
|
31273
|
+
return entities;
|
|
31274
|
+
}
|
|
31275
|
+
const [ok, err, records] = await tryFn(
|
|
31276
|
+
() => this.database.resources[this.config.stateResource].query({
|
|
31277
|
+
machineId,
|
|
31278
|
+
currentState: stateName
|
|
31279
|
+
})
|
|
31280
|
+
);
|
|
31281
|
+
if (!ok) {
|
|
31282
|
+
if (this.config.verbose) {
|
|
31283
|
+
console.warn(`[StateMachinePlugin] Failed to query entities in state '${stateName}':`, err.message);
|
|
31284
|
+
}
|
|
31285
|
+
return [];
|
|
31286
|
+
}
|
|
31287
|
+
return records || [];
|
|
31288
|
+
}
|
|
31289
|
+
/**
|
|
31290
|
+
* Increment trigger execution count for an entity
|
|
31291
|
+
* @private
|
|
31292
|
+
*/
|
|
31293
|
+
async _incrementTriggerCount(machineId, entityId, triggerName) {
|
|
31294
|
+
if (!this.config.persistTransitions) {
|
|
31295
|
+
return;
|
|
31296
|
+
}
|
|
31297
|
+
const stateId = `${machineId}_${entityId}`;
|
|
31298
|
+
const [ok, err, stateRecord] = await tryFn(
|
|
31299
|
+
() => this.database.resources[this.config.stateResource].get(stateId)
|
|
31300
|
+
);
|
|
31301
|
+
if (ok && stateRecord) {
|
|
31302
|
+
const triggerCounts = stateRecord.triggerCounts || {};
|
|
31303
|
+
triggerCounts[triggerName] = (triggerCounts[triggerName] || 0) + 1;
|
|
31304
|
+
await tryFn(
|
|
31305
|
+
() => this.database.resources[this.config.stateResource].patch(stateId, { triggerCounts })
|
|
31306
|
+
);
|
|
31307
|
+
}
|
|
31308
|
+
}
|
|
31309
|
+
/**
|
|
31310
|
+
* Setup trigger system for all state machines
|
|
31311
|
+
* @private
|
|
31312
|
+
*/
|
|
31313
|
+
async _setupTriggers() {
|
|
31314
|
+
if (!this.config.enableScheduler && !this.config.enableDateTriggers && !this.config.enableFunctionTriggers && !this.config.enableEventTriggers) {
|
|
31315
|
+
return;
|
|
31316
|
+
}
|
|
31317
|
+
const cronJobs = {};
|
|
31318
|
+
for (const [machineId, machineData] of this.machines) {
|
|
31319
|
+
const machineConfig = machineData.config;
|
|
31320
|
+
for (const [stateName, stateConfig] of Object.entries(machineConfig.states)) {
|
|
31321
|
+
const triggers = stateConfig.triggers || [];
|
|
31322
|
+
for (let i = 0; i < triggers.length; i++) {
|
|
31323
|
+
const trigger = triggers[i];
|
|
31324
|
+
const triggerName = `${trigger.action}_${i}`;
|
|
31325
|
+
if (trigger.type === "cron" && this.config.enableScheduler) {
|
|
31326
|
+
const jobName = `${machineId}_${stateName}_${triggerName}`;
|
|
31327
|
+
cronJobs[jobName] = await this._createCronJob(machineId, stateName, trigger, triggerName);
|
|
31328
|
+
} else if (trigger.type === "date" && this.config.enableDateTriggers) {
|
|
31329
|
+
await this._setupDateTrigger(machineId, stateName, trigger, triggerName);
|
|
31330
|
+
} else if (trigger.type === "function" && this.config.enableFunctionTriggers) {
|
|
31331
|
+
await this._setupFunctionTrigger(machineId, stateName, trigger, triggerName);
|
|
31332
|
+
} else if (trigger.type === "event" && this.config.enableEventTriggers) {
|
|
31333
|
+
await this._setupEventTrigger(machineId, stateName, trigger, triggerName);
|
|
31334
|
+
}
|
|
31335
|
+
}
|
|
31336
|
+
}
|
|
31337
|
+
}
|
|
31338
|
+
if (Object.keys(cronJobs).length > 0 && this.config.enableScheduler) {
|
|
31339
|
+
const { SchedulerPlugin } = await Promise.resolve().then(function () { return scheduler_plugin; });
|
|
31340
|
+
this.schedulerPlugin = new SchedulerPlugin({
|
|
31341
|
+
jobs: cronJobs,
|
|
31342
|
+
persistJobs: false,
|
|
31343
|
+
// Don't persist trigger jobs
|
|
31344
|
+
verbose: this.config.verbose,
|
|
31345
|
+
...this.config.schedulerConfig
|
|
31346
|
+
});
|
|
31347
|
+
await this.database.usePlugin(this.schedulerPlugin);
|
|
31348
|
+
if (this.config.verbose) {
|
|
31349
|
+
console.log(`[StateMachinePlugin] Installed SchedulerPlugin with ${Object.keys(cronJobs).length} cron triggers`);
|
|
31350
|
+
}
|
|
31351
|
+
}
|
|
31352
|
+
}
|
|
31353
|
+
/**
|
|
31354
|
+
* Create a SchedulerPlugin job for a cron trigger
|
|
31355
|
+
* @private
|
|
31356
|
+
*/
|
|
31357
|
+
async _createCronJob(machineId, stateName, trigger, triggerName) {
|
|
31358
|
+
return {
|
|
31359
|
+
schedule: trigger.schedule,
|
|
31360
|
+
description: `Trigger '${triggerName}' for ${machineId}.${stateName}`,
|
|
31361
|
+
action: async (database, context) => {
|
|
31362
|
+
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31363
|
+
let executedCount = 0;
|
|
31364
|
+
for (const entity of entities) {
|
|
31365
|
+
try {
|
|
31366
|
+
if (trigger.condition) {
|
|
31367
|
+
const shouldTrigger = await trigger.condition(entity.context, entity.entityId);
|
|
31368
|
+
if (!shouldTrigger) continue;
|
|
31369
|
+
}
|
|
31370
|
+
if (trigger.maxTriggers !== void 0) {
|
|
31371
|
+
const triggerCount = entity.triggerCounts?.[triggerName] || 0;
|
|
31372
|
+
if (triggerCount >= trigger.maxTriggers) {
|
|
31373
|
+
if (trigger.onMaxTriggersReached) {
|
|
31374
|
+
await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
|
|
31375
|
+
}
|
|
31376
|
+
continue;
|
|
31377
|
+
}
|
|
31378
|
+
}
|
|
31379
|
+
const result = await this._executeAction(
|
|
31380
|
+
trigger.action,
|
|
31381
|
+
entity.context,
|
|
31382
|
+
"TRIGGER",
|
|
31383
|
+
machineId,
|
|
31384
|
+
entity.entityId
|
|
31385
|
+
);
|
|
31386
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31387
|
+
executedCount++;
|
|
31388
|
+
if (trigger.eventOnSuccess) {
|
|
31389
|
+
await this.send(machineId, entity.entityId, trigger.eventOnSuccess, {
|
|
31390
|
+
...entity.context,
|
|
31391
|
+
triggerResult: result
|
|
31392
|
+
});
|
|
31393
|
+
} else if (trigger.event) {
|
|
31394
|
+
await this.send(machineId, entity.entityId, trigger.event, {
|
|
31395
|
+
...entity.context,
|
|
31396
|
+
triggerResult: result
|
|
31397
|
+
});
|
|
31398
|
+
}
|
|
31399
|
+
this.emit("plg:state-machine:trigger-executed", {
|
|
31400
|
+
machineId,
|
|
31401
|
+
entityId: entity.entityId,
|
|
31402
|
+
state: stateName,
|
|
31403
|
+
trigger: triggerName,
|
|
31404
|
+
type: "cron"
|
|
31405
|
+
});
|
|
31406
|
+
} catch (error) {
|
|
31407
|
+
if (trigger.event) {
|
|
31408
|
+
await tryFn(() => this.send(machineId, entity.entityId, trigger.event, {
|
|
31409
|
+
...entity.context,
|
|
31410
|
+
triggerError: error.message
|
|
31411
|
+
}));
|
|
31412
|
+
}
|
|
31413
|
+
if (this.config.verbose) {
|
|
31414
|
+
console.error(`[StateMachinePlugin] Trigger '${triggerName}' failed for entity ${entity.entityId}:`, error.message);
|
|
31415
|
+
}
|
|
31416
|
+
}
|
|
31417
|
+
}
|
|
31418
|
+
return { processed: entities.length, executed: executedCount };
|
|
31419
|
+
}
|
|
31420
|
+
};
|
|
31421
|
+
}
|
|
31422
|
+
/**
|
|
31423
|
+
* Setup a date-based trigger
|
|
31424
|
+
* @private
|
|
31425
|
+
*/
|
|
31426
|
+
async _setupDateTrigger(machineId, stateName, trigger, triggerName) {
|
|
31427
|
+
const checkInterval = setInterval(async () => {
|
|
31428
|
+
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31429
|
+
for (const entity of entities) {
|
|
31430
|
+
try {
|
|
31431
|
+
const triggerDateValue = entity.context?.[trigger.field];
|
|
31432
|
+
if (!triggerDateValue) continue;
|
|
31433
|
+
const triggerDate = new Date(triggerDateValue);
|
|
31434
|
+
const now = /* @__PURE__ */ new Date();
|
|
31435
|
+
if (now >= triggerDate) {
|
|
31436
|
+
if (trigger.maxTriggers !== void 0) {
|
|
31437
|
+
const triggerCount = entity.triggerCounts?.[triggerName] || 0;
|
|
31438
|
+
if (triggerCount >= trigger.maxTriggers) {
|
|
31439
|
+
if (trigger.onMaxTriggersReached) {
|
|
31440
|
+
await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
|
|
31441
|
+
}
|
|
31442
|
+
continue;
|
|
31443
|
+
}
|
|
31444
|
+
}
|
|
31445
|
+
const result = await this._executeAction(trigger.action, entity.context, "TRIGGER", machineId, entity.entityId);
|
|
31446
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31447
|
+
if (trigger.event) {
|
|
31448
|
+
await this.send(machineId, entity.entityId, trigger.event, {
|
|
31449
|
+
...entity.context,
|
|
31450
|
+
triggerResult: result
|
|
31451
|
+
});
|
|
31452
|
+
}
|
|
31453
|
+
this.emit("plg:state-machine:trigger-executed", {
|
|
31454
|
+
machineId,
|
|
31455
|
+
entityId: entity.entityId,
|
|
31456
|
+
state: stateName,
|
|
31457
|
+
trigger: triggerName,
|
|
31458
|
+
type: "date"
|
|
31459
|
+
});
|
|
31460
|
+
}
|
|
31461
|
+
} catch (error) {
|
|
31462
|
+
if (this.config.verbose) {
|
|
31463
|
+
console.error(`[StateMachinePlugin] Date trigger '${triggerName}' failed:`, error.message);
|
|
31464
|
+
}
|
|
31465
|
+
}
|
|
31466
|
+
}
|
|
31467
|
+
}, this.config.triggerCheckInterval);
|
|
31468
|
+
this.triggerIntervals.push(checkInterval);
|
|
31469
|
+
}
|
|
31470
|
+
/**
|
|
31471
|
+
* Setup a function-based trigger
|
|
31472
|
+
* @private
|
|
31473
|
+
*/
|
|
31474
|
+
async _setupFunctionTrigger(machineId, stateName, trigger, triggerName) {
|
|
31475
|
+
const interval = trigger.interval || this.config.triggerCheckInterval;
|
|
31476
|
+
const checkInterval = setInterval(async () => {
|
|
31477
|
+
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31478
|
+
for (const entity of entities) {
|
|
31479
|
+
try {
|
|
31480
|
+
if (trigger.maxTriggers !== void 0) {
|
|
31481
|
+
const triggerCount = entity.triggerCounts?.[triggerName] || 0;
|
|
31482
|
+
if (triggerCount >= trigger.maxTriggers) {
|
|
31483
|
+
if (trigger.onMaxTriggersReached) {
|
|
31484
|
+
await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
|
|
31485
|
+
}
|
|
31486
|
+
continue;
|
|
31487
|
+
}
|
|
31488
|
+
}
|
|
31489
|
+
const shouldTrigger = await trigger.condition(entity.context, entity.entityId);
|
|
31490
|
+
if (shouldTrigger) {
|
|
31491
|
+
const result = await this._executeAction(trigger.action, entity.context, "TRIGGER", machineId, entity.entityId);
|
|
31492
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31493
|
+
if (trigger.event) {
|
|
31494
|
+
await this.send(machineId, entity.entityId, trigger.event, {
|
|
31495
|
+
...entity.context,
|
|
31496
|
+
triggerResult: result
|
|
31497
|
+
});
|
|
31498
|
+
}
|
|
31499
|
+
this.emit("plg:state-machine:trigger-executed", {
|
|
31500
|
+
machineId,
|
|
31501
|
+
entityId: entity.entityId,
|
|
31502
|
+
state: stateName,
|
|
31503
|
+
trigger: triggerName,
|
|
31504
|
+
type: "function"
|
|
31505
|
+
});
|
|
31506
|
+
}
|
|
31507
|
+
} catch (error) {
|
|
31508
|
+
if (this.config.verbose) {
|
|
31509
|
+
console.error(`[StateMachinePlugin] Function trigger '${triggerName}' failed:`, error.message);
|
|
31510
|
+
}
|
|
31511
|
+
}
|
|
31512
|
+
}
|
|
31513
|
+
}, interval);
|
|
31514
|
+
this.triggerIntervals.push(checkInterval);
|
|
31515
|
+
}
|
|
31516
|
+
/**
|
|
31517
|
+
* Setup an event-based trigger
|
|
31518
|
+
* @private
|
|
31519
|
+
*/
|
|
31520
|
+
async _setupEventTrigger(machineId, stateName, trigger, triggerName) {
|
|
31521
|
+
const eventName = trigger.event;
|
|
31522
|
+
const eventHandler = async (eventData) => {
|
|
31523
|
+
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31524
|
+
for (const entity of entities) {
|
|
31525
|
+
try {
|
|
31526
|
+
if (trigger.condition) {
|
|
31527
|
+
const shouldTrigger = await trigger.condition(entity.context, entity.entityId, eventData);
|
|
31528
|
+
if (!shouldTrigger) continue;
|
|
31529
|
+
}
|
|
31530
|
+
if (trigger.maxTriggers !== void 0) {
|
|
31531
|
+
const triggerCount = entity.triggerCounts?.[triggerName] || 0;
|
|
31532
|
+
if (triggerCount >= trigger.maxTriggers) {
|
|
31533
|
+
if (trigger.onMaxTriggersReached) {
|
|
31534
|
+
await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
|
|
31535
|
+
}
|
|
31536
|
+
continue;
|
|
31537
|
+
}
|
|
31538
|
+
}
|
|
31539
|
+
const result = await this._executeAction(
|
|
31540
|
+
trigger.action,
|
|
31541
|
+
{ ...entity.context, eventData },
|
|
31542
|
+
"TRIGGER",
|
|
31543
|
+
machineId,
|
|
31544
|
+
entity.entityId
|
|
31545
|
+
);
|
|
31546
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31547
|
+
if (trigger.sendEvent) {
|
|
31548
|
+
await this.send(machineId, entity.entityId, trigger.sendEvent, {
|
|
31549
|
+
...entity.context,
|
|
31550
|
+
triggerResult: result,
|
|
31551
|
+
eventData
|
|
31552
|
+
});
|
|
31553
|
+
}
|
|
31554
|
+
this.emit("plg:state-machine:trigger-executed", {
|
|
31555
|
+
machineId,
|
|
31556
|
+
entityId: entity.entityId,
|
|
31557
|
+
state: stateName,
|
|
31558
|
+
trigger: triggerName,
|
|
31559
|
+
type: "event",
|
|
31560
|
+
eventName
|
|
31561
|
+
});
|
|
31562
|
+
} catch (error) {
|
|
31563
|
+
if (this.config.verbose) {
|
|
31564
|
+
console.error(`[StateMachinePlugin] Event trigger '${triggerName}' failed:`, error.message);
|
|
31565
|
+
}
|
|
31566
|
+
}
|
|
31567
|
+
}
|
|
31568
|
+
};
|
|
31569
|
+
if (eventName.startsWith("db:")) {
|
|
31570
|
+
const dbEventName = eventName.substring(3);
|
|
31571
|
+
this.database.on(dbEventName, eventHandler);
|
|
31572
|
+
if (this.config.verbose) {
|
|
31573
|
+
console.log(`[StateMachinePlugin] Listening to database event '${dbEventName}' for trigger '${triggerName}'`);
|
|
31574
|
+
}
|
|
31575
|
+
} else {
|
|
31576
|
+
this.on(eventName, eventHandler);
|
|
31577
|
+
if (this.config.verbose) {
|
|
31578
|
+
console.log(`[StateMachinePlugin] Listening to plugin event '${eventName}' for trigger '${triggerName}'`);
|
|
31579
|
+
}
|
|
31580
|
+
}
|
|
31581
|
+
}
|
|
31582
|
+
/**
|
|
31583
|
+
* Attach state machine instances to their associated resources
|
|
31584
|
+
* This enables the resource API: resource.state(id, event)
|
|
31585
|
+
* @private
|
|
31586
|
+
*/
|
|
31587
|
+
async _attachStateMachinesToResources() {
|
|
31588
|
+
for (const [machineName, machineConfig] of Object.entries(this.config.stateMachines)) {
|
|
31589
|
+
const resourceConfig = machineConfig.config || machineConfig;
|
|
31590
|
+
if (!resourceConfig.resource) {
|
|
31591
|
+
if (this.config.verbose) {
|
|
31592
|
+
console.log(`[StateMachinePlugin] Machine '${machineName}' has no resource configured, skipping attachment`);
|
|
31593
|
+
}
|
|
31594
|
+
continue;
|
|
31595
|
+
}
|
|
31596
|
+
let resource;
|
|
31597
|
+
if (typeof resourceConfig.resource === "string") {
|
|
31598
|
+
resource = this.database.resources[resourceConfig.resource];
|
|
31599
|
+
if (!resource) {
|
|
31600
|
+
console.warn(
|
|
31601
|
+
`[StateMachinePlugin] Resource '${resourceConfig.resource}' not found for machine '${machineName}'. Resource API will not be available.`
|
|
31602
|
+
);
|
|
31603
|
+
continue;
|
|
31604
|
+
}
|
|
31605
|
+
} else {
|
|
31606
|
+
resource = resourceConfig.resource;
|
|
31607
|
+
}
|
|
31608
|
+
const machineProxy = {
|
|
31609
|
+
send: async (id, event, eventData) => {
|
|
31610
|
+
return this.send(machineName, id, event, eventData);
|
|
31611
|
+
},
|
|
31612
|
+
getState: async (id) => {
|
|
31613
|
+
return this.getState(machineName, id);
|
|
31614
|
+
},
|
|
31615
|
+
canTransition: async (id, event) => {
|
|
31616
|
+
return this.canTransition(machineName, id, event);
|
|
31617
|
+
},
|
|
31618
|
+
getValidEvents: async (id) => {
|
|
31619
|
+
return this.getValidEvents(machineName, id);
|
|
31620
|
+
},
|
|
31621
|
+
initializeEntity: async (id, context) => {
|
|
31622
|
+
return this.initializeEntity(machineName, id, context);
|
|
31623
|
+
},
|
|
31624
|
+
getTransitionHistory: async (id, options) => {
|
|
31625
|
+
return this.getTransitionHistory(machineName, id, options);
|
|
31626
|
+
}
|
|
31627
|
+
};
|
|
31628
|
+
resource._attachStateMachine(machineProxy);
|
|
31629
|
+
if (this.config.verbose) {
|
|
31630
|
+
console.log(`[StateMachinePlugin] Attached machine '${machineName}' to resource '${resource.name}'`);
|
|
31631
|
+
}
|
|
31632
|
+
}
|
|
31633
|
+
}
|
|
30664
31634
|
async start() {
|
|
30665
31635
|
if (this.config.verbose) {
|
|
30666
31636
|
console.log(`[StateMachinePlugin] Started with ${this.machines.size} state machines`);
|
|
30667
31637
|
}
|
|
30668
31638
|
}
|
|
30669
31639
|
async stop() {
|
|
31640
|
+
for (const interval of this.triggerIntervals) {
|
|
31641
|
+
clearInterval(interval);
|
|
31642
|
+
}
|
|
31643
|
+
this.triggerIntervals = [];
|
|
31644
|
+
if (this.schedulerPlugin) {
|
|
31645
|
+
await this.schedulerPlugin.stop();
|
|
31646
|
+
this.schedulerPlugin = null;
|
|
31647
|
+
}
|
|
30670
31648
|
this.machines.clear();
|
|
30671
31649
|
this.removeAllListeners();
|
|
30672
31650
|
}
|
|
@@ -40938,7 +41916,7 @@ class TTLPlugin extends Plugin {
|
|
|
40938
41916
|
if (this.verbose) {
|
|
40939
41917
|
console.log(`[TTLPlugin] Installed with ${Object.keys(this.resources).length} resources`);
|
|
40940
41918
|
}
|
|
40941
|
-
this.emit("installed", {
|
|
41919
|
+
this.emit("db:plugin:installed", {
|
|
40942
41920
|
plugin: "TTLPlugin",
|
|
40943
41921
|
resources: Object.keys(this.resources)
|
|
40944
41922
|
});
|
|
@@ -41175,7 +42153,7 @@ class TTLPlugin extends Plugin {
|
|
|
41175
42153
|
}
|
|
41176
42154
|
this.stats.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
41177
42155
|
this.stats.lastScanDuration = Date.now() - startTime;
|
|
41178
|
-
this.emit("
|
|
42156
|
+
this.emit("plg:ttl:scan-completed", {
|
|
41179
42157
|
granularity,
|
|
41180
42158
|
duration: this.stats.lastScanDuration,
|
|
41181
42159
|
cohorts
|
|
@@ -41183,7 +42161,7 @@ class TTLPlugin extends Plugin {
|
|
|
41183
42161
|
} catch (error) {
|
|
41184
42162
|
console.error(`[TTLPlugin] Error in ${granularity} cleanup:`, error);
|
|
41185
42163
|
this.stats.totalErrors++;
|
|
41186
|
-
this.emit("
|
|
42164
|
+
this.emit("plg:ttl:cleanup-error", { granularity, error });
|
|
41187
42165
|
}
|
|
41188
42166
|
}
|
|
41189
42167
|
/**
|
|
@@ -41231,7 +42209,7 @@ class TTLPlugin extends Plugin {
|
|
|
41231
42209
|
}
|
|
41232
42210
|
await this.expirationIndex.delete(entry.id);
|
|
41233
42211
|
this.stats.totalExpired++;
|
|
41234
|
-
this.emit("
|
|
42212
|
+
this.emit("plg:ttl:record-expired", { resource: entry.resourceName, record });
|
|
41235
42213
|
} catch (error) {
|
|
41236
42214
|
console.error(`[TTLPlugin] Error processing expired entry:`, error);
|
|
41237
42215
|
this.stats.totalErrors++;
|
|
@@ -41702,15 +42680,15 @@ class VectorPlugin extends Plugin {
|
|
|
41702
42680
|
this._throttleState = /* @__PURE__ */ new Map();
|
|
41703
42681
|
}
|
|
41704
42682
|
async onInstall() {
|
|
41705
|
-
this.emit("installed", { plugin: "VectorPlugin" });
|
|
42683
|
+
this.emit("db:plugin:installed", { plugin: "VectorPlugin" });
|
|
41706
42684
|
this.validateVectorStorage();
|
|
41707
42685
|
this.installResourceMethods();
|
|
41708
42686
|
}
|
|
41709
42687
|
async onStart() {
|
|
41710
|
-
this.emit("started", { plugin: "VectorPlugin" });
|
|
42688
|
+
this.emit("db:plugin:started", { plugin: "VectorPlugin" });
|
|
41711
42689
|
}
|
|
41712
42690
|
async onStop() {
|
|
41713
|
-
this.emit("stopped", { plugin: "VectorPlugin" });
|
|
42691
|
+
this.emit("db:plugin:stopped", { plugin: "VectorPlugin" });
|
|
41714
42692
|
}
|
|
41715
42693
|
async onUninstall(options) {
|
|
41716
42694
|
for (const resource of Object.values(this.database.resources)) {
|
|
@@ -41721,7 +42699,7 @@ class VectorPlugin extends Plugin {
|
|
|
41721
42699
|
delete resource.findSimilar;
|
|
41722
42700
|
delete resource.distance;
|
|
41723
42701
|
}
|
|
41724
|
-
this.emit("uninstalled", { plugin: "VectorPlugin" });
|
|
42702
|
+
this.emit("db:plugin:uninstalled", { plugin: "VectorPlugin" });
|
|
41725
42703
|
}
|
|
41726
42704
|
/**
|
|
41727
42705
|
* Validate vector storage configuration for all resources
|
|
@@ -41750,10 +42728,10 @@ class VectorPlugin extends Plugin {
|
|
|
41750
42728
|
currentBehavior: resource.behavior || "default",
|
|
41751
42729
|
recommendation: "body-overflow"
|
|
41752
42730
|
};
|
|
41753
|
-
this.emit("vector:storage-warning", warning);
|
|
42731
|
+
this.emit("plg:vector:storage-warning", warning);
|
|
41754
42732
|
if (this.config.autoFixBehavior) {
|
|
41755
42733
|
resource.behavior = "body-overflow";
|
|
41756
|
-
this.emit("vector:behavior-fixed", {
|
|
42734
|
+
this.emit("plg:vector:behavior-fixed", {
|
|
41757
42735
|
resource: resource.name,
|
|
41758
42736
|
newBehavior: "body-overflow"
|
|
41759
42737
|
});
|
|
@@ -41785,7 +42763,7 @@ class VectorPlugin extends Plugin {
|
|
|
41785
42763
|
const partitionName = `byHas${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
|
|
41786
42764
|
const trackingFieldName = `_has${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
|
|
41787
42765
|
if (resource.config.partitions && resource.config.partitions[partitionName]) {
|
|
41788
|
-
this.emit("vector:partition-exists", {
|
|
42766
|
+
this.emit("plg:vector:partition-exists", {
|
|
41789
42767
|
resource: resource.name,
|
|
41790
42768
|
vectorField: vectorField.name,
|
|
41791
42769
|
partition: partitionName,
|
|
@@ -41808,7 +42786,7 @@ class VectorPlugin extends Plugin {
|
|
|
41808
42786
|
default: false
|
|
41809
42787
|
}, "VectorPlugin");
|
|
41810
42788
|
}
|
|
41811
|
-
this.emit("vector:partition-created", {
|
|
42789
|
+
this.emit("plg:vector:partition-created", {
|
|
41812
42790
|
resource: resource.name,
|
|
41813
42791
|
vectorField: vectorField.name,
|
|
41814
42792
|
partition: partitionName,
|
|
@@ -41883,7 +42861,7 @@ class VectorPlugin extends Plugin {
|
|
|
41883
42861
|
}
|
|
41884
42862
|
return updates;
|
|
41885
42863
|
});
|
|
41886
|
-
this.emit("vector:hooks-installed", {
|
|
42864
|
+
this.emit("plg:vector:hooks-installed", {
|
|
41887
42865
|
resource: resource.name,
|
|
41888
42866
|
vectorField,
|
|
41889
42867
|
trackingField,
|
|
@@ -41992,7 +42970,7 @@ class VectorPlugin extends Plugin {
|
|
|
41992
42970
|
const vectorField = this._findEmbeddingField(resource.schema.attributes);
|
|
41993
42971
|
this._vectorFieldCache.set(resource.name, vectorField);
|
|
41994
42972
|
if (vectorField && this.config.emitEvents) {
|
|
41995
|
-
this.emit("vector:field-detected", {
|
|
42973
|
+
this.emit("plg:vector:field-detected", {
|
|
41996
42974
|
resource: resource.name,
|
|
41997
42975
|
vectorField,
|
|
41998
42976
|
timestamp: Date.now()
|
|
@@ -42916,7 +43894,7 @@ class MemoryClient extends EventEmitter {
|
|
|
42916
43894
|
async sendCommand(command) {
|
|
42917
43895
|
const commandName = command.constructor.name;
|
|
42918
43896
|
const input = command.input || {};
|
|
42919
|
-
this.emit("
|
|
43897
|
+
this.emit("cl:request", commandName, input);
|
|
42920
43898
|
let response;
|
|
42921
43899
|
try {
|
|
42922
43900
|
switch (commandName) {
|
|
@@ -42944,7 +43922,7 @@ class MemoryClient extends EventEmitter {
|
|
|
42944
43922
|
default:
|
|
42945
43923
|
throw new Error(`Unsupported command: ${commandName}`);
|
|
42946
43924
|
}
|
|
42947
|
-
this.emit("
|
|
43925
|
+
this.emit("cl:response", commandName, response, input);
|
|
42948
43926
|
return response;
|
|
42949
43927
|
} catch (error) {
|
|
42950
43928
|
const mappedError = mapAwsError(error, {
|
|
@@ -43055,7 +44033,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43055
44033
|
contentLength,
|
|
43056
44034
|
ifMatch
|
|
43057
44035
|
});
|
|
43058
|
-
this.emit("
|
|
44036
|
+
this.emit("cl:PutObject", null, { key, metadata, contentType, body, contentEncoding, contentLength });
|
|
43059
44037
|
return response;
|
|
43060
44038
|
}
|
|
43061
44039
|
/**
|
|
@@ -43070,7 +44048,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43070
44048
|
decodedMetadata[k] = metadataDecode(v);
|
|
43071
44049
|
}
|
|
43072
44050
|
}
|
|
43073
|
-
this.emit("
|
|
44051
|
+
this.emit("cl:GetObject", null, { key });
|
|
43074
44052
|
return {
|
|
43075
44053
|
...response,
|
|
43076
44054
|
Metadata: decodedMetadata
|
|
@@ -43088,7 +44066,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43088
44066
|
decodedMetadata[k] = metadataDecode(v);
|
|
43089
44067
|
}
|
|
43090
44068
|
}
|
|
43091
|
-
this.emit("
|
|
44069
|
+
this.emit("cl:HeadObject", null, { key });
|
|
43092
44070
|
return {
|
|
43093
44071
|
...response,
|
|
43094
44072
|
Metadata: decodedMetadata
|
|
@@ -43113,7 +44091,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43113
44091
|
metadataDirective,
|
|
43114
44092
|
contentType
|
|
43115
44093
|
});
|
|
43116
|
-
this.emit("
|
|
44094
|
+
this.emit("cl:CopyObject", null, { from, to, metadata, metadataDirective });
|
|
43117
44095
|
return response;
|
|
43118
44096
|
}
|
|
43119
44097
|
/**
|
|
@@ -43129,7 +44107,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43129
44107
|
async deleteObject(key) {
|
|
43130
44108
|
const fullKey = this.keyPrefix ? path$1.join(this.keyPrefix, key) : key;
|
|
43131
44109
|
const response = await this.storage.delete(fullKey);
|
|
43132
|
-
this.emit("
|
|
44110
|
+
this.emit("cl:DeleteObject", null, { key });
|
|
43133
44111
|
return response;
|
|
43134
44112
|
}
|
|
43135
44113
|
/**
|
|
@@ -43162,7 +44140,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43162
44140
|
maxKeys,
|
|
43163
44141
|
continuationToken
|
|
43164
44142
|
});
|
|
43165
|
-
this.emit("
|
|
44143
|
+
this.emit("cl:ListObjects", null, { prefix, count: response.Contents.length });
|
|
43166
44144
|
return response;
|
|
43167
44145
|
}
|
|
43168
44146
|
/**
|
|
@@ -43202,7 +44180,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43202
44180
|
if (this.keyPrefix) {
|
|
43203
44181
|
keys = keys.map((x) => x.replace(this.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace("/", "") : x);
|
|
43204
44182
|
}
|
|
43205
|
-
this.emit("
|
|
44183
|
+
this.emit("cl:GetKeysPage", keys, params);
|
|
43206
44184
|
return keys;
|
|
43207
44185
|
}
|
|
43208
44186
|
/**
|
|
@@ -43219,7 +44197,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43219
44197
|
if (this.keyPrefix) {
|
|
43220
44198
|
keys = keys.map((x) => x.replace(this.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace("/", "") : x);
|
|
43221
44199
|
}
|
|
43222
|
-
this.emit("
|
|
44200
|
+
this.emit("cl:GetAllKeys", keys, { prefix });
|
|
43223
44201
|
return keys;
|
|
43224
44202
|
}
|
|
43225
44203
|
/**
|
|
@@ -43228,7 +44206,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43228
44206
|
async count({ prefix = "" } = {}) {
|
|
43229
44207
|
const keys = await this.getAllKeys({ prefix });
|
|
43230
44208
|
const count = keys.length;
|
|
43231
|
-
this.emit("
|
|
44209
|
+
this.emit("cl:Count", count, { prefix });
|
|
43232
44210
|
return count;
|
|
43233
44211
|
}
|
|
43234
44212
|
/**
|
|
@@ -43240,13 +44218,13 @@ class MemoryClient extends EventEmitter {
|
|
|
43240
44218
|
if (keys.length > 0) {
|
|
43241
44219
|
const result = await this.deleteObjects(keys);
|
|
43242
44220
|
totalDeleted = result.Deleted.length;
|
|
43243
|
-
this.emit("
|
|
44221
|
+
this.emit("cl:DeleteAll", {
|
|
43244
44222
|
prefix,
|
|
43245
44223
|
batch: totalDeleted,
|
|
43246
44224
|
total: totalDeleted
|
|
43247
44225
|
});
|
|
43248
44226
|
}
|
|
43249
|
-
this.emit("
|
|
44227
|
+
this.emit("cl:DeleteAllComplete", {
|
|
43250
44228
|
prefix,
|
|
43251
44229
|
totalDeleted
|
|
43252
44230
|
});
|
|
@@ -43259,11 +44237,11 @@ class MemoryClient extends EventEmitter {
|
|
|
43259
44237
|
if (offset === 0) return null;
|
|
43260
44238
|
const keys = await this.getAllKeys({ prefix });
|
|
43261
44239
|
if (offset >= keys.length) {
|
|
43262
|
-
this.emit("
|
|
44240
|
+
this.emit("cl:GetContinuationTokenAfterOffset", null, { prefix, offset });
|
|
43263
44241
|
return null;
|
|
43264
44242
|
}
|
|
43265
44243
|
const token = keys[offset];
|
|
43266
|
-
this.emit("
|
|
44244
|
+
this.emit("cl:GetContinuationTokenAfterOffset", token, { prefix, offset });
|
|
43267
44245
|
return token;
|
|
43268
44246
|
}
|
|
43269
44247
|
/**
|
|
@@ -43293,7 +44271,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43293
44271
|
});
|
|
43294
44272
|
}
|
|
43295
44273
|
}
|
|
43296
|
-
this.emit("
|
|
44274
|
+
this.emit("cl:MoveAllObjects", { results, errors }, { prefixFrom, prefixTo });
|
|
43297
44275
|
if (errors.length > 0) {
|
|
43298
44276
|
const error = new Error("Some objects could not be moved");
|
|
43299
44277
|
error.context = {
|