data-primals-engine 1.2.1 → 1.2.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 CHANGED
@@ -18,15 +18,18 @@
18
18
 
19
19
  ## 🚀 Key Features
20
20
 
21
- - **Dynamic data modeling**: Define and update schemas using JSON, no migrations required.
22
- - **Robust REST API**: Advanced CRUD operations, filtering, and querying.
23
- - **Modular architecture**: Load or extend modules dynamically.
24
- - **Automation workflows**: Trigger actions on events or schedules.
25
- - **Authentication & authorization**: Role-based access control, pluggable providers.
26
- - **📦 Starter Packs**: CRM, e-commerce, showcase websites, etc.
27
- - **🧠 AI Integration**: Supports OpenAI, Google Gemini via LangChain.
28
- - **🌐 i18n support**: Multilingual interfaces, translated validations.
29
- - **📄 Auto Documentation**: Swagger available at `/api-docs`.
21
+ - **Dynamic Data Modeling**: Define and update schemas using JSON, no migrations required.
22
+ - **Custom API Endpoints**: Create server-side logic and new API endpoints directly from the UI in a secure, sandboxed environment.
23
+ - **Automation Workflows**: Trigger complex actions based on data events (create, update, delete) or schedules (cron).
24
+ - **Advanced Querying & Aggregation**: Go beyond simple filters with deep relation expansion, complex lookups, and dynamic calculated fields.
25
+ - **Integrated Backup & Restore**: Secure, encrypted user data backups with rotation policies, supporting both local and AWS S3 storage.
26
+ - **Event-Driven & Extensible**: A core event system allows for deep customization and the easy creation of new modules or plugins.
27
+ - **Authentication & Authorization**: Robust role-based access control (RBAC) and pluggable user providers.
28
+ - **Built-in File Management**: Handle file uploads seamlessly with integrated support for AWS S3 storage.
29
+ - **🧠 AI Integration**: Natively supports OpenAI and Google Gemini models via LangChain for content generation, analysis, and more.
30
+ - **🌐 Internationalization (i18n)**: Fully supports multilingual interfaces and user-specific translated data.
31
+ - **📦 Starter Packs**: Quickly bootstrap applications with pre-built data packs for CRM, e-commerce, and more.
32
+ - **📄Auto-Generated API Documentation**: Interactive API documentation available via the interface or at `/api-docs`.
30
33
 
31
34
 
32
35
  ## 🌟 Why Choose data-primals-engine?
@@ -603,11 +606,68 @@ Expected response :
603
606
  },
604
607
  "publishedPosts": 15
605
608
  }
