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.
- package/README.md +9 -9
- package/client/package-lock.json +8430 -8121
- package/client/package.json +7 -4
- package/client/src/APIInfo.jsx +1 -1
- package/client/src/App.jsx +2 -1
- package/client/src/App.scss +1635 -1626
- package/client/src/AssistantChat.jsx +2 -3
- package/client/src/CalendarView.jsx +1 -0
- package/client/src/ContentView.jsx +3 -3
- package/client/src/Dashboard.jsx +5 -2
- package/client/src/DashboardChart.jsx +1 -0
- package/client/src/DashboardFlexViewItem.jsx +1 -0
- package/client/src/DashboardHtmlViewItem.jsx +1 -0
- package/client/src/DashboardView.jsx +2 -0
- package/client/src/DataEditor.jsx +0 -1
- package/client/src/DataImporter.jsx +489 -468
- package/client/src/DataLayout.jsx +25 -23
- package/client/src/DataTable.jsx +6 -5
- package/client/src/Dialog.jsx +92 -90
- package/client/src/Dialog.scss +122 -116
- package/client/src/DisplayFlexNodeRenderer.jsx +1 -0
- package/client/src/DocumentationPageLayout.scss +1 -1
- package/client/src/FlexBuilderControls.jsx +1 -0
- package/client/src/FlexBuilderPreview.jsx +1 -1
- package/client/src/FlexNode.jsx +1 -1
- package/client/src/HistoryDialog.jsx +3 -2
- package/client/src/KPIWidget.jsx +1 -1
- package/client/src/KanbanView.jsx +1 -0
- package/client/src/ModelCreator.jsx +4 -0
- package/client/src/ModelList.jsx +1 -0
- package/client/src/PackGallery.jsx +5 -4
- package/client/src/RTETrans.jsx +1 -1
- package/client/src/RelationField.jsx +2 -0
- package/client/src/RelationSelectorWidget.jsx +2 -0
- package/client/src/RelationValue.jsx +1 -0
- package/client/src/RestoreDialog.jsx +1 -0
- package/client/src/ViewSwitcher.jsx +1 -1
- package/client/src/WorkflowEditor.jsx +3 -0
- package/client/src/contexts/CommandContext.jsx +2 -1
- package/client/src/contexts/ModelContext.jsx +3 -3
- package/client/src/hooks/data.js +1 -0
- package/client/src/hooks/useValidation.js +1 -0
- package/client/src/translations.js +24 -24
- package/client/vite.config.js +31 -30
- package/doc/AI-assistance.md +87 -63
- package/doc/Concepts.md +122 -0
- package/doc/Custom-Endpoints.md +31 -0
- package/doc/Event-system.md +13 -14
- package/doc/Home.md +33 -0
- package/doc/Modules.md +83 -0
- package/doc/Packs-gallery.md +8 -23
- package/doc/Workflows.md +32 -0
- package/doc/automation-workflows.md +141 -102
- package/doc/dashboards-kpis-charts.md +47 -49
- package/doc/data-management.md +126 -120
- package/doc/data-models.md +68 -75
- package/doc/roles-permissions.md +144 -43
- package/doc/sharding-replication.md +158 -0
- package/doc/users.md +54 -30
- package/package.json +27 -17
- package/server.js +37 -37
- package/src/ai.jobs.js +135 -0
- package/src/constants.js +560 -545
- package/src/data.js +521 -518
- package/src/email.js +157 -154
- package/src/engine.js +167 -49
- package/src/gameObject.js +6 -0
- package/src/i18n.js +0 -1
- package/src/modules/assistant/assistant.js +782 -763
- package/src/modules/assistant/constants.js +23 -16
- package/src/modules/assistant/providers.js +77 -37
- package/src/modules/auth-google/index.js +53 -50
- package/src/modules/bucket.js +346 -335
- package/src/modules/data/data.backup.js +400 -376
- package/src/modules/data/data.cluster.js +559 -0
- package/src/modules/data/data.core.js +11 -8
- package/src/modules/data/data.js +311 -311
- package/src/modules/data/data.operations.js +3666 -3555
- package/src/modules/data/data.relations.js +1 -0
- package/src/modules/data/data.replication.js +125 -0
- package/src/modules/data/data.routes.js +2206 -1879
- package/src/modules/data/data.scheduling.js +2 -1
- package/src/modules/file.js +248 -247
- package/src/modules/mongodb.js +76 -73
- package/src/modules/user.js +18 -2
- package/src/modules/worker-script-runner.js +97 -0
- package/src/modules/workflow.js +177 -52
- package/src/packs.js +5701 -5701
- package/src/providers.js +298 -297
- package/src/sso.js +91 -25
- package/test/assistant.test.js +207 -206
- package/test/cluster.test.js +221 -0
- package/test/core.test.js +0 -2
- package/test/data.integration.test.js +1425 -1416
- package/test/import_export.integration.test.js +210 -210
- package/test/replication.test.js +163 -0
- package/test/workflow.actions.integration.test.js +487 -475
- package/test/workflow.integration.test.js +332 -329
- package/doc/core-concepts.md +0 -33
|
@@ -536,6 +536,7 @@ function prepareDocument(doc, model, me) {
|
|
|
536
536
|
docToProcess._model = model.name;
|
|
537
537
|
docToProcess._user = me.username || me._user;
|
|
538
538
|
docToProcess._hash = getFieldValueHash(model, docToProcess);
|
|
539
|
+
docToProcess._lastModifiedAt = new Date(); // Ajout systématique du timestamp
|
|
539
540
|
|
|
540
541
|
return docToProcess;
|
|
541
542
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Logger } from '../../gameObject.js';
|
|
2
|
+
import { getReplicaNodesForUser } from './data.cluster.js';
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
|
|
5
|
+
const logger = new Logger('DataReplication');
|
|
6
|
+
const replicationQueue = [];
|
|
7
|
+
const failedBatchesByReplica = new Map(); // Map<peerId, Array<chunk>>
|
|
8
|
+
const BATCH_INTERVAL = 100; // Traiter la file toutes les 100ms
|
|
9
|
+
const BATCH_SIZE_LIMIT = 50; // Envoyer un maximum de 50 opérations par requête de batch
|
|
10
|
+
|
|
11
|
+
let intervalId = null;
|
|
12
|
+
let engineInstance = null; // Pour stocker l'instance du moteur
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ajoute une tâche de réplication à la file d'attente.
|
|
16
|
+
* C'est le point d'entrée pour toutes les opérations de données.
|
|
17
|
+
* @param {string} operation - 'insert', 'update', 'delete'
|
|
18
|
+
* @param {string} modelName - Le nom du modèle.
|
|
19
|
+
* @param {object} user - L'objet utilisateur.
|
|
20
|
+
* @param {object} payload - Les données de l'opération.
|
|
21
|
+
*/
|
|
22
|
+
export function queueReplication(operation, modelName, user, payload) {
|
|
23
|
+
replicationQueue.push({ operation, modelName, user, payload, timestamp: new Date() });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Tente de renvoyer les lots qui ont précédemment échoué pour chaque réplique.
|
|
28
|
+
*/
|
|
29
|
+
async function retryFailedBatches() {
|
|
30
|
+
if (failedBatchesByReplica.size === 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
logger.debug(`[ReplicationRetry] Checking ${failedBatchesByReplica.size} replica(s) for failed batches to retry.`);
|
|
35
|
+
|
|
36
|
+
for (const [peerId, batches] of failedBatchesByReplica.entries()) {
|
|
37
|
+
if (batches.length > 0) {
|
|
38
|
+
const batchToRetry = batches.shift(); // On prend le plus ancien lot en échec
|
|
39
|
+
logger.info(`[ReplicationRetry] Retrying to send batch of ${batchToRetry.length} operations to replica peer ${peerId}`);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await engineInstance.sendToPeer(peerId, '/api/internal/replicate', { operations: batchToRetry });
|
|
43
|
+
// Si l'envoi réussit, le lot est retiré de la file. Sinon, il reste pour le prochain essai.
|
|
44
|
+
} catch (error) {
|
|
45
|
+
logger.error(`[ReplicationRetry] Retry failed for peer ${peerId}: ${error.message}. Re-queuing batch.`);
|
|
46
|
+
batches.unshift(batchToRetry); // On le remet au début pour réessayer plus tard
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Traite la file d'attente, groupe les opérations par réplique et les envoie en lots.
|
|
53
|
+
*/
|
|
54
|
+
async function processReplicationQueue() {
|
|
55
|
+
// Si les instances partagent la même base de données, la réplication est inutile.
|
|
56
|
+
if (process.env.CLUSTER_SHARED_DATABASE === 'true') {
|
|
57
|
+
if (replicationQueue.length > 0) replicationQueue.length = 0;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// D'abord, on essaie de vider les anciennes tâches en échec
|
|
62
|
+
await retryFailedBatches();
|
|
63
|
+
|
|
64
|
+
if (replicationQueue.length === 0) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const itemsToProcess = replicationQueue.splice(0, replicationQueue.length);
|
|
69
|
+
logger.debug(`[ReplicationQueue] Processing ${itemsToProcess.length} items.`);
|
|
70
|
+
|
|
71
|
+
// Regrouper les opérations par ID de nœud de réplique
|
|
72
|
+
const batchesByReplica = new Map();
|
|
73
|
+
|
|
74
|
+
for (const item of itemsToProcess) {
|
|
75
|
+
// Pour chaque opération, on trouve les nœuds de réplique responsables
|
|
76
|
+
const replicaNodes = getReplicaNodesForUser(item.user.username);
|
|
77
|
+
|
|
78
|
+
for (const replica of replicaNodes) {
|
|
79
|
+
if (!batchesByReplica.has(replica.id)) {
|
|
80
|
+
batchesByReplica.set(replica.id, []);
|
|
81
|
+
}
|
|
82
|
+
batchesByReplica.get(replica.id).push(item);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Envoyer les lots à chaque réplique concernée
|
|
87
|
+
for (const [peerId, operations] of batchesByReplica.entries()) {
|
|
88
|
+
for (let i = 0; i < operations.length; i += BATCH_SIZE_LIMIT) {
|
|
89
|
+
const chunk = operations.slice(i, i + BATCH_SIZE_LIMIT);
|
|
90
|
+
|
|
91
|
+
logger.info(`[ReplicationBatch] Sending batch of ${chunk.length} operations to replica peer ${peerId}`);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await engineInstance.sendToPeer(peerId, '/api/internal/replicate', { operations: chunk });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
logger.error(`[ReplicationBatch] Failed to send batch to replica peer ${peerId}: ${error.message}. Queueing for retry.`);
|
|
97
|
+
// En cas d'échec, on ajoute le lot à la file d'attente des échecs pour cette réplique
|
|
98
|
+
if (!failedBatchesByReplica.has(peerId)) failedBatchesByReplica.set(peerId, []);
|
|
99
|
+
failedBatchesByReplica.get(peerId).push(chunk);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Démarre le processeur de la file d'attente de réplication.
|
|
107
|
+
* @param {object} engine - L'instance du moteur.
|
|
108
|
+
*/
|
|
109
|
+
export function onInit(engine) {
|
|
110
|
+
engineInstance = engine; // Stocker l'instance du moteur
|
|
111
|
+
if (engineInstance.peers.length > 1) {
|
|
112
|
+
intervalId = setInterval(processReplicationQueue, BATCH_INTERVAL);
|
|
113
|
+
logger.info(`Replication Queue processor started with a ${BATCH_INTERVAL}ms interval.`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Arrête le processeur de la file d'attente.
|
|
119
|
+
*/
|
|
120
|
+
export function stopReplicationQueue() {
|
|
121
|
+
if (intervalId) {
|
|
122
|
+
clearInterval(intervalId);
|
|
123
|
+
logger.info('Replication Queue processor stopped.');
|
|
124
|
+
}
|
|
125
|
+
}
|