data-primals-engine 1.7.1 → 1.7.3

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.
Files changed (99) hide show
  1. package/README.md +9 -9
  2. package/client/package-lock.json +8430 -8121
  3. package/client/package.json +7 -4
  4. package/client/src/APIInfo.jsx +1 -1
  5. package/client/src/App.jsx +2 -1
  6. package/client/src/App.scss +1635 -1626
  7. package/client/src/AssistantChat.jsx +2 -3
  8. package/client/src/CalendarView.jsx +1 -0
  9. package/client/src/ContentView.jsx +3 -3
  10. package/client/src/Dashboard.jsx +5 -2
  11. package/client/src/DashboardChart.jsx +1 -0
  12. package/client/src/DashboardFlexViewItem.jsx +1 -0
  13. package/client/src/DashboardHtmlViewItem.jsx +1 -0
  14. package/client/src/DashboardView.jsx +2 -0
  15. package/client/src/DataEditor.jsx +0 -1
  16. package/client/src/DataImporter.jsx +489 -468
  17. package/client/src/DataLayout.jsx +25 -23
  18. package/client/src/DataTable.jsx +6 -5
  19. package/client/src/Dialog.jsx +92 -90
  20. package/client/src/Dialog.scss +122 -116
  21. package/client/src/DisplayFlexNodeRenderer.jsx +1 -0
  22. package/client/src/DocumentationPageLayout.scss +1 -1
  23. package/client/src/FlexBuilderControls.jsx +1 -0
  24. package/client/src/FlexBuilderPreview.jsx +1 -1
  25. package/client/src/FlexNode.jsx +1 -1
  26. package/client/src/HistoryDialog.jsx +3 -2
  27. package/client/src/KPIWidget.jsx +1 -1
  28. package/client/src/KanbanView.jsx +1 -0
  29. package/client/src/ModelCreator.jsx +4 -0
  30. package/client/src/ModelList.jsx +1 -0
  31. package/client/src/PackGallery.jsx +5 -4
  32. package/client/src/RTETrans.jsx +1 -1
  33. package/client/src/RelationField.jsx +2 -0
  34. package/client/src/RelationSelectorWidget.jsx +2 -0
  35. package/client/src/RelationValue.jsx +1 -0
  36. package/client/src/RestoreDialog.jsx +1 -0
  37. package/client/src/ViewSwitcher.jsx +1 -1
  38. package/client/src/WorkflowEditor.jsx +3 -0
  39. package/client/src/contexts/CommandContext.jsx +2 -1
  40. package/client/src/contexts/ModelContext.jsx +3 -3
  41. package/client/src/hooks/data.js +1 -0
  42. package/client/src/hooks/useValidation.js +1 -0
  43. package/client/src/translations.js +24 -24
  44. package/client/vite.config.js +31 -30
  45. package/doc/AI-assistance.md +87 -63
  46. package/doc/Concepts.md +122 -0
  47. package/doc/Custom-Endpoints.md +31 -0
  48. package/doc/Event-system.md +13 -14
  49. package/doc/Home.md +33 -0
  50. package/doc/Modules.md +83 -0
  51. package/doc/Packs-gallery.md +8 -23
  52. package/doc/Workflows.md +32 -0
  53. package/doc/automation-workflows.md +141 -102
  54. package/doc/dashboards-kpis-charts.md +47 -49
  55. package/doc/data-management.md +126 -120
  56. package/doc/data-models.md +68 -75
  57. package/doc/roles-permissions.md +144 -43
  58. package/doc/sharding-replication.md +158 -0
  59. package/doc/users.md +54 -30
  60. package/package.json +27 -17
  61. package/server.js +37 -37
  62. package/src/ai.jobs.js +135 -0
  63. package/src/constants.js +560 -545
  64. package/src/data.js +521 -518
  65. package/src/email.js +157 -154
  66. package/src/engine.js +167 -49
  67. package/src/gameObject.js +6 -0
  68. package/src/i18n.js +0 -1
  69. package/src/modules/assistant/assistant.js +782 -763
  70. package/src/modules/assistant/constants.js +23 -16
  71. package/src/modules/assistant/providers.js +77 -37
  72. package/src/modules/auth-google/index.js +53 -50
  73. package/src/modules/bucket.js +346 -335
  74. package/src/modules/data/data.backup.js +400 -376
  75. package/src/modules/data/data.cluster.js +559 -0
  76. package/src/modules/data/data.core.js +11 -8
  77. package/src/modules/data/data.js +311 -311
  78. package/src/modules/data/data.operations.js +3666 -3555
  79. package/src/modules/data/data.relations.js +1 -0
  80. package/src/modules/data/data.replication.js +125 -0
  81. package/src/modules/data/data.routes.js +2206 -1879
  82. package/src/modules/data/data.scheduling.js +2 -1
  83. package/src/modules/file.js +248 -247
  84. package/src/modules/mongodb.js +76 -73
  85. package/src/modules/user.js +18 -2
  86. package/src/modules/worker-script-runner.js +97 -0
  87. package/src/modules/workflow.js +177 -52
  88. package/src/packs.js +5701 -5701
  89. package/src/providers.js +298 -297
  90. package/src/sso.js +91 -25
  91. package/test/assistant.test.js +207 -206
  92. package/test/cluster.test.js +221 -0
  93. package/test/core.test.js +0 -2
  94. package/test/data.integration.test.js +1425 -1416
  95. package/test/import_export.integration.test.js +210 -210
  96. package/test/replication.test.js +163 -0
  97. package/test/workflow.actions.integration.test.js +487 -475
  98. package/test/workflow.integration.test.js +332 -329
  99. package/doc/core-concepts.md +0 -33
