data-primals-engine 1.7.0 → 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 (39) hide show
  1. package/README.md +160 -160
  2. package/client/package-lock.json +1080 -212
  3. package/client/package.json +12 -6
  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 +39 -23
  11. package/src/ai.jobs.js +135 -0
  12. package/src/constants.js +561 -545
  13. package/src/core.js +487 -477
  14. package/src/data.js +2 -0
  15. package/src/email.js +0 -2
  16. package/src/engine.js +50 -42
  17. package/src/filter.js +348 -343
  18. package/src/modules/assistant/assistant.js +782 -763
  19. package/src/modules/assistant/constants.js +23 -16
  20. package/src/modules/assistant/providers.js +77 -37
  21. package/src/modules/bucket.js +4 -0
  22. package/src/modules/data/data.cluster.js +191 -0
  23. package/src/modules/data/data.core.js +11 -8
  24. package/src/modules/data/data.js +13 -4
  25. package/src/modules/data/data.operations.js +186 -106
  26. package/src/modules/data/data.relations.js +1 -0
  27. package/src/modules/data/data.replication.js +83 -0
  28. package/src/modules/data/data.routes.js +2183 -1879
  29. package/src/modules/mongodb.js +76 -73
  30. package/src/modules/user.js +7 -1
  31. package/src/modules/worker-script-runner.js +97 -0
  32. package/src/modules/workflow.js +1953 -1815
  33. package/src/packs.js +5701 -5697
  34. package/src/providers.js +298 -297
  35. package/test/assistant.test.js +207 -206
  36. package/test/data.integration.test.js +1425 -1416
  37. package/test/import_export.integration.test.js +210 -210
  38. package/test/workflow.actions.integration.test.js +487 -475
  39. package/test/workflow.integration.test.js +332 -329
