data-primals-engine 1.7.1 → 1.7.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.
Files changed (36) hide show
  1. package/README.md +160 -160
  2. package/client/package-lock.json +484 -175
  3. package/client/package.json +7 -4
  4. package/client/src/AssistantChat.jsx +1 -3
  5. package/client/src/DataLayout.jsx +19 -20
  6. package/client/src/DataTable.jsx +2 -2
  7. package/client/src/DocumentationPageLayout.scss +1 -1
  8. package/client/src/ViewSwitcher.jsx +1 -1
  9. package/client/vite.config.js +31 -30
  10. package/package.json +27 -17
  11. package/src/ai.jobs.js +135 -0
  12. package/src/constants.js +561 -545
  13. package/src/data.js +2 -0
  14. package/src/engine.js +50 -42
  15. package/src/modules/assistant/assistant.js +782 -763
  16. package/src/modules/assistant/constants.js +23 -16
  17. package/src/modules/assistant/providers.js +77 -37
  18. package/src/modules/bucket.js +4 -0
  19. package/src/modules/data/data.cluster.js +191 -0
  20. package/src/modules/data/data.core.js +11 -8
  21. package/src/modules/data/data.js +311 -311
  22. package/src/modules/data/data.operations.js +186 -106
  23. package/src/modules/data/data.relations.js +1 -0
  24. package/src/modules/data/data.replication.js +83 -0
  25. package/src/modules/data/data.routes.js +2183 -1879
  26. package/src/modules/mongodb.js +76 -73
  27. package/src/modules/user.js +7 -1
  28. package/src/modules/worker-script-runner.js +97 -0
  29. package/src/modules/workflow.js +177 -52
  30. package/src/packs.js +5701 -5701
  31. package/src/providers.js +298 -297
  32. package/test/assistant.test.js +207 -206
  33. package/test/data.integration.test.js +1425 -1416
  34. package/test/import_export.integration.test.js +210 -210
  35. package/test/workflow.actions.integration.test.js +487 -475
  36. package/test/workflow.integration.test.js +332 -329
