data-primals-engine 1.6.0 → 1.6.2-rc1

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.
@@ -0,0 +1,317 @@
1
+ import React, { useState, useCallback, useEffect, useRef } from 'react';
2
+ import ReactFlow, {
3
+ MiniMap,
4
+ Controls,
5
+ Background,
6
+ useNodesState,
7
+ useEdgesState,
8
+ addEdge
9
+ } from 'reactflow';
10
+ import 'reactflow/dist/style.css';
11
+ import { useModelContext } from './contexts/ModelContext';
12
+ import { useAuthContext } from './contexts/AuthContext';
13
+ import { Trans, useTranslation } from 'react-i18next';
14
+ import { CodeField } from "./Field.jsx";
15
+ import { useQuery } from 'react-query';
16
+
17
+ import "./WorkflowEditor.scss"
18
+ // --- Custom Node Types (à créer) ---
19
+ // import TriggerNode from './nodes/TriggerNode';
20
+ // import StepNode from './nodes/StepNode';
21
+
22
+ const nodeTypes = {
23
+ // trigger: TriggerNode,
24
+ // step: StepNode,
25
+ };
26
+
27
+ const WorkflowEditor = ({ workflowId }) => {
28
+ const { t } = useTranslation();
29
+ const { me } = useAuthContext();
30
+ const { models } = useModelContext();
31
+
32
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
33
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
34
+ const [selectedNode, setSelectedNode] = useState(null);
35
+ const [panelWidth, setPanelWidth] = useState(300);
36
+ const resizeData = useRef({ isResizing: false, initialX: 0, initialWidth: 0 });
37
+
38
+ const handleMouseDown = (e) => {
39
+ e.preventDefault();
40
+ resizeData.current = {
41
+ isResizing: true,
42
+ initialX: e.clientX,
43
+ initialWidth: panelWidth
44
+ };
45
+ document.addEventListener('mousemove', handleMouseMove);
46
+ document.addEventListener('mouseup', handleMouseUp);
47
+ };
48
+
49
+ const handleMouseMove = useCallback((e) => {
50
+ if (!resizeData.current.isResizing) return; // Utilise la référence qui ne change pas
51
+ const dx = e.clientX - resizeData.current.initialX; // Calcule le delta depuis le clic initial
52
+ const newWidth = resizeData.current.initialWidth - dx; // Applique le delta à la largeur initiale
53
+ setPanelWidth(Math.max(200, Math.min(newWidth, 800))); // Applique les limites
54
+ }, []); // Plus besoin de dépendances, la logique est entièrement contenue dans la ref
55
+
56
+
57
+ const handleMouseUp = () => {
58
+ resizeData.current.isResizing = false;
59
+ document.removeEventListener('mousemove', handleMouseMove);
60
+ document.removeEventListener('mouseup', handleMouseUp);
61
+ };
62
+
63
+
64
+ // Query 1: Fetch the main workflow document
65
+ const { data: mainWorkflowData, isLoading: isLoadingMainWorkflow, error: errorMainWorkflow } = useQuery(
66
+ ['mainWorkflow', workflowId],
67
+ () => fetch('/api/data/search?_user='+me.username, {
68
+ method: 'POST',
69
+ headers: { 'Content-Type': 'application/json' },
70
+ body: JSON.stringify({
71
+ model: 'workflow',
72
+ filter: { $eq: [{$toString:'$_id'}, workflowId] },
73
+ // No pipelines, no depth here, as per user request
74
+ })
75
+ }).then(res => res.json()),
76
+ {
77
+ enabled: !!workflowId,
78
+ select: (data) => data?.data?.[0]
79
+ }
80
+ );
81
+
82
+ // Query 2: Fetch all workflowStep documents related to this workflow
83
+ const { data: workflowStepsData, isLoading: isLoadingSteps, error: errorSteps } = useQuery(
84
+ ['workflowSteps', workflowId],
85
+ () => fetch('/api/data/search?_user=' + me.username, {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({
89
+ model: 'workflowStep',
90
+ filter: { workflow: workflowId },
91
+ // Use a shallow depth to populate direct relations like 'actions'
92
+ // This avoids a deep recursive lookup for the entire graph, but makes step data usable
93
+ depth: 1
94
+ })
95
+ }).then(res => res.json()),
96
+ {
97
+ enabled: !!workflowId, // La requête ne s'exécute que si workflowId est défini
98
+ select: (data) => data?.data || []
99
+ }
100
+ );
101
+
102
+ // Query 3: Fetch all workflowAction documents related to this workflow
103
+ const { data: workflowActionsData, isLoading: isLoadingActions, error: errorActions } = useQuery(
104
+ ['workflowActions', workflowId],
105
+ () => fetch('/api/data/search?_user=' + me.username, {
106
+ method: 'POST',
107
+ headers: { 'Content-Type': 'application/json' },
108
+ body: JSON.stringify({
109
+ model: 'workflowAction',
110
+ filter: { workflow: workflowId },
111
+ // No depth needed here as actions are usually flat
112
+ })
113
+ }).then(res => res.json()),
114
+ {
115
+ enabled: !!workflowId,
116
+ select: (data) => data?.data || []
117
+ }
118
+ );
119
+
120
+ useEffect(() => {
121
+ if (mainWorkflowData && workflowStepsData && workflowActionsData) {
122
+ const initialNodes = [];
123
+ const initialEdges = [];
124
+ const processedSteps = new Set();
125
+
126
+ // Create a map for quick lookup of steps
127
+ const stepsMap = new Map(workflowStepsData.map(step => [step._id, step]));
128
+
129
+ // 1. Nœud du déclencheur (Trigger)
130
+ initialNodes.push({
131
+ id: `workflow-start-${mainWorkflowData._id}`,
132
+ type: 'input',
133
+ position: { x: 250, y: 5 },
134
+ data: {
135
+ label: `Début: ${typeof mainWorkflowData.name === 'object' ? mainWorkflowData.name.value : mainWorkflowData.name}`,
136
+ // --- CORRECTION ---
137
+ // On inclut toutes les données du workflow pour la cohérence
138
+ ...mainWorkflowData
139
+ },
140
+ });
141
+
142
+ // Create a map for quick lookup of actions
143
+ const actionsMap = new Map(workflowActionsData.map(action => [action._id, action]));
144
+
145
+ // 2. Traitement récursif des étapes
146
+ const processStep = (stepId, parentNodeId, parentPosition, isSuccessBranch = true) => {
147
+ if (!stepId || processedSteps.has(stepId)) return;
148
+
149
+ const step = stepsMap.get(stepId);
150
+ if (!step) {
151
+ console.warn(`WorkflowEditor: Step with ID ${stepId} not found in fetched steps.`);
152
+ return;
153
+ }
154
+
155
+ processedSteps.add(stepId);
156
+
157
+ const nodePosition = { x: parentPosition.x, y: parentPosition.y + 120 };
158
+ initialNodes.push({
159
+ id: step._id,
160
+ type: 'default',
161
+ className: 'workflow-step-node',
162
+ position: nodePosition,
163
+ data: {
164
+ label: (step?.name && typeof step.name === 'object' ? step.name.value : step.name) || `Étape ${step._id}`,
165
+ ...step // --- CORRECTION --- On inclut l'objet step complet
166
+ },
167
+ });
168
+ initialEdges.push({ id: `e-${parentNodeId}-${step._id}`, source: parentNodeId, target: step._id, type: 'smoothstep' });
169
+
170
+ let lastActionId = step._id;
171
+ // The actions on the step are just IDs now, we need to look them up
172
+ const actionIds = Array.isArray(step.actions) ? step.actions : (step.actions ? [step.actions] : []);
173
+ const actions = actionIds.map(id => actionsMap.get(id)).filter(Boolean); // Get full action objects
174
+
175
+ if (actionIds.length !== actions.length) {
176
+ const missingIds = actionIds.filter(id => !actionsMap.has(id));
177
+ console.warn(`WorkflowEditor: Could not find the following actions: ${missingIds.join(', ')}`);
178
+ }
179
+
180
+ actions.forEach((action, index) => {
181
+ const actionNodePosition = { x: nodePosition.x, y: nodePosition.y + (index + 1) * 80 };
182
+ initialNodes.push({
183
+ id: action._id,
184
+ type: 'default',
185
+ className: 'workflow-action-node',
186
+ position: actionNodePosition,
187
+ data: {
188
+ label: action.name.value || `Action ${action._id}`,
189
+ ...action // --- CORRECTION --- On inclut l'objet action complet
190
+ },
191
+ });
192
+ initialEdges.push({ id: `e-${lastActionId}-${action._id}`, source: lastActionId, target: action._id, type: 'smoothstep' });
193
+ lastActionId = action._id;
194
+ });
195
+
196
+ // Créer les liens (edges)
197
+ if (step.onSuccessStep && typeof step.onSuccessStep === 'string') { // onSuccessStep is an ID
198
+ processStep(step.onSuccessStep, lastActionId, { x: nodePosition.x - 200, y: nodePosition.y + (actions.length * 80) }, true);
199
+ initialEdges.push({ id: `e-${lastActionId}-success-${step.onSuccessStep}`, source: lastActionId, target: step.onSuccessStep, label: t('onSuccess', 'onSuccess'), type: 'smoothstep' });
200
+ }
201
+
202
+ if (step.onFailureStep && typeof step.onFailureStep === 'string') { // onFailureStep is an ID
203
+ processStep(step.onFailureStep, lastActionId, { x: nodePosition.x + 200, y: nodePosition.y + (actions.length * 80) }, false);
204
+ initialEdges.push({ id: `e-${lastActionId}-failure-${step.onFailureStep}`, source: lastActionId, target: step.onFailureStep, label: t('onFailure', 'onFailure'), type: 'smoothstep', animated: true, style: { stroke: '#ff4d4d' } });
205
+ }
206
+ };
207
+
208
+ if (mainWorkflowData.startStep) {
209
+ initialEdges.push({
210
+ id: `e-start-${mainWorkflowData.startStep}`, // startStep is an ID
211
+ source: `workflow-start-${mainWorkflowData._id}`,
212
+ target: mainWorkflowData.startStep,
213
+ type: 'smoothstep',
214
+ });
215
+ processStep(mainWorkflowData.startStep, `workflow-start-${mainWorkflowData._id}`, { x: 250, y: 5 });
216
+ }
217
+
218
+ setNodes(initialNodes);
219
+ setEdges(initialEdges);
220
+ }
221
+ }, [mainWorkflowData, workflowStepsData, workflowActionsData, setNodes, setEdges, t]);
222
+
223
+ const onConnect = useCallback(
224
+ (params) => setEdges((eds) => addEdge(params, eds)),
225
+ [setEdges],
226
+ );
227
+
228
+ const onNodeClick = useCallback((event, node) => { // Keep this for potential future node editing
229
+ setSelectedNode(node);
230
+ console.log('Node clicked:', node);
231
+ }, []);
232
+
233
+ const onPaneClick = useCallback(() => { // Keep this for potential future node editing
234
+ setSelectedNode(null);
235
+ }, []);
236
+
237
+ if (isLoadingMainWorkflow || isLoadingSteps || isLoadingActions) { // Check all loading states
238
+ return <div>{t('loading', 'Chargement...')}</div>;
239
+ }
240
+
241
+ if (errorMainWorkflow || errorSteps || errorActions) { // Check all error states
242
+ const errorMessage = errorMainWorkflow?.message || errorSteps?.message || errorActions?.message || 'Unknown error';
243
+ return <div><Trans i18nKey="error.generic" values={{ message: errorMessage }}>Erreur: {{ message: errorMessage }}</Trans></div>;
244
+ }
245
+
246
+ return (
247
+ <div className={"flex-1"} style={{ width: '100%', height: '80vh', display: 'flex' }}>
248
+ <ReactFlow
249
+ nodes={nodes}
250
+ edges={edges}
251
+ onNodesChange={onNodesChange}
252
+ onEdgesChange={onEdgesChange}
253
+ onConnect={onConnect}
254
+ onNodeClick={onNodeClick}
255
+ onPaneClick={onPaneClick}
256
+ nodeTypes={nodeTypes}
257
+ fitView
258
+ >
259
+ <Controls />
260
+ <MiniMap />
261
+ <Background variant="dots" gap={12} size={1} />
262
+ </ReactFlow>
263
+
264
+ {/* Panneau latéral pour l'édition des propriétés */}
265
+ {selectedNode && (
266
+ <div className="properties-panel" style={{ width: `${panelWidth}px`, padding: '10px', borderLeft: '1px solid #ccc' }}>
267
+ <div
268
+ className="resizer"
269
+ onMouseDown={handleMouseDown}
270
+ />
271
+ <h3><Trans i18nKey="properties">Propriétés</Trans></h3>
272
+ <p><strong>ID:</strong> {selectedNode.id}</p>
273
+
274
+ {/* Affiche les conditions pour un workflowStep */}
275
+ {selectedNode.data.conditions && (
276
+ <div className="mt-2">
277
+ <strong><Trans i18nKey="conditions">Conditions:</Trans></strong>
278
+ <CodeField language="json" value={JSON.stringify(selectedNode.data.conditions, null, 2)} readOnly={true} />
279
+ </div>
280
+ )}
281
+
282
+ {/* Affiche le script pour une action ExecuteScript */}
283
+ {selectedNode.data.type && (
284
+ <div className="mt-2">
285
+ <strong><Trans i18nKey="action">Action :</Trans></strong> {selectedNode.data.type}
286
+ {selectedNode.data.targetModel&& (
287
+ <p>
288
+ <strong><Trans i18nKey="action">Modèle cible : </Trans></strong>{selectedNode.data.targetModel}
289
+ </p>
290
+ )}
291
+ {selectedNode.data.targetSelector&& (
292
+ <p>
293
+ <strong><Trans i18nKey="filter">Filtre sur :</Trans></strong>
294
+ <br/><CodeField language="javascript" value={JSON.stringify(selectedNode.data.targetSelector)} readOnly={true} />
295
+ </p>
296
+ )}
297
+ {(selectedNode.data.dataToCreate || selectedNode.data.fieldsToUpdate) && (
298
+ <p>
299
+ <strong><Trans i18nKey="filter">Code déclenché :</Trans></strong>
300
+ <br/><CodeField language="javascript" value={JSON.stringify(selectedNode.data.dataToCreate || selectedNode.data.fieldsToUpdate)} readOnly={true} />
301
+ </p>
302
+ )}
303
+ </div>
304
+ )}
305
+ {/* Affiche le script pour une action ExecuteScript */}
306
+ {selectedNode.data.type === 'ExecuteScript' && selectedNode.data.script && (
307
+ <div className="mt-2">
308
+ <CodeField language="javascript" value={selectedNode.data.script} readOnly={true} />
309
+ </div>
310
+ )}
311
+ </div>
312
+ )}
313
+ </div>
314
+ );
315
+ };
316
+
317
+ export default WorkflowEditor;
@@ -0,0 +1,16 @@
1
+
2
+ .workflow-step-node {
3
+ background-color: #e0e7ff; /* Un bleu clair pour les étapes */
4
+ border-color: #a5b4fc;
5
+ border-width: 2px;
6
+ font-weight: bold;
7
+ }
8
+
9
+ .workflow-action-node {
10
+ background-color: #f0f9ff; /* Un bleu très clair pour les actions */
11
+ border-color: #bae6fd;
12
+ border-width: 1px;
13
+ border-style: dashed;
14
+ width: auto;
15
+ min-width: 150px;
16
+ }
package/package.json CHANGED
@@ -1,142 +1,142 @@
1
- {
2
- "name": "data-primals-engine",
3
- "version": "1.6.0",
4
- "description": "data-primals-engine is a package responsible from handling large amount of data using MongoDB in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation. It also has integrated AI assistant.",
5
- "main": "src/engine.js",
6
- "type": "module",
7
- "scripts": {
8
- "preinstall": "npx force-resolutions",
9
- "dev": "concurrently \"npm:client\" \"npm:devserver\"",
10
- "prod": "npm run build-server && npm run server",
11
- "client": "cd client && npm run dev",
12
- "build-server": "vite build client --config client/vite.config.js --outDir dist",
13
- "build-client": "cd client && npm run build",
14
- "server": "cross-env NODE_ENV=production node server.js",
15
- "devserver": "npm run build-server && cross-env NODE_ENV=development PORT=7633 node server.js",
16
- "lint": "eslint .",
17
- "format": "prettier --write .",
18
- "clean": "rm -rf node_modules package-lock.json",
19
- "test": "cross-env MONGO_DB_URL=\"mongodb://localhost:27017\" PORT=7635 vitest --no-file-parallelism --no-watch",
20
- "audit": "npm audit --audit-level=high",
21
- "migrate:create": "node src/migrate.js create",
22
- "migrate:up": "node src/migrate.js up",
23
- "migrate:down": "node src/migrate.js down",
24
- "migrate:revert": "node src/migrate.js revert",
25
- "migrate:status": "node src/migrate.js status"
26
- },
27
- "optionalDependencies": {
28
- "@rollup/rollup-linux-x64-gnu": "4.6.1"
29
- },
30
- "resolutions": {
31
- "on-headers": "1.1.0",
32
- "brace-expansion": "2.0.2",
33
- "prismjs": "1.30.0",
34
- "xml2js": ">=0.5.0",
35
- "tar-fs": ">=3.1.1",
36
- "vite": ">=7.0.8"
37
- },
38
- "overrides": {
39
- "react-syntax-highlighter": {
40
- "prismjs": "1.30.0"
41
- },
42
- "react-code-blocks": {
43
- "prismjs": "1.30.0"
44
- },
45
- "refractor": {
46
- "prismjs": "1.30.0"
47
- }
48
- },
49
- "repository": {
50
- "type": "git",
51
- "url": "https://github.com/anonympins/data-primals-engine.git"
52
- },
53
- "exports": {
54
- ".": "./src/index.js",
55
- "./modules/*.js": "./src/modules/*.js",
56
- "./modules/*": "./src/modules/*/index.js",
57
- "./client": "./client/index.js",
58
- "./*": "./src/*.js"
59
- },
60
- "dependencies": {
61
- "@langchain/core": "^0.3.66",
62
- "archiver": "^7.0.1",
63
- "aws-sdk": "^2.1692.0",
64
- "bcrypt": "^6.0.0",
65
- "body-parser": "^2.2.0",
66
- "chalk": "^5.4.1",
67
- "check-disk-space": "^3.4.0",
68
- "compression": "^1.8.1",
69
- "cookie-parser": "^1.4.7",
70
- "cronstrue": "^3.2.0",
71
- "csv-parse": "^6.1.0",
72
- "date-fns": "^4.1.0",
73
- "express-csrf-double-submit-cookie": "^2.0.0",
74
- "express-formidable": "^1.2.0",
75
- "express-mongo-sanitize": "^2.2.0",
76
- "express-rate-limit": "^8.0.1",
77
- "express-session": "^1.18.2",
78
- "handlebars": "^4.7.8",
79
- "i18next-browser-languagedetector": "^8.2.0",
80
- "isolated-vm": "^5.0.0",
81
- "juice": "^11.0.1",
82
- "mathjs": "^14.6.0",
83
- "mongodb": "^6.18.0",
84
- "node-cache": "^5.1.2",
85
- "node-schedule": "^2.1.1",
86
- "nodemailer": "^7.0.7",
87
- "openai": "^5.10.2",
88
- "passport": "^0.7.0",
89
- "passport-saml-encrypted": "^0.1.13",
90
- "process": "^0.11.10",
91
- "prop-types": "^15.8.1",
92
- "randomcolor": "^0.6.2",
93
- "react-markdown": "^10.1.0",
94
- "read-excel-file": "^5.8.8",
95
- "request-ip": "^3.3.0",
96
- "safe-regex": "^2.1.1",
97
- "sanitize-html": "^2.17.0",
98
- "sirv": "^3.0.1",
99
- "stripe": "^18.4.0",
100
- "swagger-ui-express": "^5.0.1",
101
- "tar": "^7.4.3",
102
- "tinycolor2": "^1.6.0",
103
- "uniqid": "^5.4.0",
104
- "vitest": "^3.2.4",
105
- "yaml": "^2.8.0"
106
- },
107
- "peerDependencies": {
108
- "express": "^5.1.0",
109
- "passport-azure-ad": "^4.3.5",
110
- "passport-google-oauth20": "^2.0.0",
111
- "react": "18.3.1",
112
- "react-i18next": "^15.6.1",
113
- "react-query": ">=3.0.0",
114
- "@langchain/anthropic": "^0.3.26",
115
- "@langchain/deepseek": "^0.1.0",
116
- "@langchain/google-genai": "^0.2.16",
117
- "@langchain/openai": "^0.6.3"
118
- },
119
- "devDependencies": {
120
- "@eslint/js": "^9.32.0",
121
- "concurrently": "^9.2.0",
122
- "cross-env": "^10.0.0",
123
- "eslint": "^9.32.0",
124
- "globals": "^16.3.0"
125
- },
126
- "subPackages": [
127
- "client"
128
- ],
129
- "keywords": [
130
- "data-driven engine",
131
- "headless CMS",
132
- "backend",
133
- "automation",
134
- "AWS S3",
135
- "MongoDB"
136
- ],
137
- "author": "anonympins",
138
- "license": "MIT",
139
- "engines": {
140
- "node": ">=20.0.0"
141
- }
142
- }
1
+ {
2
+ "name": "data-primals-engine",
3
+ "version": "1.6.2-rc1",
4
+ "description": "data-primals-engine is a package responsible from handling large amount of data using MongoDB in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation. It also has integrated AI assistant.",
5
+ "main": "src/engine.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "preinstall": "npx force-resolutions",
9
+ "dev": "concurrently \"npm:client\" \"npm:devserver\"",
10
+ "prod": "npm run build-server && npm run server",
11
+ "client": "cd client && npm run dev",
12
+ "build-server": "vite build client --config client/vite.config.js --outDir dist",
13
+ "build-client": "cd client && npm run build",
14
+ "server": "cross-env NODE_ENV=production node server.js",
15
+ "devserver": "npm run build-server && cross-env NODE_ENV=development PORT=7633 node server.js",
16
+ "lint": "eslint .",
17
+ "format": "prettier --write .",
18
+ "clean": "rm -rf node_modules package-lock.json",
19
+ "test": "cross-env MONGO_DB_URL=\"mongodb://localhost:27017\" PORT=7635 vitest --no-file-parallelism --no-watch",
20
+ "audit": "npm audit --audit-level=high",
21
+ "migrate:create": "node src/migrate.js create",
22
+ "migrate:up": "node src/migrate.js up",
23
+ "migrate:down": "node src/migrate.js down",
24
+ "migrate:revert": "node src/migrate.js revert",
25
+ "migrate:status": "node src/migrate.js status"
26
+ },
27
+ "optionalDependencies": {
28
+ "@rollup/rollup-linux-x64-gnu": "4.6.1"
29
+ },
30
+ "resolutions": {
31
+ "on-headers": "1.1.0",
32
+ "brace-expansion": "2.0.2",
33
+ "prismjs": "1.30.0",
34
+ "xml2js": ">=0.5.0",
35
+ "tar-fs": ">=3.1.1",
36
+ "vite": ">=7.0.8"
37
+ },
38
+ "overrides": {
39
+ "react-syntax-highlighter": {
40
+ "prismjs": "1.30.0"
41
+ },
42
+ "react-code-blocks": {
43
+ "prismjs": "1.30.0"
44
+ },
45
+ "refractor": {
46
+ "prismjs": "1.30.0"
47
+ }
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/anonympins/data-primals-engine.git"
52
+ },
53
+ "exports": {
54
+ ".": "./src/index.js",
55
+ "./modules/*.js": "./src/modules/*.js",
56
+ "./modules/*": "./src/modules/*/index.js",
57
+ "./client": "./client/index.js",
58
+ "./*": "./src/*.js"
59
+ },
60
+ "dependencies": {
61
+ "@langchain/core": "^0.3.79",
62
+ "archiver": "^7.0.1",
63
+ "aws-sdk": "^2.1692.0",
64
+ "bcrypt": "^6.0.0",
65
+ "body-parser": "^2.2.0",
66
+ "chalk": "^5.4.1",
67
+ "check-disk-space": "^3.4.0",
68
+ "compression": "^1.8.1",
69
+ "cookie-parser": "^1.4.7",
70
+ "cronstrue": "^3.2.0",
71
+ "csv-parse": "^6.1.0",
72
+ "date-fns": "^4.1.0",
73
+ "express-csrf-double-submit-cookie": "^2.0.0",
74
+ "express-formidable": "^1.2.0",
75
+ "express-mongo-sanitize": "^2.2.0",
76
+ "express-rate-limit": "^8.0.1",
77
+ "express-session": "^1.18.2",
78
+ "handlebars": "^4.7.8",
79
+ "i18next-browser-languagedetector": "^8.2.0",
80
+ "isolated-vm": "^5.0.0",
81
+ "juice": "^11.0.3",
82
+ "mathjs": "^15.1.0",
83
+ "mongodb": "^6.18.0",
84
+ "node-cache": "^5.1.2",
85
+ "node-schedule": "^2.1.1",
86
+ "nodemailer": "^7.0.10",
87
+ "openai": "^6.9.1",
88
+ "passport": "^0.7.0",
89
+ "passport-saml-encrypted": "^0.1.13",
90
+ "process": "^0.11.10",
91
+ "prop-types": "^15.8.1",
92
+ "randomcolor": "^0.6.2",
93
+ "react-markdown": "^10.1.0",
94
+ "read-excel-file": "^6.0.1",
95
+ "request-ip": "^3.3.0",
96
+ "safe-regex": "^2.1.1",
97
+ "sanitize-html": "^2.17.0",
98
+ "sirv": "^3.0.2",
99
+ "stripe": "^20.0.0",
100
+ "swagger-ui-express": "^5.0.1",
101
+ "tar": "^7.5.2",
102
+ "tinycolor2": "^1.6.0",
103
+ "uniqid": "^5.4.0",
104
+ "vitest": "^3.2.4",
105
+ "yaml": "^2.8.1"
106
+ },
107
+ "peerDependencies": {
108
+ "express": "^5.1.0",
109
+ "passport-azure-ad": "^4.3.5",
110
+ "passport-google-oauth20": "^2.0.0",
111
+ "react": "18.3.1",
112
+ "react-i18next": "^16.3.4",
113
+ "react-query": "^3.39.3",
114
+ "@langchain/anthropic": "^0.3.33",
115
+ "@langchain/deepseek": "^0.1.0",
116
+ "@langchain/google-genai": "^0.2.18",
117
+ "@langchain/openai": "^0.6.16"
118
+ },
119
+ "devDependencies": {
120
+ "@eslint/js": "^9.32.0",
121
+ "concurrently": "^9.2.0",
122
+ "cross-env": "^10.0.0",
123
+ "eslint": "^9.32.0",
124
+ "globals": "^16.3.0"
125
+ },
126
+ "subPackages": [
127
+ "client"
128
+ ],
129
+ "keywords": [
130
+ "data-driven engine",
131
+ "headless CMS",
132
+ "backend",
133
+ "automation",
134
+ "AWS S3",
135
+ "MongoDB"
136
+ ],
137
+ "author": "anonympins",
138
+ "license": "MIT",
139
+ "engines": {
140
+ "node": ">=20.0.0"
141
+ }
142
+ }
@@ -84,6 +84,13 @@ export const defaultModels = {
84
84
  "required": false,
85
85
  "index": true,
86
86
  "hint": "Si défini, l'exception (l'octroi ou la révocation) est temporaire."
87
+ },
88
+ {
89
+ "name": "env",
90
+ "type": "relation",
91
+ "relation": "env",
92
+ "required": false,
93
+ "hint": "Si défini, la permission ne s'applique qu'à cet environnement. Sinon, elle est globale."
87
94
  }