609
+
610
+ ```
611
+
612
+ ---
613
+ ## Extensibility
614
+
615
+ ### Events (Triggers) Table
616
+ > You can use the events below to access the engine and manipulate API responses.
617
+ > It is useful for custom modules or middlewares for your application.
618
+
619
+ Just use
620
+
621
+ ```javascript
622
+ Event.Listen("OnDataAdded", (data) => {
623
+ my_callback()
624
+ }, "event", "user");
625
+ ```
626
+
627
+ or the system version
628
+ ```javascript
629
+ Event.Listen("OnDataAdded", (engine, data) => {
630
+ my_callback()
631
+ }, "event", "system");
632
+ ```
633
+
634
+ | Event | Description | Scope | Triggered by | Arguments (Payload) |
635
+ | :--- |:------------------------------------------------------------------------| :--- | :--- | :--- |
636
+ | OnServerStart | Triggered once the HTTP server is started and listening. | System | engine.start() | engine |
637
+ | OnServerStop | Triggered right after the HTTP server is stopped. | System | engine.stop() | engine |
638
+ | OnModelsLoaded | Triggered after the initial models are loaded and validated at startup. | System | setupInitialModels() | engine, dbModels |
639
+ | OnModelsDeleted | Triggered after all models are deleted via the reset function. | System | engine.resetModels() | engine |
640
+ | OnUserDataDumped | Triggered after a user's data has been backed up (dumped). | System | jobDumpUserData() | engine |
641
+ | OnDataRestored | Triggered after a user's data has been restored from a backup. | System | loadFromDump() | (none) |
642
+ | OnPackInstalled | Triggered after a data pack has been successfully installed. | System | installPack() | pack |
643
+ | OnModelEdited | Triggered after a model definition has been modified. | System & User | editModel() | System: engine, newModel (Pipeline*)<br>User: newModel (or the version modified by the system) |
644
+ | OnDataAdded | Triggered after new data has been inserted. | System & User | insertData() | System: engine, insertedIds (Pipeline*)<br>User: insertedIds (or the version modified by the system) |
645
+ | OnDataDeleted | Triggered just after data is actually deleted. | System & User | deleteData() | System: engine, {model, filter} (Pipeline*)<br>User: {model, filter} |
646
+ | OnDataSearched | Triggered after a data search. | System & User | searchData() | System: engine, {data, count} (Pipeline*)<br>User: {data, count} (or the version modified by the system) |
647
+ | OnDataExported | Triggered after a data export. | System & User | exportData() | System: engine, exportResults, modelsToExport (Pipeline*)<br>User: exportResults, modelsToExport (or the version modified by the system) |
648
+
649
+ ### Triggering events
650
+
651
+ If you want to provide your own hooks, you can call :
652
+ ```javascript
653
+ const result = Event.Trigger("OnMyCustomEvent", "event", "user", ...args);
606
654
  ```
655
+ Results are merged together if multiple events are triggered.
656
+ - strings are concatenated
657
+ - numbers are added
658
+ - booleans are ANDed
659
+ - arrays are concatenated
660
+ - objects are merged using spread operator
661
+
662
+ ---
663
+
607
664
  ## 🤝 Contributing
608
665
 
666
+ Find the issues available for [contributions here](https://github.com/anonympins/data-primals-engine/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22%20no%3Aassignee)
667
+
609
668
  1. Fork the repo
610
669
  2. Create your feature branch: `git checkout -b feature/your-feature`
670
+ 3. Launch ```npm run dev``` and make your changes with hot-reload on local port
611
671
  3. Commit changes: `git commit -m "Add new feature"`
612
672
  4. Push to your branch: `git push origin feature/your-feature`
613
673
  5. Open a pull request
@@ -2,35 +2,19 @@ import React, {forwardRef, useCallback, useEffect, useMemo, useReducer, useRef,
2
2
 
3
3
  import "./App.scss";
4
4
  import {useMutation, useQuery, useQueryClient} from "react-query";
5
- import {CheckboxField, FileField, NumberField, SelectField, TextField} from "./Field.jsx";
6
5
  import ModelCreator from "./ModelCreator.jsx";
7
6
  import {useModelContext} from "./contexts/ModelContext.jsx";
8
- import {FaMagnifyingGlass, FaPencil} from "react-icons/fa6";
9
7
  import {Dialog, DialogProvider} from "./Dialog.jsx";
10
8
  import {Pagination} from "./Pagination.jsx";
9
+ import {Event} from "../../src/events.js";
10
+
11
11
  import {
12
- elementsPerPage, kilobytes,
13
- mainFieldsTypes,
14
- maxBytesPerSecondThrottleData,
15
- maxFileSize,
16
- maxRequestData, metaModels
17
- } from "../../src/constants.js";
18
- import {
19
- FaArrowDown,
20
- FaArrowUp, FaBell,
21
- FaBook, FaCopy,
22
- FaEdit,
23
- FaFileExport, FaFileImport,
24
12
  FaFilter, FaInfo,
25
- FaLanguage,
26
- FaLock,
27
- FaPlus,
28
- FaTrash
29
13
  } from "react-icons/fa";
30
14
  import {getDefaultForType, getUserId} from "../../src/data.js";
31
15
  import {Trans, useTranslation} from "react-i18next";
32
16
 
33
- import {debounce, escapeRegExp, event_trigger, getObjectHash, isGUID} from "../../src/core.js";
17
+ import {getObjectHash} from "../../src/core.js";
34
18
  import Button from "./Button.jsx";
35
19
  import {useAuthContext} from "./contexts/AuthContext.jsx";
36
20
  import APIInfo from "./APIInfo.jsx";
@@ -49,7 +33,6 @@ import CalendarConfigModal from "./CalendarConfigModal.jsx";
49
33
  import KanbanView from "./KanbanView.jsx";
50
34
 
51
35
 
52
- // --- AJOUT : Composants Placeholders pour la démonstration ---
53
36
  const CalendarView = ({ settings, model }) => (
54
37
  <div className="p-4 border rounded-md mt-4 bg-gray-50">
55
38
  <h3 className="font-bold">Vue Calendrier</h3>
@@ -349,7 +332,7 @@ function DataLayout() {
349
332
 
350
333
  console.log('Données enregistrées:', data, selectedModel);
351
334
 
352
- event_trigger(recordToEdit ? 'API_ADD_DATA' : 'API_ADD_DATA', {
335
+ Event.Trigger(recordToEdit ? 'API_ADD_DATA' : 'API_ADD_DATA', "custom", "data", {
353
336
  model: selectedModel.name,
354
337
  });
355
338
 
@@ -6,6 +6,8 @@ import {useAuthContext} from "./contexts/AuthContext.jsx";
6
6
  import React, {useEffect, useMemo, useRef, useState} from "react";
7
7
  import {getUserId} from "../../src/data.js";
8
8
  import cronstrue from 'cronstrue/i18n';
9
+ import {Event} from "../../src/events.js";
10
+
9
11
  import {
10
12
  FaBook,
11
13
  FaCopy,
@@ -238,7 +240,7 @@ export function DataTable({
238
240
  console.log('Données non trouvées');
239
241
  }
240
242
 
241
- event_trigger('API_DELETE_DATA', { model: item._model, id: item._id });
243
+ Event.Trigger('API_DELETE_DATA', "custom", "data",{ model: item._model, id: item._id });
242
244
  queryClient.invalidateQueries(['api/data', item._model, 'page', page, elementsPerPage, pagedFilters[item._model], pagedSort[item._model]]);
243
245
 
244
246
  } else {
@@ -15,80 +15,6 @@ const RestoreConfirmationModal = ({ isOpen, onClose, onConfirm, showS3Config = f
15
15
  const { me, fetchMe } = useAuthContext(); // fetchMe pour recharger les données utilisateur après sauvegarde
16
16
  const { addNotification } = useNotificationContext();
17
17
 
18
- // États pour les champs de configuration S3
19
- const [s3Config, setS3Config] = useState({
20
- bucketName: '',
21
- accessKeyId: '',
22
- secretAccessKey: '', // Ne sera pas affiché directement mais envoyé
23
- region: ''
24
- });
25
- const [isSavingS3Config, setIsSavingS3Config] = useState(false);
26
- const [showConfig, setConfigVisible] = useState(false);
27
-
28
- // Charger la configuration S3 existante de l'utilisateur au montage si me.s3Config existe
29
- useEffect(() => {
30
- if (me?.s3Config && showS3Config) {
31
- setS3Config({
32
- bucketName: me.s3Config.bucketName || '',
33
- accessKeyId: me.s3Config.accessKeyId || '',
34
- secretAccessKey: '', // Ne pas pré-remplir la clé secrète pour la sécurité
35
- region: me.s3Config.region || '',
36
- pathPrefix: me.s3Config.pathPrefix || ''
37
- });
38
- } else if (showS3Config) {
39
- // Réinitialiser si pas de config ou si on quitte la section S3
40
- setS3Config({ bucketName: '', accessKeyId: '', secretAccessKey: '', region: '', pathPrefix: '' });
41
- }
42
- }, [me, showS3Config, isOpen]); // Ajouter isOpen pour recharger si la modale est rouverte
43
-
44
-
45
- const handleS3ConfigChange = (e) => {
46
- const { name, value } = e.target;
47
- setS3Config(prev => ({ ...prev, [name]: value }));
48
- };
49
-
50
- const handleSaveS3Config = async () => {
51
- setIsSavingS3Config(true);
52
- // Validation basique côté client
53
- if (!s3Config.bucketName || !s3Config.accessKeyId || !s3Config.region) {
54
- addNotification({ type: 'error', message: t('backup.s3config.validationError', 'Le nom du bucket, l\'Access Key ID et la Région sont requis.') });
55
- setIsSavingS3Config(false);
56
- return;
57
- }
58
-
59
- try {
60
- const payload = { ...s3Config };
61
- // N'envoyer la clé secrète que si elle a été modifiée
62
- if (!payload.secretAccessKey) {
63
- delete payload.secretAccessKey;
64
- }
65
-
66
- const response = await fetch('/api/user/s3-config', { // Endpoint pour sauvegarder la config S3
67
- method: 'POST',
68
- headers: {
69
- 'Content-Type': 'application/json',
70
- // Les en-têtes d'authentification (_user, Authorization) sont gérés globalement par le fetch wrapper si tu en as un, sinon ajoute-les ici
71
- // Exemple:
72
- // '_user': me?.username,
73
- // 'Authorization': `Bearer ${me?.token}`,
74
- },
75
- body: JSON.stringify(payload),
76
- });
77
- const result = await response.json();
78
- if (response.ok && result.success) {
79
- addNotification({ status: 'completed', title: t('backup.s3config.saveSuccess', 'Configuration S3 enregistrée avec succès.') });
80
- } else {
81
- addNotification({ status: 'error', title: result.error || t('backup.s3config.saveError', 'Erreur lors de l\'enregistrement de la configuration S3.') });
82
- }
83
- } catch (error) {
84
- addNotification({ status: 'error', title: t('backup.s3config.saveError', 'Erreur lors de l\'enregistrement de la configuration S3.') });
85
- console.error("Error saving S3 config:", error);
86
- } finally {
87
- setIsSavingS3Config(false);
88
- }
89
- };
90
-
91
-
92
18
  if (!isOpen) {
93
19
  return null;
94
20
  }
@@ -110,71 +36,8 @@ const RestoreConfirmationModal = ({ isOpen, onClose, onConfirm, showS3Config = f
110
36
  <Trans i18nKey="btns.cancel">Annuler</Trans>
111
37
  </Button>
112
38
  </div>
113
- <div className={"flex flex-centered actions"}>
114
- {!showConfig && (<NavLink onClick={() => setConfigVisible(true)}><Trans i18nKey={"backup.s3config.title"}></Trans></NavLink>)}
115
- </div>
116
39
  </>
117
40
 
118
- {showConfig && (<>
119
- <h2>{t('backup.s3config.title', 'Configuration du stockage S3')}</h2>
120
- <p><Trans i18nKey="backup.prez"></Trans></p>
121
- <form className="s3-config-form space-y-4"> {/* Ajout de space-y pour l'espacement vertical */}
122
- <TextField
123
- name="bucketName"
124
- className={"flex flex-1"}
125
- label={t('backup.s3config.bucketName', 'Nom du Bucket S3')}
126
- value={s3Config.bucketName}
127
- onChange={handleS3ConfigChange}
128
- placeholder="my-bucket-name"
129
- required
130
- />
131
- <TextField
132
- name="accessKeyId"
133
- className={"flex flex-1"}
134
- label={t('backup.s3config.accessKeyId', 'Access Key ID AWS')}
135
- value={s3Config.accessKeyId}
136
- onChange={handleS3ConfigChange}
137
- placeholder="AKIAIOSFODNN7EXAMPLE"
138
- required
139
- />
140
- <TextField
141
- name="secretAccessKey"
142
- className={"flex flex-1"}
143
- label={t('backup.s3config.secretAccessKey', 'Secret Access Key AWS')}
144
- type="password" // Important pour masquer la clé
145
- value={s3Config.secretAccessKey}
146
- onChange={handleS3ConfigChange}
147
- placeholder={t('backup.s3config.secretPlaceholder', 'Laisser vide pour ne pas modifier')} />
148
- <TextField
149
- name="region"
150
- className={"flex flex-1"}
151
- label={t('backup.s3config.region', 'Région AWS')}
152
- value={s3Config.region}
153
- onChange={handleS3ConfigChange}
154
- placeholder="eu-west-3"
155
- required
156
- />
157
- <TextField
158
- name="pathPrefix"
159
- label={t('backup.s3config.pathPrefix', 'Préfixe de chemin (Optionnel)')}
160
- value={s3Config.pathPrefix}
161
- onChange={handleS3ConfigChange}
162
- placeholder="saves/my-app/"
163
- help={t('backup.s3config.pathPrefixHelp', 'Ex: "mon-dossier/". Laissez vide pour la racine du bucket.')}
164
- />
165
- <div
166
- className="modal-actions flex justify-end space-x-2 mt-4"> {/* Flex pour aligner les boutons */}
167
- <Button onClick={handleSaveS3Config} disabled={isSavingS3Config}
168
- className="btn-primary"> {/* Classe btn-primary pour le bouton principal */}
169
- {isSavingS3Config ? t('btns.saving', 'Enregistrement...') : t('btns.save', 'Enregistrer la configuration S3')}
170
- </Button>
171
- <Button onClick={onClose} className="btn-secondary">
172
- <Trans i18nKey="btns.cancel">Annuler</Trans>
173
- </Button>
174
- </div>
175
- </form>
176
- </>)}
177
-
178
41
  </div>
179
42
  </Dialog>
180
43
  );
@@ -6,7 +6,7 @@ import { tutorialsConfig } from '../tutorials.js';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { useCallback, useEffect, useMemo, useRef } from 'react';
8
8
  import useLocalStorage from "./useLocalStorage.js";
9
- import { event_off, event_on } from "../../../src/core.js";
9
+ import {Event} from "../../../src/events.js";
10
10
 
11
11
  /**
12
12
  * Hook pour gérer la logique des tutoriels multi-étapes.
@@ -205,10 +205,10 @@ export const useTutorials = () => {
205
205
 
206
206
  console.log(me.activeTutorial + new Date().getMilliseconds());
207
207
  const eventTypes = ['API_ADD_DATA', 'API_EDIT_DATA', 'API_DELETE_DATA'];
208
- eventTypes.forEach(type => event_on(type, handleDataChange));
208
+ eventTypes.forEach(type => Event.Listen(type, handleDataChange, "custom", "data"));
209
209
 
210
210
  return () => {
211
- eventTypes.forEach(type => event_off(type, handleDataChange));
211
+ eventTypes.forEach(type => Event.RemoveCallback(type, handleDataChange, "custom", "data"));
212
212
  };
213
213
  }, [me?.activeTutorial]);
214
214
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "data-primals-engine",
3
- "version": "1.2.1",
4
- "description": "data-primals-engine is the package responsible from handling large amount of data in a practical and performant way. It can handle large amount of data in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation.",
3
+ "version": "1.2.2",
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
5
  "main": "src/engine.js",
6
6
  "type": "module",
7
7
  "scripts": {
package/src/data.js CHANGED
@@ -5,21 +5,21 @@ import {mainFieldsTypes} from "./constants.js";
5
5
 
6
6
  const IV_LENGTH = 16;
7
7
  export function encryptValue(text) {
8
- if (!process.env.S3_CONFIG_ENCRYPTION_KEY) throw new Error("S3_CONFIG_ENCRYPTION_KEY is not set");
8
+ if (!process.env.ENCRYPTION_KEY) throw new Error("ENCRYPTION_KEY is not set");
9
9
  let iv = crypto.randomBytes(IV_LENGTH);
10
- let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(process.env.S3_CONFIG_ENCRYPTION_KEY), iv);
10
+ let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(process.env.ENCRYPTION_KEY), iv);
11
11
  let encrypted = cipher.update(text);
12
12
  encrypted = Buffer.concat([encrypted, cipher.final()]);
13
13
  return iv.toString('hex') + ':' + encrypted.toString('hex');
14
14
  }
15
15
 
16
16
  export function decryptValue(text) {
17
- if (!process.env.S3_CONFIG_ENCRYPTION_KEY) throw new Error("S3_CONFIG_ENCRYPTION_KEY is not set");
17
+ if (!process.env.ENCRYPTION_KEY) throw new Error("ENCRYPTION_KEY is not set");
18
18
  if (!text || typeof text !== 'string' || !text.includes(':')) return text; // ou throw error
19
19
  let textParts = text.split(':');
20
20
  let iv = Buffer.from(textParts.shift(), 'hex');
21
21
  let encryptedText = Buffer.from(textParts.join(':'), 'hex');
22
- let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(process.env.S3_CONFIG_ENCRYPTION_KEY), iv);
22
+ let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(process.env.ENCRYPTION_KEY), iv);
23
23
  let decrypted = decipher.update(encryptedText);
24
24
  decrypted = Buffer.concat([decrypted, decipher.final()]);
25
25
  return decrypted.toString();
package/src/email.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import process from "node:process";
2
2
  import nodemailer from "nodemailer";
3
3
  import juice from "juice";
4
- import {event_trigger} from "./core.js";
4
+ import {Event} from "./events.js";
5
5
  import {emailDefaultConfig} from "./constants.js";
6
6
 
7
7
  // Le transporteur par défaut, utilisé si aucune config spécifique n'est fournie.
@@ -52,7 +52,7 @@ export const sendEmail = async (email = "", data, smtpConfig = null, lang, tpl =
52
52
  // Choisir le transporteur à utiliser
53
53
  const transporter = smtpConfig ? createTransporter(smtpConfig||emailDefaultConfig) : defaultTransporter;
54
54
 
55
- if (tpl === null) tpl = event_trigger("sendEmail:template",data, lang);
55
+ if (tpl === null) tpl = Event.Trigger("sendEmail:template", "system", "calls", data, lang);
56
56
  let html = tpl;
57
57
  try {
58
58
  html = juice(tpl);
package/src/engine.js CHANGED
@@ -19,6 +19,7 @@ import {DefaultUserProvider} from "./providers.js";
19
19
  import formidableMiddleware from 'express-formidable';
20
20
  import sirv from "sirv";
21
21
  import * as tls from "node:tls";
22
+ import {Event} from "./events.js";
22
23
 
23
24
  // Constants
24
25
  const isProduction = process.env.NODE_ENV === 'production'
@@ -79,13 +80,13 @@ export const Engine = {
79
80
  Create: async (options = { app : null}) => {
80
81
  const engine = GameObject.Create("Engine");
81
82
  console.log("Creating engine", Config.Get('modules'));
82
- engine.addComponent(Logger);
83
+ const logger = engine.addComponent(Logger);
83
84
 
84
85
  engine.userProvider = new DefaultUserProvider(engine);
85
86
 
86
87
  engine.setUserProvider = (providerInstance) => {
87
88
  engine.userProvider = providerInstance;
88
- engine.getComponent(Logger).info(`Custom UserProvider '${providerInstance.constructor.name}' has been set.`);
89
+ logger.info(`Custom UserProvider '${providerInstance.constructor.name}' has been set.`);
89
90
  };
90
91
 
91
92
  if (!options.app) {
@@ -130,9 +131,6 @@ export const Engine = {
130
131
  return engine._modules.find(m => m.module === module);
131
132
  };
132
133
 
133
-
134
- const logger = engine.getComponent(Logger);
135
-
136
134
  const importModule = async (module) => {
137
135
  const moduleA = await import(module);
138
136
  if (moduleA.onInit){
@@ -147,13 +145,13 @@ export const Engine = {
147
145
 
148
146
  await Promise.all(Config.Get('modules', []).map(async module => {
149
147
  try {
150
- if( fs.existsSync(module)){
148
+ if( fs.existsSync(module) ){
151
149
  return await importModule(module);
152
150
  }else {
153
151
  return await importModule('./modules/' + module + ".js");
154
152
  }
155
153
  } catch (e){
156
- console.log('ERROR at loading module '+ module + ' in /modules dir.', e.stack);
154
+ logger.info('ERROR at loading module '+ module, e.stack);
157
155
  }
158
156
  })).then(async e => {
159
157
  engine._modules = e;
@@ -202,10 +200,13 @@ export const Engine = {
202
200
  });
203
201
  process.exit(1);
204
202
  });
203
+
204
+ Event.Trigger("OnServerStart", "event", "system", engine);
205
205
  }
206
206
 
207
207
  engine.stop = async () => {
208
208
  await server.close();
209
+ Event.Trigger("OnServerStop", "event", "system", engine);
209
210
  };
210
211
 
211
212
  async function setupInitialModels() {
@@ -228,9 +229,11 @@ export const Engine = {
228
229
  logger.info('Model loaded (' + model.name + ')');
229
230
  }
230
231
  logger.info("All models loaded.");
232
+ Event.Trigger("OnModelsLoaded", "event", "system", engine, dbModels);
231
233
  }
232
234
  engine.resetModels = async () => {
233
235
  await deleteModels();
236
+ Event.Trigger("OnModelsDeleted", "event", "system", engine);
234
237
  };
235
238
  return engine;
236
239
  }
package/src/events.js CHANGED
@@ -4,7 +4,9 @@ const events = {};
4
4
  const eventLayerSystems = {
5
5
  "priority": ["high", "medium", "low"],
6
6
  "log": ["info", "debug", "warn", "error", "critical"],
7
- "system": ["calls"]
7
+ "system": ["calls", "users"],
8
+ "event": ["system","user"],
9
+ "custom": ["data"]
8
10
  };
9
11
 
10
12
 
@@ -13,11 +15,12 @@ export const Event = {
13
15
  Trigger: (name, system = "priority", layer = "medium", ...args) => { // Ajout des arguments system et layer
14
16
  if (!events[system] || !events[system][name] || (layer && !events[system][name][layer])) {
15
17
  //console.warn(`No trigger found for ${name} in system ${system} layer ${layer}`);
16
- return;
18
+ return null;
17
19
  }
18
20
 
19
21
  const systemsToProcess = system ? [system] : Object.keys(events); // Si system est spécifié, on cible ce système uniquement, sinon tous les systèmes
20
22
 
23
+ let ret = null;
21
24
  for (const currentSystem of systemsToProcess) {
22
25
  if (events[currentSystem] && events[currentSystem][name]) {
23
26
  const layersToProcess = layer ? [layer] : eventLayerSystems[currentSystem] || Object.keys(events[currentSystem][name]); // Si layer est spécifié, on cible cette couche, sinon toutes les couches ou celles définies dans eventLayerSystems
@@ -27,7 +30,25 @@ export const Event = {
27
30
  if (events[currentSystem][name][currentLayer]) {
28
31
  for (const callback of events[currentSystem][name][currentLayer]) {
29
32
  try {
30
- callback(...args);
33
+ const res = callback(...args);
34
+ if (typeof res === "object" && !Array.isArray(res)) {
35
+ if (typeof ret !== "object") ret = {};
36
+ ret = {...ret, ...res};
37
+ } else if (Array.isArray(res)) {
38
+ if (!Array.isArray(ret)) ret = [];
39
+ ret = ret.concat(res);
40
+ } else if (typeof res === "string") {
41
+ if (typeof ret !== "string") ret = "";
42
+ ret += res;
43
+ } else if (typeof res === "number") {
44
+ if (typeof ret !== "number") ret = 0;
45
+ ret += res;
46
+ } else if (typeof res === "boolean") {
47
+ if (typeof ret !== "boolean") ret = true;
48
+ ret = res && ret;
49
+ } else {
50
+ ret = res || ret;
51
+ }
31
52
  } catch (error) {
32
53
  console.error(`Error in callback for event ${name} in system ${currentSystem} layer ${currentLayer}:`, error);
33
54
  }
@@ -37,6 +58,7 @@ export const Event = {
37
58
  }
38
59
  }
39
60
  }
61
+ return ret;
40
62
  },
41
63
  Listen: (name = "", callback = () => {}, system = "priority", layer = "medium") => {
42
64
  const validSystems = Object.keys(eventLayerSystems); // Récupération des clés pour une vérification plus performante