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/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("backup_start", { id: backupId, type });
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("backup_complete", {
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("backup_error", { id: backupId, type, error: error.message });
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("restore_start", { id: backupId, options });
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("restore_complete", {
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("restore_error", { id: backupId, error: error.message });
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("backup_cancelled", { id: backupId });
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("cache_clear_error", {
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("cache_clear_error", {
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("cache_clear_error", {
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("@tensorflow/tfjs-node", "MLPlugin");
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("modelTrained", {
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 [ok, err, versionInfo] = await tryFn(() => storage.get(`version_${modelName}`));
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.patch(`version_${modelName}`, {
15846
- modelName,
15847
- currentVersion: versionInfo.currentVersion,
15848
- latestVersion: versionInfo.latestVersion,
15849
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
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.patch(`model_${modelName}_v${version}`, {
15877
- modelName,
15878
- version,
15879
- type: "model",
15880
- data: JSON.stringify(exportedModel),
15881
- metrics: JSON.stringify({
15882
- loss: modelStats.loss,
15883
- accuracy: modelStats.accuracy,
15884
- samples: modelStats.samples
15885
- }),
15886
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
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.patch(`model_${modelName}_active`, {
15890
- modelName,
15891
- version,
15892
- type: "reference",
15893
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
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 plugin storage (S3)`);
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.patch(`model_${modelName}`, {
15900
- modelName,
15901
- type: "model",
15902
- data: JSON.stringify(exportedModel),
15903
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
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 plugin storage (S3)`);
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 trainingEntry = {
15925
- version: enableVersioning ? this.modelVersions.get(modelName)?.latestVersion || 1 : void 0,
15926
- samples: rawData.length,
15927
- features: modelConfig.features,
15928
- target: modelConfig.target,
15929
- data: rawData.map((item) => {
15930
- const features = {};
15931
- modelConfig.features.forEach((feature) => {
15932
- features[feature] = item[feature];
15933
- });
15934
- return {
15935
- features,
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 [ok, err, existing] = await tryFn(() => storage.get(`training_history_${modelName}`));
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
- try {
15951
- history = JSON.parse(existing.history);
15952
- } catch (e) {
15953
- history = [];
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
- history.push(trainingEntry);
15957
- await storage.patch(`training_history_${modelName}`, {
15958
- modelName,
15959
- type: "training_history",
15960
- totalTrainings: history.length,
15961
- latestVersion: trainingEntry.version,
15962
- history: JSON.stringify(history),
15963
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
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] Appended training data for "${modelName}" v${trainingEntry.version} (${trainingEntry.samples} samples, total: ${history.length} trainings)`);
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.patch(`training_data_${modelName}`, {
15970
- modelName,
15971
- type: "training_data",
15972
- samples: trainingEntry.samples,
15973
- features: JSON.stringify(trainingEntry.features),
15974
- target: trainingEntry.target,
15975
- data: JSON.stringify(trainingEntry.data),
15976
- savedAt: trainingEntry.trainedAt
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}" (${trainingEntry.samples} samples) to plugin storage (S3)`);
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(() => storage.get(`model_${modelName}_active`));
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(() => storage.get(`model_${modelName}_v${version}`));
15999
- if (ok && versionData) {
16000
- const modelData = JSON.parse(versionData.data);
16001
- await this.models[modelName].import(modelData);
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 plugin storage (S3)`);
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(() => storage.get(`model_${modelName}_v${version}`));
16012
- if (ok && versionData) {
16013
- const modelData = JSON.parse(versionData.data);
16014
- await this.models[modelName].import(modelData);
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 plugin storage (S3)`);
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(() => storage.get(`model_${modelName}`));
16026
- if (!ok || !record) {
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
- const modelData = JSON.parse(record.data);
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 plugin storage (S3)`);
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 [ok, err, record] = await tryFn(() => storage.get(`training_data_${modelName}`));
16051
- if (!ok || !record) {
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 saved training data found for "${modelName}"`);
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: record.modelName,
16059
- samples: record.samples,
16060
- features: JSON.parse(record.features),
16061
- target: record.target,
16062
- data: JSON.parse(record.data),
16063
- savedAt: record.savedAt
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
- await storage.delete(`model_${modelName}`);
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 storage`);
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
- await storage.delete(`training_data_${modelName}`);
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 storage`);
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(`model_${modelName}_v${v}`));
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 [ok, err, versionData] = await tryFn(() => storage.get(`model_${modelName}_v${version}`));
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
- const modelData = JSON.parse(versionData.data);
16154
- await this.models[modelName].import(modelData);
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.patch(`model_${modelName}_active`, {
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 [ok, err, historyData] = await tryFn(() => storage.get(`training_history_${modelName}`));
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 [ok1, err1, v1Data] = await tryFn(() => storage.get(`model_${modelName}_v${version1}`));
16237
- const [ok2, err2, v2Data] = await tryFn(() => storage.get(`model_${modelName}_v${version2}`));
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("command.request", command.constructor.name, command.input);
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("command.response", command.constructor.name, response, command.input);
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("putObject", err || response, { key, metadata, contentType, body, contentEncoding, contentLength });
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("getObject", err || response, { key });
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("headObject", err || response, { key });
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("copyObject", err || response, { from, to, metadataDirective });
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("deleteObject", err || response, { key });
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("deleteObjects", report, keys);
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("deleteAll", {
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("deleteAllComplete", {
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("listObjects", response, options);
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("count", count, { prefix });
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("getAllKeys", keys, { prefix });
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("getContinuationTokenAfterOffset", continuationToken || null, params);
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("getKeysPage", [], params);
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("getKeysPage", keys, params);
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("moveAllObjects", { results, errors }, { prefixFrom, prefixTo });
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.emit("insert", finalResult);
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.emit("insert", finalResult);
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.emit("get", data);
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.emit("update", {
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.emit("update", {
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.emit("update", {
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.emit("update", {
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.emit("delete", {
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.emit("count", count);
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.emit("insertMany", objects.length);
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.emit("deleteMany", ids.length);
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.emit("deleteAll", {
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.emit("deleteAllData", {
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.emit("listIds", ids.length);
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.emit("list", { count: results.length, errors: 0 });
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.emit("list", { partition, partitionValues, count: 0, errors: 0 });
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.emit("list", { partition, partitionValues, count: results.length, errors: 0 });
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.emit("list", { count: results.length, errors: 0 });
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.emit("list", { partition, partitionValues, count: 0, errors: 1 });
25076
+ this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
24876
25077
  return [];
24877
25078
  }
24878
- this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
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.emit("getMany", ids.length);
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.emit("page", result2);
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.emit("setContent", { id, contentType, contentLength: buffer.length });
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.emit("content", id, buffer.length, contentType);
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.emit("deleteContent", id);
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.emit("getFromPartition", data);
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.1.0" : "latest");
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("resourceDefinitionsChanged", {
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.uninstalled", { name: pluginName, 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("metadataUploaded", metadata);
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("metadataHealed", { healingLog, metadata });
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("s3db.resourceUpdated", name);
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("s3db.resourceCreated", name);
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("hookError", { event, error, context });
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("replicator_log_error", {
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("replicator_error", {
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("replicator_error", {
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("replicator_error", {
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.log.failed", { error: "replicator log resource not found", item });
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.log.failed", { error: err, item });
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.updateLog.failed", { error: err.message, logId, updates });
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.sync.resource", { resourceName, replicatorId });
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.sync.completed", { replicatorId, stats: this.stats });
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("replicator_stop_error", {
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("replicator_plugin_stop_error", {
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.started", { concurrency, workerId: this.workerId });
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.stopped", { workerId: this.workerId });
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.completed", {
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.retry", {
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.dead", {
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("job_start", { jobName, executionId, startTime });
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("job_complete", {
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("job_enabled", { jobName });
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("job_disabled", { jobName });
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("job_added", { jobName });
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("job_removed", { jobName });
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
- this.emit("initialized", { machines: Array.from(this.machines.keys()) });
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 [ok, error] = await tryFn(
30351
- () => action(context, event, { database: this.database, machineId, entityId })
30352
- );
30353
- if (!ok) {
30354
- if (this.config.verbose) {
30355
- console.error(`[StateMachinePlugin] Action '${actionName}' failed:`, error.message);
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("entity_initialized", { machineId, entityId, initialState });
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("scanCompleted", {
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("cleanupError", { granularity, error });
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("recordExpired", { resource: entry.resourceName, record });
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("command.request", commandName, input);
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("command.response", commandName, response, input);
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("putObject", null, { key, metadata, contentType, body, contentEncoding, contentLength });
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("getObject", null, { key });
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("headObject", null, { key });
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("copyObject", null, { from, to, metadata, metadataDirective });
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("deleteObject", null, { key });
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("listObjects", null, { prefix, count: response.Contents.length });
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("getKeysPage", keys, params);
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("getAllKeys", keys, { prefix });
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("count", count, { prefix });
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("deleteAll", {
44198
+ this.emit("cl:DeleteAll", {
43221
44199
  prefix,
43222
44200
  batch: totalDeleted,
43223
44201
  total: totalDeleted
43224
44202
  });
43225
44203
  }
43226
- this.emit("deleteAllComplete", {
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("getContinuationTokenAfterOffset", null, { prefix, offset });
44217
+ this.emit("cl:GetContinuationTokenAfterOffset", null, { prefix, offset });
43240
44218
  return null;
43241
44219
  }
43242
44220
  const token = keys[offset];
43243
- this.emit("getContinuationTokenAfterOffset", token, { prefix, offset });
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("moveAllObjects", { results, errors }, { prefixFrom, prefixTo });
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 = {