data-primals-engine 1.4.2 → 1.4.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/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}
@@ -300,10 +298,270 @@ metaModels['erp'] = { load: [ 'accountingExercise', 'accountingLineItem', 'accou
300
298
  * Available model field attributes
301
299
  * @type {string[]}
302
300
  */
303
- export const allowedFields = ['locked', 'hiddenable', 'anonymized', 'condition', 'color', 'index', 'type', 'required', 'hint', 'default', 'validate', 'unique', 'name', 'placeholder', 'asMain'];
301
+ export const allowedFields = ['locked', 'hiddenable', 'anonymized', 'condition', 'color', 'index', 'indexType', 'type', 'required', 'hint', 'default', 'validate', 'unique', 'name', 'placeholder', 'asMain'];
304
302
 
305
303
 
306
304
 
307
305
  export const getHost = () => {
308
306
  return process.env.HOST || host || 'localhost';
309
- }
307
+ }
308
+
309
+ /**
310
+ * Configuration of MongoDB filters
311
+ */
312
+ export const MONGO_CALC_OPERATORS = {
313
+ $find: {
314
+ label: 'Find in relation',
315
+ description: 'Recherche dans une relation/tableau',
316
+ supportsNesting: true,
317
+ multi: false,
318
+ isFindOperator: true // Nouvelle propriété pour identifier les opérateurs $find
319
+ },
320
+ '$regexMatch': {
321
+ label: 'Find by regex',
322
+ description: 'Find a string matching a regular expression (Ecmascript)',
323
+ args: 2, // input et regex
324
+ specialStructure: true // Indique une structure spéciale
325
+ },
326
+ $and: {
327
+ label: 'Et (and)',
328
+ description: 'Renvoie vrai si toutes les conditions sont vérifiées',
329
+ args: 1,
330
+ multi: true
331
+ },
332
+ $or: {
333
+ label: 'Ou (or)',
334
+ description: 'Renvoie vrai si au moins une des conditions est vérifiée.',
335
+ args: 1,
336
+ multi: true
337
+ },
338
+ $not: {label: 'Non (not)', description: 'Inverse la condition (ex: non égal à).', args: 1, multi: true},
339
+ $nor: {
340
+ label: 'Ou Non (nor)',
341
+ description: 'Renvoie vrai si aucune des conditions n\'est vérifiée',
342
+ args: 1,
343
+ multi: true
344
+ },
345
+ $eq: {label: '=', multi: false, args: 2},
346
+ $ne: {label: '!=', multi: false, args: 2},
347
+ $gt: {label: '>', multi: false, args: 2},
348
+ $gte: {label: '>=', multi: false, args: 2},
349
+ $lt: {label: '<', multi: false, args: 2},
350
+ $lte: {label: '<=', multi: false, args: 2},
351
+ $in: {label: 'in', multi: true, args: 2},
352
+ $nin: {label: 'nin', multi: true, args: 2},
353
+ $all: {label: 'afll', multi: true},
354
+ $size: {
355
+ label: 'size',
356
+ multi: false,
357
+ description: 'Renvoie le nombre d\'éléments dans un tableau',
358
+ disableAdvancedValue: true
359
+ },
360
+ $elemMatch: {label: 'elemMatch', multi: true},
361
+ $type: {
362
+ label: 'type',
363
+ description: 'Renvoie le type BSON de l\'élément',
364
+ multi: false,
365
+ disableAdvancedValue: true
366
+ },
367
+ $add: {label: '+', multi: true},
368
+ $subtract: {label: '-', multi: false, args: 2},
369
+ $multiply: {label: '*', multi: true},
370
+ $divide: {label: '/', multi: false, args: 2},
371
+ $mod: {label: '%', multi: false, args: 2},
372
+ $pow: {label: 'pow', multi: false},
373
+ $sqrt: {label: 'sqrt', multi: false},
374
+ $exp: {label: 'exp', multi: false},
375
+ $abs: {label: 'abs', multi: false},
376
+ $ceil: {label: 'ceil', multi: false},
377
+ $floor: {label: 'floor', multi: false},
378
+ $ln: {label: 'ln', multi: false},
379
+ $log10: {label: 'log', multi: false},
380
+ $concat: {label: 'concat', multi: true},
381
+
382
+ // Date-specific
383
+ $year: {label: 'year', multi: false, isDate: true},
384
+ $month: {label: 'month', multi: false, isDate: true},
385
+ $dayOfMonth: {label: 'dayOfMonth', multi: false, isDate: true},
386
+ $hour: {label: 'hour', multi: false, isDate: true},
387
+ $minute: {label: 'minute', multi: false, isDate: true},
388
+ $second: {label: 'second', multi: false, isDate: true},
389
+ $millisecond: {label: 'ms', multi: false, isDate: true},
390
+ // Date operators
391
+ $dateAdd: {
392
+ label: 'Add to date',
393
+ description: 'Adds a duration to a date',
394
+ isDate: true,
395
+ specialStructure: true,
396
+ args: [
397
+ {name: 'startDate', label: 'Start Date', type: 'date'},
398
+ {
399
+ name: 'unit',
400
+ label: 'Unit',
401
+ type: 'select',
402
+ options: ['year', 'month', 'day', 'hour', 'minute', 'second']
403
+ },
404
+ {name: 'amount', label: 'Amount', type: 'number'},
405
+ {name: 'timezone', label: 'Timezone', type: 'text', optional: true}
406
+ ]
407
+ },
408
+ $dateSubtract: {
409
+ label: 'Subtract from date',
410
+ description: 'Subtracts a duration from a date',
411
+ isDate: true,
412
+ specialStructure: true,
413
+ args: [
414
+ {name: 'startDate', label: 'Start Date', type: 'date'},
415
+ {
416
+ name: 'unit',
417
+ label: 'Unit',
418
+ type: 'select',
419
+ options: ['year', 'month', 'day', 'hour', 'minute', 'second']
420
+ },
421
+ {name: 'amount', label: 'Amount', type: 'number'},
422
+ {name: 'timezone', label: 'Timezone', type: 'text', optional: true}
423
+ ]
424
+ },
425
+ $dateDiff: {
426
+ label: 'Date Difference',
427
+ description: 'Calculates the difference between two dates in specified units',
428
+ isDate: true,
429
+ specialStructure: true,
430
+ args: [
431
+ {
432
+ name: 'startDate',
433
+ label: 'Start Date',
434
+ type: 'date',
435
+ description: 'The starting date (inclusive)'
436
+ },
437
+ {
438
+ name: 'endDate',
439
+ label: 'End Date',
440
+ type: 'date',
441
+ description: 'The ending date (exclusive)'
442
+ },
443
+ {
444
+ name: 'unit',
445
+ label: 'Unit',
446
+ type: 'select',
447
+ options: ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
448
+ description: 'The unit for the result'
449
+ },
450
+ {
451
+ name: 'timezone',
452
+ label: 'Timezone',
453
+ type: 'text',
454
+ optional: true,
455
+ description: 'The timezone (e.g. "Europe/Paris")'
456
+ }
457
+ ]
458
+ },
459
+ $dateToString: {
460
+ label: 'Format date as string',
461
+ description: 'Formats a date as a string',
462
+ isDate: true,
463
+ specialStructure: true,
464
+ args: [
465
+ {
466
+ name: 'date',
467
+ label: 'Date',
468
+ type: 'date'
469
+ },
470
+ {
471
+ name: 'format',
472
+ label: 'Format',
473
+ optional: true,
474
+ type: 'text'
475
+ },
476
+ {
477
+ name: 'timezone',
478
+ label: 'Timezone',
479
+ type: 'text',
480
+ optional: true,
481
+ description: 'The timezone (e.g. "Europe/Paris")'
482
+ }
483
+ ]
484
+ },
485
+
486
+ // Converters
487
+ $toBool: {label: 'toBool', multi: false, converter: true},
488
+ $toString: {label: 'toString', multi: false, converter: true},
489
+ $toInt: {label: 'toInt', multi: false, converter: true},
490
+ $toDouble: {label: 'toDouble', multi: false, converter: true},
491
+
492
+ // --- Special Query Operators (not for $expr) ---
493
+ // These operators are handled by a standard $match stage, not inside $expr.
494
+ // The UI should use them to construct parts of the main filter object.
495
+ $regex: {
496
+ label: 'Regex',
497
+ description: 'Matches strings using a regular expression. Applied to a specific field.',
498
+ isQueryOperator: true,
499
+ specialStructure: true,
500
+ args: [
501
+ { name: 'pattern', label: 'Pattern', type: 'text', description: 'The regex pattern.' },
502
+ { name: 'options', label: 'Options', type: 'text', optional: true, description: 'Regex options (e.g., "i" for case-insensitivity).' }
503
+ ]
504
+ },
505
+
506
+ $text: {
507
+ label: 'Text Search',
508
+ description: 'Performs a full-text search on the collection. Must be a top-level filter condition.',
509
+ isQueryOperator: true,
510
+ isTopLevel: true, // Indicates it's a key in the root of the filter, not under a field name.
511
+ specialStructure: true,
512
+ args: [
513
+ { name: '$search', label: 'Search Terms', type: 'text' },
514
+ { name: '$language', label: 'Language', type: 'text', optional: true },
515
+ { name: '$caseSensitive', label: 'Case Sensitive', type: 'boolean', optional: true },
516
+ { name: '$diacriticSensitive', label: 'Diacritic Sensitive', type: 'boolean', optional: true }
517
+ ]
518
+ },
519
+
520
+ $nearSphere: {
521
+ label: 'Near Sphere',
522
+ description: 'Finds documents near a GeoJSON point on a sphere. Applied to a 2dsphere-indexed field.',
523
+ isQueryOperator: true,
524
+ specialStructure: true,
525
+ args: [
526
+ { name: 'geometry', label: 'Center Point (GeoJSON)', type: 'code', language: 'json', description: 'e.g., { "type": "Point", "coordinates": [ -73.93, 40.82 ] }' },
527
+ { name: 'maxDistance', label: 'Max Distance (meters)', type: 'number', optional: true },
528
+ { name: 'minDistance', label: 'Min Distance (meters)', type: 'number', optional: true }
529
+ ]
530
+ },
531
+
532
+ $geoWithin: {
533
+ label: 'Geo Within',
534
+ description: 'Selects documents with geospatial data that exists entirely within a specified shape.',
535
+ isQueryOperator: true,
536
+ specialStructure: true,
537
+ args: [
538
+ { name: '$geometry', label: 'Shape (GeoJSON)', type: 'code', language: 'json', description: 'A GeoJSON Polygon or MultiPolygon.' }
539
+ ]
540
+ },
541
+
542
+ $geoIntersects: {
543
+ label: 'Geo Intersects',
544
+ description: 'Selects documents whose geospatial data intersects with a specified GeoJSON object.',
545
+ isQueryOperator: true,
546
+ specialStructure: true,
547
+ args: [
548
+ { name: '$geometry', label: 'Geometry (GeoJSON)', type: 'code', language: 'json', description: 'A GeoJSON object to test for intersection.' }
549
+ ]
550
+ },
551
+
552
+ // This represents the $geoNear aggregation stage, which has special placement rules.
553
+ $geoNear: {
554
+ label: 'Geo Near (Stage)',
555
+ description: 'Finds and sorts documents by distance. Must be the first operation in a search.',
556
+ isQueryOperator: true,
557
+ isTopLevel: true, // Indicates it's a key in the root of the filter.
558
+ specialStructure: true,
559
+ args: [
560
+ { name: 'near', label: 'Near Point (GeoJSON)', type: 'code', language: 'json', description: 'The point to search near.' },
561
+ { name: 'distanceField', label: 'Distance Field Name', type: 'text', description: 'The output field to store the distance.' },
562
+ { name: 'spherical', label: 'Spherical', type: 'boolean', optional: true, default: true },
563
+ { name: 'query', label: 'Additional Query', type: 'code', language: 'json', optional: true, description: 'Filters documents before distance calculation.' },
564
+ { name: 'maxDistance', label: 'Max Distance (meters)', type: 'number', optional: true }
565
+ ]
566
+ }
567
+ };
@@ -1486,6 +1486,12 @@ export const defaultModels = {
1486
1486
  "icon": "FaCode",
1487
1487
  description: "Defines custom API endpoints that execute a server-side script.",
1488
1488
  fields: [
1489
+ {
1490
+ name: "isActive",
1491
+ type: "boolean",
1492
+ default: true,
1493
+ hint: "If checked, the endpoint is active and can be called."
1494
+ },
1489
1495
  {
1490
1496
  name: "name",
1491
1497
  type: "string",
@@ -1493,12 +1499,6 @@ export const defaultModels = {
1493
1499
  asMain: true,
1494
1500
  hint: "A human-readable name to identify the endpoint."
1495
1501
  },
1496
- {
1497
- name: "isActive",
1498
- type: "boolean",
1499
- default: true,
1500
- hint: "If checked, the endpoint is active and can be called."
1501
- },
1502
1502
  {
1503
1503
  name: "path",
1504
1504
  type: "string",
@@ -1514,12 +1514,6 @@ export const defaultModels = {
1514
1514
  default: "POST",
1515
1515
  hint: "The HTTP method required to call this endpoint."
1516
1516
  },
1517
- {
1518
- name: 'isPublic',
1519
- type: 'boolean',
1520
- default: false,
1521
- hint: "Si coché, ce point d'accès sera accessible sans authentification."
1522
- },
1523
1517
  {
1524
1518
  name: "code",
1525
1519
  type: "code",
@@ -1534,6 +1528,12 @@ logger.info('Custom endpoint executed with body:', request.body);
1534
1528
 
1535
1529
  return { success: true, message: 'Endpoint executed!', received: request.body };
1536
1530
  `
1531
+ },
1532
+ {
1533
+ name: 'isPublic',
1534
+ type: 'boolean',
1535
+ default: false,
1536
+ hint: "Si coché, ce point d'accès sera accessible sans authentification."
1537
1537
  }
1538
1538
  ]
1539
1539
  }
package/src/engine.js CHANGED
@@ -22,6 +22,8 @@ import {Event} from "./events.js";
22
22
  import path from "node:path";
23
23
  import { fileURLToPath } from 'node:url';
24
24
  import {validateModelStructure} from "./modules/data/data.validation.js";
25
+ import { setSafeRegex } from "./filter.js";
26
+ import safeRegexCallback from "safe-regex";
25
27
  import {createModel, deleteModels, getModels, installAllPacks} from "./modules/data/data.operations.js";
26
28
  // Constants
27
29
 
@@ -97,6 +99,9 @@ export const MongoDatabase = MongoClient.db(dbName);
97
99
 
98
100
  export const Engine = {
99
101
  Create: async (options = { app : null}) => {
102
+ // On injecte la dépendance safe-regex dans le module de filtrage au tout début.
103
+ setSafeRegex(safeRegexCallback);
104
+
100
105
  const engine = GameObject.Create("Engine");
101
106
  console.log("Creating engine", Config.Get('modules'));
102
107
  const logger = engine.addComponent(Logger);