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