@amritk/generate-parsers 0.1.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/README.md ADDED
@@ -0,0 +1,122 @@
1
+ <div align="center">
2
+
3
+ # @amritk/generate-parsers
4
+
5
+ **Programmatic API for generating TypeScript parsers and type definitions from JSON Schemas.**
6
+
7
+ ![status](https://img.shields.io/badge/status-pre--alpha-ef4444?style=flat-square)&nbsp;
8
+ ![version](https://img.shields.io/badge/version-v0.1.0-6366f1?style=flat-square&logo=npm&logoColor=white)&nbsp;
9
+ ![license](https://img.shields.io/badge/license-MIT-22c55e?style=flat-square)&nbsp;
10
+ ![JSON Schema](https://img.shields.io/badge/JSON%20Schema-2020--12-f97316?style=flat-square)&nbsp;
11
+ ![node](https://img.shields.io/badge/node-%E2%89%A520-339933?style=flat-square&logo=node.js&logoColor=white)&nbsp;
12
+ ![vibe coded](https://img.shields.io/badge/vibe%20coded-84%25-a855f7?style=flat-square)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## Overview
19
+
20
+ `@amritk/generate-parsers` is the core code-generation engine behind [mjst](../../README.md). Given a JSON Schema (Draft 2020-12), it produces an array of `{ filename, content }` records — TypeScript type definitions plus optional runtime parser functions that validate and coerce unknown input.
21
+
22
+ If you want a CLI, use [`@amritk/mjst`](../cli). Use this package directly when you want to embed schema-to-TypeScript generation inside another build step or tool.
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install @amritk/generate-parsers
30
+ # or
31
+ pnpm add @amritk/generate-parsers
32
+ # or
33
+ yarn add @amritk/generate-parsers
34
+ # or
35
+ bun add @amritk/generate-parsers
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Usage
41
+
42
+ ```ts
43
+ import { buildSchema } from '@amritk/generate-parsers'
44
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
45
+
46
+ const schema: JSONSchema = {
47
+ type: 'object',
48
+ properties: {
49
+ info: { $ref: '#/$defs/info' },
50
+ },
51
+ $defs: {
52
+ info: {
53
+ type: 'object',
54
+ properties: { title: { type: 'string' } },
55
+ },
56
+ },
57
+ }
58
+
59
+ const files = await buildSchema(schema, 'Document')
60
+ // → [{ filename: 'document.ts', content: '...' }, { filename: 'info.ts', content: '...' }, ...]
61
+ ```
62
+
63
+ Each entry in `files` is a `GeneratedFile`:
64
+
65
+ ```ts
66
+ type GeneratedFile = {
67
+ filename: string
68
+ content: string
69
+ }
70
+ ```
71
+
72
+ Write them to disk however you like.
73
+
74
+ ---
75
+
76
+ ## API
77
+
78
+ ### `buildSchema(rootSchema, rootTypeName, extensions?, typesOnly?, logWarnings?)`
79
+
80
+ | Parameter | Type | Description |
81
+ |:---|:---|:---|
82
+ | `rootSchema` | `JSONSchema` | The root schema to traverse. `$ref` and `$dynamicRef` are resolved recursively. |
83
+ | `rootTypeName` | `string` | Name used for the root type (e.g. `"Document"`). |
84
+ | `extensions` | `SchemaExtensions` _(optional)_ | Map of definition name → extra optional properties to merge in before generation. |
85
+ | `typesOnly` | `boolean` _(optional)_ | When `true`, only emit `.ts` type definitions — skip parser functions and runtime helpers. |
86
+ | `logWarnings` | `boolean` _(optional)_ | When `true`, generated parsers emit a `console.warn` for every input key not declared in the schema's properties. |
87
+
88
+ Returns: `Promise<GeneratedFile[]>`.
89
+
90
+ ---
91
+
92
+ ## Options
93
+
94
+ <!-- config-table-start -->
95
+ | | Property | CLI Flag | Type | Required | Default | Description |
96
+ |:---:|:---|:---|:---:|:---:|:---:|:---|
97
+ | 🏷️ | `typesOnly` | — | `boolean` | — | `false` | Generate only TypeScript type definitions without parser functions. Runtime helper files (validators, isObject) are also omitted since they are only needed for parsers. |
98
+ | ⚠️ | `logWarnings` | — | `boolean` | — | `false` | Emit a console.warn in the generated parsers for every input key that is not declared in the schema's properties. Useful for detecting schema drift or unexpected data shapes at runtime. |
99
+ <!-- config-table-end -->
100
+
101
+ The generator handles:
102
+
103
+ - `$ref` and `$dynamicRef` resolution, including JSON Schema 2020-12 `$dynamicAnchor`
104
+ - Discriminated and non-discriminated unions (`oneOf` / `anyOf`)
105
+ - Enums and `const` values
106
+ - Nested objects, arrays, records, and tuples
107
+ - Pattern-based default values
108
+
109
+ ---
110
+
111
+ ## Related packages
112
+
113
+ - [`@amritk/mjst`](../cli) — CLI wrapping this generator
114
+ - [`@amritk/generate-validators`](../generate-validators) — predicate-style validators (sister package)
115
+ - [`@amritk/generate-markdown`](../generate-markdown) — markdown documentation generator
116
+ - [`@amritk/helpers`](../helpers) — shared schema-traversal utilities
117
+
118
+ ---
119
+
120
+ ## License
121
+
122
+ [MIT](../../LICENSE)
@@ -0,0 +1,62 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ import type { SchemaExtensions } from '#types/schema-extensions';
3
+ /**
4
+ * Represents a generated file with its filename and content.
5
+ */
6
+ export type GeneratedFile = {
7
+ filename: string;
8
+ content: string;
9
+ };
10
+ /**
11
+ * Builds all TypeScript files from a JSON Schema by traversing
12
+ * all $ref references recursively.
13
+ *
14
+ * Starting from the root schema, this function:
15
+ * 1. Generates a TypeScript file for the root type
16
+ * 2. Extracts all $ref references from the schema
17
+ * 3. Extracts all definitions with $dynamicAnchor (JSON Schema 2020-12 feature)
18
+ * 4. Resolves each ref and generates a TypeScript file for it
19
+ * 5. Recursively processes nested refs until all are handled
20
+ *
21
+ * @param rootSchema - The root JSON Schema to build from
22
+ * @param rootTypeName - The name for the root type (e.g., "Document")
23
+ * @param extensions - Optional map of custom extension properties to add to specific definitions.
24
+ * Keys are definition names (matching $defs keys), values are records of extension property
25
+ * names to their JSON Schema definitions. Extensions are merged as optional properties before
26
+ * type and parser generation.
27
+ * @param typesOnly - When true, only generate TypeScript type definitions without parser functions.
28
+ * @param logWarnings - When true, the generated parsers emit a console.warn for every input key
29
+ * that is not declared in the schema's properties.
30
+ * @returns An array of generated TypeScript files
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const schema = {
35
+ * type: "object",
36
+ * properties: {
37
+ * info: { $ref: "#/$defs/info" }
38
+ * },
39
+ * $defs: {
40
+ * info: {
41
+ * type: "object",
42
+ * properties: {
43
+ * title: { type: "string" }
44
+ * }
45
+ * }
46
+ * }
47
+ * };
48
+ *
49
+ * const files = buildSchema(schema, "Document");
50
+ *
51
+ * // Types-only mode — no parser functions or runtime helpers included:
52
+ * const typesFiles = buildSchema(schema, "Document", undefined, true);
53
+ *
54
+ * // With extensions:
55
+ * const filesWithExtensions = buildSchema(schema, "Document", {
56
+ * info: {
57
+ * 'x-internal': { type: 'boolean' },
58
+ * },
59
+ * });
60
+ * ```
61
+ */
62
+ export declare const buildSchema: (rootSchema: JSONSchema, rootTypeName: string, extensions?: SchemaExtensions, typesOnly?: boolean, logWarnings?: boolean) => Promise<GeneratedFile[]>;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generates a best-guess default value from a regex pattern.
3
+ * Recognizes common patterns and returns appropriate defaults.
4
+ */
5
+ export declare const generateDefaultFromPattern: (pattern: string) => string | null;
@@ -0,0 +1,6 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Generates a discriminated union validation expression.
4
+ * Uses a discriminator property to efficiently choose the correct schema.
5
+ */
6
+ export declare const generateDiscriminatedUnionValidation: (accessor: string, schemas: readonly JSONSchema[], discriminatorKey: string, defaultValue: string, isRequired: boolean) => string;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generates an enum validation check expression.
3
+ * Returns a string that checks if the value is in the allowed enum values.
4
+ */
5
+ export declare const generateEnumCheck: (accessor: string, enumValues: readonly unknown[]) => string;
@@ -0,0 +1,58 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Options for controlling what gets generated in a file.
4
+ */
5
+ type GenerateFileOptions = {
6
+ /**
7
+ * When true, only emit the TypeScript type definition and skip the parser function.
8
+ * Imports will be type-only as well, since there are no parsers to call.
9
+ */
10
+ readonly typesOnly?: boolean;
11
+ /**
12
+ * The $ref path of the schema being generated (e.g. `#/$defs/encoding`).
13
+ * When provided, any $ref that resolves to the same filename is excluded from
14
+ * the import list, preventing a file from importing itself.
15
+ */
16
+ readonly selfRef?: string;
17
+ /**
18
+ * The root schema document. When provided, URI refs that cannot be resolved
19
+ * within the root schema's $defs are excluded from the import list.
20
+ */
21
+ readonly rootSchema?: Record<string, unknown>;
22
+ /**
23
+ * When true, the generated parser emits a console.warn for every input key
24
+ * that is not declared in the schema's properties.
25
+ */
26
+ readonly logWarnings?: boolean;
27
+ };
28
+ /**
29
+ * Generates a complete TypeScript file from a JSON Schema.
30
+ *
31
+ * The generated file contains imports, type definition, and parser function.
32
+ * When typesOnly is true, only the type definition is emitted without any parser.
33
+ *
34
+ * @param schema - The JSON Schema to generate code from
35
+ * @param typeName - The name to use for the generated TypeScript type
36
+ * @param options - Optional settings to control what gets generated
37
+ * @returns The complete TypeScript code
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const schema = {
42
+ * type: "object",
43
+ * properties: {
44
+ * name: { type: "string" },
45
+ * age: { type: "number" }
46
+ * },
47
+ * required: ["name"]
48
+ * };
49
+ *
50
+ * const result = generateFile(schema, "User");
51
+ * // result contains imports, type User, and parseUser function
52
+ *
53
+ * const typesOnly = generateFile(schema, "User", { typesOnly: true });
54
+ * // typesOnly contains only type-only imports and type User (no parser)
55
+ * ```
56
+ */
57
+ export declare const generateFile: (schema: JSONSchema, typeName: string, options?: GenerateFileOptions) => string;
58
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Generates a non-discriminated union validation expression.
4
+ * Tries each schema in order until one matches.
5
+ */
6
+ export declare const generateNonDiscriminatedUnionValidation: (accessor: string, schemas: readonly JSONSchema[], defaultValue: string, isRequired: boolean) => string;
@@ -0,0 +1,47 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Options for controlling parser function generation behavior.
4
+ */
5
+ type GenerateParserOptions = {
6
+ /**
7
+ * When true, properties with $ref will call the imported parser function
8
+ * instead of inlining the validation logic. This is used when generating
9
+ * files where each ref has its own separate file with a parser.
10
+ */
11
+ readonly useRefImports?: boolean;
12
+ /**
13
+ * When true, the generated parser emits a console.warn for every input key
14
+ * that is not declared in the schema's properties.
15
+ */
16
+ readonly logWarnings?: boolean;
17
+ };
18
+ /**
19
+ * Generates a safe parser as an arrow function that never throws.
20
+ * The parser provides default values for all missing or invalid fields.
21
+ *
22
+ * When useRefImports is enabled, properties with $ref will call the imported
23
+ * parser function (e.g., parseContact(input?.contact)) instead of inlining
24
+ * the resolved schema's validation logic. Array properties whose items are a
25
+ * $ref will map each element through the imported parser.
26
+ */
27
+ export declare const generateParserFunction: (schema: JSONSchema, typeName: string, options?: GenerateParserOptions) => string;
28
+ /**
29
+ * Returns the predicate function name for a generated shape validator.
30
+ * @example shapeValidatorFunctionName('CustomerObject') // 'validateCustomerObjectShape'
31
+ */
32
+ export declare const shapeValidatorFunctionName: (typeName: string) => string;
33
+ /**
34
+ * Generates a `validate{TypeName}Shape(input)` predicate that returns true
35
+ * iff `input` already matches the shape produced by the parser's fast path —
36
+ * i.e. the parser would return `{ ...input } as TypeName` without coercion.
37
+ *
38
+ * Parents call this predicate to fast-path through nested ref properties
39
+ * (and arrays of refs) without recursing into their parser.
40
+ *
41
+ * Returns a stub that always returns `false` for schemas that cannot be
42
+ * predicated (composition, conditionals, pattern properties, complex refs
43
+ * in additionalProperties). The stub is safe: parents calling it will fall
44
+ * through to the slow path, matching the pre-deep-fast-path behavior.
45
+ */
46
+ export declare const generateShapeValidator: (schema: JSONSchema, typeName: string, useRefImports: boolean) => string;
47
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Generates validation checks for a single schema (used in union validation).
4
+ */
5
+ export declare const generateSchemaChecks: (accessor: string, schema: JSONSchema) => string[];
@@ -0,0 +1,7 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Generates a validation expression that checks if a value matches the expected type.
4
+ * Returns the value if valid, coerces to the correct type if invalid, or uses default if required and missing.
5
+ * Supports type checking, pattern validation, enum validation, union types, and $ref resolution.
6
+ */
7
+ export declare const generateValidationExpression: (key: string, schema: JSONSchema, defaultValue: string, isRequired: boolean, rootSchema?: Record<string, unknown>, visitedRefs?: Set<string>, accessorOverride?: string, knownNotUndefined?: boolean) => string;
@@ -0,0 +1,35 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ import type { SchemaExtensions } from '#types/schema-extensions';
3
+ /**
4
+ * Merges custom extension properties into a schema's properties based on the
5
+ * definition name. Extension properties are added as optional (not included
6
+ * in the required array) since they are custom additions that may not always
7
+ * be present.
8
+ *
9
+ * If the schema is not an object, has no matching extensions, or the
10
+ * extensions record is empty, the original schema is returned unchanged.
11
+ *
12
+ * @param schema - The JSON Schema to extend
13
+ * @param definitionName - The name of the definition (e.g., "parameter")
14
+ * @param extensions - The extensions configuration mapping definition names to properties
15
+ * @returns A new schema with extension properties merged in, or the original if unchanged
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const schema = {
20
+ * type: 'object',
21
+ * properties: { name: { type: 'string' } },
22
+ * required: ['name'],
23
+ * }
24
+ *
25
+ * const extensions = {
26
+ * parameter: {
27
+ * 'x-enabled': { type: 'boolean' },
28
+ * },
29
+ * }
30
+ *
31
+ * const extended = applySchemaExtensions(schema, 'parameter', extensions)
32
+ * // extended.properties now includes 'x-enabled' alongside 'name'
33
+ * ```
34
+ */
35
+ export declare const applySchemaExtensions: (schema: JSONSchema, definitionName: string, extensions: SchemaExtensions) => JSONSchema;
@@ -0,0 +1,2 @@
1
+ /** A more foolproof way to generate imports from the parserFunction */
2
+ export declare const collectHelpers: (parserFunction: string) => string[];
@@ -0,0 +1,64 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Options for controlling how imports are collected.
4
+ */
5
+ type CollectImportsOptions = {
6
+ /**
7
+ * When true, only generate type-only imports (no parser function imports).
8
+ * Use this when generating types-only files that do not include parser functions.
9
+ */
10
+ readonly typesOnly?: boolean;
11
+ /**
12
+ * The $ref path of the schema being generated (e.g. `#/$defs/encoding`).
13
+ * When provided, any $ref that resolves to the same filename is excluded from
14
+ * the import list, preventing a file from importing itself.
15
+ */
16
+ readonly selfRef?: string | undefined;
17
+ /**
18
+ * The root schema document. When provided, URI refs that cannot be resolved
19
+ * within the root schema's $defs are excluded from the import list, preventing
20
+ * imports for external schemas that were never generated as files.
21
+ */
22
+ readonly rootSchema?: Record<string, unknown> | undefined;
23
+ };
24
+ /**
25
+ * Collects all import statements needed for a schema by finding $ref references
26
+ * that are directly used in the type definition (from properties, items, etc.).
27
+ * Does not include refs from nested schema definitions.
28
+ *
29
+ * Generates both type imports and parser function imports for each $ref.
30
+ * The parser imports allow validators to delegate validation to the referenced
31
+ * schema's parser instead of inlining the validation logic.
32
+ *
33
+ * Also detects when helper functions (isObject) and validators (validateRecord,
34
+ * validateArray) are needed based on the schema structure.
35
+ *
36
+ * @param schema - The JSON Schema to collect imports from
37
+ * @param options - Optional settings to control import generation
38
+ * @returns An array of import statements
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const schema = {
43
+ * type: 'object',
44
+ * properties: {
45
+ * contact: { $ref: '#/$defs/contact' },
46
+ * server: { $ref: '#/$defs/server' }
47
+ * }
48
+ * }
49
+ * const imports = collectImports(schema)
50
+ * // imports = [
51
+ * // "import { type Contact, parseContact } from './contact';",
52
+ * // "import { type Server, parseServer } from './server';"
53
+ * // ]
54
+ *
55
+ * // With typesOnly, only type imports are generated:
56
+ * const typeImports = collectImports(schema, { typesOnly: true })
57
+ * // typeImports = [
58
+ * // "import type { Contact } from './contact';",
59
+ * // "import type { Server } from './server';"
60
+ * // ]
61
+ * ```
62
+ */
63
+ export declare const collectImports: (schema: JSONSchema, options?: CollectImportsOptions) => string[];
64
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Attempts to find a discriminator property that can be used to distinguish between union types.
4
+ * Returns the property name if a suitable discriminator is found, otherwise null.
5
+ */
6
+ export declare const findDiscriminator: (schemas: readonly JSONSchema[]) => string | null;
@@ -0,0 +1,7 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Returns the default value for a JSON Schema property.
4
+ * Priority order: explicit default > first enum value > first example > union first schema > pattern-based > type-based.
5
+ * These defaults ensure that parsing never fails, even with missing data.
6
+ */
7
+ export declare const getDefaultValue: (schema: JSONSchema) => string;
@@ -0,0 +1,6 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * Gets the discriminator value for a specific schema.
4
+ * Returns the const value or single enum value if available.
5
+ */
6
+ export declare const getDiscriminatorValue: (schema: JSONSchema, discriminatorKey: string) => unknown | null;
@@ -0,0 +1 @@
1
+ export { buildSchema } from './generators/build-schema';