@amritk/generate-parsers 0.6.3 → 0.7.1
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/dist/generators/build-schema.d.ts +9 -10
- package/dist/generators/build-schema.d.ts.map +1 -0
- package/dist/generators/build-schema.js +118 -0
- package/dist/generators/generate-default-from-pattern.d.ts +1 -0
- package/dist/generators/generate-default-from-pattern.d.ts.map +1 -0
- package/dist/generators/generate-default-from-pattern.js +33 -0
- package/dist/generators/generate-discriminated-union-validation.d.ts +1 -0
- package/dist/generators/generate-discriminated-union-validation.d.ts.map +1 -0
- package/dist/generators/generate-discriminated-union-validation.js +38 -0
- package/dist/generators/generate-enum-check.d.ts +1 -0
- package/dist/generators/generate-enum-check.d.ts.map +1 -0
- package/dist/generators/generate-enum-check.js +8 -0
- package/dist/generators/generate-files.d.ts +2 -1
- package/dist/generators/generate-files.d.ts.map +1 -0
- package/dist/generators/generate-files.js +74 -0
- package/dist/generators/generate-non-discriminated-union-validation.d.ts +1 -0
- package/dist/generators/generate-non-discriminated-union-validation.d.ts.map +1 -0
- package/dist/generators/generate-non-discriminated-union-validation.js +28 -0
- package/dist/generators/generate-parser-function.d.ts +1 -0
- package/dist/generators/generate-parser-function.d.ts.map +1 -0
- package/dist/generators/generate-parser-function.js +1070 -0
- package/dist/generators/generate-schema-checks.d.ts +1 -0
- package/dist/generators/generate-schema-checks.d.ts.map +1 -0
- package/dist/generators/generate-schema-checks.js +99 -0
- package/dist/generators/generate-strict-assertion.d.ts +1 -0
- package/dist/generators/generate-strict-assertion.d.ts.map +1 -0
- package/dist/generators/generate-strict-assertion.js +179 -0
- package/dist/generators/generate-validation-expression.d.ts +1 -0
- package/dist/generators/generate-validation-expression.d.ts.map +1 -0
- package/dist/generators/generate-validation-expression.js +267 -0
- package/dist/helpers/apply-schema-extensions.d.ts +2 -1
- package/dist/helpers/apply-schema-extensions.d.ts.map +1 -0
- package/dist/helpers/apply-schema-extensions.js +51 -0
- package/dist/helpers/collect-helpers.d.ts +1 -0
- package/dist/helpers/collect-helpers.d.ts.map +1 -0
- package/dist/helpers/collect-helpers.js +48 -0
- package/dist/helpers/collect-imports.d.ts +1 -0
- package/dist/helpers/collect-imports.d.ts.map +1 -0
- package/dist/helpers/collect-imports.js +216 -0
- package/dist/helpers/find-discriminator.d.ts +1 -0
- package/dist/helpers/find-discriminator.d.ts.map +1 -0
- package/dist/helpers/find-discriminator.js +33 -0
- package/dist/helpers/get-default-value.d.ts +1 -0
- package/dist/helpers/get-default-value.d.ts.map +1 -0
- package/dist/helpers/get-default-value.js +74 -0
- package/dist/helpers/get-discriminator-value.d.ts +1 -0
- package/dist/helpers/get-discriminator-value.d.ts.map +1 -0
- package/dist/helpers/get-discriminator-value.js +19 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -2783
- package/dist/types/schema-extensions.d.ts +1 -0
- package/dist/types/schema-extensions.d.ts.map +1 -0
- package/dist/types/schema-extensions.js +0 -0
- package/package.json +7 -7
- package/src/generators/build-schema.ts +22 -173
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-extensions.d.ts","sourceRoot":"","sources":["../../src/types/schema-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA"}
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/generate-parsers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "
|
|
34
|
-
"
|
|
35
|
-
"
|
|
33
|
+
"build": "tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f",
|
|
34
|
+
"types:check": "tsgo -p . --noEmit",
|
|
35
|
+
"test": "NODE_ENV=production vitest run --root ../.. generate-parsers",
|
|
36
36
|
"generate-readme": "bun run ./scripts/generate-readme.ts"
|
|
37
37
|
},
|
|
38
38
|
"imports": {
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
},
|
|
43
43
|
"exports": {
|
|
44
44
|
".": {
|
|
45
|
-
"
|
|
45
|
+
"development": "./src/index.ts",
|
|
46
46
|
"default": "./dist/index.js",
|
|
47
47
|
"types": "./dist/index.d.ts"
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"json-schema-typed": "^8.0.1",
|
|
52
|
-
"@amritk/helpers": "0.
|
|
53
|
-
"@amritk/generate-markdown": "0.2.
|
|
52
|
+
"@amritk/helpers": "0.7.1",
|
|
53
|
+
"@amritk/generate-markdown": "0.2.4"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
import { createRequire } from 'node:module'
|
|
3
3
|
import { dirname, resolve as resolvePath } from 'node:path'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { refToFilename } from '@amritk/helpers/ref-to-filename'
|
|
7
|
-
import { refToName } from '@amritk/helpers/ref-to-name'
|
|
8
|
-
import { resolveDynamicRefs } from '@amritk/helpers/resolve-dynamic-refs'
|
|
9
|
-
import { resolveRef } from '@amritk/helpers/resolve-ref'
|
|
10
|
-
import { upgradeDraft07Schema } from '@amritk/helpers/upgrade-draft07-schema'
|
|
4
|
+
import { generateIndexBarrel } from '@amritk/helpers/generate-index-barrel'
|
|
5
|
+
import { walkRefGraph } from '@amritk/helpers/walk-ref-graph'
|
|
11
6
|
import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
|
|
12
7
|
import { applySchemaExtensions } from '#helpers/apply-schema-extensions'
|
|
13
8
|
import type { HelpersMode, RuntimeHelperName } from '#helpers/collect-helpers'
|
|
@@ -33,47 +28,13 @@ export type GeneratedFile = {
|
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const refs: string[] = []
|
|
41
|
-
|
|
42
|
-
if (typeof schema !== 'object' || schema === null) {
|
|
43
|
-
return refs
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Check if schema has $defs
|
|
47
|
-
if (!('$defs' in schema) || typeof schema['$defs'] !== 'object' || schema['$defs'] === null) {
|
|
48
|
-
return refs
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const defs = schema['$defs']
|
|
52
|
-
|
|
53
|
-
// Find all definitions with $dynamicAnchor
|
|
54
|
-
for (const [key, value] of Object.entries(defs)) {
|
|
55
|
-
if (typeof value === 'object' && value !== null) {
|
|
56
|
-
const defSchema = value as Record<string, unknown>
|
|
57
|
-
if ('$dynamicAnchor' in defSchema) {
|
|
58
|
-
// Convert to a $ref-style path
|
|
59
|
-
refs.push(`#/$defs/${key}`)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return refs
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Builds all TypeScript files from a JSON Schema by traversing
|
|
69
|
-
* all $ref references recursively.
|
|
31
|
+
* Builds all TypeScript files from a JSON Schema by traversing its entire
|
|
32
|
+
* `$ref` / `$dynamicRef` graph (via the shared `@amritk/helpers/walk-ref-graph`
|
|
33
|
+
* walker, which also seeds `$dynamicAnchor` definitions and rewrites
|
|
34
|
+
* `$dynamicRef` to `$ref`).
|
|
70
35
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* 2. Extracts all $ref references from the schema
|
|
74
|
-
* 3. Extracts all definitions with $dynamicAnchor (JSON Schema 2020-12 feature)
|
|
75
|
-
* 4. Resolves each ref and generates a TypeScript file for it
|
|
76
|
-
* 5. Recursively processes nested refs until all are handled
|
|
36
|
+
* For the root schema and each reachable definition this function generates a
|
|
37
|
+
* TypeScript file, then emits an `index.ts` barrel re-exporting them all.
|
|
77
38
|
*
|
|
78
39
|
* @param rootSchema - The root JSON Schema to build from
|
|
79
40
|
* @param rootTypeName - The name for the root type (e.g., "Document")
|
|
@@ -143,107 +104,32 @@ export const buildSchema = async (
|
|
|
143
104
|
readonly = false,
|
|
144
105
|
typeSuffix = '',
|
|
145
106
|
): Promise<GeneratedFile[]> => {
|
|
146
|
-
// Upgrade draft-07 schemas to 2020-12 conventions before processing.
|
|
147
|
-
// This renames `definitions` → `$defs` recursively so the rest of the
|
|
148
|
-
// pipeline can resolve both short-name and URI-keyed refs uniformly.
|
|
149
|
-
rootSchema = upgradeDraft07Schema(rootSchema as Record<string, unknown>) as JSONSchema
|
|
150
|
-
|
|
151
107
|
const files: GeneratedFile[] = []
|
|
152
|
-
const processedRefs = new Set<string>()
|
|
153
|
-
const processedFilenames = new Set<string>()
|
|
154
|
-
const refsToProcess: string[] = []
|
|
155
108
|
const usedHelpers = new Set<RuntimeHelperName>()
|
|
156
109
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
110
|
+
walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
|
|
111
|
+
// `index` is reserved for the barrel below, so never let a definition of
|
|
112
|
+
// that name overwrite it.
|
|
113
|
+
if (node.filename === 'index') return
|
|
160
114
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
: processedRootSchema
|
|
166
|
-
const rootResult = generateFile(extendedRootSchema, rootTypeName, {
|
|
167
|
-
typesOnly: typesOnly ?? false,
|
|
168
|
-
rootSchema: rootSchema as Record<string, unknown>,
|
|
169
|
-
helpersMode,
|
|
170
|
-
helpersImportPrefix,
|
|
171
|
-
readonly,
|
|
172
|
-
typeSuffix,
|
|
173
|
-
...(logWarnings !== undefined ? { logWarnings } : {}),
|
|
174
|
-
...(strict !== undefined ? { strict } : {}),
|
|
175
|
-
})
|
|
176
|
-
const rootFilename = rootTypeName.toLowerCase()
|
|
177
|
-
|
|
178
|
-
processedFilenames.add(rootFilename)
|
|
179
|
-
files.push({
|
|
180
|
-
filename: `${rootFilename}.ts`,
|
|
181
|
-
content: rootResult.content,
|
|
182
|
-
})
|
|
183
|
-
for (const helper of rootResult.usedHelpers) usedHelpers.add(helper)
|
|
184
|
-
|
|
185
|
-
// Extract all refs from the root schema
|
|
186
|
-
const rootRefs = extractRefs(rootSchema)
|
|
187
|
-
refsToProcess.push(...rootRefs)
|
|
188
|
-
|
|
189
|
-
// Extract all definitions with $dynamicAnchor
|
|
190
|
-
const dynamicAnchorRefs = extractDynamicAnchorDefs(rootSchema)
|
|
191
|
-
refsToProcess.push(...dynamicAnchorRefs)
|
|
192
|
-
|
|
193
|
-
// Process refs until none remain
|
|
194
|
-
while (refsToProcess.length > 0) {
|
|
195
|
-
const ref = refsToProcess.shift()
|
|
196
|
-
|
|
197
|
-
// Skip if already processed
|
|
198
|
-
if (!ref || processedRefs.has(ref)) {
|
|
199
|
-
continue
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
processedRefs.add(ref)
|
|
203
|
-
|
|
204
|
-
// Resolve the ref to get the actual schema
|
|
205
|
-
const resolvedSchema = resolveRef(ref, rootSchema as Record<string, unknown>)
|
|
206
|
-
|
|
207
|
-
if (!resolvedSchema) {
|
|
208
|
-
console.warn(`Warning: Could not resolve ref: ${ref}`)
|
|
209
|
-
continue
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Generate file for this ref, resolving any $dynamicRef to $ref first
|
|
213
|
-
// and applying any matching extensions for this definition
|
|
214
|
-
const typeName = refToName(ref, typeSuffix)
|
|
215
|
-
const filename = refToFilename(ref)
|
|
216
|
-
const processedSchema = resolveDynamicRefs(resolvedSchema as JSONSchema, dynamicRefMap)
|
|
217
|
-
const extendedSchema = extensions ? applySchemaExtensions(processedSchema, filename, extensions) : processedSchema
|
|
218
|
-
const fileResult = generateFile(extendedSchema, typeName, {
|
|
115
|
+
// Extensions are keyed by definition name, which is the node's filename for
|
|
116
|
+
// both the root (the lowercased root type name) and every `$ref` target.
|
|
117
|
+
const extended = extensions ? applySchemaExtensions(node.schema, node.filename, extensions) : node.schema
|
|
118
|
+
const result = generateFile(extended, node.typeName, {
|
|
219
119
|
typesOnly: typesOnly ?? false,
|
|
220
|
-
|
|
221
|
-
rootSchema: rootSchema as Record<string, unknown>,
|
|
120
|
+
rootSchema: node.rootSchema,
|
|
222
121
|
helpersMode,
|
|
223
122
|
helpersImportPrefix,
|
|
224
123
|
readonly,
|
|
225
124
|
typeSuffix,
|
|
125
|
+
...(node.ref !== undefined ? { selfRef: node.ref } : {}),
|
|
226
126
|
...(logWarnings !== undefined ? { logWarnings } : {}),
|
|
227
127
|
...(strict !== undefined ? { strict } : {}),
|
|
228
128
|
})
|
|
229
129
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
filename: `${filename}.ts`,
|
|
234
|
-
content: fileResult.content,
|
|
235
|
-
})
|
|
236
|
-
}
|
|
237
|
-
for (const helper of fileResult.usedHelpers) usedHelpers.add(helper)
|
|
238
|
-
|
|
239
|
-
// Extract refs from this schema and add to queue
|
|
240
|
-
const nestedRefs = extractRefs(resolvedSchema as JSONSchema)
|
|
241
|
-
for (const nestedRef of nestedRefs) {
|
|
242
|
-
if (!processedRefs.has(nestedRef)) {
|
|
243
|
-
refsToProcess.push(nestedRef)
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
130
|
+
files.push({ filename: `${node.filename}.ts`, content: result.content })
|
|
131
|
+
for (const helper of result.usedHelpers) usedHelpers.add(helper)
|
|
132
|
+
})
|
|
247
133
|
|
|
248
134
|
// In embedded mode, ship the runtime helper source files alongside the parsers so
|
|
249
135
|
// the output directory is self-contained (no `@amritk/helpers` install required).
|
|
@@ -255,44 +141,7 @@ export const buildSchema = async (
|
|
|
255
141
|
}
|
|
256
142
|
}
|
|
257
143
|
|
|
258
|
-
|
|
259
|
-
// Regex matches `export type Foo` and `export const foo` declarations.
|
|
260
|
-
const TYPE_EXPORT_RE = /^export type (\w+)/gm
|
|
261
|
-
const CONST_EXPORT_RE = /^export const (\w+)/gm
|
|
262
|
-
|
|
263
|
-
// _helpers/ is an internal directory; never re-export from it.
|
|
264
|
-
const sortedFiles = [...files]
|
|
265
|
-
.filter((f) => !f.filename.startsWith('_helpers/'))
|
|
266
|
-
.sort((a, b) => a.filename.localeCompare(b.filename))
|
|
267
|
-
|
|
268
|
-
let indexContent = ''
|
|
269
|
-
for (const file of sortedFiles) {
|
|
270
|
-
const moduleName = file.filename.replace(/\.ts$/, '')
|
|
271
|
-
const typeNames: string[] = []
|
|
272
|
-
const constNames: string[] = []
|
|
273
|
-
|
|
274
|
-
for (const match of file.content.matchAll(TYPE_EXPORT_RE)) {
|
|
275
|
-
typeNames.push(match[1] as string)
|
|
276
|
-
}
|
|
277
|
-
for (const match of file.content.matchAll(CONST_EXPORT_RE)) {
|
|
278
|
-
constNames.push(match[1] as string)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (typeNames.length === 0 && constNames.length === 0) continue
|
|
282
|
-
|
|
283
|
-
if (typesOnly) {
|
|
284
|
-
indexContent += `export type { ${typeNames.join(', ')} } from './${moduleName}';\n`
|
|
285
|
-
} else {
|
|
286
|
-
const typeExports = typeNames.map((n) => `type ${n}`)
|
|
287
|
-
const allExports = [...typeExports, ...constNames]
|
|
288
|
-
indexContent += `export { ${allExports.join(', ')} } from './${moduleName}';\n`
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
files.push({
|
|
293
|
-
filename: 'index.ts',
|
|
294
|
-
content: indexContent,
|
|
295
|
-
})
|
|
144
|
+
files.push({ filename: 'index.ts', content: generateIndexBarrel(files, { typesOnly: typesOnly ?? false }) })
|
|
296
145
|
|
|
297
146
|
return files
|
|
298
147
|
}
|