@amritk/generate-examples 0.2.2 → 0.3.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 CHANGED
@@ -120,13 +120,15 @@ const res = await fetch('/users', { method: 'POST', body: JSON.stringify(userExa
120
120
 
121
121
  ## Supported keywords
122
122
 
123
- `type` (string/number/integer/boolean/null/array/object), `properties`,
123
+ `type` — including multi-type unions like `['string', 'null']`
124
+ (string/number/integer/boolean/null/array/object), `properties`,
124
125
  `required`, `items`, `minItems`/`maxItems`, `uniqueItems`,
125
126
  `minLength`/`maxLength`, `pattern`, `format` (`email`, `uuid`, `uri`/`url`,
126
- `date`, `date-time`), `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,
127
- `multipleOf`, `enum`, `const`, `oneOf`/`anyOf`, `$ref`, and the `x-mjst`
128
- extension (`Date`, `bigint`). Unsupported constructs degrade to `fc.anything()`
129
- in arbitraries and `null` in static examples.
127
+ `date`, `date-time`, `time`, `hostname`, `ipv4`, `ipv6`), `minimum`/`maximum`,
128
+ `exclusiveMinimum`/`exclusiveMaximum`, `multipleOf`, `enum`, `const`,
129
+ `oneOf`/`anyOf`, `$ref`, and the `x-mjst` extension (`Date`, `bigint`).
130
+ Unsupported constructs degrade to `fc.anything()` in arbitraries and `null` in
131
+ static examples.
130
132
 
131
133
  > [!TIP]
132
134
  > A static example constrained only by `pattern` is not guaranteed to match the
@@ -31,4 +31,3 @@ export type GeneratedFile = {
31
31
  * ```
32
32
  */
33
33
  export declare const buildExampleSchema: (rootSchema: JSONSchema, rootTypeName: string, typeSuffix?: string) => Promise<GeneratedFile[]>;
34
- //# sourceMappingURL=build-schema.d.ts.map
@@ -32,4 +32,3 @@ type CollectExampleImportsOptions = {
32
32
  */
33
33
  export declare const collectExampleImports: (schema: JSONSchema, options?: CollectExampleImportsOptions) => string[];
34
34
  export {};
35
- //# sourceMappingURL=collect-example-imports.d.ts.map
@@ -27,4 +27,3 @@ export declare const serializeValue: (value: unknown) => string;
27
27
  * ```
28
28
  */
29
29
  export declare const generateExampleConst: (schema: JSONSchema, typeName: string, rootSchema?: Record<string, unknown>) => string;
30
- //# sourceMappingURL=derive-example.d.ts.map
@@ -20,6 +20,14 @@ const exampleString = (schema) => {
20
20
  return '1970-01-01T00:00:00.000Z';
21
21
  case 'date':
22
22
  return '1970-01-01';
23
+ case 'time':
24
+ return '00:00:00.000Z';
25
+ case 'hostname':
26
+ return 'example.com';
27
+ case 'ipv4':
28
+ return '127.0.0.1';
29
+ case 'ipv6':
30
+ return '::1';
23
31
  }
24
32
  }
25
33
  let value = 'string';
@@ -70,11 +78,18 @@ export const deriveExample = (schema, rootSchema, seen = new Set()) => {
70
78
  return deriveExample(schema.oneOf[0], rootSchema, seen);
71
79
  if (hasAnyOf(schema) && schema.anyOf[0] !== undefined)
72
80
  return deriveExample(schema.anyOf[0], rootSchema, seen);
73
- // `hasType` only matches a single string `type`; multi-type schemas fall
74
- // through to `null`.
75
- if (!hasType(schema))
76
- return null;
77
- switch (schema.type) {
81
+ if (hasType(schema))
82
+ return deriveForType(schema.type, schema, rootSchema, seen);
83
+ // Multi-type schemas (`type: ['string', 'null']`) derive from their first
84
+ // member type; `hasType` only matches a single string `type`.
85
+ if (Array.isArray(schema.type) && schema.type.length > 0) {
86
+ return deriveForType(schema.type[0], schema, rootSchema, seen);
87
+ }
88
+ return null;
89
+ };
90
+ /** Derives a canonical value for a single declared `type`. */
91
+ const deriveForType = (type, schema, rootSchema, seen) => {
92
+ switch (type) {
78
93
  case 'string':
79
94
  return exampleString(schema);
80
95
  case 'number':
@@ -9,4 +9,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
9
9
  * ```
10
10
  */
11
11
  export declare const generateArbitrary: (schema: JSONSchema, typeName: string, suffix?: string) => string;
12
- //# sourceMappingURL=generate-arbitrary.d.ts.map
@@ -21,6 +21,14 @@ const stringExpr = (schema) => {
21
21
  return 'fc.date({ noInvalidDate: true }).map((d) => d.toISOString())';
22
22
  case 'date':
23
23
  return 'fc.date({ noInvalidDate: true }).map((d) => d.toISOString().slice(0, 10))';
24
+ case 'time':
25
+ return 'fc.date({ noInvalidDate: true }).map((d) => d.toISOString().slice(11))';
26
+ case 'hostname':
27
+ return 'fc.domain()';
28
+ case 'ipv4':
29
+ return 'fc.ipV4()';
30
+ case 'ipv6':
31
+ return 'fc.ipV6()';
24
32
  }
25
33
  }
26
34
  if (hasPattern(schema))
@@ -144,10 +152,14 @@ const arbitraryExpr = (schema, suffix) => {
144
152
  return oneofExpr(schema.oneOf, suffix);
145
153
  if (hasAnyOf(schema))
146
154
  return oneofExpr(schema.anyOf, suffix);
147
- // `hasType` only matches a single string `type`; multi-type schemas
148
- // (`type: ['string', 'null']`) fall through to the permissive fallback.
149
155
  if (hasType(schema))
150
156
  return scalarExpr(schema.type, schema, suffix);
157
+ // Multi-type schemas (`type: ['string', 'null']`) become a oneof over each
158
+ // member type; `hasType` only matches a single string `type`.
159
+ if (Array.isArray(schema.type)) {
160
+ const exprs = schema.type.map((type) => scalarExpr(type, schema, suffix));
161
+ return exprs.length === 1 ? exprs[0] : `fc.oneof(${exprs.join(', ')})`;
162
+ }
151
163
  return 'fc.anything()';
152
164
  };
153
165
  /**
@@ -40,4 +40,3 @@ type GenerateExampleFileOptions = {
40
40
  */
41
41
  export declare const generateExampleFile: (schema: JSONSchema, typeName: string, options?: GenerateExampleFileOptions) => string;
42
42
  export {};
43
- //# sourceMappingURL=generate-files.d.ts.map
package/dist/index.d.ts CHANGED
@@ -2,4 +2,3 @@ export type { GeneratedFile } from './generators/build-schema.js';
2
2
  export { buildExampleSchema } from './generators/build-schema.js';
3
3
  export { deriveExample, generateExampleConst, serializeValue } from './generators/derive-example.js';
4
4
  export { generateArbitrary } from './generators/generate-arbitrary.js';
5
- //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/generate-examples",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Generate fast-check arbitraries and example values from JSON Schemas.",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -26,8 +26,7 @@
26
26
  "url": "https://github.com/amritk/mjst/issues"
27
27
  },
28
28
  "files": [
29
- "dist",
30
- "src"
29
+ "dist"
31
30
  ],
32
31
  "publishConfig": {
33
32
  "access": "public"
@@ -42,7 +41,6 @@
42
41
  },
43
42
  "exports": {
44
43
  ".": {
45
- "development": "./src/index.ts",
46
44
  "default": "./dist/index.js",
47
45
  "types": "./dist/index.d.ts"
48
46
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"build-schema.d.ts","sourceRoot":"","sources":["../../src/generators/build-schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAIjE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,eACjB,UAAU,gBACR,MAAM,0BAEnB,OAAO,CAAC,aAAa,EAAE,CAmBzB,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"collect-example-imports.d.ts","sourceRoot":"","sources":["../../src/generators/collect-example-imports.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE;;GAEG;AACH,KAAK,4BAA4B,GAAG;IAClC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACzD;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AA0DD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,WAAY,UAAU,YAAY,4BAA4B,KAAG,MAAM,EA0BxG,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"derive-example.d.ts","sourceRoot":"","sources":["../../src/generators/derive-example.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAgCjE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,WAChB,UAAU,eACL,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAC9B,WAAW,CAAC,MAAM,CAAC,KACxB,OAuDF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,UAAW,OAAO,KAAG,MAW/C,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,WACvB,UAAU,YACR,MAAM,eACH,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACnC,MAGF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-arbitrary.d.ts","sourceRoot":"","sources":["../../src/generators/generate-arbitrary.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AA2JjE;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,WAAY,UAAU,YAAY,MAAM,sBAAgB,MAGrF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-files.d.ts","sourceRoot":"","sources":["../../src/generators/generate-files.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAMjE;;GAEG;AACH,KAAK,0BAA0B,GAAG;IAChC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7C;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,mBAAmB,WACtB,UAAU,YACR,MAAM,YACN,0BAA0B,KACnC,MAsBF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjG,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA"}
@@ -1,65 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { buildExampleSchema, type GeneratedFile } from './build-schema'
4
-
5
- /** Returns the content of a generated file, failing the test if it is absent. */
6
- const contentOf = (files: GeneratedFile[], filename: string): string => {
7
- const file = files.find((f) => f.filename === filename)
8
- if (!file) throw new Error(`expected a generated file named ${filename}`)
9
- return file.content
10
- }
11
-
12
- describe('buildExampleSchema', () => {
13
- it('emits a file per schema and an index barrel', async () => {
14
- const schema = {
15
- type: 'object' as const,
16
- properties: { name: { type: 'string' as const } },
17
- required: ['name'],
18
- }
19
- const files = await buildExampleSchema(schema, 'User')
20
- const names = files.map((f) => f.filename)
21
-
22
- expect(names).toContain('user.ts')
23
- expect(names).toContain('index.ts')
24
-
25
- const user = contentOf(files, 'user.ts')
26
- expect(user).toContain("import * as fc from 'fast-check'")
27
- expect(user).toContain('export type User =')
28
- expect(user).toContain('export const UserArbitrary: fc.Arbitrary<User> =')
29
- expect(user).toContain('export const userExample: User =')
30
- })
31
-
32
- it('follows $refs into their own files and imports their arbitraries', async () => {
33
- const schema = {
34
- type: 'object' as const,
35
- properties: { address: { $ref: '#/$defs/address' } },
36
- required: ['address'],
37
- $defs: {
38
- address: {
39
- type: 'object' as const,
40
- properties: { city: { type: 'string' as const } },
41
- required: ['city'],
42
- },
43
- },
44
- }
45
- const files = await buildExampleSchema(schema, 'User')
46
- const names = files.map((f) => f.filename)
47
-
48
- expect(names).toContain('address.ts')
49
-
50
- const user = contentOf(files, 'user.ts')
51
- expect(user).toContain("import { type Address, AddressArbitrary } from './address'")
52
- expect(user).toContain('"address": AddressArbitrary')
53
-
54
- // The concrete example inlines the ref's value rather than referencing a const.
55
- expect(user).toContain('"address": { "city": "string" }')
56
- })
57
-
58
- it('re-exports types, arbitraries and examples from the index barrel', async () => {
59
- const schema = { type: 'object' as const, properties: { id: { type: 'string' as const } } }
60
- const files = await buildExampleSchema(schema, 'Thing')
61
- const index = contentOf(files, 'index.ts')
62
-
63
- expect(index).toContain("export { type Thing, ThingArbitrary, thingExample } from './thing';")
64
- })
65
- })
@@ -1,62 +0,0 @@
1
- import { generateIndexBarrel } from '@amritk/helpers/generate-index-barrel'
2
- import { walkRefGraph } from '@amritk/helpers/walk-ref-graph'
3
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
4
-
5
- import { generateExampleFile } from './generate-files'
6
-
7
- /**
8
- * Represents a generated TypeScript file with its filename and content.
9
- */
10
- export type GeneratedFile = {
11
- filename: string
12
- content: string
13
- }
14
-
15
- /**
16
- * Builds all TypeScript example files from a JSON Schema by traversing all
17
- * `$ref` / `$dynamicRef` references recursively (via the shared
18
- * `@amritk/helpers/walk-ref-graph` walker).
19
- *
20
- * Each generated file exports:
21
- * - A TypeScript type definition
22
- * - A `fast-check` arbitrary (`FooArbitrary`) that produces schema-valid values
23
- * - A concrete example value (`fooExample`)
24
- *
25
- * An `index.ts` re-exports everything. The generated output imports `fast-check`,
26
- * which consumers must install as a (dev) dependency.
27
- *
28
- * @param rootSchema - The root JSON Schema to build from
29
- * @param rootTypeName - The name for the root type (e.g. "Document")
30
- * @param typeSuffix - Suffix appended to every `$ref`-derived name (default `''`)
31
- * @returns An array of generated TypeScript files
32
- *
33
- * @example
34
- * ```typescript
35
- * const files = await buildExampleSchema(schema, 'Document')
36
- * // files → [{ filename: 'document.ts', content: '...' }, { filename: 'index.ts', ... }]
37
- * ```
38
- */
39
- export const buildExampleSchema = async (
40
- rootSchema: JSONSchema,
41
- rootTypeName: string,
42
- typeSuffix = '',
43
- ): Promise<GeneratedFile[]> => {
44
- const files: GeneratedFile[] = []
45
-
46
- walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
47
- // `index` is reserved for the barrel below, so never let a definition of
48
- // that name overwrite it.
49
- if (node.filename === 'index') return
50
-
51
- const content = generateExampleFile(node.schema, node.typeName, {
52
- rootSchema: node.rootSchema,
53
- typeSuffix,
54
- ...(node.ref !== undefined ? { selfRef: node.ref } : {}),
55
- })
56
- files.push({ filename: `${node.filename}.ts`, content })
57
- })
58
-
59
- files.push({ filename: 'index.ts', content: generateIndexBarrel(files) })
60
-
61
- return files
62
- }
@@ -1,121 +0,0 @@
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, hasItems, hasOneOf, hasRef } from '@amritk/helpers/schema-guards'
5
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
6
-
7
- /**
8
- * Options for controlling how example imports are collected.
9
- */
10
- type CollectExampleImportsOptions = {
11
- /**
12
- * The $ref path of the schema being generated (e.g. `#/$defs/address`).
13
- * Prevents a file from importing itself.
14
- */
15
- readonly selfRef?: string | undefined
16
- /**
17
- * The root schema document. URI refs that cannot be resolved within it
18
- * are excluded from the import list (they were never generated as files).
19
- */
20
- readonly rootSchema?: Record<string, unknown> | undefined
21
- /**
22
- * Suffix appended to every type/arbitrary name derived from a `$ref`. Must
23
- * match the suffix used when generating the referenced files. Defaults to `''`.
24
- */
25
- readonly typeSuffix?: string
26
- }
27
-
28
- /**
29
- * Generates an import statement for a single $ref, importing both the generated
30
- * type and its arbitrary from the ref's generated file.
31
- */
32
- const buildImport = (ref: string, suffix: string): string => {
33
- const filename = refToFilename(ref)
34
- const typeName = refToName(ref, suffix)
35
- return `import { type ${typeName}, ${typeName}Arbitrary } from './${filename}'`
36
- }
37
-
38
- /**
39
- * Walks one level of the schema and yields all direct $ref strings that should
40
- * become imports: properties, additionalProperties, items, and union branches.
41
- */
42
- const collectDirectRefs = (schema: JSONSchema): string[] => {
43
- if (typeof schema === 'boolean' || schema === null) return []
44
-
45
- const refs: string[] = []
46
-
47
- if (hasRef(schema)) {
48
- refs.push(schema.$ref)
49
- return refs
50
- }
51
-
52
- const propSchemas =
53
- 'properties' in schema && typeof schema.properties === 'object' && schema.properties !== null
54
- ? Object.values(schema.properties as Record<string, JSONSchema>)
55
- : []
56
-
57
- for (const prop of propSchemas) {
58
- if (hasRef(prop)) refs.push((prop as { $ref: string }).$ref)
59
- if (hasItems(prop) && hasRef(prop.items)) refs.push((prop.items as { $ref: string }).$ref)
60
- if (hasAdditionalProperties(prop) && hasRef(prop.additionalProperties as JSONSchema)) {
61
- refs.push((prop.additionalProperties as { $ref: string }).$ref)
62
- }
63
- }
64
-
65
- if (hasItems(schema) && hasRef(schema.items)) {
66
- refs.push((schema.items as { $ref: string }).$ref)
67
- }
68
-
69
- if (hasAdditionalProperties(schema) && hasRef(schema.additionalProperties as JSONSchema)) {
70
- refs.push((schema.additionalProperties as { $ref: string }).$ref)
71
- }
72
-
73
- for (const branch of [
74
- ...(hasOneOf(schema) ? schema.oneOf : []),
75
- ...(hasAnyOf(schema) ? schema.anyOf : []),
76
- ...(hasAllOf(schema) ? schema.allOf : []),
77
- ]) {
78
- if (hasRef(branch)) refs.push((branch as { $ref: string }).$ref)
79
- }
80
-
81
- return refs
82
- }
83
-
84
- /**
85
- * Collects import statements for all $ref dependencies of a schema. Each import
86
- * brings in both the generated TypeScript type and the arbitrary for that ref.
87
- *
88
- * @example
89
- * ```typescript
90
- * const schema = { properties: { address: { $ref: '#/$defs/address' } } }
91
- * collectExampleImports(schema)
92
- * // ["import { type Address, AddressArbitrary } from './address'"]
93
- * ```
94
- */
95
- export const collectExampleImports = (schema: JSONSchema, options?: CollectExampleImportsOptions): string[] => {
96
- const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : null
97
- const rootSchema = options?.rootSchema
98
- const typeSuffix = options?.typeSuffix ?? ''
99
-
100
- const refs = collectDirectRefs(schema)
101
- const seen = new Set<string>()
102
- const imports: string[] = []
103
-
104
- for (const ref of refs) {
105
- const filename = refToFilename(ref)
106
-
107
- if (seen.has(filename)) continue
108
- if (selfFilename && filename === selfFilename) continue
109
-
110
- // Skip refs that don't resolve in this schema (external / never generated)
111
- if (rootSchema) {
112
- const resolved = resolveRef(ref, rootSchema)
113
- if (!resolved) continue
114
- }
115
-
116
- seen.add(filename)
117
- imports.push(buildImport(ref, typeSuffix))
118
- }
119
-
120
- return imports
121
- }
@@ -1,72 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { deriveExample, generateExampleConst, serializeValue } from './derive-example'
4
-
5
- describe('deriveExample', () => {
6
- it('prefers const, then examples, then default, then enum', () => {
7
- expect(deriveExample({ const: 5 })).toBe(5)
8
- expect(deriveExample({ examples: ['a', 'b'] } as never)).toBe('a')
9
- expect(deriveExample({ default: true } as never)).toBe(true)
10
- expect(deriveExample({ enum: ['x', 'y'] })).toBe('x')
11
- })
12
-
13
- it('produces canonical values per type', () => {
14
- expect(deriveExample({ type: 'string' })).toBe('string')
15
- expect(deriveExample({ type: 'integer' })).toBe(0)
16
- expect(deriveExample({ type: 'number', minimum: 3 })).toBe(3)
17
- expect(deriveExample({ type: 'boolean' })).toBe(true)
18
- expect(deriveExample({ type: 'null' })).toBe(null)
19
- })
20
-
21
- it('honours string formats and length', () => {
22
- expect(deriveExample({ type: 'string', format: 'email' })).toBe('user@example.com')
23
- expect(deriveExample({ type: 'string', minLength: 10 })).toHaveLength(10)
24
- })
25
-
26
- it('builds nested objects including all declared properties', () => {
27
- const schema = {
28
- type: 'object' as const,
29
- properties: { id: { type: 'string' as const }, count: { type: 'integer' as const } },
30
- required: ['id'],
31
- }
32
- expect(deriveExample(schema)).toEqual({ id: 'string', count: 0 })
33
- })
34
-
35
- it('builds arrays honouring minItems', () => {
36
- expect(deriveExample({ type: 'array', items: { type: 'string' }, minItems: 2 })).toEqual(['string', 'string'])
37
- })
38
-
39
- it('resolves $ref values against the root schema', () => {
40
- const root = { $defs: { id: { type: 'string', const: 'abc' } } }
41
- expect(deriveExample({ $ref: '#/$defs/id' }, root)).toBe('abc')
42
- })
43
-
44
- it('short-circuits recursive $refs to null', () => {
45
- const root = {
46
- $defs: { node: { type: 'object', properties: { next: { $ref: '#/$defs/node' } } } },
47
- }
48
- expect(deriveExample({ $ref: '#/$defs/node' }, root)).toEqual({ next: null })
49
- })
50
- })
51
-
52
- describe('serializeValue', () => {
53
- it('serializes bigint and Date as runtime expressions', () => {
54
- expect(serializeValue(0n)).toBe('0n')
55
- expect(serializeValue(new Date(0))).toBe('new Date("1970-01-01T00:00:00.000Z")')
56
- })
57
-
58
- it('omits undefined object properties', () => {
59
- expect(serializeValue({ a: 1, b: undefined })).toBe('{ "a": 1 }')
60
- })
61
-
62
- it('serializes nested arrays and objects', () => {
63
- expect(serializeValue({ items: [1, 2] })).toBe('{ "items": [1, 2] }')
64
- })
65
- })
66
-
67
- describe('generateExampleConst', () => {
68
- it('emits a typed const with a derived value', () => {
69
- const schema = { type: 'object' as const, properties: { name: { type: 'string' as const } } }
70
- expect(generateExampleConst(schema, 'Info')).toBe('export const infoExample: Info = { "name": "string" }')
71
- })
72
- })
@@ -1,159 +0,0 @@
1
- import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension'
2
- import { resolveRef } from '@amritk/helpers/resolve-ref'
3
- import {
4
- hasAnyOf,
5
- hasConst,
6
- hasDefault,
7
- hasEnum,
8
- hasExamples,
9
- hasFormat,
10
- hasItems,
11
- hasMaxLength,
12
- hasMinItems,
13
- hasMinimum,
14
- hasMinLength,
15
- hasOneOf,
16
- hasProperties,
17
- hasRef,
18
- hasType,
19
- isSchemaObject,
20
- } from '@amritk/helpers/schema-guards'
21
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
22
-
23
- /** Lowercases the first character of a name. e.g. "User" → "user" */
24
- const lowerFirst = (name: string): string => name.charAt(0).toLowerCase() + name.slice(1)
25
-
26
- /** Derives the example const name from a type name. e.g. "User" → "userExample" */
27
- const exampleName = (typeName: string): string => `${lowerFirst(typeName)}Example`
28
-
29
- /** Returns a representative string honouring `format` and length constraints. */
30
- const exampleString = (schema: JSONSchema): string => {
31
- if (hasFormat(schema)) {
32
- switch (schema.format) {
33
- case 'email':
34
- return 'user@example.com'
35
- case 'uuid':
36
- return '00000000-0000-0000-0000-000000000000'
37
- case 'uri':
38
- case 'url':
39
- return 'https://example.com'
40
- case 'date-time':
41
- return '1970-01-01T00:00:00.000Z'
42
- case 'date':
43
- return '1970-01-01'
44
- }
45
- }
46
-
47
- let value = 'string'
48
- if (hasMinLength(schema) && value.length < schema.minLength) value = value.padEnd(schema.minLength, 'x')
49
- if (hasMaxLength(schema) && value.length > schema.maxLength) value = value.slice(0, schema.maxLength)
50
- return value
51
- }
52
-
53
- /**
54
- * Derives a single concrete, schema-valid value from a JSON Schema.
55
- *
56
- * Prefers explicit hints in this order: `const`, `examples[0]`, `default`,
57
- * `enum[0]`; otherwise produces a canonical value for the declared type.
58
- * `$ref`s are resolved and inlined by value; recursive refs short-circuit to
59
- * `null` (tracked via `seen`).
60
- *
61
- * Note: values constrained only by `pattern` are not guaranteed to match the
62
- * pattern — use the generated arbitrary when pattern fidelity matters.
63
- */
64
- export const deriveExample = (
65
- schema: JSONSchema,
66
- rootSchema?: Record<string, unknown>,
67
- seen: ReadonlySet<string> = new Set(),
68
- ): unknown => {
69
- if (!isSchemaObject(schema)) return null
70
-
71
- if (hasConst(schema)) return schema.const
72
- if (hasExamples(schema) && Array.isArray(schema.examples) && schema.examples.length > 0) return schema.examples[0]
73
- if (hasDefault(schema)) return schema.default
74
- if (hasEnum(schema) && schema.enum.length > 0) return schema.enum[0]
75
-
76
- if (hasRef(schema)) {
77
- const ref = schema.$ref
78
- if (seen.has(ref) || !rootSchema) return null
79
- const resolved = resolveRef(ref, rootSchema)
80
- if (!resolved) return null
81
- return deriveExample(resolved as JSONSchema, rootSchema, new Set([...seen, ref]))
82
- }
83
-
84
- const instanceOf = getMjstInstanceOf(schema)
85
- if (instanceOf === 'Date') return new Date(0)
86
- const primitive = getMjstPrimitive(schema)
87
- if (primitive === 'bigint') return 0n
88
-
89
- if (hasOneOf(schema) && schema.oneOf[0] !== undefined) return deriveExample(schema.oneOf[0], rootSchema, seen)
90
- if (hasAnyOf(schema) && schema.anyOf[0] !== undefined) return deriveExample(schema.anyOf[0], rootSchema, seen)
91
-
92
- // `hasType` only matches a single string `type`; multi-type schemas fall
93
- // through to `null`.
94
- if (!hasType(schema)) return null
95
-
96
- switch (schema.type) {
97
- case 'string':
98
- return exampleString(schema)
99
- case 'number':
100
- case 'integer':
101
- return hasMinimum(schema) ? schema.minimum : 0
102
- case 'boolean':
103
- return true
104
- case 'null':
105
- return null
106
- case 'array': {
107
- const item = hasItems(schema) ? deriveExample(schema.items, rootSchema, seen) : null
108
- const count = hasMinItems(schema) ? Math.max(schema.minItems, 1) : 1
109
- return Array.from({ length: count }, () => item)
110
- }
111
- case 'object': {
112
- const out: Record<string, unknown> = {}
113
- if (hasProperties(schema)) {
114
- for (const [key, propSchema] of Object.entries(schema.properties)) {
115
- out[key] = deriveExample(propSchema, rootSchema, seen)
116
- }
117
- }
118
- return out
119
- }
120
- default:
121
- return null
122
- }
123
- }
124
-
125
- /**
126
- * Serializes a derived value into a TypeScript source expression. Handles the
127
- * non-JSON values `deriveExample` can produce (`Date`, `bigint`) in addition to
128
- * plain JSON.
129
- */
130
- export const serializeValue = (value: unknown): string => {
131
- if (typeof value === 'bigint') return `${value}n`
132
- if (value instanceof Date) return `new Date(${JSON.stringify(value.toISOString())})`
133
- if (Array.isArray(value)) return `[${value.map(serializeValue).join(', ')}]`
134
- if (value !== null && typeof value === 'object') {
135
- const entries = Object.entries(value)
136
- .filter(([, v]) => v !== undefined)
137
- .map(([key, v]) => `${JSON.stringify(key)}: ${serializeValue(v)}`)
138
- return `{ ${entries.join(', ')} }`
139
- }
140
- return JSON.stringify(value)
141
- }
142
-
143
- /**
144
- * Generates an exported const holding a concrete, schema-valid example value.
145
- *
146
- * @example
147
- * ```typescript
148
- * generateExampleConst({ type: 'object', properties: { name: { type: 'string' } } }, 'Info')
149
- * // export const infoExample: Info = { "name": "string" }
150
- * ```
151
- */
152
- export const generateExampleConst = (
153
- schema: JSONSchema,
154
- typeName: string,
155
- rootSchema?: Record<string, unknown>,
156
- ): string => {
157
- const value = deriveExample(schema, rootSchema)
158
- return `export const ${exampleName(typeName)}: ${typeName} = ${serializeValue(value)}`
159
- }
@@ -1,102 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { generateArbitrary } from './generate-arbitrary'
4
-
5
- describe('generate-arbitrary', () => {
6
- it('generates a record arbitrary for an object schema', () => {
7
- const schema = {
8
- type: 'object' as const,
9
- properties: { name: { type: 'string' as const }, age: { type: 'integer' as const } },
10
- required: ['name'],
11
- }
12
- const code = generateArbitrary(schema, 'User')
13
-
14
- expect(code).toContain('export const UserArbitrary: fc.Arbitrary<User> =')
15
- expect(code).toContain('fc.record(')
16
- expect(code).toContain('"name": fc.string()')
17
- expect(code).toContain('"age": fc.integer()')
18
- expect(code).toContain('requiredKeys: ["name"]')
19
- })
20
-
21
- it('omits requiredKeys when every property is required', () => {
22
- const schema = {
23
- type: 'object' as const,
24
- properties: { id: { type: 'string' as const } },
25
- required: ['id'],
26
- }
27
- const code = generateArbitrary(schema, 'Doc')
28
-
29
- expect(code).toContain('fc.record({ "id": fc.string() })')
30
- expect(code).not.toContain('requiredKeys')
31
- })
32
-
33
- it('honours string length constraints', () => {
34
- const schema = { type: 'string' as const, minLength: 2, maxLength: 8 }
35
- expect(generateArbitrary(schema, 'Code')).toContain('fc.string({ minLength: 2, maxLength: 8 })')
36
- })
37
-
38
- it('maps string formats to dedicated arbitraries', () => {
39
- expect(generateArbitrary({ type: 'string', format: 'email' }, 'E')).toContain('fc.emailAddress()')
40
- expect(generateArbitrary({ type: 'string', format: 'uuid' }, 'U')).toContain('fc.uuid()')
41
- expect(generateArbitrary({ type: 'string', format: 'date-time' }, 'D')).toContain(
42
- 'fc.date({ noInvalidDate: true }).map((d) => d.toISOString())',
43
- )
44
- })
45
-
46
- it('uses stringMatching for pattern constraints', () => {
47
- const schema = { type: 'string' as const, pattern: '^[a-z]+$' }
48
- expect(generateArbitrary(schema, 'Slug')).toContain('fc.stringMatching(/^[a-z]+$/)')
49
- })
50
-
51
- it('honours integer range and multipleOf', () => {
52
- const schema = { type: 'integer' as const, minimum: 0, maximum: 10, multipleOf: 2 }
53
- const code = generateArbitrary(schema, 'Even')
54
- expect(code).toContain('fc.integer({ min: 0, max: 10 })')
55
- expect(code).toContain('.filter((n) => n % 2 === 0)')
56
- })
57
-
58
- it('adjusts exclusive integer bounds', () => {
59
- const schema = { type: 'integer' as const, exclusiveMinimum: 0, exclusiveMaximum: 10 }
60
- expect(generateArbitrary(schema, 'N')).toContain('fc.integer({ min: 1, max: 9 })')
61
- })
62
-
63
- it('uses excluded bounds for numbers', () => {
64
- const schema = { type: 'number' as const, exclusiveMinimum: 0, maximum: 1 }
65
- const code = generateArbitrary(schema, 'Ratio')
66
- expect(code).toContain('min: 0, minExcluded: true')
67
- expect(code).toContain('max: 1')
68
- })
69
-
70
- it('generates fc.constantFrom for enums and fc.constant for const', () => {
71
- expect(generateArbitrary({ enum: ['a', 'b'] }, 'Choice')).toContain('fc.constantFrom("a", "b")')
72
- expect(generateArbitrary({ const: 42 }, 'Answer')).toContain('fc.constant(42)')
73
- })
74
-
75
- it('references the imported arbitrary for $ref', () => {
76
- const schema = {
77
- type: 'object' as const,
78
- properties: { address: { $ref: '#/$defs/address' } },
79
- required: ['address'],
80
- }
81
- expect(generateArbitrary(schema, 'User')).toContain('"address": AddressArbitrary')
82
- })
83
-
84
- it('generates fc.array with bounds and uniqueArray for unique items', () => {
85
- expect(generateArbitrary({ type: 'array', items: { type: 'string' }, minItems: 1 }, 'List')).toContain(
86
- 'fc.array(fc.string(), { minLength: 1 })',
87
- )
88
- expect(generateArbitrary({ type: 'array', items: { type: 'number' }, uniqueItems: true }, 'Set')).toContain(
89
- 'fc.uniqueArray(fc.double',
90
- )
91
- })
92
-
93
- it('generates fc.oneof for unions', () => {
94
- const schema = { oneOf: [{ type: 'string' as const }, { type: 'number' as const }] }
95
- expect(generateArbitrary(schema, 'StringOrNumber')).toContain('fc.oneof(fc.string(), fc.double(')
96
- })
97
-
98
- it('maps x-mjst Date and bigint to fc.date and fc.bigInt', () => {
99
- expect(generateArbitrary({ 'x-mjst': { instanceOf: 'Date' } } as never, 'When')).toContain('fc.date(')
100
- expect(generateArbitrary({ 'x-mjst': { primitive: 'bigint' } } as never, 'Big')).toContain('fc.bigInt()')
101
- })
102
- })
@@ -1,194 +0,0 @@
1
- import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension'
2
- import { refToName } from '@amritk/helpers/ref-to-name'
3
- import {
4
- hasAnyOf,
5
- hasConst,
6
- hasEnum,
7
- hasExclusiveMaximum,
8
- hasExclusiveMinimum,
9
- hasFormat,
10
- hasItems,
11
- hasMaxItems,
12
- hasMaximum,
13
- hasMaxLength,
14
- hasMinItems,
15
- hasMinimum,
16
- hasMinLength,
17
- hasMultipleOf,
18
- hasOneOf,
19
- hasPattern,
20
- hasProperties,
21
- hasRef,
22
- hasRequired,
23
- hasType,
24
- hasUniqueItems,
25
- isSchemaObject,
26
- } from '@amritk/helpers/schema-guards'
27
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
28
-
29
- /**
30
- * Derives the arbitrary const name from a type name.
31
- * e.g. "User" → "UserArbitrary"
32
- */
33
- const arbitraryName = (typeName: string): string => `${typeName}Arbitrary`
34
-
35
- /** Builds a `fc.string({ ... })` expression honouring format and length constraints. */
36
- const stringExpr = (schema: JSONSchema): string => {
37
- if (hasFormat(schema)) {
38
- switch (schema.format) {
39
- case 'email':
40
- return 'fc.emailAddress()'
41
- case 'uuid':
42
- return 'fc.uuid()'
43
- case 'uri':
44
- case 'url':
45
- return 'fc.webUrl()'
46
- case 'date-time':
47
- return 'fc.date({ noInvalidDate: true }).map((d) => d.toISOString())'
48
- case 'date':
49
- return 'fc.date({ noInvalidDate: true }).map((d) => d.toISOString().slice(0, 10))'
50
- }
51
- }
52
-
53
- if (hasPattern(schema)) return `fc.stringMatching(/${schema.pattern}/)`
54
-
55
- const opts: string[] = []
56
- if (hasMinLength(schema)) opts.push(`minLength: ${schema.minLength}`)
57
- if (hasMaxLength(schema)) opts.push(`maxLength: ${schema.maxLength}`)
58
- return opts.length > 0 ? `fc.string({ ${opts.join(', ')} })` : 'fc.string()'
59
- }
60
-
61
- /** Builds a `fc.integer({ ... })` expression honouring range and multiple-of constraints. */
62
- const integerExpr = (schema: JSONSchema): string => {
63
- const opts: string[] = []
64
- if (hasMinimum(schema)) opts.push(`min: ${schema.minimum}`)
65
- else if (hasExclusiveMinimum(schema)) opts.push(`min: ${Number(schema.exclusiveMinimum) + 1}`)
66
- if (hasMaximum(schema)) opts.push(`max: ${schema.maximum}`)
67
- else if (hasExclusiveMaximum(schema)) opts.push(`max: ${Number(schema.exclusiveMaximum) - 1}`)
68
-
69
- const base = opts.length > 0 ? `fc.integer({ ${opts.join(', ')} })` : 'fc.integer()'
70
- return hasMultipleOf(schema) ? `${base}.filter((n) => n % ${schema.multipleOf} === 0)` : base
71
- }
72
-
73
- /** Builds a `fc.double({ ... })` expression honouring range and multiple-of constraints. */
74
- const numberExpr = (schema: JSONSchema): string => {
75
- const opts: string[] = ['noNaN: true', 'noDefaultInfinity: true']
76
- if (hasMinimum(schema)) opts.push(`min: ${schema.minimum}`)
77
- else if (hasExclusiveMinimum(schema)) opts.push(`min: ${schema.exclusiveMinimum}`, 'minExcluded: true')
78
- if (hasMaximum(schema)) opts.push(`max: ${schema.maximum}`)
79
- else if (hasExclusiveMaximum(schema)) opts.push(`max: ${schema.exclusiveMaximum}`, 'maxExcluded: true')
80
-
81
- const base = `fc.double({ ${opts.join(', ')} })`
82
- return hasMultipleOf(schema) ? `${base}.filter((n) => n % ${schema.multipleOf} === 0)` : base
83
- }
84
-
85
- /** Builds a `fc.array(...)` / `fc.uniqueArray(...)` expression for an array schema. */
86
- const arrayExpr = (schema: JSONSchema, suffix: string): string => {
87
- const items = hasItems(schema) && isSchemaObject(schema.items) ? arbitraryExpr(schema.items, suffix) : 'fc.anything()'
88
-
89
- const opts: string[] = []
90
- if (hasMinItems(schema)) opts.push(`minLength: ${schema.minItems}`)
91
- if (hasMaxItems(schema)) opts.push(`maxLength: ${schema.maxItems}`)
92
-
93
- const fn = hasUniqueItems(schema) && schema.uniqueItems === true ? 'fc.uniqueArray' : 'fc.array'
94
- return opts.length > 0 ? `${fn}(${items}, { ${opts.join(', ')} })` : `${fn}(${items})`
95
- }
96
-
97
- /** Builds a `fc.record(...)` expression for an object schema. */
98
- const objectExpr = (schema: JSONSchema, suffix: string): string => {
99
- if (!hasProperties(schema)) return 'fc.object()'
100
-
101
- const required = new Set(hasRequired(schema) ? schema.required : [])
102
- const keys = Object.keys(schema.properties)
103
- const entries = Object.entries(schema.properties).map(
104
- ([key, propSchema]) => `${JSON.stringify(key)}: ${arbitraryExpr(propSchema, suffix)}`,
105
- )
106
-
107
- if (entries.length === 0) return 'fc.record({})'
108
-
109
- const model = `{ ${entries.join(', ')} }`
110
-
111
- // fc.record treats all keys as required by default. Only emit requiredKeys
112
- // when at least one property is optional.
113
- if (keys.every((key) => required.has(key))) return `fc.record(${model})`
114
-
115
- const requiredKeys = [...required].map((key) => JSON.stringify(key)).join(', ')
116
- return `fc.record(${model}, { requiredKeys: [${requiredKeys}] })`
117
- }
118
-
119
- /** Builds a `fc.oneof(...)` expression from a list of branch schemas. */
120
- const oneofExpr = (branches: readonly JSONSchema[], suffix: string): string => {
121
- const exprs = branches.map((branch) => arbitraryExpr(branch, suffix))
122
- return `fc.oneof(${exprs.join(', ')})`
123
- }
124
-
125
- /** Builds the fast-check expression for a single (non-union) JSON Schema type. */
126
- const scalarExpr = (type: string, schema: JSONSchema, suffix: string): string => {
127
- switch (type) {
128
- case 'string':
129
- return stringExpr(schema)
130
- case 'integer':
131
- return integerExpr(schema)
132
- case 'number':
133
- return numberExpr(schema)
134
- case 'boolean':
135
- return 'fc.boolean()'
136
- case 'null':
137
- return 'fc.constant(null)'
138
- case 'array':
139
- return arrayExpr(schema, suffix)
140
- case 'object':
141
- return objectExpr(schema, suffix)
142
- default:
143
- return 'fc.anything()'
144
- }
145
- }
146
-
147
- /**
148
- * Recursively builds the fast-check arbitrary expression for a schema node.
149
- * `$ref`s resolve to the referenced file's exported arbitrary; everything else
150
- * maps to the appropriate `fc.*` combinator.
151
- */
152
- const arbitraryExpr = (schema: JSONSchema, suffix: string): string => {
153
- if (!isSchemaObject(schema)) return 'fc.anything()'
154
-
155
- if (hasRef(schema)) return arbitraryName(refToName(schema.$ref, suffix))
156
-
157
- if (hasConst(schema)) return `fc.constant(${JSON.stringify(schema.const)})`
158
-
159
- if (hasEnum(schema)) {
160
- const values = (schema.enum as unknown[]).map((value) => JSON.stringify(value)).join(', ')
161
- return `fc.constantFrom(${values})`
162
- }
163
-
164
- const instanceOf = getMjstInstanceOf(schema)
165
- if (instanceOf === 'Date') return 'fc.date({ noInvalidDate: true })'
166
- if (instanceOf) return 'fc.anything()'
167
-
168
- const primitive = getMjstPrimitive(schema)
169
- if (primitive === 'bigint') return 'fc.bigInt()'
170
- if (primitive) return 'fc.anything()'
171
-
172
- if (hasOneOf(schema)) return oneofExpr(schema.oneOf, suffix)
173
- if (hasAnyOf(schema)) return oneofExpr(schema.anyOf, suffix)
174
-
175
- // `hasType` only matches a single string `type`; multi-type schemas
176
- // (`type: ['string', 'null']`) fall through to the permissive fallback.
177
- if (hasType(schema)) return scalarExpr(schema.type, schema, suffix)
178
-
179
- return 'fc.anything()'
180
- }
181
-
182
- /**
183
- * Generates a `fast-check` arbitrary that produces schema-valid values.
184
- *
185
- * @example
186
- * ```typescript
187
- * generateArbitrary({ type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }, 'Info')
188
- * // export const InfoArbitrary: fc.Arbitrary<Info> = fc.record({ "name": fc.string() })
189
- * ```
190
- */
191
- export const generateArbitrary = (schema: JSONSchema, typeName: string, suffix = ''): string => {
192
- const expr = arbitraryExpr(schema, suffix)
193
- return `export const ${arbitraryName(typeName)}: fc.Arbitrary<${typeName}> = ${expr}`
194
- }
@@ -1,74 +0,0 @@
1
- import { generateTypeDefinition } from '@amritk/helpers/generate-type-definition'
2
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
3
-
4
- import { collectExampleImports } from './collect-example-imports'
5
- import { generateExampleConst } from './derive-example'
6
- import { generateArbitrary } from './generate-arbitrary'
7
-
8
- /**
9
- * Options for controlling what gets generated in an example file.
10
- */
11
- type GenerateExampleFileOptions = {
12
- /**
13
- * The $ref path of the schema being generated (e.g. `#/$defs/address`).
14
- * Prevents the file from importing itself.
15
- */
16
- readonly selfRef?: string
17
- /**
18
- * The root schema document. Used to resolve `$ref`s when deriving a concrete
19
- * example value, and to filter out unresolvable refs from the import list.
20
- */
21
- readonly rootSchema?: Record<string, unknown>
22
- /**
23
- * Suffix appended to every type/arbitrary name derived from a `$ref`.
24
- * Defaults to `''` (no suffix).
25
- */
26
- readonly typeSuffix?: string
27
- }
28
-
29
- /**
30
- * Generates a complete TypeScript example file from a JSON Schema.
31
- *
32
- * The file contains:
33
- * - An import of `fast-check` and imports for any `$ref` types and arbitraries
34
- * - The exported TypeScript type definition
35
- * - An exported `fast-check` arbitrary (`FooArbitrary`)
36
- * - An exported concrete example value (`fooExample`)
37
- *
38
- * @example
39
- * ```typescript
40
- * const schema = { type: 'object', properties: { title: { type: 'string' } }, required: ['title'] }
41
- * generateExampleFile(schema, 'Info')
42
- * // import * as fc from 'fast-check'
43
- * // export type Info = { title: string }
44
- * // export const InfoArbitrary: fc.Arbitrary<Info> = fc.record({ "title": fc.string() })
45
- * // export const infoExample: Info = { "title": "string" }
46
- * ```
47
- */
48
- export const generateExampleFile = (
49
- schema: JSONSchema,
50
- typeName: string,
51
- options?: GenerateExampleFileOptions,
52
- ): string => {
53
- const typeSuffix = options?.typeSuffix ?? ''
54
- const refImports = collectExampleImports(schema, {
55
- selfRef: options?.selfRef,
56
- rootSchema: options?.rootSchema,
57
- typeSuffix,
58
- })
59
-
60
- const typeDefinition = generateTypeDefinition(schema, typeName, { typeSuffix })
61
- const arbitrary = generateArbitrary(schema, typeName, typeSuffix)
62
- const example = generateExampleConst(schema, typeName, options?.rootSchema)
63
-
64
- let result = `import * as fc from 'fast-check'\n`
65
-
66
- for (const imp of refImports) {
67
- result += imp + '\n'
68
- }
69
-
70
- result += '\n'
71
- result += typeDefinition + '\n\n' + arbitrary + '\n\n' + example + '\n'
72
-
73
- return result
74
- }
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export type { GeneratedFile } from './generators/build-schema'
2
- export { buildExampleSchema } from './generators/build-schema'
3
- export { deriveExample, generateExampleConst, serializeValue } from './generators/derive-example'
4
- export { generateArbitrary } from './generators/generate-arbitrary'