data-primals-engine 1.4.2 → 1.5.0

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 (59) hide show
  1. package/README.md +878 -856
  2. package/client/package-lock.json +82 -0
  3. package/client/package.json +2 -0
  4. package/client/src/App.jsx +1 -1
  5. package/client/src/App.scss +25 -7
  6. package/client/src/AssistantChat.scss +3 -2
  7. package/client/src/ConditionBuilder.jsx +1 -1
  8. package/client/src/ConditionBuilder2.jsx +2 -1
  9. package/client/src/DashboardView.jsx +569 -569
  10. package/client/src/DataEditor.jsx +376 -368
  11. package/client/src/DataLayout.jsx +4 -10
  12. package/client/src/DataTable.jsx +858 -817
  13. package/client/src/Field.jsx +1825 -1784
  14. package/client/src/FlexDataRenderer.jsx +2 -0
  15. package/client/src/FlexTreeUtils.js +1 -1
  16. package/client/src/GeolocationField.jsx +94 -0
  17. package/client/src/KPIDialog.jsx +11 -1
  18. package/client/src/ModelCreator.jsx +1 -2
  19. package/client/src/ModelCreatorField.jsx +24 -27
  20. package/client/src/ModelList.jsx +1 -1
  21. package/client/src/RelationField.jsx +2 -2
  22. package/client/src/constants.js +3 -3
  23. package/client/src/filter.js +0 -155
  24. package/client/src/hooks/useTutorials.jsx +62 -65
  25. package/client/src/translations.js +14 -2
  26. package/package.json +2 -1
  27. package/perf/README.md +147 -0
  28. package/perf/artillery-hooks.js +37 -0
  29. package/perf/perf-shot-hardwork.yml +84 -0
  30. package/perf/perf-shot-search.yml +45 -0
  31. package/perf/setup.yml +26 -0
  32. package/server.js +1 -1
  33. package/src/constants.js +264 -31
  34. package/src/core.js +15 -1
  35. package/src/data.js +1 -1
  36. package/src/defaultModels.js +1544 -1540
  37. package/src/email.js +5 -2
  38. package/src/engine.js +10 -3
  39. package/src/filter.js +274 -260
  40. package/src/i18n.js +187 -177
  41. package/src/modules/assistant/assistant.js +3 -1
  42. package/src/modules/bucket.js +12 -15
  43. package/src/modules/data/data.backup.js +11 -8
  44. package/src/modules/data/data.core.js +2 -1
  45. package/src/modules/data/data.js +6 -3
  46. package/src/modules/data/data.operations.js +610 -168
  47. package/src/modules/data/data.routes.js +1821 -1785
  48. package/src/modules/data/data.scheduling.js +2 -1
  49. package/src/modules/data/data.validation.js +7 -1
  50. package/src/modules/file.js +4 -2
  51. package/src/modules/user.js +4 -1
  52. package/src/modules/workflow.js +9 -10
  53. package/src/openai.jobs.js +2 -0
  54. package/src/packs.js +22 -5
  55. package/src/providers.js +22 -7
  56. package/swagger-en.yml +133 -0
  57. package/test/data.integration.test.js +1060 -981
  58. package/test/import_export.integration.test.js +1 -1
  59. package/test/model.integration.test.js +377 -221
package/src/constants.js CHANGED
@@ -1,5 +1,3 @@
1
- import {event_trigger} from "./core.js";
2
-
3
1
  /**
4
2
  * Enables auto-installation at startup
5
3
  * @type {boolean}
@@ -32,26 +30,6 @@ export const port = 7633;
32
30
  */
33
31
  export const cookiesSecret = 'hoaivuymzovyoznllmafivpzaovphlejvalwjvelfhqochakfesv';
34
32
 
35
- /**
36
- * Available languages of the system
37
- * You need to translate the i18n file, or translations ones.
38
- * @type {string[]}
39
- */
40
- export const availableLangs = [
41
- "en",
42
- "fa",
43
- "ar",
44
- "fr",
45
- "it",
46
- "es",
47
- "pt",
48
- "de",
49
- "el",
50
- "ru",
51
- "cs",
52
- "sv"
53
- ];
54
-
55
33
  /**
56
34
  * AWS default configuration (overrided by AWS_* environment variables)
57
35
  * @type {{bucketName: string, region: string}}
@@ -74,8 +52,6 @@ export const emailDefaultConfig = {
74
52
  pass: 'password'
75
53
  }
76
54
 
77
- export const useAI = true;
78
-
79
55
  /**
80
56
  * Maximum number of models per user
81
57
  * @type {number}
@@ -218,11 +194,8 @@ export const maxPrivateFileSize = 20 * megabytes; // Taille max par fichier priv
218
194
  */
