@amritk/generate-validators
Advanced tools
| import { refToFilename } from '@amritk/helpers/ref-to-filename'; | ||
| import { refToName } from '@amritk/helpers/ref-to-name'; | ||
| import { resolveRef } from '@amritk/helpers/resolve-ref'; | ||
| import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasItems, hasOneOf, hasProperties, hasRef, } from '@amritk/helpers/schema-guards'; | ||
| import { hasRef } from '@amritk/helpers/schema-guards'; | ||
| /** | ||
@@ -13,3 +13,5 @@ * Generates an import statement for a single $ref, importing both the type | ||
| const validatorName = `validate${typeName}`; | ||
| return `import { type ${typeName}, ${validatorName} } from './${filename}'`; | ||
| // `.js` extension so the emitted import resolves under Node ESM (not just Bun); | ||
| // `./x.js` → sibling `x.ts` is the standard NodeNext form. | ||
| return `import { type ${typeName}, ${validatorName} } from './${filename}.js'`; | ||
| }; | ||
@@ -25,9 +27,22 @@ /** | ||
| /** | ||
| * Walks one level of the schema and yields all direct $ref strings that should | ||
| * become imports: properties, additionalProperties, items, and union branches. | ||
| * Recursively walks a schema and yields every `$ref` the validator emitter can | ||
| * turn into a `validateX(...)` call, in traversal order. The emitter recurses | ||
| * into far more than properties/items/additionalProperties/top-level | ||
| * combinators: it also delegates for `patternProperties`, `propertyNames`, | ||
| * `if`/`then`/`else`, `contains`, `prefixItems`, `dependentSchemas`, `not`, and | ||
| * objects nested inside any combinator branch. A `$ref` reached by *any* of those | ||
| * paths must become an import, or the generated file references an undefined | ||
| * `validateX`. (Mirrors the parsers package's `collect-imports` traversal.) | ||
| */ | ||
| const collectDirectRefs = (schema) => { | ||
| if (typeof schema === 'boolean' || schema === null) | ||
| return []; | ||
| const refs = []; | ||
| const collectDirectRefs = (value, refs = []) => { | ||
| if (typeof value !== 'object' || value === null) | ||
| return refs; | ||
| if (Array.isArray(value)) { | ||
| for (const item of value) | ||
| collectDirectRefs(item, refs); | ||
| return refs; | ||
| } | ||
| const schema = value; | ||
| // A `$ref` is a leaf: the emitter delegates the whole value to the referenced | ||
| // validator, so record the ref and do not descend past it. | ||
| if (hasRef(schema)) { | ||
@@ -37,32 +52,30 @@ refs.push(schema.$ref); | ||
| } | ||
| const propSchemas = 'properties' in schema && typeof schema.properties === 'object' && schema.properties !== null | ||
| ? Object.values(schema.properties) | ||
| : []; | ||
| for (const prop of propSchemas) { | ||
| if (hasRef(prop)) | ||
| refs.push(prop.$ref); | ||
| if (hasItems(prop) && hasRef(prop.items)) | ||
| refs.push(prop.items.$ref); | ||
| if (hasAdditionalProperties(prop) && hasRef(prop.additionalProperties)) { | ||
| refs.push(prop.additionalProperties.$ref); | ||
| // Every keyword whose subschema(s) the emitter recurses into. `properties` and | ||
| // `patternProperties` hold subschemas as object *values*; the combinator/tuple | ||
| // keywords hold them in arrays; the rest are single subschemas. We deliberately | ||
| // do NOT descend into `$defs`/`definitions` — those are split into their own | ||
| // generated files, not inlined by this validator. `collectDirectRefs` | ||
| // self-guards on non-objects, so a keyword that is a boolean or missing is a | ||
| // harmless no-op. | ||
| const subSchemaMaps = ['properties', 'patternProperties']; | ||
| for (const mapKey of subSchemaMaps) { | ||
| const map = schema[mapKey]; | ||
| if (typeof map === 'object' && map !== null && !Array.isArray(map)) { | ||
| for (const sub of Object.values(map)) | ||
| collectDirectRefs(sub, refs); | ||
| } | ||
| // Inline nested objects are validated recursively by the generator, so any | ||
| // $refs anywhere inside them must become imports as well. | ||
| if (hasProperties(prop)) | ||
| refs.push(...collectDirectRefs(prop)); | ||
| } | ||
| if (hasItems(schema) && hasRef(schema.items)) { | ||
| refs.push(schema.items.$ref); | ||
| const singleSubSchemas = ['items', 'additionalProperties', 'propertyNames', 'contains', 'if', 'then', 'else', 'not']; | ||
| for (const key of singleSubSchemas) { | ||
| if (key in schema) | ||
| collectDirectRefs(schema[key], refs); | ||
| } | ||
| if (hasAdditionalProperties(schema) && hasRef(schema.additionalProperties)) { | ||
| refs.push(schema.additionalProperties.$ref); | ||
| const arraySubSchemas = ['oneOf', 'anyOf', 'allOf', 'prefixItems']; | ||
| for (const key of arraySubSchemas) { | ||
| const arr = schema[key]; | ||
| if (Array.isArray(arr)) { | ||
| for (const sub of arr) | ||
| collectDirectRefs(sub, refs); | ||
| } | ||
| } | ||
| for (const branch of [ | ||
| ...(hasOneOf(schema) ? schema.oneOf : []), | ||
| ...(hasAnyOf(schema) ? schema.anyOf : []), | ||
| ...(hasAllOf(schema) ? schema.allOf : []), | ||
| ]) { | ||
| if (hasRef(branch)) | ||
| refs.push(branch.$ref); | ||
| } | ||
| return refs; | ||
@@ -69,0 +82,0 @@ }; |
@@ -38,3 +38,4 @@ import { generateTypeDefinition } from '@amritk/helpers/generate-type-definition'; | ||
| const booleanGuard = generateBooleanGuard(schema, typeName, typeSuffix); | ||
| let result = `import type { ValidationResult, ValidationError } from './validation-result'\n`; | ||
| // `.js` extension so the relative import resolves under Node ESM, not only Bun. | ||
| let result = `import type { ValidationResult, ValidationError } from './validation-result.js'\n`; | ||
| // `const` checks on object/array values call the runtime `valuesEqual` helper. | ||
@@ -44,3 +45,3 @@ // Only import it when the generated body actually uses it, so files without a | ||
| if (validatorFunction.includes('valuesEqual(')) { | ||
| result += `import { valuesEqual } from './validation-result'\n`; | ||
| result += `import { valuesEqual } from './validation-result.js'\n`; | ||
| } | ||
@@ -47,0 +48,0 @@ for (const imp of refImports) { |
+2
-2
| { | ||
| "name": "@amritk/generate-validators", | ||
| "version": "0.11.1", | ||
| "version": "0.11.2", | ||
| "description": "Generate TypeScript validation functions from JSON Schemas.", | ||
@@ -49,3 +49,3 @@ "module": "./dist/index.js", | ||
| "json-schema-typed": "^8.0.1", | ||
| "@amritk/helpers": "0.10.1" | ||
| "@amritk/helpers": "0.10.2" | ||
| }, | ||
@@ -52,0 +52,0 @@ "devDependencies": { |
Sorry, the diff of this file is too big to display
101026
7.23%1929
6.63%+ Added
- Removed
Updated