88
95
  ]
89
96
  },
@@ -911,7 +918,8 @@ export const defaultModels = {
911
918
  "tags": ["system", "automation"],
912
919
  fields: [
913
920
  { name: 'name', type: 'string_t', required: true, hint: "Unique name for the workflow (e.g., 'Order Validation', 'Low Stock Notification')." },
914
- { name: 'description', type: 'richtext', hint: "Detailed explanation of the workflow's purpose." },
921
+ { name: 'description', type: 'richtext', hint: "Detailed explanation of the workflow's purpose."
922
+ },
915
923
  { name: 'startStep', type: 'relation', relation: 'workflowStep', required: false, hint: "The first step to execute when the workflow starts." }
916
924
  ]
917
925
  },
@@ -975,7 +983,8 @@ export const defaultModels = {
975
983
  "tags": ["system", "automation"],
976
984
  fields: [
977
985
  { name: 'workflow', type: 'relation', relation: 'workflow', required: true, hint: "The workflow this step belongs to." },
978
- { name: 'name', type: 'string_t', hint: "Optional descriptive name for the step (e.g., 'Check Inventory', 'Send Confirmation Email')." },
986
+ { name: 'name', type: 'string_t', hint: "Optional descriptive name for the step (e.g., 'Check Inventory', 'Send Confirmation Email')."
987
+ },
979
988
  { name: 'conditions', type: 'code', language: 'json', conditionBuilder: true, hint: "Optional conditions checked before executing the step's action." },
980
989
  { name: 'actions', type: 'relation', relation: 'workflowAction', multiple: true, required: true, hint: "The main actions performed by this step." },
981
990
  { name: 'onSuccessStep', type: 'relation', relation: 'workflowStep', hint: "Optional: The next step if this step's action succeeds." },
@@ -990,6 +999,7 @@ export const defaultModels = {
990
999
  "tags": ["system", "automation"],
991
1000
  fields: [
992
1001
  { name: 'name', type: 'string_t', required: true, hint: "Name of the action (e.g., 'Update Order Status', 'Send Email', 'Call Payment API')." },
1002
+ { name: 'workflow', type: 'relation', relation: 'workflow', required: false },
993
1003
  {
994
1004
  name: 'type',
995
1005
  type: 'enum',