data-primals-engine 1.2.6 → 1.3.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.
@@ -33,7 +33,7 @@ import {
33
33
  middlewareAuthenticator,
34
34
  userInitiator
35
35
  } from "../user.js";
36
- import {assistantGlobalLimiter} from "../assistant.js";
36
+ import {assistantGlobalLimiter} from "../assistant/assistant.js";
37
37
  import {Config} from "../../config.js";
38
38
  import {processFilterPlaceholders} from "../../../client/src/filter.js";
39
39
  import {tutorialsConfig} from "../../../client/src/tutorials.js";
@@ -43,7 +43,7 @@ import {
43
43
  editData, editModel, exportData,
44
44
  getModel, getResource,
45
45
  handleCustomEndpointRequest,
46
- handleDemoInitialization, importData, insertData, installPack, loadFromDump,
46
+ handleDemoInitialization, importData, insertData, installPack, loadFromDump, middlewareEndpointAuthenticator,
47
47
  patchData, searchData, validateModelStructure
48
48
  } from "./data.js";
49
49
  import process from "node:process";
@@ -161,7 +161,7 @@ export async function registerRoutes(engine){
161
161
 
162
162
  logger = engine.getComponent(Logger);
163
163
 
164
- engine.all('/api/actions/:path', [middlewareAuthenticator, userInitiator], handleCustomEndpointRequest);
164
+ engine.all('/api/actions/:path', [middlewareEndpointAuthenticator, userInitiator], handleCustomEndpointRequest);
165
165
  engine.post('/api/demo/initialize', [middlewareAuthenticator, userInitiator], handleDemoInitialization);
166
166
 
167
167
  engine.post('/api/magnets', [middlewareAuthenticator, userInitiator], async (req, res) => {
@@ -1556,6 +1556,32 @@ export async function registerRoutes(engine){
1556
1556
  res.status(500).json({ success: false, error: error.message || 'An internal server error occurred.' });
1557
1557
  }
1558
1558
  });
1559
+ engine.post('/api/packs/install', [throttle, middlewareAuthenticator, userInitiator, setTimeoutMiddleware(60000)], async (req, res) => {
1560
+ const { id } = req.params;
1561
+ const user = req.me;
1562
+ const lang = req.query.lang || req.fields.lang;
1563
+
1564
+ try {
1565
+ // Vérification des permissions
1566
+ if (user.username !== 'demo' && isLocalUser(user) && !await hasPermission(["API_ADMIN", "API_INSTALL_PACK"], user)) {
1567
+ return res.status(403).json({ success: false, error: i18n.t('api.permission.installPack') });
1568
+ }
1569
+
1570
+ const result = await installPack(req.fields.packData, user, lang);
1571
+
1572
+ if (result.success) {
1573
+ res.status(200).json({ success: true, message: `Pack installed successfully.`, summary: result.summary });
1574
+ } else if (!result.success && !result.modifiedCount) {
1575
+ res.status(200).json({ success: true, message: `No data to insert.`, summary: result.summary });
1576
+ } else {
1577
+ res.status(400).json({ success: false, error: 'Pack installation had errors.', errors: result.errors, summary: result.summary });
1578
+ }
1579
+
1580
+ } catch (error) {
1581
+ logger.error(`[POST /api/packs/${id}/install] Critical error:`, error);
1582
+ res.status(500).json({ success: false, error: error.message || 'An internal server error occurred.' });
1583
+ }
1584
+ });
1559
1585
  /*
1560
1586
  engine.post('/api/packs/install', [throttle, middlewareAuthenticator, userInitiator, ...userMiddlewares], async (req, res) => {
1561
1587
 
@@ -3,9 +3,10 @@ import {MongoDatabase} from "../engine.js";
3
3
  import {getCollection, getCollectionForUser, getUserCollectionName} from "./mongodb.js";
4
4
  import {isLocalUser} from "../data.js";
5
5
  import {ObjectId} from "mongodb";
6
- import {getAPILang} from "./data/index.js";
6
+ import {getAPILang, searchData} from "./data/index.js";
7
7
  import {Logger} from "../gameObject.js";
8
8
  import rateLimit from "express-rate-limit";
9
+ import ivm from "isolated-vm";
9
10
 
10
11
  export const userInitiator = async (req, res, next) => {
11
12
 
@@ -256,3 +257,13 @@ export async function calculateTotalUserStorageUsage(user) {
256
257
  logger.debug(`[Storage] User ${userId}: Data size = ${dataSize} bytes, Files size = ${filesSize} bytes. Total = ${dataSize + filesSize} bytes.`);
257
258
  return dataSize + filesSize;
258
259
  }
260
+
261
+
262
+ export async function getEnv(user){
263
+ const result = await searchData({ model: 'env' }, user);
264
+ const envObject = result.data.reduce((acc, v) => {
265
+ acc[v.name] = v.value;
266
+ return acc;
267
+ }, {});
268
+ return envObject;
269
+ }
@@ -7,7 +7,7 @@ import ivm from 'isolated-vm';
7
7
 
8
8
  import {Logger} from "../gameObject.js";
9
9
  import {deleteData, getModel, insertData, patchData, scheduleAlerts, searchData} from "./data/index.js";
10
- import {emailDefaultConfig, maxExecutionsByStep, maxWorkflowSteps} from "../constants.js";
10
+ import {emailDefaultConfig, maxExecutionsByStep, maxWorkflowSteps, port} 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";
@@ -16,9 +16,13 @@ import i18n from "../../src/i18n.js";
16
16
  import {sendEmail} from "../email.js";
17
17
 
18
18
  import * as workflowModule from './workflow.js';
19
- import util from "node:util";
20
- import {object_equals} from "../core.js";
21
19
  import {isConditionMet} from "../filter.js";
20
+ import { services } from '../services/index.js';
21
+ import {getEnv} from "./user.js";
22
+ import {getHost} from "../constants.js";
23
+ import {providers} from "./assistant/constants.js";
24
+ import {ChatAnthropic} from "@langchain/anthropic";
25
+ import {getAIProvider} from "./assistant/assistant.js";
22
26
 
23
27
  let logger = null;
24
28
  export async function onInit(defaultEngine) {
@@ -27,6 +31,51 @@ export async function onInit(defaultEngine) {
27
31
  await scheduleWorkflowTriggers();
28
32
  }
29
33
 
34
+
35
+
36
+ /**
37
+ * Déclenche un workflow par son nom et lui passe des données de contexte.
38
+ * C'est la fonction clé à exposer aux endpoints pour lancer des processus métier.
39
+ *
40
+ * @param {string} name - Le nom du workflow à exécuter.
41
+ * @param {object} data - Les données à injecter dans context.triggerData.
42
+ * @param {object} user - L'objet utilisateur qui initie l'action.
43
+ * @returns {Promise<{success: boolean, message?: string, runId?: ObjectId}>}
44
+ */
45
+ export async function runWorkflowByName(name, data, user) {
46
+ if (!name) {
47
+ return { success: false, message: "Workflow name is required." };
48
+ }
49
+
50
+ const dbCollection = await getCollectionForUser(user);
51
+
52
+ // 1. Trouver la définition du workflow par son nom
53
+ const workflowDefinition = await dbCollection.findOne({ _model: 'workflow', name });
54
+
55
+ if (!workflowDefinition) {
56
+ const msg = `Workflow with name "${name}" not found.`;
57
+ logger.error(`[runWorkflowByName] ${msg}`);
58
+ return { success: false, message: msg };
59
+ }
60
+
61
+ // 2. Créer le document workflowRun
62
+ const workflowRunData = {
63
+ _model: 'workflowRun',
64
+ _user: user._user || user.username,
65
+ workflow: workflowDefinition._id,
66
+ contextData: { triggerData: data }, // Les données passées deviennent le triggerData
67
+ status: 'pending',
68
+ startedAt: new Date()
69
+ };
70
+
71
+ const insertResult = await dbCollection.insertOne(workflowRunData);
72
+ logger.info(`[runWorkflowByName] Created workflowRun ${insertResult.insertedId} for workflow "${name}".`);
73
+
74
+ // 3. Lancer le traitement de manière asynchrone
75
+ await processWorkflowRun(insertResult.insertedId, user);
76
+
77
+ return { success: true, runId: insertResult.insertedId };
78
+ }
30
79
  /**
31
80
  * Exécute une fonction de manière sécurisée en s'assurant qu'une seule instance
32
81
  * s'exécute à la fois, grâce à un système de verrouillage distribué basé sur la base de données.
@@ -220,7 +269,11 @@ export async function executeSafeJavascript(actionDef, context, user) {
220
269
  return new ivm.ExternalCopy(result.data?.[0] || null).copyInto();
221
270
  };
222
271
 
223
- // 1. Build the sandboxed API
272
+ // 1. Build the sandboxed API methods
273
+ await jail.set('_workflow_run', new ivm.Reference(async (name, contextData) => {
274
+ const result = await runWorkflowByName(name, JSON.parse(contextData), user);
275
+ return new ivm.ExternalCopy(result).copyInto();
276
+ }));
224
277
  await jail.set('_db_create', new ivm.Reference(async (modelName, dataObject) => {
225
278
  const result = await insertData(modelName, JSON.parse(dataObject), {}, user, false);
226
279
  if (result.success && result.insertedIds) {
@@ -255,12 +308,8 @@ export async function executeSafeJavascript(actionDef, context, user) {
255
308
  return new ivm.ExternalCopy(result.data?.[0]?.value || null).copyInto();
256
309
  }));
257
310
  await jail.set('_env_get_all', new ivm.Reference(async () => {
258
- const result = await searchData({ model: 'env' }, user);
259
- const envObject = result.data.reduce((acc, v) => {
260
- acc[v.name] = v.value;
261
- return acc;
262
- }, {});
263
- return new ivm.ExternalCopy(envObject).copyInto();
311
+ const result = await getEnv(user);
312
+ return new ivm.ExternalCopy(result).copyInto();
264
313
  }));
265
314
 
266
315
  // Contexte sécurisé
@@ -282,6 +331,10 @@ export async function executeSafeJavascript(actionDef, context, user) {
282
331
  update: (...args) => _db_update.applySyncPromise(null, normalizeArgs(args)),
283
332
  delete: (...args) => _db_delete.applySyncPromise(null, normalizeArgs(args))
284
333
  };
334
+
335
+ const workflow = {
336
+ run: (...args) => _workflow_run.applySyncPromise(null, normalizeArgs(args))
337
+ };
285
338
 
286
339
  const logger = {
287
340
  info: _log_info,
@@ -332,7 +385,7 @@ export async function executeSafeJavascript(actionDef, context, user) {
332
385
  }
333
386
 
334
387
  /**
335
- * Handles the 'Webhook' workflow action.
388
+ * Handles the 'HttpRequest' workflow action.
336
389
  * Sends an HTTP request to a specified URL with substituted data using native fetch.
337
390
  *
338
391
  * @param {object} actionDef - The definition of the 'Webhook' action.
@@ -341,17 +394,17 @@ export async function executeSafeJavascript(actionDef, context, user) {
341
394
  * @param {object} dbCollection - The MongoDB collection (moins pertinent ici, mais gardé pour la cohérence).
342
395
  * @returns {Promise<{success: boolean, message?: string, responseStatus?: number, responseBody?: any}>} - Result of the action.
343
396
  */
344
- async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
397
+ async function handleHttpRequestAction(actionDef, contextData, user, dbCollection) {
345
398
  const { name: actionName, _id: actionId, url, method = 'POST', headers: headersTemplate, body: bodyTemplate } = actionDef;
346
399
 
347
400
  // 1. Basic Validation
348
401
  if (!url) {
349
- const msg = `[handleWebhookAction] Action ${actionName} (${actionId}): Missing 'url'.`;
402
+ const msg = `[handleHttpRequestAction] Action ${actionName} (${actionId}): Missing 'url'.`;
350
403
  logger.error(msg);
351
404
  return { success: false, message: msg };
352
405
  }
353
406
 
354
- logger.info(`[handleWebhookAction] Action ${actionName} (${actionId}): Executing webhook. Method: ${method}`);
407
+ logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Executing webhook. Method: ${method}`);
355
408
 
356
409
  try {
357
410
  // 2. Substitute Variables
@@ -368,7 +421,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
368
421
  } else if (typeof headersTemplate === 'object') {
369
422
  headersObject = await substituteVariables(headersTemplate, contextData, user);
370
423
  } else {
371
- logger.warn(`[handleWebhookAction] Action ${actionName} (${actionId}): 'headers' has an invalid type (${typeof headersTemplate}). Ignoring.`);
424
+ logger.warn(`[handleHttpRequestAction] Action ${actionName} (${actionId}): 'headers' has an invalid type (${typeof headersTemplate}). Ignoring.`);
372
425
  }
373
426
  }
374
427
 
@@ -379,7 +432,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
379
432
  } else if (typeof bodyTemplate === 'object') {
380
433
  bodyObject = await substituteVariables(bodyTemplate, contextData, user);
381
434
  } else {
382
- logger.warn(`[handleWebhookAction] Action ${actionName} (${actionId}): 'body' has an invalid type (${typeof bodyTemplate}). Ignoring.`);
435
+ logger.warn(`[handleHttpRequestAction] Action ${actionName} (${actionId}): 'body' has an invalid type (${typeof bodyTemplate}). Ignoring.`);
383
436
  }
384
437
  }
385
438
 
@@ -391,7 +444,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
391
444
  throw new Error("Parsed headers is not a valid object.");
392
445
  }
393
446
  } catch (parseError) {
394
- logger.error(`[handleWebhookAction] Action ${actionName} (${actionId}): Failed to parse substituted 'headers' JSON. Error: ${parseError.message}. Using default headers. Substituted string: ${substitutedHeadersString}`);
447
+ logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Failed to parse substituted 'headers' JSON. Error: ${parseError.message}. Using default headers. Substituted string: ${substitutedHeadersString}`);
395
448
  headersObject = { 'Content-Type': 'application/json' }; // Fallback
396
449
  }