@@ -1,207 +1,208 @@
1
- // __tests__/assistant.test.js
2
- import { vi, expect, describe, it, beforeAll, afterAll, beforeEach } from 'vitest';
3
- import { Config } from '../src/config.js';
4
- import { getCollection, getCollectionForUser } from '../src/modules/mongodb.js';
5
- import { generateUniqueName, initEngine } from "../src/setenv.js";
6
- import { purgeData } from "../src/modules/data/data.history.js";
7
- import { handleChatRequest } from '../src/modules/assistant/assistant.js';
8
- import { insertData } from '../src/index.js';
9
- import * as dataOperations from '../src/modules/data/data.operations.js';
10
-
11
- // --- CORRECTION ---
12
- // On mock le nouveau module 'providers.js' qui contient la fonction que l'on veut surcharger.
13
- vi.mock('../src/modules/assistant/providers.js', () => ({
14
- getAIProvider: vi.fn(),
15
- }));
16
-
17
- // On importe la version mockée de getAIProvider
18
- import { getAIProvider } from '../src/modules/assistant/providers.js';
19
-
20
- let testUser;
21
- let modelsCollection;
22
- let datasCollection;
23
-
24
- const modelName = generateUniqueName('productAssistant');
25
-
26
- describe('Assistant Module Unit Tests', () => {
27
-
28
- beforeAll(async () => {
29
- Config.Set("modules", ["mongodb", "data", "user", "assistant"]);
30
- await initEngine(); // Cette ligne ne devrait plus planter.
31
- testUser = {
32
- username: generateUniqueName('testUserAssistant'),
33
- email: generateUniqueName('test') + '@example.com',
34
- lang: 'fr'
35
- };
36
-
37
- modelsCollection = getCollection('models');
38
- datasCollection = await getCollectionForUser(testUser);
39
-
40
- // Create a model for testing
41
- const productModelDef = {
42
- name: modelName,
43
- description: 'A model for testing',
44
- _user: testUser.username,
45
- fields: [
46
- { name: 'name', type: 'string', required: true },
47
- { name: 'price', type: 'number' }
48
- ]
49
- };
50
- await modelsCollection.insertOne(productModelDef);
51
- });
52
-
53
- beforeEach(async () => {
54
- // Clear mocks before each test
55
- vi.clearAllMocks();
56
- // Clean data, but keep the model
57
- await datasCollection.deleteMany({ _user: testUser.username });
58
- });
59
-
60
- // Helper to create a mock LLM stream
61
- const createMockStream = (content) => {
62
- return (async function* () {
63
- yield { content };
64
- })();
65
- };
66
-
67
- it('should return a simple text message from the AI', async () => {
68
- const mockLLM = {
69
- stream: vi.fn().mockReturnValue(createMockStream('Bonjour, ceci est un test.')),
70
- };
71
- getAIProvider.mockResolvedValue(mockLLM);
72
-
73
- const params = { message: 'Dis bonjour', history: [] };
74
- const result = await handleChatRequest(params, testUser);
75
-
76
- expect(getAIProvider).toHaveBeenCalled();
77
- expect(mockLLM.stream).toHaveBeenCalled();
78
- expect(result).toEqual({
79
- success: true,
80
- displayMessage: 'Bonjour, ceci est un test.'
81
- });
82
- });
83
-
84
- it('should handle a search action', async () => {
85
-
86
- // Configure le retour du mock pour ce test spécifique
87
- const searchDataSpy = vi.spyOn(dataOperations, 'searchData').mockResolvedValue({ data: [{ name: 'Test Widget', price: 100 }], count: 1 });
88
-
89
- const aiResponse = JSON.stringify([
90
- { action: 'search_models', params: { query: modelName } },
91
- { action: 'search', params: { model: modelName, filter: {} } }
92
- ]);
93
- const mockLLM = {
94
- stream: vi.fn().mockReturnValue(createMockStream(aiResponse))
95
- };
96
- getAIProvider.mockResolvedValue(mockLLM);
97
-
98
- const params = { message: `cherche les produits`, history: [] };
99
- const result = await handleChatRequest(params, testUser);
100
-
101
- expect(searchDataSpy).toHaveBeenCalled();
102
-
103
- expect(result.success).toBe(true);
104
- expect(result.dataResult).toBeDefined();
105
- expect(result.dataResult.model).toBe(modelName);
106
- expect(result.dataResult.data).toHaveLength(1);
107
- expect(result.dataResult.data[0].name).toBe('Test Widget');
108
- });
109
-
110
- it('should request confirmation for a "post" action', async () => {
111
- const aiResponse = JSON.stringify({
112
- action: 'post',
113
- params: {
114
- model: modelName,
115
- data: { name: 'New Gadget', price: 19.99 }
116
- }
117
- });
118
- const mockLLM = {
119
- stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
120
- };
121
- getAIProvider.mockResolvedValue(mockLLM);
122
-
123
- const params = { message: 'Crée un produit', history: [] };
124
- const result = await handleChatRequest(params, testUser);
125
-
126
- expect(result.success).toBe(true);
127
- expect(result.confirmationRequest).toBeDefined();
128
- expect(result.confirmationRequest.action).toBe('post');
129
- expect(result.confirmationRequest.params.model).toBe(modelName);
130
- expect(result.confirmationRequest.params.data.name).toBe('New Gadget');
131
- });
132
-
133
- it('should execute a confirmed "post" action', async () => {
134
- const confirmedAction = {
135
- action: 'post',
136
- params: {
137
- model: modelName,
138
- data: { name: 'Confirmed Gadget', price: 50 }
139
- }
140
- };
141
-
142
- const params = { message: '', confirmedAction };
143
- const result = await handleChatRequest(params, testUser);
144
-
145
- expect(result.success).toBe(true);
146
- // --- CORRECTION ---
147
- // On vérifie le message de succès spécifique à la création, qui est plus informatif.
148
- expect(result.displayMessage).toContain("Élément créé avec l'ID:");
149
-
150
- // Verify the data was actually inserted
151
- const dataInDb = await datasCollection.findOne({ name: 'Confirmed Gadget' });
152
- expect(dataInDb).not.toBeNull();
153
- expect(dataInDb.price).toBe(50);
154
- });
155
-
156
- it('should handle malformed JSON from AI by returning it as a text message', async () => {
157
- const aiResponse = `{ "action": "search", "params": { "model": "${modelName}" }`; // Missing closing brace
158
- const mockLLM = {
159
- stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
160
- };
161
- getAIProvider.mockResolvedValue(mockLLM);
162
-
163
- const params = { message: 'test', history: [] };
164
- const result = await handleChatRequest(params, testUser);
165
-
166
- expect(result.success).toBe(true);
167
- expect(result.displayMessage).toBe(aiResponse);
168
- });
169
-
170
- it('should correct a malformed filter from the AI', async () => {
171
- const aiResponse = JSON.stringify([{
172
- action: 'search',
173
- params: {
174
- model: modelName,
175
- // This filter is malformed: multiple operators at the root level
176
- filter: {
177
- "$gt": ["$price", 50],
178
- "$lt": ["$price", 200]
179
- }
180
- }
181
- }]);
182
- const mockLLM = {
183
- stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
184
- };
185
- getAIProvider.mockResolvedValue(mockLLM);
186
-
187
- // Configure le retour du mock pour ce test spécifique
188
- const searchDataSpy = vi.spyOn(dataOperations, 'searchData').mockResolvedValue({ data: [], count: 0 });
189
-
190
- const params = { message: 'cherche produits entre 50 et 200', history: [] };
191
- // On peut maintenant utiliser la fonction handleChatRequest importée normalement
192
- await handleChatRequest(params, testUser);
193
-
194
- // On s'attend à ce que le filtre ait été encapsulé dans un $and
195
- const expectedFilter = {
196
- "$and": [
197
- { "$gt": ["$price", 50] },
198
- { "$lt": ["$price", 200] }
199
- ]
200
- };
201
-
202
- expect(searchDataSpy).toHaveBeenCalledWith(expect.objectContaining({ filter: expectedFilter }), expect.anything());
203
-
204
- // On nettoie l'espion après le test
205
- searchDataSpy.mockRestore();
206
- });
1
+ // __tests__/assistant.test.js
2
+ import { vi, expect, describe, it, beforeAll, afterAll, beforeEach } from 'vitest';
3
+ import { Config } from '../src/config.js';
4
+ import { getCollection, getCollectionForUser } from '../src/modules/mongodb.js';
5
+ import { generateUniqueName, initEngine } from "../src/setenv.js";
6
+ import { purgeData } from "../src/modules/data/data.history.js";
7
+ import { handleChatRequest } from '../src/modules/assistant/assistant.js';
8
+ import { insertData } from '../src/index.js';
9
+ import * as dataOperations from '../src/modules/data/data.operations.js';
10
+
11
+ // --- CORRECTION ---
12
+ // On mock le nouveau module 'providers.js' qui contient la fonction que l'on veut surcharger.
13
+ vi.mock('../src/modules/assistant/providers.js', () => ({
14
+ getAIProvider: vi.fn(),
15
+ findFirstAvailableProvider: vi.fn().mockResolvedValue({ provider: 'OpenAI', apiKey: 'mock-api-key' }),
16
+ }));
17
+
18
+ // On importe la version mockée de getAIProvider
19
+ import { getAIProvider, findFirstAvailableProvider } from '../src/modules/assistant/providers.js';
20
+
21
+ let testUser;
22
+ let modelsCollection;
23
+ let datasCollection;
24
+
25
+ const modelName = generateUniqueName('productAssistant');
26
+
27
+ describe('Assistant Module Unit Tests', () => {
28
+
29
+ beforeAll(async () => {
30
+ Config.Set("modules", ["mongodb", "data", "user", "assistant"]);
31
+ await initEngine(); // Cette ligne ne devrait plus planter.
32
+ testUser = {
33
+ username: generateUniqueName('testUserAssistant'),
34
+ email: generateUniqueName('test') + '@example.com',
35
+ lang: 'fr'
36
+ };
37
+
38
+ modelsCollection = getCollection('models');
39
+ datasCollection = await getCollectionForUser(testUser);
40
+
41
+ // Create a model for testing
42
+ const productModelDef = {
43
+ name: modelName,
44
+ description: 'A model for testing',
45
+ _user: testUser.username,
46
+ fields: [
47
+ { name: 'name', type: 'string', required: true },
48
+ { name: 'price', type: 'number' }
49
+ ]
50
+ };
51
+ await modelsCollection.insertOne(productModelDef);
52
+ });
53
+
54
+ beforeEach(async () => {
55
+ // Clear mocks before each test
56
+ vi.clearAllMocks();
57
+ // Clean data, but keep the model
58
+ await datasCollection.deleteMany({ _user: testUser.username });
59
+ });
60
+
61
+ // Helper to create a mock LLM stream
62
+ const createMockStream = (content) => {
63
+ return (async function* () {
64
+ yield { content };
65
+ })();
66
+ };
67
+
68
+ it('should return a simple text message from the AI', async () => {
69
+ const mockLLM = {
70
+ stream: vi.fn().mockReturnValue(createMockStream('Bonjour, ceci est un test.')),
71
+ };
72
+ getAIProvider.mockResolvedValue(mockLLM);
73
+
74
+ const params = { message: 'Dis bonjour', history: [] };
75
+ const result = await handleChatRequest(params, testUser);
76
+
77
+ expect(getAIProvider).toHaveBeenCalled();
78
+ expect(mockLLM.stream).toHaveBeenCalled();
79
+ expect(result).toEqual({
80
+ success: true,
81
+ displayMessage: 'Bonjour, ceci est un test.'
82
+ });
83
+ });
84
+
85
+ it('should handle a search action', async () => {
86
+
87
+ // Configure le retour du mock pour ce test spécifique
88
+ const searchDataSpy = vi.spyOn(dataOperations, 'searchData').mockResolvedValue({ data: [{ name: 'Test Widget', price: 100 }], count: 1 });
89
+
90
+ const aiResponse = JSON.stringify([
91
+ { action: 'search_models', params: { query: modelName } },
92
+ { action: 'search', params: { model: modelName, filter: {} } }
93
+ ]);
94
+ const mockLLM = {
95
+ stream: vi.fn().mockReturnValue(createMockStream(aiResponse))
96
+ };
97
+ getAIProvider.mockResolvedValue(mockLLM);
98
+
99
+ const params = { message: `cherche les produits`, history: [] };
100
+ const result = await handleChatRequest(params, testUser);
101
+
102
+ expect(searchDataSpy).toHaveBeenCalled();
103
+
104
+ expect(result.success).toBe(true);
105
+ expect(result.dataResult).toBeDefined();
106
+ expect(result.dataResult.model).toBe(modelName);
107
+ expect(result.dataResult.data).toHaveLength(1);
108
+ expect(result.dataResult.data[0].name).toBe('Test Widget');
109
+ });
110
+
111
+ it('should request confirmation for a "post" action', async () => {
112
+ const aiResponse = JSON.stringify({
113
+ action: 'post',
114
+ params: {
115
+ model: modelName,
116
+ data: { name: 'New Gadget', price: 19.99 }
117
+ }
118
+ });
119
+ const mockLLM = {
120
+ stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
121
+ };
122
+ getAIProvider.mockResolvedValue(mockLLM);
123
+
124
+ const params = { message: 'Crée un produit', history: [] };
125
+ const result = await handleChatRequest(params, testUser);
126
+
127
+ expect(result.success).toBe(true);
128
+ expect(result.confirmationRequest).toBeDefined();
129
+ expect(result.confirmationRequest.action).toBe('post');
130
+ expect(result.confirmationRequest.params.model).toBe(modelName);
131
+ expect(result.confirmationRequest.params.data.name).toBe('New Gadget');
132
+ });
133
+
134
+ it('should execute a confirmed "post" action', async () => {
135
+ const confirmedAction = {
136
+ action: 'post',
137
+ params: {
138
+ model: modelName,
139
+ data: { name: 'Confirmed Gadget', price: 50 }
140
+ }
141
+ };
142
+
143
+ const params = { message: '', confirmedAction };
144
+ const result = await handleChatRequest(params, testUser);
145
+
146
+ expect(result.success).toBe(true);
147
+ // --- CORRECTION ---
148
+ // On vérifie le message de succès spécifique à la création, qui est plus informatif.
149
+ expect(result.displayMessage).toContain("Élément créé avec l'ID:");
150
+
151
+ // Verify the data was actually inserted
152
+ const dataInDb = await datasCollection.findOne({ name: 'Confirmed Gadget' });
153
+ expect(dataInDb).not.toBeNull();
154
+ expect(dataInDb.price).toBe(50);
155
+ });
156
+
157
+ it('should handle malformed JSON from AI by returning it as a text message', async () => {
158
+ const aiResponse = `{ "action": "search", "params": { "model": "${modelName}" }`; // Missing closing brace
159
+ const mockLLM = {
160
+ stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
161
+ };
162
+ getAIProvider.mockResolvedValue(mockLLM);
163
+
164
+ const params = { message: 'test', history: [] };
165
+ const result = await handleChatRequest(params, testUser);
166
+
167
+ expect(result.success).toBe(true);
168
+ expect(result.displayMessage).toBe(aiResponse);
169
+ });
170
+
171
+ it('should correct a malformed filter from the AI', async () => {
172
+ const aiResponse = JSON.stringify([{
173
+ action: 'search',
174
+ params: {
175
+ model: modelName,
176
+ // This filter is malformed: multiple operators at the root level
177
+ filter: {
178
+ "$gt": ["$price", 50],
179
+ "$lt": ["$price", 200]
180
+ }
181
+ }
182
+ }]);
183
+ const mockLLM = {
184
+ stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
185
+ };
186
+ getAIProvider.mockResolvedValue(mockLLM);
187
+
188
+ // Configure le retour du mock pour ce test spécifique
189
+ const searchDataSpy = vi.spyOn(dataOperations, 'searchData').mockResolvedValue({ data: [], count: 0 });
190
+
191
+ const params = { message: 'cherche produits entre 50 et 200', history: [] };
192
+ // On peut maintenant utiliser la fonction handleChatRequest importée normalement
193
+ await handleChatRequest(params, testUser);
194
+
195
+ // On s'attend à ce que le filtre ait été encapsulé dans un $and
196
+ const expectedFilter = {
197
+ "$and": [
198
+ { "$gt": ["$price", 50] },
199
+ { "$lt": ["$price", 200] }
200
+ ]
201
+ };
202
+
203
+ expect(searchDataSpy).toHaveBeenCalledWith(expect.objectContaining({ filter: expectedFilter }), expect.anything());
204
+
205
+ // On nettoie l'espion après le test
206
+ searchDataSpy.mockRestore();
207
+ });
207
208
  });