@@ -1,73 +1,76 @@
1
-
2
- import {Logger} from "../gameObject.js";
3
- import {MongoDatabase} from "../engine.js";
4
- import {ObjectId} from "mongodb";
5
- import {isLocalUser} from "../data.js";
6
- import {Event} from "../events.js";
7
- import {Config} from "../config.js";
8
-
9
- export let modelsCollection, datasCollection, filesCollection, packsCollection;
10
-
11
- export {ObjectId};
12
-
13
- let engine, logger, currentDb
14
-
15
- let colls= [];
16
- export async function onInit(defaultEngine) {
17
- engine = defaultEngine;
18
- logger = engine.getComponent(Logger);
19
-
20
- currentDb = MongoDatabase();
21
- modelsCollection = getCollection("models");
22
- datasCollection = getCollection(Config.Get('dataCollection', 'datas'));
23
- filesCollection = getCollection("files");
24
- packsCollection = getCollection("packs");
25
-
26
- colls = await currentDb.listCollections().toArray();
27
-
28
- await Event.Trigger("OnDatabaseLoaded", "system", "calls", engine)
29
- logger.info("MongoDB collections loaded.");
30
- }
31
-
32
- export const getCollections= async (forceRefresh)=>{
33
- if( !forceRefresh )
34
- return colls;
35
- colls = await currentDb.listCollections().toArray();
36
- }
37
-
38
- export const createCollection = async (coll)=>{
39
- const found =colls.find(f => f.name === coll);
40
- if( found){
41
- return getCollection(coll);
42
- }
43
- return await currentDb.createCollection(coll);
44
- }
45
-
46
- export const isObjectId = (id) => {
47
- return (typeof(id) === 'string' && id.match(/^[0-9a-fA-F]{24}$/));
48
- };
49
-
50
-
51
- export const getCollection = (str) => {
52
- return currentDb.collection(str);
53
- }
54
-
55
- export const getDatabase = () => {
56
- return currentDb;
57
- }
58
-
59
- // New function to determine the collection name for a user
60
- export const getUserCollectionName = async (user) => {
61
- const feat = await engine.userProvider.hasFeature(user, 'indexes');
62
- const dataCollectionName = Config.Get('dataCollection', 'datas');
63
- return feat ? (isLocalUser(user) ? `${dataCollectionName}_${user._user}` :`${dataCollectionName}_${user.username}` ) : dataCollectionName;
64
- };
65
-
66
-
67
- // Modify existing functions to use the correct collection
68
- export const getCollectionForUser = async (user) => {
69
- const collectionName = await getUserCollectionName(user);
70
- return getCollection(collectionName);
71
- };
72
-
73
-
1
+
2
+ import {Logger} from "../gameObject.js";
3
+ import {MongoDatabase} from "../engine.js";
4
+ import {ObjectId} from "mongodb";
5
+ import {isLocalUser} from "../data.js";
6
+ import {Event} from "../events.js";
7
+ import {Config} from "../config.js";
8
+
9
+ export let modelsCollection, datasCollection, filesCollection, packsCollection;
10
+
11
+ export {ObjectId};
12
+
13
+ let engine, logger, currentDb
14
+
15
+ let colls= [];
16
+ export async function onInit(defaultEngine) {
17
+ engine = defaultEngine;
18
+ logger = engine.getComponent(Logger);
19
+
20
+ currentDb = MongoDatabase();
21
+ modelsCollection = getCollection("models");
22
+ datasCollection = getCollection(Config.Get('dataCollection', 'datas'));
23
+ filesCollection = getCollection("files");
24
+ packsCollection = getCollection("packs");
25
+
26
+
27
+ colls = await (currentDb.listCollections()).toArray();
28
+
29
+ await Event.Trigger("OnDatabaseLoaded", "system", "calls", engine)
30
+ logger.info("MongoDB collections loaded.");
31
+ }
32
+
33
+ export const getCollections= async (forceRefresh)=>{
34
+ if( !forceRefresh )
35
+ return colls;
36
+ colls = await currentDb.listCollections().toArray();
37
+ }
38
+
39
+ export const createCollection = async (coll)=>{
40
+ const found =colls.find(f => f.name === coll);
41
+ if( found){
42
+ return getCollection(coll);
43
+ }
44
+ return await currentDb.createCollection(coll);
45
+ }
46
+
47
+ export const isObjectId = (id) => {
48
+ return (typeof(id) === 'string' && id.match(/^[0-9a-fA-F]{24}$/));
49
+ };
50
+
51
+
52
+ export const getCollection = (str) => {
53
+ if (typeof str !== 'string') {
54
+ logger?.error(`[MongoDB] Tentative d'accès à une collection avec un type invalide: ${typeof str}`);
55
+ throw new Error("Le nom de la collection doit être une chaîne de caractères.");
56
+ }
57
+ return currentDb.collection(str);
58
+ }
59
+
60
+ export const getDatabase = () => {
61
+ return currentDb;
62
+ }
63
+
64
+ // New function to determine the collection name for a user
65
+ export const getUserCollectionName = async (user) => {
66
+ const feat = await engine.userProvider.hasFeature(user, 'indexes');
67
+ const dataCollectionName = Config.Get('dataCollection', 'datas');
68
+ return feat ? (isLocalUser(user) ? `${dataCollectionName}_${user._user}` :`${dataCollectionName}_${user.username}` ) : dataCollectionName;
69
+ };
70
+
71
+
72
+ // Modify existing functions to use the correct collection
73
+ export const getCollectionForUser = async (user) => {
74
+ const collectionName = await getUserCollectionName(user);
75
+ return getCollection(collectionName);
76
+ };
@@ -68,7 +68,8 @@ export const middlewareAuthenticator = async (req, res, next) => {
68
68
  }