397
450
  }
@@ -433,7 +486,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
433
486
  }
434
487
 
435
488
  // 5. Execute Fetch Request using native fetch
436
- logger.info(`[handleWebhookAction] Action ${actionName} (${actionId}): Calling URL: ${substitutedUrl}`);
489
+ logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Calling URL: ${substitutedUrl}`);
437
490
  const response = await fetch(substitutedUrl, fetchOptions); // Utilisation de fetch natif
438
491
 
439
492
  // 6. Process Response
@@ -446,7 +499,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
446
499
  responseBody = await response.text();
447
500
  }
448
501
  } catch (responseParseError) {
449
- logger.error(`[handleWebhookAction] Action ${actionName} (${actionId}): Failed to parse response body. Error: ${responseParseError.message}`);
502
+ logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Failed to parse response body. Error: ${responseParseError.message}`);
450
503
  // Try reading as text again in case of error during json parsing
451
504
  try {
452
505
  responseBody = await response.text();
@@ -455,7 +508,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
455
508
  }
456
509
  }
457
510
 
458
- logger.info(`[handleWebhookAction] Action ${actionName} (${actionId}): Received response. Status: ${response.status}`);
511
+ logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Received response. Status: ${response.status}`);
459
512
 
460
513
  // 7. Return Result
461
514
  if (response.ok) { // Status code 200-299
@@ -463,13 +516,13 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
463
516
  success: true,
464
517
  message: `Webhook executed successfully. Status: ${response.status}`,
465
518
  responseStatus: response.status,
466
- responseBody: responseBody
467
- // updatedContext: { webhookResponse: responseBody } // Optionnel: Ajouter la réponse au contexte
519
+ responseBody: responseBody,
520
+ updatedContext: { httpResponse: responseBody }
468
521
  };
469
522
  } else {
470
523
  // Handle non-successful responses (4xx, 5xx)
471
524
  const errorMsg = `Webhook execution failed. Status: ${response.status}. Response: ${typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody)}`;
472
- logger.error(`[handleWebhookAction] Action ${actionName} (${actionId}): ${errorMsg}`);
525
+ logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): ${errorMsg}`);
473
526
  return {
474
527
  success: false,
475
528
  message: errorMsg,
@@ -480,7 +533,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
480
533
 
481
534
  } catch (error) {
482
535
  // Catch network errors or other unexpected errors during the process
483
- const msg = `[handleWebhookAction] Action ${actionName} (${actionId}): Unexpected error during webhook execution. Error: ${error.message}`;
536
+ const msg = `[handleHttpRequestAction] Action ${actionName} (${actionId}): Unexpected error during webhook execution. Error: ${error.message}`;
484
537
  logger.error(msg, error.stack);
485
538
  return { success: false, message: msg };
486
539
  }
