@amritk/generate-parsers 0.12.3 → 0.14.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/dist/generators/build-schema.d.ts +6 -1
- package/dist/generators/build-schema.js +30 -5
- package/dist/generators/generate-files.d.ts +8 -0
- package/dist/generators/generate-files.js +12 -5
- package/dist/generators/generate-parser-function.d.ts +9 -1
- package/dist/generators/generate-parser-function.js +346 -192
- package/dist/generators/generate-strict-assertion.d.ts +16 -1
- package/dist/generators/generate-strict-assertion.js +71 -12
- package/dist/generators/generate-type-checks.d.ts +82 -0
- package/dist/generators/generate-type-checks.js +438 -0
- package/dist/generators/generate-validation-expression.d.ts +7 -0
- package/dist/generators/generate-validation-expression.js +17 -5
- package/dist/helpers/collect-helpers.d.ts +1 -9
- package/dist/helpers/collect-helpers.js +36 -9
- package/dist/helpers/collect-imports.d.ts +15 -0
- package/dist/helpers/collect-imports.js +36 -14
- package/dist/index.d.ts +1 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
2
|
import type { HelpersMode } from '../helpers/collect-helpers.js';
|
|
3
|
+
import type { ImportExtension } from '../helpers/collect-imports.js';
|
|
3
4
|
import type { SchemaExtensions } from '../types/schema-extensions.js';
|
|
4
5
|
/**
|
|
5
6
|
* Represents a generated file with its filename and content.
|
|
@@ -45,6 +46,10 @@ export type GeneratedFile = {
|
|
|
45
46
|
* @param typeSuffix - Suffix appended to every type/parser name derived from a `$ref`
|
|
46
47
|
* (e.g. `'Object'` → `ContactObject`). Defaults to `''` (no suffix). The root type name
|
|
47
48
|
* is used verbatim and is not affected by this suffix.
|
|
49
|
+
* @param importExt - Extension used on every relative import specifier in the generated
|
|
50
|
+
* output (cross-file `$ref` imports, the index barrel, and embedded-helper imports).
|
|
51
|
+
* `'js'` (default) is the standard TS NodeNext form; `'ts'` emits the literal on-disk
|
|
52
|
+
* paths so the generated sources run directly under Node's type stripping.
|
|
48
53
|
* @returns An array of generated TypeScript files
|
|
49
54
|
*
|
|
50
55
|
* @example
|
|
@@ -77,4 +82,4 @@ export type GeneratedFile = {
|
|
|
77
82
|
* });
|
|
78
83
|
* ```
|
|
79
84
|
*/
|
|
80
|
-
export declare const buildSchema: (rootSchema: JSONSchema, rootTypeName: string, extensions?: SchemaExtensions, typesOnly?: boolean, logWarnings?: boolean, strict?: boolean, helpersMode?: HelpersMode, helpersImportPrefix?: string, readonly?: boolean, stripUnknown?: boolean, typeSuffix?: string) => Promise<GeneratedFile[]>;
|
|
85
|
+
export declare const buildSchema: (rootSchema: JSONSchema, rootTypeName: string, extensions?: SchemaExtensions, typesOnly?: boolean, logWarnings?: boolean, strict?: boolean, helpersMode?: HelpersMode, helpersImportPrefix?: string, readonly?: boolean, stripUnknown?: boolean, typeSuffix?: string, importExt?: ImportExtension) => Promise<GeneratedFile[]>;
|
|
@@ -16,15 +16,32 @@ import { generateFile } from './generate-files.js';
|
|
|
16
16
|
* on this lookup), it falls back to the always-published compiled `dist/<helper>.js`
|
|
17
17
|
* — which tsc already emits with `.js`-extension imports — and writes it verbatim.
|
|
18
18
|
*/
|
|
19
|
-
const readHelperSource = async (helper) => {
|
|
19
|
+
const readHelperSource = async (helper, importExt) => {
|
|
20
20
|
const require = createRequire(import.meta.url);
|
|
21
21
|
const helpersRoot = dirname(require.resolve('@amritk/helpers/package.json'));
|
|
22
22
|
try {
|
|
23
23
|
const source = await readFile(resolvePath(helpersRoot, 'src', `${helper}.ts`), 'utf-8');
|
|
24
|
-
|
|
24
|
+
// The leading `\b` in this pattern is load-bearing beyond its regex meaning.
|
|
25
|
+
// Our build runs tsc-alias --resolveFullPaths over the compiled output, and its
|
|
26
|
+
// scanner rewrites anything that looks like `from '<path>'` — even inside a
|
|
27
|
+
// regex literal. Without the `\b`, normalize-path turned this pattern's
|
|
28
|
+
// backslashes into slashes and v0.12.3 shipped with an unparseable regex that
|
|
29
|
+
// crashed the CLI on load. The `\b` blocks that scan (in the emitted text,
|
|
30
|
+
// `from` is preceded by the word character `b`, so tsc-alias's own `\bfrom`
|
|
31
|
+
// never matches) and is also semantically correct: an import keyword always
|
|
32
|
+
// sits at a word boundary. The dist smoke test (scripts/dist-smoke.test.ts)
|
|
33
|
+
// fails the build if this ever regresses.
|
|
34
|
+
return { ext: 'ts', content: source.replace(/\bfrom '(\.\/[^'".]+)'/g, `from '$1.${importExt}'`) };
|
|
25
35
|
}
|
|
26
36
|
catch {
|
|
27
37
|
const compiled = await readFile(resolvePath(helpersRoot, 'dist', `${helper}.js`), 'utf-8');
|
|
38
|
+
// With `.ts` import specifiers the on-disk file must literally be `.ts`, so
|
|
39
|
+
// ship the compiled JS (valid TS) under that name and retarget its own
|
|
40
|
+
// sibling imports to match. The pattern keeps the same `\bfrom` shape as
|
|
41
|
+
// above so tsc-alias's scanner can't corrupt it (see the comment there).
|
|
42
|
+
if (importExt === 'ts') {
|
|
43
|
+
return { ext: 'ts', content: compiled.replace(/\bfrom '(\.\/[^'"]+)\.js'/g, "from '$1.ts'") };
|
|
44
|
+
}
|
|
28
45
|
return { ext: 'js', content: compiled };
|
|
29
46
|
}
|
|
30
47
|
};
|
|
@@ -65,6 +82,10 @@ const readHelperSource = async (helper) => {
|
|
|
65
82
|
* @param typeSuffix - Suffix appended to every type/parser name derived from a `$ref`
|
|
66
83
|
* (e.g. `'Object'` → `ContactObject`). Defaults to `''` (no suffix). The root type name
|
|
67
84
|
* is used verbatim and is not affected by this suffix.
|
|
85
|
+
* @param importExt - Extension used on every relative import specifier in the generated
|
|
86
|
+
* output (cross-file `$ref` imports, the index barrel, and embedded-helper imports).
|
|
87
|
+
* `'js'` (default) is the standard TS NodeNext form; `'ts'` emits the literal on-disk
|
|
88
|
+
* paths so the generated sources run directly under Node's type stripping.
|
|
68
89
|
* @returns An array of generated TypeScript files
|
|
69
90
|
*
|
|
70
91
|
* @example
|
|
@@ -97,7 +118,7 @@ const readHelperSource = async (helper) => {
|
|
|
97
118
|
* });
|
|
98
119
|
* ```
|
|
99
120
|
*/
|
|
100
|
-
export const buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWarnings, strict, helpersMode = 'package', helpersImportPrefix = './', readonly = false, stripUnknown = false, typeSuffix = '') => {
|
|
121
|
+
export const buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWarnings, strict, helpersMode = 'package', helpersImportPrefix = './', readonly = false, stripUnknown = false, typeSuffix = '', importExt = 'js') => {
|
|
101
122
|
const files = [];
|
|
102
123
|
const usedHelpers = new Set();
|
|
103
124
|
walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
|
|
@@ -115,6 +136,7 @@ export const buildSchema = async (rootSchema, rootTypeName, extensions, typesOnl
|
|
|
115
136
|
helpersImportPrefix,
|
|
116
137
|
readonly,
|
|
117
138
|
typeSuffix,
|
|
139
|
+
importExt,
|
|
118
140
|
...(node.ref !== undefined ? { selfRef: node.ref } : {}),
|
|
119
141
|
...(logWarnings !== undefined ? { logWarnings } : {}),
|
|
120
142
|
...(strict !== undefined ? { strict } : {}),
|
|
@@ -129,10 +151,13 @@ export const buildSchema = async (rootSchema, rootTypeName, extensions, typesOnl
|
|
|
129
151
|
// typesOnly skips parser generation entirely, so no runtime helpers are needed.
|
|
130
152
|
if (helpersMode === 'embedded' && !typesOnly) {
|
|
131
153
|
for (const helper of usedHelpers) {
|
|
132
|
-
const { ext, content } = await readHelperSource(helper);
|
|
154
|
+
const { ext, content } = await readHelperSource(helper, importExt);
|
|
133
155
|
files.push({ filename: `_helpers/${helper}.${ext}`, content });
|
|
134
156
|
}
|
|
135
157
|
}
|
|
136
|
-
files.push({
|
|
158
|
+
files.push({
|
|
159
|
+
filename: 'index.ts',
|
|
160
|
+
content: generateIndexBarrel(files, { typesOnly: typesOnly ?? false, importExt }),
|
|
161
|
+
});
|
|
137
162
|
return files;
|
|
138
163
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
2
|
import { type HelpersMode, type RuntimeHelperName } from '../helpers/collect-helpers.js';
|
|
3
|
+
import { type ImportExtension } from '../helpers/collect-imports.js';
|
|
3
4
|
/**
|
|
4
5
|
* Options for controlling what gets generated in a file.
|
|
5
6
|
*/
|
|
@@ -63,6 +64,13 @@ type GenerateFileOptions = {
|
|
|
63
64
|
* Defaults to `''` (no suffix). Set to e.g. `'Object'` to emit `ContactObject`.
|
|
64
65
|
*/
|
|
65
66
|
readonly typeSuffix?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Extension used on every relative import specifier (cross-file `$ref`
|
|
69
|
+
* imports and embedded-helper imports). Defaults to `'js'` (the TS NodeNext
|
|
70
|
+
* form); `'ts'` makes the generated sources runnable under Node's type
|
|
71
|
+
* stripping.
|
|
72
|
+
*/
|
|
73
|
+
readonly importExt?: ImportExtension;
|
|
66
74
|
};
|
|
67
75
|
/** Result of generating a single parser file. */
|
|
68
76
|
export type GeneratedFileContent = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateTypeDefinition } from '@amritk/helpers/generate-type-definition';
|
|
2
2
|
import { collectHelpers, } from '../helpers/collect-helpers.js';
|
|
3
|
-
import { collectImports } from '../helpers/collect-imports.js';
|
|
3
|
+
import { collectImports, collectImportTypeNames } from '../helpers/collect-imports.js';
|
|
4
4
|
import { generateParserFunction, generateShapeValidator } from './generate-parser-function.js';
|
|
5
5
|
/**
|
|
6
6
|
* Generates a complete TypeScript file from a JSON Schema.
|
|
@@ -37,10 +37,11 @@ export const generateFile = (schema, typeName, options) => {
|
|
|
37
37
|
const rootSchema = options?.rootSchema;
|
|
38
38
|
const helpersMode = options?.helpersMode ?? 'package';
|
|
39
39
|
const typeSuffix = options?.typeSuffix ?? '';
|
|
40
|
+
const importExt = options?.importExt ?? 'js';
|
|
40
41
|
const typeDefinition = generateTypeDefinition(schema, typeName, { readonly: options?.readonly ?? false, typeSuffix });
|
|
41
42
|
if (typesOnly) {
|
|
42
43
|
// In types-only mode, skip the parser function and use type-only imports
|
|
43
|
-
const imports = collectImports(schema, { typesOnly: true, selfRef, rootSchema, typeSuffix });
|
|
44
|
+
const imports = collectImports(schema, { typesOnly: true, selfRef, rootSchema, typeSuffix, importExt });
|
|
44
45
|
let result = '';
|
|
45
46
|
if (imports.length > 0) {
|
|
46
47
|
result += imports[0];
|
|
@@ -52,18 +53,24 @@ export const generateFile = (schema, typeName, options) => {
|
|
|
52
53
|
return { content: result + typeDefinition, usedHelpers: new Set() };
|
|
53
54
|
}
|
|
54
55
|
const stripUnknown = options?.stripUnknown ?? false;
|
|
56
|
+
// Names this file imports for its $refs: synthesized private sub-type names
|
|
57
|
+
// dedup against them so they can never shadow an imported identifier. The
|
|
58
|
+
// parser and the shape validator must receive the SAME set — their derived
|
|
59
|
+
// sub-names have to agree.
|
|
60
|
+
const reservedNames = collectImportTypeNames(schema, { selfRef, rootSchema, typeSuffix });
|
|
55
61
|
const parserFunction = generateParserFunction(schema, typeName, {
|
|
56
62
|
useRefImports: true,
|
|
57
63
|
typeSuffix,
|
|
64
|
+
reservedNames,
|
|
58
65
|
...(rootSchema !== undefined ? { rootSchema } : {}),
|
|
59
66
|
...(options?.logWarnings !== undefined ? { logWarnings: options.logWarnings } : {}),
|
|
60
67
|
...(options?.strict !== undefined ? { strict: options.strict } : {}),
|
|
61
68
|
...(options?.stripUnknown !== undefined ? { stripUnknown: options.stripUnknown } : {}),
|
|
62
69
|
});
|
|
63
|
-
const shapeValidator = generateShapeValidator(schema, typeName, true, typeSuffix, true, stripUnknown);
|
|
70
|
+
const shapeValidator = generateShapeValidator(schema, typeName, true, typeSuffix, true, stripUnknown, reservedNames);
|
|
64
71
|
const combinedFunctions = `${shapeValidator}\n\n${parserFunction}`;
|
|
65
|
-
const helpers = collectHelpers(combinedFunctions, helpersMode, options?.helpersImportPrefix);
|
|
66
|
-
const imports = [...collectImports(schema, { selfRef, rootSchema, typeSuffix }), ...helpers.imports];
|
|
72
|
+
const helpers = collectHelpers(combinedFunctions, helpersMode, options?.helpersImportPrefix, importExt);
|
|
73
|
+
const imports = [...collectImports(schema, { selfRef, rootSchema, typeSuffix, importExt }), ...helpers.imports];
|
|
67
74
|
// Build file output using string concatenation instead of array join for performance
|
|
68
75
|
let result = '';
|
|
69
76
|
if (imports.length > 0) {
|
|
@@ -42,6 +42,14 @@ type GenerateParserOptions = {
|
|
|
42
42
|
* dispatch to the branch parsers, instead of falling back to a passthrough cast.
|
|
43
43
|
*/
|
|
44
44
|
readonly rootSchema?: Record<string, unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* Type names the generated file imports for its `$ref`s. Synthesized private
|
|
47
|
+
* sub-type names (nested objects, array items, the root array's `{Type}Item`)
|
|
48
|
+
* dedup against this set so they can never shadow an imported identifier —
|
|
49
|
+
* which would both fail to compile (duplicate declaration) and silently
|
|
50
|
+
* validate against the wrong schema.
|
|
51
|
+
*/
|
|
52
|
+
readonly reservedNames?: ReadonlySet<string>;
|
|
45
53
|
};
|
|
46
54
|
/**
|
|
47
55
|
* Generates a safe parser as an arrow function that never throws.
|
|
@@ -71,5 +79,5 @@ export declare const shapeValidatorFunctionName: (typeName: string) => string;
|
|
|
71
79
|
* in additionalProperties). The stub is safe: parents calling it will fall
|
|
72
80
|
* through to the slow path, matching the pre-deep-fast-path behavior.
|
|
73
81
|
*/
|
|
74
|
-
export declare const generateShapeValidator: (schema: JSONSchema, typeName: string, useRefImports: boolean, suffix?: string, exported?: boolean, stripUnknown?: boolean) => string;
|
|
82
|
+
export declare const generateShapeValidator: (schema: JSONSchema, typeName: string, useRefImports: boolean, suffix?: string, exported?: boolean, stripUnknown?: boolean, reservedNames?: ReadonlySet<string>) => string;
|
|
75
83
|
export {};
|