@naturalcycles/nodejs-lib 15.107.1 → 15.107.2

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.
@@ -1178,6 +1178,9 @@ function executeValidation(fn, builtSchema, input, opt = {}, defaultInputName) {
1178
1178
  const errors = fn.errors;
1179
1179
  const { inputId = _isObject(input) ? input['id'] : undefined, inputName = defaultInputName || 'Object', } = opt;
1180
1180
  const dataVar = [inputName, inputId].filter(Boolean).join('.');
1181
+ // Build fingerprint before applyImprovementsOnErrorMessages: after it, /items/0/name becomes
1182
+ // .items[0].name, embedding the index into the segment and making it harder to strip without regex
1183
+ const fingerprint = buildAjvErrorFingerprint(errors[0], inputName);
1181
1184
  applyImprovementsOnErrorMessages(errors, builtSchema);
1182
1185
  let message = getAjv().errorsText(errors, {
1183
1186
  dataVar,
@@ -1187,9 +1190,6 @@ function executeValidation(fn, builtSchema, input, opt = {}, defaultInputName) {
1187
1190
  // the error message Input would contain already mutated object print, such as Input: {}
1188
1191
  // Unless `getOriginalInput` function is provided - then it will be used to preserve the Input pureness.
1189
1192
  const inputStringified = _inspect(opt.getOriginalInput?.() || input, { maxLen: 4000 });
1190
- // fingerprint is captured before appending the dynamic Input snippet,
1191
- // so we can group repeated validation errors by rule rather than by unique request content.
1192
- const fingerprint = message;
1193
1193
  message = [message, 'Input: ' + inputStringified].join(separator);
1194
1194
  const err = new AjvValidationError(message, _filterNullishValues({
1195
1195
  errors,
@@ -1222,6 +1222,22 @@ function applyImprovementsOnErrorMessages(errors, schema) {
1222
1222
  error.instancePath = error.instancePath.replaceAll(/\/(\d+)/g, `[$1]`).replaceAll('/', '.');
1223
1223
  }
1224
1224
  }
1225
+ /**
1226
+ * Groups repeated validation errors by rule rather than by unique request content.
1227
+ * Excludes instance-specific data like record IDs and array indices.
1228
+ */
1229
+ function buildAjvErrorFingerprint(e, inputName) {
1230
+ const value = Object.values(e.params || {})[0];
1231
+ let rule = e.keyword;
1232
+ if (value !== undefined)
1233
+ rule += `:${value}`;
1234
+ const path = e.instancePath
1235
+ .split('/')
1236
+ .filter(s => s && isNaN(Number(s)))
1237
+ .join('.');
1238
+ const location = [inputName, path].filter(Boolean).join('.');
1239
+ return [location, rule].join(' ');
1240
+ }
1225
1241
  /**
1226
1242
  * Filters out noisy errors produced by nullable anyOf patterns.
1227
1243
  * When `nullable()` wraps a schema in `anyOf: [realSchema, { type: 'null' }]`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.107.1",
4
+ "version": "15.107.2",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@standard-schema/spec": "^1",
@@ -17,7 +17,7 @@
17
17
  "yargs": "^18"
18
18
  },
19
19
  "devDependencies": {
20
- "@typescript/native-preview": "7.0.0-dev.20260401.1",
20
+ "@typescript/native-preview": "7.0.0-dev.20260415.1",
21
21
  "@naturalcycles/dev-lib": "18.4.2"
22
22
  },
23
23
  "exports": {
@@ -1692,6 +1692,10 @@ function executeValidation<OUT>(
1692
1692
  } = opt
1693
1693
  const dataVar = [inputName, inputId].filter(Boolean).join('.')
1694
1694
 
1695
+ // Build fingerprint before applyImprovementsOnErrorMessages: after it, /items/0/name becomes
1696
+ // .items[0].name, embedding the index into the segment and making it harder to strip without regex
1697
+ const fingerprint = buildAjvErrorFingerprint(errors[0], inputName)
1698
+
1695
1699
  applyImprovementsOnErrorMessages(errors, builtSchema)
1696
1700
 
1697
1701
  let message = getAjv().errorsText(errors, {
@@ -1703,9 +1707,6 @@ function executeValidation<OUT>(
1703
1707
  // the error message Input would contain already mutated object print, such as Input: {}
1704
1708
  // Unless `getOriginalInput` function is provided - then it will be used to preserve the Input pureness.
1705
1709
  const inputStringified = _inspect(opt.getOriginalInput?.() || input, { maxLen: 4000 })
1706
- // fingerprint is captured before appending the dynamic Input snippet,
1707
- // so we can group repeated validation errors by rule rather than by unique request content.
1708
- const fingerprint = message
1709
1710
  message = [message, 'Input: ' + inputStringified].join(separator)
1710
1711
 
1711
1712
  const err = new AjvValidationError(
@@ -1723,7 +1724,7 @@ function executeValidation<OUT>(
1723
1724
  // ==== Error formatting helpers ====
1724
1725
 
1725
1726
  function applyImprovementsOnErrorMessages(
1726
- errors: ErrorObject<string, Record<string, any>, unknown>[] | null | undefined,
1727
+ errors: ErrorObject[] | null | undefined,
1727
1728
  schema: JsonSchema,
1728
1729
  ): void {
1729
1730
  if (!errors) return
@@ -1750,16 +1751,29 @@ function applyImprovementsOnErrorMessages(
1750
1751
  }
1751
1752
  }
1752
1753
 
1754
+ /**
1755
+ * Groups repeated validation errors by rule rather than by unique request content.
1756
+ * Excludes instance-specific data like record IDs and array indices.
1757
+ */
1758
+ function buildAjvErrorFingerprint(e: ErrorObject, inputName: string): string {
1759
+ const value = Object.values(e.params || {})[0]
1760
+ let rule = e.keyword
1761
+ if (value !== undefined) rule += `:${value}`
1762
+ const path = e.instancePath
1763
+ .split('/')
1764
+ .filter(s => s && isNaN(Number(s)))
1765
+ .join('.')
1766
+ const location = [inputName, path].filter(Boolean).join('.')
1767
+ return [location, rule].join(' ')
1768
+ }
1769
+
1753
1770
  /**
1754
1771
  * Filters out noisy errors produced by nullable anyOf patterns.
1755
1772
  * When `nullable()` wraps a schema in `anyOf: [realSchema, { type: 'null' }]`,
1756
1773
  * AJV produces "must be null" and "must match a schema in anyOf" errors
1757
1774
  * that are confusing. This method splices them out, keeping only the real errors.
1758
1775
  */
1759
- function filterNullableAnyOfErrors(
1760
- errors: ErrorObject<string, Record<string, any>, unknown>[],
1761
- schema: JsonSchema,
1762
- ): void {
1776
+ function filterNullableAnyOfErrors(errors: ErrorObject[], schema: JsonSchema): void {
1763
1777
  // Collect exact schemaPaths to remove (anyOf aggregates) and prefixes (null branches)
1764
1778
  const exactPaths: string[] = []
1765
1779
  const nullBranchPrefixes: string[] = []