@anteros/core 0.0.3 → 0.0.4

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 (2) hide show
  1. package/package.json +1 -1
  2. package/utils/func.ts +36 -9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@anteros/core",
3
3
  "module": "index.ts",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "type": "module",
6
6
  "description": "Anteros Core",
7
7
  "private": false,
package/utils/func.ts CHANGED
@@ -208,16 +208,28 @@ function cleanDeep<T extends DeepRecord | DeepRecord[]>(data: T): Partial<T> {
208
208
  return result as Partial<T>;
209
209
  }
210
210
 
211
+ /**
212
+ * Vérifie si une string est une date valide.
213
+ * Accepte : ISO 8601 (YYYY-MM-DD, YYYY-MM-DDTHH:mm...) et timestamps UNIX (s ou ms).
214
+ * Rejette les strings ambiguës que certains moteurs JS parsent en date (ex: "REF-2024").
215
+ */
211
216
  function isDate(value: string): boolean {
212
217
  if (!value || typeof value !== 'string') return false;
213
218
  const trimmed = value.trim();
214
- // Chaînes 100 % chiffres : seuls timestamps UNIX s (10) ou ms (13) — sinon codes type "170273" seraient des dates via `new Date("170273")`.
219
+
220
+ // Timestamps numériques purs (10 ou 13 chiffres)
215
221
  if (/^\d+$/.test(trimmed)) {
216
222
  const len = trimmed.length;
217
223
  if (len !== 10 && len !== 13) return false;
218
224
  const ms = new Date(Number(trimmed)).getTime();
219
225
  return !isNaN(ms);
220
226
  }
227
+
228
+ // ISO 8601 : YYYY-MM-DD avec heure optionnelle
229
+ if (!/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})?)?$/.test(trimmed)) {
230
+ return false;
231
+ }
232
+
221
233
  const date = new Date(trimmed);
222
234
  return !isNaN(date.getTime());
223
235
  }
@@ -269,14 +281,26 @@ function joinFormatToDatePath(parent: string, segment: string | number): string
269
281
  return parent === '' ? s : `${parent}.${s}`;
270
282
  }
271
283
 
272
- /** Correspond à un nom de champ schéma (`createdAt`, `user.birthDate`) y compris en profondeur (`$set.createdAt`). */
284
+ /**
285
+ * Correspond à un nom de champ schéma (`createdAt`, `user.birthDate`)
286
+ * y compris en profondeur (`$set.createdAt`) ou derrière un opérateur
287
+ * MongoDB (`createdAt.$lte`, `$match.createdAt.$gte`).
288
+ */
273
289
  function pathMatchesFieldKey(path: string, key: string): boolean {
274
- return path === key || path.endsWith('.' + key);
290
+ // Strip trailing MongoDB operator segments: createdAt.$lte → createdAt
291
+ const cleanPath = path.replace(/\.\$[a-z]+$/i, '');
292
+ return cleanPath === key || cleanPath.endsWith('.' + key);
275
293
  }
276
294
 
277
295
  function shouldFormatDateAtPath(path: string, options?: FormatToDateOptions): boolean {
278
296
  // Aucune option → convertir partout où `isDate` est vrai
279
297
  if (!options) return true;
298
+
299
+ // Contexte d'opérateur MongoDB ($lte, $gte, $eq, $in, $nin, $exists, etc.)
300
+ // → toujours convertir : un opérateur sur une date n'a de sens qu'avec un vrai Date
301
+ // On vérifie si un segment du chemin commence par '$' (ex: createdAt.$lte, $in.0)
302
+ if (path.split('.').some((seg) => /^\$[a-z]+$/i.test(seg))) return true;
303
+
280
304
  if (options.omitKeys?.some((k) => pathMatchesFieldKey(path, k))) return false;
281
305
  // Whitelist seulement si includeKeys est un tableau non vide
282
306
  if (options.includeKeys && options.includeKeys.length > 0) {
@@ -290,6 +314,11 @@ function formatToDate(data: any, options?: FormatToDateOptions, path = ''): any
290
314
  if (data instanceof Date) return data;
291
315
  if (data instanceof ObjectId) return data;
292
316
 
317
+ // String racine (ex: item de tableau $in) — vérifier avant la branche objet
318
+ if (typeof data === 'string' && isDate(data)) {
319
+ return shouldFormatDateAtPath(path, options) ? stringToDate(data) : data;
320
+ }
321
+
293
322
  if (Array.isArray(data)) {
294
323
  return data.map((item, i) => {
295
324
  const p = joinFormatToDatePath(path, i);
@@ -563,13 +592,11 @@ function collectDateFieldPathsFromCollection(col: Collection): string[] {
563
592
  return [...paths];
564
593
  }
565
594
 
566
- function toBson<T>(data: any | any[], options?: FormatInputOptions): T {
595
+ function toBson<T>(data: any | any[], _options?: FormatInputOptions): T {
567
596
  let data_ = formatToObjectId(data);
568
- const dateOpts: FormatToDateOptions | undefined =
569
- options?.col !== undefined
570
- ? { includeKeys: collectDateFieldPathsFromCollection(options.col) }
571
- : undefined;
572
- data_ = formatToDate(data_, dateOpts);
597
+ // Toujours convertir toutes les strings date valide en Date,
598
+ // quel que soit le contexte (insert, update, match, pipeline).
599
+ data_ = formatToDate(data_);
573
600
 
574
601
  if (Array.isArray(data)) {
575
602
  data.length = 0;