@@ -786,6 +839,52 @@ async function handleDeleteDataAction(actionDef, contextData, user, dbCollection
786
839
  }
787
840
  }
788
841
 
842
+ /**
843
+ * Handles the 'ExecuteServiceFunction' workflow action.
844
+ * Acts as a secure bridge between the workflow engine and native service modules.
845
+ *
846
+ * @param {object} actionDef - The action definition.
847
+ * @param {object} contextData - The current workflow context.
848
+ * @param {object} user - The user object.
849
+ * @returns {Promise<{success: boolean, message?: string, updatedContext?: object}>}
850
+ */
851
+ async function handleExecuteServiceFunction(actionDef, contextData, user) {
852
+ const { serviceName, functionName, args: argsTemplate } = actionDef;
853
+
854
+ if (!serviceName || !functionName) {
855
+ return { success: false, message: "Action requires 'serviceName' and 'functionName'." };
856
+ }
857
+
858
+ const service = services[serviceName];
859
+ if (!service) {
860
+ return { success: false, message: `Service '${serviceName}' not found in the registry.` };
861
+ }
862
+
863
+ const func = service[functionName];
864
+ if (typeof func !== 'function') {
865
+ return { success: false, message: `Function '${functionName}' not found in service '${serviceName}'.` };
866
+ }
867
+
868
+ try {
869
+ // Substitute variables in the arguments array
870
+ const substitutedArgs = Array.isArray(argsTemplate)
871
+ ? await substituteVariables(argsTemplate, contextData, user)
872
+ : [];
873
+
874
+ logger.info(`[Service Call] Calling ${serviceName}.${functionName} with ${substitutedArgs.length} argument(s).`);
875
+ const result = await func(...substitutedArgs, user);
876
+
877
+ return {
878
+ success: true,
879
+ updatedContext: { serviceResult: result } // Store result in context
880
+ };
881
+ } catch (error) {
882
+ const msg = `Error executing ${serviceName}.${functionName}: ${error.message}`;
883
+ logger.error(`[Service Call] ${msg}`, error.stack);
884
+ return { success: false, message: msg };
885
+ }
886
+ }
887
+
789
888
  // Dans workflow.js
