@amritk/generate-examples 0.5.1 → 0.5.3
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/LICENSE +21 -0
- package/dist/generators/build-schema.js +23 -50
- package/dist/generators/collect-example-imports.js +64 -94
- package/dist/generators/derive-example.js +507 -680
- package/dist/generators/find-schema-cycles.js +91 -115
- package/dist/generators/generate-arbitrary.js +316 -471
- package/dist/generators/generate-files.js +28 -45
- package/dist/generators/schema-validation.js +55 -78
- package/dist/index.js +10 -3
- package/package.json +4 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 amritk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,52 +1,25 @@
|
|
|
1
|
-
import { generateIndexBarrel } from
|
|
2
|
-
import { walkRefGraph } from
|
|
3
|
-
import { findSchemaCycles } from
|
|
4
|
-
import { generateExampleFile } from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
* @param rootSchema - The root JSON Schema to build from
|
|
19
|
-
* @param rootTypeName - The name for the root type (e.g. "Document")
|
|
20
|
-
* @param typeSuffix - Suffix appended to every `$ref`-derived name (default `''`)
|
|
21
|
-
* @returns An array of generated TypeScript files
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```typescript
|
|
25
|
-
* const files = await buildExampleSchema(schema, 'Document')
|
|
26
|
-
* // files → [{ filename: 'document.ts', content: '...' }, { filename: 'index.ts', ... }]
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export const buildExampleSchema = async (rootSchema, rootTypeName, typeSuffix = '') => {
|
|
30
|
-
const files = [];
|
|
31
|
-
// Cross-file `$ref` cycles (A→B→A across modules) must emit lazy references
|
|
32
|
-
// for their cycle edges; eager top-level references would crash with a
|
|
33
|
-
// circular-ESM TDZ error at import. Detect the cycles up front so each file
|
|
34
|
-
// knows which siblings to defer.
|
|
35
|
-
const cycles = findSchemaCycles(rootSchema, rootTypeName, typeSuffix);
|
|
36
|
-
walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
|
|
37
|
-
// `index` is reserved for the barrel below, so never let a definition of
|
|
38
|
-
// that name overwrite it.
|
|
39
|
-
if (node.filename === 'index')
|
|
40
|
-
return;
|
|
41
|
-
const lazyRefFilenames = cycles.get(node.filename);
|
|
42
|
-
const content = generateExampleFile(node.schema, node.typeName, {
|
|
43
|
-
rootSchema: node.rootSchema,
|
|
44
|
-
typeSuffix,
|
|
45
|
-
...(node.ref !== undefined ? { selfRef: node.ref } : {}),
|
|
46
|
-
...(lazyRefFilenames !== undefined ? { lazyRefFilenames } : {}),
|
|
47
|
-
});
|
|
48
|
-
files.push({ filename: `${node.filename}.ts`, content });
|
|
1
|
+
import { generateIndexBarrel } from "@amritk/helpers/generate-index-barrel";
|
|
2
|
+
import { walkRefGraph } from "@amritk/helpers/walk-ref-graph";
|
|
3
|
+
import { findSchemaCycles } from "./find-schema-cycles.js";
|
|
4
|
+
import { generateExampleFile } from "./generate-files.js";
|
|
5
|
+
const buildExampleSchema = async (rootSchema, rootTypeName, typeSuffix = "") => {
|
|
6
|
+
const files = [];
|
|
7
|
+
const cycles = findSchemaCycles(rootSchema, rootTypeName, typeSuffix);
|
|
8
|
+
walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
|
|
9
|
+
if (node.filename === "index")
|
|
10
|
+
return;
|
|
11
|
+
const lazyRefFilenames = cycles.get(node.filename);
|
|
12
|
+
const content = generateExampleFile(node.schema, node.typeName, {
|
|
13
|
+
rootSchema: node.rootSchema,
|
|
14
|
+
typeSuffix,
|
|
15
|
+
...node.ref !== void 0 ? { selfRef: node.ref } : {},
|
|
16
|
+
...lazyRefFilenames !== void 0 ? { lazyRefFilenames } : {}
|
|
49
17
|
});
|
|
50
|
-
files.push({ filename:
|
|
51
|
-
|
|
18
|
+
files.push({ filename: `${node.filename}.ts`, content });
|
|
19
|
+
});
|
|
20
|
+
files.push({ filename: "index.ts", content: generateIndexBarrel(files) });
|
|
21
|
+
return files;
|
|
22
|
+
};
|
|
23
|
+
export {
|
|
24
|
+
buildExampleSchema
|
|
52
25
|
};
|
|
@@ -1,100 +1,70 @@
|
|
|
1
|
-
import { refToFilename } from
|
|
2
|
-
import { refToName } from
|
|
3
|
-
import { resolveRef } from
|
|
4
|
-
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasOneOf, hasProperties, hasRef, isSchemaObject
|
|
5
|
-
/**
|
|
6
|
-
* Generates an import statement for a single $ref, importing both the generated
|
|
7
|
-
* type and its arbitrary from the ref's generated file.
|
|
8
|
-
*/
|
|
1
|
+
import { refToFilename } from "@amritk/helpers/ref-to-filename";
|
|
2
|
+
import { refToName } from "@amritk/helpers/ref-to-name";
|
|
3
|
+
import { resolveRef } from "@amritk/helpers/resolve-ref";
|
|
4
|
+
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasOneOf, hasProperties, hasRef, isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
9
5
|
const buildImport = (ref, suffix) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return `import { type ${typeName}, ${typeName}Arbitrary } from './${filename}.js'`;
|
|
6
|
+
const filename = refToFilename(ref);
|
|
7
|
+
const typeName = refToName(ref, suffix);
|
|
8
|
+
return `import { type ${typeName}, ${typeName}Arbitrary } from './${filename}.js'`;
|
|
14
9
|
};
|
|
15
|
-
/**
|
|
16
|
-
* Recursively collects every `$ref` string reachable through the schema surface
|
|
17
|
-
* that the type and arbitrary generators traverse. A generated file's single
|
|
18
|
-
* import block must cover every ref those generators emit, so this walks the
|
|
19
|
-
* *same* nested surface `arbitraryExpr` (generate-arbitrary.ts) descends into —
|
|
20
|
-
* combinator branches, object `properties`/`patternProperties`/
|
|
21
|
-
* `additionalProperties`, and array `items`/`prefixItems` (both the single-schema
|
|
22
|
-
* and tuple array forms) — not just the top level. Missing any of these emits a
|
|
23
|
-
* bare `XxxArbitrary` identifier (or a bare `Xxx` type) with no matching import,
|
|
24
|
-
* producing TypeScript that fails to compile.
|
|
25
|
-
*
|
|
26
|
-
* `$ref` nodes short-circuit (matching `arbitraryExpr`, which resolves a `$ref`
|
|
27
|
-
* and ignores sibling keywords), so recursion is bounded by the schema's own
|
|
28
|
-
* structural nesting and cannot loop on a self-referential ref.
|
|
29
|
-
*/
|
|
30
10
|
const collectRefs = (schema) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return refs;
|
|
11
|
+
if (!isSchemaObject(schema))
|
|
12
|
+
return [];
|
|
13
|
+
if (hasRef(schema))
|
|
14
|
+
return [schema.$ref];
|
|
15
|
+
const refs = [];
|
|
16
|
+
const visit = (sub) => {
|
|
17
|
+
refs.push(...collectRefs(sub));
|
|
18
|
+
};
|
|
19
|
+
if (hasOneOf(schema))
|
|
20
|
+
schema.oneOf.forEach(visit);
|
|
21
|
+
if (hasAnyOf(schema))
|
|
22
|
+
schema.anyOf.forEach(visit);
|
|
23
|
+
if (hasAllOf(schema))
|
|
24
|
+
schema.allOf.forEach(visit);
|
|
25
|
+
if (hasProperties(schema))
|
|
26
|
+
Object.values(schema.properties).forEach(visit);
|
|
27
|
+
const raw = schema;
|
|
28
|
+
const patternProperties = raw["patternProperties"];
|
|
29
|
+
if (typeof patternProperties === "object" && patternProperties !== null) {
|
|
30
|
+
Object.values(patternProperties).forEach(visit);
|
|
31
|
+
}
|
|
32
|
+
if (hasAdditionalProperties(schema) && isSchemaObject(schema.additionalProperties)) {
|
|
33
|
+
visit(schema.additionalProperties);
|
|
34
|
+
}
|
|
35
|
+
const prefixItems = raw["prefixItems"];
|
|
36
|
+
if (Array.isArray(prefixItems))
|
|
37
|
+
prefixItems.forEach(visit);
|
|
38
|
+
const items = raw["items"];
|
|
39
|
+
if (Array.isArray(items))
|
|
40
|
+
items.forEach(visit);
|
|
41
|
+
else if (isSchemaObject(items))
|
|
42
|
+
visit(items);
|
|
43
|
+
return refs;
|
|
65
44
|
};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const imports = [];
|
|
84
|
-
for (const ref of refs) {
|
|
85
|
-
const filename = refToFilename(ref);
|
|
86
|
-
if (seen.has(filename))
|
|
87
|
-
continue;
|
|
88
|
-
if (selfFilename && filename === selfFilename)
|
|
89
|
-
continue;
|
|
90
|
-
// Skip refs that don't resolve in this schema (external / never generated)
|
|
91
|
-
if (rootSchema) {
|
|
92
|
-
const resolved = resolveRef(ref, rootSchema);
|
|
93
|
-
if (!resolved)
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
seen.add(filename);
|
|
97
|
-
imports.push(buildImport(ref, typeSuffix));
|
|
45
|
+
const collectExampleImports = (schema, options) => {
|
|
46
|
+
const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : null;
|
|
47
|
+
const rootSchema = options?.rootSchema;
|
|
48
|
+
const typeSuffix = options?.typeSuffix ?? "";
|
|
49
|
+
const refs = collectRefs(schema);
|
|
50
|
+
const seen = /* @__PURE__ */ new Set();
|
|
51
|
+
const imports = [];
|
|
52
|
+
for (const ref of refs) {
|
|
53
|
+
const filename = refToFilename(ref);
|
|
54
|
+
if (seen.has(filename))
|
|
55
|
+
continue;
|
|
56
|
+
if (selfFilename && filename === selfFilename)
|
|
57
|
+
continue;
|
|
58
|
+
if (rootSchema) {
|
|
59
|
+
const resolved = resolveRef(ref, rootSchema);
|
|
60
|
+
if (!resolved)
|
|
61
|
+
continue;
|
|
98
62
|
}
|
|
99
|
-
|
|
63
|
+
seen.add(filename);
|
|
64
|
+
imports.push(buildImport(ref, typeSuffix));
|
|
65
|
+
}
|
|
66
|
+
return imports;
|
|
67
|
+
};
|
|
68
|
+
export {
|
|
69
|
+
collectExampleImports
|
|
100
70
|
};
|