69
69
 
70
70
  try {
71
- // 1. On demande au provider (votre PrimalsUserProvider) d'identifier l'utilisateur
71
+ const logger = engine.getComponent(Logger);
72
+ // 1. On demande au provider d'identifier l'utilisateur
72
73
  await engine.userProvider.initiateUser(req);
73
74
 
74
75
  // 2. On vérifie simplement si le provider a attaché un utilisateur
@@ -344,6 +345,15 @@ export async function getEnv(user){
344
345
  return envObject;
345
346
  }
346
347
 
348
+ export function getInternalSmtpConfig(){
349
+ const cfg = Config.Get('emailDefaultConfig', emailDefaultConfig);
350
+ cfg.host = process.env.SMTP_HOST || cfg.host;
351
+ cfg.port = process.env.SMTP_PORT || cfg.port;
352
+ cfg.user = process.env.SMTP_USER || cfg.user;
353
+ cfg.pass = process.env.SMTP_PASS || cfg.pass;
354
+ return cfg;
355
+ }
356
+
347
357
  export async function getSmtpConfig(user) {
348
358
 
349
359
  const cfg = Config.Get('emailDefaultConfig', emailDefaultConfig);
@@ -358,8 +368,14 @@ export async function getSmtpConfig(user) {
358
368
  acc[variable.name.replace('SMTP_', '').toLowerCase()] = variable.value;
359
369
  return acc;
360
370
  }, {});
371
+
361
372
  if( !smtpConfig.port )
362
373
  smtpConfig.port = cfg.port;
363
-
374
+ if( !smtpConfig.host )
375
+ smtpConfig.host = cfg.host;
376
+ if( !smtpConfig.user )
377
+ smtpConfig.user = cfg.user;
378
+ if( !smtpConfig.pass )
379
+ smtpConfig.pass = cfg.pass;
364
380
  return smtpConfig;
365
381
  }
