@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.
Files changed (56) hide show
  1. package/dist/generators/build-schema.d.ts +9 -10
  2. package/dist/generators/build-schema.d.ts.map +1 -0
  3. package/dist/generators/build-schema.js +118 -0
  4. package/dist/generators/generate-default-from-pattern.d.ts +1 -0
  5. package/dist/generators/generate-default-from-pattern.d.ts.map +1 -0
  6. package/dist/generators/generate-default-from-pattern.js +33 -0
  7. package/dist/generators/generate-discriminated-union-validation.d.ts +1 -0
  8. package/dist/generators/generate-discriminated-union-validation.d.ts.map +1 -0
  9. package/dist/generators/generate-discriminated-union-validation.js +38 -0
  10. package/dist/generators/generate-enum-check.d.ts +1 -0
  11. package/dist/generators/generate-enum-check.d.ts.map +1 -0
  12. package/dist/generators/generate-enum-check.js +8 -0
  13. package/dist/generators/generate-files.d.ts +2 -1
  14. package/dist/generators/generate-files.d.ts.map +1 -0
  15. package/dist/generators/generate-files.js +74 -0
  16. package/dist/generators/generate-non-discriminated-union-validation.d.ts +1 -0
  17. package/dist/generators/generate-non-discriminated-union-validation.d.ts.map +1 -0
  18. package/dist/generators/generate-non-discriminated-union-validation.js +28 -0
  19. package/dist/generators/generate-parser-function.d.ts +1 -0
  20. package/dist/generators/generate-parser-function.d.ts.map +1 -0
  21. package/dist/generators/generate-parser-function.js +1070 -0
  22. package/dist/generators/generate-schema-checks.d.ts +1 -0
  23. package/dist/generators/generate-schema-checks.d.ts.map +1 -0
  24. package/dist/generators/generate-schema-checks.js +99 -0
  25. package/dist/generators/generate-strict-assertion.d.ts +1 -0
  26. package/dist/generators/generate-strict-assertion.d.ts.map +1 -0
  27. package/dist/generators/generate-strict-assertion.js +179 -0
  28. package/dist/generators/generate-validation-expression.d.ts +1 -0
  29. package/dist/generators/generate-validation-expression.d.ts.map +1 -0
  30. package/dist/generators/generate-validation-expression.js +267 -0
  31. package/dist/helpers/apply-schema-extensions.d.ts +2 -1
  32. package/dist/helpers/apply-schema-extensions.d.ts.map +1 -0
  33. package/dist/helpers/apply-schema-extensions.js +51 -0
  34. package/dist/helpers/collect-helpers.d.ts +1 -0
  35. package/dist/helpers/collect-helpers.d.ts.map +1 -0
  36. package/dist/helpers/collect-helpers.js +48 -0
  37. package/dist/helpers/collect-imports.d.ts +1 -0
  38. package/dist/helpers/collect-imports.d.ts.map +1 -0
  39. package/dist/helpers/collect-imports.js +216 -0
  40. package/dist/helpers/find-discriminator.d.ts +1 -0
  41. package/dist/helpers/find-discriminator.d.ts.map +1 -0
  42. package/dist/helpers/find-discriminator.js +33 -0
  43. package/dist/helpers/get-default-value.d.ts +1 -0
  44. package/dist/helpers/get-default-value.d.ts.map +1 -0
  45. package/dist/helpers/get-default-value.js +74 -0
  46. package/dist/helpers/get-discriminator-value.d.ts +1 -0
  47. package/dist/helpers/get-discriminator-value.d.ts.map +1 -0
  48. package/dist/helpers/get-discriminator-value.js +19 -0
  49. package/dist/index.d.ts +2 -1
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +1 -2783
  52. package/dist/types/schema-extensions.d.ts +1 -0
  53. package/dist/types/schema-extensions.d.ts.map +1 -0
  54. package/dist/types/schema-extensions.js +0 -0
  55. package/package.json +7 -7
  56. package/src/generators/build-schema.ts +22 -173
@@ -32,3 +32,4 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
32
32
  * ```
33
33
  */
34
34
  export type SchemaExtensions = Record<string, Record<string, JSONSchema>>;
35
+ //# sourceMappingURL=schema-extensions.d.ts.map
@@ -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.6.3",
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": "bun run build:code && bun run build:types",
34
- "build:code": "bun build ./src/index.ts --outdir=dist --target=node",
35
- "build:types": "tsc -p .",
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
- "bun": "./src/index.ts",
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.6.2",
53
- "@amritk/generate-markdown": "0.2.3"
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 { buildDynamicRefMap } from '@amritk/helpers/build-dynamic-ref-map'
5
- import { extractRefs } from '@amritk/helpers/extract-refs'
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
- * Extracts all definitions from $defs that have a $dynamicAnchor.
37
- * These definitions need to be generated even if not directly referenced by $ref.
38
- */
39
- const extractDynamicAnchorDefs = (schema: JSONSchema): string[] => {
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
- * Starting from the root schema, this function:
72
- * 1. Generates a TypeScript file for the root type
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
- // Build a map of $dynamicRef anchors to their $ref paths so we can
158
- // convert $dynamicRef: "#meta" to $ref: "#/$defs/schema" before generating
159
- const dynamicRefMap = buildDynamicRefMap(rootSchema)
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
- // Generate file for the root schema, applying any matching extensions
162
- const processedRootSchema = resolveDynamicRefs(rootSchema, dynamicRefMap)
163
- const extendedRootSchema = extensions
164
- ? applySchemaExtensions(processedRootSchema, rootTypeName.toLowerCase(), extensions)
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
- selfRef: ref,
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
- if (!processedFilenames.has(filename)) {
231
- processedFilenames.add(filename)
232
- files.push({
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
- // Generate index.ts with named re-exports extracted from each generated file's content.
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
  }