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.
- package/README.md +2 -2
- package/client/src/App.jsx +33 -16
- package/client/src/App.scss +21 -0
- package/client/src/Dashboard.scss +5 -1
- package/client/src/DashboardFlexViewItem.jsx +1 -2
- package/client/src/DisplayFlexNodeRenderer.jsx +120 -4
- package/client/src/FlexBuilder.jsx +2 -2
- package/client/src/FlexBuilder.scss +5 -0
- package/client/src/FlexBuilderControls.jsx +201 -22
- package/client/src/FlexBuilderPreview.jsx +43 -1
- package/client/src/FlexNode.jsx +93 -5
- package/client/src/FlexTreeUtils.js +42 -11
- package/client/src/ModelList.jsx +33 -20
- package/client/src/constants.js +4 -4
- package/package.json +1 -1
- package/server.js +1 -7
- package/src/constants.js +165 -16
- package/src/defaultModels.js +49 -0
- package/src/email.js +1 -1
- package/src/engine.js +17 -0
- package/src/modules/data.js +146 -13
- package/src/modules/workflow.js +98 -25
- package/test/data.integration.test.js +2 -2
package/src/modules/workflow.js
CHANGED
|
@@ -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
|
-
|
|
227
|
+
|
|
228
|
+
export async function executeSafeJavascript(actionDef, context, user) {
|
|
228
229
|
const code = actionDef.script;
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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);
|