@@ -0,0 +1,97 @@
1
+ import { parentPort } from 'worker_threads';
2
+
3
+ let callIdCounter = 0;
4
+ const pendingCalls = new Map();
5
+
6
+ /**
7
+ * Appelle une fonction sur le thread principal et attend la réponse.
8
+ * @param {string} service - Le nom du service (ex: 'db').
9
+ * @param {string} method - Le nom de la méthode (ex: 'find').
10
+ * @param {...any} args - Les arguments de la méthode.
11
+ * @returns {Promise<any>}
12
+ */
13
+ function callHost(service, method, ...args) {
14
+ return new Promise((resolve, reject) => {
15
+ const callId = callIdCounter++;
16
+ pendingCalls.set(callId, { resolve, reject });
17
+ parentPort.postMessage({ type: 'call', callId, service, method, args });
18
+ });
19
+ }
20
+
21
+ // API exposée au script de l'utilisateur
22
+ const db = {
23
+ create: (model, data) => callHost('db', 'create', model, data),
24
+ find: (model, filter, options) => callHost('db', 'find', model, filter, options),
25
+ findOne: (model, filter) => callHost('db', 'findOne', model, filter),
26
+ update: (model, filter, update) => callHost('db', 'update', model, filter, update),
27
+ delete: (model, filter) => callHost('db', 'delete', model, filter),
28
+ };
29
+
30
+ const workflow = {
31
+ run: (name, context) => callHost('workflow', 'run', name, context),
32
+ };
33
+
34
+ const logger = {
35
+ info: (...args) => callHost('log', 'info', ...args),
36
+ warn: (...args) => callHost('log', 'warn', ...args),
37
+ error: (...args) => callHost('log', 'error', ...args),
38
+ };
39
+
40
+ const env = {
41
+ get: (name) => callHost('env', 'get', name),
42
+ getAll: () => callHost('env', 'getAll'),
43
+ };
44
+
45
+ const http = {
46
+ request: (method, url, options) => callHost('http', 'request', method, url, options),
47
+ };
48
+
49
+ /**
50
+ * Exécute le code utilisateur dans un scope sécurisé.
51
+ * @param {string} code - Le code à exécuter.
52
+ * @param {object} context - Le contexte du workflow.
53
+ */
54
+ async function runUserCode(code, context) {
55
+ // Crée une fonction asynchrone avec le code utilisateur.
56
+ // Les variables `db`, `logger`, `context`, etc., sont disponibles dans son scope.
57
+ const userFunction = new Function('db', 'workflow', 'logger', 'env', 'http', 'context', `return (async () => { ${code} })();`);
58
+
59
+ // Appelle la fonction avec l'API sandboxed.
60
+ return userFunction(db, workflow, logger, env, http, context);
61
+ }
62
+
63
+ // Gestionnaire de messages unique pour le worker
64
+ parentPort.on('message', (msg) => {
65
+ // Message initial pour démarrer l'exécution
66
+ if (msg.code) {
67
+ // Le worker ne doit pas se terminer tant que le code n'est pas fini.
68
+ parentPort.ref();
69
+ runUserCode(msg.code, msg.context)
70
+ .then(result => {
71
+ parentPort.postMessage({ type: 'done', result });
72
+ })
73
+ .catch(e => {
74
+ parentPort.postMessage({ type: 'error', error: e.message });
75
+ })
76
+ .finally(() => {
77
+ // Une fois terminé, le worker peut se fermer.
78
+ parentPort.unref();
79
+ });
80
+ }
81
+ // Message de réponse à un appel `callHost`
82
+ else if (msg.type === 'response') {
83
+ const { callId, result, error } = msg;
84
+ if (pendingCalls.has(callId)) {
85
+ const { resolve, reject } = pendingCalls.get(callId);
86
+ if (error) {
87
+ reject(new Error(error));
88
+ } else {
89
+ resolve(result);
90
+ }
91
+ pendingCalls.delete(callId);
92
+ }
93
+ } else if (msg.type === 'call') {
94
+ // Ce cas ne devrait pas arriver (le worker envoie 'call', il ne le reçoit pas),
95
+ // mais on le laisse pour la robustesse.
96
+ }
97
+ });
@@ -1,8 +1,7 @@
1
1
  import {getCollection, getCollectionForUser, isObjectId} from "./mongodb.js";
