data-primals-engine 1.0.7 → 1.0.9
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/.github/workflows/node.js.yml +59 -0
- package/.github/workflows/npm-publish.yml +23 -0
- package/Dockerfile +15 -0
- package/DockerfileTest +15 -0
- package/package.json +4 -7
- package/server.js +11 -2
- package/src/data.js +15 -5
- package/src/email.js +2 -0
- package/src/engine.js +42 -40
- package/src/gameObject.js +5 -7
- package/src/migrate.js +1 -1
- package/src/modules/data.js +87 -61
- package/src/modules/mongodb.js +1 -8
- package/src/modules/workflow.js +190 -58
- package/src/packs.js +26 -10
- package/src/setenv.js +39 -0
- package/test/data.backup.integration.test.js +141 -0
- package/test/data.integration.test.js +796 -0
- package/test/globalSetup.js +15 -0
- package/test/globalTeardown.js +8 -0
- package/test/import_export.integration.test.js +200 -0
- package/test/workflow.integration.test.js +314 -0
- package/test/workflow.robustness.test.js +195 -0
- package/vitest.config.js +16 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
+
|
|
4
|
+
name: Node.js CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "develop", "main" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "develop", "main" ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
container:
|
|
17
|
+
image: node:18 # Specify the Docker version you needx
|
|
18
|
+
env:
|
|
19
|
+
NODE_OPTIONS: "--max_old_space_size=4096"
|
|
20
|
+
NODE_ENV: development
|
|
21
|
+
ports:
|
|
22
|
+
- 7635
|
|
23
|
+
- 27017
|
|
24
|
+
strategy:
|
|
25
|
+
matrix:
|
|
26
|
+
node-version: [18.x]
|
|
27
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
28
|
+
services:
|
|
29
|
+
mongodb:
|
|
30
|
+
image: mongo:5.0
|
|
31
|
+
ports:
|
|
32
|
+
- 27017:27017
|
|
33
|
+
options: >-
|
|
34
|
+
--health-cmd "mongo --eval 'db.serverStatus()'"
|
|
35
|
+
--health-interval 10s
|
|
36
|
+
--health-timeout 5s
|
|
37
|
+
--health-retries 5
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
|
|
41
|
+
- name: Install MongoDB Tools
|
|
42
|
+
run: |
|
|
43
|
+
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add -
|
|
44
|
+
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list
|
|
45
|
+
apt-get update
|
|
46
|
+
apt-get install -y mongodb-org-tools
|
|
47
|
+
|
|
48
|
+
- name: Set up Node.js
|
|
49
|
+
uses: actions/setup-node@v2
|
|
50
|
+
with:
|
|
51
|
+
node-version: '18.18.2'
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: npm install
|
|
55
|
+
|
|
56
|
+
- name: Run tests
|
|
57
|
+
run: npm test
|
|
58
|
+
|
|
59
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Publish package to GitHub Packages
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
packages: write
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
# Setup .npmrc file to publish to GitHub Packages
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
node-version: '20.x'
|
|
17
|
+
registry-url: 'https://npm.pkg.github.com'
|
|
18
|
+
# Defaults to the user or organization that owns the workflow file
|
|
19
|
+
scope: '@octocat'
|
|
20
|
+
- run: npm ci
|
|
21
|
+
- run: npm publish
|
|
22
|
+
env:
|
|
23
|
+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Base image
|
|
2
|
+
FROM node:18.18.2
|
|
3
|
+
|
|
4
|
+
# Set working directory
|
|
5
|
+
WORKDIR /
|
|
6
|
+
|
|
7
|
+
# Copy package files and install dependencies
|
|
8
|
+
COPY package.json ./
|
|
9
|
+
RUN npm install
|
|
10
|
+
|
|
11
|
+
# Copy application code
|
|
12
|
+
COPY . .
|
|
13
|
+
|
|
14
|
+
# Expose the application port
|
|
15
|
+
EXPOSE 7633
|
package/DockerfileTest
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Base image
|
|
2
|
+
FROM node:18.18.2
|
|
3
|
+
|
|
4
|
+
# Set working directory
|
|
5
|
+
WORKDIR /
|
|
6
|
+
|
|
7
|
+
# Copy package files and install dependencies
|
|
8
|
+
COPY package.json ./
|
|
9
|
+
RUN npm install
|
|
10
|
+
|
|
11
|
+
# Copy application code
|
|
12
|
+
COPY . .
|
|
13
|
+
|
|
14
|
+
# Expose the application port
|
|
15
|
+
EXPOSE 7635
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-primals-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
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.",
|
|
5
5
|
"main": "src/engine.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"test": "cross-env MONGO_DB_URL=\"mongodb://localhost:27017\" PORT=7635 vitest --no-file-parallelism",
|
|
8
9
|
"preinstall": "npx force-resolutions",
|
|
9
10
|
"devserver": "cross-env NODE_ENV=development node server.js",
|
|
10
11
|
"server": "cross-env NODE_ENV=production node server.js",
|
|
@@ -39,11 +40,9 @@
|
|
|
39
40
|
"chalk": "^5.4.1",
|
|
40
41
|
"check-disk-space": "^3.4.0",
|
|
41
42
|
"compression": "^1.8.0",
|
|
42
|
-
"connect-mongo": "^5.1.0",
|
|
43
43
|
"cookie-parser": "^1.4.7",
|
|
44
44
|
"cronstrue": "^3.1.0",
|
|
45
45
|
"csv-parse": "^5.6.0",
|
|
46
|
-
"csv-parser": "^3.2.0",
|
|
47
46
|
"date-fns": "^4.1.0",
|
|
48
47
|
"express": "^4.21.2",
|
|
49
48
|
"express-csrf-double-submit-cookie": "^1.2.1",
|
|
@@ -61,7 +60,6 @@
|
|
|
61
60
|
"nodemailer": "^6.10.0",
|
|
62
61
|
"openai": "^5.8.2",
|
|
63
62
|
"randomcolor": "^0.6.2",
|
|
64
|
-
"rate-limiter-flexible": "^5.0.5",
|
|
65
63
|
"react-helmet": "^6.1.0",
|
|
66
64
|
"react-i18next": "^15.4.1",
|
|
67
65
|
"react-markdown": "^10.1.0",
|
|
@@ -70,10 +68,9 @@
|
|
|
70
68
|
"sirv": "^3.0.1",
|
|
71
69
|
"swagger-ui-express": "^5.0.1",
|
|
72
70
|
"tar": "^7.4.3",
|
|
73
|
-
"tar-fs": "^3.0.8",
|
|
74
71
|
"uniqid": "^5.4.0",
|
|
75
|
-
"
|
|
76
|
-
"
|
|
72
|
+
"vitest": "^3.2.3",
|
|
73
|
+
"yaml": "^2.8.0"
|
|
77
74
|
},
|
|
78
75
|
"devDependencies": {
|
|
79
76
|
"@eslint/js": "^9.17.0",
|
package/server.js
CHANGED
|
@@ -13,11 +13,20 @@ const bench = GameObject.Create("Benchmark");
|
|
|
13
13
|
const timer = bench.addComponent(BenchmarkTool);
|
|
14
14
|
timer.start();
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
const engine = await Engine.Create();
|
|
18
|
+
|
|
19
|
+
if (process.argv.length === 3) {
|
|
20
|
+
let arg = process.argv[2];
|
|
21
|
+
if( arg === 'reset-models'){
|
|
22
|
+
console.log("resetting models");
|
|
23
|
+
await engine.resetModels();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
17
26
|
|
|
18
27
|
const port = process.env.PORT || 7633;
|
|
19
28
|
engine.start(port, async (r) => {
|
|
20
29
|
const logger = engine.getComponent(Logger);
|
|
21
|
-
|
|
30
|
+
console.log("Server started on port" + port);
|
|
22
31
|
timer.stop();
|
|
23
32
|
});
|
package/src/data.js
CHANGED
|
@@ -29,8 +29,12 @@ export const isLocalUser = (user) => {
|
|
|
29
29
|
return user && user._model === 'user' && typeof(user._user) === 'string' && user._user.trim() !== '';
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
export const isDemoUser = (user) => {
|
|
33
|
+
return /^demo[0-9]{1,2}$/.test(user?.username);
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
export function getUserHash(user) {
|
|
33
|
-
if(
|
|
37
|
+
if( isDemoUser(user) ){
|
|
34
38
|
return user.username;
|
|
35
39
|
}
|
|
36
40
|
return user ? (
|
|
@@ -419,8 +423,9 @@ export const getFieldValueHash = (model, data) => {
|
|
|
419
423
|
* @param {array} allModels - Un tableau contenant les définitions de TOUS les modèles du système.
|
|
420
424
|
* @returns {string} - La chaîne de caractères représentant la donnée.
|
|
421
425
|
*/
|
|
422
|
-
export const getDataAsString = (model, data,
|
|
423
|
-
|
|
426
|
+
export const getDataAsString = (model, data, tr, allModels, extended=false) => {
|
|
427
|
+
const { t, i18n} = tr;
|
|
428
|
+
const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
|
|
424
429
|
// Cas de base : si le modèle ou les données sont manquants, on ne peut rien faire.
|
|
425
430
|
if (!model || !data) {
|
|
426
431
|
return '';
|
|
@@ -464,7 +469,6 @@ export const getDataAsString = (model, data, t, allModels, extended=false) => {
|
|
|
464
469
|
const relatedModel = allModels?.find(m => m.name === fieldDef.relation);
|
|
465
470
|
if (!relatedModel) return `[${fieldDef.relation}]`; // Modnon trouvé
|
|
466
471
|
|
|
467
|
-
console.log({relatedModel, value});
|
|
468
472
|
// Si la relation est multiple (un tableau d'objets)
|
|
469
473
|
if (Array.isArray(value)) {
|
|
470
474
|
return value
|
|
@@ -474,10 +478,16 @@ export const getDataAsString = (model, data, t, allModels, extended=false) => {
|
|
|
474
478
|
}
|
|
475
479
|
// Si la relation est simple (un seul objet)
|
|
476
480
|
else if (typeof value === 'object') {
|
|
477
|
-
console.log({relatedModel, value});
|
|
478
481
|
return getDataAsString(relatedModel, value, t, allModels); // Appel récursif
|
|
479
482
|
}
|
|
480
483
|
}
|
|
484
|
+
|
|
485
|
+
if(fieldDef.type === 'datetime'){
|
|
486
|
+
return new Date(value).toLocaleDateString(lang, {year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric'});
|
|
487
|
+
}
|
|
488
|
+
if(fieldDef.type === 'date'){
|
|
489
|
+
return new Date(value).toLocaleDateString(lang, {year: 'numeric', month: 'numeric', day: 'numeric'});
|
|
490
|
+
}
|
|
481
491
|
// --- FIN DE LA NOUVELLE LOGIQUE ---
|
|
482
492
|
|
|
483
493
|
// Logique existante pour les autres types de champs
|
package/src/email.js
CHANGED
|
@@ -2,6 +2,8 @@ import { translations } from "data-primals-engine/i18n";
|
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import nodemailer from "nodemailer";
|
|
4
4
|
import juice from "juice";
|
|
5
|
+
import {event_trigger} from "./core.js";
|
|
6
|
+
import {emailDefaultConfig} from "./constants.js";
|
|
5
7
|
|
|
6
8
|
// Le transporteur par défaut, utilisé si aucune config spécifique n'est fournie.
|
|
7
9
|
const defaultTransporter = nodemailer.createTransport({
|
package/src/engine.js
CHANGED
|
@@ -8,7 +8,7 @@ import {cookiesSecret, dbName} from "./constants.js";
|
|
|
8
8
|
import http from "http";
|
|
9
9
|
import cookieParser from "cookie-parser";
|
|
10
10
|
import requestIp from 'request-ip';
|
|
11
|
-
import {createModel, getModels, validateModelStructure} from "./modules/data.js";
|
|
11
|
+
import {createModel, deleteModels, getModels, validateModelStructure} from "./modules/data.js";
|
|
12
12
|
import {defaultModels} from "./defaultModels.js";
|
|
13
13
|
import {DefaultUserProvider} from "./providers.js";
|
|
14
14
|
import formidableMiddleware from 'express-formidable';
|
|
@@ -17,7 +17,7 @@ import formidableMiddleware from 'express-formidable';
|
|
|
17
17
|
const isProduction = process.env.NODE_ENV === 'production'
|
|
18
18
|
|
|
19
19
|
// Connection URL
|
|
20
|
-
export const dbUrl = process.env.MONGO_DB_URL || 'mongodb://127.0.0.1:27017';
|
|
20
|
+
export const dbUrl = process.env.CI ? 'mongodb://mongodb:27017' : (process.env.MONGO_DB_URL || 'mongodb://127.0.0.1:27017');
|
|
21
21
|
export const MongoClient = new InternalMongoClient(dbUrl, { maxPoolSize: 20 });
|
|
22
22
|
|
|
23
23
|
// Database Name
|
|
@@ -25,9 +25,10 @@ export const MongoDatabase = MongoClient.db(dbName);
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
export const Engine = {
|
|
28
|
-
Create:
|
|
28
|
+
Create: async (options) => {
|
|
29
29
|
const engine = GameObject.Create("Engine");
|
|
30
30
|
console.log("Creating engine", Config.Get('modules'));
|
|
31
|
+
engine.addComponent(Logger);
|
|
31
32
|
|
|
32
33
|
engine.userProvider = new DefaultUserProvider(engine);
|
|
33
34
|
|
|
@@ -71,10 +72,38 @@ export const Engine = {
|
|
|
71
72
|
return engine._modules.find(m => m.module === module);
|
|
72
73
|
};
|
|
73
74
|
|
|
75
|
+
|
|
76
|
+
const logger = engine.getComponent(Logger);
|
|
77
|
+
|
|
78
|
+
const importModule = async (module) => {
|
|
79
|
+
const moduleA = await import(module);
|
|
80
|
+
if (moduleA.onInit){
|
|
81
|
+
await moduleA.onInit(engine);
|
|
82
|
+
return {...moduleA, module};
|
|
83
|
+
}else {
|
|
84
|
+
const mod = moduleA.default();
|
|
85
|
+
await mod?.onInit(engine);
|
|
86
|
+
return { ...mod, module};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
await Promise.all(Config.Get('modules', []).map(async module => {
|
|
91
|
+
try {
|
|
92
|
+
if( fs.existsSync(module)){
|
|
93
|
+
return await importModule(module);
|
|
94
|
+
}else {
|
|
95
|
+
return await importModule('./modules/' + module + ".js");
|
|
96
|
+
}
|
|
97
|
+
} catch (e){
|
|
98
|
+
console.log('ERROR at loading module '+ module + ' in /modules dir.'+ e);
|
|
99
|
+
}
|
|
100
|
+
})).then(async e => {
|
|
101
|
+
engine._modules = e;
|
|
102
|
+
return Promise.resolve();
|
|
103
|
+
});
|
|
74
104
|
let server;
|
|
75
105
|
engine.start = async (port, cb) =>{
|
|
76
106
|
// Use connect method to connect to the server
|
|
77
|
-
await MongoClient.connect();
|
|
78
107
|
|
|
79
108
|
// Start http server
|
|
80
109
|
server = http.createServer(app);
|
|
@@ -87,37 +116,11 @@ export const Engine = {
|
|
|
87
116
|
|
|
88
117
|
server.listen(port);
|
|
89
118
|
|
|
90
|
-
const importModule = async (module) => {
|
|
91
|
-
const moduleA = await import(module);
|
|
92
|
-
if (moduleA.onInit){
|
|
93
|
-
await moduleA.onInit(engine);
|
|
94
|
-
return {...moduleA, module};
|
|
95
|
-
}else {
|
|
96
|
-
const mod = moduleA.default();
|
|
97
|
-
await mod?.onInit(engine);
|
|
98
|
-
return { ...mod, module};
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
await Promise.all(Config.Get('modules').map(async module => {
|
|
103
|
-
try {
|
|
104
|
-
if( fs.existsSync(module)){
|
|
105
|
-
return await importModule(module);
|
|
106
|
-
}else {
|
|
107
|
-
return await importModule('./modules/' + module + ".js");
|
|
108
|
-
}
|
|
109
|
-
} catch (e){
|
|
110
|
-
console.log('ERROR at loading module '+ module + ' in /modules dir.'+ e);
|
|
111
|
-
}
|
|
112
|
-
})).then(async e => {
|
|
113
|
-
engine._modules = e;
|
|
114
|
-
if (cb)
|
|
115
|
-
return await cb();
|
|
116
|
-
return Promise.resolve();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
119
|
await setupInitialModels();
|
|
120
120
|
|
|
121
|
+
if (cb)
|
|
122
|
+
await cb();
|
|
123
|
+
|
|
121
124
|
process.on('uncaughtException', function (exception) {
|
|
122
125
|
console.error(exception);
|
|
123
126
|
fs.appendFile('issues.txt', JSON.stringify({ code: exception.code, message: exception.message, stack: exception.stack }), function (err) {
|
|
@@ -128,14 +131,11 @@ export const Engine = {
|
|
|
128
131
|
process.exit(0);
|
|
129
132
|
});
|
|
130
133
|
}
|
|
131
|
-
engine.addComponent(Logger);
|
|
132
134
|
|
|
133
135
|
engine.stop = async () => {
|
|
134
136
|
await server.close();
|
|
135
137
|
};
|
|
136
138
|
|
|
137
|
-
const logger = engine.getComponent(Logger);
|
|
138
|
-
|
|
139
139
|
async function setupInitialModels() {
|
|
140
140
|
logger.info("Validating structures of default models...");
|
|
141
141
|
const ms = Object.values(Config.Get('defaultModels', defaultModels));
|
|
@@ -151,13 +151,15 @@ export const Engine = {
|
|
|
151
151
|
model.locked = true;
|
|
152
152
|
const r = await createModel(model);
|
|
153
153
|
dbModels.push({...model, _id: r.insertedId });
|
|
154
|
-
|
|
155
|
-
else
|
|
156
|
-
|
|
154
|
+
logger.info('Model inserted (' + model.name + ')');
|
|
155
|
+
}else
|
|
156
|
+
logger.info('Model loaded (' + model.name + ')');
|
|
157
157
|
}
|
|
158
158
|
logger.info("All models loaded.");
|
|
159
159
|
}
|
|
160
|
-
|
|
160
|
+
engine.resetModels = async () => {
|
|
161
|
+
await deleteModels();
|
|
162
|
+
};
|
|
161
163
|
return engine;
|
|
162
164
|
}
|
|
163
165
|
}
|
package/src/gameObject.js
CHANGED
|
@@ -70,23 +70,21 @@ export class UsableBehaviour extends Behaviour {
|
|
|
70
70
|
|
|
71
71
|
use() {
|
|
72
72
|
Event.Trigger("GameObject.UsableBehavior.use", "system", "calls", this);
|
|
73
|
-
// Logique d'utilisation (exemple)
|
|
74
|
-
console.log(this.gameObject.name + " a été utilisé !");
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
const
|
|
76
|
+
const mainDriver = GameObject.Create("MainDrivers");
|
|
79
77
|
|
|
80
78
|
// Exemple d'attachement de comportements
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
mainDriver.addComponent(MovableBehaviour, 10);
|
|
80
|
+
mainDriver.addComponent(UsableBehaviour);
|
|
83
81
|
|
|
84
82
|
// Accéder et utiliser les composants
|
|
85
|
-
const movable =
|
|
83
|
+
const movable = mainDriver.getComponent(MovableBehaviour);
|
|
86
84
|
if (movable) {
|
|
87
85
|
movable.update();
|
|
88
86
|
}
|
|
89
|
-
const usable =
|
|
87
|
+
const usable = mainDriver.getComponent(UsableBehaviour);
|
|
90
88
|
if (usable) {
|
|
91
89
|
usable.use();
|
|
92
90
|
}
|
package/src/migrate.js
CHANGED
|
@@ -15,7 +15,7 @@ Config.Set("modules", ["mongodb"]);
|
|
|
15
15
|
const MIGRATIONS_DIR = path.resolve(process.cwd(), 'server', 'src', 'migrations');
|
|
16
16
|
const MIGRATIONS_COLLECTION = 'migrations_log';
|
|
17
17
|
|
|
18
|
-
const engine = Engine.Create();
|
|
18
|
+
const engine = await Engine.Create();
|
|
19
19
|
const port = process.env.MIGRATE_PORT || 7640;
|
|
20
20
|
|
|
21
21
|
engine.start(port, async () => {
|