s3db.js 13.1.0 → 13.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/s3db.es.js CHANGED
@@ -2544,6 +2544,18 @@ const PLUGIN_DEPENDENCIES = {
2544
2544
  npmUrl: "https://www.npmjs.com/package/@hono/swagger-ui"
2545
2545
  }
2546
2546
  }
2547
+ },
2548
+ "ml-plugin": {
2549
+ name: "ML Plugin",
2550
+ docsUrl: "https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/ml-plugin.md",
2551
+ dependencies: {
2552
+ "@tensorflow/tfjs-node": {
2553
+ version: "^4.0.0",
2554
+ description: "TensorFlow.js for Node.js with native bindings",
2555
+ installCommand: "pnpm add @tensorflow/tfjs-node",
2556
+ npmUrl: "https://www.npmjs.com/package/@tensorflow/tfjs-node"
2557
+ }
2558
+ }
2547
2559
  }
2548
2560
  };
2549
2561
  function isVersionCompatible(actual, required) {
@@ -6593,7 +6605,7 @@ class BackupPlugin extends Plugin {
6593
6605
  const storageInfo = this.driver.getStorageInfo();
6594
6606
  console.log(`[BackupPlugin] Initialized with driver: ${storageInfo.type}`);
6595
6607
  }
6596
- this.emit("initialized", {
6608
+ this.emit("db:plugin:initialized", {
6597
6609
  driver: this.driver.getType(),
6598
6610
  config: this.driver.getStorageInfo()
6599
6611
  });
@@ -6641,7 +6653,7 @@ class BackupPlugin extends Plugin {
6641
6653
  if (this.config.onBackupStart) {
6642
6654
  await this._executeHook(this.config.onBackupStart, type, { backupId });
6643
6655
  }
6644
- this.emit("backup_start", { id: backupId, type });
6656
+ this.emit("plg:backup:start", { id: backupId, type });
6645
6657
  const metadata = await this._createBackupMetadata(backupId, type);
6646
6658
  const tempBackupDir = path$1.join(this.config.tempDir, backupId);
6647
6659
  await mkdir(tempBackupDir, { recursive: true });
@@ -6674,7 +6686,7 @@ class BackupPlugin extends Plugin {
6674
6686
  const stats = { backupId, type, size: totalSize, duration, driverInfo: uploadResult };
6675
6687
  await this._executeHook(this.config.onBackupComplete, type, stats);
6676
6688
  }
6677
- this.emit("backup_complete", {
6689
+ this.emit("plg:backup:complete", {
6678
6690
  id: backupId,
6679
6691
  type,
6680
6692
  size: totalSize,
@@ -6702,7 +6714,7 @@ class BackupPlugin extends Plugin {
6702
6714
  error: error.message,
6703
6715
  duration: Date.now() - startTime
6704
6716
  });
6705
- this.emit("backup_error", { id: backupId, type, error: error.message });
6717
+ this.emit("plg:backup:error", { id: backupId, type, error: error.message });
6706
6718
  throw error;
6707
6719
  } finally {
6708
6720
  this.activeBackups.delete(backupId);
@@ -6923,7 +6935,7 @@ class BackupPlugin extends Plugin {
6923
6935
  if (this.config.onRestoreStart) {
6924
6936
  await this._executeHook(this.config.onRestoreStart, backupId, options);
6925
6937
  }
6926
- this.emit("restore_start", { id: backupId, options });
6938
+ this.emit("plg:backup:restore-start", { id: backupId, options });
6927
6939
  const backup = await this.getBackupStatus(backupId);
6928
6940
  if (!backup) {
6929
6941
  throw new Error(`Backup '${backupId}' not found`);
@@ -6946,7 +6958,7 @@ class BackupPlugin extends Plugin {
6946
6958
  if (this.config.onRestoreComplete) {
6947
6959
  await this._executeHook(this.config.onRestoreComplete, backupId, { restored: restoredResources });
6948
6960
  }
6949
- this.emit("restore_complete", {
6961
+ this.emit("plg:backup:restore-complete", {
6950
6962
  id: backupId,
6951
6963
  restored: restoredResources
6952
6964
  });
@@ -6961,7 +6973,7 @@ class BackupPlugin extends Plugin {
6961
6973
  if (this.config.onRestoreError) {
6962
6974
  await this._executeHook(this.config.onRestoreError, backupId, { error });
6963
6975
  }
6964
- this.emit("restore_error", { id: backupId, error: error.message });
6976
+ this.emit("plg:backup:restore-error", { id: backupId, error: error.message });
6965
6977
  throw error;
6966
6978
  }
6967
6979
  }
@@ -7211,7 +7223,7 @@ class BackupPlugin extends Plugin {
7211
7223
  }
7212
7224
  async stop() {
7213
7225
  for (const backupId of this.activeBackups) {
7214
- this.emit("backup_cancelled", { id: backupId });
7226
+ this.emit("plg:backup:cancelled", { id: backupId });
7215
7227
  }
7216
7228
  this.activeBackups.clear();
7217
7229
  if (this.driver) {
@@ -8735,7 +8747,7 @@ class CachePlugin extends Plugin {
8735
8747
  const specificKey = await this.generateCacheKey(resource, method, { id: data.id });
8736
8748
  const [ok2, err2] = await this.clearCacheWithRetry(resource.cache, specificKey);
8737
8749
  if (!ok2) {
8738
- this.emit("cache_clear_error", {
8750
+ this.emit("plg:cache:clear-error", {
8739
8751
  resource: resource.name,
8740
8752
  method,
8741
8753
  id: data.id,
@@ -8753,7 +8765,7 @@ class CachePlugin extends Plugin {
8753
8765
  const partitionKeyPrefix = join(keyPrefix, `partition=${partitionName}`);
8754
8766
  const [ok2, err2] = await this.clearCacheWithRetry(resource.cache, partitionKeyPrefix);
8755
8767
  if (!ok2) {
8756
- this.emit("cache_clear_error", {
8768
+ this.emit("plg:cache:clear-error", {
8757
8769
  resource: resource.name,
8758
8770
  partition: partitionName,
8759
8771
  error: err2.message
@@ -8768,7 +8780,7 @@ class CachePlugin extends Plugin {
8768
8780
  }
8769
8781
  const [ok, err] = await this.clearCacheWithRetry(resource.cache, keyPrefix);
8770
8782
  if (!ok) {
8771
- this.emit("cache_clear_error", {
8783
+ this.emit("plg:cache:clear-error", {
8772
8784
  resource: resource.name,
8773
8785
  type: "broad",
8774
8786
  error: err.message
@@ -12668,7 +12680,7 @@ class GeoPlugin extends Plugin {
12668
12680
  if (this.verbose) {
12669
12681
  console.log(`[GeoPlugin] Installed with ${Object.keys(this.resources).length} resources`);
12670
12682
  }
12671
- this.emit("installed", {
12683
+ this.emit("db:plugin:installed", {
12672
12684
  plugin: "GeoPlugin",
12673
12685
  resources: Object.keys(this.resources)
12674
12686
  });
@@ -13235,7 +13247,7 @@ class GeoPlugin extends Plugin {
13235
13247
  if (this.verbose) {
13236
13248
  console.log("[GeoPlugin] Uninstalled");
13237
13249
  }
13238
- this.emit("uninstalled", {
13250
+ this.emit("db:plugin:uninstalled", {
13239
13251
  plugin: "GeoPlugin"
13240
13252
  });
13241
13253
  await super.uninstall();
@@ -15237,7 +15249,7 @@ class MLPlugin extends Plugin {
15237
15249
  enableVersioning: options.enableVersioning !== false
15238
15250
  // Default true
15239
15251
  };
15240
- requirePluginDependency("@tensorflow/tfjs-node", "MLPlugin");
15252
+ requirePluginDependency("ml-plugin");
15241
15253
  this.models = {};
15242
15254
  this.modelVersions = /* @__PURE__ */ new Map();
15243
15255
  this.modelCache = /* @__PURE__ */ new Map();
@@ -15275,7 +15287,7 @@ class MLPlugin extends Plugin {
15275
15287
  if (this.config.verbose) {
15276
15288
  console.log(`[MLPlugin] Installed with ${Object.keys(this.models).length} models`);
15277
15289
  }
15278
- this.emit("installed", {
15290
+ this.emit("db:plugin:installed", {
15279
15291
  plugin: "MLPlugin",
15280
15292
  models: Object.keys(this.models)
15281
15293
  });
@@ -15626,6 +15638,22 @@ class MLPlugin extends Plugin {
15626
15638
  }
15627
15639
  data = allData;
15628
15640
  }
15641
+ if (modelConfig.filter && typeof modelConfig.filter === "function") {
15642
+ if (this.config.verbose) {
15643
+ console.log(`[MLPlugin] Applying custom filter function...`);
15644
+ }
15645
+ const originalLength = data.length;
15646
+ data = data.filter(modelConfig.filter);
15647
+ if (this.config.verbose) {
15648
+ console.log(`[MLPlugin] Filter reduced dataset from ${originalLength} to ${data.length} samples`);
15649
+ }
15650
+ }
15651
+ if (modelConfig.map && typeof modelConfig.map === "function") {
15652
+ if (this.config.verbose) {
15653
+ console.log(`[MLPlugin] Applying custom map function...`);
15654
+ }
15655
+ data = data.map(modelConfig.map);
15656
+ }
15629
15657
  if (!data || data.length < this.config.minTrainingSamples) {
15630
15658
  throw new TrainingError(
15631
15659
  `Insufficient training data: ${data?.length || 0} samples (minimum: ${this.config.minTrainingSamples})`,
@@ -15648,7 +15676,7 @@ class MLPlugin extends Plugin {
15648
15676
  if (this.config.verbose) {
15649
15677
  console.log(`[MLPlugin] Training completed for "${modelName}":`, result);
15650
15678
  }
15651
- this.emit("modelTrained", {
15679
+ this.emit("plg:ml:model-trained", {
15652
15680
  modelName,
15653
15681
  type: modelConfig.type,
15654
15682
  result
@@ -15684,7 +15712,7 @@ class MLPlugin extends Plugin {
15684
15712
  try {
15685
15713
  const result = await model.predict(input);
15686
15714
  this.stats.totalPredictions++;
15687
- this.emit("prediction", {
15715
+ this.emit("plg:ml:prediction", {
15688
15716
  modelName,
15689
15717
  input,
15690
15718
  result
@@ -15799,7 +15827,11 @@ class MLPlugin extends Plugin {
15799
15827
  async _initializeVersioning(modelName) {
15800
15828
  try {
15801
15829
  const storage = this.getStorage();
15802
- const [ok, err, versionInfo] = await tryFn(() => storage.get(`version_${modelName}`));
15830
+ const modelConfig = this.config.models[modelName];
15831
+ const resourceName = modelConfig.resource;
15832
+ const [ok, err, versionInfo] = await tryFn(
15833
+ () => storage.get(storage.getPluginKey(resourceName, "metadata", modelName, "versions"))
15834
+ );
15803
15835
  if (ok && versionInfo) {
15804
15836
  this.modelVersions.set(modelName, {
15805
15837
  currentVersion: versionInfo.currentVersion || 1,
@@ -15838,16 +15870,22 @@ class MLPlugin extends Plugin {
15838
15870
  async _updateVersionInfo(modelName, version) {
15839
15871
  try {
15840
15872
  const storage = this.getStorage();
15873
+ const modelConfig = this.config.models[modelName];
15874
+ const resourceName = modelConfig.resource;
15841
15875
  const versionInfo = this.modelVersions.get(modelName) || { currentVersion: 1, latestVersion: 0 };
15842
15876
  versionInfo.latestVersion = Math.max(versionInfo.latestVersion, version);
15843
15877
  versionInfo.currentVersion = version;
15844
15878
  this.modelVersions.set(modelName, versionInfo);
15845
- await storage.patch(`version_${modelName}`, {
15846
- modelName,
15847
- currentVersion: versionInfo.currentVersion,
15848
- latestVersion: versionInfo.latestVersion,
15849
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15850
- });
15879
+ await storage.set(
15880
+ storage.getPluginKey(resourceName, "metadata", modelName, "versions"),
15881
+ {
15882
+ modelName,
15883
+ currentVersion: versionInfo.currentVersion,
15884
+ latestVersion: versionInfo.latestVersion,
15885
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15886
+ },
15887
+ { behavior: "body-overflow" }
15888
+ );
15851
15889
  if (this.config.verbose) {
15852
15890
  console.log(`[MLPlugin] Updated version info for "${modelName}": current=v${versionInfo.currentVersion}, latest=v${versionInfo.latestVersion}`);
15853
15891
  }
@@ -15862,6 +15900,8 @@ class MLPlugin extends Plugin {
15862
15900
  async _saveModel(modelName) {
15863
15901
  try {
15864
15902
  const storage = this.getStorage();
15903
+ const modelConfig = this.config.models[modelName];
15904
+ const resourceName = modelConfig.resource;
15865
15905
  const exportedModel = await this.models[modelName].export();
15866
15906
  if (!exportedModel) {
15867
15907
  if (this.config.verbose) {
@@ -15873,37 +15913,52 @@ class MLPlugin extends Plugin {
15873
15913
  if (enableVersioning) {
15874
15914
  const version = this._getNextVersion(modelName);
15875
15915
  const modelStats = this.models[modelName].getStats();
15876
- await storage.patch(`model_${modelName}_v${version}`, {
15877
- modelName,
15878
- version,
15879
- type: "model",
15880
- data: JSON.stringify(exportedModel),
15881
- metrics: JSON.stringify({
15882
- loss: modelStats.loss,
15883
- accuracy: modelStats.accuracy,
15884
- samples: modelStats.samples
15885
- }),
15886
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
15887
- });
15916
+ await storage.set(
15917
+ storage.getPluginKey(resourceName, "models", modelName, `v${version}`),
15918
+ {
15919
+ modelName,
15920
+ version,
15921
+ type: "model",
15922
+ modelData: exportedModel,
15923
+ // TensorFlow.js model object (will go to body)
15924
+ metrics: {
15925
+ loss: modelStats.loss,
15926
+ accuracy: modelStats.accuracy,
15927
+ samples: modelStats.samples
15928
+ },
15929
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
15930
+ },
15931
+ { behavior: "body-only" }
15932
+ // Large binary data goes to S3 body
15933
+ );
15888
15934
  await this._updateVersionInfo(modelName, version);
15889
- await storage.patch(`model_${modelName}_active`, {
15890
- modelName,
15891
- version,
15892
- type: "reference",
15893
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15894
- });
15935
+ await storage.set(
15936
+ storage.getPluginKey(resourceName, "metadata", modelName, "active"),
15937
+ {
15938
+ modelName,
15939
+ version,
15940
+ type: "reference",
15941
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15942
+ },
15943
+ { behavior: "body-overflow" }
15944
+ // Small metadata
15945
+ );
15895
15946
  if (this.config.verbose) {
15896
- console.log(`[MLPlugin] Saved model "${modelName}" v${version} to plugin storage (S3)`);
15947
+ console.log(`[MLPlugin] Saved model "${modelName}" v${version} to S3 (resource=${resourceName}/plugin=ml/models/${modelName}/v${version})`);
15897
15948
  }
15898
15949
  } else {
15899
- await storage.patch(`model_${modelName}`, {
15900
- modelName,
15901
- type: "model",
15902
- data: JSON.stringify(exportedModel),
15903
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
15904
- });
15950
+ await storage.set(
15951
+ storage.getPluginKey(resourceName, "models", modelName, "latest"),
15952
+ {
15953
+ modelName,
15954
+ type: "model",
15955
+ modelData: exportedModel,
15956
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
15957
+ },
15958
+ { behavior: "body-only" }
15959
+ );
15905
15960
  if (this.config.verbose) {
15906
- console.log(`[MLPlugin] Saved model "${modelName}" to plugin storage (S3)`);
15961
+ console.log(`[MLPlugin] Saved model "${modelName}" to S3 (resource=${resourceName}/plugin=ml/models/${modelName}/latest)`);
15907
15962
  }
15908
15963
  }
15909
15964
  } catch (error) {
@@ -15911,7 +15966,7 @@ class MLPlugin extends Plugin {
15911
15966
  }
15912
15967
  }
15913
15968
  /**
15914
- * Save intermediate training data to plugin storage (incremental)
15969
+ * Save intermediate training data to plugin storage (incremental - only new samples)
15915
15970
  * @private
15916
15971
  */
15917
15972
  async _saveTrainingData(modelName, rawData) {
@@ -15919,64 +15974,106 @@ class MLPlugin extends Plugin {
15919
15974
  const storage = this.getStorage();
15920
15975
  const model = this.models[modelName];
15921
15976
  const modelConfig = this.config.models[modelName];
15977
+ const resourceName = modelConfig.resource;
15922
15978
  const modelStats = model.getStats();
15923
15979
  const enableVersioning = this.config.enableVersioning;
15924
- const trainingEntry = {
15925
- version: enableVersioning ? this.modelVersions.get(modelName)?.latestVersion || 1 : void 0,
15926
- samples: rawData.length,
15927
- features: modelConfig.features,
15928
- target: modelConfig.target,
15929
- data: rawData.map((item) => {
15930
- const features = {};
15931
- modelConfig.features.forEach((feature) => {
15932
- features[feature] = item[feature];
15933
- });
15934
- return {
15935
- features,
15936
- target: item[modelConfig.target]
15937
- };
15938
- }),
15939
- metrics: {
15940
- loss: modelStats.loss,
15941
- accuracy: modelStats.accuracy,
15942
- r2: modelStats.r2
15943
- },
15944
- trainedAt: (/* @__PURE__ */ new Date()).toISOString()
15945
- };
15980
+ const processedData = rawData.map((item) => {
15981
+ const features = {};
15982
+ modelConfig.features.forEach((feature) => {
15983
+ features[feature] = item[feature];
15984
+ });
15985
+ return {
15986
+ id: item.id || `${Date.now()}_${Math.random()}`,
15987
+ // Use record ID or generate
15988
+ features,
15989
+ target: item[modelConfig.target]
15990
+ };
15991
+ });
15946
15992
  if (enableVersioning) {
15947
- const [ok, err, existing] = await tryFn(() => storage.get(`training_history_${modelName}`));
15993
+ const version = this._getNextVersion(modelName);
15994
+ const [ok, err, existing] = await tryFn(
15995
+ () => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
15996
+ );
15948
15997
  let history = [];
15998
+ let previousSampleIds = /* @__PURE__ */ new Set();
15949
15999
  if (ok && existing && existing.history) {
15950
- try {
15951
- history = JSON.parse(existing.history);
15952
- } catch (e) {
15953
- history = [];
15954
- }
16000
+ history = existing.history;
16001
+ history.forEach((entry) => {
16002
+ if (entry.sampleIds) {
16003
+ entry.sampleIds.forEach((id) => previousSampleIds.add(id));
16004
+ }
16005
+ });
15955
16006
  }
15956
- history.push(trainingEntry);
15957
- await storage.patch(`training_history_${modelName}`, {
15958
- modelName,
15959
- type: "training_history",
15960
- totalTrainings: history.length,
15961
- latestVersion: trainingEntry.version,
15962
- history: JSON.stringify(history),
15963
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15964
- });
16007
+ const currentSampleIds = new Set(processedData.map((d) => d.id));
16008
+ const newSamples = processedData.filter((d) => !previousSampleIds.has(d.id));
16009
+ const newSampleIds = newSamples.map((d) => d.id);
16010
+ if (newSamples.length > 0) {
16011
+ await storage.set(
16012
+ storage.getPluginKey(resourceName, "training", "data", modelName, `v${version}`),
16013
+ {
16014
+ modelName,
16015
+ version,
16016
+ samples: newSamples,
16017
+ // Only new samples
16018
+ features: modelConfig.features,
16019
+ target: modelConfig.target,
16020
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
16021
+ },
16022
+ { behavior: "body-only" }
16023
+ // Dataset goes to S3 body
16024
+ );
16025
+ }
16026
+ const historyEntry = {
16027
+ version,
16028
+ totalSamples: processedData.length,
16029
+ // Total cumulative
16030
+ newSamples: newSamples.length,
16031
+ // Only new in this version
16032
+ sampleIds: Array.from(currentSampleIds),
16033
+ // All IDs for this version
16034
+ newSampleIds,
16035
+ // IDs of new samples
16036
+ storageKey: newSamples.length > 0 ? `training/data/${modelName}/v${version}` : null,
16037
+ metrics: {
16038
+ loss: modelStats.loss,
16039
+ accuracy: modelStats.accuracy,
16040
+ r2: modelStats.r2
16041
+ },
16042
+ trainedAt: (/* @__PURE__ */ new Date()).toISOString()
16043
+ };
16044
+ history.push(historyEntry);
16045
+ await storage.set(
16046
+ storage.getPluginKey(resourceName, "training", "history", modelName),
16047
+ {
16048
+ modelName,
16049
+ type: "training_history",
16050
+ totalTrainings: history.length,
16051
+ latestVersion: version,
16052
+ history,
16053
+ // Array of metadata entries (not full data)
16054
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16055
+ },
16056
+ { behavior: "body-overflow" }
16057
+ // History metadata
16058
+ );
15965
16059
  if (this.config.verbose) {
15966
- console.log(`[MLPlugin] Appended training data for "${modelName}" v${trainingEntry.version} (${trainingEntry.samples} samples, total: ${history.length} trainings)`);
16060
+ console.log(`[MLPlugin] Saved training data for "${modelName}" v${version}: ${newSamples.length} new samples (total: ${processedData.length}, storage: resource=${resourceName}/plugin=ml/training/data/${modelName}/v${version})`);
15967
16061
  }
15968
16062
  } else {
15969
- await storage.patch(`training_data_${modelName}`, {
15970
- modelName,
15971
- type: "training_data",
15972
- samples: trainingEntry.samples,
15973
- features: JSON.stringify(trainingEntry.features),
15974
- target: trainingEntry.target,
15975
- data: JSON.stringify(trainingEntry.data),
15976
- savedAt: trainingEntry.trainedAt
15977
- });
16063
+ await storage.set(
16064
+ storage.getPluginKey(resourceName, "training", "data", modelName, "latest"),
16065
+ {
16066
+ modelName,
16067
+ type: "training_data",
16068
+ samples: processedData,
16069
+ features: modelConfig.features,
16070
+ target: modelConfig.target,
16071
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
16072
+ },
16073
+ { behavior: "body-only" }
16074
+ );
15978
16075
  if (this.config.verbose) {
15979
- console.log(`[MLPlugin] Saved training data for "${modelName}" (${trainingEntry.samples} samples) to plugin storage (S3)`);
16076
+ console.log(`[MLPlugin] Saved training data for "${modelName}" (${processedData.length} samples) to S3 (resource=${resourceName}/plugin=ml/training/data/${modelName}/latest)`);
15980
16077
  }
15981
16078
  }
15982
16079
  } catch (error) {
@@ -15990,17 +16087,22 @@ class MLPlugin extends Plugin {
15990
16087
  async _loadModel(modelName) {
15991
16088
  try {
15992
16089
  const storage = this.getStorage();
16090
+ const modelConfig = this.config.models[modelName];
16091
+ const resourceName = modelConfig.resource;
15993
16092
  const enableVersioning = this.config.enableVersioning;
15994
16093
  if (enableVersioning) {
15995
- const [okRef, errRef, activeRef] = await tryFn(() => storage.get(`model_${modelName}_active`));
16094
+ const [okRef, errRef, activeRef] = await tryFn(
16095
+ () => storage.get(storage.getPluginKey(resourceName, "metadata", modelName, "active"))
16096
+ );
15996
16097
  if (okRef && activeRef && activeRef.version) {
15997
16098
  const version = activeRef.version;
15998
- const [ok, err, versionData] = await tryFn(() => storage.get(`model_${modelName}_v${version}`));
15999
- if (ok && versionData) {
16000
- const modelData = JSON.parse(versionData.data);
16001
- await this.models[modelName].import(modelData);
16099
+ const [ok, err, versionData] = await tryFn(
16100
+ () => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`))
16101
+ );
16102
+ if (ok && versionData && versionData.modelData) {
16103
+ await this.models[modelName].import(versionData.modelData);
16002
16104
  if (this.config.verbose) {
16003
- console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (active) from plugin storage (S3)`);
16105
+ console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (active) from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/v${version})`);
16004
16106
  }
16005
16107
  return;
16006
16108
  }
@@ -16008,12 +16110,13 @@ class MLPlugin extends Plugin {
16008
16110
  const versionInfo = this.modelVersions.get(modelName);
16009
16111
  if (versionInfo && versionInfo.latestVersion > 0) {
16010
16112
  const version = versionInfo.latestVersion;
16011
- const [ok, err, versionData] = await tryFn(() => storage.get(`model_${modelName}_v${version}`));
16012
- if (ok && versionData) {
16013
- const modelData = JSON.parse(versionData.data);
16014
- await this.models[modelName].import(modelData);
16113
+ const [ok, err, versionData] = await tryFn(
16114
+ () => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`))
16115
+ );
16116
+ if (ok && versionData && versionData.modelData) {
16117
+ await this.models[modelName].import(versionData.modelData);
16015
16118
  if (this.config.verbose) {
16016
- console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (latest) from plugin storage (S3)`);
16119
+ console.log(`[MLPlugin] Loaded model "${modelName}" v${version} (latest) from S3`);
16017
16120
  }
16018
16121
  return;
16019
16122
  }
@@ -16022,17 +16125,18 @@ class MLPlugin extends Plugin {
16022
16125
  console.log(`[MLPlugin] No saved model versions found for "${modelName}"`);
16023
16126
  }
16024
16127
  } else {
16025
- const [ok, err, record] = await tryFn(() => storage.get(`model_${modelName}`));
16026
- if (!ok || !record) {
16128
+ const [ok, err, record] = await tryFn(
16129
+ () => storage.get(storage.getPluginKey(resourceName, "models", modelName, "latest"))
16130
+ );
16131
+ if (!ok || !record || !record.modelData) {
16027
16132
  if (this.config.verbose) {
16028
16133
  console.log(`[MLPlugin] No saved model found for "${modelName}"`);
16029
16134
  }
16030
16135
  return;
16031
16136
  }
16032
- const modelData = JSON.parse(record.data);
16033
- await this.models[modelName].import(modelData);
16137
+ await this.models[modelName].import(record.modelData);
16034
16138
  if (this.config.verbose) {
16035
- console.log(`[MLPlugin] Loaded model "${modelName}" from plugin storage (S3)`);
16139
+ console.log(`[MLPlugin] Loaded model "${modelName}" from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/latest)`);
16036
16140
  }
16037
16141
  }
16038
16142
  } catch (error) {
@@ -16040,27 +16144,68 @@ class MLPlugin extends Plugin {
16040
16144
  }
16041
16145
  }
16042
16146
  /**
16043
- * Load training data from plugin storage
16147
+ * Load training data from plugin storage (reconstructs specific version from incremental data)
16044
16148
  * @param {string} modelName - Model name
16149
+ * @param {number} version - Version number (optional, defaults to latest)
16045
16150
  * @returns {Object|null} Training data or null if not found
16046
16151
  */
16047
- async getTrainingData(modelName) {
16152
+ async getTrainingData(modelName, version = null) {
16048
16153
  try {
16049
16154
  const storage = this.getStorage();
16050
- const [ok, err, record] = await tryFn(() => storage.get(`training_data_${modelName}`));
16051
- if (!ok || !record) {
16155
+ const modelConfig = this.config.models[modelName];
16156
+ const resourceName = modelConfig.resource;
16157
+ const enableVersioning = this.config.enableVersioning;
16158
+ if (!enableVersioning) {
16159
+ const [ok, err, record] = await tryFn(
16160
+ () => storage.get(storage.getPluginKey(resourceName, "training", "data", modelName, "latest"))
16161
+ );
16162
+ if (!ok || !record) {
16163
+ if (this.config.verbose) {
16164
+ console.log(`[MLPlugin] No saved training data found for "${modelName}"`);
16165
+ }
16166
+ return null;
16167
+ }
16168
+ return {
16169
+ modelName: record.modelName,
16170
+ samples: record.samples,
16171
+ features: record.features,
16172
+ target: record.target,
16173
+ data: record.samples,
16174
+ savedAt: record.savedAt
16175
+ };
16176
+ }
16177
+ const [okHistory, errHistory, historyData] = await tryFn(
16178
+ () => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
16179
+ );
16180
+ if (!okHistory || !historyData || !historyData.history) {
16052
16181
  if (this.config.verbose) {
16053
- console.log(`[MLPlugin] No saved training data found for "${modelName}"`);
16182
+ console.log(`[MLPlugin] No training history found for "${modelName}"`);
16054
16183
  }
16055
16184
  return null;
16056
16185
  }
16186
+ const targetVersion = version || historyData.latestVersion;
16187
+ const reconstructedSamples = [];
16188
+ for (const entry of historyData.history) {
16189
+ if (entry.version > targetVersion) break;
16190
+ if (entry.storageKey && entry.newSamples > 0) {
16191
+ const [ok, err, versionData] = await tryFn(
16192
+ () => storage.get(storage.getPluginKey(resourceName, "training", "data", modelName, `v${entry.version}`))
16193
+ );
16194
+ if (ok && versionData && versionData.samples) {
16195
+ reconstructedSamples.push(...versionData.samples);
16196
+ }
16197
+ }
16198
+ }
16199
+ const targetEntry = historyData.history.find((e) => e.version === targetVersion);
16057
16200
  return {
16058
- modelName: record.modelName,
16059
- samples: record.samples,
16060
- features: JSON.parse(record.features),
16061
- target: record.target,
16062
- data: JSON.parse(record.data),
16063
- savedAt: record.savedAt
16201
+ modelName,
16202
+ version: targetVersion,
16203
+ samples: reconstructedSamples,
16204
+ totalSamples: reconstructedSamples.length,
16205
+ features: modelConfig.features,
16206
+ target: modelConfig.target,
16207
+ metrics: targetEntry?.metrics,
16208
+ savedAt: targetEntry?.trainedAt
16064
16209
  };
16065
16210
  } catch (error) {
16066
16211
  console.error(`[MLPlugin] Failed to load training data for "${modelName}":`, error.message);
@@ -16068,15 +16213,29 @@ class MLPlugin extends Plugin {
16068
16213
  }
16069
16214
  }
16070
16215
  /**
16071
- * Delete model from plugin storage
16216
+ * Delete model from plugin storage (all versions)
16072
16217
  * @private
16073
16218
  */
16074
16219
  async _deleteModel(modelName) {
16075
16220
  try {
16076
16221
  const storage = this.getStorage();
16077
- await storage.delete(`model_${modelName}`);
16222
+ const modelConfig = this.config.models[modelName];
16223
+ const resourceName = modelConfig.resource;
16224
+ const enableVersioning = this.config.enableVersioning;
16225
+ if (enableVersioning) {
16226
+ const versionInfo = this.modelVersions.get(modelName);
16227
+ if (versionInfo && versionInfo.latestVersion > 0) {
16228
+ for (let v = 1; v <= versionInfo.latestVersion; v++) {
16229
+ await storage.delete(storage.getPluginKey(resourceName, "models", modelName, `v${v}`));
16230
+ }
16231
+ }
16232
+ await storage.delete(storage.getPluginKey(resourceName, "metadata", modelName, "active"));
16233
+ await storage.delete(storage.getPluginKey(resourceName, "metadata", modelName, "versions"));
16234
+ } else {
16235
+ await storage.delete(storage.getPluginKey(resourceName, "models", modelName, "latest"));
16236
+ }
16078
16237
  if (this.config.verbose) {
16079
- console.log(`[MLPlugin] Deleted model "${modelName}" from plugin storage`);
16238
+ console.log(`[MLPlugin] Deleted model "${modelName}" from S3 (resource=${resourceName}/plugin=ml/models/${modelName}/)`);
16080
16239
  }
16081
16240
  } catch (error) {
16082
16241
  if (this.config.verbose) {
@@ -16085,15 +16244,32 @@ class MLPlugin extends Plugin {
16085
16244
  }
16086
16245
  }
16087
16246
  /**
16088
- * Delete training data from plugin storage
16247
+ * Delete training data from plugin storage (all versions)
16089
16248
  * @private
16090
16249
  */
16091
16250
  async _deleteTrainingData(modelName) {
16092
16251
  try {
16093
16252
  const storage = this.getStorage();
16094
- await storage.delete(`training_data_${modelName}`);
16253
+ const modelConfig = this.config.models[modelName];
16254
+ const resourceName = modelConfig.resource;
16255
+ const enableVersioning = this.config.enableVersioning;
16256
+ if (enableVersioning) {
16257
+ const [ok, err, historyData] = await tryFn(
16258
+ () => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName))
16259
+ );
16260
+ if (ok && historyData && historyData.history) {
16261
+ for (const entry of historyData.history) {
16262
+ if (entry.storageKey) {
16263
+ await storage.delete(storage.getPluginKey(resourceName, "training", "data", modelName, `v${entry.version}`));
16264
+ }
16265
+ }
16266
+ }
16267
+ await storage.delete(storage.getPluginKey(resourceName, "training", "history", modelName));
16268
+ } else {
16269
+ await storage.delete(storage.getPluginKey(resourceName, "training", "data", modelName, "latest"));
16270
+ }
16095
16271
  if (this.config.verbose) {
16096
- console.log(`[MLPlugin] Deleted training data for "${modelName}" from plugin storage`);
16272
+ console.log(`[MLPlugin] Deleted training data for "${modelName}" from S3 (resource=${resourceName}/plugin=ml/training/)`);
16097
16273
  }
16098
16274
  } catch (error) {
16099
16275
  if (this.config.verbose) {
@@ -16112,17 +16288,18 @@ class MLPlugin extends Plugin {
16112
16288
  }
16113
16289
  try {
16114
16290
  const storage = this.getStorage();
16291
+ const modelConfig = this.config.models[modelName];
16292
+ const resourceName = modelConfig.resource;
16115
16293
  const versionInfo = this.modelVersions.get(modelName) || { latestVersion: 0 };
16116
16294
  const versions = [];
16117
16295
  for (let v = 1; v <= versionInfo.latestVersion; v++) {
16118
- const [ok, err, versionData] = await tryFn(() => storage.get(`model_${modelName}_v${v}`));
16296
+ const [ok, err, versionData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${v}`)));
16119
16297
  if (ok && versionData) {
16120
- const metrics = versionData.metrics ? JSON.parse(versionData.metrics) : {};
16121
16298
  versions.push({
16122
16299
  version: v,
16123
16300
  savedAt: versionData.savedAt,
16124
16301
  isCurrent: v === versionInfo.currentVersion,
16125
- metrics
16302
+ metrics: versionData.metrics
16126
16303
  });
16127
16304
  }
16128
16305
  }
@@ -16146,12 +16323,16 @@ class MLPlugin extends Plugin {
16146
16323
  }
16147
16324
  try {
16148
16325
  const storage = this.getStorage();
16149
- const [ok, err, versionData] = await tryFn(() => storage.get(`model_${modelName}_v${version}`));
16326
+ const modelConfig = this.config.models[modelName];
16327
+ const resourceName = modelConfig.resource;
16328
+ const [ok, err, versionData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version}`)));
16150
16329
  if (!ok || !versionData) {
16151
16330
  throw new MLError(`Version ${version} not found for model "${modelName}"`, { modelName, version });
16152
16331
  }
16153
- const modelData = JSON.parse(versionData.data);
16154
- await this.models[modelName].import(modelData);
16332
+ if (!versionData.modelData) {
16333
+ throw new MLError(`Model data not found in version ${version}`, { modelName, version });
16334
+ }
16335
+ await this.models[modelName].import(versionData.modelData);
16155
16336
  const versionInfo = this.modelVersions.get(modelName);
16156
16337
  if (versionInfo) {
16157
16338
  versionInfo.currentVersion = version;
@@ -16179,10 +16360,12 @@ class MLPlugin extends Plugin {
16179
16360
  if (!this.config.enableVersioning) {
16180
16361
  throw new MLError("Versioning is not enabled", { modelName });
16181
16362
  }
16363
+ const modelConfig = this.config.models[modelName];
16364
+ const resourceName = modelConfig.resource;
16182
16365
  await this.loadModelVersion(modelName, version);
16183
16366
  await this._updateVersionInfo(modelName, version);
16184
16367
  const storage = this.getStorage();
16185
- await storage.patch(`model_${modelName}_active`, {
16368
+ await storage.set(storage.getPluginKey(resourceName, "metadata", modelName, "active"), {
16186
16369
  modelName,
16187
16370
  version,
16188
16371
  type: "reference",
@@ -16204,7 +16387,9 @@ class MLPlugin extends Plugin {
16204
16387
  }
16205
16388
  try {
16206
16389
  const storage = this.getStorage();
16207
- const [ok, err, historyData] = await tryFn(() => storage.get(`training_history_${modelName}`));
16390
+ const modelConfig = this.config.models[modelName];
16391
+ const resourceName = modelConfig.resource;
16392
+ const [ok, err, historyData] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "training", "history", modelName)));
16208
16393
  if (!ok || !historyData) {
16209
16394
  return null;
16210
16395
  }
@@ -16233,8 +16418,10 @@ class MLPlugin extends Plugin {
16233
16418
  }
16234
16419
  try {
16235
16420
  const storage = this.getStorage();
16236
- const [ok1, err1, v1Data] = await tryFn(() => storage.get(`model_${modelName}_v${version1}`));
16237
- const [ok2, err2, v2Data] = await tryFn(() => storage.get(`model_${modelName}_v${version2}`));
16421
+ const modelConfig = this.config.models[modelName];
16422
+ const resourceName = modelConfig.resource;
16423
+ const [ok1, err1, v1Data] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version1}`)));
16424
+ const [ok2, err2, v2Data] = await tryFn(() => storage.get(storage.getPluginKey(resourceName, "models", modelName, `v${version2}`)));
16238
16425
  if (!ok1 || !v1Data) {
16239
16426
  throw new MLError(`Version ${version1} not found`, { modelName, version: version1 });
16240
16427
  }
@@ -16690,7 +16877,7 @@ class RelationPlugin extends Plugin {
16690
16877
  if (this.verbose) {
16691
16878
  console.log(`[RelationPlugin] Installed with ${Object.keys(this.relations).length} resources`);
16692
16879
  }
16693
- this.emit("installed", {
16880
+ this.emit("db:plugin:installed", {
16694
16881
  plugin: "RelationPlugin",
16695
16882
  resources: Object.keys(this.relations)
16696
16883
  });
@@ -20265,7 +20452,7 @@ class S3Client extends EventEmitter {
20265
20452
  return client;
20266
20453
  }
20267
20454
  async sendCommand(command) {
20268
- this.emit("command.request", command.constructor.name, command.input);
20455
+ this.emit("cl:request", command.constructor.name, command.input);
20269
20456
  const [ok, err, response] = await tryFn(() => this.client.send(command));
20270
20457
  if (!ok) {
20271
20458
  const bucket = this.config.bucket;
@@ -20277,7 +20464,7 @@ class S3Client extends EventEmitter {
20277
20464
  commandInput: command.input
20278
20465
  });
20279
20466
  }
20280
- this.emit("command.response", command.constructor.name, response, command.input);
20467
+ this.emit("cl:response", command.constructor.name, response, command.input);
20281
20468
  return response;
20282
20469
  }
20283
20470
  async putObject({ key, metadata, contentType, body, contentEncoding, contentLength, ifMatch }) {
@@ -20302,7 +20489,7 @@ class S3Client extends EventEmitter {
20302
20489
  if (contentLength !== void 0) options.ContentLength = contentLength;
20303
20490
  if (ifMatch !== void 0) options.IfMatch = ifMatch;
20304
20491
  const [ok, err, response] = await tryFn(() => this.sendCommand(new PutObjectCommand(options)));
20305
- this.emit("putObject", err || response, { key, metadata, contentType, body, contentEncoding, contentLength });
20492
+ this.emit("cl:PutObject", err || response, { key, metadata, contentType, body, contentEncoding, contentLength });
20306
20493
  if (!ok) {
20307
20494
  throw mapAwsError(err, {
20308
20495
  bucket: this.config.bucket,
@@ -20330,7 +20517,7 @@ class S3Client extends EventEmitter {
20330
20517
  }
20331
20518
  return res;
20332
20519
  });
20333
- this.emit("getObject", err || response, { key });
20520
+ this.emit("cl:GetObject", err || response, { key });
20334
20521
  if (!ok) {
20335
20522
  throw mapAwsError(err, {
20336
20523
  bucket: this.config.bucket,
@@ -20358,7 +20545,7 @@ class S3Client extends EventEmitter {
20358
20545
  }
20359
20546
  return res;
20360
20547
  });
20361
- this.emit("headObject", err || response, { key });
20548
+ this.emit("cl:HeadObject", err || response, { key });
20362
20549
  if (!ok) {
20363
20550
  throw mapAwsError(err, {
20364
20551
  bucket: this.config.bucket,
@@ -20391,7 +20578,7 @@ class S3Client extends EventEmitter {
20391
20578
  options.ContentType = contentType;
20392
20579
  }
20393
20580
  const [ok, err, response] = await tryFn(() => this.sendCommand(new CopyObjectCommand(options)));
20394
- this.emit("copyObject", err || response, { from, to, metadataDirective });
20581
+ this.emit("cl:CopyObject", err || response, { from, to, metadataDirective });
20395
20582
  if (!ok) {
20396
20583
  throw mapAwsError(err, {
20397
20584
  bucket: this.config.bucket,
@@ -20416,7 +20603,7 @@ class S3Client extends EventEmitter {
20416
20603
  Key: keyPrefix ? path$1.join(keyPrefix, key) : key
20417
20604
  };
20418
20605
  const [ok, err, response] = await tryFn(() => this.sendCommand(new DeleteObjectCommand(options)));
20419
- this.emit("deleteObject", err || response, { key });
20606
+ this.emit("cl:DeleteObject", err || response, { key });
20420
20607
  if (!ok) {
20421
20608
  throw mapAwsError(err, {
20422
20609
  bucket: this.config.bucket,
@@ -20456,7 +20643,7 @@ class S3Client extends EventEmitter {
20456
20643
  deleted: results,
20457
20644
  notFound: errors
20458
20645
  };
20459
- this.emit("deleteObjects", report, keys);
20646
+ this.emit("cl:DeleteObjects", report, keys);
20460
20647
  return report;
20461
20648
  }
20462
20649
  /**
@@ -20486,7 +20673,7 @@ class S3Client extends EventEmitter {
20486
20673
  const deleteResponse = await this.client.send(deleteCommand);
20487
20674
  const deletedCount = deleteResponse.Deleted ? deleteResponse.Deleted.length : 0;
20488
20675
  totalDeleted += deletedCount;
20489
- this.emit("deleteAll", {
20676
+ this.emit("cl:DeleteAll", {
20490
20677
  prefix,
20491
20678
  batch: deletedCount,
20492
20679
  total: totalDeleted
@@ -20494,7 +20681,7 @@ class S3Client extends EventEmitter {
20494
20681
  }
20495
20682
  continuationToken = listResponse.IsTruncated ? listResponse.NextContinuationToken : void 0;
20496
20683
  } while (continuationToken);
20497
- this.emit("deleteAllComplete", {
20684
+ this.emit("cl:DeleteAllComplete", {
20498
20685
  prefix,
20499
20686
  totalDeleted
20500
20687
  });
@@ -20525,7 +20712,7 @@ class S3Client extends EventEmitter {
20525
20712
  if (!ok) {
20526
20713
  throw new UnknownError("Unknown error in listObjects", { prefix, bucket: this.config.bucket, original: err });
20527
20714
  }
20528
- this.emit("listObjects", response, options);
20715
+ this.emit("cl:ListObjects", response, options);
20529
20716
  return response;
20530
20717
  }
20531
20718
  async count({ prefix } = {}) {
@@ -20542,7 +20729,7 @@ class S3Client extends EventEmitter {
20542
20729
  truncated = response.IsTruncated || false;
20543
20730
  continuationToken = response.NextContinuationToken;
20544
20731
  }
20545
- this.emit("count", count, { prefix });
20732
+ this.emit("cl:Count", count, { prefix });
20546
20733
  return count;
20547
20734
  }
20548
20735
  async getAllKeys({ prefix } = {}) {
@@ -20564,7 +20751,7 @@ class S3Client extends EventEmitter {
20564
20751
  if (this.config.keyPrefix) {
20565
20752
  keys = keys.map((x) => x.replace(this.config.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace(`/`, "") : x);
20566
20753
  }
20567
- this.emit("getAllKeys", keys, { prefix });
20754
+ this.emit("cl:GetAllKeys", keys, { prefix });
20568
20755
  return keys;
20569
20756
  }
20570
20757
  async getContinuationTokenAfterOffset(params = {}) {
@@ -20593,7 +20780,7 @@ class S3Client extends EventEmitter {
20593
20780
  break;
20594
20781
  }
20595
20782
  }
20596
- this.emit("getContinuationTokenAfterOffset", continuationToken || null, params);
20783
+ this.emit("cl:GetContinuationTokenAfterOffset", continuationToken || null, params);
20597
20784
  return continuationToken || null;
20598
20785
  }
20599
20786
  async getKeysPage(params = {}) {
@@ -20611,7 +20798,7 @@ class S3Client extends EventEmitter {
20611
20798
  offset
20612
20799
  });
20613
20800
  if (!continuationToken) {
20614
- this.emit("getKeysPage", [], params);
20801
+ this.emit("cl:GetKeysPage", [], params);
20615
20802
  return [];
20616
20803
  }
20617
20804
  }
@@ -20634,7 +20821,7 @@ class S3Client extends EventEmitter {
20634
20821
  if (this.config.keyPrefix) {
20635
20822
  keys = keys.map((x) => x.replace(this.config.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace(`/`, "") : x);
20636
20823
  }
20637
- this.emit("getKeysPage", keys, params);
20824
+ this.emit("cl:GetKeysPage", keys, params);
20638
20825
  return keys;
20639
20826
  }
20640
20827
  async moveAllObjects({ prefixFrom, prefixTo }) {
@@ -20652,7 +20839,7 @@ class S3Client extends EventEmitter {
20652
20839
  }
20653
20840
  return to;
20654
20841
  });
20655
- this.emit("moveAllObjects", { results, errors }, { prefixFrom, prefixTo });
20842
+ this.emit("cl:MoveAllObjects", { results, errors }, { prefixFrom, prefixTo });
20656
20843
  if (errors.length > 0) {
20657
20844
  throw new UnknownError("Some objects could not be moved", {
20658
20845
  bucket: this.config.bucket,
@@ -23507,6 +23694,20 @@ ${errorDetails}`,
23507
23694
  if (typeof body === "object") return Buffer.byteLength(JSON.stringify(body), "utf8");
23508
23695
  return Buffer.byteLength(String(body), "utf8");
23509
23696
  }
23697
+ /**
23698
+ * Emit standardized events with optional ID-specific variant
23699
+ *
23700
+ * @private
23701
+ * @param {string} event - Event name
23702
+ * @param {Object} payload - Event payload
23703
+ * @param {string} [id] - Optional ID for ID-specific events
23704
+ */
23705
+ _emitStandardized(event, payload, id = null) {
23706
+ this.emit(event, payload);
23707
+ if (id) {
23708
+ this.emit(`${event}:${id}`, payload);
23709
+ }
23710
+ }
23510
23711
  /**
23511
23712
  * Insert a new resource object
23512
23713
  * @param {Object} attributes - Resource attributes
@@ -23647,11 +23848,11 @@ ${errorDetails}`,
23647
23848
  for (const hook of nonPartitionHooks) {
23648
23849
  finalResult = await hook(finalResult);
23649
23850
  }
23650
- this.emit("insert", finalResult);
23851
+ this._emitStandardized("inserted", finalResult, finalResult?.id || insertedObject?.id);
23651
23852
  return finalResult;
23652
23853
  } else {
23653
23854
  const finalResult = await this.executeHooks("afterInsert", insertedObject);
23654
- this.emit("insert", finalResult);
23855
+ this._emitStandardized("inserted", finalResult, finalResult?.id || insertedObject?.id);
23655
23856
  return finalResult;
23656
23857
  }
23657
23858
  }
@@ -23715,7 +23916,7 @@ ${errorDetails}`,
23715
23916
  data = await this.applyVersionMapping(data, objectVersion, this.version);
23716
23917
  }
23717
23918
  data = await this.executeHooks("afterGet", data);
23718
- this.emit("get", data);
23919
+ this._emitWithDeprecation("get", "fetched", data, data.id);
23719
23920
  const value = data;
23720
23921
  return value;
23721
23922
  }
@@ -23966,19 +24167,19 @@ ${errorDetails}`,
23966
24167
  for (const hook of nonPartitionHooks) {
23967
24168
  finalResult = await hook(finalResult);
23968
24169
  }
23969
- this.emit("update", {
24170
+ this._emitStandardized("updated", {
23970
24171
  ...updatedData,
23971
24172
  $before: { ...originalData },
23972
24173
  $after: { ...finalResult }
23973
- });
24174
+ }, updatedData.id);
23974
24175
  return finalResult;
23975
24176
  } else {
23976
24177
  const finalResult = await this.executeHooks("afterUpdate", updatedData);
23977
- this.emit("update", {
24178
+ this._emitStandardized("updated", {
23978
24179
  ...updatedData,
23979
24180
  $before: { ...originalData },
23980
24181
  $after: { ...finalResult }
23981
- });
24182
+ }, updatedData.id);
23982
24183
  return finalResult;
23983
24184
  }
23984
24185
  }
@@ -24377,11 +24578,11 @@ ${errorDetails}`,
24377
24578
  for (const hook of nonPartitionHooks) {
24378
24579
  finalResult = await hook(finalResult);
24379
24580
  }
24380
- this.emit("update", {
24581
+ this._emitStandardized("updated", {
24381
24582
  ...updatedData,
24382
24583
  $before: { ...originalData },
24383
24584
  $after: { ...finalResult }
24384
- });
24585
+ }, updatedData.id);
24385
24586
  return {
24386
24587
  success: true,
24387
24588
  data: finalResult,
@@ -24390,11 +24591,11 @@ ${errorDetails}`,
24390
24591
  } else {
24391
24592
  await this.handlePartitionReferenceUpdates(oldData, newData);
24392
24593
  const finalResult = await this.executeHooks("afterUpdate", updatedData);
24393
- this.emit("update", {
24594
+ this._emitStandardized("updated", {
24394
24595
  ...updatedData,
24395
24596
  $before: { ...originalData },
24396
24597
  $after: { ...finalResult }
24397
- });
24598
+ }, updatedData.id);
24398
24599
  return {
24399
24600
  success: true,
24400
24601
  data: finalResult,
@@ -24425,11 +24626,11 @@ ${errorDetails}`,
24425
24626
  await this.executeHooks("beforeDelete", objectData);
24426
24627
  const key = this.getResourceKey(id);
24427
24628
  const [ok2, err2, response] = await tryFn(() => this.client.deleteObject(key));
24428
- this.emit("delete", {
24629
+ this._emitWithDeprecation("delete", "deleted", {
24429
24630
  ...objectData,
24430
24631
  $before: { ...objectData },
24431
24632
  $after: null
24432
- });
24633
+ }, id);
24433
24634
  if (deleteError) {
24434
24635
  throw mapAwsError(deleteError, {
24435
24636
  bucket: this.client.config.bucket,
@@ -24553,7 +24754,7 @@ ${errorDetails}`,
24553
24754
  }
24554
24755
  const count = await this.client.count({ prefix });
24555
24756
  await this.executeHooks("afterCount", { count, partition, partitionValues });
24556
- this.emit("count", count);
24757
+ this._emitWithDeprecation("count", "counted", count);
24557
24758
  return count;
24558
24759
  }
24559
24760
  /**
@@ -24576,7 +24777,7 @@ ${errorDetails}`,
24576
24777
  const result = await this.insert(attributes);
24577
24778
  return result;
24578
24779
  });
24579
- this.emit("insertMany", objects.length);
24780
+ this._emitWithDeprecation("insertMany", "inserted-many", objects.length);
24580
24781
  return results;
24581
24782
  }
24582
24783
  /**
@@ -24611,7 +24812,7 @@ ${errorDetails}`,
24611
24812
  return response;
24612
24813
  });
24613
24814
  await this.executeHooks("afterDeleteMany", { ids, results });
24614
- this.emit("deleteMany", ids.length);
24815
+ this._emitWithDeprecation("deleteMany", "deleted-many", ids.length);
24615
24816
  return results;
24616
24817
  }
24617
24818
  async deleteAll() {
@@ -24620,7 +24821,7 @@ ${errorDetails}`,
24620
24821
  }
24621
24822
  const prefix = `resource=${this.name}/data`;
24622
24823
  const deletedCount = await this.client.deleteAll({ prefix });
24623
- this.emit("deleteAll", {
24824
+ this._emitWithDeprecation("deleteAll", "deleted-all", {
24624
24825
  version: this.version,
24625
24826
  prefix,
24626
24827
  deletedCount
@@ -24637,7 +24838,7 @@ ${errorDetails}`,
24637
24838
  }
24638
24839
  const prefix = `resource=${this.name}`;
24639
24840
  const deletedCount = await this.client.deleteAll({ prefix });
24640
- this.emit("deleteAllData", {
24841
+ this._emitWithDeprecation("deleteAllData", "deleted-all-data", {
24641
24842
  resource: this.name,
24642
24843
  prefix,
24643
24844
  deletedCount
@@ -24707,7 +24908,7 @@ ${errorDetails}`,
24707
24908
  const idPart = parts.find((part) => part.startsWith("id="));
24708
24909
  return idPart ? idPart.replace("id=", "") : null;
24709
24910
  }).filter(Boolean);
24710
- this.emit("listIds", ids.length);
24911
+ this._emitWithDeprecation("listIds", "listed-ids", ids.length);
24711
24912
  return ids;
24712
24913
  }
24713
24914
  /**
@@ -24749,12 +24950,12 @@ ${errorDetails}`,
24749
24950
  const [ok, err, ids] = await tryFn(() => this.listIds({ limit, offset }));
24750
24951
  if (!ok) throw err;
24751
24952
  const results = await this.processListResults(ids, "main");
24752
- this.emit("list", { count: results.length, errors: 0 });
24953
+ this._emitWithDeprecation("list", "listed", { count: results.length, errors: 0 });
24753
24954
  return results;
24754
24955
  }
24755
24956
  async listPartition({ partition, partitionValues, limit, offset = 0 }) {
24756
24957
  if (!this.config.partitions?.[partition]) {
24757
- this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
24958
+ this._emitWithDeprecation("list", "listed", { partition, partitionValues, count: 0, errors: 0 });
24758
24959
  return [];
24759
24960
  }
24760
24961
  const partitionDef = this.config.partitions[partition];
@@ -24764,7 +24965,7 @@ ${errorDetails}`,
24764
24965
  const ids = this.extractIdsFromKeys(keys).slice(offset);
24765
24966
  const filteredIds = limit ? ids.slice(0, limit) : ids;
24766
24967
  const results = await this.processPartitionResults(filteredIds, partition, partitionDef, keys);
24767
- this.emit("list", { partition, partitionValues, count: results.length, errors: 0 });
24968
+ this._emitWithDeprecation("list", "listed", { partition, partitionValues, count: results.length, errors: 0 });
24768
24969
  return results;
24769
24970
  }
24770
24971
  /**
@@ -24809,7 +25010,7 @@ ${errorDetails}`,
24809
25010
  }
24810
25011
  return this.handleResourceError(err, id, context);
24811
25012
  });
24812
- this.emit("list", { count: results.length, errors: 0 });
25013
+ this._emitWithDeprecation("list", "listed", { count: results.length, errors: 0 });
24813
25014
  return results;
24814
25015
  }
24815
25016
  /**
@@ -24872,10 +25073,10 @@ ${errorDetails}`,
24872
25073
  */
24873
25074
  handleListError(error, { partition, partitionValues }) {
24874
25075
  if (error.message.includes("Partition '") && error.message.includes("' not found")) {
24875
- this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
25076
+ this._emitWithDeprecation("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
24876
25077
  return [];
24877
25078
  }
24878
- this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
25079
+ this._emitWithDeprecation("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
24879
25080
  return [];
24880
25081
  }
24881
25082
  /**
@@ -24908,7 +25109,7 @@ ${errorDetails}`,
24908
25109
  throw err;
24909
25110
  });
24910
25111
  const finalResults = await this.executeHooks("afterGetMany", results);
24911
- this.emit("getMany", ids.length);
25112
+ this._emitWithDeprecation("getMany", "fetched-many", ids.length);
24912
25113
  return finalResults;
24913
25114
  }
24914
25115
  /**
@@ -24994,7 +25195,7 @@ ${errorDetails}`,
24994
25195
  hasTotalItems: totalItems !== null
24995
25196
  }
24996
25197
  };
24997
- this.emit("page", result2);
25198
+ this._emitWithDeprecation("page", "paginated", result2);
24998
25199
  return result2;
24999
25200
  });
25000
25201
  if (ok) return result;
@@ -25064,7 +25265,7 @@ ${errorDetails}`,
25064
25265
  contentType
25065
25266
  }));
25066
25267
  if (!ok2) throw err2;
25067
- this.emit("setContent", { id, contentType, contentLength: buffer.length });
25268
+ this._emitWithDeprecation("setContent", "content-set", { id, contentType, contentLength: buffer.length }, id);
25068
25269
  return updatedData;
25069
25270
  }
25070
25271
  /**
@@ -25093,7 +25294,7 @@ ${errorDetails}`,
25093
25294
  }
25094
25295
  const buffer = Buffer.from(await response.Body.transformToByteArray());
25095
25296
  const contentType = response.ContentType || null;
25096
- this.emit("content", id, buffer.length, contentType);
25297
+ this._emitWithDeprecation("content", "content-fetched", { id, contentLength: buffer.length, contentType }, id);
25097
25298
  return {
25098
25299
  buffer,
25099
25300
  contentType
@@ -25125,7 +25326,7 @@ ${errorDetails}`,
25125
25326
  metadata: existingMetadata
25126
25327
  }));
25127
25328
  if (!ok2) throw err2;
25128
- this.emit("deleteContent", id);
25329
+ this._emitWithDeprecation("deleteContent", "content-deleted", id, id);
25129
25330
  return response;
25130
25331
  }
25131
25332
  /**
@@ -25437,7 +25638,7 @@ ${errorDetails}`,
25437
25638
  const data = await this.get(id);
25438
25639
  data._partition = partitionName;
25439
25640
  data._partitionValues = partitionValues;
25440
- this.emit("getFromPartition", data);
25641
+ this._emitWithDeprecation("getFromPartition", "partition-fetched", data, data.id);
25441
25642
  return data;
25442
25643
  }
25443
25644
  /**
@@ -25846,7 +26047,7 @@ class Database extends EventEmitter {
25846
26047
  })();
25847
26048
  this.version = "1";
25848
26049
  this.s3dbVersion = (() => {
25849
- const [ok, err, version] = tryFn(() => true ? "13.1.0" : "latest");
26050
+ const [ok, err, version] = tryFn(() => true ? "13.2.1" : "latest");
25850
26051
  return ok ? version : "latest";
25851
26052
  })();
25852
26053
  this._resourcesMap = {};
@@ -26016,12 +26217,12 @@ class Database extends EventEmitter {
26016
26217
  }
26017
26218
  }
26018
26219
  if (definitionChanges.length > 0) {
26019
- this.emit("resourceDefinitionsChanged", {
26220
+ this.emit("db:resource-definitions-changed", {
26020
26221
  changes: definitionChanges,
26021
26222
  metadata: this.savedMetadata
26022
26223
  });
26023
26224
  }
26024
- this.emit("connected", /* @__PURE__ */ new Date());
26225
+ this.emit("db:connected", /* @__PURE__ */ new Date());
26025
26226
  }
26026
26227
  /**
26027
26228
  * Detect changes in resource definitions compared to saved metadata
@@ -26235,7 +26436,7 @@ class Database extends EventEmitter {
26235
26436
  if (index > -1) {
26236
26437
  this.pluginList.splice(index, 1);
26237
26438
  }
26238
- this.emit("plugin.uninstalled", { name: pluginName, plugin });
26439
+ this.emit("db:plugin:uninstalled", { name: pluginName, plugin });
26239
26440
  }
26240
26441
  async uploadMetadataFile() {
26241
26442
  const metadata = {
@@ -26294,7 +26495,7 @@ class Database extends EventEmitter {
26294
26495
  contentType: "application/json"
26295
26496
  });
26296
26497
  this.savedMetadata = metadata;
26297
- this.emit("metadataUploaded", metadata);
26498
+ this.emit("db:metadata-uploaded", metadata);
26298
26499
  }
26299
26500
  blankMetadataStructure() {
26300
26501
  return {
@@ -26551,7 +26752,7 @@ class Database extends EventEmitter {
26551
26752
  body: JSON.stringify(metadata, null, 2),
26552
26753
  contentType: "application/json"
26553
26754
  });
26554
- this.emit("metadataHealed", { healingLog, metadata });
26755
+ this.emit("db:metadata-healed", { healingLog, metadata });
26555
26756
  if (this.verbose) {
26556
26757
  console.warn("S3DB: Successfully uploaded healed metadata");
26557
26758
  }
@@ -26691,7 +26892,7 @@ class Database extends EventEmitter {
26691
26892
  if (!existingVersionData || existingVersionData.hash !== newHash) {
26692
26893
  await this.uploadMetadataFile();
26693
26894
  }
26694
- this.emit("s3db.resourceUpdated", name);
26895
+ this.emit("db:resource:updated", name);
26695
26896
  return existingResource;
26696
26897
  }
26697
26898
  const existingMetadata = this.savedMetadata?.resources?.[name];
@@ -26728,7 +26929,7 @@ class Database extends EventEmitter {
26728
26929
  this._applyMiddlewares(resource, middlewares);
26729
26930
  }
26730
26931
  await this.uploadMetadataFile();
26731
- this.emit("s3db.resourceCreated", name);
26932
+ this.emit("db:resource:created", name);
26732
26933
  return resource;
26733
26934
  }
26734
26935
  /**
@@ -26892,7 +27093,7 @@ class Database extends EventEmitter {
26892
27093
  if (this.client && typeof this.client.removeAllListeners === "function") {
26893
27094
  this.client.removeAllListeners();
26894
27095
  }
26895
- await this.emit("disconnected", /* @__PURE__ */ new Date());
27096
+ await this.emit("db:disconnected", /* @__PURE__ */ new Date());
26896
27097
  this.removeAllListeners();
26897
27098
  if (this._exitListener && typeof process !== "undefined") {
26898
27099
  process.off("exit", this._exitListener);
@@ -27004,7 +27205,7 @@ class Database extends EventEmitter {
27004
27205
  for (const hook of hooks) {
27005
27206
  const [ok, error] = await tryFn(() => hook({ database: this, ...context }));
27006
27207
  if (!ok) {
27007
- this.emit("hookError", { event, error, context });
27208
+ this.emit("db:hook-error", { event, error, context });
27008
27209
  if (this.strictHooks) {
27009
27210
  throw new DatabaseError(`Hook execution failed for event '${event}': ${error.message}`, {
27010
27211
  event,
@@ -28580,7 +28781,7 @@ class ReplicatorPlugin extends Plugin {
28580
28781
  if (this.config.verbose) {
28581
28782
  console.warn(`[ReplicatorPlugin] Insert event failed for resource ${resource.name}: ${error.message}`);
28582
28783
  }
28583
- this.emit("error", { operation: "insert", error: error.message, resource: resource.name });
28784
+ this.emit("plg:replicator:error", { operation: "insert", error: error.message, resource: resource.name });
28584
28785
  }
28585
28786
  };
28586
28787
  const updateHandler = async (data, beforeData) => {
@@ -28593,7 +28794,7 @@ class ReplicatorPlugin extends Plugin {
28593
28794
  if (this.config.verbose) {
28594
28795
  console.warn(`[ReplicatorPlugin] Update event failed for resource ${resource.name}: ${error.message}`);
28595
28796
  }
28596
- this.emit("error", { operation: "update", error: error.message, resource: resource.name });
28797
+ this.emit("plg:replicator:error", { operation: "update", error: error.message, resource: resource.name });
28597
28798
  }
28598
28799
  };
28599
28800
  const deleteHandler = async (data) => {
@@ -28604,7 +28805,7 @@ class ReplicatorPlugin extends Plugin {
28604
28805
  if (this.config.verbose) {
28605
28806
  console.warn(`[ReplicatorPlugin] Delete event failed for resource ${resource.name}: ${error.message}`);
28606
28807
  }
28607
- this.emit("error", { operation: "delete", error: error.message, resource: resource.name });
28808
+ this.emit("plg:replicator:error", { operation: "delete", error: error.message, resource: resource.name });
28608
28809
  }
28609
28810
  };
28610
28811
  this.eventHandlers.set(resource.name, {
@@ -28725,7 +28926,7 @@ class ReplicatorPlugin extends Plugin {
28725
28926
  if (this.config.verbose) {
28726
28927
  console.warn(`[ReplicatorPlugin] Failed to log error for ${resourceName}: ${logError.message}`);
28727
28928
  }
28728
- this.emit("replicator_log_error", {
28929
+ this.emit("plg:replicator:log-error", {
28729
28930
  replicator: replicator.name || replicator.id,
28730
28931
  resourceName,
28731
28932
  operation,
@@ -28750,7 +28951,7 @@ class ReplicatorPlugin extends Plugin {
28750
28951
  () => replicator.replicate(resourceName, operation, data, recordId, beforeData),
28751
28952
  this.config.maxRetries
28752
28953
  );
28753
- this.emit("replicated", {
28954
+ this.emit("plg:replicator:replicated", {
28754
28955
  replicator: replicator.name || replicator.id,
28755
28956
  resourceName,
28756
28957
  operation,
@@ -28766,7 +28967,7 @@ class ReplicatorPlugin extends Plugin {
28766
28967
  if (this.config.verbose) {
28767
28968
  console.warn(`[ReplicatorPlugin] Replication failed for ${replicator.name || replicator.id} on ${resourceName}: ${error.message}`);
28768
28969
  }
28769
- this.emit("replicator_error", {
28970
+ this.emit("plg:replicator:error", {
28770
28971
  replicator: replicator.name || replicator.id,
28771
28972
  resourceName,
28772
28973
  operation,
@@ -28798,7 +28999,7 @@ class ReplicatorPlugin extends Plugin {
28798
28999
  if (this.config.verbose) {
28799
29000
  console.warn(`[ReplicatorPlugin] Replicator item processing failed for ${replicator.name || replicator.id} on ${item.resourceName}: ${err.message}`);
28800
29001
  }
28801
- this.emit("replicator_error", {
29002
+ this.emit("plg:replicator:error", {
28802
29003
  replicator: replicator.name || replicator.id,
28803
29004
  resourceName: item.resourceName,
28804
29005
  operation: item.operation,
@@ -28810,7 +29011,7 @@ class ReplicatorPlugin extends Plugin {
28810
29011
  }
28811
29012
  return { success: false, error: err.message };
28812
29013
  }
28813
- this.emit("replicated", {
29014
+ this.emit("plg:replicator:replicated", {
28814
29015
  replicator: replicator.name || replicator.id,
28815
29016
  resourceName: item.resourceName,
28816
29017
  operation: item.operation,
@@ -28826,7 +29027,7 @@ class ReplicatorPlugin extends Plugin {
28826
29027
  if (this.config.verbose) {
28827
29028
  console.warn(`[ReplicatorPlugin] Wrapper processing failed for ${replicator.name || replicator.id} on ${item.resourceName}: ${wrapperError.message}`);
28828
29029
  }
28829
- this.emit("replicator_error", {
29030
+ this.emit("plg:replicator:error", {
28830
29031
  replicator: replicator.name || replicator.id,
28831
29032
  resourceName: item.resourceName,
28832
29033
  operation: item.operation,
@@ -28844,7 +29045,7 @@ class ReplicatorPlugin extends Plugin {
28844
29045
  async logReplicator(item) {
28845
29046
  const logRes = this.replicatorLog || this.database.resources[normalizeResourceName(this.config.replicatorLogResource)];
28846
29047
  if (!logRes) {
28847
- this.emit("replicator.log.failed", { error: "replicator log resource not found", item });
29048
+ this.emit("plg:replicator:log-failed", { error: "replicator log resource not found", item });
28848
29049
  return;
28849
29050
  }
28850
29051
  const logItem = {
@@ -28862,7 +29063,7 @@ class ReplicatorPlugin extends Plugin {
28862
29063
  if (this.config.verbose) {
28863
29064
  console.warn(`[ReplicatorPlugin] Failed to log replicator item: ${err.message}`);
28864
29065
  }
28865
- this.emit("replicator.log.failed", { error: err, item });
29066
+ this.emit("plg:replicator:log-failed", { error: err, item });
28866
29067
  }
28867
29068
  }
28868
29069
  async updateReplicatorLog(logId, updates) {
@@ -28874,7 +29075,7 @@ class ReplicatorPlugin extends Plugin {
28874
29075
  });
28875
29076
  });
28876
29077
  if (!ok) {
28877
- this.emit("replicator.updateLog.failed", { error: err.message, logId, updates });
29078
+ this.emit("plg:replicator:update-log-failed", { error: err.message, logId, updates });
28878
29079
  }
28879
29080
  }
28880
29081
  // Utility methods
@@ -28958,7 +29159,7 @@ class ReplicatorPlugin extends Plugin {
28958
29159
  for (const resourceName in this.database.resources) {
28959
29160
  if (normalizeResourceName(resourceName) === normalizeResourceName("plg_replicator_logs")) continue;
28960
29161
  if (replicator.shouldReplicateResource(resourceName)) {
28961
- this.emit("replicator.sync.resource", { resourceName, replicatorId });
29162
+ this.emit("plg:replicator:sync-resource", { resourceName, replicatorId });
28962
29163
  const resource = this.database.resources[resourceName];
28963
29164
  let offset = 0;
28964
29165
  const pageSize = this.config.batchSize || 100;
@@ -28974,7 +29175,7 @@ class ReplicatorPlugin extends Plugin {
28974
29175
  }
28975
29176
  }
28976
29177
  }
28977
- this.emit("replicator.sync.completed", { replicatorId, stats: this.stats });
29178
+ this.emit("plg:replicator:sync-completed", { replicatorId, stats: this.stats });
28978
29179
  }
28979
29180
  async stop() {
28980
29181
  const [ok, error] = await tryFn(async () => {
@@ -28989,7 +29190,7 @@ class ReplicatorPlugin extends Plugin {
28989
29190
  if (this.config.verbose) {
28990
29191
  console.warn(`[ReplicatorPlugin] Failed to stop replicator ${replicator.name || replicator.id}: ${replicatorError.message}`);
28991
29192
  }
28992
- this.emit("replicator_stop_error", {
29193
+ this.emit("plg:replicator:stop-error", {
28993
29194
  replicator: replicator.name || replicator.id || "unknown",
28994
29195
  driver: replicator.driver || "unknown",
28995
29196
  error: replicatorError.message
@@ -29020,7 +29221,7 @@ class ReplicatorPlugin extends Plugin {
29020
29221
  if (this.config.verbose) {
29021
29222
  console.warn(`[ReplicatorPlugin] Failed to stop plugin: ${error.message}`);
29022
29223
  }
29023
- this.emit("replicator_plugin_stop_error", {
29224
+ this.emit("plg:replicator:plugin-stop-error", {
29024
29225
  error: error.message
29025
29226
  });
29026
29227
  }
@@ -29177,7 +29378,7 @@ class S3QueuePlugin extends Plugin {
29177
29378
  if (this.config.verbose) {
29178
29379
  console.log(`[S3QueuePlugin] Started ${concurrency} workers`);
29179
29380
  }
29180
- this.emit("workers.started", { concurrency, workerId: this.workerId });
29381
+ this.emit("plg:s3-queue:workers-started", { concurrency, workerId: this.workerId });
29181
29382
  }
29182
29383
  async stopProcessing() {
29183
29384
  if (!this.isRunning) return;
@@ -29192,7 +29393,7 @@ class S3QueuePlugin extends Plugin {
29192
29393
  if (this.config.verbose) {
29193
29394
  console.log("[S3QueuePlugin] Stopped all workers");
29194
29395
  }
29195
- this.emit("workers.stopped", { workerId: this.workerId });
29396
+ this.emit("plg:s3-queue:workers-stopped", { workerId: this.workerId });
29196
29397
  }
29197
29398
  createWorker(handler, workerIndex) {
29198
29399
  return (async () => {
@@ -29360,7 +29561,7 @@ class S3QueuePlugin extends Plugin {
29360
29561
  });
29361
29562
  await this.completeMessage(message.queueId, result);
29362
29563
  const duration = Date.now() - startTime;
29363
- this.emit("message.completed", {
29564
+ this.emit("plg:s3-queue:message-completed", {
29364
29565
  queueId: message.queueId,
29365
29566
  originalId: message.record.id,
29366
29567
  duration,
@@ -29373,7 +29574,7 @@ class S3QueuePlugin extends Plugin {
29373
29574
  const shouldRetry = message.attempts < message.maxAttempts;
29374
29575
  if (shouldRetry) {
29375
29576
  await this.retryMessage(message.queueId, message.attempts, error.message);
29376
- this.emit("message.retry", {
29577
+ this.emit("plg:s3-queue:message-retry", {
29377
29578
  queueId: message.queueId,
29378
29579
  originalId: message.record.id,
29379
29580
  attempts: message.attempts,
@@ -29381,7 +29582,7 @@ class S3QueuePlugin extends Plugin {
29381
29582
  });
29382
29583
  } else {
29383
29584
  await this.moveToDeadLetter(message.queueId, message.record, error.message);
29384
- this.emit("message.dead", {
29585
+ this.emit("plg:s3-queue:message-dead", {
29385
29586
  queueId: message.queueId,
29386
29587
  originalId: message.record.id,
29387
29588
  error: error.message
@@ -29613,7 +29814,7 @@ class SchedulerPlugin extends Plugin {
29613
29814
  });
29614
29815
  }
29615
29816
  await this._startScheduling();
29616
- this.emit("initialized", { jobs: this.jobs.size });
29817
+ this.emit("db:plugin:initialized", { jobs: this.jobs.size });
29617
29818
  }
29618
29819
  async _createJobHistoryResource() {
29619
29820
  const [ok] = await tryFn(() => this.database.createResource({
@@ -29751,7 +29952,7 @@ class SchedulerPlugin extends Plugin {
29751
29952
  if (this.config.onJobStart) {
29752
29953
  await this._executeHook(this.config.onJobStart, jobName, context);
29753
29954
  }
29754
- this.emit("job_start", { jobName, executionId, startTime });
29955
+ this.emit("plg:scheduler:job-start", { jobName, executionId, startTime });
29755
29956
  let attempt = 0;
29756
29957
  let lastError = null;
29757
29958
  let result = null;
@@ -29818,7 +30019,7 @@ class SchedulerPlugin extends Plugin {
29818
30019
  } else if (status !== "success" && this.config.onJobError) {
29819
30020
  await this._executeHook(this.config.onJobError, jobName, lastError, attempt);
29820
30021
  }
29821
- this.emit("job_complete", {
30022
+ this.emit("plg:scheduler:job-complete", {
29822
30023
  jobName,
29823
30024
  executionId,
29824
30025
  status,
@@ -29904,7 +30105,7 @@ class SchedulerPlugin extends Plugin {
29904
30105
  }
29905
30106
  job.enabled = true;
29906
30107
  this._scheduleNextExecution(jobName);
29907
- this.emit("job_enabled", { jobName });
30108
+ this.emit("plg:scheduler:job-enabled", { jobName });
29908
30109
  }
29909
30110
  /**
29910
30111
  * Disable a job
@@ -29925,7 +30126,7 @@ class SchedulerPlugin extends Plugin {
29925
30126
  clearTimeout(timer);
29926
30127
  this.timers.delete(jobName);
29927
30128
  }
29928
- this.emit("job_disabled", { jobName });
30129
+ this.emit("plg:scheduler:job-disabled", { jobName });
29929
30130
  }
29930
30131
  /**
29931
30132
  * Get job status and statistics
@@ -30063,7 +30264,7 @@ class SchedulerPlugin extends Plugin {
30063
30264
  if (job.enabled) {
30064
30265
  this._scheduleNextExecution(jobName);
30065
30266
  }
30066
- this.emit("job_added", { jobName });
30267
+ this.emit("plg:scheduler:job-added", { jobName });
30067
30268
  }
30068
30269
  /**
30069
30270
  * Remove a job
@@ -30086,7 +30287,7 @@ class SchedulerPlugin extends Plugin {
30086
30287
  this.jobs.delete(jobName);
30087
30288
  this.statistics.delete(jobName);
30088
30289
  this.activeJobs.delete(jobName);
30089
- this.emit("job_removed", { jobName });
30290
+ this.emit("plg:scheduler:job-removed", { jobName });
30090
30291
  }
30091
30292
  /**
30092
30293
  * Get plugin instance by name (for job actions that need other plugins)
@@ -30127,9 +30328,14 @@ class SchedulerPlugin extends Plugin {
30127
30328
  }
30128
30329
  }
30129
30330
 
30331
+ var scheduler_plugin = /*#__PURE__*/Object.freeze({
30332
+ __proto__: null,
30333
+ SchedulerPlugin: SchedulerPlugin
30334
+ });
30335
+
30130
30336
  class StateMachineError extends S3dbError {
30131
30337
  constructor(message, details = {}) {
30132
- const { currentState, targetState, resourceName, operation = "unknown", ...rest } = details;
30338
+ const { currentState, targetState, resourceName, operation = "unknown", retriable, ...rest } = details;
30133
30339
  let description = details.description;
30134
30340
  if (!description) {
30135
30341
  description = `
@@ -30154,6 +30360,158 @@ Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/state-mach
30154
30360
  `.trim();
30155
30361
  }
30156
30362
  super(message, { ...rest, currentState, targetState, resourceName, operation, description });
30363
+ if (retriable !== void 0) {
30364
+ this.retriable = retriable;
30365
+ }
30366
+ }
30367
+ }
30368
+
30369
+ const RETRIABLE = "RETRIABLE";
30370
+ const NON_RETRIABLE = "NON_RETRIABLE";
30371
+ const RETRIABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
30372
+ "ECONNREFUSED",
30373
+ "ETIMEDOUT",
30374
+ "ECONNRESET",
30375
+ "EPIPE",
30376
+ "ENOTFOUND",
30377
+ "NetworkError",
30378
+ "NETWORK_ERROR",
30379
+ "TimeoutError",
30380
+ "TIMEOUT"
30381
+ ]);
30382
+ const RETRIABLE_AWS_CODES = /* @__PURE__ */ new Set([
30383
+ "ThrottlingException",
30384
+ "TooManyRequestsException",
30385
+ "RequestLimitExceeded",
30386
+ "ProvisionedThroughputExceededException",
30387
+ "RequestThrottledException",
30388
+ "SlowDown",
30389
+ "ServiceUnavailable"
30390
+ ]);
30391
+ const RETRIABLE_AWS_CONFLICTS = /* @__PURE__ */ new Set([
30392
+ "ConditionalCheckFailedException",
30393
+ "TransactionConflictException"
30394
+ ]);
30395
+ const RETRIABLE_STATUS_CODES = /* @__PURE__ */ new Set([
30396
+ 429,
30397
+ // Too Many Requests
30398
+ 500,
30399
+ // Internal Server Error
30400
+ 502,
30401
+ // Bad Gateway
30402
+ 503,
30403
+ // Service Unavailable
30404
+ 504,
30405
+ // Gateway Timeout
30406
+ 507,
30407
+ // Insufficient Storage
30408
+ 509
30409
+ // Bandwidth Limit Exceeded
30410
+ ]);
30411
+ const NON_RETRIABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
30412
+ "ValidationError",
30413
+ "StateMachineError",
30414
+ "SchemaError",
30415
+ "AuthenticationError",
30416
+ "PermissionError",
30417
+ "BusinessLogicError",
30418
+ "InvalidStateTransition"
30419
+ ]);
30420
+ const NON_RETRIABLE_STATUS_CODES = /* @__PURE__ */ new Set([
30421
+ 400,
30422
+ // Bad Request
30423
+ 401,
30424
+ // Unauthorized
30425
+ 403,
30426
+ // Forbidden
30427
+ 404,
30428
+ // Not Found
30429
+ 405,
30430
+ // Method Not Allowed
30431
+ 406,
30432
+ // Not Acceptable
30433
+ 409,
30434
+ // Conflict
30435
+ 410,
30436
+ // Gone
30437
+ 422
30438
+ // Unprocessable Entity
30439
+ ]);
30440
+ class ErrorClassifier {
30441
+ /**
30442
+ * Classify an error as RETRIABLE or NON_RETRIABLE
30443
+ *
30444
+ * @param {Error} error - The error to classify
30445
+ * @param {Object} options - Classification options
30446
+ * @param {Array<string>} options.retryableErrors - Custom retriable error names/codes
30447
+ * @param {Array<string>} options.nonRetriableErrors - Custom non-retriable error names/codes
30448
+ * @returns {string} 'RETRIABLE' or 'NON_RETRIABLE'
30449
+ */
30450
+ static classify(error, options = {}) {
30451
+ if (!error) return NON_RETRIABLE;
30452
+ const {
30453
+ retryableErrors = [],
30454
+ nonRetriableErrors = []
30455
+ } = options;
30456
+ if (retryableErrors.length > 0) {
30457
+ const isCustomRetriable = retryableErrors.some(
30458
+ (errType) => error.code === errType || error.name === errType || error.message?.includes(errType)
30459
+ );
30460
+ if (isCustomRetriable) return RETRIABLE;
30461
+ }
30462
+ if (nonRetriableErrors.length > 0) {
30463
+ const isCustomNonRetriable = nonRetriableErrors.some(
30464
+ (errType) => error.code === errType || error.name === errType || error.message?.includes(errType)
30465
+ );
30466
+ if (isCustomNonRetriable) return NON_RETRIABLE;
30467
+ }
30468
+ if (error.retriable === false) return NON_RETRIABLE;
30469
+ if (error.retriable === true) return RETRIABLE;
30470
+ if (NON_RETRIABLE_ERROR_NAMES.has(error.name)) {
30471
+ return NON_RETRIABLE;
30472
+ }
30473
+ if (error.statusCode && NON_RETRIABLE_STATUS_CODES.has(error.statusCode)) {
30474
+ return NON_RETRIABLE;
30475
+ }
30476
+ if (error.code && RETRIABLE_NETWORK_CODES.has(error.code)) {
30477
+ return RETRIABLE;
30478
+ }
30479
+ if (error.code && RETRIABLE_AWS_CODES.has(error.code)) {
30480
+ return RETRIABLE;
30481
+ }
30482
+ if (error.code && RETRIABLE_AWS_CONFLICTS.has(error.code)) {
30483
+ return RETRIABLE;
30484
+ }
30485
+ if (error.statusCode && RETRIABLE_STATUS_CODES.has(error.statusCode)) {
30486
+ return RETRIABLE;
30487
+ }
30488
+ if (error.message && typeof error.message === "string") {
30489
+ const lowerMessage = error.message.toLowerCase();
30490
+ if (lowerMessage.includes("timeout") || lowerMessage.includes("timed out") || lowerMessage.includes("network") || lowerMessage.includes("connection")) {
30491
+ return RETRIABLE;
30492
+ }
30493
+ }
30494
+ return RETRIABLE;
30495
+ }
30496
+ /**
30497
+ * Check if an error is retriable
30498
+ *
30499
+ * @param {Error} error - The error to check
30500
+ * @param {Object} options - Classification options
30501
+ * @returns {boolean} true if retriable
30502
+ */
30503
+ static isRetriable(error, options = {}) {
30504
+ return this.classify(error, options) === RETRIABLE;
30505
+ }
30506
+ /**
30507
+ * Check if an error is non-retriable
30508
+ *
30509
+ * @param {Error} error - The error to check
30510
+ * @param {Object} options - Classification options
30511
+ * @returns {boolean} true if non-retriable
30512
+ */
30513
+ static isNonRetriable(error, options = {}) {
30514
+ return this.classify(error, options) === NON_RETRIABLE;
30157
30515
  }
30158
30516
  }
30159
30517
 
@@ -30174,11 +30532,23 @@ class StateMachinePlugin extends Plugin {
30174
30532
  workerId: options.workerId || "default",
30175
30533
  lockTimeout: options.lockTimeout || 1e3,
30176
30534
  // Wait up to 1s for lock
30177
- lockTTL: options.lockTTL || 5
30535
+ lockTTL: options.lockTTL || 5,
30178
30536
  // Lock expires after 5s (prevent deadlock)
30537
+ // Global retry configuration for action execution
30538
+ retryConfig: options.retryConfig || null,
30539
+ // Trigger system configuration
30540
+ enableScheduler: options.enableScheduler || false,
30541
+ schedulerConfig: options.schedulerConfig || {},
30542
+ enableDateTriggers: options.enableDateTriggers !== false,
30543
+ enableFunctionTriggers: options.enableFunctionTriggers !== false,
30544
+ enableEventTriggers: options.enableEventTriggers !== false,
30545
+ triggerCheckInterval: options.triggerCheckInterval || 6e4
30546
+ // Check triggers every 60s by default
30179
30547
  };
30180
30548
  this.database = null;
30181
30549
  this.machines = /* @__PURE__ */ new Map();
30550
+ this.triggerIntervals = [];
30551
+ this.schedulerPlugin = null;
30182
30552
  this._validateConfiguration();
30183
30553
  }
30184
30554
  _validateConfiguration() {
@@ -30227,7 +30597,8 @@ class StateMachinePlugin extends Plugin {
30227
30597
  // entityId -> currentState
30228
30598
  });
30229
30599
  }
30230
- this.emit("initialized", { machines: Array.from(this.machines.keys()) });
30600
+ await this._setupTriggers();
30601
+ this.emit("db:plugin:initialized", { machines: Array.from(this.machines.keys()) });
30231
30602
  }
30232
30603
  async _createStateResources() {
30233
30604
  const [logOk] = await tryFn(() => this.database.createResource({
@@ -30258,6 +30629,8 @@ class StateMachinePlugin extends Plugin {
30258
30629
  currentState: "string|required",
30259
30630
  context: "json|default:{}",
30260
30631
  lastTransition: "string|default:null",
30632
+ triggerCounts: "json|default:{}",
30633
+ // Track trigger execution counts
30261
30634
  updatedAt: "string|required"
30262
30635
  },
30263
30636
  behavior: "body-overflow"
@@ -30321,7 +30694,7 @@ class StateMachinePlugin extends Plugin {
30321
30694
  if (targetStateConfig && targetStateConfig.entry) {
30322
30695
  await this._executeAction(targetStateConfig.entry, context, event, machineId, entityId);
30323
30696
  }
30324
- this.emit("transition", {
30697
+ this.emit("plg:state-machine:transition", {
30325
30698
  machineId,
30326
30699
  entityId,
30327
30700
  from: currentState,
@@ -30347,14 +30720,97 @@ class StateMachinePlugin extends Plugin {
30347
30720
  }
30348
30721
  return;
30349
30722
  }
30350
- const [ok, error] = await tryFn(
30351
- () => action(context, event, { database: this.database, machineId, entityId })
30352
- );
30353
- if (!ok) {
30354
- if (this.config.verbose) {
30355
- console.error(`[StateMachinePlugin] Action '${actionName}' failed:`, error.message);
30723
+ const machine = this.machines.get(machineId);
30724
+ const currentState = await this.getState(machineId, entityId);
30725
+ const stateConfig = machine?.config?.states?.[currentState];
30726
+ const retryConfig = {
30727
+ ...this.config.retryConfig || {},
30728
+ ...machine?.config?.retryConfig || {},
30729
+ ...stateConfig?.retryConfig || {}
30730
+ };
30731
+ const maxAttempts = retryConfig.maxAttempts ?? 0;
30732
+ const retryEnabled = maxAttempts > 0;
30733
+ let attempt = 0;
30734
+ while (attempt <= maxAttempts) {
30735
+ try {
30736
+ const result = await action(context, event, { database: this.database, machineId, entityId });
30737
+ if (attempt > 0) {
30738
+ this.emit("plg:state-machine:action-retry-success", {
30739
+ machineId,
30740
+ entityId,
30741
+ action: actionName,
30742
+ attempts: attempt + 1,
30743
+ state: currentState
30744
+ });
30745
+ if (this.config.verbose) {
30746
+ console.log(`[StateMachinePlugin] Action '${actionName}' succeeded after ${attempt + 1} attempts`);
30747
+ }
30748
+ }
30749
+ return result;
30750
+ } catch (error) {
30751
+ if (!retryEnabled) {
30752
+ if (this.config.verbose) {
30753
+ console.error(`[StateMachinePlugin] Action '${actionName}' failed:`, error.message);
30754
+ }
30755
+ this.emit("plg:state-machine:action-error", { actionName, error: error.message, machineId, entityId });
30756
+ return;
30757
+ }
30758
+ const classification = ErrorClassifier.classify(error, {
30759
+ retryableErrors: retryConfig.retryableErrors,
30760
+ nonRetriableErrors: retryConfig.nonRetriableErrors
30761
+ });
30762
+ if (classification === "NON_RETRIABLE") {
30763
+ this.emit("plg:state-machine:action-error-non-retriable", {
30764
+ machineId,
30765
+ entityId,
30766
+ action: actionName,
30767
+ error: error.message,
30768
+ state: currentState
30769
+ });
30770
+ if (this.config.verbose) {
30771
+ console.error(`[StateMachinePlugin] Action '${actionName}' failed with non-retriable error:`, error.message);
30772
+ }
30773
+ throw error;
30774
+ }
30775
+ if (attempt >= maxAttempts) {
30776
+ this.emit("plg:state-machine:action-retry-exhausted", {
30777
+ machineId,
30778
+ entityId,
30779
+ action: actionName,
30780
+ attempts: attempt + 1,
30781
+ error: error.message,
30782
+ state: currentState
30783
+ });
30784
+ if (this.config.verbose) {
30785
+ console.error(`[StateMachinePlugin] Action '${actionName}' failed after ${attempt + 1} attempts:`, error.message);
30786
+ }
30787
+ throw error;
30788
+ }
30789
+ attempt++;
30790
+ const delay = this._calculateBackoff(attempt, retryConfig);
30791
+ if (retryConfig.onRetry) {
30792
+ try {
30793
+ await retryConfig.onRetry(attempt, error, context);
30794
+ } catch (hookError) {
30795
+ if (this.config.verbose) {
30796
+ console.warn(`[StateMachinePlugin] onRetry hook failed:`, hookError.message);
30797
+ }
30798
+ }
30799
+ }
30800
+ this.emit("plg:state-machine:action-retry-attempt", {
30801
+ machineId,
30802
+ entityId,
30803
+ action: actionName,
30804
+ attempt,
30805
+ delay,
30806
+ error: error.message,
30807
+ state: currentState
30808
+ });
30809
+ if (this.config.verbose) {
30810
+ console.warn(`[StateMachinePlugin] Action '${actionName}' failed (attempt ${attempt + 1}/${maxAttempts + 1}), retrying in ${delay}ms:`, error.message);
30811
+ }
30812
+ await new Promise((resolve) => setTimeout(resolve, delay));
30356
30813
  }
30357
- this.emit("action_error", { actionName, error: error.message, machineId, entityId });
30358
30814
  }
30359
30815
  }
30360
30816
  async _transition(machineId, entityId, fromState, toState, event, context) {
@@ -30452,6 +30908,27 @@ class StateMachinePlugin extends Plugin {
30452
30908
  console.warn(`[StateMachinePlugin] Failed to release lock '${lockName}':`, err.message);
30453
30909
  }
30454
30910
  }
30911
+ /**
30912
+ * Calculate backoff delay for retry attempts
30913
+ * @private
30914
+ */
30915
+ _calculateBackoff(attempt, retryConfig) {
30916
+ const {
30917
+ backoffStrategy = "exponential",
30918
+ baseDelay = 1e3,
30919
+ maxDelay = 3e4
30920
+ } = retryConfig || {};
30921
+ let delay;
30922
+ if (backoffStrategy === "exponential") {
30923
+ delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
30924
+ } else if (backoffStrategy === "linear") {
30925
+ delay = Math.min(baseDelay * attempt, maxDelay);
30926
+ } else {
30927
+ delay = baseDelay;
30928
+ }
30929
+ const jitter = delay * 0.2 * (Math.random() - 0.5);
30930
+ return Math.round(delay + jitter);
30931
+ }
30455
30932
  /**
30456
30933
  * Get current state for an entity
30457
30934
  */
@@ -30581,7 +31058,7 @@ class StateMachinePlugin extends Plugin {
30581
31058
  if (initialStateConfig && initialStateConfig.entry) {
30582
31059
  await this._executeAction(initialStateConfig.entry, context, "INIT", machineId, entityId);
30583
31060
  }
30584
- this.emit("entity_initialized", { machineId, entityId, initialState });
31061
+ this.emit("plg:state-machine:entity-initialized", { machineId, entityId, initialState });
30585
31062
  return initialState;
30586
31063
  }
30587
31064
  /**
@@ -30638,12 +31115,343 @@ class StateMachinePlugin extends Plugin {
30638
31115
  `;
30639
31116
  return dot;
30640
31117
  }
31118
+ /**
31119
+ * Get all entities currently in a specific state
31120
+ * @private
31121
+ */
31122
+ async _getEntitiesInState(machineId, stateName) {
31123
+ if (!this.config.persistTransitions) {
31124
+ const machine = this.machines.get(machineId);
31125
+ if (!machine) return [];
31126
+ const entities = [];
31127
+ for (const [entityId, currentState] of machine.currentStates) {
31128
+ if (currentState === stateName) {
31129
+ entities.push({ entityId, currentState, context: {}, triggerCounts: {} });
31130
+ }
31131
+ }
31132
+ return entities;
31133
+ }
31134
+ const [ok, err, records] = await tryFn(
31135
+ () => this.database.resources[this.config.stateResource].query({
31136
+ machineId,
31137
+ currentState: stateName
31138
+ })
31139
+ );
31140
+ if (!ok) {
31141
+ if (this.config.verbose) {
31142
+ console.warn(`[StateMachinePlugin] Failed to query entities in state '${stateName}':`, err.message);
31143
+ }
31144
+ return [];
31145
+ }
31146
+ return records || [];
31147
+ }
31148
+ /**
31149
+ * Increment trigger execution count for an entity
31150
+ * @private
31151
+ */
31152
+ async _incrementTriggerCount(machineId, entityId, triggerName) {
31153
+ if (!this.config.persistTransitions) {
31154
+ return;
31155
+ }
31156
+ const stateId = `${machineId}_${entityId}`;
31157
+ const [ok, err, stateRecord] = await tryFn(
31158
+ () => this.database.resources[this.config.stateResource].get(stateId)
31159
+ );
31160
+ if (ok && stateRecord) {
31161
+ const triggerCounts = stateRecord.triggerCounts || {};
31162
+ triggerCounts[triggerName] = (triggerCounts[triggerName] || 0) + 1;
31163
+ await tryFn(
31164
+ () => this.database.resources[this.config.stateResource].patch(stateId, { triggerCounts })
31165
+ );
31166
+ }
31167
+ }
31168
+ /**
31169
+ * Setup trigger system for all state machines
31170
+ * @private
31171
+ */
31172
+ async _setupTriggers() {
31173
+ if (!this.config.enableScheduler && !this.config.enableDateTriggers && !this.config.enableFunctionTriggers && !this.config.enableEventTriggers) {
31174
+ return;
31175
+ }
31176
+ const cronJobs = {};
31177
+ for (const [machineId, machineData] of this.machines) {
31178
+ const machineConfig = machineData.config;
31179
+ for (const [stateName, stateConfig] of Object.entries(machineConfig.states)) {
31180
+ const triggers = stateConfig.triggers || [];
31181
+ for (let i = 0; i < triggers.length; i++) {
31182
+ const trigger = triggers[i];
31183
+ const triggerName = `${trigger.action}_${i}`;
31184
+ if (trigger.type === "cron" && this.config.enableScheduler) {
31185
+ const jobName = `${machineId}_${stateName}_${triggerName}`;
31186
+ cronJobs[jobName] = await this._createCronJob(machineId, stateName, trigger, triggerName);
31187
+ } else if (trigger.type === "date" && this.config.enableDateTriggers) {
31188
+ await this._setupDateTrigger(machineId, stateName, trigger, triggerName);
31189
+ } else if (trigger.type === "function" && this.config.enableFunctionTriggers) {
31190
+ await this._setupFunctionTrigger(machineId, stateName, trigger, triggerName);
31191
+ } else if (trigger.type === "event" && this.config.enableEventTriggers) {
31192
+ await this._setupEventTrigger(machineId, stateName, trigger, triggerName);
31193
+ }
31194
+ }
31195
+ }
31196
+ }
31197
+ if (Object.keys(cronJobs).length > 0 && this.config.enableScheduler) {
31198
+ const { SchedulerPlugin } = await Promise.resolve().then(function () { return scheduler_plugin; });
31199
+ this.schedulerPlugin = new SchedulerPlugin({
31200
+ jobs: cronJobs,
31201
+ persistJobs: false,
31202
+ // Don't persist trigger jobs
31203
+ verbose: this.config.verbose,
31204
+ ...this.config.schedulerConfig
31205
+ });
31206
+ await this.database.usePlugin(this.schedulerPlugin);
31207
+ if (this.config.verbose) {
31208
+ console.log(`[StateMachinePlugin] Installed SchedulerPlugin with ${Object.keys(cronJobs).length} cron triggers`);
31209
+ }
31210
+ }
31211
+ }
31212
+ /**
31213
+ * Create a SchedulerPlugin job for a cron trigger
31214
+ * @private
31215
+ */
31216
+ async _createCronJob(machineId, stateName, trigger, triggerName) {
31217
+ return {
31218
+ schedule: trigger.schedule,
31219
+ description: `Trigger '${triggerName}' for ${machineId}.${stateName}`,
31220
+ action: async (database, context) => {
31221
+ const entities = await this._getEntitiesInState(machineId, stateName);
31222
+ let executedCount = 0;
31223
+ for (const entity of entities) {
31224
+ try {
31225
+ if (trigger.condition) {
31226
+ const shouldTrigger = await trigger.condition(entity.context, entity.entityId);
31227
+ if (!shouldTrigger) continue;
31228
+ }
31229
+ if (trigger.maxTriggers !== void 0) {
31230
+ const triggerCount = entity.triggerCounts?.[triggerName] || 0;
31231
+ if (triggerCount >= trigger.maxTriggers) {
31232
+ if (trigger.onMaxTriggersReached) {
31233
+ await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
31234
+ }
31235
+ continue;
31236
+ }
31237
+ }
31238
+ const result = await this._executeAction(
31239
+ trigger.action,
31240
+ entity.context,
31241
+ "TRIGGER",
31242
+ machineId,
31243
+ entity.entityId
31244
+ );
31245
+ await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
31246
+ executedCount++;
31247
+ if (trigger.eventOnSuccess) {
31248
+ await this.send(machineId, entity.entityId, trigger.eventOnSuccess, {
31249
+ ...entity.context,
31250
+ triggerResult: result
31251
+ });
31252
+ } else if (trigger.event) {
31253
+ await this.send(machineId, entity.entityId, trigger.event, {
31254
+ ...entity.context,
31255
+ triggerResult: result
31256
+ });
31257
+ }
31258
+ this.emit("plg:state-machine:trigger-executed", {
31259
+ machineId,
31260
+ entityId: entity.entityId,
31261
+ state: stateName,
31262
+ trigger: triggerName,
31263
+ type: "cron"
31264
+ });
31265
+ } catch (error) {
31266
+ if (trigger.event) {
31267
+ await tryFn(() => this.send(machineId, entity.entityId, trigger.event, {
31268
+ ...entity.context,
31269
+ triggerError: error.message
31270
+ }));
31271
+ }
31272
+ if (this.config.verbose) {
31273
+ console.error(`[StateMachinePlugin] Trigger '${triggerName}' failed for entity ${entity.entityId}:`, error.message);
31274
+ }
31275
+ }
31276
+ }
31277
+ return { processed: entities.length, executed: executedCount };
31278
+ }
31279
+ };
31280
+ }
31281
+ /**
31282
+ * Setup a date-based trigger
31283
+ * @private
31284
+ */
31285
+ async _setupDateTrigger(machineId, stateName, trigger, triggerName) {
31286
+ const checkInterval = setInterval(async () => {
31287
+ const entities = await this._getEntitiesInState(machineId, stateName);
31288
+ for (const entity of entities) {
31289
+ try {
31290
+ const triggerDateValue = entity.context?.[trigger.field];
31291
+ if (!triggerDateValue) continue;
31292
+ const triggerDate = new Date(triggerDateValue);
31293
+ const now = /* @__PURE__ */ new Date();
31294
+ if (now >= triggerDate) {
31295
+ if (trigger.maxTriggers !== void 0) {
31296
+ const triggerCount = entity.triggerCounts?.[triggerName] || 0;
31297
+ if (triggerCount >= trigger.maxTriggers) {
31298
+ if (trigger.onMaxTriggersReached) {
31299
+ await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
31300
+ }
31301
+ continue;
31302
+ }
31303
+ }
31304
+ const result = await this._executeAction(trigger.action, entity.context, "TRIGGER", machineId, entity.entityId);
31305
+ await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
31306
+ if (trigger.event) {
31307
+ await this.send(machineId, entity.entityId, trigger.event, {
31308
+ ...entity.context,
31309
+ triggerResult: result
31310
+ });
31311
+ }
31312
+ this.emit("plg:state-machine:trigger-executed", {
31313
+ machineId,
31314
+ entityId: entity.entityId,
31315
+ state: stateName,
31316
+ trigger: triggerName,
31317
+ type: "date"
31318
+ });
31319
+ }
31320
+ } catch (error) {
31321
+ if (this.config.verbose) {
31322
+ console.error(`[StateMachinePlugin] Date trigger '${triggerName}' failed:`, error.message);
31323
+ }
31324
+ }
31325
+ }
31326
+ }, this.config.triggerCheckInterval);
31327
+ this.triggerIntervals.push(checkInterval);
31328
+ }
31329
+ /**
31330
+ * Setup a function-based trigger
31331
+ * @private
31332
+ */
31333
+ async _setupFunctionTrigger(machineId, stateName, trigger, triggerName) {
31334
+ const interval = trigger.interval || this.config.triggerCheckInterval;
31335
+ const checkInterval = setInterval(async () => {
31336
+ const entities = await this._getEntitiesInState(machineId, stateName);
31337
+ for (const entity of entities) {
31338
+ try {
31339
+ if (trigger.maxTriggers !== void 0) {
31340
+ const triggerCount = entity.triggerCounts?.[triggerName] || 0;
31341
+ if (triggerCount >= trigger.maxTriggers) {
31342
+ if (trigger.onMaxTriggersReached) {
31343
+ await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
31344
+ }
31345
+ continue;
31346
+ }
31347
+ }
31348
+ const shouldTrigger = await trigger.condition(entity.context, entity.entityId);
31349
+ if (shouldTrigger) {
31350
+ const result = await this._executeAction(trigger.action, entity.context, "TRIGGER", machineId, entity.entityId);
31351
+ await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
31352
+ if (trigger.event) {
31353
+ await this.send(machineId, entity.entityId, trigger.event, {
31354
+ ...entity.context,
31355
+ triggerResult: result
31356
+ });
31357
+ }
31358
+ this.emit("plg:state-machine:trigger-executed", {
31359
+ machineId,
31360
+ entityId: entity.entityId,
31361
+ state: stateName,
31362
+ trigger: triggerName,
31363
+ type: "function"
31364
+ });
31365
+ }
31366
+ } catch (error) {
31367
+ if (this.config.verbose) {
31368
+ console.error(`[StateMachinePlugin] Function trigger '${triggerName}' failed:`, error.message);
31369
+ }
31370
+ }
31371
+ }
31372
+ }, interval);
31373
+ this.triggerIntervals.push(checkInterval);
31374
+ }
31375
+ /**
31376
+ * Setup an event-based trigger
31377
+ * @private
31378
+ */
31379
+ async _setupEventTrigger(machineId, stateName, trigger, triggerName) {
31380
+ const eventName = trigger.event;
31381
+ const eventHandler = async (eventData) => {
31382
+ const entities = await this._getEntitiesInState(machineId, stateName);
31383
+ for (const entity of entities) {
31384
+ try {
31385
+ if (trigger.condition) {
31386
+ const shouldTrigger = await trigger.condition(entity.context, entity.entityId, eventData);
31387
+ if (!shouldTrigger) continue;
31388
+ }
31389
+ if (trigger.maxTriggers !== void 0) {
31390
+ const triggerCount = entity.triggerCounts?.[triggerName] || 0;
31391
+ if (triggerCount >= trigger.maxTriggers) {
31392
+ if (trigger.onMaxTriggersReached) {
31393
+ await this.send(machineId, entity.entityId, trigger.onMaxTriggersReached, entity.context);
31394
+ }
31395
+ continue;
31396
+ }
31397
+ }
31398
+ const result = await this._executeAction(
31399
+ trigger.action,
31400
+ { ...entity.context, eventData },
31401
+ "TRIGGER",
31402
+ machineId,
31403
+ entity.entityId
31404
+ );
31405
+ await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
31406
+ if (trigger.sendEvent) {
31407
+ await this.send(machineId, entity.entityId, trigger.sendEvent, {
31408
+ ...entity.context,
31409
+ triggerResult: result,
31410
+ eventData
31411
+ });
31412
+ }
31413
+ this.emit("plg:state-machine:trigger-executed", {
31414
+ machineId,
31415
+ entityId: entity.entityId,
31416
+ state: stateName,
31417
+ trigger: triggerName,
31418
+ type: "event",
31419
+ eventName
31420
+ });
31421
+ } catch (error) {
31422
+ if (this.config.verbose) {
31423
+ console.error(`[StateMachinePlugin] Event trigger '${triggerName}' failed:`, error.message);
31424
+ }
31425
+ }
31426
+ }
31427
+ };
31428
+ if (eventName.startsWith("db:")) {
31429
+ const dbEventName = eventName.substring(3);
31430
+ this.database.on(dbEventName, eventHandler);
31431
+ if (this.config.verbose) {
31432
+ console.log(`[StateMachinePlugin] Listening to database event '${dbEventName}' for trigger '${triggerName}'`);
31433
+ }
31434
+ } else {
31435
+ this.on(eventName, eventHandler);
31436
+ if (this.config.verbose) {
31437
+ console.log(`[StateMachinePlugin] Listening to plugin event '${eventName}' for trigger '${triggerName}'`);
31438
+ }
31439
+ }
31440
+ }
30641
31441
  async start() {
30642
31442
  if (this.config.verbose) {
30643
31443
  console.log(`[StateMachinePlugin] Started with ${this.machines.size} state machines`);
30644
31444
  }
30645
31445
  }
30646
31446
  async stop() {
31447
+ for (const interval of this.triggerIntervals) {
31448
+ clearInterval(interval);
31449
+ }
31450
+ this.triggerIntervals = [];
31451
+ if (this.schedulerPlugin) {
31452
+ await this.schedulerPlugin.stop();
31453
+ this.schedulerPlugin = null;
31454
+ }
30647
31455
  this.machines.clear();
30648
31456
  this.removeAllListeners();
30649
31457
  }
@@ -40915,7 +41723,7 @@ class TTLPlugin extends Plugin {
40915
41723
  if (this.verbose) {
40916
41724
  console.log(`[TTLPlugin] Installed with ${Object.keys(this.resources).length} resources`);
40917
41725
  }
40918
- this.emit("installed", {
41726
+ this.emit("db:plugin:installed", {
40919
41727
  plugin: "TTLPlugin",
40920
41728
  resources: Object.keys(this.resources)
40921
41729
  });
@@ -41152,7 +41960,7 @@ class TTLPlugin extends Plugin {
41152
41960
  }
41153
41961
  this.stats.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
41154
41962
  this.stats.lastScanDuration = Date.now() - startTime;
41155
- this.emit("scanCompleted", {
41963
+ this.emit("plg:ttl:scan-completed", {
41156
41964
  granularity,
41157
41965
  duration: this.stats.lastScanDuration,
41158
41966
  cohorts
@@ -41160,7 +41968,7 @@ class TTLPlugin extends Plugin {
41160
41968
  } catch (error) {
41161
41969
  console.error(`[TTLPlugin] Error in ${granularity} cleanup:`, error);
41162
41970
  this.stats.totalErrors++;
41163
- this.emit("cleanupError", { granularity, error });
41971
+ this.emit("plg:ttl:cleanup-error", { granularity, error });
41164
41972
  }
41165
41973
  }
41166
41974
  /**
@@ -41208,7 +42016,7 @@ class TTLPlugin extends Plugin {
41208
42016
  }
41209
42017
  await this.expirationIndex.delete(entry.id);
41210
42018
  this.stats.totalExpired++;
41211
- this.emit("recordExpired", { resource: entry.resourceName, record });
42019
+ this.emit("plg:ttl:record-expired", { resource: entry.resourceName, record });
41212
42020
  } catch (error) {
41213
42021
  console.error(`[TTLPlugin] Error processing expired entry:`, error);
41214
42022
  this.stats.totalErrors++;
@@ -41679,15 +42487,15 @@ class VectorPlugin extends Plugin {
41679
42487
  this._throttleState = /* @__PURE__ */ new Map();
41680
42488
  }
41681
42489
  async onInstall() {
41682
- this.emit("installed", { plugin: "VectorPlugin" });
42490
+ this.emit("db:plugin:installed", { plugin: "VectorPlugin" });
41683
42491
  this.validateVectorStorage();
41684
42492
  this.installResourceMethods();
41685
42493
  }
41686
42494
  async onStart() {
41687
- this.emit("started", { plugin: "VectorPlugin" });
42495
+ this.emit("db:plugin:started", { plugin: "VectorPlugin" });
41688
42496
  }
41689
42497
  async onStop() {
41690
- this.emit("stopped", { plugin: "VectorPlugin" });
42498
+ this.emit("db:plugin:stopped", { plugin: "VectorPlugin" });
41691
42499
  }
41692
42500
  async onUninstall(options) {
41693
42501
  for (const resource of Object.values(this.database.resources)) {
@@ -41698,7 +42506,7 @@ class VectorPlugin extends Plugin {
41698
42506
  delete resource.findSimilar;
41699
42507
  delete resource.distance;
41700
42508
  }
41701
- this.emit("uninstalled", { plugin: "VectorPlugin" });
42509
+ this.emit("db:plugin:uninstalled", { plugin: "VectorPlugin" });
41702
42510
  }
41703
42511
  /**
41704
42512
  * Validate vector storage configuration for all resources
@@ -41727,10 +42535,10 @@ class VectorPlugin extends Plugin {
41727
42535
  currentBehavior: resource.behavior || "default",
41728
42536
  recommendation: "body-overflow"
41729
42537
  };
41730
- this.emit("vector:storage-warning", warning);
42538
+ this.emit("plg:vector:storage-warning", warning);
41731
42539
  if (this.config.autoFixBehavior) {
41732
42540
  resource.behavior = "body-overflow";
41733
- this.emit("vector:behavior-fixed", {
42541
+ this.emit("plg:vector:behavior-fixed", {
41734
42542
  resource: resource.name,
41735
42543
  newBehavior: "body-overflow"
41736
42544
  });
@@ -41762,7 +42570,7 @@ class VectorPlugin extends Plugin {
41762
42570
  const partitionName = `byHas${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
41763
42571
  const trackingFieldName = `_has${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
41764
42572
  if (resource.config.partitions && resource.config.partitions[partitionName]) {
41765
- this.emit("vector:partition-exists", {
42573
+ this.emit("plg:vector:partition-exists", {
41766
42574
  resource: resource.name,
41767
42575
  vectorField: vectorField.name,
41768
42576
  partition: partitionName,
@@ -41785,7 +42593,7 @@ class VectorPlugin extends Plugin {
41785
42593
  default: false
41786
42594
  }, "VectorPlugin");
41787
42595
  }
41788
- this.emit("vector:partition-created", {
42596
+ this.emit("plg:vector:partition-created", {
41789
42597
  resource: resource.name,
41790
42598
  vectorField: vectorField.name,
41791
42599
  partition: partitionName,
@@ -41860,7 +42668,7 @@ class VectorPlugin extends Plugin {
41860
42668
  }
41861
42669
  return updates;
41862
42670
  });
41863
- this.emit("vector:hooks-installed", {
42671
+ this.emit("plg:vector:hooks-installed", {
41864
42672
  resource: resource.name,
41865
42673
  vectorField,
41866
42674
  trackingField,
@@ -41969,7 +42777,7 @@ class VectorPlugin extends Plugin {
41969
42777
  const vectorField = this._findEmbeddingField(resource.schema.attributes);
41970
42778
  this._vectorFieldCache.set(resource.name, vectorField);
41971
42779
  if (vectorField && this.config.emitEvents) {
41972
- this.emit("vector:field-detected", {
42780
+ this.emit("plg:vector:field-detected", {
41973
42781
  resource: resource.name,
41974
42782
  vectorField,
41975
42783
  timestamp: Date.now()
@@ -42893,7 +43701,7 @@ class MemoryClient extends EventEmitter {
42893
43701
  async sendCommand(command) {
42894
43702
  const commandName = command.constructor.name;
42895
43703
  const input = command.input || {};
42896
- this.emit("command.request", commandName, input);
43704
+ this.emit("cl:request", commandName, input);
42897
43705
  let response;
42898
43706
  try {
42899
43707
  switch (commandName) {
@@ -42921,7 +43729,7 @@ class MemoryClient extends EventEmitter {
42921
43729
  default:
42922
43730
  throw new Error(`Unsupported command: ${commandName}`);
42923
43731
  }
42924
- this.emit("command.response", commandName, response, input);
43732
+ this.emit("cl:response", commandName, response, input);
42925
43733
  return response;
42926
43734
  } catch (error) {
42927
43735
  const mappedError = mapAwsError(error, {
@@ -43032,7 +43840,7 @@ class MemoryClient extends EventEmitter {
43032
43840
  contentLength,
43033
43841
  ifMatch
43034
43842
  });
43035
- this.emit("putObject", null, { key, metadata, contentType, body, contentEncoding, contentLength });
43843
+ this.emit("cl:PutObject", null, { key, metadata, contentType, body, contentEncoding, contentLength });
43036
43844
  return response;
43037
43845
  }
43038
43846
  /**
@@ -43047,7 +43855,7 @@ class MemoryClient extends EventEmitter {
43047
43855
  decodedMetadata[k] = metadataDecode(v);
43048
43856
  }
43049
43857
  }
43050
- this.emit("getObject", null, { key });
43858
+ this.emit("cl:GetObject", null, { key });
43051
43859
  return {
43052
43860
  ...response,
43053
43861
  Metadata: decodedMetadata
@@ -43065,7 +43873,7 @@ class MemoryClient extends EventEmitter {
43065
43873
  decodedMetadata[k] = metadataDecode(v);
43066
43874
  }
43067
43875
  }
43068
- this.emit("headObject", null, { key });
43876
+ this.emit("cl:HeadObject", null, { key });
43069
43877
  return {
43070
43878
  ...response,
43071
43879
  Metadata: decodedMetadata
@@ -43090,7 +43898,7 @@ class MemoryClient extends EventEmitter {
43090
43898
  metadataDirective,
43091
43899
  contentType
43092
43900
  });
43093
- this.emit("copyObject", null, { from, to, metadata, metadataDirective });
43901
+ this.emit("cl:CopyObject", null, { from, to, metadata, metadataDirective });
43094
43902
  return response;
43095
43903
  }
43096
43904
  /**
@@ -43106,7 +43914,7 @@ class MemoryClient extends EventEmitter {
43106
43914
  async deleteObject(key) {
43107
43915
  const fullKey = this.keyPrefix ? path$1.join(this.keyPrefix, key) : key;
43108
43916
  const response = await this.storage.delete(fullKey);
43109
- this.emit("deleteObject", null, { key });
43917
+ this.emit("cl:DeleteObject", null, { key });
43110
43918
  return response;
43111
43919
  }
43112
43920
  /**
@@ -43139,7 +43947,7 @@ class MemoryClient extends EventEmitter {
43139
43947
  maxKeys,
43140
43948
  continuationToken
43141
43949
  });
43142
- this.emit("listObjects", null, { prefix, count: response.Contents.length });
43950
+ this.emit("cl:ListObjects", null, { prefix, count: response.Contents.length });
43143
43951
  return response;
43144
43952
  }
43145
43953
  /**
@@ -43179,7 +43987,7 @@ class MemoryClient extends EventEmitter {
43179
43987
  if (this.keyPrefix) {
43180
43988
  keys = keys.map((x) => x.replace(this.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace("/", "") : x);
43181
43989
  }
43182
- this.emit("getKeysPage", keys, params);
43990
+ this.emit("cl:GetKeysPage", keys, params);
43183
43991
  return keys;
43184
43992
  }
43185
43993
  /**
@@ -43196,7 +44004,7 @@ class MemoryClient extends EventEmitter {
43196
44004
  if (this.keyPrefix) {
43197
44005
  keys = keys.map((x) => x.replace(this.keyPrefix, "")).map((x) => x.startsWith("/") ? x.replace("/", "") : x);
43198
44006
  }
43199
- this.emit("getAllKeys", keys, { prefix });
44007
+ this.emit("cl:GetAllKeys", keys, { prefix });
43200
44008
  return keys;
43201
44009
  }
43202
44010
  /**
@@ -43205,7 +44013,7 @@ class MemoryClient extends EventEmitter {
43205
44013
  async count({ prefix = "" } = {}) {
43206
44014
  const keys = await this.getAllKeys({ prefix });
43207
44015
  const count = keys.length;
43208
- this.emit("count", count, { prefix });
44016
+ this.emit("cl:Count", count, { prefix });
43209
44017
  return count;
43210
44018
  }
43211
44019
  /**
@@ -43217,13 +44025,13 @@ class MemoryClient extends EventEmitter {
43217
44025
  if (keys.length > 0) {
43218
44026
  const result = await this.deleteObjects(keys);
43219
44027
  totalDeleted = result.Deleted.length;
43220
- this.emit("deleteAll", {
44028
+ this.emit("cl:DeleteAll", {
43221
44029
  prefix,
43222
44030
  batch: totalDeleted,
43223
44031
  total: totalDeleted
43224
44032
  });
43225
44033
  }
43226
- this.emit("deleteAllComplete", {
44034
+ this.emit("cl:DeleteAllComplete", {
43227
44035
  prefix,
43228
44036
  totalDeleted
43229
44037
  });
@@ -43236,11 +44044,11 @@ class MemoryClient extends EventEmitter {
43236
44044
  if (offset === 0) return null;
43237
44045
  const keys = await this.getAllKeys({ prefix });
43238
44046
  if (offset >= keys.length) {
43239
- this.emit("getContinuationTokenAfterOffset", null, { prefix, offset });
44047
+ this.emit("cl:GetContinuationTokenAfterOffset", null, { prefix, offset });
43240
44048
  return null;
43241
44049
  }
43242
44050
  const token = keys[offset];
43243
- this.emit("getContinuationTokenAfterOffset", token, { prefix, offset });
44051
+ this.emit("cl:GetContinuationTokenAfterOffset", token, { prefix, offset });
43244
44052
  return token;
43245
44053
  }
43246
44054
  /**
@@ -43270,7 +44078,7 @@ class MemoryClient extends EventEmitter {
43270
44078
  });
43271
44079
  }
43272
44080
  }
43273
- this.emit("moveAllObjects", { results, errors }, { prefixFrom, prefixTo });
44081
+ this.emit("cl:MoveAllObjects", { results, errors }, { prefixFrom, prefixTo });
43274
44082
  if (errors.length > 0) {
43275
44083
  const error = new Error("Some objects could not be moved");
43276
44084
  error.context = {