2
2
  import schedule from "node-schedule";
3
3
  import {ObjectId} from "mongodb";
4
-
5
- import ivm from 'isolated-vm';
4
+ import { Worker } from 'worker_threads';
6
5
 
7
6
  import {Logger} from "../gameObject.js";
8
7
  import {deleteData, getModel, insertData, patchData, searchData} from "./data/index.js";
@@ -19,7 +18,22 @@ import { providers } from "./assistant/constants.js";
19
18
  import { getAIProvider } from "./assistant/providers.js";
20
19
  import {Config} from "../config.js";
21
20
  import {safeAssignObject} from "../core.js";
22
- import {Event} from "../events.js";
21
+
22
+ // Add Node.js path utilities for worker_threads
23
+ import { fileURLToPath, URL } from 'node:url';
24
+ import path from "node:path";
25
+ let executionEngine = { type: null, module: null };
26
+ try {
27
+ // Tentative de chargement de 'isolated-vm'.
28
+ // Si cela échoue (ex: incompatibilité d'architecture, build manquant),
29
+ // le serveur pourra quand même démarrer.
30
+
31
+ const ivm = (await import(/* @vite-ignore */ 'isolated-vm')).default;
32
+ executionEngine = { type: 'isolated-vm', module: ivm };
33
+ } catch (e) {
34
+ executionEngine = { type: 'worker_threads', module: null };
35
+ console.warn(`[Module] Le module 'isolated-vm' n'a pas pu être chargé. L'action de workflow 'ExecuteScript' sera désactivée. Erreur : ${e.message}`);
36
+ }
23
37
 
