@amritk/generate-validators 0.3.1 → 0.4.0

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.
@@ -27,4 +27,4 @@ export type GeneratedFile = {
27
27
  * // files → [{ filename: 'document.ts', content: '...' }, { filename: 'info.ts', ... }, ...]
28
28
  * ```
29
29
  */
30
- export declare const buildValidatorSchema: (rootSchema: JSONSchema, rootTypeName: string) => Promise<GeneratedFile[]>;
30
+ export declare const buildValidatorSchema: (rootSchema: JSONSchema, rootTypeName: string, typeSuffix?: string) => Promise<GeneratedFile[]>;
@@ -13,6 +13,11 @@ type CollectValidatorImportsOptions = {
13
13
  * are excluded from the import list (they were never generated as files).
14
14
  */
15
15
  readonly rootSchema?: Record<string, unknown> | undefined;
16
+ /**
17
+ * Suffix appended to every type/validator name derived from a `$ref`. Must
18
+ * match the suffix used when generating the referenced files. Defaults to `''`.
19
+ */
20
+ readonly typeSuffix?: string;
16
21
  };
17
22
  /**
18
23
  * Collects import statements for all $ref dependencies of a schema.
@@ -22,7 +27,7 @@ type CollectValidatorImportsOptions = {
22
27
  * ```typescript
23
28
  * const schema = { properties: { contact: { $ref: '#/$defs/contact' } } }
24
29
  * collectValidatorImports(schema)
25
- * // ["import { type ContactObject, validateContactObject } from './contact-object'"]
30
+ * // ["import { type Contact, validateContact } from './contact'"]
26
31
  * ```
27
32
  */
28
33
  export declare const collectValidatorImports: (schema: JSONSchema, options?: CollectValidatorImportsOptions) => string[];
@@ -12,6 +12,11 @@ type GenerateValidatorFileOptions = {
12
12
  * The root schema document. Used to filter out unresolvable refs.
13
13
  */
14
14
  readonly rootSchema?: Record<string, unknown>;
15
+ /**
16
+ * Suffix appended to every type/validator name derived from a `$ref`.
17
+ * Defaults to `''` (no suffix).
18
+ */
19
+ readonly typeSuffix?: string;
15
20
  };
16
21
  /**
17
22
  * Generates a complete TypeScript validator file from a JSON Schema.
@@ -21,4 +21,4 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
21
21
  * // }
22
22
  * ```
23
23
  */
24
- export declare const generateValidatorFunction: (schema: JSONSchema, typeName: string) => string;
24
+ export declare const generateValidatorFunction: (schema: JSONSchema, typeName: string, suffix?: string) => string;
package/dist/index.js CHANGED
@@ -132,15 +132,15 @@ var refToFilename2 = (ref) => {
132
132
  }
133
133
  return filename;
134
134
  };
