@amritk/generate-validators 0.4.1 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/generate-validators",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Generate TypeScript validation functions from JSON Schemas.",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -31,23 +31,23 @@
31
31
  "access": "public"
32
32
  },
33
33
  "scripts": {
34
- "build": "bun run build:code && bun run build:types",
35
- "build:code": "bun build ./src/index.ts --outdir=dist --target=node",
36
- "build:types": "tsc -p ."
34
+ "build": "tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f",
35
+ "types:check": "tsgo -p . --noEmit",
36
+ "test": "NODE_ENV=production vitest run --root ../.. generate-validators"
37
37
  },
38
38
  "imports": {
39
39
  "#generators/*": "./src/generators/*.ts"
40
40
  },
41
41
  "exports": {
42
42
  ".": {
43
- "bun": "./src/index.ts",
43
+ "development": "./src/index.ts",
44
44
  "default": "./dist/index.js",
45
45
  "types": "./dist/index.d.ts"
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
49
  "json-schema-typed": "^8.0.1",
50
- "@amritk/helpers": "0.6.1"
50
+ "@amritk/helpers": "0.7.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@scalar/openapi-parser": "^0.26.1"
@@ -1,10 +1,5 @@
1
- import { buildDynamicRefMap } from '@amritk/helpers/build-dynamic-ref-map'
2
- import { extractRefs } from '@amritk/helpers/extract-refs'
3
- import { refToFilename } from '@amritk/helpers/ref-to-filename'
4
- import { refToName } from '@amritk/helpers/ref-to-name'
5
- import { resolveDynamicRefs } from '@amritk/helpers/resolve-dynamic-refs'
6
- import { resolveRef } from '@amritk/helpers/resolve-ref'
7
- import { upgradeDraft07Schema } from '@amritk/helpers/upgrade-draft07-schema'
1
+ import { generateIndexBarrel } from '@amritk/helpers/generate-index-barrel'
2
+ import { walkRefGraph } from '@amritk/helpers/walk-ref-graph'
8
3
  import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
9
4
 
10
5
  import { generateValidatorFile } from './generate-files'
@@ -35,8 +30,9 @@ export type ValidationResult = true | { valid: false; errors: ValidationError[]
35
30
  `
36
31
 
37
32
  /**
38
- * Builds all TypeScript validator files from a JSON Schema by traversing
39
- * all $ref references recursively, mirroring the generate-parsers pipeline.
33
+ * Builds all TypeScript validator files from a JSON Schema by traversing all
34
+ * `$ref` / `$dynamicRef` references recursively (via the shared
35
+ * `@amritk/helpers/walk-ref-graph` walker).
40
36
  *
41
37
  * Each generated file exports:
42
38
  * - A TypeScript type definition
@@ -60,87 +56,26 @@ export const buildValidatorSchema = async (
60
56
  rootTypeName: string,
61
57
  typeSuffix = '',
62
58
  ): Promise<GeneratedFile[]> => {
63
- rootSchema = upgradeDraft07Schema(rootSchema as Record<string, unknown>) as JSONSchema
64
-
65
59
  const files: GeneratedFile[] = []
66
- const processedRefs = new Set<string>()
67
- const processedFilenames = new Set<string>()
68
- const refsToProcess: string[] = []
69
-
70
- const dynamicRefMap = buildDynamicRefMap(rootSchema)
71
-
72
- // Root schema
73
- const processedRootSchema = resolveDynamicRefs(rootSchema, dynamicRefMap)
74
- const rootContent = generateValidatorFile(processedRootSchema, rootTypeName, {
75
- rootSchema: rootSchema as Record<string, unknown>,
76
- typeSuffix,
77
- })
78
- const rootFilename = rootTypeName.toLowerCase()
79
60
 
80
- if (rootFilename !== 'validation-result') {
81
- processedFilenames.add(rootFilename)
82
- files.push({ filename: `${rootFilename}.ts`, content: rootContent })
83
- }
61
+ walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
62
+ // `validation-result` and `index` are reserved output filenames, so never
63
+ // let a definition of either name overwrite them.
64
+ if (node.filename === 'validation-result' || node.filename === 'index') return
84
65
 
85
- const rootRefs = extractRefs(rootSchema)
86
- refsToProcess.push(...rootRefs)
87
-
88
- while (refsToProcess.length > 0) {
89
- const ref = refsToProcess.shift()
90
- if (!ref || processedRefs.has(ref)) continue
91
- processedRefs.add(ref)
92
-
93
- const resolvedSchema = resolveRef(ref, rootSchema as Record<string, unknown>)
94
- if (!resolvedSchema) {
95
- console.warn(`Warning: Could not resolve ref: ${ref}`)
96
- continue
97
- }
98
-
99
- const typeName = refToName(ref, typeSuffix)
100
- const filename = refToFilename(ref)
101
- const processedSchema = resolveDynamicRefs(resolvedSchema as JSONSchema, dynamicRefMap)
102
- const content = generateValidatorFile(processedSchema, typeName, {
103
- selfRef: ref,
104
- rootSchema: rootSchema as Record<string, unknown>,
66
+ const content = generateValidatorFile(node.schema, node.typeName, {
67
+ rootSchema: node.rootSchema,
105
68
  typeSuffix,
69
+ ...(node.ref !== undefined ? { selfRef: node.ref } : {}),
106
70
  })
107
-
108
- if (filename !== 'validation-result' && !processedFilenames.has(filename)) {
109
- processedFilenames.add(filename)
110
- files.push({ filename: `${filename}.ts`, content })
111
- }
112
-
113
- for (const nestedRef of extractRefs(resolvedSchema as JSONSchema)) {
114
- if (!processedRefs.has(nestedRef)) refsToProcess.push(nestedRef)
115
- }
116
- }
71
+ files.push({ filename: `${node.filename}.ts`, content })
72
+ })
117
73
 
118
74
  // Emit the runtime contract for validators. ValidationResult is mjst-defined
119
75
  // (not derived from the input schema), so its content is fixed.
120
76
  files.push({ filename: 'validation-result.ts', content: VALIDATION_RESULT_CONTENT })
121
77
 
122
- // Generate index.ts
123
- const TYPE_EXPORT_RE = /^export type (\w+)/gm
124
- const CONST_EXPORT_RE = /^export const (\w+)/gm
125
-
126
- const sortedFiles = [...files].sort((a, b) => a.filename.localeCompare(b.filename))
127
- let indexContent = ''
128
-
129
- for (const file of sortedFiles) {
130
- const moduleName = file.filename.replace(/\.ts$/, '')
131
- const typeNames: string[] = []
132
- const constNames: string[] = []
133
-
134
- for (const match of file.content.matchAll(TYPE_EXPORT_RE)) typeNames.push(match[1] as string)
135
- for (const match of file.content.matchAll(CONST_EXPORT_RE)) constNames.push(match[1] as string)
136
-
137
- if (typeNames.length === 0 && constNames.length === 0) continue
138
-
139
- const typeExports = typeNames.map((n) => `type ${n}`)
140
- indexContent += `export { ${[...typeExports, ...constNames].join(', ')} } from './${moduleName}';\n`
141
- }
142
-
143
- files.push({ filename: 'index.ts', content: indexContent })
78
+ files.push({ filename: 'index.ts', content: generateIndexBarrel(files) })
144
79
 
145
80
  return files
146
81
  }