24
38
  /**
25
39
  * Récupère une valeur imbriquée dans un objet (ex: 'user.address.city').
@@ -383,7 +397,103 @@ async function handleWaitAction(actionDef, contextData, user) {
383
397
  }
384
398
 
385
399
 
386
- export async function executeSafeJavascript(actionDef, context, user) {
400
+ async function executeWithWorkerThreads(actionDef, context, user) {
401
+ const TIMEOUT = 5000;
402
+ const collectedLogs = [];
403
+ const code = actionDef.script;
404
+ // Contexte sécurisé
405
+ const safeContext = JSON.parse(JSON.stringify(context));
406
+
407
+ return new Promise((resolve, reject) => {
408
+ // Construct an absolute file path for the worker. This is the most reliable method,
409
+ // ensuring Node.js loads the script directly without ambiguity.
410
+ const workerPath = path.dirname('src/workers/worker-script-runner.js');
411
+
412
+ const worker = new Worker(workerPath);
413
+
414
+ const timeoutId = setTimeout(() => {
415
+ worker.terminate();
416
+ const errorMessage = `Script execution timed out after ${TIMEOUT}ms.`;
417
+ collectedLogs.push({level: 'critical', message: errorMessage, timestamp: new Date().toISOString()});
418
+ resolve({success: false, message: errorMessage, logs: collectedLogs});
419
+ }, TIMEOUT);
420
+
421
+ worker.on('message', async (msg) => {
422
+ if (msg.type === 'call') {
423
+ const {callId, service, method, args} = msg;
424
+ try {
425
+ let result;
426
+ // Recréez ici la logique d'appel des fonctions natives
427
+ // C'est un pont entre le worker et votre application principale.
428
+ if (service === 'db') {
429
+ if (method === 'find') result = await searchData({
430
+ model: args[0],
431
+ filter: args[1], ...args[2]
432
+ }, user);
433
+ else if (method === 'findOne') result = (await searchData({
434
+ model: args[0],
435
+ filter: args[1],
436
+ limit: 1
437
+ }, user))?.data?.[0] || null;
438
+ else if (method === 'create') result = await insertData(args[0], args[1], {}, user, false);
439
+ else if (method === 'update') result = await patchData(args[0], args[1], args[2], {}, user, false);
440
+ else if (method === 'delete') result = await deleteData(args[0], args[1], user, false);
441
+ } else if (service === 'workflow') {
442
+ if (method === 'run') result = await runWorkflowByName(args[0], args[1], user);
443
+ } else if (service === 'log') {
444
+ const message = args.join(' ');
445
+ collectedLogs.push({level: method, message, timestamp: new Date().toISOString()});
446
+ logger.trace(`[Worker Script Log - ${method}]`, message);
447
+ result = {success: true};
448
+ } else if (service === 'env') {
449
+ if (method === 'get') result = (await searchData({
450
+ model: 'env',
451
+ filter: {name: args[0]},
452
+ limit: 1
453
+ }, user))?.data?.[0]?.value || null;
454
+ else if (method === 'getAll') result = await getEnv(user);
455
+ } else if (service === 'http') {
456
+ const [methodHttp, url, options] = args;
457
+ const fetchOptions = {
458
+ method: methodHttp.toUpperCase(),
459
+ headers: options.headers || {},
460
+ body: options.body ? (typeof options.body === 'object' ? JSON.stringify(options.body) : options.body) : undefined
461
+ };
462
+ const response = await fetch(url, fetchOptions);
463
+ const responseBody = await response.json().catch(() => response.text());
464
+ result = {success: response.ok, status: response.status, body: responseBody};
465
+ }
466
+
467
+ worker.postMessage({type: 'response', callId, result});
468
+ } catch (error) {
469
+ worker.postMessage({type: 'response', callId, error: error.message});
470
+ }
471
+ } else if (msg.type === 'done') {
472
+ clearTimeout(timeoutId);
473
+ worker.terminate();
474
+ resolve({success: true, data: msg.result, logs: collectedLogs, updatedContext: {result: msg.result}});
475
+ } else if (msg.type === 'error') {
476
+ clearTimeout(timeoutId);
477
+ worker.terminate();
478
+ const errorMessage = `Script execution failed: ${msg.error}`;
479
+ collectedLogs.push({level: 'critical', message: errorMessage, timestamp: new Date().toISOString()});
480
+ resolve({success: false, message: errorMessage, logs: collectedLogs});
481
+ }
482
+ });
483
+
484
+ worker.on('error', (err) => {
485
+ clearTimeout(timeoutId);
486
+ const errorMessage = `Worker error: ${err.message}`;
487
+ collectedLogs.push({level: 'critical', message: errorMessage, timestamp: new Date().toISOString()});
488
+ resolve({success: false, message: errorMessage, logs: collectedLogs});
489
+ });
490
+
491
+ worker.postMessage({code, context: safeContext});
492
+ });
493
+ }
494
+
495
+ async function executeWithIsolatedVm(actionDef, context, user) {
496
+ const ivm = executionEngine.module;
387
497
  const code = actionDef.script;
388
498
  const collectedLogs = [];
389
499
  const isolate = new ivm.Isolate({ memoryLimit: 128 }); // 128MB memory limit
@@ -477,43 +587,43 @@ export async function executeSafeJavascript(actionDef, context, user) {
477
587
 
478
588
  // Exécution
479
589
  const fullScript = `
480
- const normalizeArgs = args => args.map(arg => {
481
- if (typeof arg === 'object' && arg !== null) {
482
- return JSON.stringify(arg); // Convert objects to strings
483
- }
484
- return arg;
485
- });
486
- const db = {
487
- create: (...args) => _db_create.applySyncPromise(null, normalizeArgs(args)),
488
- find: (...args) => _db_find.applySyncPromise(null, normalizeArgs(args)),
489
- findOne: (...args) => _db_findOne.applySyncPromise(null, normalizeArgs(args)),
490
- update: (...args) => _db_update.applySyncPromise(null, normalizeArgs(args)),
491
- delete: (...args) => _db_delete.applySyncPromise(null, normalizeArgs(args))
492
- };
493
-
494
- const workflow = {
495
- run: (...args) => _workflow_run.applySyncPromise(null, normalizeArgs(args))
496
- };
497
-
498
- const logger = {
499
- info: _log_info,
500
- warn: _log_warn,
501
- error: _log_error
502
- };
503
-
504
- const env = {
505
- get: _env_get,
506
- getAll: _env_get_all
507
- };
508
-
509
- const http = {
510
- request: (...args) => _http_request.applySyncPromise(null, normalizeArgs(args))
511
- };
512
-
513
- (async function() {
514
- ${code}
515
- })();
516
- `;
590
+ const normalizeArgs = args => args.map(arg => {
591
+ if (typeof arg === 'object' && arg !== null) {
592
+ return JSON.stringify(arg); // Convert objects to strings
593
+ }
594
+ return arg;
595
+ });
596
+ const db = {
597
+ create: (...args) => _db_create.applySyncPromise(null, normalizeArgs(args)),
598
+ find: (...args) => _db_find.applySyncPromise(null, normalizeArgs(args)),
599
+ findOne: (...args) => _db_findOne.applySyncPromise(null, normalizeArgs(args)),
600
+ update: (...args) => _db_update.applySyncPromise(null, normalizeArgs(args)),
601
+ delete: (...args) => _db_delete.applySyncPromise(null, normalizeArgs(args))
602
+ };
603
+
604
+ const workflow = {
605
+ run: (...args) => _workflow_run.applySyncPromise(null, normalizeArgs(args))
606
+ };
607
+
608
+ const logger = {
609
+ info: _log_info,
610
+ warn: _log_warn,
611
+ error: _log_error
612
+ };
613
+
614
+ const env = {
615
+ get: _env_get,
616
+ getAll: _env_get_all
617
+ };
618
+
619
+ const http = {
620
+ request: (...args) => _http_request.applySyncPromise(null, normalizeArgs(args))
621
+ };
622
+
623
+ (async function() {
624
+ ${code}
625
+ })();
626
+ `;
517
627
 
518
628
  const TIMEOUT = 5000;
519
629
  const script = await isolate.compileScript(fullScript, { timeout: TIMEOUT });
@@ -556,6 +666,22 @@ export async function executeSafeJavascript(actionDef, context, user) {
556
666
  }
557
667
  }
558
668
 
669
+ export async function executeSafeJavascript(actionDef, context, user) {
670
+ // Vérifie si un moteur d'exécution est disponible.
671
+ if (!executionEngine.type) {
672
+ const errorMessage = "L'action 'ExecuteScript' est désactivée car aucun moteur d'exécution (isolated-vm ou fallback) n'a pu être initialisé.";
673
+ logger.error(`[Script Engine] ${errorMessage}`);
674
+ return { success: false, message: errorMessage, logs: [{ level: 'critical', message: errorMessage, timestamp: new Date().toISOString() }] };
675
+ }
676
+
677
+ logger.info(`[Script Engine] Using '${executionEngine.type}' for script execution.`);
678
+
679
+ if (executionEngine.type === 'isolated-vm') {
680
+ return executeWithIsolatedVm(actionDef, context, user);
681
+ } else { // 'worker_threads'
682
+ return executeWithWorkerThreads(actionDef, context, user);
683
+ }
684
+ }
559
685
  /**
560
686
  * Handles the 'HttpRequest' workflow action.
561
687
  * Sends an HTTP request to a specified URL with substituted data using native fetch.
@@ -567,18 +693,17 @@ export async function executeSafeJavascript(actionDef, context, user) {
567
693
  * @returns {Promise<{success: boolean, message?: string, responseStatus?: number, responseBody?: any}>} - Result of the action.
568
694
  */
