s3db.js 13.3.1 → 13.4.0

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.
@@ -1170,10 +1170,22 @@ export class StateMachinePlugin extends Plugin {
1170
1170
 
1171
1171
  /**
1172
1172
  * Setup an event-based trigger
1173
+ * Supports both old API (trigger.event) and new API (trigger.eventName + eventSource)
1173
1174
  * @private
1174
1175
  */
1175
1176
  async _setupEventTrigger(machineId, stateName, trigger, triggerName) {
1176
- const eventName = trigger.event;
1177
+ // Support both old API (event) and new API (eventName)
1178
+ const baseEventName = trigger.eventName || trigger.event;
1179
+ const eventSource = trigger.eventSource;
1180
+
1181
+ if (!baseEventName) {
1182
+ throw new StateMachineError(`Event trigger '${triggerName}' must have either 'event' or 'eventName' property`, {
1183
+ operation: '_setupEventTrigger',
1184
+ machineId,
1185
+ stateName,
1186
+ triggerName
1187
+ });
1188
+ }
1177
1189
 
1178
1190
  // Create event listener
1179
1191
  const eventHandler = async (eventData) => {
@@ -1181,6 +1193,26 @@ export class StateMachinePlugin extends Plugin {
1181
1193
 
1182
1194
  for (const entity of entities) {
1183
1195
  try {
1196
+ // Resolve dynamic event name if it's a function
1197
+ let resolvedEventName;
1198
+ if (typeof baseEventName === 'function') {
1199
+ resolvedEventName = baseEventName(entity.context);
1200
+ } else {
1201
+ resolvedEventName = baseEventName;
1202
+ }
1203
+
1204
+ // Skip if event name doesn't match (for dynamic event names)
1205
+ // This allows filtering events by entity context
1206
+ if (eventSource && typeof baseEventName === 'function') {
1207
+ // For resource-specific events with dynamic names, we need to check
1208
+ // if this specific event matches this entity
1209
+ // The eventData will contain the ID that was part of the event name
1210
+ const eventIdMatch = eventData?.id || eventData?.entityId;
1211
+ if (eventIdMatch && entity.entityId !== eventIdMatch) {
1212
+ continue; // Not for this entity
1213
+ }
1214
+ }
1215
+
1184
1216
  // Check condition if provided
1185
1217
  if (trigger.condition) {
1186
1218
  const shouldTrigger = await trigger.condition(entity.context, entity.entityId, eventData);
@@ -1198,33 +1230,92 @@ export class StateMachinePlugin extends Plugin {
1198
1230
  }
1199
1231
  }
1200
1232
 
1201
- // Execute trigger action with event data in context
1202
- const result = await this._executeAction(
1203
- trigger.action,
1204
- { ...entity.context, eventData },
1205
- 'TRIGGER',
1206
- machineId,
1207
- entity.entityId
1208
- );
1233
+ // NEW: Support targetState for automatic transitions
1234
+ if (trigger.targetState) {
1235
+ // Automatic transition to target state
1236
+ await this._transition(
1237
+ machineId,
1238
+ entity.entityId,
1239
+ stateName,
1240
+ trigger.targetState,
1241
+ 'TRIGGER',
1242
+ { ...entity.context, eventData, triggerName }
1243
+ );
1209
1244
 
1210
- await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
1245
+ // Update resource's stateField if configured
1246
+ const machine = this.machines.get(machineId);
1247
+ const resourceConfig = machine.config;
1248
+ if (resourceConfig.resource && resourceConfig.stateField) {
1249
+ // Get the resource instance
1250
+ let resource;
1251
+ if (typeof resourceConfig.resource === 'string') {
1252
+ resource = await this.database.getResource(resourceConfig.resource);
1253
+ } else {
1254
+ resource = resourceConfig.resource;
1255
+ }
1211
1256
 
1212
- // Send success event if configured
1213
- if (trigger.sendEvent) {
1214
- await this.send(machineId, entity.entityId, trigger.sendEvent, {
1215
- ...entity.context,
1216
- triggerResult: result,
1217
- eventData
1257
+ // Update the state field in the resource
1258
+ if (resource) {
1259
+ const [ok] = await tryFn(() =>
1260
+ resource.patch(entity.entityId, { [resourceConfig.stateField]: trigger.targetState })
1261
+ );
1262
+ if (!ok && this.config.verbose) {
1263
+ console.warn(`[StateMachinePlugin] Failed to update resource stateField for entity ${entity.entityId}`);
1264
+ }
1265
+ }
1266
+ }
1267
+
1268
+ // Execute entry action of target state if exists
1269
+ const targetStateConfig = machine.config.states[trigger.targetState];
1270
+ if (targetStateConfig?.entry) {
1271
+ await this._executeAction(
1272
+ targetStateConfig.entry,
1273
+ { ...entity.context, eventData },
1274
+ 'TRIGGER',
1275
+ machineId,
1276
+ entity.entityId
1277
+ );
1278
+ }
1279
+
1280
+ // Emit transition event
1281
+ this.emit('plg:state-machine:transition', {
1282
+ machineId,
1283
+ entityId: entity.entityId,
1284
+ from: stateName,
1285
+ to: trigger.targetState,
1286
+ event: 'TRIGGER',
1287
+ context: { ...entity.context, eventData, triggerName }
1218
1288
  });
1289
+ } else if (trigger.action) {
1290
+ // Execute trigger action with event data in context
1291
+ const result = await this._executeAction(
1292
+ trigger.action,
1293
+ { ...entity.context, eventData },
1294
+ 'TRIGGER',
1295
+ machineId,
1296
+ entity.entityId
1297
+ );
1298
+
1299
+ // Send success event if configured
1300
+ if (trigger.sendEvent) {
1301
+ await this.send(machineId, entity.entityId, trigger.sendEvent, {
1302
+ ...entity.context,
1303
+ triggerResult: result,
1304
+ eventData
1305
+ });
1306
+ }
1219
1307
  }
1220
1308
 
1309
+ await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
1310
+
1221
1311
  this.emit('plg:state-machine:trigger-executed', {
1222
1312
  machineId,
1223
1313
  entityId: entity.entityId,
1224
1314
  state: stateName,
1225
1315
  trigger: triggerName,
1226
1316
  type: 'event',
1227
- eventName
1317
+ eventName: resolvedEventName,
1318
+ targetState: trigger.targetState
1228
1319
  });
1229
1320
  } catch (error) {
1230
1321
  if (this.config.verbose) {
@@ -1234,20 +1325,35 @@ export class StateMachinePlugin extends Plugin {
1234
1325
  }
1235
1326
  };
1236
1327
 
1237
- // Listen to database events if eventName starts with 'db:'
1238
- if (eventName.startsWith('db:')) {
1239
- const dbEventName = eventName.substring(3); // Remove 'db:' prefix
1240
- this.database.on(dbEventName, eventHandler);
1328
+ // NEW: Support eventSource for resource-specific events
1329
+ if (eventSource) {
1330
+ // Listen to events from a specific resource
1331
+ // Resource events are typically: inserted, updated, deleted
1332
+ const baseEvent = typeof baseEventName === 'function' ? 'updated' : baseEventName;
1333
+
1334
+ eventSource.on(baseEvent, eventHandler);
1241
1335
 
1242
1336
  if (this.config.verbose) {
1243
- console.log(`[StateMachinePlugin] Listening to database event '${dbEventName}' for trigger '${triggerName}'`);
1337
+ console.log(`[StateMachinePlugin] Listening to resource event '${baseEvent}' from '${eventSource.name}' for trigger '${triggerName}'`);
1244
1338
  }
1245
1339
  } else {
1246
- // Listen to plugin events
1247
- this.on(eventName, eventHandler);
1340
+ // Original behavior: listen to database or plugin events
1341
+ const staticEventName = typeof baseEventName === 'function' ? 'updated' : baseEventName;
1248
1342
 
1249
- if (this.config.verbose) {
1250
- console.log(`[StateMachinePlugin] Listening to plugin event '${eventName}' for trigger '${triggerName}'`);
1343
+ if (staticEventName.startsWith('db:')) {
1344
+ const dbEventName = staticEventName.substring(3); // Remove 'db:' prefix
1345
+ this.database.on(dbEventName, eventHandler);
1346
+
1347
+ if (this.config.verbose) {
1348
+ console.log(`[StateMachinePlugin] Listening to database event '${dbEventName}' for trigger '${triggerName}'`);
1349
+ }
1350
+ } else {
1351
+ // Listen to plugin events
1352
+ this.on(staticEventName, eventHandler);
1353
+
1354
+ if (this.config.verbose) {
1355
+ console.log(`[StateMachinePlugin] Listening to plugin event '${staticEventName}' for trigger '${triggerName}'`);
1356
+ }
1251
1357
  }
1252
1358
  }
1253
1359
  }