data-primals-engine 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,10 +4,10 @@ import schedule from "node-schedule";
4
4
  import {ObjectId} from "mongodb";
5
5
  import crypto from "node:crypto";
6
6
 
7
- import {VM, VMScript} from 'vm2';
7
+ import {NodeVM, VM, VMScript} from 'vm2';
8
8
  import {Logger} from "../gameObject.js";
9
9
  import {deleteData, insertData, patchData, searchData} from "./data.js";
10
- import {maxExecutionsByStep, maxWorkflowSteps} from "../constants.js";
10
+ import {maxExecutionsByStep, maxWorkflowSteps, timeoutVM} from "../constants.js";
11
11
  import {ChatOpenAI} from "@langchain/openai";
12
12
  import {ChatGoogleGenerativeAI} from "@langchain/google-genai";
13
13
  import {ChatPromptTemplate} from "@langchain/core/prompts";
@@ -224,25 +224,100 @@ async function executeWithIsolatedVm(actionDef, context) {
224
224
  }
225
225
  REQUIRES NODE >=20
226
226
  */
227
- async function executeSafeJavascript(actionDef, context) {
227
+
228
+ export async function executeSafeJavascript(actionDef, context, user) {
228
229
  const code = actionDef.script;
229
- const vm = new VM({
230
- timeout: 1000, // Time out after 1 second
231
- sandbox: context, // Pass the context object
232
- console: 'redirect', // Redirect console output
233
- require: false, // Disable require
234
- wasm: false // disable WebAssembly
230
+ const collectedLogs = []; // Tableau pour capturer les logs de cette exécution
231
+
232
+ // 1. Construire l'API qui sera exposée au script
233
+ const scriptApi = {
234
+ db: {
235
+ create: (modelName, dataObject) => insertData(modelName, dataObject, {}, user),
236
+ find: async (modelName, filter) => {
237
+ const result = await searchData({ user, query: { model: modelName, filter } });
238
+ return result.data;
239
+ },
240
+ findOne: async (modelName, filter) => {
241
+ const result = await searchData({ user, query: { model: modelName, filter, limit: 1 } });
242
+ return result.data?.[0] || null;
243
+ },
244
+ update: (modelName, filter, updateObject) => patchData(modelName, filter, updateObject, {}, user),
245
+ delete: (modelName, filter) => deleteData(modelName, null, filter, user)
246
+ },
247
+ logger: {
248
+ // Chaque fonction de log utilise maintenant logger.trace() pour générer le message final
249
+ info: (...args) => {
250
+ // On génère le message final formaté une seule fois avec trace()
251
+ const finalMessage = logger.trace('info', '[VM Script]', ...args);
252
+ // On l'affiche en couleur dans la console du serveur
253
+ // On stocke ce même message final dans les logs collectés
254
+ collectedLogs.push({
255
+ level: 'info',
256
+ message: finalMessage, // Le message complet est stocké
257
+ timestamp: new Date().toISOString()
258
+ });
259
+ },
260
+ warn: (...args) => {
261
+ const finalMessage = logger.trace('warn', '[VM Script]', ...args);
262
+ collectedLogs.push({
263
+ level: 'warn',
264
+ message: finalMessage,
265
+ timestamp: new Date().toISOString()
266
+ });
267
+ },
268
+ error: (...args) => {
269
+ const finalMessage = logger.trace('error', '[VM Script]', ...args);
270
+ collectedLogs.push({
271
+ level: 'error',
272
+ message: finalMessage,
273
+ timestamp: new Date().toISOString()
274
+ });
275
+ }
276
+ },
277
+ env: {
278
+ get: async (variableName) => {
279
+ if (!variableName) return null;
280
+ const result = await searchData({ user, query: { model: 'env', filter: { name: variableName }, limit: 1 } });
281
+ return result.data?.[0]?.value || null;
282
+ },
283
+ getAll: async () => {
284
+ const result = await searchData({ user, query: { model: 'env' } });
285
+ // Retourne un objet { NOM: valeur, ... } plus pratique
286
+ return result.data.reduce((acc, v) => {
287
+ acc[v.name] = v.value;
288
+ return acc;
289
+ }, {});
290
+ }
291
+ },
292
+ ...context
293
+ };
294
+
295
+ const vm = new NodeVM({
296
+ timeout: timeoutVM,
297
+ sandbox: scriptApi,
298
+ console: 'off',
299
+ require: false,
300
+ wasm: false
235
301
  });
236
302
 
237
303
  try {
238
- const script = new VMScript(code);
239
- return vm.run(script);
304
+ const script = new VMScript(`const script = () => {${code}}; module.exports = script();`);
305
+ const result= vm.run(script);
306
+ return { success: true, data: result, logs: collectedLogs };
240
307
  } catch (error) {
241
- console.error("Error executing script:", error);
242
- throw error; // or return an error object
308
+ const errorMessage = `Script execution failed: ${error.message}`;
309
+
310
+ // On utilise aussi trace() pour l'erreur critique
311
+ const finalErrorMessage = logger.trace('critical', `[VM Script] ${errorMessage}\n${error.stack}`);
312
+ collectedLogs.push({
313
+ level: 'critical',
314
+ message: finalErrorMessage,
315
+ timestamp: new Date().toISOString()
316
+ });
317
+
318
+ return { success: false, message: errorMessage, logs: collectedLogs };
243
319
  }
244
320
  }
245
-
246
321
  /**
247
322
  * Handles the 'Webhook' workflow action.
248
323
  * Sends an HTTP request to a specified URL with substituted data using native fetch.
@@ -734,10 +809,8 @@ export async function executeStepAction(actionDef, contextData, user, dbCollecti
734
809
  result = await handleSendEmailAction(actionDef, contextData, user);
735
810
  break;
736
811
  case 'ExecuteScript':
737
- result = await executeSafeJavascript(actionDef, contextData);
812
+ result = await executeSafeJavascript(actionDef, contextData, user);
738
813
  break;
739
-
740
- // ... autres cases à venir ...
741
814
  default:
742
815
  logger.error(`[executeStepAction] Unknown action type: ${actionDef.type}`);
743
816
  return { success: false, message: `Unknown action type: ${actionDef.type}` };
@@ -1244,7 +1317,7 @@ export async function processWorkflowRun(workflowRunId, user) {
1244
1317
  );
1245
1318
 
1246
1319
  let stepSucceeded = true;
1247
- let stepError = null;
1320
+ let logInfo = null;
1248
1321
  let conditionsMet = true;
1249
1322
 
1250
1323
  try {
@@ -1266,8 +1339,10 @@ export async function processWorkflowRun(workflowRunId, user) {
1266
1339
  const actionResult = await workflowModule.executeStepAction(actionDef, contextData, user, dbCollection);
1267
1340
  if (!actionResult.success) {
1268
1341
  stepSucceeded = false;
1269
- stepError = actionResult.message || `Action ${actionDef.name || actionId} failed.`;
1342
+ logInfo = actionResult.message || `Action ${actionDef.name || actionId} failed.`;
1270
1343
  break;
1344
+ }else{
1345
+ logInfo = `Action ${actionDef.name || actionId} : ${actionResult.message}`;
1271
1346
  }
1272
1347
  if (actionResult.updatedContext) {
1273
1348
  contextData = { ...contextData, ...actionResult.updatedContext };
@@ -1281,7 +1356,7 @@ export async function processWorkflowRun(workflowRunId, user) {
1281
1356
  } catch (error) {
1282
1357
  logger.error(`[processWorkflowRun] Run ID: ${runId}, Step ID: ${currentStepId}: Error during condition/action execution: ${error.message}`);
1283
1358
  stepSucceeded = false;
1284
- stepError = error.message;
1359
+ logInfo = error.message;
1285
1360
  }
1286
1361
 
1287
1362
  // --- 9. Détermination de la prochaine étape ---
@@ -1298,13 +1373,13 @@ export async function processWorkflowRun(workflowRunId, user) {
1298
1373
  }
1299
1374
  } else {
1300
1375
  // CHEMIN ÉCHEC/BRANCHE : Une action a échoué OU les conditions n'ont pas été remplies.
1301
- const reason = stepError ? `Action failed: ${stepError}` : 'Step conditions not met.';
1376
+ const reason = logInfo ? `Action failed: ${logInfo}` : 'Step conditions not met.';
1302
1377
  logger.warn(`[processWorkflowRun] Run ID: ${runId}, Step ID: ${currentStepId}: Taking failure/branching path. Reason: ${reason}`);
1303
1378
  nextStepId = currentStepDef.onFailureStep;
1304
1379
 
1305
1380
  if (!nextStepId || !isObjectId(nextStepId)) {
1306
1381
  // Fin du workflow. Le statut est 'failed' seulement si une vraie erreur s'est produite.
1307
- finalStatusForRun = stepError ? 'failed' : 'completed';
1382
+ finalStatusForRun = logInfo ? 'failed' : 'completed';
1308
1383
  nextStepId = null;
1309
1384
  }
1310
1385
  }
@@ -1317,9 +1392,7 @@ export async function processWorkflowRun(workflowRunId, user) {
1317
1392
  updatePayload.status = finalStatusForRun;
1318
1393
  updatePayload.completedAt = new Date();
1319
1394
  updatePayload.currentStep = null;
1320
- if (finalStatusForRun === 'failed' && stepError) {
1321
- updatePayload.error = stepError;
1322
- }
1395
+ updatePayload.log = logInfo;
1323
1396
  } else {
1324
1397
  updatePayload.currentStep = currentStepId;
1325
1398
  }
@@ -723,7 +723,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
723
723
  const packId = packInsertResult.insertedId;
724
724
 
725
725
  // 3. Appeler la fonction à tester avec la nouvelle signature
726
- const result = await installPack(console, packId, currentTestUser, 'en');
726
+ const result = await installPack(packId, currentTestUser, 'en');
727
727
 
728
728
  // 4. Assertions sur le résumé de l'installation
729
729
  expect(result.success, `L'installation du pack a échoué: ${result.errors?.join('; ')}`).toBe(true);
@@ -776,7 +776,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
776
776
  const packInsertResult = await testPacksColInstance.insertOne(mockPackWithInvalidModel);
777
777
  const packId = packInsertResult.insertedId;
778
778
 
779
- const result = await installPack(console, packId, currentTestUser, 'en');
779
+ const result = await installPack(packId, currentTestUser, 'en');
780
780
 
781
781
  // Le succès global est faux s'il y a des erreurs
782
782
  expect(result.success).toBe(false);