569
695
  async function handleHttpRequestAction(actionDef, contextData, user, dbCollection) {
570
- const { name: actionName, _id: actionId, url, method = 'POST', headers: headersTemplate, body: bodyTemplate } = actionDef;
571
-
572
- // 1. Basic Validation
573
- if (!url) {
574
- const msg = `[handleHttpRequestAction] Action ${actionName} (${actionId}): Missing 'url'.`;
575
- logger.error(msg);
576
- return { success: false, message: msg };
577
- }
578
-
579
- logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Executing webhook. Method: ${method}`);
580
696
 
581
697
  try {
698
+ const {
699
+ method = 'GET',
700
+ url,
701
+ headers: headersTemplate,
702
+ body: bodyTemplate,
703
+ name: actionName = 'Unnamed Action',
704
+ _id: actionId = 'unknown'
705
+ } = actionDef;
706
+
582
707
  // 2. Substitute Variables
583
708
  const substitutedUrl = await substituteVariables(url, contextData, user);
584
709
  let substitutedHeadersString;
@@ -1423,7 +1548,7 @@ export async function processWorkflowRun(workflowRunId, user) {
1423
1548
  const mw = Config.Get('maxWorkflowSteps', maxWorkflowSteps);
1424
1549
  while (currentStepId) {
1425
1550
  if (stepCount++ >= mw) {
1426
- return await logError(`Maximum workflow step executions exceeded (${maxWorkflowSteps} max).`);
1551
+ return await logError(`Maximum workflow step executions exceeded (${mw} max).`);
1427
1552
  }
1428
1553
 
1429
1554
  const execCount = (stepExecutionsCount[currentStepId] || 0) + 1;