135
- var kebabToPascal = (kebab) => {
135
+ var kebabToPascal = (kebab, suffix) => {
136
136
  const words = kebab.split("-");
137
137
  let pascalCase = "";
138
138
  for (const word of words) {
139
139
  pascalCase += word.charAt(0).toUpperCase() + word.slice(1);
140
140
  }
141
- return pascalCase + "Object";
141
+ return pascalCase + suffix;
142
142
  };
143
- var refToName = (ref) => kebabToPascal(refToFilename2(ref));
143
+ var refToName = (ref, suffix = "") => kebabToPascal(refToFilename2(ref), suffix);
144
144
 
145
145
  // ../helpers/dist/resolve-dynamic-refs.js
146
146
  var resolveDynamicRefs = (schema, dynamicRefMap) => {
@@ -415,15 +415,15 @@ var refToFilename4 = (ref) => {
415
415
  }
416
416
  return filename;
417
417
  };
418
- var kebabToPascal2 = (kebab) => {
418
+ var kebabToPascal2 = (kebab, suffix) => {
419
419
  const words = kebab.split("-");
420
420
  let pascalCase = "";
421
421
  for (const word of words) {
422
422
  pascalCase += word.charAt(0).toUpperCase() + word.slice(1);
423
423
  }
424
- return pascalCase + "Object";
424
+ return pascalCase + suffix;
425
425
  };
426
- var refToName2 = (ref) => kebabToPascal2(refToFilename4(ref));
426
+ var refToName2 = (ref, suffix = "") => kebabToPascal2(refToFilename4(ref), suffix);
427
427
  var JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
428
428
  var safeKey = (key) => {
429
429
  if (JS_IDENTIFIER.test(key)) {
@@ -541,13 +541,13 @@ var getUnbrandedType = (schema, options = {}) => {
541
541
  if (!schema.$ref.startsWith("#")) {
542
542
  return "unknown";
543
543
  }
544
- return refToName2(schema.$ref);
544
+ return refToName2(schema.$ref, options.typeSuffix);
545
545
  }
546
546
  if (schema.$dynamicRef) {
547
547
  if (schema.$dynamicRef === "#meta") {
548
- return "Schema";
548
+ return `Schema${options.typeSuffix ?? ""}`;
549
549
  }
550
- return refToName2(schema.$dynamicRef);
550
+ return refToName2(schema.$dynamicRef, options.typeSuffix);
551
551
  }
552
552
  if (schema.const !== undefined) {
553
553
  return JSON.stringify(schema.const);
@@ -594,7 +594,7 @@ var getUnbrandedType = (schema, options = {}) => {
594
594
  if (conditionalResult) {
595
595
  const baseType = getTypeScriptType(conditionalResult.schema, options);
596
596
  if (conditionalResult.thenRef) {
597
- return `(${baseType}) & ${refToName2(conditionalResult.thenRef)}`;
597
+ return `(${baseType}) & ${refToName2(conditionalResult.thenRef, options.typeSuffix)}`;
598
598
  }
599
599
  return baseType;
600
600
  }
@@ -787,12 +787,12 @@ var generateTypeDefinition = (schema, typeName, options = {}) => {
787
787
  if (isSchemaObject2(schema) && Array.isArray(schema.allOf)) {
788
788
  for (const entry of schema.allOf) {
789
789
  if (isSchemaObject2(entry) && entry.$ref) {
790
- allOfIntersections.push(refToName2(entry.$ref));
790
+ allOfIntersections.push(refToName2(entry.$ref, options.typeSuffix));
791
791
  }
792
792
  }
793
793
  }
794
794
  if (isSchemaObject2(schema) && typeof schema.$ref === "string" && schema.$ref.startsWith("#")) {
795
- allOfIntersections.push(refToName2(schema.$ref));
795
+ allOfIntersections.push(refToName2(schema.$ref, options.typeSuffix));
796
796
  }
797
797
  let result = "";
798
798
  if (jsDocTitle && jsDocDescription) {
@@ -802,7 +802,7 @@ var generateTypeDefinition = (schema, typeName, options = {}) => {
802
802
  ` + properties + `
803
803
  }`;
804
804
  if (conditionalThenRef) {
805
- typeBody += " & " + refToName2(conditionalThenRef);
805
+ typeBody += " & " + refToName2(conditionalThenRef, options.typeSuffix);
806
806
  }
807
807
  for (const intersectionType of allOfIntersections) {
808
808
  typeBody += " & " + intersectionType;
@@ -876,9 +876,9 @@ var hasMultipleOf = (schema) => {
876
876
  };
877
877
 
878
878
  // src/generators/collect-validator-imports.ts
879
- var buildImport = (ref) => {
879
+ var buildImport = (ref, suffix) => {
880
880
  const filename = refToFilename(ref);
881
- const typeName = refToName(ref);
881
+ const typeName = refToName(ref, suffix);
882
882
  const validatorName = `validate${typeName}`;
883
883
  return `import { type ${typeName}, ${validatorName} } from './${filename}'`;
884
884
  };
@@ -923,6 +923,7 @@ var collectDirectRefs = (schema) => {
923
923
  var collectValidatorImports = (schema, options) => {
924
924
  const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : null;
925
925
  const rootSchema = options?.rootSchema;
926
+ const typeSuffix = options?.typeSuffix ?? "";
926
927
  const refs = collectDirectRefs(schema);
927
928
  const seen = new Set;
928
929
  const imports = [];
@@ -939,7 +940,7 @@ var collectValidatorImports = (schema, options) => {
939
940
  }
940
941
  seen.add(filename);
941
942
  const importRef = ref.endsWith("-or-reference") ? ref.replace("-or-reference", "") : ref;
942
- imports.push(buildImport(importRef));
943
+ imports.push(buildImport(importRef, typeSuffix));
943
944
  }
944
945
  return imports;
945
946
  };
@@ -993,7 +994,7 @@ var wrongTypeCondition = (accessor, type) => {
993
994
  return "";
994
995
  }
995
996
  };
996
- var generatePropertyChecks = (key, propSchema, isRequired) => {
997
+ var generatePropertyChecks = (key, propSchema, isRequired, suffix) => {
997
998
  if (!isSchemaObject3(propSchema))
998
999
  return [];
999
1000
  const raw = `obj[${JSON.stringify(key)}]`;
@@ -1001,7 +1002,7 @@ var generatePropertyChecks = (key, propSchema, isRequired) => {
1001
1002
  const lines = [];
1002
1003
  if (hasRef(propSchema)) {
1003
1004
  const ref = propSchema.$ref;
1004
- const vName = validatorName(refToName(ref));
1005
+ const vName = validatorName(refToName(ref, suffix));
1005
1006
  if (isRequired) {
1006
1007
  lines.push(` if (!(${JSON.stringify(key)} in obj)) {`);
1007
1008
  lines.push(` errors.push({ message: "must have required property '${key}'", path: _path })`);
@@ -1127,7 +1128,7 @@ var generatePropertyChecks = (key, propSchema, isRequired) => {
1127
1128
  if (t === "array" && hasItems(propSchema)) {
1128
1129
  const itemSchema = propSchema.items;
1129
1130
  if (hasRef(itemSchema)) {
1130
- const vName = validatorName(refToName(itemSchema.$ref));
1131
+ const vName = validatorName(refToName(itemSchema.$ref, suffix));
1131
1132
  lines.push(` if (Array.isArray(${raw})) {`);
1132
1133
  lines.push(` for (let _i = 0; _i < ${raw}.length; _i++) {`);
1133
1134
  lines.push(` const _ir = ${vName}(${raw}[_i], \`${path.slice(1, -1)}/${key}/\${_i}\`)`);
@@ -1151,19 +1152,19 @@ var generatePropertyChecks = (key, propSchema, isRequired) => {
1151
1152
  }
1152
1153
  return lines;
1153
1154
  };
1154
- var generateObjectValidator = (schema, typeName) => {
1155
+ var generateObjectValidator = (schema, typeName, suffix) => {
1155
1156
  const vName = validatorName(typeName);
1156
1157
  const required = new Set(hasRequired(schema) ? schema.required : []);
1157
1158
  const properties = hasProperties(schema) ? schema.properties : {};
1158
1159
  const propertyLines = [];
1159
1160
  for (const [key, propSchema] of Object.entries(properties)) {
1160
- const checks = generatePropertyChecks(key, propSchema, required.has(key));
1161
+ const checks = generatePropertyChecks(key, propSchema, required.has(key), suffix);
1161
1162
  if (checks.length > 0) {
1162
1163
  propertyLines.push(...checks);
1163
1164
  }
1164
1165
  }
1165
1166
  if (hasAdditionalProperties(schema) && isSchemaObject3(schema.additionalProperties) && hasRef(schema.additionalProperties)) {
1166
- const vRefName = validatorName(refToName(schema.additionalProperties.$ref));
1167
+ const vRefName = validatorName(refToName(schema.additionalProperties.$ref, suffix));
1167
1168
  propertyLines.push(` for (const _key of Object.keys(obj)) {`);
1168
1169
  propertyLines.push(` if (${JSON.stringify(Object.keys(properties))}.includes(_key)) continue`);
1169
1170
  propertyLines.push(` const _r = ${vRefName}(obj[_key as keyof typeof obj], \`\${_path}/\${_key}\`)`);
@@ -1188,14 +1189,14 @@ var generateObjectValidator = (schema, typeName) => {
1188
1189
  ].join(`
1189
1190
  `);
1190
1191
  };
1191
- var generateScalarValidator = (schema, typeName) => {
1192
+ var generateScalarValidator = (schema, typeName, suffix) => {
1192
1193
  const vName = validatorName(typeName);
1193
1194
  if (!isSchemaObject3(schema)) {
1194
1195
  return [`export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`, ` return true`, `}`].join(`
1195
1196
  `);
1196
1197
  }
1197
1198
  if (hasRef(schema)) {
1198
- const delegateName = validatorName(refToName(schema.$ref));
1199
+ const delegateName = validatorName(refToName(schema.$ref, suffix));
1199
1200
  return [
1200
1201
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1201
1202
  ` return ${delegateName}(input, _path)`,
@@ -1244,7 +1245,7 @@ var generateScalarValidator = (schema, typeName) => {
1244
1245
  const branches = schema.oneOf.map((branch, i) => {
1245
1246
  if (!hasRef(branch))
1246
1247
  return null;
1247
- const bName = validatorName(refToName(branch.$ref));
1248
+ const bName = validatorName(refToName(branch.$ref, suffix));
1248
1249
  return ` const _r${i} = ${bName}(input, _path)
1249
1250
  if (_r${i} === true) return true`;
1250
1251
  }).filter(Boolean).join(`
@@ -1314,21 +1315,23 @@ var generateScalarValidator = (schema, typeName) => {
1314
1315
  return [`export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`, ` return true`, `}`].join(`
1315
1316
  `);
1316
1317
  };
1317
- var generateValidatorFunction = (schema, typeName) => {
1318
+ var generateValidatorFunction = (schema, typeName, suffix = "") => {
1318
1319
  if (isObjectSchema2(schema)) {
1319
- return generateObjectValidator(schema, typeName);
1320
+ return generateObjectValidator(schema, typeName, suffix);
1320
1321
  }
1321
- return generateScalarValidator(schema, typeName);
1322
+ return generateScalarValidator(schema, typeName, suffix);
1322
1323
  };
1323
1324
 
1324
1325
  // src/generators/generate-files.ts
1325
1326
  var generateValidatorFile = (schema, typeName, options) => {
1327
+ const typeSuffix = options?.typeSuffix ?? "";
1326
1328
  const refImports = collectValidatorImports(schema, {
1327
1329
  selfRef: options?.selfRef,
1328
- rootSchema: options?.rootSchema
1330
+ rootSchema: options?.rootSchema,
1331
+ typeSuffix
1329
1332
  });
1330
- const typeDefinition = generateTypeDefinition(schema, typeName);
1331
- const validatorFunction = generateValidatorFunction(schema, typeName);
1333
+ const typeDefinition = generateTypeDefinition(schema, typeName, { typeSuffix });
1334
+ const validatorFunction = generateValidatorFunction(schema, typeName, typeSuffix);
1332
1335
  let result = `import type { ValidationResult, ValidationError } from './validation-result'
1333
1336
  `;
1334
1337
  for (const imp of refImports) {
@@ -1365,7 +1368,7 @@ export type ValidationError = {
1365
1368
  */
1366
1369
  export type ValidationResult = true | { valid: false; errors: ValidationError[] }
1367
1370
  `;
1368
- var buildValidatorSchema = async (rootSchema, rootTypeName) => {
1371
+ var buildValidatorSchema = async (rootSchema, rootTypeName, typeSuffix = "") => {
1369
1372
  rootSchema = upgradeDraft07Schema(rootSchema);
1370
1373
  const files = [];
1371
1374
  const processedRefs = new Set;
@@ -1374,7 +1377,8 @@ var buildValidatorSchema = async (rootSchema, rootTypeName) => {
1374
1377
  const dynamicRefMap = buildDynamicRefMap(rootSchema);
1375
1378
  const processedRootSchema = resolveDynamicRefs(rootSchema, dynamicRefMap);
1376
1379
  const rootContent = generateValidatorFile(processedRootSchema, rootTypeName, {
1377
- rootSchema
1380
+ rootSchema,
1381
+ typeSuffix
1378
1382
  });
1379
1383
  const rootFilename = rootTypeName.toLowerCase();
1380
1384
  if (rootFilename !== "validation-result") {
@@ -1393,12 +1397,13 @@ var buildValidatorSchema = async (rootSchema, rootTypeName) => {
1393
1397
  console.warn(`Warning: Could not resolve ref: ${ref}`);
1394
1398
  continue;
1395
1399
  }
1396
- const typeName = refToName(ref);
1400
+ const typeName = refToName(ref, typeSuffix);
1397
1401
  const filename = refToFilename(ref);
1398
1402
  const processedSchema = resolveDynamicRefs(resolvedSchema, dynamicRefMap);
1399
1403
  const content = generateValidatorFile(processedSchema, typeName, {
1400
1404
  selfRef: ref,
1401
- rootSchema
1405
+ rootSchema,
1406
+ typeSuffix
1402
1407
  });
1403
1408
  if (filename !== "validation-result" && !processedFilenames.has(filename)) {
1404
1409
  processedFilenames.add(filename);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/generate-validators",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Generate TypeScript validation functions from JSON Schemas.",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "json-schema-typed": "^8.0.1",
50
- "@amritk/helpers": "0.5.0"
50
+ "@amritk/helpers": "0.6.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@scalar/openapi-parser": "^0.26.1"
@@ -74,7 +74,7 @@ describe('build-schema', () => {
74
74
  const documentFile = files.find((f) => f.filename === 'document.ts')
75
75
 
76
76
  expect(documentFile?.content).toContain("from './info'")
77
- expect(documentFile?.content).toContain('validateInfoObject')
77
+ expect(documentFile?.content).toContain('validateInfo')
78
78
  })
79
79
 
80
80
  it('generates a valid index.ts with re-exports', async () => {
@@ -136,7 +136,7 @@ describe('build-schema', () => {
136
136
  const infoFile = files.find((f) => f.filename === 'info.ts')
137
137
 
138
138
  expect(documentFile?.content).toContain('validateDocument')
139
- expect(infoFile?.content).toContain('validateInfoObject')
139
+ expect(infoFile?.content).toContain('validateInfo')
140
140
 
141
141
  // Cross-check: @scalar/openapi-parser says a complete document is valid
142
142
  const validDoc = { openapi: '3.1.0', info: { title: 'API', version: '1.0' }, paths: {} }
@@ -55,7 +55,11 @@ export type ValidationResult = true | { valid: false; errors: ValidationError[]
55
55
  * // files → [{ filename: 'document.ts', content: '...' }, { filename: 'info.ts', ... }, ...]
56
56
  * ```
57
57
  */
58
- export const buildValidatorSchema = async (rootSchema: JSONSchema, rootTypeName: string): Promise<GeneratedFile[]> => {
58
+ export const buildValidatorSchema = async (
59
+ rootSchema: JSONSchema,
60
+ rootTypeName: string,
61
+ typeSuffix = '',
62
+ ): Promise<GeneratedFile[]> => {
59
63
  rootSchema = upgradeDraft07Schema(rootSchema as Record<string, unknown>) as JSONSchema
60
64
 
61
65
  const files: GeneratedFile[] = []
@@ -69,6 +73,7 @@ export const buildValidatorSchema = async (rootSchema: JSONSchema, rootTypeName:
69
73
  const processedRootSchema = resolveDynamicRefs(rootSchema, dynamicRefMap)
70
74
  const rootContent = generateValidatorFile(processedRootSchema, rootTypeName, {
71
75
  rootSchema: rootSchema as Record<string, unknown>,
76
+ typeSuffix,
72
77
  })
73
78
  const rootFilename = rootTypeName.toLowerCase()
74
79
 
@@ -91,12 +96,13 @@ export const buildValidatorSchema = async (rootSchema: JSONSchema, rootTypeName:
91
96
  continue
92
97
  }
93
98
 
94
- const typeName = refToName(ref)
99
+ const typeName = refToName(ref, typeSuffix)
95
100
  const filename = refToFilename(ref)
96
101
  const processedSchema = resolveDynamicRefs(resolvedSchema as JSONSchema, dynamicRefMap)
97
102
  const content = generateValidatorFile(processedSchema, typeName, {
98
103
  selfRef: ref,
99
104
  rootSchema: rootSchema as Record<string, unknown>,
105
+ typeSuffix,
100
106
  })
101
107
 
102
108
  if (filename !== 'validation-result' && !processedFilenames.has(filename)) {
@@ -18,15 +18,20 @@ type CollectValidatorImportsOptions = {
18
18
  * are excluded from the import list (they were never generated as files).
19
19
  */
20
20
  readonly rootSchema?: Record<string, unknown> | undefined
21
+ /**
22
+ * Suffix appended to every type/validator name derived from a `$ref`. Must
23
+ * match the suffix used when generating the referenced files. Defaults to `''`.
24
+ */
25
+ readonly typeSuffix?: string
21
26
  }
22
27
 
23
28
  /**
24
29
  * Generates an import statement for a single $ref, importing both the type
25
30
  * and the validator function from the ref's generated file.
26
31
  */
27
- const buildImport = (ref: string): string => {
32
+ const buildImport = (ref: string, suffix: string): string => {
28
33
  const filename = refToFilename(ref)
29
- const typeName = refToName(ref)
34
+ const typeName = refToName(ref, suffix)
30
35
  const validatorName = `validate${typeName}`
31
36
  return `import { type ${typeName}, ${validatorName} } from './${filename}'`
32
37
  }
@@ -94,12 +99,13 @@ const collectDirectRefs = (schema: JSONSchema): string[] => {
94
99
  * ```typescript
95
100
  * const schema = { properties: { contact: { $ref: '#/$defs/contact' } } }
96
101
  * collectValidatorImports(schema)
97
- * // ["import { type ContactObject, validateContactObject } from './contact-object'"]
102
+ * // ["import { type Contact, validateContact } from './contact'"]
98
103
  * ```
99
104
  */
100
105
  export const collectValidatorImports = (schema: JSONSchema, options?: CollectValidatorImportsOptions): string[] => {
101
106
  const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : null
102
107
  const rootSchema = options?.rootSchema
108
+ const typeSuffix = options?.typeSuffix ?? ''
103
109
 
104
110
  const refs = collectDirectRefs(schema)
105
111
  const seen = new Set<string>()
@@ -121,7 +127,7 @@ export const collectValidatorImports = (schema: JSONSchema, options?: CollectVal
121
127
 
122
128
  // -or-reference unions import the base type's validator
123
129
  const importRef = ref.endsWith('-or-reference') ? ref.replace('-or-reference', '') : ref
124
- imports.push(buildImport(importRef))
130
+ imports.push(buildImport(importRef, typeSuffix))
125
131
  }
126
132
 
127
133
  return imports
@@ -17,6 +17,11 @@ type GenerateValidatorFileOptions = {
17
17
  * The root schema document. Used to filter out unresolvable refs.
18
18
  */
19
19
  readonly rootSchema?: Record<string, unknown>
20
+ /**
21
+ * Suffix appended to every type/validator name derived from a `$ref`.
22
+ * Defaults to `''` (no suffix).
23
+ */
24
+ readonly typeSuffix?: string
20
25
  }
21
26
 
22
27
  /**
@@ -46,13 +51,15 @@ export const generateValidatorFile = (
46
51
  typeName: string,
47
52
  options?: GenerateValidatorFileOptions,
48
53
  ): string => {
54
+ const typeSuffix = options?.typeSuffix ?? ''
49
55
  const refImports = collectValidatorImports(schema, {
50
56
  selfRef: options?.selfRef,
51
57
  rootSchema: options?.rootSchema,
58
+ typeSuffix,
52
59
  })
53
60
 
54
- const typeDefinition = generateTypeDefinition(schema, typeName)
55
- const validatorFunction = generateValidatorFunction(schema, typeName)
61
+ const typeDefinition = generateTypeDefinition(schema, typeName, { typeSuffix })
62
+ const validatorFunction = generateValidatorFunction(schema, typeName, typeSuffix)
56
63
 
57
64
  let result = `import type { ValidationResult, ValidationError } from './validation-result'\n`
58
65
 
@@ -135,7 +135,7 @@ describe('generate-validator-function', () => {
135
135
  }
136
136
  const code = generateValidatorFunction(schema, 'Document')
137
137
 
138
- expect(code).toContain('validateInfoObject(')
138
+ expect(code).toContain('validateInfo(')
139
139
  expect(code).toContain('"info" in obj')
140
140
  })
141
141
 
@@ -61,7 +61,7 @@ const wrongTypeCondition = (accessor: string, type: string): string => {
61
61
  * Generates validation lines for a single property in an object schema.
62
62
  * Handles $ref delegation, enum checks, type checks, and string/number constraints.
63
63
  */
64
- const generatePropertyChecks = (key: string, propSchema: JSONSchema, isRequired: boolean): string[] => {
64
+ const generatePropertyChecks = (key: string, propSchema: JSONSchema, isRequired: boolean, suffix: string): string[] => {
65
65
  if (!isSchemaObject(propSchema)) return []
66
66
 
67
67
  const raw = `obj[${JSON.stringify(key)}]`
@@ -71,7 +71,7 @@ const generatePropertyChecks = (key: string, propSchema: JSONSchema, isRequired:
71
71
  // $ref — delegate to the imported validator
72
72
  if (hasRef(propSchema)) {
73
73
  const ref = propSchema.$ref
74
- const vName = validatorName(refToName(ref))
74
+ const vName = validatorName(refToName(ref, suffix))
75
75
 
76
76
  if (isRequired) {
77
77
  lines.push(` if (!(${JSON.stringify(key)} in obj)) {`)
@@ -218,7 +218,7 @@ const generatePropertyChecks = (key: string, propSchema: JSONSchema, isRequired:
218
218
  if (t === 'array' && hasItems(propSchema)) {
219
219
  const itemSchema = propSchema.items
220
220
  if (hasRef(itemSchema)) {
221
- const vName = validatorName(refToName(itemSchema.$ref))
221
+ const vName = validatorName(refToName(itemSchema.$ref, suffix))
222
222
  lines.push(` if (Array.isArray(${raw})) {`)
223
223
  lines.push(` for (let _i = 0; _i < ${raw}.length; _i++) {`)
224
224
  lines.push(` const _ir = ${vName}(${raw}[_i], \`${path.slice(1, -1)}/${key}/\${_i}\`)`)
@@ -250,7 +250,7 @@ const generatePropertyChecks = (key: string, propSchema: JSONSchema, isRequired:
250
250
  * Generates a validator function body for an object schema, checking each
251
251
  * property's presence and type and collecting all errors.
252
252
  */
253
- const generateObjectValidator = (schema: JSONSchema, typeName: string): string => {
253
+ const generateObjectValidator = (schema: JSONSchema, typeName: string, suffix: string): string => {
254
254
  const vName = validatorName(typeName)
255
255
  const required = new Set(hasRequired(schema) ? schema.required : [])
256
256
  const properties = hasProperties(schema) ? schema.properties : {}
@@ -258,7 +258,7 @@ const generateObjectValidator = (schema: JSONSchema, typeName: string): string =
258
258
  const propertyLines: string[] = []
259
259
 
260
260
  for (const [key, propSchema] of Object.entries(properties)) {
261
- const checks = generatePropertyChecks(key, propSchema as JSONSchema, required.has(key))
261
+ const checks = generatePropertyChecks(key, propSchema as JSONSchema, required.has(key), suffix)
262
262
  if (checks.length > 0) {
263
263
  propertyLines.push(...checks)
264
264
  }
@@ -270,7 +270,7 @@ const generateObjectValidator = (schema: JSONSchema, typeName: string): string =
270
270
  isSchemaObject(schema.additionalProperties) &&
271
271
  hasRef(schema.additionalProperties)
272
272
  ) {
273
- const vRefName = validatorName(refToName(schema.additionalProperties.$ref))
273
+ const vRefName = validatorName(refToName(schema.additionalProperties.$ref, suffix))
274
274
  propertyLines.push(` for (const _key of Object.keys(obj)) {`)
275
275
  propertyLines.push(` if (${JSON.stringify(Object.keys(properties))}.includes(_key)) continue`)
276
276
  propertyLines.push(` const _r = ${vRefName}(obj[_key as keyof typeof obj], \`\${_path}/\${_key}\`)`)
@@ -297,7 +297,7 @@ const generateObjectValidator = (schema: JSONSchema, typeName: string): string =
297
297
  /**
298
298
  * Generates a validator function for a non-object schema (primitive, array, enum, $ref).
299
299
  */
300
- const generateScalarValidator = (schema: JSONSchema, typeName: string): string => {
300
+ const generateScalarValidator = (schema: JSONSchema, typeName: string, suffix: string): string => {
301
301
  const vName = validatorName(typeName)
302
302
 
303
303
  if (!isSchemaObject(schema)) {
@@ -308,7 +308,7 @@ const generateScalarValidator = (schema: JSONSchema, typeName: string): string =
308
308
 
309
309
  // Top-level $ref — delegate entirely
310
310
  if (hasRef(schema)) {
311
- const delegateName = validatorName(refToName(schema.$ref))
311
+ const delegateName = validatorName(refToName(schema.$ref, suffix))
312
312
  return [
313
313
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
314
314
  ` return ${delegateName}(input, _path)`,
@@ -361,7 +361,7 @@ const generateScalarValidator = (schema: JSONSchema, typeName: string): string =
361
361
  const branches = schema.oneOf
362
362
  .map((branch, i) => {
363
363
  if (!hasRef(branch)) return null
364
- const bName = validatorName(refToName((branch as { $ref: string }).$ref))
364
+ const bName = validatorName(refToName((branch as { $ref: string }).$ref, suffix))
365
365
  return ` const _r${i} = ${bName}(input, _path)\n if (_r${i} === true) return true`
366
366
  })
367
367
  .filter(Boolean)
@@ -463,10 +463,10 @@ const generateScalarValidator = (schema: JSONSchema, typeName: string): string =
463
463
  * // }
464
464
  * ```
465
465
  */
466
- export const generateValidatorFunction = (schema: JSONSchema, typeName: string): string => {
466
+ export const generateValidatorFunction = (schema: JSONSchema, typeName: string, suffix = ''): string => {
467
467
  if (isObjectSchema(schema)) {
468
- return generateObjectValidator(schema, typeName)
468
+ return generateObjectValidator(schema, typeName, suffix)
469
469
  }
470
470
 
471
- return generateScalarValidator(schema, typeName)
471
+ return generateScalarValidator(schema, typeName, suffix)
472
472
  }