data-primals-engine 1.2.6-rc3 → 1.2.6

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.
@@ -13,17 +13,17 @@
13
13
  },
14
14
  "peerDependencies": {
15
15
  "@tiptap/react": "^2.11.5",
16
- "react-router-dom": "^7.1.5",
17
- "react-query": "^3.39.3",
18
- "react-icons": "^5.0.1",
19
- "react-tooltip": "^5.28.1",
20
- "react-helmet": "^6.1.0",
21
- "react-cookie": "^8.0.0",
22
- "react-syntax-highlighter": "^15.6.1",
23
- "react-international-phone": "^4.5.0",
24
16
  "react-chartjs-2": "^5.3.0",
25
17
  "react-code-blocks": "^0.1.6",
26
- "react-ga": "^3.3.1"
18
+ "react-cookie": "^8.0.0",
19
+ "react-ga": "^3.3.1",
20
+ "react-helmet": "^6.1.0",
21
+ "react-icons": "^5.0.1",
22
+ "react-international-phone": "^4.5.0",
23
+ "react-query": "^3.39.3",
24
+ "react-router-dom": "^7.1.5",
25
+ "react-syntax-highlighter": "^15.6.1",
26
+ "react-tooltip": "^5.28.1"
27
27
  },
28
28
  "dependencies": {
29
29
  "@babel/runtime": "^7.24.7",
@@ -43,15 +43,16 @@
43
43
  "@uiw/react-codemirror": "^4.23.10",
44
44
  "chart.js": "^4.4.9",
45
45
  "chartjs-adapter-date-fns": "^3.0.0",
46
- "react-dom": "^18.3.1",
47
- "react-router": "^7.2.0",
48
- "yet-another-react-lightbox": "^3.22.0",
49
- "react": ">=18.0.0",
50
46
  "classnames": "^2.5.1",
51
47
  "date-fns": "^4.1.0",
52
48
  "express-rate-limit": "^7.5.1",
53
49
  "lowlight": "^3.3.0",
54
- "uniqid": "^5.4.0"
50
+ "react": ">=18.0.0",
51
+ "react-dom": "^18.3.1",
52
+ "react-router": "^7.2.0",
53
+ "react-switch": "^7.1.0",
54
+ "uniqid": "^5.4.0",
55
+ "yet-another-react-lightbox": "^3.22.0"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@eslint/js": "^9.17.0",
@@ -67,4 +68,4 @@
67
68
  "sass-embedded": "^1.83.4",
68
69
  "vite": "^6.1.1"
69
70
  }