219
195
  export const maxTotalPrivateFilesSize = 250 * megabytes;
220
196
 
221
- /**
222
- * Timeout for Javacript execution in VMs (in milliseconds)
223
- * @type {number}
224
- */
225
- export const timeoutVM = 5000;
197
+ export const maxPackData = 5000;
198
+ export const maxPackPreviewData = maxPackData / 10;
226
199
 
227
200
  /**
228
201
  * Default maximum number of data per request
@@ -300,10 +273,270 @@ metaModels['erp'] = { load: [ 'accountingExercise', 'accountingLineItem', 'accou
300
273
  * Available model field attributes
301
274
  * @type {string[]}
302
275
  */
303
- export const allowedFields = ['locked', 'hiddenable', 'anonymized', 'condition', 'color', 'index', 'type', 'required', 'hint', 'default', 'validate', 'unique', 'name', 'placeholder', 'asMain'];
276
+ export const allowedFields = ['locked', 'hiddenable', 'anonymized', 'condition', 'color', 'index', 'indexType', 'type', 'required', 'hint', 'default', 'validate', 'unique', 'name', 'placeholder', 'asMain'];
304
277
 
305
278
 
306
279
 
307
280
  export const getHost = () => {
308
281
  return process.env.HOST || host || 'localhost';
309
- }
282
+ }
283
+
284
+ /**
285
+ * Configuration of MongoDB filters
286
+ */
287
+ export const MONGO_CALC_OPERATORS = {
288
+ $find: {
289
+ label: 'Find in relation',
290
+ description: 'Recherche dans une relation/tableau',
291
+ supportsNesting: true,
292
+ multi: false,
293
+ isFindOperator: true // Nouvelle propriété pour identifier les opérateurs $find
294
+ },
295
+ '$regexMatch': {
296
+ label: 'Find by regex',
297
+ description: 'Find a string matching a regular expression (Ecmascript)',
298
+ args: 2, // input et regex
299
+ specialStructure: true // Indique une structure spéciale
300
+ },
301
+ $and: {
302
+ label: 'Et (and)',
303
+ description: 'Renvoie vrai si toutes les conditions sont vérifiées',
304
+ args: 1,
305
+ multi: true
306
+ },
307
+ $or: {
308
+ label: 'Ou (or)',
309
+ description: 'Renvoie vrai si au moins une des conditions est vérifiée.',
310
+ args: 1,
311
+ multi: true
312
+ },
313
+ $not: {label: 'Non (not)', description: 'Inverse la condition (ex: non égal à).', args: 1, multi: true},
314
+ $nor: {
315
+ label: 'Ou Non (nor)',
316
+ description: 'Renvoie vrai si aucune des conditions n\'est vérifiée',
317
+ args: 1,
318
+ multi: true
319
+ },
320
+ $eq: {label: '=', multi: false, args: 2},
321
+ $ne: {label: '!=', multi: false, args: 2},
322
+ $gt: {label: '>', multi: false, args: 2},
323
+ $gte: {label: '>=', multi: false, args: 2},
324
+ $lt: {label: '<', multi: false, args: 2},
325
+ $lte: {label: '<=', multi: false, args: 2},
326
+ $in: {label: 'in', multi: true, args: 2},
327
+ $nin: {label: 'nin', multi: true, args: 2},
328
+ $all: {label: 'afll', multi: true},
329
+ $size: {
330
+ label: 'size',
331
+ multi: false,
332
+ description: 'Renvoie le nombre d\'éléments dans un tableau',
333
+ disableAdvancedValue: true
334
+ },
335
+ $elemMatch: {label: 'elemMatch', multi: true},
336
+ $type: {
337
+ label: 'type',
338
+ description: 'Renvoie le type BSON de l\'élément',
339
+ multi: false,
340
+ disableAdvancedValue: true
341
+ },
342
+ $add: {label: '+', multi: true},
343
+ $subtract: {label: '-', multi: false, args: 2},
344
+ $multiply: {label: '*', multi: true},
345
+ $divide: {label: '/', multi: false, args: 2},
346
+ $mod: {label: '%', multi: false, args: 2},
347
+ $pow: {label: 'pow', multi: false},
348
+ $sqrt: {label: 'sqrt', multi: false},
349
+ $exp: {label: 'exp', multi: false},
350
+ $abs: {label: 'abs', multi: false},
351
+ $ceil: {label: 'ceil', multi: false},
352
+ $floor: {label: 'floor', multi: false},
353
+ $ln: {label: 'ln', multi: false},
354
+ $log10: {label: 'log', multi: false},
355
+ $concat: {label: 'concat', multi: true},
356
+
357
+ // Date-specific
358
+ $year: {label: 'year', multi: false, isDate: true},
359
+ $month: {label: 'month', multi: false, isDate: true},
360
+ $dayOfMonth: {label: 'dayOfMonth', multi: false, isDate: true},
361
+ $hour: {label: 'hour', multi: false, isDate: true},
362
+ $minute: {label: 'minute', multi: false, isDate: true},
363
+ $second: {label: 'second', multi: false, isDate: true},
364
+ $millisecond: {label: 'ms', multi: false, isDate: true},
365
+ // Date operators
366
+ $dateAdd: {
367
+ label: 'Add to date',
368
+ description: 'Adds a duration to a date',
369
+ isDate: true,
370
+ specialStructure: true,
371
+ args: [
372
+ {name: 'startDate', label: 'Start Date', type: 'date'},
373
+ {
374
+ name: 'unit',
375
+ label: 'Unit',
376
+ type: 'select',
377
+ options: ['year', 'month', 'day', 'hour', 'minute', 'second']
378
+ },
379
+ {name: 'amount', label: 'Amount', type: 'number'},
380
+ {name: 'timezone', label: 'Timezone', type: 'text', optional: true}
381
+ ]
382
+ },
383
+ $dateSubtract: {
384
+ label: 'Subtract from date',
385
+ description: 'Subtracts a duration from a date',
386
+ isDate: true,
387
+ specialStructure: true,
388
+ args: [
389
+ {name: 'startDate', label: 'Start Date', type: 'date'},
390
+ {
391
+ name: 'unit',
392
+ label: 'Unit',
393
+ type: 'select',
394
+ options: ['year', 'month', 'day', 'hour', 'minute', 'second']
395
+ },
396
+ {name: 'amount', label: 'Amount', type: 'number'},
397
+ {name: 'timezone', label: 'Timezone', type: 'text', optional: true}
398
+ ]
399
+ },
400
+ $dateDiff: {
401
+ label: 'Date Difference',
402
+ description: 'Calculates the difference between two dates in specified units',
403
+ isDate: true,
404
+ specialStructure: true,
405
+ args: [
406
+ {
407
+ name: 'startDate',
408
+ label: 'Start Date',
409
+ type: 'date',
410
+ description: 'The starting date (inclusive)'
411
+ },
412
+ {
413
+ name: 'endDate',
414
+ label: 'End Date',
415
+ type: 'date',
416
+ description: 'The ending date (exclusive)'
417
+ },
418
+ {
419
+ name: 'unit',
420
+ label: 'Unit',
421
+ type: 'select',
422
+ options: ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
423
+ description: 'The unit for the result'
424
+ },
425
+ {
426
+ name: 'timezone',
427
+ label: 'Timezone',
428
+ type: 'text',
429
+ optional: true,
430
+ description: 'The timezone (e.g. "Europe/Paris")'
431
+ }
432
+ ]
433
+ },
434
+ $dateToString: {
435
+ label: 'Format date as string',
436
+ description: 'Formats a date as a string',
437
+ isDate: true,
438
+ specialStructure: true,
439
+ args: [
440
+ {
441
+ name: 'date',
442
+ label: 'Date',
443
+ type: 'date'
444
+ },
445
+ {
446
+ name: 'format',
447
+ label: 'Format',
448
+ optional: true,
449
+ type: 'text'
450
+ },
451
+ {
452
+ name: 'timezone',
453
+ label: 'Timezone',
454
+ type: 'text',
455
+ optional: true,
456
+ description: 'The timezone (e.g. "Europe/Paris")'
457
+ }
458
+ ]
459
+ },
460
+
461
+ // Converters
462
+ $toBool: {label: 'toBool', multi: false, converter: true},
463
+ $toString: {label: 'toString', multi: false, converter: true},
464
+ $toInt: {label: 'toInt', multi: false, converter: true},
465
+ $toDouble: {label: 'toDouble', multi: false, converter: true},
466
+
467
+ // --- Special Query Operators (not for $expr) ---
468
+ // These operators are handled by a standard $match stage, not inside $expr.
469
+ // The UI should use them to construct parts of the main filter object.
470
+ $regex: {
471
+ label: 'Regex',
472
+ description: 'Matches strings using a regular expression. Applied to a specific field.',
473
+ isQueryOperator: true,
474
+ specialStructure: true,
475
+ args: [
476
+ { name: 'pattern', label: 'Pattern', type: 'text', description: 'The regex pattern.' },
477
+ { name: 'options', label: 'Options', type: 'text', optional: true, description: 'Regex options (e.g., "i" for case-insensitivity).' }
478
+ ]
479
+ },
480
+
481
+ $text: {
482
+ label: 'Text Search',
483
+ description: 'Performs a full-text search on the collection. Must be a top-level filter condition.',
484
+ isQueryOperator: true,
485
+ isTopLevel: true, // Indicates it's a key in the root of the filter, not under a field name.
486
+ specialStructure: true,
487
+ args: [
488
+ { name: '$search', label: 'Search Terms', type: 'text' },
489
+ { name: '$language', label: 'Language', type: 'text', optional: true },
490
+ { name: '$caseSensitive', label: 'Case Sensitive', type: 'boolean', optional: true },
491
+ { name: '$diacriticSensitive', label: 'Diacritic Sensitive', type: 'boolean', optional: true }
492
+ ]
493
+ },
494
+
495
+ $nearSphere: {
496
+ label: 'Near Sphere',
497
+ description: 'Finds documents near a GeoJSON point on a sphere. Applied to a 2dsphere-indexed field.',
498
+ isQueryOperator: true,
499
+ specialStructure: true,
500
+ args: [
501
+ { name: 'geometry', label: 'Center Point (GeoJSON)', type: 'code', language: 'json', description: 'e.g., { "type": "Point", "coordinates": [ -73.93, 40.82 ] }' },
502
+ { name: 'maxDistance', label: 'Max Distance (meters)', type: 'number', optional: true },
503
+ { name: 'minDistance', label: 'Min Distance (meters)', type: 'number', optional: true }
504
+ ]
505
+ },
506
+
507
+ $geoWithin: {
508
+ label: 'Geo Within',
509
+ description: 'Selects documents with geospatial data that exists entirely within a specified shape.',
510
+ isQueryOperator: true,
511
+ specialStructure: true,
512
+ args: [
513
+ { name: '$geometry', label: 'Shape (GeoJSON)', type: 'code', language: 'json', description: 'A GeoJSON Polygon or MultiPolygon.' }
514
+ ]
515
+ },
516
+
517
+ $geoIntersects: {
518
+ label: 'Geo Intersects',
519
+ description: 'Selects documents whose geospatial data intersects with a specified GeoJSON object.',
520
+ isQueryOperator: true,
521
+ specialStructure: true,
522
+ args: [
523
+ { name: '$geometry', label: 'Geometry (GeoJSON)', type: 'code', language: 'json', description: 'A GeoJSON object to test for intersection.' }
524
+ ]
525
+ },
526
+
527
+ // This represents the $geoNear aggregation stage, which has special placement rules.
528
+ $geoNear: {
529
+ label: 'Geo Near (Stage)',
530
+ description: 'Finds and sorts documents by distance. Must be the first operation in a search.',
531
+ isQueryOperator: true,
532
+ isTopLevel: true, // Indicates it's a key in the root of the filter.
533
+ specialStructure: true,
534
+ args: [
535
+ { name: 'near', label: 'Near Point (GeoJSON)', type: 'code', language: 'json', description: 'The point to search near.' },
536
+ { name: 'distanceField', label: 'Distance Field Name', type: 'text', description: 'The output field to store the distance.' },
537
+ { name: 'spherical', label: 'Spherical', type: 'boolean', optional: true, default: true },
538
+ { name: 'query', label: 'Additional Query', type: 'code', language: 'json', optional: true, description: 'Filters documents before distance calculation.' },
539
+ { name: 'maxDistance', label: 'Max Distance (meters)', type: 'number', optional: true }
540
+ ]
541
+ }
542
+ };
package/src/core.js CHANGED
@@ -435,4 +435,18 @@ export const stringToHslColor = (str) => {
435
435
  const s = 70; // Saturation (fixe pour des couleurs vives mais pas criardes)
436
436
  const l = 55; // Luminosité (fixe pour une bonne lisibilité)
437
437
  return `hsl(${h}, ${s}%, ${l}%)`;
438
- };
438
+ };
439
+
440
+ export function countKeys(t) {
441
+ switch (t?.constructor) {
442
+ case Object: // 1
443
+ return Object
444
+ .values(t)
445
+ .reduce((r, v) => r + 1 + countKeys(v), 0)
446
+ case Array: // 2
447
+ return t
448
+ .reduce((r, v) => r + countKeys(v), 0)
449
+ default: // 3
450
+ return 0
451
+ }
452
+ }
package/src/data.js CHANGED
@@ -30,7 +30,7 @@ export const isLocalUser = (user) => {
30
30
  };
31
31
 
32
32
  export const isDemoUser = (user) => {
33
- return /^demo[0-9]{1,2}$/.test(user?.username);
33
+ return user?.temporary;
34
34
  }
35
35
 
36
36
  export function getUserHash(user) {