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.
Files changed (99) hide show
  1. package/README.md +9 -9
  2. package/client/package-lock.json +8430 -8121
  3. package/client/package.json +7 -4
  4. package/client/src/APIInfo.jsx +1 -1
  5. package/client/src/App.jsx +2 -1
  6. package/client/src/App.scss +1635 -1626
  7. package/client/src/AssistantChat.jsx +2 -3
  8. package/client/src/CalendarView.jsx +1 -0
  9. package/client/src/ContentView.jsx +3 -3
  10. package/client/src/Dashboard.jsx +5 -2
  11. package/client/src/DashboardChart.jsx +1 -0
  12. package/client/src/DashboardFlexViewItem.jsx +1 -0
  13. package/client/src/DashboardHtmlViewItem.jsx +1 -0
  14. package/client/src/DashboardView.jsx +2 -0
  15. package/client/src/DataEditor.jsx +0 -1
  16. package/client/src/DataImporter.jsx +489 -468
  17. package/client/src/DataLayout.jsx +25 -23
  18. package/client/src/DataTable.jsx +6 -5
  19. package/client/src/Dialog.jsx +92 -90
  20. package/client/src/Dialog.scss +122 -116
  21. package/client/src/DisplayFlexNodeRenderer.jsx +1 -0
  22. package/client/src/DocumentationPageLayout.scss +1 -1
  23. package/client/src/FlexBuilderControls.jsx +1 -0
  24. package/client/src/FlexBuilderPreview.jsx +1 -1
  25. package/client/src/FlexNode.jsx +1 -1
  26. package/client/src/HistoryDialog.jsx +3 -2
  27. package/client/src/KPIWidget.jsx +1 -1
  28. package/client/src/KanbanView.jsx +1 -0
  29. package/client/src/ModelCreator.jsx +4 -0
  30. package/client/src/ModelList.jsx +1 -0
  31. package/client/src/PackGallery.jsx +5 -4
  32. package/client/src/RTETrans.jsx +1 -1
  33. package/client/src/RelationField.jsx +2 -0
  34. package/client/src/RelationSelectorWidget.jsx +2 -0
  35. package/client/src/RelationValue.jsx +1 -0
  36. package/client/src/RestoreDialog.jsx +1 -0
  37. package/client/src/ViewSwitcher.jsx +1 -1
  38. package/client/src/WorkflowEditor.jsx +3 -0
  39. package/client/src/contexts/CommandContext.jsx +2 -1
  40. package/client/src/contexts/ModelContext.jsx +3 -3
  41. package/client/src/hooks/data.js +1 -0
  42. package/client/src/hooks/useValidation.js +1 -0
  43. package/client/src/translations.js +24 -24
  44. package/client/vite.config.js +31 -30
  45. package/doc/AI-assistance.md +87 -63
  46. package/doc/Concepts.md +122 -0
  47. package/doc/Custom-Endpoints.md +31 -0
  48. package/doc/Event-system.md +13 -14
  49. package/doc/Home.md +33 -0
  50. package/doc/Modules.md +83 -0
  51. package/doc/Packs-gallery.md +8 -23
  52. package/doc/Workflows.md +32 -0
  53. package/doc/automation-workflows.md +141 -102
  54. package/doc/dashboards-kpis-charts.md +47 -49
  55. package/doc/data-management.md +126 -120
  56. package/doc/data-models.md +68 -75
  57. package/doc/roles-permissions.md +144 -43
  58. package/doc/sharding-replication.md +158 -0
  59. package/doc/users.md +54 -30
  60. package/package.json +27 -17
  61. package/server.js +37 -37
  62. package/src/ai.jobs.js +135 -0
  63. package/src/constants.js +560 -545
  64. package/src/data.js +521 -518
  65. package/src/email.js +157 -154
  66. package/src/engine.js +167 -49
  67. package/src/gameObject.js +6 -0
  68. package/src/i18n.js +0 -1
  69. package/src/modules/assistant/assistant.js +782 -763
  70. package/src/modules/assistant/constants.js +23 -16
  71. package/src/modules/assistant/providers.js +77 -37
  72. package/src/modules/auth-google/index.js +53 -50
  73. package/src/modules/bucket.js +346 -335
  74. package/src/modules/data/data.backup.js +400 -376
  75. package/src/modules/data/data.cluster.js +559 -0
  76. package/src/modules/data/data.core.js +11 -8
  77. package/src/modules/data/data.js +311 -311
  78. package/src/modules/data/data.operations.js +3666 -3555
  79. package/src/modules/data/data.relations.js +1 -0
  80. package/src/modules/data/data.replication.js +125 -0
  81. package/src/modules/data/data.routes.js +2206 -1879
  82. package/src/modules/data/data.scheduling.js +2 -1
  83. package/src/modules/file.js +248 -247
  84. package/src/modules/mongodb.js +76 -73
  85. package/src/modules/user.js +18 -2
  86. package/src/modules/worker-script-runner.js +97 -0
  87. package/src/modules/workflow.js +177 -52
  88. package/src/packs.js +5701 -5701
  89. package/src/providers.js +298 -297
  90. package/src/sso.js +91 -25
  91. package/test/assistant.test.js +207 -206
  92. package/test/cluster.test.js +221 -0
  93. package/test/core.test.js +0 -2
  94. package/test/data.integration.test.js +1425 -1416
  95. package/test/import_export.integration.test.js +210 -210
  96. package/test/replication.test.js +163 -0
  97. package/test/workflow.actions.integration.test.js +487 -475
  98. package/test/workflow.integration.test.js +332 -329
  99. 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
+ }