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.es.js
CHANGED
|
@@ -2544,6 +2544,18 @@ const PLUGIN_DEPENDENCIES = {
|
|
|
2544
2544
|
npmUrl: "https://www.npmjs.com/package/@hono/swagger-ui"
|
|
2545
2545
|
}
|
|
2546
2546
|
}
|
|
2547
|
+
},
|
|
2548
|
+
"ml-plugin": {
|
|
2549
|
+
name: "ML Plugin",
|
|
2550
|
+
docsUrl: "https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/ml-plugin.md",
|
|
2551
|
+
dependencies: {
|
|
2552
|
+
"@tensorflow/tfjs-node": {
|
|
2553
|
+
version: "^4.0.0",
|
|
2554
|
+
description: "TensorFlow.js for Node.js with native bindings",
|
|
2555
|
+
installCommand: "pnpm add @tensorflow/tfjs-node",
|
|
2556
|
+
npmUrl: "https://www.npmjs.com/package/@tensorflow/tfjs-node"
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2547
2559
|
}
|
|
2548
2560
|
};
|
|
2549
2561
|
function isVersionCompatible(actual, required) {
|
|
@@ -6593,7 +6605,7 @@ class BackupPlugin extends Plugin {
|
|
|
6593
6605
|
const storageInfo = this.driver.getStorageInfo();
|
|
6594
6606
|
console.log(`[BackupPlugin] Initialized with driver: ${storageInfo.type}`);
|
|
6595
6607
|
}
|
|
6596
|
-
this.emit("initialized", {
|
|
6608
|
+
this.emit("db:plugin:initialized", {
|
|
6597
6609
|
driver: this.driver.getType(),
|
|
6598
6610
|
config: this.driver.getStorageInfo()
|
|
6599
6611
|
});
|
|
@@ -6641,7 +6653,7 @@ class BackupPlugin extends Plugin {
|
|
|
6641
6653
|
if (this.config.onBackupStart) {
|
|
6642
6654
|
await this._executeHook(this.config.onBackupStart, type, { backupId });
|
|
6643
6655
|
}
|
|
6644
|
-
this.emit("
|
|
6656
|
+
this.emit("plg:backup:start", { id: backupId, type });
|
|
6645
6657
|
const metadata = await this._createBackupMetadata(backupId, type);
|
|
6646
6658
|
const tempBackupDir = path$1.join(this.config.tempDir, backupId);
|
|
6647
6659
|
await mkdir(tempBackupDir, { recursive: true });
|
|
@@ -6674,7 +6686,7 @@ class BackupPlugin extends Plugin {
|
|
|
6674
6686
|
const stats = { backupId, type, size: totalSize, duration, driverInfo: uploadResult };
|
|
6675
6687
|
await this._executeHook(this.config.onBackupComplete, type, stats);
|
|
6676
6688
|
}
|
|
6677
|
-
this.emit("
|
|
6689
|
+
this.emit("plg:backup:complete", {
|
|
6678
6690
|
id: backupId,
|
|
6679
6691
|
type,
|
|
6680
6692
|
size: totalSize,
|
|
@@ -6702,7 +6714,7 @@ class BackupPlugin extends Plugin {
|
|
|
6702
6714
|
error: error.message,
|
|
6703
6715
|
duration: Date.now() - startTime
|
|
6704
6716
|
});
|
|
6705
|
-
this.emit("
|
|
6717
|
+
this.emit("plg:backup:error", { id: backupId, type, error: error.message });
|
|
6706
6718
|
throw error;
|
|
6707
6719
|
} finally {
|
|
6708
6720
|
this.activeBackups.delete(backupId);
|
|
@@ -6923,7 +6935,7 @@ class BackupPlugin extends Plugin {
|
|
|
6923
6935
|
if (this.config.onRestoreStart) {
|
|
6924
6936
|
await this._executeHook(this.config.onRestoreStart, backupId, options);
|
|
6925
6937
|
}
|
|
6926
|
-
this.emit("
|
|
6938
|
+
this.emit("plg:backup:restore-start", { id: backupId, options });
|
|
6927
6939
|
const backup = await this.getBackupStatus(backupId);
|
|
6928
6940
|
if (!backup) {
|
|
6929
6941
|
throw new Error(`Backup '${backupId}' not found`);
|
|
@@ -6946,7 +6958,7 @@ class BackupPlugin extends Plugin {
|
|
|
6946
6958
|
if (this.config.onRestoreComplete) {
|
|
6947
6959
|
await this._executeHook(this.config.onRestoreComplete, backupId, { restored: restoredResources });
|
|
6948
6960
|
}
|
|
6949
|
-
this.emit("
|
|
6961
|
+
this.emit("plg:backup:restore-complete", {
|
|
6950
6962
|
id: backupId,
|
|
6951
6963
|
restored: restoredResources
|
|
6952
6964
|
});
|
|
@@ -6961,7 +6973,7 @@ class BackupPlugin extends Plugin {
|
|
|
6961
6973
|
if (this.config.onRestoreError) {
|
|
6962
6974
|
await this._executeHook(this.config.onRestoreError, backupId, { error });
|
|
6963
6975
|
}
|
|
6964
|
-
this.emit("
|
|
6976
|
+
this.emit("plg:backup:restore-error", { id: backupId, error: error.message });
|
|
6965
6977
|
throw error;
|
|
6966
6978
|
}
|
|
6967
6979
|
}
|
|
@@ -7211,7 +7223,7 @@ class BackupPlugin extends Plugin {
|
|
|
7211
7223
|
}
|
|
7212
7224
|
async stop() {
|
|
7213
7225
|
for (const backupId of this.activeBackups) {
|
|
7214
|
-
this.emit("
|
|
7226
|
+
this.emit("plg:backup:cancelled", { id: backupId });
|
|
7215
7227
|
}
|
|
7216
7228
|
this.activeBackups.clear();
|
|
7217
7229
|
if (this.driver) {
|
|
@@ -8735,7 +8747,7 @@ class CachePlugin extends Plugin {
|
|
|
8735
8747
|
const specificKey = await this.generateCacheKey(resource, method, { id: data.id });
|
|
8736
8748
|
const [ok2, err2] = await this.clearCacheWithRetry(resource.cache, specificKey);
|
|
8737
8749
|
if (!ok2) {
|
|
8738
|
-
this.emit("
|
|
8750
|
+
this.emit("plg:cache:clear-error", {
|
|
8739
8751
|
resource: resource.name,
|
|
8740
8752
|
method,
|
|
8741
8753
|
id: data.id,
|
|
@@ -8753,7 +8765,7 @@ class CachePlugin extends Plugin {
|
|
|
8753
8765
|
const partitionKeyPrefix = join(keyPrefix, `partition=${partitionName}`);
|
|
8754
8766
|
const [ok2, err2] = await this.clearCacheWithRetry(resource.cache, partitionKeyPrefix);
|
|
8755
8767
|
if (!ok2) {
|
|
8756
|
-
this.emit("
|
|
8768
|
+
this.emit("plg:cache:clear-error", {
|
|
8757
8769
|
resource: resource.name,
|
|
8758
8770
|
partition: partitionName,
|
|
8759
8771
|
error: err2.message
|
|
@@ -8768,7 +8780,7 @@ class CachePlugin extends Plugin {
|
|
|
8768
8780
|
}
|
|
8769
8781
|
const [ok, err] = await this.clearCacheWithRetry(resource.cache, keyPrefix);
|
|
8770
8782
|
if (!ok) {
|
|
8771
|
-
this.emit("
|
|
8783
|
+
this.emit("plg:cache:clear-error", {
|
|
8772
8784
|
resource: resource.name,
|
|
8773
8785
|
type: "broad",
|
|
8774
8786
|
error: err.message
|
|
@@ -12668,7 +12680,7 @@ class GeoPlugin extends Plugin {
|
|
|
12668
12680
|
if (this.verbose) {
|
|
12669
12681
|
console.log(`[GeoPlugin] Installed with ${Object.keys(this.resources).length} resources`);
|
|
12670
12682
|
}
|
|
12671
|
-
this.emit("installed", {
|
|
12683
|
+
this.emit("db:plugin:installed", {
|
|
12672
12684
|
plugin: "GeoPlugin",
|
|
12673
12685
|
resources: Object.keys(this.resources)
|
|
12674
12686
|
});
|
|
@@ -13235,7 +13247,7 @@ class GeoPlugin extends Plugin {
|
|
|
13235
13247
|
if (this.verbose) {
|
|
13236
13248
|
console.log("[GeoPlugin] Uninstalled");
|
|
13237
13249
|
}
|
|
13238
|
-
this.emit("uninstalled", {
|
|
13250
|
+
this.emit("db:plugin:uninstalled", {
|
|
13239
13251
|
plugin: "GeoPlugin"
|
|
13240
13252
|
});
|
|
13241
13253
|
await super.uninstall();
|
|
@@ -15237,7 +15249,7 @@ class MLPlugin extends Plugin {
|
|
|
15237
15249
|
enableVersioning: options.enableVersioning !== false
|
|
15238
15250
|
// Default true
|
|
15239
15251
|
};
|
|
15240
|
-
requirePluginDependency("
|
|
15252
|
+
requirePluginDependency("ml-plugin");
|
|
15241
15253
|
this.models = {};
|
|
15242
15254
|
this.modelVersions = /* @__PURE__ */ new Map();
|
|
15243
15255
|
this.modelCache = /* @__PURE__ */ new Map();
|
|
@@ -15275,7 +15287,7 @@ class MLPlugin extends Plugin {
|
|
|
15275
15287
|
if (this.config.verbose) {
|
|
15276
15288
|
console.log(`[MLPlugin] Installed with ${Object.keys(this.models).length} models`);
|
|
15277
15289
|
}
|
|
15278
|
-
this.emit("installed", {
|
|
15290
|
+
this.emit("db:plugin:installed", {
|
|
15279
15291
|
plugin: "MLPlugin",
|
|
15280
15292
|
models: Object.keys(this.models)
|
|
15281
15293
|
});
|
|
@@ -15626,6 +15638,22 @@ class MLPlugin extends Plugin {
|
|
|
15626
15638
|
}
|
|
15627
15639
|
data = allData;
|
|
15628
15640
|
}
|
|
15641
|
+
if (modelConfig.filter && typeof modelConfig.filter === "function") {
|
|
15642
|
+
if (this.config.verbose) {
|
|
15643
|
+
console.log(`[MLPlugin] Applying custom filter function...`);
|
|
15644
|
+
}
|
|
15645
|
+
const originalLength = data.length;
|
|
15646
|
+
data = data.filter(modelConfig.filter);
|
|
15647
|
+
if (this.config.verbose) {
|
|
15648
|
+
console.log(`[MLPlugin] Filter reduced dataset from ${originalLength} to ${data.length} samples`);
|
|
15649
|
+
}
|
|
15650
|
+
}
|
|
15651
|
+
if (modelConfig.map && typeof modelConfig.map === "function") {
|
|
15652
|
+
if (this.config.verbose) {
|
|
15653
|
+
console.log(`[MLPlugin] Applying custom map function...`);
|
|
15654
|
+
}
|
|
15655
|
+
data = data.map(modelConfig.map);
|
|
15656
|
+
}
|
|
15629
15657
|
if (!data || data.length < this.config.minTrainingSamples) {
|
|
15630
15658
|
throw new TrainingError(
|
|
15631
15659
|
`Insufficient training data: ${data?.length || 0} samples (minimum: ${this.config.minTrainingSamples})`,
|
|
@@ -15648,7 +15676,7 @@ class MLPlugin extends Plugin {
|
|
|
15648
15676
|
if (this.config.verbose) {
|
|
15649
15677
|
console.log(`[MLPlugin] Training completed for "${modelName}":`, result);
|
|
15650
15678
|
}
|
|
15651
|
-
this.emit("
|
|
15679
|
+
this.emit("plg:ml:model-trained", {
|
|
15652
15680
|
modelName,
|
|
15653
15681
|
type: modelConfig.type,
|
|
15654
15682
|
result
|
|
@@ -15684,7 +15712,7 @@ class MLPlugin extends Plugin {
|
|
|
15684
15712
|
try {
|
|
15685
15713
|
const result = await model.predict(input);
|
|
15686
15714
|
this.stats.totalPredictions++;
|
|
15687
|
-
this.emit("prediction", {
|
|
15715
|
+
this.emit("plg:ml:prediction", {
|
|
15688
15716
|
modelName,
|
|
15689
15717
|
input,
|
|
15690
15718
|
result
|
|
@@ -15799,7 +15827,11 @@ class MLPlugin extends Plugin {
|
|
|
15799
15827
|
async _initializeVersioning(modelName) {
|
|
15800
15828
|
try {
|
|
15801
15829
|
const storage = this.getStorage();
|
|
15802
|
-
const
|
|
15830
|
+
const modelConfig = this.config.models[modelName];
|
|
15831
|
+
const resourceName = modelConfig.resource;
|
|
15832
|
+
const [ok, err, versionInfo] = await tryFn(
|
|
15833
|
+
() => storage.get(storage.getPluginKey(resourceName, "metadata", modelName, "versions"))
|
|
15834
|
+
);
|
|
15803
15835
|
if (ok && versionInfo) {
|
|
15804
15836
|
this.modelVersions.set(modelName, {
|
|
15805
15837
|
currentVersion: versionInfo.currentVersion || 1,
|
|
@@ -15838,16 +15870,22 @@ class MLPlugin extends Plugin {
|
|
|
15838
15870
|
async _updateVersionInfo(modelName, version) {
|
|
15839
15871
|
try {
|
|
15840
15872
|
const storage = this.getStorage();
|
|
15873
|
+
const modelConfig = this.config.models[modelName];
|
|
15874
|
+
const resourceName = modelConfig.resource;
|
|
15841
15875
|
const versionInfo = this.modelVersions.get(modelName) || { currentVersion: 1, latestVersion: 0 };
|
|
15842
15876
|
versionInfo.latestVersion = Math.max(versionInfo.latestVersion, version);
|
|
15843
15877
|
versionInfo.currentVersion = version;
|
|
15844
15878
|
this.modelVersions.set(modelName, versionInfo);
|
|
15845
|
-
await storage.
|
|
15846
|
-
modelName,
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15879
|
+
await storage.set(
|
|
15880
|
+
storage.getPluginKey(resourceName, "metadata", modelName, "versions"),
|
|
15881
|
+
{
|
|
15882
|
+
modelName,
|
|
15883
|
+
currentVersion: versionInfo.currentVersion,
|
|
15884
|
+
latestVersion: versionInfo.latestVersion,
|
|
15885
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15886
|
+
},
|
|
15887
|
+
{ behavior: "body-overflow" }
|
|
15888
|
+
);
|
|
15851
15889
|
if (this.config.verbose) {
|
|
15852
15890
|
console.log(`[MLPlugin] Updated version info for "${modelName}": current=v${versionInfo.currentVersion}, latest=v${versionInfo.latestVersion}`);
|
|
15853
15891
|
}
|
|
@@ -15862,6 +15900,8 @@ class MLPlugin extends Plugin {
|
|
|
15862
15900
|
async _saveModel(modelName) {
|
|
15863
15901
|
try {
|
|
15864
15902
|
const storage = this.getStorage();
|
|
15903
|
+
const modelConfig = this.config.models[modelName];
|
|
15904
|
+
const resourceName = modelConfig.resource;
|
|
15865
15905
|
const exportedModel = await this.models[modelName].export();
|
|
15866
15906
|
if (!exportedModel) {
|
|
15867
15907
|
if (this.config.verbose) {
|
|
@@ -15873,37 +15913,52 @@ class MLPlugin extends Plugin {
|
|
|
15873
15913
|
if (enableVersioning) {
|
|
15874
15914
|
const version = this._getNextVersion(modelName);
|
|
15875
15915
|
const modelStats = this.models[modelName].getStats();
|
|
15876
|
-
await storage.
|
|
15877
|
-
modelName,
|
|
15878
|
-
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
|
|
15884
|
-
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
15916
|
+
await storage.set(
|
|
15917
|
+
storage.getPluginKey(resourceName, "models", modelName, `v${version}`),
|
|
15918
|
+
{
|
|
15919
|
+
modelName,
|
|
15920
|
+
version,
|
|
15921
|
+
type: "model",
|
|
15922
|
+
modelData: exportedModel,
|
|
15923
|
+
// TensorFlow.js model object (will go to body)
|
|
15924
|
+
metrics: {
|
|
15925
|
+
loss: modelStats.loss,
|
|
15926
|
+
accuracy: modelStats.accuracy,
|
|
15927
|
+
samples: modelStats.samples
|
|
15928
|
+
},
|
|
15929
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15930
|
+
},
|
|
15931
|
+
{ behavior: "body-only" }
|
|
15932
|
+
// Large binary data goes to S3 body
|
|
15933
|
+
);
|
|
15888
15934
|
await this._updateVersionInfo(modelName, version);
|
|
15889
|
-
await storage.
|
|
15890
|
-
modelName,
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15935
|
+
await storage.set(
|
|
15936
|
+
storage.getPluginKey(resourceName, "metadata", modelName, "active"),
|
|
15937
|
+
{
|
|
15938
|
+
modelName,
|
|
15939
|
+
version,
|
|
15940
|
+
type: "reference",
|
|
15941
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15942
|
+
},
|
|
15943
|
+
{ behavior: "body-overflow" }
|
|
15944
|
+
// Small metadata
|
|
15945
|
+
);
|
|
15895
15946
|
if (this.config.verbose) {
|
|
15896
|
-
console.log(`[MLPlugin] Saved model "${modelName}" v${version} to
|
|
15947
|
+
console.log(`[MLPlugin] Saved model "${modelName}" v${version} to S3 (resource=${resourceName}/plugin=ml/models/${modelName}/v${version})`);
|
|
15897
15948
|
}
|
|
15898
15949
|
} else {
|
|
15899
|
-
await storage.
|
|
15900
|
-
modelName,
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15950
|
+
await storage.set(
|
|
15951
|
+
storage.getPluginKey(resourceName, "models", modelName, "latest"),
|
|
15952
|
+
{
|
|
15953
|
+
modelName,
|
|
15954
|
+
type: "model",
|
|
15955
|
+
modelData: exportedModel,
|
|
15956
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15957
|
+
},
|
|
15958
|
+
{ behavior: "body-only" }
|
|
15959
|
+
);
|
|
15905
15960
|
if (this.config.verbose) {
|
|
15906
|
-
console.log(`[MLPlugin] Saved model "${modelName}" to
|
|
15961
|
+
console.log(`[MLPlugin] Saved model "${modelName}" to S3 (resource=${resourceName}/plugin=ml/models/${modelName}/latest)`);
|
|
15907
15962
|
}
|
|
15908
15963
|
}
|
|
15909
15964
|
} catch (error) {
|
|
@@ -15911,7 +15966,7 @@ class MLPlugin extends Plugin {
|
|
|
15911
15966
|
}
|
|
15912
15967
|
}
|
|
15913
15968
|
/**
|
|
15914
|
-
* Save intermediate training data to plugin storage (incremental)
|
|
15969
|
+
* Save intermediate training data to plugin storage (incremental - only new samples)
|
|
15915
15970
|
* @private
|
|
15916
15971
|
*/
|
|
15917
15972
|
async _saveTrainingData(modelName, rawData) {
|
|
@@ -15919,64 +15974,106 @@ class MLPlugin extends Plugin {
|
|
|
15919
15974
|
const storage = this.getStorage();
|
|
15920
15975
|
const model = this.models[modelName];
|
|
15921
15976
|
const modelConfig = this.config.models[modelName];
|
|
15977
|
+
const resourceName = modelConfig.resource;
|
|
15922
15978
|
const modelStats = model.getStats();
|
|
15923
15979
|
const enableVersioning = this.config.enableVersioning;
|
|
15924
|
-
const
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
target: item[modelConfig.target]
|
|
15937
|
-
};
|
|
15938
|
-
}),
|
|
15939
|
-
metrics: {
|
|
15940
|
-
loss: modelStats.loss,
|
|
15941
|
-
accuracy: modelStats.accuracy,
|
|
15942
|
-
r2: modelStats.r2
|
|
15943
|
-
},
|
|
15944
|
-
trainedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15945
|
-
};
|
|
15980
|
+
const processedData = rawData.map((item) => {
|
|
15981
|
+
const features = {};
|
|
15982
|
+
modelConfig.features.forEach((feature) => {
|
|
15983
|
+
features[feature] = item[feature];
|
|
15984
|
+
});
|
|
15985
|
+
return {
|
|
15986
|
+
id: item.id || `${Date.now()}_${Math.random()}`,
|
|
15987
|
+
// Use record ID or generate
|
|
15988
|
+
features,
|
|
15989
|
+
target: item[modelConfig.target]
|
|
15990
|
+
};
|
|
15991
|
+
});
|
|
15946
15992
|
if (enableVersioning) {
|
|
15947
|
-
const
|
|
15993
|
+
const version = this._getNextVersion(modelName);
|
|
15994
|
+
const [ok, err, existing] = await tryFn(
|
|
15995
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
|
|
15996
|
+
);
|
|
15948
15997
|
let history = [];
|
|
15998
|
+
let previousSampleIds = /* @__PURE__ */ new Set();
|
|
15949
15999
|
if (ok && existing && existing.history) {
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
16000
|
+
history = existing.history;
|
|
16001
|
+
history.forEach((entry) => {
|
|
16002
|
+
if (entry.sampleIds) {
|
|
16003
|
+
entry.sampleIds.forEach((id) => previousSampleIds.add(id));
|
|
16004
|
+
}
|
|
16005
|
+
});
|
|
15955
16006
|
}
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
16007
|
+
const currentSampleIds = new Set(processedData.map((d) => d.id));
|
|
16008
|
+
const newSamples = processedData.filter((d) => !previousSampleIds.has(d.id));
|
|
16009
|
+
const newSampleIds = newSamples.map((d) => d.id);
|
|
16010
|
+
if (newSamples.length > 0) {
|
|
16011
|
+
await storage.set(
|
|
16012
|
+
storage.getPluginKey(resourceName, "training", "data", modelName, `v${version}`),
|
|
16013
|
+
{
|
|
16014
|
+
modelName,
|
|
16015
|
+
version,
|
|
16016
|
+
samples: newSamples,
|
|
16017
|
+
// Only new samples
|
|
16018
|
+
features: modelConfig.features,
|
|
16019
|
+
target: modelConfig.target,
|
|
16020
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16021
|
+
},
|
|
16022
|
+
{ behavior: "body-only" }
|
|
16023
|
+
// Dataset goes to S3 body
|
|
16024
|
+
);
|
|
16025
|
+
}
|
|
16026
|
+
const historyEntry = {
|
|
16027
|
+
version,
|
|
16028
|
+
totalSamples: processedData.length,
|
|
16029
|
+
// Total cumulative
|
|
16030
|
+
newSamples: newSamples.length,
|
|
16031
|
+
// Only new in this version
|
|
16032
|
+
sampleIds: Array.from(currentSampleIds),
|
|
16033
|
+
// All IDs for this version
|
|
16034
|
+
newSampleIds,
|
|
16035
|
+
// IDs of new samples
|
|
16036
|
+
storageKey: newSamples.length > 0 ? `training/data/${modelName}/v${version}` : null,
|
|
16037
|
+
metrics: {
|
|
16038
|
+
loss: modelStats.loss,
|
|
16039
|
+
accuracy: modelStats.accuracy,
|
|
16040
|
+
r2: modelStats.r2
|
|
16041
|
+
},
|
|
16042
|
+
trainedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16043
|
+
};
|
|
16044
|
+
history.push(historyEntry);
|
|
16045
|
+
await storage.set(
|
|
16046
|
+
storage.getPluginKey(resourceName, "training", "history", modelName),
|
|
16047
|
+
{
|
|
16048
|
+
modelName,
|
|
16049
|
+
type: "training_history",
|
|
16050
|
+
totalTrainings: history.length,
|
|
16051
|
+
latestVersion: version,
|
|
16052
|
+
history,
|
|
16053
|
+
// Array of metadata entries (not full data)
|
|
16054
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16055
|
+
},
|
|
16056
|
+
{ behavior: "body-overflow" }
|
|
16057
|
+
// History metadata
|
|
16058
|
+
);
|
|
15965
16059
|
if (this.config.verbose) {
|
|
15966
|
-
console.log(`[MLPlugin]
|
|
16060
|
+
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})`);
|
|
15967
16061
|
}
|
|
15968
16062
|
} else {
|
|
15969
|
-
await storage.
|
|
15970
|
-
modelName,
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
16063
|
+
await storage.set(
|
|
16064
|
+
storage.getPluginKey(resourceName, "training", "data", modelName, "latest"),
|
|
16065
|
+
{
|
|
16066
|
+
modelName,
|
|
16067
|
+
type: "training_data",
|
|
16068
|
+
samples: processedData,
|
|
16069
|
+
features: modelConfig.features,
|
|
16070
|
+
target: modelConfig.target,
|
|
16071
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16072
|
+
},
|
|
16073
|
+
{ behavior: "body-only" }
|
|
16074
|
+
);
|
|
15978
16075
|
if (this.config.verbose) {
|
|
15979
|
-
console.log(`[MLPlugin] Saved training data for "${modelName}" (${
|
|
16076
|
+
console.log(`[MLPlugin] Saved training data for "${modelName}" (${processedData.length} samples) to S3 (resource=${resourceName}/plugin=ml/training/data/${modelName}/latest)`);
|
|
15980
16077
|
}
|
|
15981
16078
|
}
|
|
15982
16079
|
} catch (error) {
|
|
@@ -15990,17 +16087,22 @@ class MLPlugin extends Plugin {
|
|
|
15990
16087
|
async _loadModel(modelName) {
|
|
15991
16088
|
try {
|
|
15992
16089
|
const storage = this.getStorage();
|
|
16090
|
+
const modelConfig = this.config.models[modelName];
|
|
16091
|
+
const resourceName = modelConfig.resource;
|
|
15993
16092
|
const enableVersioning = this.config.enableVersioning;
|
|
15994
16093
|
if (enableVersioning) {
|
|
15995
|
-
const [okRef, errRef, activeRef] = await tryFn(
|
|
16094
|
+
const [okRef, errRef, activeRef] = await tryFn(
|
|
16095
|
+
() => storage.get(storage.getPluginKey(resourceName, "metadata", modelName, "active"))
|
|
16096
|
+
);
|
|
15996
16097
|
if (okRef && activeRef && activeRef.version) {
|
|
15997
16098
|
const version = activeRef.version;
|
|
15998
|
-
const [ok, err, versionData] = await tryFn(
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16099
|
+
const [ok, err, versionData] = await tryFn(
|
|
16100
|
+
() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`))
|
|
16101
|
+
);
|
|
16102
|
+
if (ok && versionData && versionData.modelData) {
|
|
16103
|
+
await this.models[modelName].import(versionData.modelData);
|
|
16002
16104
|
if (this.config.verbose) {
|
|
16003
|
-
console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (active) from
|
|
16105
|
+
console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (active) from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/v${version})`);
|
|
16004
16106
|
}
|
|
16005
16107
|
return;
|
|
16006
16108
|
}
|
|
@@ -16008,12 +16110,13 @@ class MLPlugin extends Plugin {
|
|
|
16008
16110
|
const versionInfo = this.modelVersions.get(modelName);
|
|
16009
16111
|
if (versionInfo && versionInfo.latestVersion > 0) {
|
|
16010
16112
|
const version = versionInfo.latestVersion;
|
|
16011
|
-
const [ok, err, versionData] = await tryFn(
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16113
|
+
const [ok, err, versionData] = await tryFn(
|
|
16114
|
+
() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`))
|
|
16115
|
+
);
|
|
16116
|
+
if (ok && versionData && versionData.modelData) {
|
|
16117
|
+
await this.models[modelName].import(versionData.modelData);
|
|
16015
16118
|
if (this.config.verbose) {
|
|
16016
|
-
console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (latest) from
|
|
16119
|
+
console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (latest) from S3`);
|
|
16017
16120
|
}
|
|
16018
16121
|
return;
|
|
16019
16122
|
}
|
|
@@ -16022,17 +16125,18 @@ class MLPlugin extends Plugin {
|
|
|
16022
16125
|
console.log(`[MLPlugin] No saved model versions found for "${modelName}"`);
|
|
16023
16126
|
}
|
|
16024
16127
|
} else {
|
|
16025
|
-
const [ok, err, record] = await tryFn(
|
|
16026
|
-
|
|
16128
|
+
const [ok, err, record] = await tryFn(
|
|
16129
|
+
() => storage.get(storage.getPluginKey(resourceName, "models", modelName, "latest"))
|
|
16130
|
+
);
|
|
16131
|
+
if (!ok || !record || !record.modelData) {
|
|
16027
16132
|
if (this.config.verbose) {
|
|
16028
16133
|
console.log(`[MLPlugin] No saved model found for "${modelName}"`);
|
|
16029
16134
|
}
|
|
16030
16135
|
return;
|
|
16031
16136
|
}
|
|
16032
|
-
|
|
16033
|
-
await this.models[modelName].import(modelData);
|
|
16137
|
+
await this.models[modelName].import(record.modelData);
|
|
16034
16138
|
if (this.config.verbose) {
|
|
16035
|
-
console.log(`[MLPlugin] Loaded model "${modelName}" from
|
|
16139
|
+
console.log(`[MLPlugin] Loaded model "${modelName}" from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/latest)`);
|
|
16036
16140
|
}
|
|
16037
16141
|
}
|
|
16038
16142
|
} catch (error) {
|
|
@@ -16040,27 +16144,68 @@ class MLPlugin extends Plugin {
|
|
|
16040
16144
|
}
|
|
16041
16145
|
}
|
|
16042
16146
|
/**
|
|
16043
|
-
* Load training data from plugin storage
|
|
16147
|
+
* Load training data from plugin storage (reconstructs specific version from incremental data)
|
|
16044
16148
|
* @param {string} modelName - Model name
|
|
16149
|
+
* @param {number} version - Version number (optional, defaults to latest)
|
|
16045
16150
|
* @returns {Object|null} Training data or null if not found
|
|
16046
16151
|
*/
|
|
16047
|
-
async getTrainingData(modelName) {
|
|
16152
|
+
async getTrainingData(modelName, version = null) {
|
|
16048
16153
|
try {
|
|
16049
16154
|
const storage = this.getStorage();
|
|
16050
|
-
const
|
|
16051
|
-
|
|
16155
|
+
const modelConfig = this.config.models[modelName];
|
|
16156
|
+
const resourceName = modelConfig.resource;
|
|
16157
|
+
const enableVersioning = this.config.enableVersioning;
|
|
16158
|
+
if (!enableVersioning) {
|
|
16159
|
+
const [ok, err, record] = await tryFn(
|
|
16160
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "data", modelName, "latest"))
|
|
16161
|
+
);
|
|
16162
|
+
if (!ok || !record) {
|
|
16163
|
+
if (this.config.verbose) {
|
|
16164
|
+
console.log(`[MLPlugin] No saved training data found for "${modelName}"`);
|
|
16165
|
+
}
|
|
16166
|
+
return null;
|
|
16167
|
+
}
|
|
16168
|
+
return {
|
|
16169
|
+
modelName: record.modelName,
|
|
16170
|
+
samples: record.samples,
|
|
16171
|
+
features: record.features,
|
|
16172
|
+
target: record.target,
|
|
16173
|
+
data: record.samples,
|
|
16174
|
+
savedAt: record.savedAt
|
|
16175
|
+
};
|
|
16176
|
+
}
|
|
16177
|
+
const [okHistory, errHistory, historyData] = await tryFn(
|
|
16178
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
|
|
16179
|
+
);
|
|
16180
|
+
if (!okHistory || !historyData || !historyData.history) {
|
|
16052
16181
|
if (this.config.verbose) {
|
|
16053
|
-
console.log(`[MLPlugin] No
|
|
16182
|
+
console.log(`[MLPlugin] No training history found for "${modelName}"`);
|
|
16054
16183
|
}
|
|
16055
16184
|
return null;
|
|
16056
16185
|
}
|
|
16186
|
+
const targetVersion = version || historyData.latestVersion;
|
|
16187
|
+
const reconstructedSamples = [];
|
|
16188
|
+
for (const entry of historyData.history) {
|
|
16189
|
+
if (entry.version > targetVersion) break;
|
|
16190
|
+
if (entry.storageKey && entry.newSamples > 0) {
|
|
16191
|
+
const [ok, err, versionData] = await tryFn(
|
|
16192
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "data", modelName, `v${entry.version}`))
|
|
16193
|
+
);
|
|
16194
|
+
if (ok && versionData && versionData.samples) {
|
|
16195
|
+
reconstructedSamples.push(...versionData.samples);
|
|
16196
|
+
}
|
|
16197
|
+
}
|
|
16198
|
+
}
|
|
16199
|
+
const targetEntry = historyData.history.find((e) => e.version === targetVersion);
|
|
16057
16200
|
return {
|
|
16058
|
-
modelName
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
|
|
16063
|
-
|
|
16201
|
+
modelName,
|
|
16202
|
+
version: targetVersion,
|
|
16203
|
+
samples: reconstructedSamples,
|
|
16204
|
+
totalSamples: reconstructedSamples.length,
|
|
16205
|
+
features: modelConfig.features,
|
|
16206
|
+
target: modelConfig.target,
|
|
16207
|
+
metrics: targetEntry?.metrics,
|
|
16208
|
+
savedAt: targetEntry?.trainedAt
|
|
16064
16209
|
};
|
|
16065
16210
|
} catch (error) {
|
|
16066
16211
|
console.error(`[MLPlugin] Failed to load training data for "${modelName}":`, error.message);
|
|
@@ -16068,15 +16213,29 @@ class MLPlugin extends Plugin {
|
|
|
16068
16213
|
}
|
|
16069
16214
|
}
|
|
16070
16215
|
/**
|
|
16071
|
-
* Delete model from plugin storage
|
|
16216
|
+
* Delete model from plugin storage (all versions)
|
|
16072
16217
|
* @private
|
|
16073
16218
|
*/
|
|
16074
16219
|
async _deleteModel(modelName) {
|
|
16075
16220
|
try {
|
|
16076
16221
|
const storage = this.getStorage();
|
|
16077
|
-
|
|
16222
|
+
const modelConfig = this.config.models[modelName];
|
|
16223
|
+
const resourceName = modelConfig.resource;
|
|
16224
|
+
const enableVersioning = this.config.enableVersioning;
|
|
16225
|
+
if (enableVersioning) {
|
|
16226
|
+
const versionInfo = this.modelVersions.get(modelName);
|
|
16227
|
+
if (versionInfo && versionInfo.latestVersion > 0) {
|
|
16228
|
+
for (let v = 1; v <= versionInfo.latestVersion; v++) {
|
|
16229
|
+
await storage.delete(storage.getPluginKey(resourceName, "models", modelName, `v${v}`));
|
|
16230
|
+
}
|
|
16231
|
+
}
|
|
16232
|
+
await storage.delete(storage.getPluginKey(resourceName, "metadata", modelName, "active"));
|
|
16233
|
+
await storage.delete(storage.getPluginKey(resourceName, "metadata", modelName, "versions"));
|
|
16234
|
+
} else {
|
|
16235
|
+
await storage.delete(storage.getPluginKey(resourceName, "models", modelName, "latest"));
|
|
16236
|
+
}
|
|
16078
16237
|
if (this.config.verbose) {
|
|
16079
|
-
console.log(`[MLPlugin] Deleted model "${modelName}" from plugin
|
|
16238
|
+
console.log(`[MLPlugin] Deleted model "${modelName}" from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/)`);
|
|
16080
16239
|
}
|
|
16081
16240
|
} catch (error) {
|
|
16082
16241
|
if (this.config.verbose) {
|
|
@@ -16085,15 +16244,32 @@ class MLPlugin extends Plugin {
|
|
|
16085
16244
|
}
|
|
16086
16245
|
}
|
|
16087
16246
|
/**
|
|
16088
|
-
* Delete training data from plugin storage
|
|
16247
|
+
* Delete training data from plugin storage (all versions)
|
|
16089
16248
|
* @private
|
|
16090
16249
|
*/
|
|
16091
16250
|
async _deleteTrainingData(modelName) {
|
|
16092
16251
|
try {
|
|
16093
16252
|
const storage = this.getStorage();
|
|
16094
|
-
|
|
16253
|
+
const modelConfig = this.config.models[modelName];
|
|
16254
|
+
const resourceName = modelConfig.resource;
|
|
16255
|
+
const enableVersioning = this.config.enableVersioning;
|
|
16256
|
+
if (enableVersioning) {
|
|
16257
|
+
const [ok, err, historyData] = await tryFn(
|
|
16258
|
+
() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
|
|
16259
|
+
);
|
|
16260
|
+
if (ok && historyData && historyData.history) {
|
|
16261
|
+
for (const entry of historyData.history) {
|
|
16262
|
+
if (entry.storageKey) {
|
|
16263
|
+
await storage.delete(storage.getPluginKey(resourceName, "training", "data", modelName, `v${entry.version}`));
|
|
16264
|
+
}
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
16267
|
+
await storage.delete(storage.getPluginKey(resourceName, "training", "history", modelName));
|
|
16268
|
+
} else {
|
|
16269
|
+
await storage.delete(storage.getPluginKey(resourceName, "training", "data", modelName, "latest"));
|
|
16270
|
+
}
|
|
16095
16271
|
if (this.config.verbose) {
|
|
16096
|
-
console.log(`[MLPlugin] Deleted training data for "${modelName}" from plugin
|
|
16272
|
+
console.log(`[MLPlugin] Deleted training data for "${modelName}" from S3 (resource=${resourceName}/plugin=ml/training/)`);
|
|
16097
16273
|
}
|
|
16098
16274
|
} catch (error) {
|
|
16099
16275
|
if (this.config.verbose) {
|
|
@@ -16112,17 +16288,18 @@ class MLPlugin extends Plugin {
|
|
|
16112
16288
|
}
|
|
16113
16289
|
try {
|
|
16114
16290
|
const storage = this.getStorage();
|
|
16291
|
+
const modelConfig = this.config.models[modelName];
|
|
16292
|
+
const resourceName = modelConfig.resource;
|
|
16115
16293
|
const versionInfo = this.modelVersions.get(modelName) || { latestVersion: 0 };
|
|
16116
16294
|
const versions = [];
|
|
16117
16295
|
for (let v = 1; v <= versionInfo.latestVersion; v++) {
|
|
16118
|
-
const [ok, err, versionData] = await tryFn(() => storage.get(`
|
|
16296
|
+
const [ok, err, versionData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${v}`)));
|
|
16119
16297
|
if (ok && versionData) {
|
|
16120
|
-
const metrics = versionData.metrics ? JSON.parse(versionData.metrics) : {};
|
|
16121
16298
|
versions.push({
|
|
16122
16299
|
version: v,
|
|
16123
16300
|
savedAt: versionData.savedAt,
|
|
16124
16301
|
isCurrent: v === versionInfo.currentVersion,
|
|
16125
|
-
metrics
|
|
16302
|
+
metrics: versionData.metrics
|
|
16126
16303
|
});
|
|
16127
16304
|
}
|
|
16128
16305
|
}
|
|
@@ -16146,12 +16323,16 @@ class MLPlugin extends Plugin {
|
|
|
16146
16323
|
}
|
|
16147
16324
|
try {
|
|
16148
16325
|
const storage = this.getStorage();
|
|
16149
|
-
const
|
|
16326
|
+
const modelConfig = this.config.models[modelName];
|
|
16327
|
+
const resourceName = modelConfig.resource;
|
|
16328
|
+
const [ok, err, versionData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`)));
|
|
16150
16329
|
if (!ok || !versionData) {
|
|
16151
16330
|
throw new MLError(`Version ${version} not found for model "${modelName}"`, { modelName, version });
|
|
16152
16331
|
}
|
|
16153
|
-
|
|
16154
|
-
|
|
16332
|
+
if (!versionData.modelData) {
|
|
16333
|
+
throw new MLError(`Model data not found in version ${version}`, { modelName, version });
|
|
16334
|
+
}
|
|
16335
|
+
await this.models[modelName].import(versionData.modelData);
|
|
16155
16336
|
const versionInfo = this.modelVersions.get(modelName);
|
|
16156
16337
|
if (versionInfo) {
|
|
16157
16338
|
versionInfo.currentVersion = version;
|
|
@@ -16179,10 +16360,12 @@ class MLPlugin extends Plugin {
|
|
|
16179
16360
|
if (!this.config.enableVersioning) {
|
|
16180
16361
|
throw new MLError("Versioning is not enabled", { modelName });
|
|
16181
16362
|
}
|
|
16363
|
+
const modelConfig = this.config.models[modelName];
|
|
16364
|
+
const resourceName = modelConfig.resource;
|
|
16182
16365
|
await this.loadModelVersion(modelName, version);
|
|
16183
16366
|
await this._updateVersionInfo(modelName, version);
|
|
16184
16367
|
const storage = this.getStorage();
|
|
16185
|
-
await storage.
|
|
16368
|
+
await storage.set(storage.getPluginKey(resourceName, "metadata", modelName, "active"), {
|
|
16186
16369
|
modelName,
|
|
16187
16370
|
version,
|
|
16188
16371
|
type: "reference",
|
|
@@ -16204,7 +16387,9 @@ class MLPlugin extends Plugin {
|
|
|
16204
16387
|
}
|
|
16205
16388
|
try {
|
|
16206
16389
|
const storage = this.getStorage();
|
|
16207
|
-
const
|
|
16390
|
+
const modelConfig = this.config.models[modelName];
|
|
16391
|
+
const resourceName = modelConfig.resource;
|
|
16392
|
+
const [ok, err, historyData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName)));
|
|
16208
16393
|
if (!ok || !historyData) {
|
|
16209
16394
|
return null;
|
|
16210
16395
|
}
|
|
@@ -16233,8 +16418,10 @@ class MLPlugin extends Plugin {
|
|
|
16233
16418
|
}
|
|
16234
16419
|
try {
|
|
16235
16420
|
const storage = this.getStorage();
|
|
16236
|
-
const
|
|
16237
|
-
const
|
|
16421
|
+
const modelConfig = this.config.models[modelName];
|
|
16422
|
+
const resourceName = modelConfig.resource;
|
|
16423
|
+
const [ok1, err1, v1Data] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version1}`)));
|
|
16424
|
+
const [ok2, err2, v2Data] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version2}`)));
|
|
16238
16425
|
if (!ok1 || !v1Data) {
|
|
16239
16426
|
throw new MLError(`Version ${version1} not found`, { modelName, version: version1 });
|
|
16240
16427
|
}
|
|
@@ -16690,7 +16877,7 @@ class RelationPlugin extends Plugin {
|
|
|
16690
16877
|
if (this.verbose) {
|
|
16691
16878
|
console.log(`[RelationPlugin] Installed with ${Object.keys(this.relations).length} resources`);
|
|
16692
16879
|
}
|
|
16693
|
-
this.emit("installed", {
|
|
16880
|
+
this.emit("db:plugin:installed", {
|
|
16694
16881
|
plugin: "RelationPlugin",
|
|
16695
16882
|
resources: Object.keys(this.relations)
|
|
16696
16883
|
});
|
|
@@ -20265,7 +20452,7 @@ class S3Client extends EventEmitter {
|
|
|
20265
20452
|
return client;
|
|
20266
20453
|
}
|
|
20267
20454
|
async sendCommand(command) {
|
|
20268
|
-
this.emit("
|
|
20455
|
+
this.emit("cl:request", command.constructor.name, command.input);
|
|
20269
20456
|
const [ok, err, response] = await tryFn(() => this.client.send(command));
|
|
20270
20457
|
if (!ok) {
|
|
20271
20458
|
const bucket = this.config.bucket;
|
|
@@ -20277,7 +20464,7 @@ class S3Client extends EventEmitter {
|
|
|
20277
20464
|
commandInput: command.input
|
|
20278
20465
|
});
|
|
20279
20466
|
}
|
|
20280
|
-
this.emit("
|
|
20467
|
+
this.emit("cl:response", command.constructor.name, response, command.input);
|
|
20281
20468
|
return response;
|
|
20282
20469
|
}
|
|
20283
20470
|
async putObject({ key, metadata, contentType, body, contentEncoding, contentLength, ifMatch }) {
|
|
@@ -20302,7 +20489,7 @@ class S3Client extends EventEmitter {
|
|
|
20302
20489
|
if (contentLength !== void 0) options.ContentLength = contentLength;
|
|
20303
20490
|
if (ifMatch !== void 0) options.IfMatch = ifMatch;
|
|
20304
20491
|
const [ok, err, response] = await tryFn(() => this.sendCommand(new PutObjectCommand(options)));
|
|
20305
|
-
this.emit("
|
|
20492
|
+
this.emit("cl:PutObject", err || response, { key, metadata, contentType, body, contentEncoding, contentLength });
|
|
20306
20493
|
if (!ok) {
|
|
20307
20494
|
throw mapAwsError(err, {
|
|
20308
20495
|
bucket: this.config.bucket,
|
|
@@ -20330,7 +20517,7 @@ class S3Client extends EventEmitter {
|
|
|
20330
20517
|
}
|
|
20331
20518
|
return res;
|
|
20332
20519
|
});
|
|
20333
|
-
this.emit("
|
|
20520
|
+
this.emit("cl:GetObject", err || response, { key });
|
|
20334
20521
|
if (!ok) {
|
|
20335
20522
|
throw mapAwsError(err, {
|
|
20336
20523
|
bucket: this.config.bucket,
|
|
@@ -20358,7 +20545,7 @@ class S3Client extends EventEmitter {
|
|
|
20358
20545
|
}
|
|
20359
20546
|
return res;
|
|
20360
20547
|
});
|
|
20361
|
-
this.emit("
|
|
20548
|
+
this.emit("cl:HeadObject", err || response, { key });
|
|
20362
20549
|
if (!ok) {
|
|
20363
20550
|
throw mapAwsError(err, {
|
|
20364
20551
|
bucket: this.config.bucket,
|
|
@@ -20391,7 +20578,7 @@ class S3Client extends EventEmitter {
|
|
|
20391
20578
|
options.ContentType = contentType;
|
|
20392
20579
|
}
|
|
20393
20580
|
const [ok, err, response] = await tryFn(() => this.sendCommand(new CopyObjectCommand(options)));
|
|
20394
|
-
this.emit("
|
|
20581
|
+
this.emit("cl:CopyObject", err || response, { from, to, metadataDirective });
|
|
20395
20582
|
if (!ok) {
|
|
20396
20583
|
throw mapAwsError(err, {
|
|
20397
20584
|
bucket: this.config.bucket,
|
|
@@ -20416,7 +20603,7 @@ class S3Client extends EventEmitter {
|
|
|
20416
20603
|
Key: keyPrefix ? path$1.join(keyPrefix, key) : key
|
|
20417
20604
|
};
|
|
20418
20605
|
const [ok, err, response] = await tryFn(() => this.sendCommand(new DeleteObjectCommand(options)));
|
|
20419
|
-
this.emit("
|
|
20606
|
+
this.emit("cl:DeleteObject", err || response, { key });
|
|
20420
20607
|
if (!ok) {
|
|
20421
20608
|
throw mapAwsError(err, {
|
|
20422
20609
|
bucket: this.config.bucket,
|
|
@@ -20456,7 +20643,7 @@ class S3Client extends EventEmitter {
|
|
|
20456
20643
|
deleted: results,
|
|
20457
20644
|
notFound: errors
|
|
20458
20645
|
};
|
|
20459
|
-
this.emit("
|
|
20646
|
+
this.emit("cl:DeleteObjects", report, keys);
|
|
20460
20647
|
return report;
|
|
20461
20648
|
}
|
|
20462
20649
|
/**
|
|
@@ -20486,7 +20673,7 @@ class S3Client extends EventEmitter {
|
|
|
20486
20673
|
const deleteResponse = await this.client.send(deleteCommand);
|
|
20487
20674
|
const deletedCount = deleteResponse.Deleted ? deleteResponse.Deleted.length : 0;
|
|
20488
20675
|
totalDeleted += deletedCount;
|
|
20489
|
-
this.emit("
|
|
20676
|
+
this.emit("cl:DeleteAll", {
|
|
20490
20677
|
prefix,
|
|
20491
20678
|
batch: deletedCount,
|
|
20492
20679
|
total: totalDeleted
|
|
@@ -20494,7 +20681,7 @@ class S3Client extends EventEmitter {
|
|
|
20494
20681
|
}
|
|
20495
20682
|
continuationToken = listResponse.IsTruncated ? listResponse.NextContinuationToken : void 0;
|
|
20496
20683
|
} while (continuationToken);
|
|
20497
|
-
this.emit("
|
|
20684
|
+
this.emit("cl:DeleteAllComplete", {
|
|
20498
20685
|
prefix,
|
|
20499
20686
|
totalDeleted
|
|
20500
20687
|
});
|
|
@@ -20525,7 +20712,7 @@ class S3Client extends EventEmitter {
|
|
|
20525
20712
|
if (!ok) {
|
|
20526
20713
|
throw new UnknownError("Unknown error in listObjects", { prefix, bucket: this.config.bucket, original: err });
|
|
20527
20714
|
}
|
|
20528
|
-
this.emit("
|
|
20715
|
+
this.emit("cl:ListObjects", response, options);
|
|
20529
20716
|
return response;
|
|
20530
20717
|
}
|
|
20531
20718
|
async count({ prefix } = {}) {
|
|
@@ -20542,7 +20729,7 @@ class S3Client extends EventEmitter {
|
|
|
20542
20729
|
truncated = response.IsTruncated || false;
|
|
20543
20730
|
continuationToken = response.NextContinuationToken;
|
|
20544
20731
|
}
|
|
20545
|
-
this.emit("
|
|
20732
|
+
this.emit("cl:Count", count, { prefix });
|
|
20546
20733
|
return count;
|
|
20547
20734
|
}
|
|
20548
20735
|
async getAllKeys({ prefix } = {}) {
|
|
@@ -20564,7 +20751,7 @@ class S3Client extends EventEmitter {
|
|
|
20564
20751
|
if (this.config.keyPrefix) {
|
|
20565
20752
|
keys = keys.map((x) => x.replace(this.config.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace(`/`, "") : x);
|
|
20566
20753
|
}
|
|
20567
|
-
this.emit("
|
|
20754
|
+
this.emit("cl:GetAllKeys", keys, { prefix });
|
|
20568
20755
|
return keys;
|
|
20569
20756
|
}
|
|
20570
20757
|
async getContinuationTokenAfterOffset(params = {}) {
|
|
@@ -20593,7 +20780,7 @@ class S3Client extends EventEmitter {
|
|
|
20593
20780
|
break;
|
|
20594
20781
|
}
|
|
20595
20782
|
}
|
|
20596
|
-
this.emit("
|
|
20783
|
+
this.emit("cl:GetContinuationTokenAfterOffset", continuationToken || null, params);
|
|
20597
20784
|
return continuationToken || null;
|
|
20598
20785
|
}
|
|
20599
20786
|
async getKeysPage(params = {}) {
|
|
@@ -20611,7 +20798,7 @@ class S3Client extends EventEmitter {
|
|
|
20611
20798
|
offset
|
|
20612
20799
|
});
|
|
20613
20800
|
if (!continuationToken) {
|
|
20614
|
-
this.emit("
|
|
20801
|
+
this.emit("cl:GetKeysPage", [], params);
|
|
20615
20802
|
return [];
|
|
20616
20803
|
}
|
|
20617
20804
|
}
|
|
@@ -20634,7 +20821,7 @@ class S3Client extends EventEmitter {
|
|
|
20634
20821
|
if (this.config.keyPrefix) {
|
|
20635
20822
|
keys = keys.map((x) => x.replace(this.config.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace(`/`, "") : x);
|
|
20636
20823
|
}
|
|
20637
|
-
this.emit("
|
|
20824
|
+
this.emit("cl:GetKeysPage", keys, params);
|
|
20638
20825
|
return keys;
|
|
20639
20826
|
}
|
|
20640
20827
|
async moveAllObjects({ prefixFrom, prefixTo }) {
|
|
@@ -20652,7 +20839,7 @@ class S3Client extends EventEmitter {
|
|
|
20652
20839
|
}
|
|
20653
20840
|
return to;
|
|
20654
20841
|
});
|
|
20655
|
-
this.emit("
|
|
20842
|
+
this.emit("cl:MoveAllObjects", { results, errors }, { prefixFrom, prefixTo });
|
|
20656
20843
|
if (errors.length > 0) {
|
|
20657
20844
|
throw new UnknownError("Some objects could not be moved", {
|
|
20658
20845
|
bucket: this.config.bucket,
|
|
@@ -23507,6 +23694,20 @@ ${errorDetails}`,
|
|
|
23507
23694
|
if (typeof body === "object") return Buffer.byteLength(JSON.stringify(body), "utf8");
|
|
23508
23695
|
return Buffer.byteLength(String(body), "utf8");
|
|
23509
23696
|
}
|
|
23697
|
+
/**
|
|
23698
|
+
* Emit standardized events with optional ID-specific variant
|
|
23699
|
+
*
|
|
23700
|
+
* @private
|
|
23701
|
+
* @param {string} event - Event name
|
|
23702
|
+
* @param {Object} payload - Event payload
|
|
23703
|
+
* @param {string} [id] - Optional ID for ID-specific events
|
|
23704
|
+
*/
|
|
23705
|
+
_emitStandardized(event, payload, id = null) {
|
|
23706
|
+
this.emit(event, payload);
|
|
23707
|
+
if (id) {
|
|
23708
|
+
this.emit(`${event}:${id}`, payload);
|
|
23709
|
+
}
|
|
23710
|
+
}
|
|
23510
23711
|
/**
|
|
23511
23712
|
* Insert a new resource object
|
|
23512
23713
|
* @param {Object} attributes - Resource attributes
|
|
@@ -23647,11 +23848,11 @@ ${errorDetails}`,
|
|
|
23647
23848
|
for (const hook of nonPartitionHooks) {
|
|
23648
23849
|
finalResult = await hook(finalResult);
|
|
23649
23850
|
}
|
|
23650
|
-
this.
|
|
23851
|
+
this._emitStandardized("inserted", finalResult, finalResult?.id || insertedObject?.id);
|
|
23651
23852
|
return finalResult;
|
|
23652
23853
|
} else {
|
|
23653
23854
|
const finalResult = await this.executeHooks("afterInsert", insertedObject);
|
|
23654
|
-
this.
|
|
23855
|
+
this._emitStandardized("inserted", finalResult, finalResult?.id || insertedObject?.id);
|
|
23655
23856
|
return finalResult;
|
|
23656
23857
|
}
|
|
23657
23858
|
}
|
|
@@ -23715,7 +23916,7 @@ ${errorDetails}`,
|
|
|
23715
23916
|
data = await this.applyVersionMapping(data, objectVersion, this.version);
|
|
23716
23917
|
}
|
|
23717
23918
|
data = await this.executeHooks("afterGet", data);
|
|
23718
|
-
this.
|
|
23919
|
+
this._emitStandardized("get", "fetched", data, data.id);
|
|
23719
23920
|
const value = data;
|
|
23720
23921
|
return value;
|
|
23721
23922
|
}
|
|
@@ -23966,19 +24167,19 @@ ${errorDetails}`,
|
|
|
23966
24167
|
for (const hook of nonPartitionHooks) {
|
|
23967
24168
|
finalResult = await hook(finalResult);
|
|
23968
24169
|
}
|
|
23969
|
-
this.
|
|
24170
|
+
this._emitStandardized("updated", {
|
|
23970
24171
|
...updatedData,
|
|
23971
24172
|
$before: { ...originalData },
|
|
23972
24173
|
$after: { ...finalResult }
|
|
23973
|
-
});
|
|
24174
|
+
}, updatedData.id);
|
|
23974
24175
|
return finalResult;
|
|
23975
24176
|
} else {
|
|
23976
24177
|
const finalResult = await this.executeHooks("afterUpdate", updatedData);
|
|
23977
|
-
this.
|
|
24178
|
+
this._emitStandardized("updated", {
|
|
23978
24179
|
...updatedData,
|
|
23979
24180
|
$before: { ...originalData },
|
|
23980
24181
|
$after: { ...finalResult }
|
|
23981
|
-
});
|
|
24182
|
+
}, updatedData.id);
|
|
23982
24183
|
return finalResult;
|
|
23983
24184
|
}
|
|
23984
24185
|
}
|
|
@@ -24377,11 +24578,11 @@ ${errorDetails}`,
|
|
|
24377
24578
|
for (const hook of nonPartitionHooks) {
|
|
24378
24579
|
finalResult = await hook(finalResult);
|
|
24379
24580
|
}
|
|
24380
|
-
this.
|
|
24581
|
+
this._emitStandardized("updated", {
|
|
24381
24582
|
...updatedData,
|
|
24382
24583
|
$before: { ...originalData },
|
|
24383
24584
|
$after: { ...finalResult }
|
|
24384
|
-
});
|
|
24585
|
+
}, updatedData.id);
|
|
24385
24586
|
return {
|
|
24386
24587
|
success: true,
|
|
24387
24588
|
data: finalResult,
|
|
@@ -24390,11 +24591,11 @@ ${errorDetails}`,
|
|
|
24390
24591
|
} else {
|
|
24391
24592
|
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
24392
24593
|
const finalResult = await this.executeHooks("afterUpdate", updatedData);
|
|
24393
|
-
this.
|
|
24594
|
+
this._emitStandardized("updated", {
|
|
24394
24595
|
...updatedData,
|
|
24395
24596
|
$before: { ...originalData },
|
|
24396
24597
|
$after: { ...finalResult }
|
|
24397
|
-
});
|
|
24598
|
+
}, updatedData.id);
|
|
24398
24599
|
return {
|
|
24399
24600
|
success: true,
|
|
24400
24601
|
data: finalResult,
|
|
@@ -24425,11 +24626,11 @@ ${errorDetails}`,
|
|
|
24425
24626
|
await this.executeHooks("beforeDelete", objectData);
|
|
24426
24627
|
const key = this.getResourceKey(id);
|
|
24427
24628
|
const [ok2, err2, response] = await tryFn(() => this.client.deleteObject(key));
|
|
24428
|
-
this.
|
|
24629
|
+
this._emitStandardized("delete", "deleted", {
|
|
24429
24630
|
...objectData,
|
|
24430
24631
|
$before: { ...objectData },
|
|
24431
24632
|
$after: null
|
|
24432
|
-
});
|
|
24633
|
+
}, id);
|
|
24433
24634
|
if (deleteError) {
|
|
24434
24635
|
throw mapAwsError(deleteError, {
|
|
24435
24636
|
bucket: this.client.config.bucket,
|
|
@@ -24553,7 +24754,7 @@ ${errorDetails}`,
|
|
|
24553
24754
|
}
|
|
24554
24755
|
const count = await this.client.count({ prefix });
|
|
24555
24756
|
await this.executeHooks("afterCount", { count, partition, partitionValues });
|
|
24556
|
-
this.
|
|
24757
|
+
this._emitStandardized("count", "counted", count);
|
|
24557
24758
|
return count;
|
|
24558
24759
|
}
|
|
24559
24760
|
/**
|
|
@@ -24576,7 +24777,7 @@ ${errorDetails}`,
|
|
|
24576
24777
|
const result = await this.insert(attributes);
|
|
24577
24778
|
return result;
|
|
24578
24779
|
});
|
|
24579
|
-
this.
|
|
24780
|
+
this._emitStandardized("insertMany", "inserted-many", objects.length);
|
|
24580
24781
|
return results;
|
|
24581
24782
|
}
|
|
24582
24783
|
/**
|
|
@@ -24611,7 +24812,7 @@ ${errorDetails}`,
|
|
|
24611
24812
|
return response;
|
|
24612
24813
|
});
|
|
24613
24814
|
await this.executeHooks("afterDeleteMany", { ids, results });
|
|
24614
|
-
this.
|
|
24815
|
+
this._emitStandardized("deleteMany", "deleted-many", ids.length);
|
|
24615
24816
|
return results;
|
|
24616
24817
|
}
|
|
24617
24818
|
async deleteAll() {
|
|
@@ -24620,7 +24821,7 @@ ${errorDetails}`,
|
|
|
24620
24821
|
}
|
|
24621
24822
|
const prefix = `resource=${this.name}/data`;
|
|
24622
24823
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24623
|
-
this.
|
|
24824
|
+
this._emitStandardized("deleteAll", "deleted-all", {
|
|
24624
24825
|
version: this.version,
|
|
24625
24826
|
prefix,
|
|
24626
24827
|
deletedCount
|
|
@@ -24637,7 +24838,7 @@ ${errorDetails}`,
|
|
|
24637
24838
|
}
|
|
24638
24839
|
const prefix = `resource=${this.name}`;
|
|
24639
24840
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24640
|
-
this.
|
|
24841
|
+
this._emitStandardized("deleteAllData", "deleted-all-data", {
|
|
24641
24842
|
resource: this.name,
|
|
24642
24843
|
prefix,
|
|
24643
24844
|
deletedCount
|
|
@@ -24707,7 +24908,7 @@ ${errorDetails}`,
|
|
|
24707
24908
|
const idPart = parts.find((part) => part.startsWith("id="));
|
|
24708
24909
|
return idPart ? idPart.replace("id=", "") : null;
|
|
24709
24910
|
}).filter(Boolean);
|
|
24710
|
-
this.
|
|
24911
|
+
this._emitStandardized("listIds", "listed-ids", ids.length);
|
|
24711
24912
|
return ids;
|
|
24712
24913
|
}
|
|
24713
24914
|
/**
|
|
@@ -24749,12 +24950,12 @@ ${errorDetails}`,
|
|
|
24749
24950
|
const [ok, err, ids] = await tryFn(() => this.listIds({ limit, offset }));
|
|
24750
24951
|
if (!ok) throw err;
|
|
24751
24952
|
const results = await this.processListResults(ids, "main");
|
|
24752
|
-
this.
|
|
24953
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
24753
24954
|
return results;
|
|
24754
24955
|
}
|
|
24755
24956
|
async listPartition({ partition, partitionValues, limit, offset = 0 }) {
|
|
24756
24957
|
if (!this.config.partitions?.[partition]) {
|
|
24757
|
-
this.
|
|
24958
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 0 });
|
|
24758
24959
|
return [];
|
|
24759
24960
|
}
|
|
24760
24961
|
const partitionDef = this.config.partitions[partition];
|
|
@@ -24764,7 +24965,7 @@ ${errorDetails}`,
|
|
|
24764
24965
|
const ids = this.extractIdsFromKeys(keys).slice(offset);
|
|
24765
24966
|
const filteredIds = limit ? ids.slice(0, limit) : ids;
|
|
24766
24967
|
const results = await this.processPartitionResults(filteredIds, partition, partitionDef, keys);
|
|
24767
|
-
this.
|
|
24968
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: results.length, errors: 0 });
|
|
24768
24969
|
return results;
|
|
24769
24970
|
}
|
|
24770
24971
|
/**
|
|
@@ -24809,7 +25010,7 @@ ${errorDetails}`,
|
|
|
24809
25010
|
}
|
|
24810
25011
|
return this.handleResourceError(err, id, context);
|
|
24811
25012
|
});
|
|
24812
|
-
this.
|
|
25013
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
24813
25014
|
return results;
|
|
24814
25015
|
}
|
|
24815
25016
|
/**
|
|
@@ -24872,10 +25073,10 @@ ${errorDetails}`,
|
|
|
24872
25073
|
*/
|
|
24873
25074
|
handleListError(error, { partition, partitionValues }) {
|
|
24874
25075
|
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
24875
|
-
this.
|
|
25076
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
24876
25077
|
return [];
|
|
24877
25078
|
}
|
|
24878
|
-
this.
|
|
25079
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
24879
25080
|
return [];
|
|
24880
25081
|
}
|
|
24881
25082
|
/**
|
|
@@ -24908,7 +25109,7 @@ ${errorDetails}`,
|
|
|
24908
25109
|
throw err;
|
|
24909
25110
|
});
|
|
24910
25111
|
const finalResults = await this.executeHooks("afterGetMany", results);
|
|
24911
|
-
this.
|
|
25112
|
+
this._emitStandardized("getMany", "fetched-many", ids.length);
|
|
24912
25113
|
return finalResults;
|
|
24913
25114
|
}
|
|
24914
25115
|
/**
|
|
@@ -24994,7 +25195,7 @@ ${errorDetails}`,
|
|
|
24994
25195
|
hasTotalItems: totalItems !== null
|
|
24995
25196
|
}
|
|
24996
25197
|
};
|
|
24997
|
-
this.
|
|
25198
|
+
this._emitStandardized("page", "paginated", result2);
|
|
24998
25199
|
return result2;
|
|
24999
25200
|
});
|
|
25000
25201
|
if (ok) return result;
|
|
@@ -25064,7 +25265,7 @@ ${errorDetails}`,
|
|
|
25064
25265
|
contentType
|
|
25065
25266
|
}));
|
|
25066
25267
|
if (!ok2) throw err2;
|
|
25067
|
-
this.
|
|
25268
|
+
this._emitStandardized("setContent", "content-set", { id, contentType, contentLength: buffer.length }, id);
|
|
25068
25269
|
return updatedData;
|
|
25069
25270
|
}
|
|
25070
25271
|
/**
|
|
@@ -25093,7 +25294,7 @@ ${errorDetails}`,
|
|
|
25093
25294
|
}
|
|
25094
25295
|
const buffer = Buffer.from(await response.Body.transformToByteArray());
|
|
25095
25296
|
const contentType = response.ContentType || null;
|
|
25096
|
-
this.
|
|
25297
|
+
this._emitStandardized("content", "content-fetched", { id, contentLength: buffer.length, contentType }, id);
|
|
25097
25298
|
return {
|
|
25098
25299
|
buffer,
|
|
25099
25300
|
contentType
|
|
@@ -25125,7 +25326,7 @@ ${errorDetails}`,
|
|
|
25125
25326
|
metadata: existingMetadata
|
|
25126
25327
|
}));
|
|
25127
25328
|
if (!ok2) throw err2;
|
|
25128
|
-
this.
|
|
25329
|
+
this._emitStandardized("deleteContent", "content-deleted", id, id);
|
|
25129
25330
|
return response;
|
|
25130
25331
|
}
|
|
25131
25332
|
/**
|
|
@@ -25437,7 +25638,7 @@ ${errorDetails}`,
|
|
|
25437
25638
|
const data = await this.get(id);
|
|
25438
25639
|
data._partition = partitionName;
|
|
25439
25640
|
data._partitionValues = partitionValues;
|
|
25440
|
-
this.
|
|
25641
|
+
this._emitStandardized("getFromPartition", "partition-fetched", data, data.id);
|
|
25441
25642
|
return data;
|
|
25442
25643
|
}
|
|
25443
25644
|
/**
|
|
@@ -25686,6 +25887,123 @@ ${errorDetails}`,
|
|
|
25686
25887
|
}
|
|
25687
25888
|
return out;
|
|
25688
25889
|
}
|
|
25890
|
+
// ============================================================================
|
|
25891
|
+
// STATE MACHINE METHODS
|
|
25892
|
+
// ============================================================================
|
|
25893
|
+
/**
|
|
25894
|
+
* Send an event to trigger a state transition
|
|
25895
|
+
* @param {string} id - Entity ID
|
|
25896
|
+
* @param {string} event - Event name
|
|
25897
|
+
* @param {Object} [eventData] - Event data
|
|
25898
|
+
* @returns {Promise<Object>} Transition result
|
|
25899
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25900
|
+
* @example
|
|
25901
|
+
* await orders.state('order-123', 'CONFIRM', { confirmedBy: 'user-456' });
|
|
25902
|
+
*/
|
|
25903
|
+
async state(id, event, eventData) {
|
|
25904
|
+
if (!this._stateMachine) {
|
|
25905
|
+
throw new Error(
|
|
25906
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25907
|
+
);
|
|
25908
|
+
}
|
|
25909
|
+
return this._stateMachine.send(id, event, eventData);
|
|
25910
|
+
}
|
|
25911
|
+
/**
|
|
25912
|
+
* Get current state of an entity
|
|
25913
|
+
* @param {string} id - Entity ID
|
|
25914
|
+
* @returns {Promise<string>} Current state
|
|
25915
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25916
|
+
* @example
|
|
25917
|
+
* const currentState = await orders.getState('order-123');
|
|
25918
|
+
*/
|
|
25919
|
+
async getState(id) {
|
|
25920
|
+
if (!this._stateMachine) {
|
|
25921
|
+
throw new Error(
|
|
25922
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25923
|
+
);
|
|
25924
|
+
}
|
|
25925
|
+
return this._stateMachine.getState(id);
|
|
25926
|
+
}
|
|
25927
|
+
/**
|
|
25928
|
+
* Check if a transition is valid
|
|
25929
|
+
* @param {string} id - Entity ID
|
|
25930
|
+
* @param {string} event - Event name
|
|
25931
|
+
* @returns {Promise<boolean>} True if transition is valid
|
|
25932
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25933
|
+
* @example
|
|
25934
|
+
* const canConfirm = await orders.canTransition('order-123', 'CONFIRM');
|
|
25935
|
+
*/
|
|
25936
|
+
async canTransition(id, event) {
|
|
25937
|
+
if (!this._stateMachine) {
|
|
25938
|
+
throw new Error(
|
|
25939
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25940
|
+
);
|
|
25941
|
+
}
|
|
25942
|
+
return this._stateMachine.canTransition(id, event);
|
|
25943
|
+
}
|
|
25944
|
+
/**
|
|
25945
|
+
* Get all valid events for the current state
|
|
25946
|
+
* @param {string} id - Entity ID
|
|
25947
|
+
* @returns {Promise<Array<string>>} Array of valid event names
|
|
25948
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25949
|
+
* @example
|
|
25950
|
+
* const events = await orders.getValidEvents('order-123');
|
|
25951
|
+
* // Returns: ['SHIP', 'CANCEL']
|
|
25952
|
+
*/
|
|
25953
|
+
async getValidEvents(id) {
|
|
25954
|
+
if (!this._stateMachine) {
|
|
25955
|
+
throw new Error(
|
|
25956
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25957
|
+
);
|
|
25958
|
+
}
|
|
25959
|
+
return this._stateMachine.getValidEvents(id);
|
|
25960
|
+
}
|
|
25961
|
+
/**
|
|
25962
|
+
* Initialize entity with initial state
|
|
25963
|
+
* @param {string} id - Entity ID
|
|
25964
|
+
* @param {Object} [context] - Initial context data
|
|
25965
|
+
* @returns {Promise<void>}
|
|
25966
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25967
|
+
* @example
|
|
25968
|
+
* await orders.initializeState('order-456', { customerId: 'user-123' });
|
|
25969
|
+
*/
|
|
25970
|
+
async initializeState(id, context) {
|
|
25971
|
+
if (!this._stateMachine) {
|
|
25972
|
+
throw new Error(
|
|
25973
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25974
|
+
);
|
|
25975
|
+
}
|
|
25976
|
+
return this._stateMachine.initializeEntity(id, context);
|
|
25977
|
+
}
|
|
25978
|
+
/**
|
|
25979
|
+
* Get transition history for an entity
|
|
25980
|
+
* @param {string} id - Entity ID
|
|
25981
|
+
* @param {Object} [options] - Query options
|
|
25982
|
+
* @param {number} [options.limit=100] - Maximum number of transitions
|
|
25983
|
+
* @param {Date} [options.fromDate] - Filter from date
|
|
25984
|
+
* @param {Date} [options.toDate] - Filter to date
|
|
25985
|
+
* @returns {Promise<Array<Object>>} Transition history
|
|
25986
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25987
|
+
* @example
|
|
25988
|
+
* const history = await orders.getStateHistory('order-123', { limit: 50 });
|
|
25989
|
+
*/
|
|
25990
|
+
async getStateHistory(id, options) {
|
|
25991
|
+
if (!this._stateMachine) {
|
|
25992
|
+
throw new Error(
|
|
25993
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25994
|
+
);
|
|
25995
|
+
}
|
|
25996
|
+
return this._stateMachine.getTransitionHistory(id, options);
|
|
25997
|
+
}
|
|
25998
|
+
/**
|
|
25999
|
+
* Internal method to attach state machine instance
|
|
26000
|
+
* This is called by StateMachinePlugin during initialization
|
|
26001
|
+
* @private
|
|
26002
|
+
* @param {Object} stateMachine - State machine instance
|
|
26003
|
+
*/
|
|
26004
|
+
_attachStateMachine(stateMachine) {
|
|
26005
|
+
this._stateMachine = stateMachine;
|
|
26006
|
+
}
|
|
25689
26007
|
}
|
|
25690
26008
|
function validateResourceConfig(config) {
|
|
25691
26009
|
const errors = [];
|
|
@@ -25846,7 +26164,7 @@ class Database extends EventEmitter {
|
|
|
25846
26164
|
})();
|
|
25847
26165
|
this.version = "1";
|
|
25848
26166
|
this.s3dbVersion = (() => {
|
|
25849
|
-
const [ok, err, version] = tryFn(() => true ? "13.
|
|
26167
|
+
const [ok, err, version] = tryFn(() => true ? "13.2.2" : "latest");
|
|
25850
26168
|
return ok ? version : "latest";
|
|
25851
26169
|
})();
|
|
25852
26170
|
this._resourcesMap = {};
|
|
@@ -26016,12 +26334,12 @@ class Database extends EventEmitter {
|
|
|
26016
26334
|
}
|
|
26017
26335
|
}
|
|
26018
26336
|
if (definitionChanges.length > 0) {
|
|
26019
|
-
this.emit("
|
|
26337
|
+
this.emit("db:resource-definitions-changed", {
|
|
26020
26338
|
changes: definitionChanges,
|
|
26021
26339
|
metadata: this.savedMetadata
|
|
26022
26340
|
});
|
|
26023
26341
|
}
|
|
26024
|
-
this.emit("connected", /* @__PURE__ */ new Date());
|
|
26342
|
+
this.emit("db:connected", /* @__PURE__ */ new Date());
|
|
26025
26343
|
}
|
|
26026
26344
|
/**
|
|
26027
26345
|
* Detect changes in resource definitions compared to saved metadata
|
|
@@ -26235,7 +26553,7 @@ class Database extends EventEmitter {
|
|
|
26235
26553
|
if (index > -1) {
|
|
26236
26554
|
this.pluginList.splice(index, 1);
|
|
26237
26555
|
}
|
|
26238
|
-
this.emit("plugin
|
|
26556
|
+
this.emit("db:plugin:uninstalled", { name: pluginName, plugin });
|
|
26239
26557
|
}
|
|
26240
26558
|
async uploadMetadataFile() {
|
|
26241
26559
|
const metadata = {
|
|
@@ -26294,7 +26612,7 @@ class Database extends EventEmitter {
|
|
|
26294
26612
|
contentType: "application/json"
|
|
26295
26613
|
});
|
|
26296
26614
|
this.savedMetadata = metadata;
|
|
26297
|
-
this.emit("
|
|
26615
|
+
this.emit("db:metadata-uploaded", metadata);
|
|
26298
26616
|
}
|
|
26299
26617
|
blankMetadataStructure() {
|
|
26300
26618
|
return {
|
|
@@ -26551,7 +26869,7 @@ class Database extends EventEmitter {
|
|
|
26551
26869
|
body: JSON.stringify(metadata, null, 2),
|
|
26552
26870
|
contentType: "application/json"
|
|
26553
26871
|
});
|
|
26554
|
-
this.emit("
|
|
26872
|
+
this.emit("db:metadata-healed", { healingLog, metadata });
|
|
26555
26873
|
if (this.verbose) {
|
|
26556
26874
|
console.warn("S3DB: Successfully uploaded healed metadata");
|
|
26557
26875
|
}
|
|
@@ -26691,7 +27009,7 @@ class Database extends EventEmitter {
|
|
|
26691
27009
|
if (!existingVersionData || existingVersionData.hash !== newHash) {
|
|
26692
27010
|
await this.uploadMetadataFile();
|
|
26693
27011
|
}
|
|
26694
|
-
this.emit("
|
|
27012
|
+
this.emit("db:resource:updated", name);
|
|
26695
27013
|
return existingResource;
|
|
26696
27014
|
}
|
|
26697
27015
|
const existingMetadata = this.savedMetadata?.resources?.[name];
|
|
@@ -26728,7 +27046,7 @@ class Database extends EventEmitter {
|
|
|
26728
27046
|
this._applyMiddlewares(resource, middlewares);
|
|
26729
27047
|
}
|
|
26730
27048
|
await this.uploadMetadataFile();
|
|
26731
|
-
this.emit("
|
|
27049
|
+
this.emit("db:resource:created", name);
|
|
26732
27050
|
return resource;
|
|
26733
27051
|
}
|
|
26734
27052
|
/**
|
|
@@ -26892,7 +27210,7 @@ class Database extends EventEmitter {
|
|
|
26892
27210
|
if (this.client && typeof this.client.removeAllListeners === "function") {
|
|
26893
27211
|
this.client.removeAllListeners();
|
|
26894
27212
|
}
|
|
26895
|
-
await this.emit("disconnected", /* @__PURE__ */ new Date());
|
|
27213
|
+
await this.emit("db:disconnected", /* @__PURE__ */ new Date());
|
|
26896
27214
|
this.removeAllListeners();
|
|
26897
27215
|
if (this._exitListener && typeof process !== "undefined") {
|
|
26898
27216
|
process.off("exit", this._exitListener);
|
|
@@ -27004,7 +27322,7 @@ class Database extends EventEmitter {
|
|
|
27004
27322
|
for (const hook of hooks) {
|
|
27005
27323
|
const [ok, error] = await tryFn(() => hook({ database: this, ...context }));
|
|
27006
27324
|
if (!ok) {
|
|
27007
|
-
this.emit("
|
|
27325
|
+
this.emit("db:hook-error", { event, error, context });
|
|
27008
27326
|
if (this.strictHooks) {
|
|
27009
27327
|
throw new DatabaseError(`Hook execution failed for event '${event}': ${error.message}`, {
|
|
27010
27328
|
event,
|
|
@@ -28580,7 +28898,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28580
28898
|
if (this.config.verbose) {
|
|
28581
28899
|
console.warn(`[ReplicatorPlugin] Insert event failed for resource ${resource.name}: ${error.message}`);
|
|
28582
28900
|
}
|
|
28583
|
-
this.emit("error", { operation: "insert", error: error.message, resource: resource.name });
|
|
28901
|
+
this.emit("plg:replicator:error", { operation: "insert", error: error.message, resource: resource.name });
|
|
28584
28902
|
}
|
|
28585
28903
|
};
|
|
28586
28904
|
const updateHandler = async (data, beforeData) => {
|
|
@@ -28593,7 +28911,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28593
28911
|
if (this.config.verbose) {
|
|
28594
28912
|
console.warn(`[ReplicatorPlugin] Update event failed for resource ${resource.name}: ${error.message}`);
|
|
28595
28913
|
}
|
|
28596
|
-
this.emit("error", { operation: "update", error: error.message, resource: resource.name });
|
|
28914
|
+
this.emit("plg:replicator:error", { operation: "update", error: error.message, resource: resource.name });
|
|
28597
28915
|
}
|
|
28598
28916
|
};
|
|
28599
28917
|
const deleteHandler = async (data) => {
|
|
@@ -28604,7 +28922,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28604
28922
|
if (this.config.verbose) {
|
|
28605
28923
|
console.warn(`[ReplicatorPlugin] Delete event failed for resource ${resource.name}: ${error.message}`);
|
|
28606
28924
|
}
|
|
28607
|
-
this.emit("error", { operation: "delete", error: error.message, resource: resource.name });
|
|
28925
|
+
this.emit("plg:replicator:error", { operation: "delete", error: error.message, resource: resource.name });
|
|
28608
28926
|
}
|
|
28609
28927
|
};
|
|
28610
28928
|
this.eventHandlers.set(resource.name, {
|
|
@@ -28725,7 +29043,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28725
29043
|
if (this.config.verbose) {
|
|
28726
29044
|
console.warn(`[ReplicatorPlugin] Failed to log error for ${resourceName}: ${logError.message}`);
|
|
28727
29045
|
}
|
|
28728
|
-
this.emit("
|
|
29046
|
+
this.emit("plg:replicator:log-error", {
|
|
28729
29047
|
replicator: replicator.name || replicator.id,
|
|
28730
29048
|
resourceName,
|
|
28731
29049
|
operation,
|
|
@@ -28750,7 +29068,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28750
29068
|
() => replicator.replicate(resourceName, operation, data, recordId, beforeData),
|
|
28751
29069
|
this.config.maxRetries
|
|
28752
29070
|
);
|
|
28753
|
-
this.emit("replicated", {
|
|
29071
|
+
this.emit("plg:replicator:replicated", {
|
|
28754
29072
|
replicator: replicator.name || replicator.id,
|
|
28755
29073
|
resourceName,
|
|
28756
29074
|
operation,
|
|
@@ -28766,7 +29084,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28766
29084
|
if (this.config.verbose) {
|
|
28767
29085
|
console.warn(`[ReplicatorPlugin] Replication failed for ${replicator.name || replicator.id} on ${resourceName}: ${error.message}`);
|
|
28768
29086
|
}
|
|
28769
|
-
this.emit("
|
|
29087
|
+
this.emit("plg:replicator:error", {
|
|
28770
29088
|
replicator: replicator.name || replicator.id,
|
|
28771
29089
|
resourceName,
|
|
28772
29090
|
operation,
|
|
@@ -28798,7 +29116,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28798
29116
|
if (this.config.verbose) {
|
|
28799
29117
|
console.warn(`[ReplicatorPlugin] Replicator item processing failed for ${replicator.name || replicator.id} on ${item.resourceName}: ${err.message}`);
|
|
28800
29118
|
}
|
|
28801
|
-
this.emit("
|
|
29119
|
+
this.emit("plg:replicator:error", {
|
|
28802
29120
|
replicator: replicator.name || replicator.id,
|
|
28803
29121
|
resourceName: item.resourceName,
|
|
28804
29122
|
operation: item.operation,
|
|
@@ -28810,7 +29128,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28810
29128
|
}
|
|
28811
29129
|
return { success: false, error: err.message };
|
|
28812
29130
|
}
|
|
28813
|
-
this.emit("replicated", {
|
|
29131
|
+
this.emit("plg:replicator:replicated", {
|
|
28814
29132
|
replicator: replicator.name || replicator.id,
|
|
28815
29133
|
resourceName: item.resourceName,
|
|
28816
29134
|
operation: item.operation,
|
|
@@ -28826,7 +29144,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28826
29144
|
if (this.config.verbose) {
|
|
28827
29145
|
console.warn(`[ReplicatorPlugin] Wrapper processing failed for ${replicator.name || replicator.id} on ${item.resourceName}: ${wrapperError.message}`);
|
|
28828
29146
|
}
|
|
28829
|
-
this.emit("
|
|
29147
|
+
this.emit("plg:replicator:error", {
|
|
28830
29148
|
replicator: replicator.name || replicator.id,
|
|
28831
29149
|
resourceName: item.resourceName,
|
|
28832
29150
|
operation: item.operation,
|
|
@@ -28844,7 +29162,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28844
29162
|
async logReplicator(item) {
|
|
28845
29163
|
const logRes = this.replicatorLog || this.database.resources[normalizeResourceName(this.config.replicatorLogResource)];
|
|
28846
29164
|
if (!logRes) {
|
|
28847
|
-
this.emit("replicator
|
|
29165
|
+
this.emit("plg:replicator:log-failed", { error: "replicator log resource not found", item });
|
|
28848
29166
|
return;
|
|
28849
29167
|
}
|
|
28850
29168
|
const logItem = {
|
|
@@ -28862,7 +29180,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28862
29180
|
if (this.config.verbose) {
|
|
28863
29181
|
console.warn(`[ReplicatorPlugin] Failed to log replicator item: ${err.message}`);
|
|
28864
29182
|
}
|
|
28865
|
-
this.emit("replicator
|
|
29183
|
+
this.emit("plg:replicator:log-failed", { error: err, item });
|
|
28866
29184
|
}
|
|
28867
29185
|
}
|
|
28868
29186
|
async updateReplicatorLog(logId, updates) {
|
|
@@ -28874,7 +29192,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28874
29192
|
});
|
|
28875
29193
|
});
|
|
28876
29194
|
if (!ok) {
|
|
28877
|
-
this.emit("replicator
|
|
29195
|
+
this.emit("plg:replicator:update-log-failed", { error: err.message, logId, updates });
|
|
28878
29196
|
}
|
|
28879
29197
|
}
|
|
28880
29198
|
// Utility methods
|
|
@@ -28958,7 +29276,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28958
29276
|
for (const resourceName in this.database.resources) {
|
|
28959
29277
|
if (normalizeResourceName(resourceName) === normalizeResourceName("plg_replicator_logs")) continue;
|
|
28960
29278
|
if (replicator.shouldReplicateResource(resourceName)) {
|
|
28961
|
-
this.emit("replicator
|
|
29279
|
+
this.emit("plg:replicator:sync-resource", { resourceName, replicatorId });
|
|
28962
29280
|
const resource = this.database.resources[resourceName];
|
|
28963
29281
|
let offset = 0;
|
|
28964
29282
|
const pageSize = this.config.batchSize || 100;
|
|
@@ -28974,7 +29292,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28974
29292
|
}
|
|
28975
29293
|
}
|
|
28976
29294
|
}
|
|
28977
|
-
this.emit("replicator
|
|
29295
|
+
this.emit("plg:replicator:sync-completed", { replicatorId, stats: this.stats });
|
|
28978
29296
|
}
|
|
28979
29297
|
async stop() {
|
|
28980
29298
|
const [ok, error] = await tryFn(async () => {
|
|
@@ -28989,7 +29307,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28989
29307
|
if (this.config.verbose) {
|
|
28990
29308
|
console.warn(`[ReplicatorPlugin] Failed to stop replicator ${replicator.name || replicator.id}: ${replicatorError.message}`);
|
|
28991
29309
|
}
|
|
28992
|
-
this.emit("
|
|
29310
|
+
this.emit("plg:replicator:stop-error", {
|
|
28993
29311
|
replicator: replicator.name || replicator.id || "unknown",
|
|
28994
29312
|
driver: replicator.driver || "unknown",
|
|
28995
29313
|
error: replicatorError.message
|
|
@@ -29020,7 +29338,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
29020
29338
|
if (this.config.verbose) {
|
|
29021
29339
|
console.warn(`[ReplicatorPlugin] Failed to stop plugin: ${error.message}`);
|
|
29022
29340
|
}
|
|
29023
|
-
this.emit("
|
|
29341
|
+
this.emit("plg:replicator:plugin-stop-error", {
|
|
29024
29342
|
error: error.message
|
|
29025
29343
|
});
|
|
29026
29344
|
}
|
|
@@ -29177,7 +29495,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29177
29495
|
if (this.config.verbose) {
|
|
29178
29496
|
console.log(`[S3QueuePlugin] Started ${concurrency} workers`);
|
|
29179
29497
|
}
|
|
29180
|
-
this.emit("workers
|
|
29498
|
+
this.emit("plg:s3-queue:workers-started", { concurrency, workerId: this.workerId });
|
|
29181
29499
|
}
|
|
29182
29500
|
async stopProcessing() {
|
|
29183
29501
|
if (!this.isRunning) return;
|
|
@@ -29192,7 +29510,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29192
29510
|
if (this.config.verbose) {
|
|
29193
29511
|
console.log("[S3QueuePlugin] Stopped all workers");
|
|
29194
29512
|
}
|
|
29195
|
-
this.emit("workers
|
|
29513
|
+
this.emit("plg:s3-queue:workers-stopped", { workerId: this.workerId });
|
|
29196
29514
|
}
|
|
29197
29515
|
createWorker(handler, workerIndex) {
|
|
29198
29516
|
return (async () => {
|
|
@@ -29360,7 +29678,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29360
29678
|
});
|
|
29361
29679
|
await this.completeMessage(message.queueId, result);
|
|
29362
29680
|
const duration = Date.now() - startTime;
|
|
29363
|
-
this.emit("message
|
|
29681
|
+
this.emit("plg:s3-queue:message-completed", {
|
|
29364
29682
|
queueId: message.queueId,
|
|
29365
29683
|
originalId: message.record.id,
|
|
29366
29684
|
duration,
|
|
@@ -29373,7 +29691,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29373
29691
|
const shouldRetry = message.attempts < message.maxAttempts;
|
|
29374
29692
|
if (shouldRetry) {
|
|
29375
29693
|
await this.retryMessage(message.queueId, message.attempts, error.message);
|
|
29376
|
-
this.emit("message
|
|
29694
|
+
this.emit("plg:s3-queue:message-retry", {
|
|
29377
29695
|
queueId: message.queueId,
|
|
29378
29696
|
originalId: message.record.id,
|
|
29379
29697
|
attempts: message.attempts,
|
|
@@ -29381,7 +29699,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29381
29699
|
});
|
|
29382
29700
|
} else {
|
|
29383
29701
|
await this.moveToDeadLetter(message.queueId, message.record, error.message);
|
|
29384
|
-
this.emit("message
|
|
29702
|
+
this.emit("plg:s3-queue:message-dead", {
|
|
29385
29703
|
queueId: message.queueId,
|
|
29386
29704
|
originalId: message.record.id,
|
|
29387
29705
|
error: error.message
|
|
@@ -29613,7 +29931,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29613
29931
|
});
|
|
29614
29932
|
}
|
|
29615
29933
|
await this._startScheduling();
|
|
29616
|
-
this.emit("initialized", { jobs: this.jobs.size });
|
|
29934
|
+
this.emit("db:plugin:initialized", { jobs: this.jobs.size });
|
|
29617
29935
|
}
|
|
29618
29936
|
async _createJobHistoryResource() {
|
|
29619
29937
|
const [ok] = await tryFn(() => this.database.createResource({
|
|
@@ -29751,7 +30069,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29751
30069
|
if (this.config.onJobStart) {
|
|
29752
30070
|
await this._executeHook(this.config.onJobStart, jobName, context);
|
|
29753
30071
|
}
|
|
29754
|
-
this.emit("
|
|
30072
|
+
this.emit("plg:scheduler:job-start", { jobName, executionId, startTime });
|
|
29755
30073
|
let attempt = 0;
|
|
29756
30074
|
let lastError = null;
|
|
29757
30075
|
let result = null;
|
|
@@ -29818,7 +30136,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29818
30136
|
} else if (status !== "success" && this.config.onJobError) {
|
|
29819
30137
|
await this._executeHook(this.config.onJobError, jobName, lastError, attempt);
|
|
29820
30138
|
}
|
|
29821
|
-
this.emit("
|
|
30139
|
+
this.emit("plg:scheduler:job-complete", {
|
|
29822
30140
|
jobName,
|
|
29823
30141
|
executionId,
|
|
29824
30142
|
status,
|
|
@@ -29904,7 +30222,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29904
30222
|
}
|
|
29905
30223
|
job.enabled = true;
|
|
29906
30224
|
this._scheduleNextExecution(jobName);
|
|
29907
|
-
this.emit("
|
|
30225
|
+
this.emit("plg:scheduler:job-enabled", { jobName });
|
|
29908
30226
|
}
|
|
29909
30227
|
/**
|
|
29910
30228
|
* Disable a job
|
|
@@ -29925,7 +30243,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
29925
30243
|
clearTimeout(timer);
|
|
29926
30244
|
this.timers.delete(jobName);
|
|
29927
30245
|
}
|
|
29928
|
-
this.emit("
|
|
30246
|
+
this.emit("plg:scheduler:job-disabled", { jobName });
|
|
29929
30247
|
}
|
|
29930
30248
|
/**
|
|
29931
30249
|
* Get job status and statistics
|
|
@@ -30063,7 +30381,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
30063
30381
|
if (job.enabled) {
|
|
30064
30382
|
this._scheduleNextExecution(jobName);
|
|
30065
30383
|
}
|
|
30066
|
-
this.emit("
|
|
30384
|
+
this.emit("plg:scheduler:job-added", { jobName });
|
|
30067
30385
|
}
|
|
30068
30386
|
/**
|
|
30069
30387
|
* Remove a job
|
|
@@ -30086,7 +30404,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
30086
30404
|
this.jobs.delete(jobName);
|
|
30087
30405
|
this.statistics.delete(jobName);
|
|
30088
30406
|
this.activeJobs.delete(jobName);
|
|
30089
|
-
this.emit("
|
|
30407
|
+
this.emit("plg:scheduler:job-removed", { jobName });
|
|
30090
30408
|
}
|
|
30091
30409
|
/**
|
|
30092
30410
|
* Get plugin instance by name (for job actions that need other plugins)
|
|
@@ -30127,9 +30445,14 @@ class SchedulerPlugin extends Plugin {
|
|
|
30127
30445
|
}
|
|
30128
30446
|
}
|
|
30129
30447
|
|
|
30448
|
+
var scheduler_plugin = /*#__PURE__*/Object.freeze({
|
|
30449
|
+
__proto__: null,
|
|
30450
|
+
SchedulerPlugin: SchedulerPlugin
|
|
30451
|
+
});
|
|
30452
|
+
|
|
30130
30453
|
class StateMachineError extends S3dbError {
|
|
30131
30454
|
constructor(message, details = {}) {
|
|
30132
|
-
const { currentState, targetState, resourceName, operation = "unknown", ...rest } = details;
|
|
30455
|
+
const { currentState, targetState, resourceName, operation = "unknown", retriable, ...rest } = details;
|
|
30133
30456
|
let description = details.description;
|
|
30134
30457
|
if (!description) {
|
|
30135
30458
|
description = `
|
|
@@ -30154,6 +30477,158 @@ Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/state-mach
|
|
|
30154
30477
|
`.trim();
|
|
30155
30478
|
}
|
|
30156
30479
|
super(message, { ...rest, currentState, targetState, resourceName, operation, description });
|
|
30480
|
+
if (retriable !== void 0) {
|
|
30481
|
+
this.retriable = retriable;
|
|
30482
|
+
}
|
|
30483
|
+
}
|
|
30484
|
+
}
|
|
30485
|
+
|
|
30486
|
+
const RETRIABLE = "RETRIABLE";
|
|
30487
|
+
const NON_RETRIABLE = "NON_RETRIABLE";
|
|
30488
|
+
const RETRIABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
30489
|
+
"ECONNREFUSED",
|
|
30490
|
+
"ETIMEDOUT",
|
|
30491
|
+
"ECONNRESET",
|
|
30492
|
+
"EPIPE",
|
|
30493
|
+
"ENOTFOUND",
|
|
30494
|
+
"NetworkError",
|
|
30495
|
+
"NETWORK_ERROR",
|
|
30496
|
+
"TimeoutError",
|
|
30497
|
+
"TIMEOUT"
|
|
30498
|
+
]);
|
|
30499
|
+
const RETRIABLE_AWS_CODES = /* @__PURE__ */ new Set([
|
|
30500
|
+
"ThrottlingException",
|
|
30501
|
+
"TooManyRequestsException",
|
|
30502
|
+
"RequestLimitExceeded",
|
|
30503
|
+
"ProvisionedThroughputExceededException",
|
|
30504
|
+
"RequestThrottledException",
|
|
30505
|
+
"SlowDown",
|
|
30506
|
+
"ServiceUnavailable"
|
|
30507
|
+
]);
|
|
30508
|
+
const RETRIABLE_AWS_CONFLICTS = /* @__PURE__ */ new Set([
|
|
30509
|
+
"ConditionalCheckFailedException",
|
|
30510
|
+
"TransactionConflictException"
|
|
30511
|
+
]);
|
|
30512
|
+
const RETRIABLE_STATUS_CODES = /* @__PURE__ */ new Set([
|
|
30513
|
+
429,
|
|
30514
|
+
// Too Many Requests
|
|
30515
|
+
500,
|
|
30516
|
+
// Internal Server Error
|
|
30517
|
+
502,
|
|
30518
|
+
// Bad Gateway
|
|
30519
|
+
503,
|
|
30520
|
+
// Service Unavailable
|
|
30521
|
+
504,
|
|
30522
|
+
// Gateway Timeout
|
|
30523
|
+
507,
|
|
30524
|
+
// Insufficient Storage
|
|
30525
|
+
509
|
|
30526
|
+
// Bandwidth Limit Exceeded
|
|
30527
|
+
]);
|
|
30528
|
+
const NON_RETRIABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
30529
|
+
"ValidationError",
|
|
30530
|
+
"StateMachineError",
|
|
30531
|
+
"SchemaError",
|
|
30532
|
+
"AuthenticationError",
|
|
30533
|
+
"PermissionError",
|
|
30534
|
+
"BusinessLogicError",
|
|
30535
|
+
"InvalidStateTransition"
|
|
30536
|
+
]);
|
|
30537
|
+
const NON_RETRIABLE_STATUS_CODES = /* @__PURE__ */ new Set([
|
|
30538
|
+
400,
|
|
30539
|
+
// Bad Request
|
|
30540
|
+
401,
|
|
30541
|
+
// Unauthorized
|
|
30542
|
+
403,
|
|
30543
|
+
// Forbidden
|
|
30544
|
+
404,
|
|
30545
|
+
// Not Found
|
|
30546
|
+
405,
|
|
30547
|
+
// Method Not Allowed
|
|
30548
|
+
406,
|
|
30549
|
+
// Not Acceptable
|
|
30550
|
+
409,
|
|
30551
|
+
// Conflict
|
|
30552
|
+
410,
|
|
30553
|
+
// Gone
|
|
30554
|
+
422
|
|
30555
|
+
// Unprocessable Entity
|
|
30556
|
+
]);
|
|
30557
|
+
class ErrorClassifier {
|
|
30558
|
+
/**
|
|
30559
|
+
* Classify an error as RETRIABLE or NON_RETRIABLE
|
|
30560
|
+
*
|
|
30561
|
+
* @param {Error} error - The error to classify
|
|
30562
|
+
* @param {Object} options - Classification options
|
|
30563
|
+
* @param {Array<string>} options.retryableErrors - Custom retriable error names/codes
|
|
30564
|
+
* @param {Array<string>} options.nonRetriableErrors - Custom non-retriable error names/codes
|
|
30565
|
+
* @returns {string} 'RETRIABLE' or 'NON_RETRIABLE'
|
|
30566
|
+
*/
|
|
30567
|
+
static classify(error, options = {}) {
|
|
30568
|
+
if (!error) return NON_RETRIABLE;
|
|
30569
|
+
const {
|
|
30570
|
+
retryableErrors = [],
|
|
30571
|
+
nonRetriableErrors = []
|
|
30572
|
+
} = options;
|
|
30573
|
+
if (retryableErrors.length > 0) {
|
|
30574
|
+
const isCustomRetriable = retryableErrors.some(
|
|
30575
|
+
(errType) => error.code === errType || error.name === errType || error.message?.includes(errType)
|
|
30576
|
+
);
|
|
30577
|
+
if (isCustomRetriable) return RETRIABLE;
|
|
30578
|
+
}
|
|
30579
|
+
if (nonRetriableErrors.length > 0) {
|
|
30580
|
+
const isCustomNonRetriable = nonRetriableErrors.some(
|
|
30581
|
+
(errType) => error.code === errType || error.name === errType || error.message?.includes(errType)
|
|
30582
|
+
);
|
|
30583
|
+
if (isCustomNonRetriable) return NON_RETRIABLE;
|
|
30584
|
+
}
|
|
30585
|
+
if (error.retriable === false) return NON_RETRIABLE;
|
|
30586
|
+
if (error.retriable === true) return RETRIABLE;
|
|
30587
|
+
if (NON_RETRIABLE_ERROR_NAMES.has(error.name)) {
|
|
30588
|
+
return NON_RETRIABLE;
|
|
30589
|
+
}
|
|
30590
|
+
if (error.statusCode && NON_RETRIABLE_STATUS_CODES.has(error.statusCode)) {
|
|
30591
|
+
return NON_RETRIABLE;
|
|
30592
|
+
}
|
|
30593
|
+
if (error.code && RETRIABLE_NETWORK_CODES.has(error.code)) {
|
|
30594
|
+
return RETRIABLE;
|
|
30595
|
+
}
|
|
30596
|
+
if (error.code && RETRIABLE_AWS_CODES.has(error.code)) {
|
|
30597
|
+
return RETRIABLE;
|
|
30598
|
+
}
|
|
30599
|
+
if (error.code && RETRIABLE_AWS_CONFLICTS.has(error.code)) {
|
|
30600
|
+
return RETRIABLE;
|
|
30601
|
+
}
|
|
30602
|
+
if (error.statusCode && RETRIABLE_STATUS_CODES.has(error.statusCode)) {
|
|
30603
|
+
return RETRIABLE;
|
|
30604
|
+
}
|
|
30605
|
+
if (error.message && typeof error.message === "string") {
|
|
30606
|
+
const lowerMessage = error.message.toLowerCase();
|
|
30607
|
+
if (lowerMessage.includes("timeout") || lowerMessage.includes("timed out") || lowerMessage.includes("network") || lowerMessage.includes("connection")) {
|
|
30608
|
+
return RETRIABLE;
|
|
30609
|
+
}
|
|
30610
|
+
}
|
|
30611
|
+
return RETRIABLE;
|
|
30612
|
+
}
|
|
30613
|
+
/**
|
|
30614
|
+
* Check if an error is retriable
|
|
30615
|
+
*
|
|
30616
|
+
* @param {Error} error - The error to check
|
|
30617
|
+
* @param {Object} options - Classification options
|
|
30618
|
+
* @returns {boolean} true if retriable
|
|
30619
|
+
*/
|
|
30620
|
+
static isRetriable(error, options = {}) {
|
|
30621
|
+
return this.classify(error, options) === RETRIABLE;
|
|
30622
|
+
}
|
|
30623
|
+
/**
|
|
30624
|
+
* Check if an error is non-retriable
|
|
30625
|
+
*
|
|
30626
|
+
* @param {Error} error - The error to check
|
|
30627
|
+
* @param {Object} options - Classification options
|
|
30628
|
+
* @returns {boolean} true if non-retriable
|
|
30629
|
+
*/
|
|
30630
|
+
static isNonRetriable(error, options = {}) {
|
|
30631
|
+
return this.classify(error, options) === NON_RETRIABLE;
|
|
30157
30632
|
}
|
|
30158
30633
|
}
|
|
30159
30634
|
|
|
@@ -30174,11 +30649,23 @@ class StateMachinePlugin extends Plugin {
|
|
|
30174
30649
|
workerId: options.workerId || "default",
|
|
30175
30650
|
lockTimeout: options.lockTimeout || 1e3,
|
|
30176
30651
|
// Wait up to 1s for lock
|
|
30177
|
-
lockTTL: options.lockTTL || 5
|
|
30652
|
+
lockTTL: options.lockTTL || 5,
|
|
30178
30653
|
// Lock expires after 5s (prevent deadlock)
|
|
30654
|
+
// Global retry configuration for action execution
|
|
30655
|
+
retryConfig: options.retryConfig || null,
|
|
30656
|
+
// Trigger system configuration
|
|
30657
|
+
enableScheduler: options.enableScheduler || false,
|
|
30658
|
+
schedulerConfig: options.schedulerConfig || {},
|
|
30659
|
+
enableDateTriggers: options.enableDateTriggers !== false,
|
|
30660
|
+
enableFunctionTriggers: options.enableFunctionTriggers !== false,
|
|
30661
|
+
enableEventTriggers: options.enableEventTriggers !== false,
|
|
30662
|
+
triggerCheckInterval: options.triggerCheckInterval || 6e4
|
|
30663
|
+
// Check triggers every 60s by default
|
|
30179
30664
|
};
|
|
30180
30665
|
this.database = null;
|
|
30181
30666
|
this.machines = /* @__PURE__ */ new Map();
|
|
30667
|
+
this.triggerIntervals = [];
|
|
30668
|
+
this.schedulerPlugin = null;
|
|
30182
30669
|
this._validateConfiguration();
|
|
30183
30670
|
}
|
|
30184
30671
|
_validateConfiguration() {
|
|
@@ -30227,7 +30714,9 @@ class StateMachinePlugin extends Plugin {
|
|
|
30227
30714
|
// entityId -> currentState
|
|
30228
30715
|
});
|
|
30229
30716
|
}
|
|
30230
|
-
|
|
30717
|
+
await this._attachStateMachinesToResources();
|
|
30718
|
+
await this._setupTriggers();
|
|
30719
|
+
this.emit("db:plugin:initialized", { machines: Array.from(this.machines.keys()) });
|
|
30231
30720
|
}
|
|
30232
30721
|
async _createStateResources() {
|
|
30233
30722
|
const [logOk] = await tryFn(() => this.database.createResource({
|
|
@@ -30258,6 +30747,8 @@ class StateMachinePlugin extends Plugin {
|
|
|
30258
30747
|
currentState: "string|required",
|
|
30259
30748
|
context: "json|default:{}",
|
|
30260
30749
|
lastTransition: "string|default:null",
|
|
30750
|
+
triggerCounts: "json|default:{}",
|
|
30751
|
+
// Track trigger execution counts
|
|
30261
30752
|
updatedAt: "string|required"
|
|
30262
30753
|
},
|
|
30263
30754
|
behavior: "body-overflow"
|
|
@@ -30321,7 +30812,7 @@ class StateMachinePlugin extends Plugin {
|
|
|
30321
30812
|
if (targetStateConfig && targetStateConfig.entry) {
|
|
30322
30813
|
await this._executeAction(targetStateConfig.entry, context, event, machineId, entityId);
|
|
30323
30814
|
}
|
|
30324
|
-
this.emit("transition", {
|
|
30815
|
+
this.emit("plg:state-machine:transition", {
|
|
30325
30816
|
machineId,
|
|
30326
30817
|
entityId,
|
|
30327
30818
|
from: currentState,
|
|
@@ -30347,14 +30838,97 @@ class StateMachinePlugin extends Plugin {
|
|
|
30347
30838
|
}
|
|
30348
30839
|
return;
|
|
30349
30840
|
}
|
|
30350
|
-
const
|
|
30351
|
-
|
|
30352
|
-
|
|
30353
|
-
|
|
30354
|
-
|
|
30355
|
-
|
|
30841
|
+
const machine = this.machines.get(machineId);
|
|
30842
|
+
const currentState = await this.getState(machineId, entityId);
|
|
30843
|
+
const stateConfig = machine?.config?.states?.[currentState];
|
|
30844
|
+
const retryConfig = {
|
|
30845
|
+
...this.config.retryConfig || {},
|
|
30846
|
+
...machine?.config?.retryConfig || {},
|
|
30847
|
+
...stateConfig?.retryConfig || {}
|
|
30848
|
+
};
|
|
30849
|
+
const maxAttempts = retryConfig.maxAttempts ?? 0;
|
|
30850
|
+
const retryEnabled = maxAttempts > 0;
|
|
30851
|
+
let attempt = 0;
|
|
30852
|
+
while (attempt <= maxAttempts) {
|
|
30853
|
+
try {
|
|
30854
|
+
const result = await action(context, event, { database: this.database, machineId, entityId });
|
|
30855
|
+
if (attempt > 0) {
|
|
30856
|
+
this.emit("plg:state-machine:action-retry-success", {
|
|
30857
|
+
machineId,
|
|
30858
|
+
entityId,
|
|
30859
|
+
action: actionName,
|
|
30860
|
+
attempts: attempt + 1,
|
|
30861
|
+
state: currentState
|
|
30862
|
+
});
|
|
30863
|
+
if (this.config.verbose) {
|
|
30864
|
+
console.log(`[StateMachinePlugin] Action '${actionName}' succeeded after ${attempt + 1} attempts`);
|
|
30865
|
+
}
|
|
30866
|
+
}
|
|
30867
|
+
return result;
|
|
30868
|
+
} catch (error) {
|
|
30869
|
+
if (!retryEnabled) {
|
|
30870
|
+
if (this.config.verbose) {
|
|
30871
|
+
console.error(`[StateMachinePlugin] Action '${actionName}' failed:`, error.message);
|
|
30872
|
+
}
|
|
30873
|
+
this.emit("plg:state-machine:action-error", { actionName, error: error.message, machineId, entityId });
|
|
30874
|
+
return;
|
|
30875
|
+
}
|
|
30876
|
+
const classification = ErrorClassifier.classify(error, {
|
|
30877
|
+
retryableErrors: retryConfig.retryableErrors,
|
|
30878
|
+
nonRetriableErrors: retryConfig.nonRetriableErrors
|
|
30879
|
+
});
|
|
30880
|
+
if (classification === "NON_RETRIABLE") {
|
|
30881
|
+
this.emit("plg:state-machine:action-error-non-retriable", {
|
|
30882
|
+
machineId,
|
|
30883
|
+
entityId,
|
|
30884
|
+
action: actionName,
|
|
30885
|
+
error: error.message,
|
|
30886
|
+
state: currentState
|
|
30887
|
+
});
|
|
30888
|
+
if (this.config.verbose) {
|
|
30889
|
+
console.error(`[StateMachinePlugin] Action '${actionName}' failed with non-retriable error:`, error.message);
|
|
30890
|
+
}
|
|
30891
|
+
throw error;
|
|
30892
|
+
}
|
|
30893
|
+
if (attempt >= maxAttempts) {
|
|
30894
|
+
this.emit("plg:state-machine:action-retry-exhausted", {
|
|
30895
|
+
machineId,
|
|
30896
|
+
entityId,
|
|
30897
|
+
action: actionName,
|
|
30898
|
+
attempts: attempt + 1,
|
|
30899
|
+
error: error.message,
|
|
30900
|
+
state: currentState
|
|
30901
|
+
});
|
|
30902
|
+
if (this.config.verbose) {
|
|
30903
|
+
console.error(`[StateMachinePlugin] Action '${actionName}' failed after ${attempt + 1} attempts:`, error.message);
|
|
30904
|
+
}
|
|
30905
|
+
throw error;
|
|
30906
|
+
}
|
|
30907
|
+
attempt++;
|
|
30908
|
+
const delay = this._calculateBackoff(attempt, retryConfig);
|
|
30909
|
+
if (retryConfig.onRetry) {
|
|
30910
|
+
try {
|
|
30911
|
+
await retryConfig.onRetry(attempt, error, context);
|
|
30912
|
+
} catch (hookError) {
|
|
30913
|
+
if (this.config.verbose) {
|
|
30914
|
+
console.warn(`[StateMachinePlugin] onRetry hook failed:`, hookError.message);
|
|
30915
|
+
}
|
|
30916
|
+
}
|
|
30917
|
+
}
|
|
30918
|
+
this.emit("plg:state-machine:action-retry-attempt", {
|
|
30919
|
+
machineId,
|
|
30920
|
+
entityId,
|
|
30921
|
+
action: actionName,
|
|
30922
|
+
attempt,
|
|
30923
|
+
delay,
|
|
30924
|
+
error: error.message,
|
|
30925
|
+
state: currentState
|
|
30926
|
+
});
|
|
30927
|
+
if (this.config.verbose) {
|
|
30928
|
+
console.warn(`[StateMachinePlugin] Action '${actionName}' failed (attempt ${attempt + 1}/${maxAttempts + 1}), retrying in ${delay}ms:`, error.message);
|
|
30929
|
+
}
|
|
30930
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
30356
30931
|
}
|
|
30357
|
-
this.emit("action_error", { actionName, error: error.message, machineId, entityId });
|
|
30358
30932
|
}
|
|
30359
30933
|
}
|
|
30360
30934
|
async _transition(machineId, entityId, fromState, toState, event, context) {
|
|
@@ -30452,6 +31026,27 @@ class StateMachinePlugin extends Plugin {
|
|
|
30452
31026
|
console.warn(`[StateMachinePlugin] Failed to release lock '${lockName}':`, err.message);
|
|
30453
31027
|
}
|
|
30454
31028
|
}
|
|
31029
|
+
/**
|
|
31030
|
+
* Calculate backoff delay for retry attempts
|
|
31031
|
+
* @private
|
|
31032
|
+
*/
|
|
31033
|
+
_calculateBackoff(attempt, retryConfig) {
|
|
31034
|
+
const {
|
|
31035
|
+
backoffStrategy = "exponential",
|
|
31036
|
+
baseDelay = 1e3,
|
|
31037
|
+
maxDelay = 3e4
|
|
31038
|
+
} = retryConfig || {};
|
|
31039
|
+
let delay;
|
|
31040
|
+
if (backoffStrategy === "exponential") {
|
|
31041
|
+
delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
31042
|
+
} else if (backoffStrategy === "linear") {
|
|
31043
|
+
delay = Math.min(baseDelay * attempt, maxDelay);
|
|
31044
|
+
} else {
|
|
31045
|
+
delay = baseDelay;
|
|
31046
|
+
}
|
|
31047
|
+
const jitter = delay * 0.2 * (Math.random() - 0.5);
|
|
31048
|
+
return Math.round(delay + jitter);
|
|
31049
|
+
}
|
|
30455
31050
|
/**
|
|
30456
31051
|
* Get current state for an entity
|
|
30457
31052
|
*/
|
|
@@ -30581,7 +31176,7 @@ class StateMachinePlugin extends Plugin {
|
|
|
30581
31176
|
if (initialStateConfig && initialStateConfig.entry) {
|
|
30582
31177
|
await this._executeAction(initialStateConfig.entry, context, "INIT", machineId, entityId);
|
|
30583
31178
|
}
|
|
30584
|
-
this.emit("
|
|
31179
|
+
this.emit("plg:state-machine:entity-initialized", { machineId, entityId, initialState });
|
|
30585
31180
|
return initialState;
|
|
30586
31181
|
}
|
|
30587
31182
|
/**
|
|
@@ -30638,12 +31233,395 @@ class StateMachinePlugin extends Plugin {
|
|
|
30638
31233
|
`;
|
|
30639
31234
|
return dot;
|
|
30640
31235
|
}
|
|
31236
|
+
/**
|
|
31237
|
+
* Get all entities currently in a specific state
|
|
31238
|
+
* @private
|
|
31239
|
+
*/
|
|
31240
|
+
async _getEntitiesInState(machineId, stateName) {
|
|
31241
|
+
if (!this.config.persistTransitions) {
|
|
31242
|
+
const machine = this.machines.get(machineId);
|
|
31243
|
+
if (!machine) return [];
|
|
31244
|
+
const entities = [];
|
|
31245
|
+
for (const [entityId, currentState] of machine.currentStates) {
|
|
31246
|
+
if (currentState === stateName) {
|
|
31247
|
+
entities.push({ entityId, currentState, context: {}, triggerCounts: {} });
|
|
31248
|
+
}
|
|
31249
|
+
}
|
|
31250
|
+
return entities;
|
|
31251
|
+
}
|
|
31252
|
+
const [ok, err, records] = await tryFn(
|
|
31253
|
+
() => this.database.resources[this.config.stateResource].query({
|
|
31254
|
+
machineId,
|
|
31255
|
+
currentState: stateName
|
|
31256
|
+
})
|
|
31257
|
+
);
|
|
31258
|
+
if (!ok) {
|
|
31259
|
+
if (this.config.verbose) {
|
|
31260
|
+
console.warn(`[StateMachinePlugin] Failed to query entities in state '${stateName}':`, err.message);
|
|
31261
|
+
}
|
|
31262
|
+
return [];
|
|
31263
|
+
}
|
|
31264
|
+
return records || [];
|
|
31265
|
+
}
|
|
31266
|
+
/**
|
|
31267
|
+
* Increment trigger execution count for an entity
|
|
31268
|
+
* @private
|
|
31269
|
+
*/
|
|
31270
|
+
async _incrementTriggerCount(machineId, entityId, triggerName) {
|
|
31271
|
+
if (!this.config.persistTransitions) {
|
|
31272
|
+
return;
|
|
31273
|
+
}
|
|
31274
|
+
const stateId = `${machineId}_${entityId}`;
|
|
31275
|
+
const [ok, err, stateRecord] = await tryFn(
|
|
31276
|
+
() => this.database.resources[this.config.stateResource].get(stateId)
|
|
31277
|
+
);
|
|
31278
|
+
if (ok && stateRecord) {
|
|
31279
|
+
const triggerCounts = stateRecord.triggerCounts || {};
|
|
31280
|
+
triggerCounts[triggerName] = (triggerCounts[triggerName] || 0) + 1;
|
|
31281
|
+
await tryFn(
|
|
31282
|
+
() => this.database.resources[this.config.stateResource].patch(stateId, { triggerCounts })
|
|
31283
|
+
);
|
|
31284
|
+
}
|
|
31285
|
+
}
|
|
31286
|
+
/**
|
|
31287
|
+
* Setup trigger system for all state machines
|
|
31288
|
+
* @private
|
|
31289
|
+
*/
|
|
31290
|
+
async _setupTriggers() {
|
|
31291
|
+
if (!this.config.enableScheduler && !this.config.enableDateTriggers && !this.config.enableFunctionTriggers && !this.config.enableEventTriggers) {
|
|
31292
|
+
return;
|
|
31293
|
+
}
|
|
31294
|
+
const cronJobs = {};
|
|
31295
|
+
for (const [machineId, machineData] of this.machines) {
|
|
31296
|
+
const machineConfig = machineData.config;
|
|
31297
|
+
for (const [stateName, stateConfig] of Object.entries(machineConfig.states)) {
|
|
31298
|
+
const triggers = stateConfig.triggers || [];
|
|
31299
|
+
for (let i = 0; i < triggers.length; i++) {
|
|
31300
|
+
const trigger = triggers[i];
|
|
31301
|
+
const triggerName = `${trigger.action}_${i}`;
|
|
31302
|
+
if (trigger.type === "cron" && this.config.enableScheduler) {
|
|
31303
|
+
const jobName = `${machineId}_${stateName}_${triggerName}`;
|
|
31304
|
+
cronJobs[jobName] = await this._createCronJob(machineId, stateName, trigger, triggerName);
|
|
31305
|
+
} else if (trigger.type === "date" && this.config.enableDateTriggers) {
|
|
31306
|
+
await this._setupDateTrigger(machineId, stateName, trigger, triggerName);
|
|
31307
|
+
} else if (trigger.type === "function" && this.config.enableFunctionTriggers) {
|
|
31308
|
+
await this._setupFunctionTrigger(machineId, stateName, trigger, triggerName);
|
|
31309
|
+
} else if (trigger.type === "event" && this.config.enableEventTriggers) {
|
|
31310
|
+
await this._setupEventTrigger(machineId, stateName, trigger, triggerName);
|
|
31311
|
+
}
|
|
31312
|
+
}
|
|
31313
|
+
}
|
|
31314
|
+
}
|
|
31315
|
+
if (Object.keys(cronJobs).length > 0 && this.config.enableScheduler) {
|
|
31316
|
+
const { SchedulerPlugin } = await Promise.resolve().then(function () { return scheduler_plugin; });
|
|
31317
|
+
this.schedulerPlugin = new SchedulerPlugin({
|
|
31318
|
+
jobs: cronJobs,
|
|
31319
|
+
persistJobs: false,
|
|
31320
|
+
// Don't persist trigger jobs
|
|
31321
|
+
verbose: this.config.verbose,
|
|
31322
|
+
...this.config.schedulerConfig
|
|
31323
|
+
});
|
|
31324
|
+
await this.database.usePlugin(this.schedulerPlugin);
|
|
31325
|
+
if (this.config.verbose) {
|
|
31326
|
+
console.log(`[StateMachinePlugin] Installed SchedulerPlugin with ${Object.keys(cronJobs).length} cron triggers`);
|
|
31327
|
+
}
|
|
31328
|
+
}
|
|
31329
|
+
}
|
|
31330
|
+
/**
|
|
31331
|
+
* Create a SchedulerPlugin job for a cron trigger
|
|
31332
|
+
* @private
|
|
31333
|
+
*/
|
|
31334
|
+
async _createCronJob(machineId, stateName, trigger, triggerName) {
|
|
31335
|
+
return {
|
|
31336
|
+
schedule: trigger.schedule,
|
|
31337
|
+
description: `Trigger '${triggerName}' for ${machineId}.${stateName}`,
|
|
31338
|
+
action: async (database, context) => {
|
|
31339
|
+
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31340
|
+
let executedCount = 0;
|
|
31341
|
+
for (const entity of entities) {
|
|
31342
|
+
try {
|
|
31343
|
+
if (trigger.condition) {
|
|
31344
|
+
const shouldTrigger = await trigger.condition(entity.context, entity.entityId);
|
|
31345
|
+
if (!shouldTrigger) continue;
|
|
31346
|
+
}
|
|
31347
|
+
if (trigger.maxTriggers !== void 0) {
|
|
31348
|
+
const triggerCount = entity.triggerCounts?.[triggerName] || 0;
|
|
31349
|
+
if (triggerCount >= trigger.maxTriggers) {
|
|
31350
|
+
if (trigger.onMaxTriggersReached) {
|
|
31351
|
+
await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
|
|
31352
|
+
}
|
|
31353
|
+
continue;
|
|
31354
|
+
}
|
|
31355
|
+
}
|
|
31356
|
+
const result = await this._executeAction(
|
|
31357
|
+
trigger.action,
|
|
31358
|
+
entity.context,
|
|
31359
|
+
"TRIGGER",
|
|
31360
|
+
machineId,
|
|
31361
|
+
entity.entityId
|
|
31362
|
+
);
|
|
31363
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31364
|
+
executedCount++;
|
|
31365
|
+
if (trigger.eventOnSuccess) {
|
|
31366
|
+
await this.send(machineId, entity.entityId, trigger.eventOnSuccess, {
|
|
31367
|
+
...entity.context,
|
|
31368
|
+
triggerResult: result
|
|
31369
|
+
});
|
|
31370
|
+
} else if (trigger.event) {
|
|
31371
|
+
await this.send(machineId, entity.entityId, trigger.event, {
|
|
31372
|
+
...entity.context,
|
|
31373
|
+
triggerResult: result
|
|
31374
|
+
});
|
|
31375
|
+
}
|
|
31376
|
+
this.emit("plg:state-machine:trigger-executed", {
|
|
31377
|
+
machineId,
|
|
31378
|
+
entityId: entity.entityId,
|
|
31379
|
+
state: stateName,
|
|
31380
|
+
trigger: triggerName,
|
|
31381
|
+
type: "cron"
|
|
31382
|
+
});
|
|
31383
|
+
} catch (error) {
|
|
31384
|
+
if (trigger.event) {
|
|
31385
|
+
await tryFn(() => this.send(machineId, entity.entityId, trigger.event, {
|
|
31386
|
+
...entity.context,
|
|
31387
|
+
triggerError: error.message
|
|
31388
|
+
}));
|
|
31389
|
+
}
|
|
31390
|
+
if (this.config.verbose) {
|
|
31391
|
+
console.error(`[StateMachinePlugin] Trigger '${triggerName}' failed for entity ${entity.entityId}:`, error.message);
|
|
31392
|
+
}
|
|
31393
|
+
}
|
|
31394
|
+
}
|
|
31395
|
+
return { processed: entities.length, executed: executedCount };
|
|
31396
|
+
}
|
|
31397
|
+
};
|
|
31398
|
+
}
|
|
31399
|
+
/**
|
|
31400
|
+
* Setup a date-based trigger
|
|
31401
|
+
* @private
|
|
31402
|
+
*/
|
|
31403
|
+
async _setupDateTrigger(machineId, stateName, trigger, triggerName) {
|
|
31404
|
+
const checkInterval = setInterval(async () => {
|
|
31405
|
+
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31406
|
+
for (const entity of entities) {
|
|
31407
|
+
try {
|
|
31408
|
+
const triggerDateValue = entity.context?.[trigger.field];
|
|
31409
|
+
if (!triggerDateValue) continue;
|
|
31410
|
+
const triggerDate = new Date(triggerDateValue);
|
|
31411
|
+
const now = /* @__PURE__ */ new Date();
|
|
31412
|
+
if (now >= triggerDate) {
|
|
31413
|
+
if (trigger.maxTriggers !== void 0) {
|
|
31414
|
+
const triggerCount = entity.triggerCounts?.[triggerName] || 0;
|
|
31415
|
+
if (triggerCount >= trigger.maxTriggers) {
|
|
31416
|
+
if (trigger.onMaxTriggersReached) {
|
|
31417
|
+
await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
|
|
31418
|
+
}
|
|
31419
|
+
continue;
|
|
31420
|
+
}
|
|
31421
|
+
}
|
|
31422
|
+
const result = await this._executeAction(trigger.action, entity.context, "TRIGGER", machineId, entity.entityId);
|
|
31423
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31424
|
+
if (trigger.event) {
|
|
31425
|
+
await this.send(machineId, entity.entityId, trigger.event, {
|
|
31426
|
+
...entity.context,
|
|
31427
|
+
triggerResult: result
|
|
31428
|
+
});
|
|
31429
|
+
}
|
|
31430
|
+
this.emit("plg:state-machine:trigger-executed", {
|
|
31431
|
+
machineId,
|
|
31432
|
+
entityId: entity.entityId,
|
|
31433
|
+
state: stateName,
|
|
31434
|
+
trigger: triggerName,
|
|
31435
|
+
type: "date"
|
|
31436
|
+
});
|
|
31437
|
+
}
|
|
31438
|
+
} catch (error) {
|
|
31439
|
+
if (this.config.verbose) {
|
|
31440
|
+
console.error(`[StateMachinePlugin] Date trigger '${triggerName}' failed:`, error.message);
|
|
31441
|
+
}
|
|
31442
|
+
}
|
|
31443
|
+
}
|
|
31444
|
+
}, this.config.triggerCheckInterval);
|
|
31445
|
+
this.triggerIntervals.push(checkInterval);
|
|
31446
|
+
}
|
|
31447
|
+
/**
|
|
31448
|
+
* Setup a function-based trigger
|
|
31449
|
+
* @private
|
|
31450
|
+
*/
|
|
31451
|
+
async _setupFunctionTrigger(machineId, stateName, trigger, triggerName) {
|
|
31452
|
+
const interval = trigger.interval || this.config.triggerCheckInterval;
|
|
31453
|
+
const checkInterval = setInterval(async () => {
|
|
31454
|
+
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31455
|
+
for (const entity of entities) {
|
|
31456
|
+
try {
|
|
31457
|
+
if (trigger.maxTriggers !== void 0) {
|
|
31458
|
+
const triggerCount = entity.triggerCounts?.[triggerName] || 0;
|
|
31459
|
+
if (triggerCount >= trigger.maxTriggers) {
|
|
31460
|
+
if (trigger.onMaxTriggersReached) {
|
|
31461
|
+
await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
|
|
31462
|
+
}
|
|
31463
|
+
continue;
|
|
31464
|
+
}
|
|
31465
|
+
}
|
|
31466
|
+
const shouldTrigger = await trigger.condition(entity.context, entity.entityId);
|
|
31467
|
+
if (shouldTrigger) {
|
|
31468
|
+
const result = await this._executeAction(trigger.action, entity.context, "TRIGGER", machineId, entity.entityId);
|
|
31469
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31470
|
+
if (trigger.event) {
|
|
31471
|
+
await this.send(machineId, entity.entityId, trigger.event, {
|
|
31472
|
+
...entity.context,
|
|
31473
|
+
triggerResult: result
|
|
31474
|
+
});
|
|
31475
|
+
}
|
|
31476
|
+
this.emit("plg:state-machine:trigger-executed", {
|
|
31477
|
+
machineId,
|
|
31478
|
+
entityId: entity.entityId,
|
|
31479
|
+
state: stateName,
|
|
31480
|
+
trigger: triggerName,
|
|
31481
|
+
type: "function"
|
|
31482
|
+
});
|
|
31483
|
+
}
|
|
31484
|
+
} catch (error) {
|
|
31485
|
+
if (this.config.verbose) {
|
|
31486
|
+
console.error(`[StateMachinePlugin] Function trigger '${triggerName}' failed:`, error.message);
|
|
31487
|
+
}
|
|
31488
|
+
}
|
|
31489
|
+
}
|
|
31490
|
+
}, interval);
|
|
31491
|
+
this.triggerIntervals.push(checkInterval);
|
|
31492
|
+
}
|
|
31493
|
+
/**
|
|
31494
|
+
* Setup an event-based trigger
|
|
31495
|
+
* @private
|
|
31496
|
+
*/
|
|
31497
|
+
async _setupEventTrigger(machineId, stateName, trigger, triggerName) {
|
|
31498
|
+
const eventName = trigger.event;
|
|
31499
|
+
const eventHandler = async (eventData) => {
|
|
31500
|
+
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31501
|
+
for (const entity of entities) {
|
|
31502
|
+
try {
|
|
31503
|
+
if (trigger.condition) {
|
|
31504
|
+
const shouldTrigger = await trigger.condition(entity.context, entity.entityId, eventData);
|
|
31505
|
+
if (!shouldTrigger) continue;
|
|
31506
|
+
}
|
|
31507
|
+
if (trigger.maxTriggers !== void 0) {
|
|
31508
|
+
const triggerCount = entity.triggerCounts?.[triggerName] || 0;
|
|
31509
|
+
if (triggerCount >= trigger.maxTriggers) {
|
|
31510
|
+
if (trigger.onMaxTriggersReached) {
|
|
31511
|
+
await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
|
|
31512
|
+
}
|
|
31513
|
+
continue;
|
|
31514
|
+
}
|
|
31515
|
+
}
|
|
31516
|
+
const result = await this._executeAction(
|
|
31517
|
+
trigger.action,
|
|
31518
|
+
{ ...entity.context, eventData },
|
|
31519
|
+
"TRIGGER",
|
|
31520
|
+
machineId,
|
|
31521
|
+
entity.entityId
|
|
31522
|
+
);
|
|
31523
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31524
|
+
if (trigger.sendEvent) {
|
|
31525
|
+
await this.send(machineId, entity.entityId, trigger.sendEvent, {
|
|
31526
|
+
...entity.context,
|
|
31527
|
+
triggerResult: result,
|
|
31528
|
+
eventData
|
|
31529
|
+
});
|
|
31530
|
+
}
|
|
31531
|
+
this.emit("plg:state-machine:trigger-executed", {
|
|
31532
|
+
machineId,
|
|
31533
|
+
entityId: entity.entityId,
|
|
31534
|
+
state: stateName,
|
|
31535
|
+
trigger: triggerName,
|
|
31536
|
+
type: "event",
|
|
31537
|
+
eventName
|
|
31538
|
+
});
|
|
31539
|
+
} catch (error) {
|
|
31540
|
+
if (this.config.verbose) {
|
|
31541
|
+
console.error(`[StateMachinePlugin] Event trigger '${triggerName}' failed:`, error.message);
|
|
31542
|
+
}
|
|
31543
|
+
}
|
|
31544
|
+
}
|
|
31545
|
+
};
|
|
31546
|
+
if (eventName.startsWith("db:")) {
|
|
31547
|
+
const dbEventName = eventName.substring(3);
|
|
31548
|
+
this.database.on(dbEventName, eventHandler);
|
|
31549
|
+
if (this.config.verbose) {
|
|
31550
|
+
console.log(`[StateMachinePlugin] Listening to database event '${dbEventName}' for trigger '${triggerName}'`);
|
|
31551
|
+
}
|
|
31552
|
+
} else {
|
|
31553
|
+
this.on(eventName, eventHandler);
|
|
31554
|
+
if (this.config.verbose) {
|
|
31555
|
+
console.log(`[StateMachinePlugin] Listening to plugin event '${eventName}' for trigger '${triggerName}'`);
|
|
31556
|
+
}
|
|
31557
|
+
}
|
|
31558
|
+
}
|
|
31559
|
+
/**
|
|
31560
|
+
* Attach state machine instances to their associated resources
|
|
31561
|
+
* This enables the resource API: resource.state(id, event)
|
|
31562
|
+
* @private
|
|
31563
|
+
*/
|
|
31564
|
+
async _attachStateMachinesToResources() {
|
|
31565
|
+
for (const [machineName, machineConfig] of Object.entries(this.config.stateMachines)) {
|
|
31566
|
+
const resourceConfig = machineConfig.config || machineConfig;
|
|
31567
|
+
if (!resourceConfig.resource) {
|
|
31568
|
+
if (this.config.verbose) {
|
|
31569
|
+
console.log(`[StateMachinePlugin] Machine '${machineName}' has no resource configured, skipping attachment`);
|
|
31570
|
+
}
|
|
31571
|
+
continue;
|
|
31572
|
+
}
|
|
31573
|
+
let resource;
|
|
31574
|
+
if (typeof resourceConfig.resource === "string") {
|
|
31575
|
+
resource = this.database.resources[resourceConfig.resource];
|
|
31576
|
+
if (!resource) {
|
|
31577
|
+
console.warn(
|
|
31578
|
+
`[StateMachinePlugin] Resource '${resourceConfig.resource}' not found for machine '${machineName}'. Resource API will not be available.`
|
|
31579
|
+
);
|
|
31580
|
+
continue;
|
|
31581
|
+
}
|
|
31582
|
+
} else {
|
|
31583
|
+
resource = resourceConfig.resource;
|
|
31584
|
+
}
|
|
31585
|
+
const machineProxy = {
|
|
31586
|
+
send: async (id, event, eventData) => {
|
|
31587
|
+
return this.send(machineName, id, event, eventData);
|
|
31588
|
+
},
|
|
31589
|
+
getState: async (id) => {
|
|
31590
|
+
return this.getState(machineName, id);
|
|
31591
|
+
},
|
|
31592
|
+
canTransition: async (id, event) => {
|
|
31593
|
+
return this.canTransition(machineName, id, event);
|
|
31594
|
+
},
|
|
31595
|
+
getValidEvents: async (id) => {
|
|
31596
|
+
return this.getValidEvents(machineName, id);
|
|
31597
|
+
},
|
|
31598
|
+
initializeEntity: async (id, context) => {
|
|
31599
|
+
return this.initializeEntity(machineName, id, context);
|
|
31600
|
+
},
|
|
31601
|
+
getTransitionHistory: async (id, options) => {
|
|
31602
|
+
return this.getTransitionHistory(machineName, id, options);
|
|
31603
|
+
}
|
|
31604
|
+
};
|
|
31605
|
+
resource._attachStateMachine(machineProxy);
|
|
31606
|
+
if (this.config.verbose) {
|
|
31607
|
+
console.log(`[StateMachinePlugin] Attached machine '${machineName}' to resource '${resource.name}'`);
|
|
31608
|
+
}
|
|
31609
|
+
}
|
|
31610
|
+
}
|
|
30641
31611
|
async start() {
|
|
30642
31612
|
if (this.config.verbose) {
|
|
30643
31613
|
console.log(`[StateMachinePlugin] Started with ${this.machines.size} state machines`);
|
|
30644
31614
|
}
|
|
30645
31615
|
}
|
|
30646
31616
|
async stop() {
|
|
31617
|
+
for (const interval of this.triggerIntervals) {
|
|
31618
|
+
clearInterval(interval);
|
|
31619
|
+
}
|
|
31620
|
+
this.triggerIntervals = [];
|
|
31621
|
+
if (this.schedulerPlugin) {
|
|
31622
|
+
await this.schedulerPlugin.stop();
|
|
31623
|
+
this.schedulerPlugin = null;
|
|
31624
|
+
}
|
|
30647
31625
|
this.machines.clear();
|
|
30648
31626
|
this.removeAllListeners();
|
|
30649
31627
|
}
|
|
@@ -40915,7 +41893,7 @@ class TTLPlugin extends Plugin {
|
|
|
40915
41893
|
if (this.verbose) {
|
|
40916
41894
|
console.log(`[TTLPlugin] Installed with ${Object.keys(this.resources).length} resources`);
|
|
40917
41895
|
}
|
|
40918
|
-
this.emit("installed", {
|
|
41896
|
+
this.emit("db:plugin:installed", {
|
|
40919
41897
|
plugin: "TTLPlugin",
|
|
40920
41898
|
resources: Object.keys(this.resources)
|
|
40921
41899
|
});
|
|
@@ -41152,7 +42130,7 @@ class TTLPlugin extends Plugin {
|
|
|
41152
42130
|
}
|
|
41153
42131
|
this.stats.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
41154
42132
|
this.stats.lastScanDuration = Date.now() - startTime;
|
|
41155
|
-
this.emit("
|
|
42133
|
+
this.emit("plg:ttl:scan-completed", {
|
|
41156
42134
|
granularity,
|
|
41157
42135
|
duration: this.stats.lastScanDuration,
|
|
41158
42136
|
cohorts
|
|
@@ -41160,7 +42138,7 @@ class TTLPlugin extends Plugin {
|
|
|
41160
42138
|
} catch (error) {
|
|
41161
42139
|
console.error(`[TTLPlugin] Error in ${granularity} cleanup:`, error);
|
|
41162
42140
|
this.stats.totalErrors++;
|
|
41163
|
-
this.emit("
|
|
42141
|
+
this.emit("plg:ttl:cleanup-error", { granularity, error });
|
|
41164
42142
|
}
|
|
41165
42143
|
}
|
|
41166
42144
|
/**
|
|
@@ -41208,7 +42186,7 @@ class TTLPlugin extends Plugin {
|
|
|
41208
42186
|
}
|
|
41209
42187
|
await this.expirationIndex.delete(entry.id);
|
|
41210
42188
|
this.stats.totalExpired++;
|
|
41211
|
-
this.emit("
|
|
42189
|
+
this.emit("plg:ttl:record-expired", { resource: entry.resourceName, record });
|
|
41212
42190
|
} catch (error) {
|
|
41213
42191
|
console.error(`[TTLPlugin] Error processing expired entry:`, error);
|
|
41214
42192
|
this.stats.totalErrors++;
|
|
@@ -41679,15 +42657,15 @@ class VectorPlugin extends Plugin {
|
|
|
41679
42657
|
this._throttleState = /* @__PURE__ */ new Map();
|
|
41680
42658
|
}
|
|
41681
42659
|
async onInstall() {
|
|
41682
|
-
this.emit("installed", { plugin: "VectorPlugin" });
|
|
42660
|
+
this.emit("db:plugin:installed", { plugin: "VectorPlugin" });
|
|
41683
42661
|
this.validateVectorStorage();
|
|
41684
42662
|
this.installResourceMethods();
|
|
41685
42663
|
}
|
|
41686
42664
|
async onStart() {
|
|
41687
|
-
this.emit("started", { plugin: "VectorPlugin" });
|
|
42665
|
+
this.emit("db:plugin:started", { plugin: "VectorPlugin" });
|
|
41688
42666
|
}
|
|
41689
42667
|
async onStop() {
|
|
41690
|
-
this.emit("stopped", { plugin: "VectorPlugin" });
|
|
42668
|
+
this.emit("db:plugin:stopped", { plugin: "VectorPlugin" });
|
|
41691
42669
|
}
|
|
41692
42670
|
async onUninstall(options) {
|
|
41693
42671
|
for (const resource of Object.values(this.database.resources)) {
|
|
@@ -41698,7 +42676,7 @@ class VectorPlugin extends Plugin {
|
|
|
41698
42676
|
delete resource.findSimilar;
|
|
41699
42677
|
delete resource.distance;
|
|
41700
42678
|
}
|
|
41701
|
-
this.emit("uninstalled", { plugin: "VectorPlugin" });
|
|
42679
|
+
this.emit("db:plugin:uninstalled", { plugin: "VectorPlugin" });
|
|
41702
42680
|
}
|
|
41703
42681
|
/**
|
|
41704
42682
|
* Validate vector storage configuration for all resources
|
|
@@ -41727,10 +42705,10 @@ class VectorPlugin extends Plugin {
|
|
|
41727
42705
|
currentBehavior: resource.behavior || "default",
|
|
41728
42706
|
recommendation: "body-overflow"
|
|
41729
42707
|
};
|
|
41730
|
-
this.emit("vector:storage-warning", warning);
|
|
42708
|
+
this.emit("plg:vector:storage-warning", warning);
|
|
41731
42709
|
if (this.config.autoFixBehavior) {
|
|
41732
42710
|
resource.behavior = "body-overflow";
|
|
41733
|
-
this.emit("vector:behavior-fixed", {
|
|
42711
|
+
this.emit("plg:vector:behavior-fixed", {
|
|
41734
42712
|
resource: resource.name,
|
|
41735
42713
|
newBehavior: "body-overflow"
|
|
41736
42714
|
});
|
|
@@ -41762,7 +42740,7 @@ class VectorPlugin extends Plugin {
|
|
|
41762
42740
|
const partitionName = `byHas${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
|
|
41763
42741
|
const trackingFieldName = `_has${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
|
|
41764
42742
|
if (resource.config.partitions && resource.config.partitions[partitionName]) {
|
|
41765
|
-
this.emit("vector:partition-exists", {
|
|
42743
|
+
this.emit("plg:vector:partition-exists", {
|
|
41766
42744
|
resource: resource.name,
|
|
41767
42745
|
vectorField: vectorField.name,
|
|
41768
42746
|
partition: partitionName,
|
|
@@ -41785,7 +42763,7 @@ class VectorPlugin extends Plugin {
|
|
|
41785
42763
|
default: false
|
|
41786
42764
|
}, "VectorPlugin");
|
|
41787
42765
|
}
|
|
41788
|
-
this.emit("vector:partition-created", {
|
|
42766
|
+
this.emit("plg:vector:partition-created", {
|
|
41789
42767
|
resource: resource.name,
|
|
41790
42768
|
vectorField: vectorField.name,
|
|
41791
42769
|
partition: partitionName,
|
|
@@ -41860,7 +42838,7 @@ class VectorPlugin extends Plugin {
|
|
|
41860
42838
|
}
|
|
41861
42839
|
return updates;
|
|
41862
42840
|
});
|
|
41863
|
-
this.emit("vector:hooks-installed", {
|
|
42841
|
+
this.emit("plg:vector:hooks-installed", {
|
|
41864
42842
|
resource: resource.name,
|
|
41865
42843
|
vectorField,
|
|
41866
42844
|
trackingField,
|
|
@@ -41969,7 +42947,7 @@ class VectorPlugin extends Plugin {
|
|
|
41969
42947
|
const vectorField = this._findEmbeddingField(resource.schema.attributes);
|
|
41970
42948
|
this._vectorFieldCache.set(resource.name, vectorField);
|
|
41971
42949
|
if (vectorField && this.config.emitEvents) {
|
|
41972
|
-
this.emit("vector:field-detected", {
|
|
42950
|
+
this.emit("plg:vector:field-detected", {
|
|
41973
42951
|
resource: resource.name,
|
|
41974
42952
|
vectorField,
|
|
41975
42953
|
timestamp: Date.now()
|
|
@@ -42893,7 +43871,7 @@ class MemoryClient extends EventEmitter {
|
|
|
42893
43871
|
async sendCommand(command) {
|
|
42894
43872
|
const commandName = command.constructor.name;
|
|
42895
43873
|
const input = command.input || {};
|
|
42896
|
-
this.emit("
|
|
43874
|
+
this.emit("cl:request", commandName, input);
|
|
42897
43875
|
let response;
|
|
42898
43876
|
try {
|
|
42899
43877
|
switch (commandName) {
|
|
@@ -42921,7 +43899,7 @@ class MemoryClient extends EventEmitter {
|
|
|
42921
43899
|
default:
|
|
42922
43900
|
throw new Error(`Unsupported command: ${commandName}`);
|
|
42923
43901
|
}
|
|
42924
|
-
this.emit("
|
|
43902
|
+
this.emit("cl:response", commandName, response, input);
|
|
42925
43903
|
return response;
|
|
42926
43904
|
} catch (error) {
|
|
42927
43905
|
const mappedError = mapAwsError(error, {
|
|
@@ -43032,7 +44010,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43032
44010
|
contentLength,
|
|
43033
44011
|
ifMatch
|
|
43034
44012
|
});
|
|
43035
|
-
this.emit("
|
|
44013
|
+
this.emit("cl:PutObject", null, { key, metadata, contentType, body, contentEncoding, contentLength });
|
|
43036
44014
|
return response;
|
|
43037
44015
|
}
|
|
43038
44016
|
/**
|
|
@@ -43047,7 +44025,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43047
44025
|
decodedMetadata[k] = metadataDecode(v);
|
|
43048
44026
|
}
|
|
43049
44027
|
}
|
|
43050
|
-
this.emit("
|
|
44028
|
+
this.emit("cl:GetObject", null, { key });
|
|
43051
44029
|
return {
|
|
43052
44030
|
...response,
|
|
43053
44031
|
Metadata: decodedMetadata
|
|
@@ -43065,7 +44043,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43065
44043
|
decodedMetadata[k] = metadataDecode(v);
|
|
43066
44044
|
}
|
|
43067
44045
|
}
|
|
43068
|
-
this.emit("
|
|
44046
|
+
this.emit("cl:HeadObject", null, { key });
|
|
43069
44047
|
return {
|
|
43070
44048
|
...response,
|
|
43071
44049
|
Metadata: decodedMetadata
|
|
@@ -43090,7 +44068,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43090
44068
|
metadataDirective,
|
|
43091
44069
|
contentType
|
|
43092
44070
|
});
|
|
43093
|
-
this.emit("
|
|
44071
|
+
this.emit("cl:CopyObject", null, { from, to, metadata, metadataDirective });
|
|
43094
44072
|
return response;
|
|
43095
44073
|
}
|
|
43096
44074
|
/**
|
|
@@ -43106,7 +44084,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43106
44084
|
async deleteObject(key) {
|
|
43107
44085
|
const fullKey = this.keyPrefix ? path$1.join(this.keyPrefix, key) : key;
|
|
43108
44086
|
const response = await this.storage.delete(fullKey);
|
|
43109
|
-
this.emit("
|
|
44087
|
+
this.emit("cl:DeleteObject", null, { key });
|
|
43110
44088
|
return response;
|
|
43111
44089
|
}
|
|
43112
44090
|
/**
|
|
@@ -43139,7 +44117,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43139
44117
|
maxKeys,
|
|
43140
44118
|
continuationToken
|
|
43141
44119
|
});
|
|
43142
|
-
this.emit("
|
|
44120
|
+
this.emit("cl:ListObjects", null, { prefix, count: response.Contents.length });
|
|
43143
44121
|
return response;
|
|
43144
44122
|
}
|
|
43145
44123
|
/**
|
|
@@ -43179,7 +44157,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43179
44157
|
if (this.keyPrefix) {
|
|
43180
44158
|
keys = keys.map((x) => x.replace(this.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace("/", "") : x);
|
|
43181
44159
|
}
|
|
43182
|
-
this.emit("
|
|
44160
|
+
this.emit("cl:GetKeysPage", keys, params);
|
|
43183
44161
|
return keys;
|
|
43184
44162
|
}
|
|
43185
44163
|
/**
|
|
@@ -43196,7 +44174,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43196
44174
|
if (this.keyPrefix) {
|
|
43197
44175
|
keys = keys.map((x) => x.replace(this.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace("/", "") : x);
|
|
43198
44176
|
}
|
|
43199
|
-
this.emit("
|
|
44177
|
+
this.emit("cl:GetAllKeys", keys, { prefix });
|
|
43200
44178
|
return keys;
|
|
43201
44179
|
}
|
|
43202
44180
|
/**
|
|
@@ -43205,7 +44183,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43205
44183
|
async count({ prefix = "" } = {}) {
|
|
43206
44184
|
const keys = await this.getAllKeys({ prefix });
|
|
43207
44185
|
const count = keys.length;
|
|
43208
|
-
this.emit("
|
|
44186
|
+
this.emit("cl:Count", count, { prefix });
|
|
43209
44187
|
return count;
|
|
43210
44188
|
}
|
|
43211
44189
|
/**
|
|
@@ -43217,13 +44195,13 @@ class MemoryClient extends EventEmitter {
|
|
|
43217
44195
|
if (keys.length > 0) {
|
|
43218
44196
|
const result = await this.deleteObjects(keys);
|
|
43219
44197
|
totalDeleted = result.Deleted.length;
|
|
43220
|
-
this.emit("
|
|
44198
|
+
this.emit("cl:DeleteAll", {
|
|
43221
44199
|
prefix,
|
|
43222
44200
|
batch: totalDeleted,
|
|
43223
44201
|
total: totalDeleted
|
|
43224
44202
|
});
|
|
43225
44203
|
}
|
|
43226
|
-
this.emit("
|
|
44204
|
+
this.emit("cl:DeleteAllComplete", {
|
|
43227
44205
|
prefix,
|
|
43228
44206
|
totalDeleted
|
|
43229
44207
|
});
|
|
@@ -43236,11 +44214,11 @@ class MemoryClient extends EventEmitter {
|
|
|
43236
44214
|
if (offset === 0) return null;
|
|
43237
44215
|
const keys = await this.getAllKeys({ prefix });
|
|
43238
44216
|
if (offset >= keys.length) {
|
|
43239
|
-
this.emit("
|
|
44217
|
+
this.emit("cl:GetContinuationTokenAfterOffset", null, { prefix, offset });
|
|
43240
44218
|
return null;
|
|
43241
44219
|
}
|
|
43242
44220
|
const token = keys[offset];
|
|
43243
|
-
this.emit("
|
|
44221
|
+
this.emit("cl:GetContinuationTokenAfterOffset", token, { prefix, offset });
|
|
43244
44222
|
return token;
|
|
43245
44223
|
}
|
|
43246
44224
|
/**
|
|
@@ -43270,7 +44248,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43270
44248
|
});
|
|
43271
44249
|
}
|
|
43272
44250
|
}
|
|
43273
|
-
this.emit("
|
|
44251
|
+
this.emit("cl:MoveAllObjects", { results, errors }, { prefixFrom, prefixTo });
|
|
43274
44252
|
if (errors.length > 0) {
|
|
43275
44253
|
const error = new Error("Some objects could not be moved");
|
|
43276
44254
|
error.context = {
|