790
889
  export async function executeStepAction(actionDef, contextData, user, dbCollection) {
791
890
  logger.info(`[executeStepAction] Executing action type ${actionDef.type} for action ${actionDef._id} (${actionDef.name})`);
@@ -797,8 +896,8 @@ export async function executeStepAction(actionDef, contextData, user, dbCollecti
797
896
  logger.info(`[Workflow Log Action] Action: ${actionDef.name}. Contexte:`, contextData);
798
897
  result = { success: true, message: 'Log action executed successfully.' }; // <--- CORRECTION
799
898
  break;
800
- case 'Webhook':
801
- result = await handleWebhookAction(actionDef, contextData, user, dbCollection);
899
+ case 'HttpRequest':
900
+ result = await handleHttpRequestAction(actionDef, contextData, user, dbCollection);
802
901
  break;
803
902
  case 'CreateData':
804
903
  result = await handleCreateDataAction(actionDef, contextData, user, dbCollection);
@@ -821,6 +920,9 @@ export async function executeStepAction(actionDef, contextData, user, dbCollecti
821
920
  case 'ExecuteScript':
822
921
  result = await executeSafeJavascript(actionDef, contextData, user);
823
922
  break;
923
+ case 'ExecuteServiceFunction':
924
+ result = await handleExecuteServiceFunction(actionDef, contextData, user);
925
+ break;
824
926
  default:
825
927
  logger.error(`[executeStepAction] Unknown action type: ${actionDef.type}`);
826
928
  return { success: false, message: `Unknown action type: ${actionDef.type}` };
@@ -1035,6 +1137,8 @@ export async function substituteVariables(template, contextData, user) {
1035
1137
  return new Date().toISOString();
1036
1138
  } else if (path === 'randomUUID') {
1037
1139
  return crypto.randomUUID();
1140
+ } else if( path === "baseUrl" ){
1141
+ return process.env.NODE_ENV === 'production' ? 'https://'+getHost()+'/' : 'http://localhost:/'+port;
1038
1142
  }
1039
1143
 
1040
1144
  // Détecter si le chemin est complexe (contient plus d'un point)
@@ -1479,12 +1583,7 @@ async function executeGenerateAIContentAction(action, context, user) {
1479
1583
  // 1. Retrieve the API key (User Environment > Machine Environment)
1480
1584
  let apiKey;
1481
1585
 
1482
- const providers = {
1483
- "OpenAI" : "OPENAI_API_KEY",
1484
- "Google": "GOOGLE_API_KEY",
1485
- "DeepSeek": "DEEPSEEK_API_KEY"
1486
- }
1487
- const envKeyName = providers[aiProvider];
1586
+ const envKeyName = providers[aiProvider].key;
1488
1587
  if( !envKeyName ) {
1489
1588
  return {success: false, message: i18n.t('aiContent.env', `API key for provider ${aiProvider} (${envKeyName}) not found in user environment.`)};
1490
1589
  }
@@ -1508,22 +1607,8 @@ async function executeGenerateAIContentAction(action, context, user) {
1508
1607
  }
1509
1608
 
1510
1609
  // 2. Initialize the LLM client with LangChain
1511
- let llm;
1512
- try {
1513
- switch (aiProvider) {
1514
- case 'OpenAI':
1515
- llm = new ChatOpenAI({ apiKey, model: aiModel, temperature: 0.7 });
1516
- break;
1517
- case 'Google':
1518
- llm = new ChatGoogleGenerativeAI({ apiKey, model: aiModel, temperature: 0.7 });
1519
- break;
1520
- case 'DeepSeek':
1521
- llm = new ChatDeepSeek({ apiKey, model: aiModel, temperature: 0.7 });
1522
- break;
1523
- default:
1524
- throw new Error(`Unsupported AI provider: ${aiProvider}`);
1525
- }
1526
- } catch (initError) {
1610
+ let llm = getAIProvider(aiProvider, aiModel, apiKey);
1611
+ if( !llm ) {
1527
1612
  const message = `Failed to initialize AI client for ${aiProvider}: ${initError.message}`;
1528
1613
  logger.error(`[AI Action] ${message}`);
1529
1614
  return { success: false, message };