@naturalcycles/nodejs-lib 15.122.1 → 15.122.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.
@@ -543,10 +543,15 @@ export class JString extends JBuilder {
543
543
  }
544
544
  pattern(pattern, opt) {
545
545
  const clone = this.cloneAndUpdateSchema({ pattern });
546
- if (opt?.name)
547
- clone.setErrorMessage('pattern', `is not a valid ${opt.name}`);
548
- if (opt?.msg)
546
+ if (opt?.msg) {
549
547
  clone.setErrorMessage('pattern', opt.msg);
548
+ }
549
+ else if (opt?.name) {
550
+ clone.setErrorMessage('pattern', `is not a valid ${opt.name}`);
551
+ }
552
+ else {
553
+ clone.setErrorMessage('pattern', 'is not matching the expected pattern');
554
+ }
550
555
  return clone;
551
556
  }
552
557
  minLength(minLength) {
@@ -1268,18 +1273,14 @@ function applyImprovementsOnErrorMessages(errors, schema) {
1268
1273
  }
1269
1274
  /**
1270
1275
  * Looks up the user-provided custom error message (set via `{ msg }` / `{ name }`)
1271
- * for a given AJV error, walking the schema along the error's instancePath and
1272
- * falling back to top-level `errorMessages` (including through nullable wrappers).
1276
+ * for a given AJV error, resolving the error's schemaPath to the exact subschema that
1277
+ * failed - which, unlike the instancePath, also addresses anyOf/oneOf branches.
1273
1278
  * Returns undefined if no custom message was registered for the failing keyword.
1274
1279
  */
1275
1280
  function resolveCustomErrorMessage(schema, error) {
1276
- const byPath = getErrorMessageForInstancePath(schema, error.instancePath, error.keyword);
1277
- if (byPath)
1278
- return byPath;
1279
- if (schema.errorMessages?.[error.keyword])
1280
- return schema.errorMessages[error.keyword];
1281
- const unwrapped = unwrapNullableAnyOf(schema);
1282
- return unwrapped?.errorMessages?.[error.keyword];
1281
+ if (!error.schemaPath)
1282
+ return undefined;
1283
+ return resolveSchemaPath(schema, error.schemaPath)?.errorMessages?.[error.keyword];
1283
1284
  }
1284
1285
  /**
1285
1286
  * Groups repeated validation errors by rule rather than by unique request content.
@@ -1351,59 +1352,6 @@ function resolveSchemaPath(schema, schemaPath) {
1351
1352
  }
1352
1353
  return current;
1353
1354
  }
1354
- function getErrorMessageForInstancePath(schema, instancePath, keyword) {
1355
- if (!schema || !instancePath)
1356
- return undefined;
1357
- const segments = instancePath.split('/').filter(Boolean);
1358
- return traverseSchemaPath(schema, segments, keyword);
1359
- }
1360
- function traverseSchemaPath(schema, segments, keyword) {
1361
- if (!segments.length)
1362
- return undefined;
1363
- const [currentSegment, ...remainingSegments] = segments;
1364
- const nextSchema = getChildSchema(schema, currentSegment);
1365
- if (!nextSchema)
1366
- return undefined;
1367
- if (nextSchema.errorMessages?.[keyword]) {
1368
- return nextSchema.errorMessages[keyword];
1369
- }
1370
- // Check through nullable wrapper
1371
- const unwrapped = unwrapNullableAnyOf(nextSchema);
1372
- if (unwrapped?.errorMessages?.[keyword]) {
1373
- return unwrapped.errorMessages[keyword];
1374
- }
1375
- if (remainingSegments.length) {
1376
- return traverseSchemaPath(nextSchema, remainingSegments, keyword);
1377
- }
1378
- return undefined;
1379
- }
1380
- function getChildSchema(schema, segment) {
1381
- if (!segment)
1382
- return undefined;
1383
- // Unwrap nullable anyOf to find properties/items through nullable wrappers
1384
- const effectiveSchema = unwrapNullableAnyOf(schema) ?? schema;
1385
- if (/^\d+$/.test(segment) && effectiveSchema.items) {
1386
- return getArrayItemSchema(effectiveSchema, segment);
1387
- }
1388
- return getObjectPropertySchema(effectiveSchema, segment);
1389
- }
1390
- function getArrayItemSchema(schema, indexSegment) {
1391
- if (!schema.items)
1392
- return undefined;
1393
- if (Array.isArray(schema.items)) {
1394
- return schema.items[Number(indexSegment)];
1395
- }
1396
- return schema.items;
1397
- }
1398
- function getObjectPropertySchema(schema, segment) {
1399
- return schema.properties?.[segment];
1400
- }
1401
- function unwrapNullableAnyOf(schema) {
1402
- const nullIndex = unwrapNullableAnyOfIndex(schema);
1403
- if (nullIndex === -1)
1404
- return undefined;
1405
- return schema.anyOf[1 - nullIndex];
1406
- }
1407
1355
  function unwrapNullableAnyOfIndex(schema) {
1408
1356
  if (schema.anyOf?.length !== 2)
1409
1357
  return -1;
@@ -60,8 +60,13 @@ const MAX_DOS_DATE = new Date(2107, 11, 31, 23, 59, 58);
60
60
  * timestamp is rendered in the local timezone. Out-of-range dates are clamped to 1980-2107.
61
61
  */
62
62
  export function unixToDosDateTime(ts) {
63
- const jsDate = new Date(ts * 1000);
64
- const d = jsDate < MIN_DOS_DATE ? MIN_DOS_DATE : jsDate > MAX_DOS_DATE ? MAX_DOS_DATE : jsDate;
63
+ let d = new Date(ts * 1000);
64
+ if (d < MIN_DOS_DATE) {
65
+ d = MIN_DOS_DATE;
66
+ }
67
+ else if (d > MAX_DOS_DATE) {
68
+ d = MAX_DOS_DATE;
69
+ }
65
70
  const date = (d.getDate() & 0x1f) | // 1-31
66
71
  (((d.getMonth() + 1) & 0x0f) << 5) | // 1-12
67
72
  (((d.getFullYear() - 1980) & 0x7f) << 9); // 1980-2107
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.122.1",
4
+ "version": "15.122.3",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@standard-schema/spec": "^1",
@@ -734,8 +734,14 @@ export class JString<
734
734
 
735
735
  pattern(pattern: string, opt?: JsonBuilderRuleOpt): this {
736
736
  const clone = this.cloneAndUpdateSchema({ pattern })
737
- if (opt?.name) clone.setErrorMessage('pattern', `is not a valid ${opt.name}`)
738
- if (opt?.msg) clone.setErrorMessage('pattern', opt.msg)
737
+ if (opt?.msg) {
738
+ clone.setErrorMessage('pattern', opt.msg)
739
+ } else if (opt?.name) {
740
+ clone.setErrorMessage('pattern', `is not a valid ${opt.name}`)
741
+ } else {
742
+ clone.setErrorMessage('pattern', 'is not matching the expected pattern')
743
+ }
744
+
739
745
  return clone
740
746
  }
741
747
 
@@ -1816,16 +1822,13 @@ function applyImprovementsOnErrorMessages(
1816
1822
 
1817
1823
  /**
1818
1824
  * Looks up the user-provided custom error message (set via `{ msg }` / `{ name }`)
1819
- * for a given AJV error, walking the schema along the error's instancePath and
1820
- * falling back to top-level `errorMessages` (including through nullable wrappers).
1825
+ * for a given AJV error, resolving the error's schemaPath to the exact subschema that
1826
+ * failed - which, unlike the instancePath, also addresses anyOf/oneOf branches.
1821
1827
  * Returns undefined if no custom message was registered for the failing keyword.
1822
1828
  */
1823
1829
  function resolveCustomErrorMessage(schema: JsonSchema, error: ErrorObject): string | undefined {
1824
- const byPath = getErrorMessageForInstancePath(schema, error.instancePath, error.keyword)
1825
- if (byPath) return byPath
1826
- if (schema.errorMessages?.[error.keyword]) return schema.errorMessages[error.keyword]
1827
- const unwrapped = unwrapNullableAnyOf(schema)
1828
- return unwrapped?.errorMessages?.[error.keyword]
1830
+ if (!error.schemaPath) return undefined
1831
+ return resolveSchemaPath(schema, error.schemaPath)?.errorMessages?.[error.keyword]
1829
1832
  }
1830
1833
 
1831
1834
  /**
@@ -1906,79 +1909,6 @@ function resolveSchemaPath(schema: JsonSchema, schemaPath: string): JsonSchema |
1906
1909
  return current as JsonSchema | undefined
1907
1910
  }
1908
1911
 
1909
- function getErrorMessageForInstancePath(
1910
- schema: JsonSchema | undefined,
1911
- instancePath: string,
1912
- keyword: string,
1913
- ): string | undefined {
1914
- if (!schema || !instancePath) return undefined
1915
-
1916
- const segments = instancePath.split('/').filter(Boolean)
1917
- return traverseSchemaPath(schema, segments, keyword)
1918
- }
1919
-
1920
- function traverseSchemaPath(
1921
- schema: JsonSchema,
1922
- segments: string[],
1923
- keyword: string,
1924
- ): string | undefined {
1925
- if (!segments.length) return undefined
1926
-
1927
- const [currentSegment, ...remainingSegments] = segments
1928
-
1929
- const nextSchema = getChildSchema(schema, currentSegment)
1930
- if (!nextSchema) return undefined
1931
-
1932
- if (nextSchema.errorMessages?.[keyword]) {
1933
- return nextSchema.errorMessages[keyword]
1934
- }
1935
-
1936
- // Check through nullable wrapper
1937
- const unwrapped = unwrapNullableAnyOf(nextSchema)
1938
- if (unwrapped?.errorMessages?.[keyword]) {
1939
- return unwrapped.errorMessages[keyword]
1940
- }
1941
-
1942
- if (remainingSegments.length) {
1943
- return traverseSchemaPath(nextSchema, remainingSegments, keyword)
1944
- }
1945
-
1946
- return undefined
1947
- }
1948
-
1949
- function getChildSchema(schema: JsonSchema, segment: string | undefined): JsonSchema | undefined {
1950
- if (!segment) return undefined
1951
-
1952
- // Unwrap nullable anyOf to find properties/items through nullable wrappers
1953
- const effectiveSchema = unwrapNullableAnyOf(schema) ?? schema
1954
-
1955
- if (/^\d+$/.test(segment) && effectiveSchema.items) {
1956
- return getArrayItemSchema(effectiveSchema, segment)
1957
- }
1958
-
1959
- return getObjectPropertySchema(effectiveSchema, segment)
1960
- }
1961
-
1962
- function getArrayItemSchema(schema: JsonSchema, indexSegment: string): JsonSchema | undefined {
1963
- if (!schema.items) return undefined
1964
-
1965
- if (Array.isArray(schema.items)) {
1966
- return schema.items[Number(indexSegment)]
1967
- }
1968
-
1969
- return schema.items
1970
- }
1971
-
1972
- function getObjectPropertySchema(schema: JsonSchema, segment: string): JsonSchema | undefined {
1973
- return schema.properties?.[segment as keyof typeof schema.properties]
1974
- }
1975
-
1976
- function unwrapNullableAnyOf(schema: JsonSchema): JsonSchema | undefined {
1977
- const nullIndex = unwrapNullableAnyOfIndex(schema)
1978
- if (nullIndex === -1) return undefined
1979
- return schema.anyOf![1 - nullIndex]!
1980
- }
1981
-
1982
1912
  function unwrapNullableAnyOfIndex(schema: JsonSchema): number {
1983
1913
  if (schema.anyOf?.length !== 2) return -1
1984
1914
  const nullIndex = schema.anyOf.findIndex(s => s.type === 'null')
@@ -76,8 +76,12 @@ const MAX_DOS_DATE = new Date(2107, 11, 31, 23, 59, 58)
76
76
  * timestamp is rendered in the local timezone. Out-of-range dates are clamped to 1980-2107.
77
77
  */
78
78
  export function unixToDosDateTime(ts: UnixTimestamp): { date: number; time: number } {
79
- const jsDate = new Date(ts * 1000)
80
- const d = jsDate < MIN_DOS_DATE ? MIN_DOS_DATE : jsDate > MAX_DOS_DATE ? MAX_DOS_DATE : jsDate
79
+ let d = new Date(ts * 1000)
80
+ if (d < MIN_DOS_DATE) {
81
+ d = MIN_DOS_DATE
82
+ } else if (d > MAX_DOS_DATE) {
83
+ d = MAX_DOS_DATE
84
+ }
81
85
 
82
86
  const date =
83
87
  (d.getDate() & 0x1f) | // 1-31