@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 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 '@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
- /**
6
- * Builds all TypeScript example files from a JSON Schema by traversing all
7
- * `$ref` / `$dynamicRef` references recursively (via the shared
8
- * `@amritk/helpers/walk-ref-graph` walker).
9
- *
10
- * Each generated file exports:
11
- * - A TypeScript type definition
12
- * - A `fast-check` arbitrary (`FooArbitrary`) that produces schema-valid values
13
- * - A concrete example value (`fooExample`)
14
- *
15
- * An `index.ts` re-exports everything. The generated output imports `fast-check`,
16
- * which consumers must install as a (dev) dependency.
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: 'index.ts', content: generateIndexBarrel(files) });
51
- return files;
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 '@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';
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
- const filename = refToFilename(ref);
11
- const typeName = refToName(ref, suffix);
12
- // `.js` extension so the emitted import resolves under Node ESM, not only Bun.
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
- if (!isSchemaObject(schema))
32
- return [];
33
- if (hasRef(schema))
34
- return [schema.$ref];
35
- const refs = [];
36
- const visit = (sub) => {
37
- refs.push(...collectRefs(sub));
38
- };
39
- if (hasOneOf(schema))
40
- schema.oneOf.forEach(visit);
41
- if (hasAnyOf(schema))
42
- schema.anyOf.forEach(visit);
43
- if (hasAllOf(schema))
44
- schema.allOf.forEach(visit);
45
- if (hasProperties(schema))
46
- Object.values(schema.properties).forEach(visit);
47
- const raw = schema;
48
- const patternProperties = raw['patternProperties'];
49
- if (typeof patternProperties === 'object' && patternProperties !== null) {
50
- Object.values(patternProperties).forEach(visit);
51
- }
52
- if (hasAdditionalProperties(schema) && isSchemaObject(schema.additionalProperties)) {
53
- visit(schema.additionalProperties);
54
- }
55
- const prefixItems = raw['prefixItems'];
56
- if (Array.isArray(prefixItems))
57
- prefixItems.forEach(visit);
58
- const items = raw['items'];
59
- // `items` is either a tuple (draft-07 array form) or a single item schema.
60
- if (Array.isArray(items))
61
- items.forEach(visit);
62
- else if (isSchemaObject(items))
63
- visit(items);
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
- * Collects import statements for all $ref dependencies of a schema. Each import
68
- * brings in both the generated TypeScript type and the arbitrary for that ref.
69
- *
70
- * @example
71
- * ```typescript
72
- * const schema = { properties: { address: { $ref: '#/$defs/address' } } }
73
- * collectExampleImports(schema)
74
- * // ["import { type Address, AddressArbitrary } from './address'"]
75
- * ```
76
- */
77
- export const collectExampleImports = (schema, options) => {
78
- const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : null;
79
- const rootSchema = options?.rootSchema;
80
- const typeSuffix = options?.typeSuffix ?? '';
81
- const refs = collectRefs(schema);
82
- const seen = new Set();
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
- return imports;
63
+ seen.add(filename);
64
+ imports.push(buildImport(ref, typeSuffix));
65
+ }
66
+ return imports;
67
+ };
68
+ export {
69
+ collectExampleImports
100
70
  };