70
- }
71
+ }
@@ -421,6 +421,7 @@ input[type=number]::-webkit-outer-spin-button {
421
421
  form .field {
422
422
  width: 100%;
423
423
  .help {
424
+ width: 100%;
424
425
  color: #575757;
425
426
  padding: 4px;
426
427
  }
@@ -239,7 +239,7 @@ export const DataEditor = forwardRef(function MyDataEditor({
239
239
  onChange={(e) => handleChange({name: field.name, value: e})}/>
240
240
  case 'boolean':
241
241
  return <CheckboxField
242
- help={value && t('field_' + model.name + '_' + field.name + '_hint', field.hint || '')}
242
+ help={t('field_' + model.name + '_' + field.name + '_hint', field.hint || '')}
243
243
  key={field.name} {...inputProps} checked={value}
244
244
  onChange={(e) => handleChange({name: field.name, value: e.target.checked})}/>
245
245
  case 'string':
@@ -34,7 +34,7 @@ import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
34
34
  import { PhoneInput } from 'react-international-phone';
35
35
  import 'react-international-phone/style.css';
36
36
  import {useAuthContext} from "./contexts/AuthContext.jsx";
37
-
37
+ import Switch from "react-switch";
38
38
  export const Form = ({
39
39
  name,
40
40
  onValidate,
@@ -492,20 +492,6 @@ const CheckboxField = forwardRef(
492
492
  return (
493
493
  <>
494
494
  <div className={cn({field: true, "field-checkbox": true,"field-bg": true})}>
495
- <div className="inline"><input
496
- aria-required={required}
497
- aria-readonly={readOnly}
498
- readOnly={readOnly}
499
- type="checkbox"
500
- checked={value}
501
- placeholder={placeholder}
502
- id={id}
503
- name={name}
504
- onChange={handleChange}
505
- minLength={minlength}
506
- maxLength={maxlength}
507
- {...rest}
508
- />
509
495
  {label && (
510
496
  <label
511
497
  contentEditable={editable}
@@ -522,7 +508,9 @@ const CheckboxField = forwardRef(
522
508
  )}
523
509
  </label>
524
510
  )}
525
- </div>
511
+ <Switch
512
+ onChange={handleChange}
513
+ checked={value} />
526
514
  {help && <div className="flex help">{help}</div>}
527
515
  </div>
528
516
  {errors.length > 0 && (
@@ -2,6 +2,9 @@
2
2
  export const websiteTranslations = {
3
3
  fr: {
4
4
  translation: {
5
+
6
+ "api.data.relationFilterFailed": "La valeur {{value}} pour le champ {{field}} ne respecte pas le filtre de relation défini.",
7
+
5
8
  "dataimporter.excelPreview":"Aperçu des données Excel",
6
9
  "dataimporter.previewNote": "Note: Ceci est un aperçu des premières lignes. Les cellules vides sont affichées comme \"(vide)\".",
7
10
  "dataimporter.nullValue": "(vide)",
@@ -567,6 +570,8 @@ export const websiteTranslations = {
567
570
  "suggested_prompt.recipes.title": "Cooking Recipes",
568
571
  "suggested_prompt.recipes.prompt": "A collection of cooking recipes, with ingredients, instructions, and preparation time.",
569
572
 
573
+ "api.data.relationFilterFailed": "The value {{value}} for field {{field}} does not match the defined relationship filter.",
574
+
570
575
  "api.data.duplicateValue": "The value '{{value}}' already exists for the unique field '{{field}}'.",
571
576
  "api.model.invalidStructure": "The model is invalid. Some fields are incorrect.",
572
577
  "api.validate.fieldArray": "Field '{{0}}' must be an array.",
@@ -1975,6 +1980,7 @@ export const websiteTranslations = {
1975
1980
  "suggested_prompt.recipes.prompt": "Una colección de recetas de cocina, con ingredientes, instrucciones y tiempo de preparación.",
1976
1981
 
1977
1982
  "api.data.duplicateValue": "El valor '{{value}}' ya existe para el campo único '{{field}}'.",
1983
+ "api.data.relationFilterFailed": "El valor {{value}} del campo {{field}} no coincide con el filtro de relación definido.",
1978
1984
  "api.model.invalidStructure": "El modelo no es válido. Algunos campos son incorrectos.",
1979
1985
  "api.validate.fieldArray": "El campo '{{0}}' debe ser un array.",
1980
1986
  "api.validate.invalidMimeType": "Tipo de archivo '{{type}}' no válido. Los tipos permitidos son: {{authorized}}.",
@@ -3388,6 +3394,7 @@ export const websiteTranslations = {
3388
3394
  "suggested_prompt.recipes.prompt": "Uma coleção de receitas culinárias, com ingredientes, instruções e tempo de preparação.",
3389
3395
 
3390
3396
  "api.data.duplicateValue": "O valor '{{value}}' já existe para o campo exclusivo '{{field}}'.",
3397
+ "api.data.relationFilterFailed": "O valor {{value}} para o campo {{field}} não corresponde ao filtro de relacionamento definido.",
3391
3398
  "api.model.invalidStructure": "O modelo é inválido. Alguns campos estão incorretos.",
3392
3399
  "api.validate.fieldArray": "O campo '{{0}}' deve ser um array.",
3393
3400
  "api.validate.invalidMimeType": "Tipo de ficheiro inválido '{{type}}'. Os tipos permitidos são: {{authorized}}.",
@@ -4782,6 +4789,7 @@ export const websiteTranslations = {
4782
4789
  "suggested_prompt.recipes.prompt": "Eine Sammlung von Kochrezepten mit Zutaten, Anweisungen und Vorbereitungszeit.",
4783
4790
 
4784
4791
  "api.data.duplicateValue": "Der Wert '{{value}}' ist für das eindeutige Feld '{{field}}' bereits vorhanden.",
4792
+ "api.data.relationFilterFailed": "Der Wert {{value}} für das Feld {{field}} entspricht nicht dem definierten Beziehungsfilter.",
4785
4793
  "api.model.invalidStructure": "Das Modell ist ungültig. Einige Felder sind fehlerhaft.",
4786
4794
  "api.validate.fieldArray": "Feld '{{0}}' muss ein Array sein.",
4787
4795
  "api.validate.invalidMimeType": "Ungültiger Dateityp '{{type}}'. Zulässige Typen sind: {{authorized}}.",
@@ -6185,6 +6193,7 @@ export const websiteTranslations = {
6185
6193
  "suggested_prompt.recipes.prompt": "Una raccolta di ricette di cucina, con ingredienti, istruzioni e tempi di preparazione.",
6186
6194
 
6187
6195
  "api.data.duplicateValue": "Il valore '{{value}}' esiste già per il campo univoco '{{field}}'.",
6196
+ "api.data.relationFilterFailed": "Il valore {{value}} per il campo {{field}} non corrisponde al filtro di relazione definito.",
6188
6197
  "api.model.invalidStructure": "Il modello non è valido. Alcuni campi sono errati.",
6189
6198
  "api.validate.fieldArray": "Il campo '{{0}}' deve essere un array.",
6190
6199
  "api.validate.invalidMimeType": "Tipo di file '{{type}}' non valido. I tipi consentiti sono: {{authorized}}.",
@@ -7589,6 +7598,7 @@ export const websiteTranslations = {
7589
7598
  "suggested_prompt.recipes.prompt": "Sbírka receptů na vaření s ingrediencemi, návodem a přípravou čas.",
7590
7599
 
7591
7600
  "api.data.duplicateValue": "Hodnota '{{value}}' pro jedinečné pole '{{field}}' již existuje.",
7601
+ "api.data.relationFilterFailed": "Hodnota {{value}} pro pole {{field}} neodpovídá definovanému filtru vztahů.",
7592
7602
  "api.model.invalidStructure": "Model je neplatný. Některá pole jsou nesprávná.",
7593
7603
  "api.validate.fieldArray": "Pole '{{0}}' musí být pole.",
7594
7604
  "api.validate.invalidMimeType": "Neplatný typ souboru '{{type}}'. Povolené typy jsou: {{authorized}}.",
@@ -8988,6 +8998,7 @@ export const websiteTranslations = {
8988
8998
  "suggested_prompt.recipes.prompt": "Коллекция кулинарных рецептов с ингредиентами, инструкциями и описанием приготовления. время.",
8989
8999
 
8990
9000
  "api.data.duplicateValue": "Значение '{{value}}' уже существует для уникального поля '{{field}}'.",
9001
+ "api.data.relationFilterFailed": "Значение {{value}} для поля {{field}} не соответствует заданному фильтру связи.",
8991
9002
  "api.model.invalidStructure": "Модель недействительна. Некоторые поля неверны.",
8992
9003
  "api.validate.fieldArray": "Поле '{{0}}' должно быть массивом.",
8993
9004
  "api.validate.invalidMimeType": "Недопустимый тип файла '{{type}}'. Допустимые типы: {{authorized}}.",
@@ -10396,6 +10407,7 @@ export const websiteTranslations = {
10396
10407
  "suggested_prompt.recipes.prompt": "مجموعة من وصفات الطبخ، مع المكونات، والتعليمات، ومدة التحضير.",
10397
10408
 
10398
10409
  "api.data.duplicateValue": "القيمة '{{value}}' موجودة بالفعل للحقل الفريد '{{field}}'.",
10410
+ "api.data.relationFilterFailed": "القيمة {{value}} للحقل {{field}} لا تتطابق مع مرشح العلاقة المحدد.",
10399
10411
  "api.model.invalidStructure": "النموذج غير صالح. بعض الحقول غير صحيحة.",
10400
10412
  "api.validate.fieldArray": "يجب أن يكون الحقل '{{0}}' مصفوفة.",
10401
10413
  "api.validate.invalidMimeType": "نوع الملف '{{type}}' غير صالح. الأنواع المسموح بها هي: {{authorized}}.",
@@ -11823,6 +11835,7 @@ export const websiteTranslations = {
11823
11835
  "suggested_prompt.recipes.prompt": "En samling matlagningsrecept, med ingredienser, instruktioner, och förberedelsetid.",
11824
11836
 
11825
11837
  "api.data.duplicateValue": "Värdet '{{value}}' finns redan för det unika fältet '{{field}}'.",
11838
+ "api.data.relationFilterFailed": "Värdet {{value}} för fältet {{field}} matchar inte det definierade relationsfiltret.",
11826
11839
  "api.model.invalidStructure": "Modellen är ogiltig. Vissa fält är felaktiga.",
11827
11840
  "api.validate.fieldArray": "Fältet '{{0}}' måste vara en array.",
11828
11841
  "api.validate.invalidMimeType": "Ogiltig filtyp '{{type}}'. Tillåtna typer är: {{authorized}}.",
@@ -13222,6 +13235,8 @@ export const websiteTranslations = {
13222
13235
  "suggested_prompt.recipes.prompt": "Μια συλλογή συνταγών μαγειρικής, με υλικά, οδηγίες και χρόνο προετοιμασίας.",
13223
13236
 
13224
13237
  "api.data.duplicateValue": "Η τιμή '{{value}}' υπάρχει ήδη για το μοναδικό πεδίο '{{field}}'.",
13238
+ "api.data.relationFilterFailed": "Η τιμή {{value}} για το πεδίο {{field}} δεν ταιριάζει με το καθορισμένο φίλτρο σχέσης.",
13239
+
13225
13240
  "api.model.invalidStructure": "Το μοντέλο δεν είναι έγκυρο. Ορισμένα πεδία είναι λανθασμένα.",
13226
13241
  "api.validate.fieldArray": "Το πεδίο '{{0}}' πρέπει να είναι ένας πίνακας.",
13227
13242
  "api.validate.invalidMimeType": "Μη έγκυρος τύπος αρχείου '{{type}}'. Οι επιτρεπόμενοι τύποι είναι: {{authorized}}.",
@@ -14629,6 +14644,8 @@ export const websiteTranslations = {
14629
14644
  "suggested_prompt.recipes.prompt": "مجموعه‌ای از دستور پخت غذا، با مواد لازم، دستورالعمل و زمان آماده‌سازی.",
14630
14645
 
14631
14646
  "api.data.duplicateValue": "مقدار '{{value}}' از قبل برای فیلد منحصر به فرد '{{field}}' وجود دارد.",
14647
+ "api.data.relationFilterFailed": "مقدار {{value}} برای فیلد {{field}} با فیلتر رابطه تعریف شده مطابقت ندارد.",
14648
+
14632
14649
  "api.model.invalidStructure": "مدل نامعتبر است. برخی از فیلدها نادرست هستند.",
14633
14650
  "api.validate.fieldArray": "فیلد '{{0}}' باید یک آرایه باشد.",
14634
14651
  "api.validate.invalidMimeType": "نوع فایل '{{type}}' نامعتبر است. انواع مجاز عبارتند از: {{authorized}}.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "data-primals-engine",
3
- "version": "1.2.6-rc3",
3
+ "version": "1.2.6",
4
4
  "description": "data-primals-engine is a package responsible from handling large amount of data using MongoDB in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation. It also has integrated AI assistant.",
5
5
  "main": "src/engine.js",
6
6
  "type": "module",
package/src/filter.js CHANGED
@@ -175,15 +175,45 @@ export const isConditionMet = (model, cond, formData, allModels, user) => {
175
175
 
176
176
  if (!condition) return true;
177
177
 
178
+ // Cas 0: Condition est une valeur primitive (string, number, boolean)
179
+ // On la considère comme toujours vraie (comportement de searchData)
180
+ if (typeof condition !== 'object' || condition === null) {
181
+ return true;
182
+ }
183
+
178
184
  // Cas 1: Condition simple {field: value} → transformée en {field: {$eq: value}}
179
185
  if (typeof condition === 'object' && !Array.isArray(condition)) {
180
186
  const keys = Object.keys(condition);
187
+
188
+ // Cas spécial pour les conditions de type {field: value} (pas d'opérateur $)
181
189
  if (keys.length === 1 && !keys[0].startsWith('$') &&
182
190
  typeof condition[keys[0]] !== 'object') {
183
- const simpleCondition = {
184
- [keys[0]]: {$eq: condition[keys[0]]}
185
- };
186
- return evaluateSingleCondition(model, simpleCondition, formData, allModels, user);
191
+ const fieldName = keys[0];
192
+ const value = condition[fieldName];
193
+
194
+ // Si la valeur est null/undefined, on vérifie simplement l'existence
195
+ if (value === null || value === undefined) {
196
+ return formData[fieldName] === value;
197
+ }
198
+
199
+ // Sinon on fait une comparaison d'égalité simple
200
+ return formData[fieldName] == value;
201
+ }
202
+
203
+ // Cas spécial pour les tableaux - vérifie si la valeur est incluse
204
+ if (keys.length === 1 && !keys[0].startsWith('$') &&
205
+ Array.isArray(condition[keys[0]])) {
206
+ const fieldName = keys[0];
207
+ const values = condition[fieldName];
208
+ const fieldValue = formData[fieldName];
209
+
210
+ // Si le champ est aussi un tableau, vérifie l'intersection
211
+ if (Array.isArray(fieldValue)) {
212
+ return fieldValue.some(v => values.includes(v));
213
+ }
214
+
215
+ // Sinon vérifie si la valeur est dans le tableau
216
+ return values.includes(fieldValue);
187
217
  }
188
218
  }
189
219
 
@@ -218,4 +248,4 @@ export const isConditionMet = (model, cond, formData, allModels, user) => {
218
248
 
219
249
  // Cas 4: Tous les autres cas (conditions normales avec opérateurs)
220
250
  return evaluateSingleCondition(model, condition, formData, allModels, user);
221
- };
251
+ };
@@ -1,8 +1,6 @@
1
1
  import NodeCache from "node-cache";
2
- import i18n from "../../i18n.js";
3
- import {allowedFields, maxStringLength} from "../../constants.js";
4
2
  import path from "node:path";
5
-
3
+ import {Worker} from 'worker_threads';
6
4
  export const modelsCache = new NodeCache( { stdTTL: 100, checkperiod: 120 } );
7
5
 
8
6
  export const mongoDBWhitelist = [
@@ -1,6 +1,6 @@
1
1
  import {Logger} from "../../gameObject.js";
2
2
  import {BSON, ObjectId} from "mongodb";
3
- import {promisify} from 'node:util';
3
+ import util, {promisify} from 'node:util';
4
4
  import crypto from "node:crypto";
5
5
  import {exec, execFile} from 'node:child_process';
6
6
  import sanitizeHtml from 'sanitize-html';
@@ -67,7 +67,6 @@ import {
67
67
  import NodeCache from "node-cache";
68
68
  import AWS from 'aws-sdk';
69
69
  import checkDiskSpace from "check-disk-space";
70
- import {Worker} from 'worker_threads';
71
70
  import {addFile, removeFile} from "../file.js";
72
71
  import {downloadFromS3, getUserS3Config, listS3Backups, uploadToS3} from "../bucket.js";
73
72
  import {
@@ -2042,22 +2041,41 @@ async function insertAndResolveRelations(doc, model, collection, me, idMap) {
2042
2041
 
2043
2042
  for (const field of model.fields) {
2044
2043
  if (field.type === 'relation' && field.relationFilter && docToProcess[field.name]) {
2045
- const relatedIds = Array.isArray(docToProcess[field.name]) ? docToProcess[field.name] : [docToProcess[field.name]];
2046
- for (const id of relatedIds) {
2047
- const targetCollection = await getCollectionForUser(me, field.targetModel);
2048
- const validationQuery = {
2049
- _id: new ObjectId(id), // L'ID doit correspondre
2050
- ...field.relationFilter // ET le document doit respecter le filtre
2051
- };
2052
- const relatedDoc = await targetCollection.findOne(validationQuery);
2053
- if (!relatedDoc) {
2054
- // Si on ne trouve rien, c'est que l'ID est invalide ou ne respecte pas le filtre.
2055
- throw new Error(`La valeur '${id}' pour le champ '${field.name}' ne respecte pas le filtre de relation défini.`);
2056
- }
2044
+
2045
+ const relatedIds = Array.isArray(docToProcess[field.name])
2046
+ ? docToProcess[field.name]
2047
+ : [docToProcess[field.name]];
2048
+
2049
+ // Préparer un filtre global : match si _id dans relatedIds ET respecte relationFilter
2050
+ const validationQuery = {
2051
+ $and: [
2052
+ { $in: ['$_id', relatedIds.map(id => ({ $toObjectId: id }))] },
2053
+ field.relationFilter
2054
+ ]
2055
+ };
2056
+
2057
+ const relatedDocs = await searchData({
2058
+ filter: validationQuery,
2059
+ model: field.relation,
2060
+ limit: relatedIds.length
2061
+ }, me);
2062
+
2063
+ if ((relatedDocs?.count || 0) !== relatedIds.length) {
2064
+ const invalidIds = relatedIds.filter(id =>
2065
+ !relatedDocs.data.some(doc => doc._id.toString() === id.toString())
2066
+ );
2067
+ throw new Error(
2068
+ i18n.t(
2069
+ 'api.data.relationFilterFailed',
2070
+ 'Les valeurs {{values}} pour le champ {{field}} ne respectent pas le filtre de relation défini.',
2071
+ { field: field.name, values: invalidIds.join(', ') }
2072
+ )
2073
+ );
2057
2074
  }
2058
2075
  }
2059
2076
  }
2060
2077
 
2078
+
2061
2079
  // Insertion en conservant éventuellement l'ID original
2062
2080
  const result = docToProcess._id
2063
2081
  ? await collection.insertOne(docToProcess)
@@ -2297,28 +2315,37 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
2297
2315
  }
2298
2316
 
2299
2317
  for (const field of model.fields) {
2300
- // On ne vérifie que si un champ de relation avec un filtre est en cours de modification.
2301
- if (field.type === 'relation' && field.relationFilter && updateData[field.name] !== undefined) {
2318
+ if (field.type === 'relation' && field.relationFilter && updateData[field.name]) {
2319
+
2302
2320
  const relatedIds = Array.isArray(updateData[field.name])
2303
2321
  ? updateData[field.name]
2304
- : (updateData[field.name] ? [updateData[field.name]] : []);
2305
-
2306
- for (const id of relatedIds) {
2307
- if (!id || !isObjectId(id)) continue; // Ignorer les valeurs null/invalides
2322
+ : [updateData[field.name]];
2308
2323
 
2309
- const targetCollection = await getCollectionForUser(user, field.relation);
2310
-
2311
- const validationQuery = {
2312
- _id: new ObjectId(id),
2313
- ...field.relationFilter
2314
- };
2324
+ // Préparer un filtre global : match si _id dans relatedIds ET respecte relationFilter
2325
+ const validationQuery = {
2326
+ $and: [
2327
+ { $in: ['$_id', relatedIds.map(id => ({ $toObjectId: id }))] },
2328
+ field.relationFilter
2329
+ ]
2330
+ };
2315
2331
 
2316
- const relatedDoc = await targetCollection.findOne(validationQuery);
2332
+ const relatedDocs = await searchData({
2333
+ filter: validationQuery,
2334
+ model: field.relation,
2335
+ limit: relatedIds.length
2336
+ }, user);
2317
2337
 
2318
- if (!relatedDoc) {
2319
- // Si on ne trouve rien, c'est que l'ID est invalide ou ne respecte pas le filtre.
2320
- throw new Error(`La valeur '${id}' pour le champ '${field.name}' ne respecte pas le filtre de relation défini.`);
2321
- }
2338
+ if ((relatedDocs?.count || 0) !== relatedIds.length) {
2339
+ const invalidIds = relatedIds.filter(id =>
2340
+ !relatedDocs.data.some(doc => doc._id.toString() === id.toString())
2341
+ );
2342
+ throw new Error(
2343
+ i18n.t(
2344
+ 'api.data.relationFilterFailed',
2345
+ 'Les valeurs {{values}} pour le champ {{field}} ne respectent pas le filtre de relation défini.',
2346
+ { field: field.name, values: invalidIds.join(', ') }
2347
+ )
2348
+ );
2322
2349
  }
2323
2350
  }
2324
2351
  }
@@ -1,10 +1,12 @@
1
1
 
2
- import process from "process";
3
2
  import {Logger} from "../gameObject.js";
4
3
  import {MongoDatabase} from "../engine.js";
4
+ import {ObjectId} from "mongodb";
5
5
 
6
6
  export let modelsCollection, datasCollection, filesCollection, packsCollection;
7
7
 
8
+ export {ObjectId};
9
+
8
10
  let engine, logger;
9
11
  export async function onInit(defaultEngine) {
10
12
  engine = defaultEngine;