package/src/constants.js CHANGED
@@ -1,546 +1,562 @@
1
- /**
2
- * Enables auto-installation at startup
3
- * @type {boolean}
4
- */
5
- export const install = true;
6
-
7
- /**
8
- * Maximum reflective steps to be used by the AI (looping over own research)
9
- * @type {number}
10
- */
11
- export const maxAIReflectiveSteps = 4;
12
-
13
- /**
14
- * Database name
15
- * @type {string}
16
- */
17
- export const dbName = "engine";
18
-
19
- /**
20
- * Web server host (for cookie domain)
21
- * @type {string}
22
- */
23
- export const host = 'localhost'; // or myhost.tld
24
-
25
- export const port = 7633;
26
-
27
- /**
28
- * Cookie secret key (if COOKIES_SECRET is set, it will override this variable)
29
- * @type {string}
30
- */
31
- export const cookiesSecret = 'hoaivuymzovyoznllmafivpzaovphlejvalwjvelfhqochakfesv';
32
-
33
- /**
34
- * AWS default configuration (overrided by AWS_* environment variables)
35
- * @type {{bucketName: string, region: string}}
36
- */
37
- export const awsDefaultConfig = {
38
- bucketName : 'bucket-primals',
39
- region: 'eu-north-1'
40
- }
41
-
42
- /**
43
- * Email default configuration (overrided by SMTP_* environment variables)
44
- * @type {{from: string}}
45
- */
46
- export const emailDefaultConfig = {
47
- from: "Support - data@primals.net <data@primals.net>",
48
- host: 'smtp.mydomain.tld',
49
- port: 587,
50
- secure: false,
51
- user: 'user',
52
- pass: 'password'
53
- }
54
-
55
- export const assistantConfig = {
56
- maxTokens: 4000
57
- }
58
-
59
- /**
60
- * Maximum number of models per user
61
- * @type {number}
62
- */
63
- export const maxModelsPerUser = 1000;
64
-
65
- /**
66
- * Maximum number of data per user
67
- * @type {number}
68
- */
69
- export const maxTotalDataPerUser = 500000;
70
-
71
- /**
72
- * Maximum length of a string
73
- * @type {number}
74
- */
75
- export const maxStringLength = 4096;
76
-
77
- /**
78
- * Maximum length of a password
79
- * @type {number}
80
- */
81
- export const maxPasswordLength = 100000;
82
-
83
- /**
84
- * Maximum length of a rich text
85
- * @type {number}
86
- */
87
- export const maxRichTextLength = 100000;
88
-
89
- /**
90
- * Maximum number of exportable data per user per request
91
- * @type {number}
92
- */
93
- export const maxExportCount = 50000;
94
-
95
- export const maxMagnetsDataPerModel = 100;
96
- export const maxMagnetsModels = 20;
97
-
98
- /**
99
- * Maximum number of data per request
100
- * @type {number}
101
- */
102
- export const maxRequestData = 2500;
103
-
104
- /**
105
- * Maximum number of data per post
106
- * @type {number}
107
- */
108
- export const maxPostData = 500;
109
-
110
-
111
- /**
112
- * Maximum number of relations per data
113
- * @type {number}
114
- */
115
- export const maxRelationsPerData = 1500;
116
-
117
- /**
118
- * Maximum number of filters per request
119
- * @type {number}
120
- */
121
- export const maxFilterDepth = 8;
122
- export const elementsPerPage = 30;
123
-
124
- /**
125
- * Maximum number of alerts per user
126
- * @type {number}
127
- */
128
- export const maxAlertsPerUser = 15;
129
-
130
- /**
131
- * Storage safety margin (between 0 and 1)
132
- * @type {number}
133
- */
134
- export const storageSafetyMargin = 0.95;
135
-
136
- /**
137
- * Number of bytes in Kilobytes constant
138
- * @type {number}
139
- */
140
- export const kilobytes = 1024;
141
-
142
- /**
143
- * Number of bytes in Megabytes constant
144
- * @type {number}
145
- */
146
- export const megabytes = 1024*1024;
147
-
148
- /**
149
- * Maximum bytes per second for data throttling
150
- * @type {number}
151
- */
152
- export const maxBytesPerSecondThrottleData = 200*kilobytes; // 200Ko/s
153
-
154
- /**
155
- * Search request timeout (in milliseconds)
156
- * @type {number}
157
- */
158
- export const searchRequestTimeout = 15000;
159
-
160
- /**
161
- * Maximum model name length
162
- * @type {number}
163
- */
164
- export const maxModelNameLength = 150;
165
-
166
- /**
167
- * Maximum file size (in bytes)
168
- * @type {number}
169
- */
170
- export const maxFileSize = 20 * 1024 * 1024; // 20 Mo
171
-
172
- /**
173
- * Main fields types
174
- * @type {string[]}
175
- */
176
- export const mainFieldsTypes = ['string_t', 'string', 'url', 'enum', 'email', 'phone', 'date', 'datetime'];
177
-
178
- /**
179
- * Maximum number of executions per step in workflows
180
- * @type {number}
181
- */
182
- export const maxExecutionsByStep = 5;
183
- /**
184
- * Maximum number of steps per workflow
185
- * @type {number}
186
- */
187
- export const maxWorkflowSteps = 15;
188
-
189
- /**
190
- * Maximum number of private files per user
191
- * @type {number}
192
- */
193
- export const maxPrivateFileSize = 20 * megabytes; // Taille max par fichier privé (20 Mo)
194
-
195
- /**
196
- * Maximum total size of private files (in bytes)
197
- * @type {number}
198
- */
199
- export const maxTotalPrivateFilesSize = 250 * megabytes;
200
-
201
- export const maxPackData = 5000;
202
- export const maxPackPreviewData = maxPackData / 10;
203
-
204
- /**
205
- * Default maximum number of data per request
206
- * @type {number}
207
- */
208
- export const defaultMaxRequestData = 500;
209
-
210
- /**
211
- * Database connections pool size for MongoDB access
212
- * @type {number}
213
- */
214
- export const databasePoolSize = 30;
215
-
216
- /**
217
- * TLS options for MongoDB access
218
- * @type {boolean}
219
- */
220
- export const tlsAllowInvalidCertificates = false;
221
- export const tlsAllowInvalidHostnames = false;
222
-
223
- /**
224
- * Options for the HTML sanitizer
225
- * @type {{allowedSchemesByTag: {}, selfClosing: string[], allowedSchemes: string[], enforceHtmlBoundary: boolean, disallowedTagsMode: string, allowProtocolRelative: boolean, allowedAttributes: {a: string[], img: string[], code: string[]}, allowedTags: string[], allowedSchemesAppliedToAttributes: string[]}}
226
- */
227
- export const optionsSanitizer = {
228
- allowedTags: [
229
- "img",
230
- "address", "article", "aside", "footer", "header", "h1", "h2", "h3", "h4",
231
- "h5", "h6", "hgroup", "main", "nav", "section", "blockquote", "dd", "div",
232
- "dl", "dt", "figcaption", "figure", "hr", "li", "main", "ol", "p", "pre",
233
- "ul", "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn",
234
- "em", "i", "kbd", "mark", "q", "rb", "rp", "rt", "rtc", "ruby", "s", "samp",
235
- "small", "span", "strong", "sub", "sup", "time", "u", "var", "wbr", "caption",
236
- "col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr"
237
- ],
238
- disallowedTagsMode: 'discard',
239
- allowedAttributes: {
240
- a: [ 'href', 'name', 'target' ],
241
- code: ['class'],
242
- img: [ 'src', "alt","width","height","style" ]
243
- },
244
- // Lots of these won't come up by default because we don't allow them
245
- selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ],
246
- // URL schemes we permit
247
- allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ],
248
- allowedSchemesByTag: {},
249
- allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
250
- allowProtocolRelative: true,
251
- enforceHtmlBoundary: false
252
- }
253
-
254
-
255
- /**
256
- * Meta models are arranging models in groups
257
- * May evolve in the future with the use of packs
258
- * @type {{}}
259
- */
260
- export const metaModels = {};
261
- metaModels['common'] = { load: ['contact', 'location', 'request'] };
262
- metaModels['personal'] = { load: ['budget', 'imageGallery'] };
263
- metaModels['users'] = { load: ['permission', 'role', 'user', 'userPermission', 'token'], 'require': ['i18n', 'common'] };
264
- metaModels['i18n'] = { load: ['translation','lang']};
265
- metaModels['website'] = { load: ['webpage', 'content', 'taxonomy', 'contact', 'event', 'resource'], 'require': ['i18n'] };
266
- metaModels['messaging'] = { load: ['alert','ticket', 'message', 'channel'], 'require': ['i18n'] };
267
- metaModels['eshopping'] = { load: [
268
- 'order', 'currency', 'product', 'productVariant', 'discount', 'cart', 'cartItem',
269
- 'brand', 'return', 'review', 'stock', 'returnItem', 'userSubscription',
270
- 'warehouse', 'shipment', 'stockAlert', 'invoice'],
271
- 'require': ['i18n', 'users', 'messaging'] };
272
- metaModels['workflow'] = { load: ['env', 'workflow', 'workflowRun', 'workflowAction', 'workflowStep', 'workflowTrigger']};
273
- metaModels['erp'] = { load: [ 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'dashboard', 'kpi'] };
274
-
275
-
276
- /**
277
- * Available model field attributes
278
- * @type {string[]}
279
- */
280
- export const allowedFields = ['locked', 'hiddenable', 'encrypted', 'anonymized', 'condition', 'color', 'index', 'indexType', 'type', 'required', 'hint', 'default', 'validate', 'unique', 'name', 'placeholder', 'asMain'];
281
-
282
-
283
-
284
- export const getHost = () => {
285
- return process.env.HOST || host || 'localhost';
286
- }
287
-
288
- /**
289
- * Configuration of MongoDB filters
290
- */
291
- export const MONGO_CALC_OPERATORS = {
292
- $find: {
293
- label: 'Find in relation',
294
- description: 'Recherche dans une relation/tableau',
295
- supportsNesting: true,
296
- multi: false,
297
- isFindOperator: true // Nouvelle propriété pour identifier les opérateurs $find
298
- },
299
- '$regexMatch': {
300
- label: 'Find by regex',
301
- description: 'Find a string matching a regular expression (Ecmascript)',
302
- args: 2, // input et regex
303
- specialStructure: true // Indique une structure spéciale
304
- },
305
- $and: {
306
- label: 'Et (and)',
307
- description: 'Renvoie vrai si toutes les conditions sont vérifiées',
308
- args: 1,
309
- multi: true
310
- },
311
- $or: {
312
- label: 'Ou (or)',
313
- description: 'Renvoie vrai si au moins une des conditions est vérifiée.',
314
- args: 1,
315
- multi: true
316
- },
317
- $not: {label: 'Non (not)', description: 'Inverse la condition (ex: non égal à).', args: 1, multi: true},
318
- $nor: {
319
- label: 'Ou Non (nor)',
320
- description: 'Renvoie vrai si aucune des conditions n\'est vérifiée',
321
- args: 1,
322
- multi: true
323
- },
324
- $eq: {label: '=', multi: false, args: 2},
325
- $ne: {label: '!=', multi: false, args: 2},
326
- $gt: {label: '>', multi: false, args: 2},
327
- $gte: {label: '>=', multi: false, args: 2},
328
- $lt: {label: '<', multi: false, args: 2},
329
- $lte: {label: '<=', multi: false, args: 2},
330
- $in: {label: 'in', multi: true, args: 2},
331
- $nin: {label: 'nin', multi: true, args: 2},
332
- $all: {label: 'afll', multi: true},
333
- $size: {
334
- label: 'size',
335
- multi: false,
336
- description: 'Renvoie le nombre d\'éléments dans un tableau',
337
- disableAdvancedValue: true
338
- },
339
- $elemMatch: {label: 'elemMatch', multi: true},
340
- $type: {
341
- label: 'type',
342
- description: 'Renvoie le type BSON de l\'élément',
343
- multi: false,
344
- disableAdvancedValue: true
345
- },
346
- $add: {label: '+', multi: true},
347
- $subtract: {label: '-', multi: false, args: 2},
348
- $multiply: {label: '*', multi: true},
349
- $divide: {label: '/', multi: false, args: 2},
350
- $mod: {label: '%', multi: false, args: 2},
351
- $pow: {label: 'pow', multi: false},
352
- $sqrt: {label: 'sqrt', multi: false},
353
- $exp: {label: 'exp', multi: false},
354
- $abs: {label: 'abs', multi: false},
355
- $ceil: {label: 'ceil', multi: false},
356
- $floor: {label: 'floor', multi: false},
357
- $ln: {label: 'ln', multi: false},
358
- $log10: {label: 'log', multi: false},
359
- $concat: {label: 'concat', multi: true},
360
-
361
- // Date-specific
362
- $year: {label: 'year', multi: false, isDate: true},
363
- $month: {label: 'month', multi: false, isDate: true},
364
- $dayOfMonth: {label: 'dayOfMonth', multi: false, isDate: true},
365
- $hour: {label: 'hour', multi: false, isDate: true},
366
- $minute: {label: 'minute', multi: false, isDate: true},
367
- $second: {label: 'second', multi: false, isDate: true},
368
- $millisecond: {label: 'ms', multi: false, isDate: true},
369
- // Date operators
370
- $dateAdd: {
371
- label: 'Add to date',
372
- description: 'Adds a duration to a date',
373
- isDate: true,
374
- specialStructure: true,
375
- args: [
376
- {name: 'startDate', label: 'Start Date', type: 'date'},
377
- {
378
- name: 'unit',
379
- label: 'Unit',
380
- type: 'select',
381
- options: ['year', 'month', 'day', 'hour', 'minute', 'second']
382
- },
383
- {name: 'amount', label: 'Amount', type: 'number'},
384
- {name: 'timezone', label: 'Timezone', type: 'text', optional: true}
385
- ]
386
- },
387
- $dateSubtract: {
388
- label: 'Subtract from date',
389
- description: 'Subtracts a duration from a date',
390
- isDate: true,
391
- specialStructure: true,
392
- args: [
393
- {name: 'startDate', label: 'Start Date', type: 'date'},
394
- {
395
- name: 'unit',
396
- label: 'Unit',
397
- type: 'select',
398
- options: ['year', 'month', 'day', 'hour', 'minute', 'second']
399
- },
400
- {name: 'amount', label: 'Amount', type: 'number'},
401
- {name: 'timezone', label: 'Timezone', type: 'text', optional: true}
402
- ]
403
- },
404
- $dateDiff: {
405
- label: 'Date Difference',
406
- description: 'Calculates the difference between two dates in specified units',
407
- isDate: true,
408
- specialStructure: true,
409
- args: [
410
- {
411
- name: 'startDate',
412
- label: 'Start Date',
413
- type: 'date',
414
- description: 'The starting date (inclusive)'
415
- },
416
- {
417
- name: 'endDate',
418
- label: 'End Date',
419
- type: 'date',
420
- description: 'The ending date (exclusive)'
421
- },
422
- {
423
- name: 'unit',
424
- label: 'Unit',
425
- type: 'select',
426
- options: ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
427
- description: 'The unit for the result'
428
- },
429
- {
430
- name: 'timezone',
431
- label: 'Timezone',
432
- type: 'text',
433
- optional: true,
434
- description: 'The timezone (e.g. "Europe/Paris")'
435
- }
436
- ]
437
- },
438
- $dateToString: {
439
- label: 'Format date as string',
440
- description: 'Formats a date as a string',
441
- isDate: true,
442
- specialStructure: true,
443
- args: [
444
- {
445
- name: 'date',
446
- label: 'Date',
447
- type: 'date'
448
- },
449
- {
450
- name: 'format',
451
- label: 'Format',
452
- optional: true,
453
- type: 'text'
454
- },
455
- {
456
- name: 'timezone',
457
- label: 'Timezone',
458
- type: 'text',
459
- optional: true,
460
- description: 'The timezone (e.g. "Europe/Paris")'
461
- }
462
- ]
463
- },
464
-
465
- // Converters
466
- $toBool: {label: 'toBool', multi: false, converter: true},
467
- $toString: {label: 'toString', multi: false, converter: true},
468
- $toInt: {label: 'toInt', multi: false, converter: true},
469
- $toDouble: {label: 'toDouble', multi: false, converter: true},
470
-
471
- // --- Special Query Operators (not for $expr) ---
472
- // These operators are handled by a standard $match stage, not inside $expr.
473
- // The UI should use them to construct parts of the main filter object.
474
- $regex: {
475
- label: 'Regex',
476
- description: 'Matches strings using a regular expression. Applied to a specific field.',
477
- isQueryOperator: true,
478
- specialStructure: true,
479
- args: [
480
- { name: 'pattern', label: 'Pattern', type: 'text', description: 'The regex pattern.' },
481
- { name: 'options', label: 'Options', type: 'text', optional: true, description: 'Regex options (e.g., "i" for case-insensitivity).' }
482
- ]
483
- },
484
-
485
- $text: {
486
- label: 'Text Search',
487
- description: 'Performs a full-text search on the collection. Must be a top-level filter condition.',
488
- isQueryOperator: true,
489
- isTopLevel: true, // Indicates it's a key in the root of the filter, not under a field name.
490
- specialStructure: true,
491
- args: [
492
- { name: '$search', label: 'Search Terms', type: 'text' },
493
- { name: '$language', label: 'Language', type: 'text', optional: true },
494
- { name: '$caseSensitive', label: 'Case Sensitive', type: 'boolean', optional: true },
495
- { name: '$diacriticSensitive', label: 'Diacritic Sensitive', type: 'boolean', optional: true }
496
- ]
497
- },
498
-
499
- $nearSphere: {
500
- label: 'Near Sphere',
501
- description: 'Finds documents near a GeoJSON point on a sphere. Applied to a 2dsphere-indexed field.',
502
- isQueryOperator: true,
503
- specialStructure: true,
504
- args: [
505
- { name: 'geometry', label: 'Center Point (GeoJSON)', type: 'code', language: 'json', description: 'e.g., { "type": "Point", "coordinates": [ -73.93, 40.82 ] }' },
506
- { name: 'maxDistance', label: 'Max Distance (meters)', type: 'number', optional: true },
507
- { name: 'minDistance', label: 'Min Distance (meters)', type: 'number', optional: true }
508
- ]
509
- },
510
-
511
- $geoWithin: {
512
- label: 'Geo Within',
513
- description: 'Selects documents with geospatial data that exists entirely within a specified shape.',
514
- isQueryOperator: true,
515
- specialStructure: true,
516
- args: [
517
- { name: '$geometry', label: 'Shape (GeoJSON)', type: 'code', language: 'json', description: 'A GeoJSON Polygon or MultiPolygon.' }
518
- ]
519
- },
520
-
521
- $geoIntersects: {
522
- label: 'Geo Intersects',
523
- description: 'Selects documents whose geospatial data intersects with a specified GeoJSON object.',
524
- isQueryOperator: true,
525
- specialStructure: true,
526
- args: [
527
- { name: '$geometry', label: 'Geometry (GeoJSON)', type: 'code', language: 'json', description: 'A GeoJSON object to test for intersection.' }
528
- ]
529
- },
530
-
531
- // This represents the $geoNear aggregation stage, which has special placement rules.
532
- $geoNear: {
533
- label: 'Geo Near (Stage)',
534
- description: 'Finds and sorts documents by distance. Must be the first operation in a search.',
535
- isQueryOperator: true,
536
- isTopLevel: true, // Indicates it's a key in the root of the filter.
537
- specialStructure: true,
538
- args: [
539
- { name: 'near', label: 'Near Point (GeoJSON)', type: 'code', language: 'json', description: 'The point to search near.' },
540
- { name: 'distanceField', label: 'Distance Field Name', type: 'text', description: 'The output field to store the distance.' },
541
- { name: 'spherical', label: 'Spherical', type: 'boolean', optional: true, default: true },
542
- { name: 'query', label: 'Additional Query', type: 'code', language: 'json', optional: true, description: 'Filters documents before distance calculation.' },
543
- { name: 'maxDistance', label: 'Max Distance (meters)', type: 'number', optional: true }
544
- ]
545
- }
1
+ /**
2
+ * Enables auto-installation at startup
3
+ * @type {boolean}
4
+ */
5
+ export const install = true;
6
+
7
+ /**
8
+ * Maximum reflective steps to be used by the AI (looping over own research)
9
+ * @type {number}
10
+ */
11
+ export const maxAIReflectiveSteps = 4;
12
+
13
+ /**
14
+ * Database name
15
+ * @type {string}
16
+ */
17
+ export const dbName = "engine";
18
+
19
+ /**
20
+ * Web server host (for cookie domain)
21
+ * @type {string}
22
+ */
23
+ export const host = 'localhost'; // or myhost.tld
24
+
25
+ export const port = 7633;
26
+
27
+ /**
28
+ * Cookie secret key (if COOKIES_SECRET is set, it will override this variable)
29
+ * @type {string}
30
+ */
31
+ export const cookiesSecret = 'hoaivuymzovyoznllmafivpzaovphlejvalwjvelfhqochakfesv';
32
+
33
+ /**
34
+ * AWS default configuration (overrided by AWS_* environment variables)
35
+ * @type {{bucketName: string, region: string}}
36
+ */
37
+ export const awsDefaultConfig = {
38
+ bucketName : 'bucket-primals',
39
+ region: 'eu-north-1'
40
+ }
41
+
42
+ /**
43
+ * Email default configuration (overrided by SMTP_* environment variables)
44
+ * @type {{from: string}}
45
+ */
46
+ export const emailDefaultConfig = {
47
+ from: "Support - data@primals.net <data@primals.net>",
48
+ host: 'smtp.mydomain.tld',
49
+ port: 587,
50
+ secure: false,
51
+ user: 'user',
52
+ pass: 'password'
53
+ }
54
+
55
+ export const assistantConfig = {
56
+ maxTokens: 4000
57
+ }
58
+
59
+ /**
60
+ * Maximum number of models per user
61
+ * @type {number}
62
+ */
63
+ export const maxModelsPerUser = 1000;
64
+
65
+ /**
66
+ * Maximum number of data per user
67
+ * @type {number}
68
+ */
69
+ export const maxTotalDataPerUser = 500000;
70
+
71
+ /**
72
+ * Maximum length of a string
73
+ * @type {number}
74
+ */
75
+ export const maxStringLength = 4096;
76
+
77
+ /**
78
+ * Maximum length of a password
79
+ * @type {number}
80
+ */
81
+ export const maxPasswordLength = 100000;
82
+
83
+ /**
84
+ * Maximum length of a rich text
85
+ * @type {number}
86
+ */
87
+ export const maxRichTextLength = 100000;
88
+
89
+ /**
90
+ * Maximum number of exportable data per user per request
91
+ * @type {number}
92
+ */
93
+ export const maxExportCount = 50000;
94
+
95
+ export const maxMagnetsDataPerModel = 100;
96
+ export const maxMagnetsModels = 20;
97
+
98
+ /**
99
+ * Maximum number of data per request
100
+ * @type {number}
101
+ */
102
+ export const maxRequestData = 2500;
103
+
104
+ /**
105
+ * Maximum number of data per post
106
+ * @type {number}
107
+ */
108
+ export const maxPostData = 500;
109
+
110
+
111
+ /**
112
+ * Maximum number of relations per data
113
+ * @type {number}
114
+ */
115
+ export const maxRelationsPerData = 1500;
116
+
117
+ /**
118
+ * Maximum number of filters per request
119
+ * @type {number}
120
+ */
121
+ export const maxFilterDepth = 8;
122
+ export const elementsPerPage = 30;
123
+
124
+ /**
125
+ * Maximum number of alerts per user
126
+ * @type {number}
127
+ */
128
+ export const maxAlertsPerUser = 15;
129
+
130
+ /**
131
+ * Storage safety margin (between 0 and 1)
132
+ * @type {number}
133
+ */
134
+ export const storageSafetyMargin = 0.95;
135
+
136
+ /**
137
+ * Number of bytes in Kilobytes constant
138
+ * @type {number}
139
+ */
140
+ export const kilobytes = 1024;
141
+
142
+ /**
143
+ * Number of bytes in Megabytes constant
144
+ * @type {number}
145
+ */
146
+ export const megabytes = 1024*1024;
147
+
148
+ export const gigabytes = 1024*megabytes;
149
+
150
+ export const terabytes = 1024*gigabytes;
151
+
152
+ /**
153
+ * Maximum bytes per second for data throttling
154
+ * @type {number}
155
+ */
156
+ export const maxBytesPerSecondThrottleData = 200*kilobytes; // 200Ko/s
157
+
158
+ /**
159
+ * Search request timeout (in milliseconds)
160
+ * @type {number}
161
+ */
162
+ export const searchRequestTimeout = 15000;
163
+
164
+ /**
165
+ * Maximum model name length
166
+ * @type {number}
167
+ */
168
+ export const maxModelNameLength = 150;
169
+
170
+ /**
171
+ * Maximum file size (in bytes)
172
+ * @type {number}
173
+ */
174
+ export const maxFileSize = 20 * 1024 * 1024; // 20 Mo
175
+
176
+ /**
177
+ * Main fields types
178
+ * @type {string[]}
179
+ */
180
+ export const mainFieldsTypes = ['string_t', 'string', 'url', 'enum', 'email', 'phone', 'date', 'datetime'];
181
+
182
+ /**
183
+ * Maximum number of executions per step in workflows
184
+ * @type {number}
185
+ */
186
+ export const maxExecutionsByStep = 5;
187
+ /**
188
+ * Maximum number of steps per workflow
189
+ * @type {number}
190
+ */
191
+ export const maxWorkflowSteps = 15;
192
+
193
+ /**
194
+ * Maximum number of private files per user
195
+ * @type {number}
196
+ */
197
+ export const maxPrivateFileSize = 20 * megabytes; // Taille max par fichier privé (20 Mo)
198
+
199
+ /**
200
+ * Maximum total size of private files (in bytes)
201
+ * @type {number}
202
+ */
203
+ export const maxTotalPrivateFilesSize = 250 * megabytes;
204
+
205
+ export const maxPackData = 5000;
206
+ export const maxPackPreviewData = maxPackData / 10;
207
+
208
+ /**
209
+ * Maximum total size of database data per user (in bytes)
210
+ * @type {number}
211
+ */
212
+ export const maxTotalDataSizePerUser = 50 * megabytes; // Exemple : 50 Mo
213
+
214
+ /**
215
+ * Default maximum number of data per request
216
+ * @type {number}
217
+ */
218
+ export const defaultMaxRequestData = 500;
219
+
220
+ /**
221
+ * Database connections pool size for MongoDB access
222
+ * @type {number}
223
+ */
224
+ export const databasePoolSize = 30;
225
+
226
+ /**
227
+ * TLS options for MongoDB access
228
+ * @type {boolean}
229
+ */
230
+ export const tlsAllowInvalidCertificates = false;
231
+ export const tlsAllowInvalidHostnames = false;
232
+
233
+ /**
234
+ * Cluster configuration for data federation
235
+ * @type {string[]}
236
+ */
237
+ export const clusterPeers = (process.env.CLUSTER_PEERS || '').split(',').filter(Boolean);
238
+
239
+ /**
240
+ * Options for the HTML sanitizer
241
+ * @type {{allowedSchemesByTag: {}, selfClosing: string[], allowedSchemes: string[], enforceHtmlBoundary: boolean, disallowedTagsMode: string, allowProtocolRelative: boolean, allowedAttributes: {a: string[], img: string[], code: string[]}, allowedTags: string[], allowedSchemesAppliedToAttributes: string[]}}
242
+ */
243
+ export const optionsSanitizer = {
244
+ allowedTags: [
245
+ "img",
246
+ "address", "article", "aside", "footer", "header", "h1", "h2", "h3", "h4",
247
+ "h5", "h6", "hgroup", "main", "nav", "section", "blockquote", "dd", "div",
248
+ "dl", "dt", "figcaption", "figure", "hr", "li", "main", "ol", "p", "pre",
249
+ "ul", "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn",
250
+ "em", "i", "kbd", "mark", "q", "rb", "rp", "rt", "rtc", "ruby", "s", "samp",
251
+ "small", "span", "strong", "sub", "sup", "time", "u", "var", "wbr", "caption",
252
+ "col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr"
253
+ ],
254
+ disallowedTagsMode: 'discard',
255
+ allowedAttributes: {
256
+ a: [ 'href', 'name', 'target' ],
257
+ code: ['class'],
258
+ img: [ 'src', "alt","width","height","style" ]
259
+ },
260
+ // Lots of these won't come up by default because we don't allow them
261
+ selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ],
262
+ // URL schemes we permit
263
+ allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ],
264
+ allowedSchemesByTag: {},
265
+ allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
266
+ allowProtocolRelative: true,
267
+ enforceHtmlBoundary: false
268
+ }
269
+
270
+
271
+ /**
272
+ * Meta models are arranging models in groups
273
+ * May evolve in the future with the use of packs
274
+ * @type {{}}
275
+ */
276
+ export const metaModels = {};
277
+ metaModels['common'] = { load: ['contact', 'location', 'request'] };
278
+ metaModels['personal'] = { load: ['budget', 'imageGallery'] };
279
+ metaModels['users'] = { load: ['permission', 'role', 'user', 'userPermission', 'token'], 'require': ['i18n', 'common'] };
280
+ metaModels['i18n'] = { load: ['translation','lang']};
281
+ metaModels['website'] = { load: ['webpage', 'content', 'taxonomy', 'contact', 'event', 'resource'], 'require': ['i18n'] };
282
+ metaModels['messaging'] = { load: ['alert','ticket', 'message', 'channel'], 'require': ['i18n'] };
283
+ metaModels['eshopping'] = { load: [
284
+ 'order', 'currency', 'product', 'productVariant', 'discount', 'cart', 'cartItem',
285
+ 'brand', 'return', 'review', 'stock', 'returnItem', 'userSubscription',
286
+ 'warehouse', 'shipment', 'stockAlert', 'invoice'],
287
+ 'require': ['i18n', 'users', 'messaging'] };
288
+ metaModels['workflow'] = { load: ['env', 'workflow', 'workflowRun', 'workflowAction', 'workflowStep', 'workflowTrigger']};
289
+ metaModels['erp'] = { load: [ 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'dashboard', 'kpi'] };
290
+
291
+
292
+ /**
293
+ * Available model field attributes
294
+ * @type {string[]}
295
+ */
296
+ export const allowedFields = ['locked', 'hiddenable', 'encrypted', 'anonymized', 'condition', 'color', 'index', 'indexType', 'type', 'required', 'hint', 'default', 'validate', 'unique', 'name', 'placeholder', 'asMain'];
297
+
298
+
299
+
300
+ export const getHost = () => {
301
+ return process.env.HOST || host || 'localhost';
302
+ }
303
+
304
+ /**
305
+ * Configuration of MongoDB filters
306
+ */
307
+ export const MONGO_CALC_OPERATORS = {
308
+ $find: {
309
+ label: 'Find in relation',
310
+ description: 'Recherche dans une relation/tableau',
311
+ supportsNesting: true,
312
+ multi: false,
313
+ isFindOperator: true // Nouvelle propriété pour identifier les opérateurs $find
314
+ },
315
+ '$regexMatch': {
316
+ label: 'Find by regex',
317
+ description: 'Find a string matching a regular expression (Ecmascript)',
318
+ args: 2, // input et regex
319
+ specialStructure: true // Indique une structure spéciale
320
+ },
321
+ $and: {
322
+ label: 'Et (and)',
323
+ description: 'Renvoie vrai si toutes les conditions sont vérifiées',
324
+ args: 1,
325
+ multi: true
326
+ },
327
+ $or: {
328
+ label: 'Ou (or)',
329
+ description: 'Renvoie vrai si au moins une des conditions est vérifiée.',
330
+ args: 1,
331
+ multi: true
332
+ },
333
+ $not: {label: 'Non (not)', description: 'Inverse la condition (ex: non égal à).', args: 1, multi: true},
334
+ $nor: {
335
+ label: 'Ou Non (nor)',
336
+ description: 'Renvoie vrai si aucune des conditions n\'est vérifiée',
337
+ args: 1,
338
+ multi: true
339
+ },
340
+ $eq: {label: '=', multi: false, args: 2},
341
+ $ne: {label: '!=', multi: false, args: 2},
342
+ $gt: {label: '>', multi: false, args: 2},
343
+ $gte: {label: '>=', multi: false, args: 2},
344
+ $lt: {label: '<', multi: false, args: 2},
345
+ $lte: {label: '<=', multi: false, args: 2},
346
+ $in: {label: 'in', multi: true, args: 2},
347
+ $nin: {label: 'nin', multi: true, args: 2},
348
+ $all: {label: 'afll', multi: true},
349
+ $size: {
350
+ label: 'size',
351
+ multi: false,
352
+ description: 'Renvoie le nombre d\'éléments dans un tableau',
353
+ disableAdvancedValue: true
354
+ },
355
+ $elemMatch: {label: 'elemMatch', multi: true},
356
+ $type: {
357
+ label: 'type',
358
+ description: 'Renvoie le type BSON de l\'élément',
359
+ multi: false,
360
+ disableAdvancedValue: true
361
+ },
362
+ $add: {label: '+', multi: true},
363
+ $subtract: {label: '-', multi: false, args: 2},
364
+ $multiply: {label: '*', multi: true},
365
+ $divide: {label: '/', multi: false, args: 2},
366
+ $mod: {label: '%', multi: false, args: 2},
367
+ $pow: {label: 'pow', multi: false},
368
+ $sqrt: {label: 'sqrt', multi: false},
369
+ $exp: {label: 'exp', multi: false},
370
+ $abs: {label: 'abs', multi: false},
371
+ $ceil: {label: 'ceil', multi: false},
372
+ $floor: {label: 'floor', multi: false},
373
+ $ln: {label: 'ln', multi: false},
374
+ $log10: {label: 'log', multi: false},
375
+ $concat: {label: 'concat', multi: true},
376
+
377
+ // Date-specific
378
+ $year: {label: 'year', multi: false, isDate: true},
379
+ $month: {label: 'month', multi: false, isDate: true},
380
+ $dayOfMonth: {label: 'dayOfMonth', multi: false, isDate: true},
381
+ $hour: {label: 'hour', multi: false, isDate: true},
382
+ $minute: {label: 'minute', multi: false, isDate: true},
383
+ $second: {label: 'second', multi: false, isDate: true},
384
+ $millisecond: {label: 'ms', multi: false, isDate: true},
385
+ // Date operators
386
+ $dateAdd: {
387
+ label: 'Add to date',
388
+ description: 'Adds a duration to a date',
389
+ isDate: true,
390
+ specialStructure: true,
391
+ args: [
392
+ {name: 'startDate', label: 'Start Date', type: 'date'},
393
+ {
394
+ name: 'unit',
395
+ label: 'Unit',
396
+ type: 'select',
397
+ options: ['year', 'month', 'day', 'hour', 'minute', 'second']
398
+ },
399
+ {name: 'amount', label: 'Amount', type: 'number'},
400
+ {name: 'timezone', label: 'Timezone', type: 'text', optional: true}
401
+ ]
402
+ },
403
+ $dateSubtract: {
404
+ label: 'Subtract from date',
405
+ description: 'Subtracts a duration from a date',
406
+ isDate: true,
407
+ specialStructure: true,
408
+ args: [
409
+ {name: 'startDate', label: 'Start Date', type: 'date'},
410
+ {
411
+ name: 'unit',
412
+ label: 'Unit',
413
+ type: 'select',
414
+ options: ['year', 'month', 'day', 'hour', 'minute', 'second']
415
+ },
416
+ {name: 'amount', label: 'Amount', type: 'number'},
417
+ {name: 'timezone', label: 'Timezone', type: 'text', optional: true}
418
+ ]
419
+ },
420
+ $dateDiff: {
421
+ label: 'Date Difference',
422
+ description: 'Calculates the difference between two dates in specified units',
423
+ isDate: true,
424
+ specialStructure: true,
425
+ args: [
426
+ {
427
+ name: 'startDate',
428
+ label: 'Start Date',
429
+ type: 'date',
430
+ description: 'The starting date (inclusive)'
431
+ },
432
+ {
433
+ name: 'endDate',
434
+ label: 'End Date',
435
+ type: 'date',
436
+ description: 'The ending date (exclusive)'
437
+ },
438
+ {
439
+ name: 'unit',
440
+ label: 'Unit',
441
+ type: 'select',
442
+ options: ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
443
+ description: 'The unit for the result'
444
+ },
445
+ {
446
+ name: 'timezone',
447
+ label: 'Timezone',
448
+ type: 'text',
449
+ optional: true,
450
+ description: 'The timezone (e.g. "Europe/Paris")'
451
+ }
452
+ ]
453
+ },
454
+ $dateToString: {
455
+ label: 'Format date as string',
456
+ description: 'Formats a date as a string',
457
+ isDate: true,
458
+ specialStructure: true,
459
+ args: [
460
+ {
461
+ name: 'date',
462
+ label: 'Date',
463
+ type: 'date'
464
+ },
465
+ {
466
+ name: 'format',
467
+ label: 'Format',
468
+ optional: true,
469
+ type: 'text'
470
+ },
471
+ {
472
+ name: 'timezone',
473
+ label: 'Timezone',
474
+ type: 'text',
475
+ optional: true,
476
+ description: 'The timezone (e.g. "Europe/Paris")'
477
+ }
478
+ ]
479
+ },
480
+
481
+ // Converters
482
+ $toBool: {label: 'toBool', multi: false, converter: true},
483
+ $toString: {label: 'toString', multi: false, converter: true},
484
+ $toInt: {label: 'toInt', multi: false, converter: true},
485
+ $toDouble: {label: 'toDouble', multi: false, converter: true},
486
+
487
+ // --- Special Query Operators (not for $expr) ---
488
+ // These operators are handled by a standard $match stage, not inside $expr.
489
+ // The UI should use them to construct parts of the main filter object.
490
+ $regex: {
491
+ label: 'Regex',
492
+ description: 'Matches strings using a regular expression. Applied to a specific field.',
493
+ isQueryOperator: true,
494
+ specialStructure: true,
495
+ args: [
496
+ { name: 'pattern', label: 'Pattern', type: 'text', description: 'The regex pattern.' },
497
+ { name: 'options', label: 'Options', type: 'text', optional: true, description: 'Regex options (e.g., "i" for case-insensitivity).' }
498
+ ]
499
+ },
500
+
501
+ $text: {
502
+ label: 'Text Search',
503
+ description: 'Performs a full-text search on the collection. Must be a top-level filter condition.',
504
+ isQueryOperator: true,
505
+ isTopLevel: true, // Indicates it's a key in the root of the filter, not under a field name.
506
+ specialStructure: true,
507
+ args: [
508
+ { name: '$search', label: 'Search Terms', type: 'text' },
509
+ { name: '$language', label: 'Language', type: 'text', optional: true },
510
+ { name: '$caseSensitive', label: 'Case Sensitive', type: 'boolean', optional: true },
511
+ { name: '$diacriticSensitive', label: 'Diacritic Sensitive', type: 'boolean', optional: true }
512
+ ]
513
+ },
514
+
515
+ $nearSphere: {
516
+ label: 'Near Sphere',
517
+ description: 'Finds documents near a GeoJSON point on a sphere. Applied to a 2dsphere-indexed field.',
518
+ isQueryOperator: true,
519
+ specialStructure: true,
520
+ args: [
521
+ { name: 'geometry', label: 'Center Point (GeoJSON)', type: 'code', language: 'json', description: 'e.g., { "type": "Point", "coordinates": [ -73.93, 40.82 ] }' },
522
+ { name: 'maxDistance', label: 'Max Distance (meters)', type: 'number', optional: true },
523
+ { name: 'minDistance', label: 'Min Distance (meters)', type: 'number', optional: true }
524
+ ]
525
+ },
526
+
527
+ $geoWithin: {
528
+ label: 'Geo Within',
529
+ description: 'Selects documents with geospatial data that exists entirely within a specified shape.',
530
+ isQueryOperator: true,
531
+ specialStructure: true,
532
+ args: [
533
+ { name: '$geometry', label: 'Shape (GeoJSON)', type: 'code', language: 'json', description: 'A GeoJSON Polygon or MultiPolygon.' }
534
+ ]
535
+ },
536
+
537
+ $geoIntersects: {
538
+ label: 'Geo Intersects',
539
+ description: 'Selects documents whose geospatial data intersects with a specified GeoJSON object.',
540
+ isQueryOperator: true,
541
+ specialStructure: true,
542
+ args: [
543
+ { name: '$geometry', label: 'Geometry (GeoJSON)', type: 'code', language: 'json', description: 'A GeoJSON object to test for intersection.' }
544
+ ]
545
+ },
546
+
547
+ // This represents the $geoNear aggregation stage, which has special placement rules.
548
+ $geoNear: {
549
+ label: 'Geo Near (Stage)',
550
+ description: 'Finds and sorts documents by distance. Must be the first operation in a search.',
551
+ isQueryOperator: true,
552
+ isTopLevel: true, // Indicates it's a key in the root of the filter.
553
+ specialStructure: true,
554
+ args: [
555
+ { name: 'near', label: 'Near Point (GeoJSON)', type: 'code', language: 'json', description: 'The point to search near.' },
556
+ { name: 'distanceField', label: 'Distance Field Name', type: 'text', description: 'The output field to store the distance.' },
557
+ { name: 'spherical', label: 'Spherical', type: 'boolean', optional: true, default: true },
558
+ { name: 'query', label: 'Additional Query', type: 'code', language: 'json', optional: true, description: 'Filters documents before distance calculation.' },
559
+ { name: 'maxDistance', label: 'Max Distance (meters)', type: 'number', optional: true }
560
+ ]
561
+ }
546
562
  };