Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@amritk/generate-examples

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@amritk/generate-examples - npm Package Compare versions

Comparing version
0.1.1
to
0.2.0
+1
dist/generators/build-schema.d.ts.map
{"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"}
import { generateIndexBarrel } from '@amritk/helpers/generate-index-barrel';
import { walkRefGraph } from '@amritk/helpers/walk-ref-graph';
import { generateExampleFile } from './generate-files.js';
/**
* Builds all TypeScript example files from a JSON Schema by traversing all
* `$ref` / `$dynamicRef` references recursively (via the shared
* `@amritk/helpers/walk-ref-graph` walker).
*
* Each generated file exports:
* - A TypeScript type definition
* - A `fast-check` arbitrary (`FooArbitrary`) that produces schema-valid values
* - A concrete example value (`fooExample`)
*
* An `index.ts` re-exports everything. The generated output imports `fast-check`,
* which consumers must install as a (dev) dependency.
*
* @param rootSchema - The root JSON Schema to build from
* @param rootTypeName - The name for the root type (e.g. "Document")
* @param typeSuffix - Suffix appended to every `$ref`-derived name (default `''`)
* @returns An array of generated TypeScript files
*
* @example
* ```typescript
* const files = await buildExampleSchema(schema, 'Document')
* // files → [{ filename: 'document.ts', content: '...' }, { filename: 'index.ts', ... }]
* ```
*/
export const buildExampleSchema = async (rootSchema, rootTypeName, typeSuffix = '') => {
const files = [];
walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
// `index` is reserved for the barrel below, so never let a definition of
// that name overwrite it.
if (node.filename === 'index')
return;
const content = generateExampleFile(node.schema, node.typeName, {
rootSchema: node.rootSchema,
typeSuffix,
...(node.ref !== undefined ? { selfRef: node.ref } : {}),
});
files.push({ filename: `${node.filename}.ts`, content });
});
files.push({ filename: 'index.ts', content: generateIndexBarrel(files) });
return files;
};
{"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"}
import { refToFilename } from '@amritk/helpers/ref-to-filename';
import { refToName } from '@amritk/helpers/ref-to-name';
import { resolveRef } from '@amritk/helpers/resolve-ref';
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasItems, hasOneOf, hasRef } from '@amritk/helpers/schema-guards';
/**
* Generates an import statement for a single $ref, importing both the generated
* type and its arbitrary from the ref's generated file.
*/
const buildImport = (ref, suffix) => {
const filename = refToFilename(ref);
const typeName = refToName(ref, suffix);
return `import { type ${typeName}, ${typeName}Arbitrary } from './${filename}'`;
};
/**
* Walks one level of the schema and yields all direct $ref strings that should
* become imports: properties, additionalProperties, items, and union branches.
*/
const collectDirectRefs = (schema) => {
if (typeof schema === 'boolean' || schema === null)
return [];
const refs = [];
if (hasRef(schema)) {
refs.push(schema.$ref);
return refs;
}
const propSchemas = 'properties' in schema && typeof schema.properties === 'object' && schema.properties !== null
? Object.values(schema.properties)
: [];
for (const prop of propSchemas) {
if (hasRef(prop))
refs.push(prop.$ref);
if (hasItems(prop) && hasRef(prop.items))
refs.push(prop.items.$ref);
if (hasAdditionalProperties(prop) && hasRef(prop.additionalProperties)) {
refs.push(prop.additionalProperties.$ref);
}
}
if (hasItems(schema) && hasRef(schema.items)) {
refs.push(schema.items.$ref);
}
if (hasAdditionalProperties(schema) && hasRef(schema.additionalProperties)) {
refs.push(schema.additionalProperties.$ref);
}
for (const branch of [
...(hasOneOf(schema) ? schema.oneOf : []),
...(hasAnyOf(schema) ? schema.anyOf : []),
...(hasAllOf(schema) ? schema.allOf : []),
]) {
if (hasRef(branch))
refs.push(branch.$ref);
}
return refs;
};
/**
* Collects import statements for all $ref dependencies of a schema. Each import
* brings in both the generated TypeScript type and the arbitrary for that ref.
*
* @example
* ```typescript
* const schema = { properties: { address: { $ref: '#/$defs/address' } } }
* collectExampleImports(schema)
* // ["import { type Address, AddressArbitrary } from './address'"]
* ```
*/
export const collectExampleImports = (schema, options) => {
const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : null;
const rootSchema = options?.rootSchema;
const typeSuffix = options?.typeSuffix ?? '';
const refs = collectDirectRefs(schema);
const seen = new Set();
const imports = [];
for (const ref of refs) {
const filename = refToFilename(ref);
if (seen.has(filename))
continue;
if (selfFilename && filename === selfFilename)
continue;
// Skip refs that don't resolve in this schema (external / never generated)
if (rootSchema) {
const resolved = resolveRef(ref, rootSchema);
if (!resolved)
continue;
}
seen.add(filename);
imports.push(buildImport(ref, typeSuffix));
}
return imports;
};
{"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"}
import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension';
import { resolveRef } from '@amritk/helpers/resolve-ref';
import { hasAnyOf, hasConst, hasDefault, hasEnum, hasExamples, hasFormat, hasItems, hasMaxLength, hasMinItems, hasMinimum, hasMinLength, hasOneOf, hasProperties, hasRef, hasType, isSchemaObject, } from '@amritk/helpers/schema-guards';
/** Lowercases the first character of a name. e.g. "User" → "user" */
const lowerFirst = (name) => name.charAt(0).toLowerCase() + name.slice(1);
/** Derives the example const name from a type name. e.g. "User" → "userExample" */
const exampleName = (typeName) => `${lowerFirst(typeName)}Example`;
/** Returns a representative string honouring `format` and length constraints. */
const exampleString = (schema) => {
if (hasFormat(schema)) {
switch (schema.format) {
case 'email':
return 'user@example.com';
case 'uuid':
return '00000000-0000-0000-0000-000000000000';
case 'uri':
case 'url':
return 'https://example.com';
case 'date-time':
return '1970-01-01T00:00:00.000Z';
case 'date':
return '1970-01-01';
}
}
let value = 'string';
if (hasMinLength(schema) && value.length < schema.minLength)
value = value.padEnd(schema.minLength, 'x');
if (hasMaxLength(schema) && value.length > schema.maxLength)
value = value.slice(0, schema.maxLength);
return value;
};
/**
* Derives a single concrete, schema-valid value from a JSON Schema.
*
* Prefers explicit hints in this order: `const`, `examples[0]`, `default`,
* `enum[0]`; otherwise produces a canonical value for the declared type.
* `$ref`s are resolved and inlined by value; recursive refs short-circuit to
* `null` (tracked via `seen`).
*
* Note: values constrained only by `pattern` are not guaranteed to match the
* pattern — use the generated arbitrary when pattern fidelity matters.
*/
export const deriveExample = (schema, rootSchema, seen = new Set()) => {
if (!isSchemaObject(schema))
return null;
if (hasConst(schema))
return schema.const;
if (hasExamples(schema) && Array.isArray(schema.examples) && schema.examples.length > 0)
return schema.examples[0];
if (hasDefault(schema))
return schema.default;
if (hasEnum(schema) && schema.enum.length > 0)
return schema.enum[0];
if (hasRef(schema)) {
const ref = schema.$ref;
if (seen.has(ref) || !rootSchema)
return null;
const resolved = resolveRef(ref, rootSchema);
if (!resolved)
return null;
return deriveExample(resolved, rootSchema, new Set([...seen, ref]));
}
const instanceOf = getMjstInstanceOf(schema);
if (instanceOf === 'Date')
return new Date(0);
const primitive = getMjstPrimitive(schema);
if (primitive === 'bigint')
return 0n;
if (hasOneOf(schema) && schema.oneOf[0] !== undefined)
return deriveExample(schema.oneOf[0], rootSchema, seen);
if (hasAnyOf(schema) && schema.anyOf[0] !== undefined)
return deriveExample(schema.anyOf[0], rootSchema, seen);
// `hasType` only matches a single string `type`; multi-type schemas fall
// through to `null`.
if (!hasType(schema))
return null;
switch (schema.type) {
case 'string':
return exampleString(schema);
case 'number':
case 'integer':
return hasMinimum(schema) ? schema.minimum : 0;
case 'boolean':
return true;
case 'null':
return null;
case 'array': {
const item = hasItems(schema) ? deriveExample(schema.items, rootSchema, seen) : null;
const count = hasMinItems(schema) ? Math.max(schema.minItems, 1) : 1;
return Array.from({ length: count }, () => item);
}
case 'object': {
const out = {};
if (hasProperties(schema)) {
for (const [key, propSchema] of Object.entries(schema.properties)) {
out[key] = deriveExample(propSchema, rootSchema, seen);
}
}
return out;
}
default:
return null;
}
};
/**
* Serializes a derived value into a TypeScript source expression. Handles the
* non-JSON values `deriveExample` can produce (`Date`, `bigint`) in addition to
* plain JSON.
*/
export const serializeValue = (value) => {
if (typeof value === 'bigint')
return `${value}n`;
if (value instanceof Date)
return `new Date(${JSON.stringify(value.toISOString())})`;
if (Array.isArray(value))
return `[${value.map(serializeValue).join(', ')}]`;
if (value !== null && typeof value === 'object') {
const entries = Object.entries(value)
.filter(([, v]) => v !== undefined)
.map(([key, v]) => `${JSON.stringify(key)}: ${serializeValue(v)}`);
return `{ ${entries.join(', ')} }`;
}
return JSON.stringify(value);
};
/**
* Generates an exported const holding a concrete, schema-valid example value.
*
* @example
* ```typescript
* generateExampleConst({ type: 'object', properties: { name: { type: 'string' } } }, 'Info')
* // export const infoExample: Info = { "name": "string" }
* ```
*/
export const generateExampleConst = (schema, typeName, rootSchema) => {
const value = deriveExample(schema, rootSchema);
return `export const ${exampleName(typeName)}: ${typeName} = ${serializeValue(value)}`;
};
{"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"}
import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension';
import { refToName } from '@amritk/helpers/ref-to-name';
import { hasAnyOf, hasConst, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasFormat, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMinItems, hasMinimum, hasMinLength, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject, } from '@amritk/helpers/schema-guards';
/**
* Derives the arbitrary const name from a type name.
* e.g. "User" → "UserArbitrary"
*/
const arbitraryName = (typeName) => `${typeName}Arbitrary`;
/** Builds a `fc.string({ ... })` expression honouring format and length constraints. */
const stringExpr = (schema) => {
if (hasFormat(schema)) {
switch (schema.format) {
case 'email':
return 'fc.emailAddress()';
case 'uuid':
return 'fc.uuid()';
case 'uri':
case 'url':
return 'fc.webUrl()';
case 'date-time':
return 'fc.date({ noInvalidDate: true }).map((d) => d.toISOString())';
case 'date':
return 'fc.date({ noInvalidDate: true }).map((d) => d.toISOString().slice(0, 10))';
}
}
if (hasPattern(schema))
return `fc.stringMatching(/${schema.pattern}/)`;
const opts = [];
if (hasMinLength(schema))
opts.push(`minLength: ${schema.minLength}`);
if (hasMaxLength(schema))
opts.push(`maxLength: ${schema.maxLength}`);
return opts.length > 0 ? `fc.string({ ${opts.join(', ')} })` : 'fc.string()';
};
/** Builds a `fc.integer({ ... })` expression honouring range and multiple-of constraints. */
const integerExpr = (schema) => {
const opts = [];
if (hasMinimum(schema))
opts.push(`min: ${schema.minimum}`);
else if (hasExclusiveMinimum(schema))
opts.push(`min: ${Number(schema.exclusiveMinimum) + 1}`);
if (hasMaximum(schema))
opts.push(`max: ${schema.maximum}`);
else if (hasExclusiveMaximum(schema))
opts.push(`max: ${Number(schema.exclusiveMaximum) - 1}`);
const base = opts.length > 0 ? `fc.integer({ ${opts.join(', ')} })` : 'fc.integer()';
return hasMultipleOf(schema) ? `${base}.filter((n) => n % ${schema.multipleOf} === 0)` : base;
};
/** Builds a `fc.double({ ... })` expression honouring range and multiple-of constraints. */
const numberExpr = (schema) => {
const opts = ['noNaN: true', 'noDefaultInfinity: true'];
if (hasMinimum(schema))
opts.push(`min: ${schema.minimum}`);
else if (hasExclusiveMinimum(schema))
opts.push(`min: ${schema.exclusiveMinimum}`, 'minExcluded: true');
if (hasMaximum(schema))
opts.push(`max: ${schema.maximum}`);
else if (hasExclusiveMaximum(schema))
opts.push(`max: ${schema.exclusiveMaximum}`, 'maxExcluded: true');
const base = `fc.double({ ${opts.join(', ')} })`;
return hasMultipleOf(schema) ? `${base}.filter((n) => n % ${schema.multipleOf} === 0)` : base;
};
/** Builds a `fc.array(...)` / `fc.uniqueArray(...)` expression for an array schema. */
const arrayExpr = (schema, suffix) => {
const items = hasItems(schema) && isSchemaObject(schema.items) ? arbitraryExpr(schema.items, suffix) : 'fc.anything()';
const opts = [];
if (hasMinItems(schema))
opts.push(`minLength: ${schema.minItems}`);
if (hasMaxItems(schema))
opts.push(`maxLength: ${schema.maxItems}`);
const fn = hasUniqueItems(schema) && schema.uniqueItems === true ? 'fc.uniqueArray' : 'fc.array';
return opts.length > 0 ? `${fn}(${items}, { ${opts.join(', ')} })` : `${fn}(${items})`;
};
/** Builds a `fc.record(...)` expression for an object schema. */
const objectExpr = (schema, suffix) => {
if (!hasProperties(schema))
return 'fc.object()';
const required = new Set(hasRequired(schema) ? schema.required : []);
const keys = Object.keys(schema.properties);
const entries = Object.entries(schema.properties).map(([key, propSchema]) => `${JSON.stringify(key)}: ${arbitraryExpr(propSchema, suffix)}`);
if (entries.length === 0)
return 'fc.record({})';
const model = `{ ${entries.join(', ')} }`;
// fc.record treats all keys as required by default. Only emit requiredKeys
// when at least one property is optional.
if (keys.every((key) => required.has(key)))
return `fc.record(${model})`;
const requiredKeys = [...required].map((key) => JSON.stringify(key)).join(', ');
return `fc.record(${model}, { requiredKeys: [${requiredKeys}] })`;
};
/** Builds a `fc.oneof(...)` expression from a list of branch schemas. */
const oneofExpr = (branches, suffix) => {
const exprs = branches.map((branch) => arbitraryExpr(branch, suffix));
return `fc.oneof(${exprs.join(', ')})`;
};
/** Builds the fast-check expression for a single (non-union) JSON Schema type. */
const scalarExpr = (type, schema, suffix) => {
switch (type) {
case 'string':
return stringExpr(schema);
case 'integer':
return integerExpr(schema);
case 'number':
return numberExpr(schema);
case 'boolean':
return 'fc.boolean()';
case 'null':
return 'fc.constant(null)';
case 'array':
return arrayExpr(schema, suffix);
case 'object':
return objectExpr(schema, suffix);
default:
return 'fc.anything()';
}
};
/**
* Recursively builds the fast-check arbitrary expression for a schema node.
* `$ref`s resolve to the referenced file's exported arbitrary; everything else
* maps to the appropriate `fc.*` combinator.
*/
const arbitraryExpr = (schema, suffix) => {
if (!isSchemaObject(schema))
return 'fc.anything()';
if (hasRef(schema))
return arbitraryName(refToName(schema.$ref, suffix));
if (hasConst(schema))
return `fc.constant(${JSON.stringify(schema.const)})`;
if (hasEnum(schema)) {
const values = schema.enum.map((value) => JSON.stringify(value)).join(', ');
return `fc.constantFrom(${values})`;
}
const instanceOf = getMjstInstanceOf(schema);
if (instanceOf === 'Date')
return 'fc.date({ noInvalidDate: true })';
if (instanceOf)
return 'fc.anything()';
const primitive = getMjstPrimitive(schema);
if (primitive === 'bigint')
return 'fc.bigInt()';
if (primitive)
return 'fc.anything()';
if (hasOneOf(schema))
return oneofExpr(schema.oneOf, suffix);
if (hasAnyOf(schema))
return oneofExpr(schema.anyOf, suffix);
// `hasType` only matches a single string `type`; multi-type schemas
// (`type: ['string', 'null']`) fall through to the permissive fallback.
if (hasType(schema))
return scalarExpr(schema.type, schema, suffix);
return 'fc.anything()';
};
/**
* Generates a `fast-check` arbitrary that produces schema-valid values.
*
* @example
* ```typescript
* generateArbitrary({ type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }, 'Info')
* // export const InfoArbitrary: fc.Arbitrary<Info> = fc.record({ "name": fc.string() })
* ```
*/
export const generateArbitrary = (schema, typeName, suffix = '') => {
const expr = arbitraryExpr(schema, suffix);
return `export const ${arbitraryName(typeName)}: fc.Arbitrary<${typeName}> = ${expr}`;
};
{"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"}
import { generateTypeDefinition } from '@amritk/helpers/generate-type-definition';
import { collectExampleImports } from './collect-example-imports.js';
import { generateExampleConst } from './derive-example.js';
import { generateArbitrary } from './generate-arbitrary.js';
/**
* Generates a complete TypeScript example file from a JSON Schema.
*
* The file contains:
* - An import of `fast-check` and imports for any `$ref` types and arbitraries
* - The exported TypeScript type definition
* - An exported `fast-check` arbitrary (`FooArbitrary`)
* - An exported concrete example value (`fooExample`)
*
* @example
* ```typescript
* const schema = { type: 'object', properties: { title: { type: 'string' } }, required: ['title'] }
* generateExampleFile(schema, 'Info')
* // import * as fc from 'fast-check'
* // export type Info = { title: string }
* // export const InfoArbitrary: fc.Arbitrary<Info> = fc.record({ "title": fc.string() })
* // export const infoExample: Info = { "title": "string" }
* ```
*/
export const generateExampleFile = (schema, typeName, options) => {
const typeSuffix = options?.typeSuffix ?? '';
const refImports = collectExampleImports(schema, {
selfRef: options?.selfRef,
rootSchema: options?.rootSchema,
typeSuffix,
});
const typeDefinition = generateTypeDefinition(schema, typeName, { typeSuffix });
const arbitrary = generateArbitrary(schema, typeName, typeSuffix);
const example = generateExampleConst(schema, typeName, options?.rootSchema);
let result = `import * as fc from 'fast-check'\n`;
for (const imp of refImports) {
result += imp + '\n';
}
result += '\n';
result += typeDefinition + '\n\n' + arbitrary + '\n\n' + example + '\n';
return result;
};
{"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"}
+3
-1

@@ -11,3 +11,4 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';

* Builds all TypeScript example files from a JSON Schema by traversing all
* $ref references recursively, mirroring the generate-parsers pipeline.
* `$ref` / `$dynamicRef` references recursively (via the shared
* `@amritk/helpers/walk-ref-graph` walker).
*

@@ -34,1 +35,2 @@ * Each generated file exports:

export declare const buildExampleSchema: (rootSchema: JSONSchema, rootTypeName: string, typeSuffix?: string) => Promise<GeneratedFile[]>;
//# sourceMappingURL=build-schema.d.ts.map

@@ -35,1 +35,2 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';

export {};
//# sourceMappingURL=collect-example-imports.d.ts.map

@@ -30,1 +30,2 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';

export declare const generateExampleConst: (schema: JSONSchema, typeName: string, rootSchema?: Record<string, unknown>) => string;
//# sourceMappingURL=derive-example.d.ts.map

@@ -12,1 +12,2 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';

export declare const generateArbitrary: (schema: JSONSchema, typeName: string, suffix?: string) => string;
//# sourceMappingURL=generate-arbitrary.d.ts.map

@@ -43,1 +43,2 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';

export {};
//# sourceMappingURL=generate-files.d.ts.map

@@ -1,4 +0,5 @@

export type { GeneratedFile } from './generators/build-schema';
export { buildExampleSchema } from './generators/build-schema';
export { deriveExample, generateExampleConst, serializeValue } from './generators/derive-example';
export { generateArbitrary } from './generators/generate-arbitrary';
export type { GeneratedFile } from './generators/build-schema.js';
export { buildExampleSchema } from './generators/build-schema.js';
export { deriveExample, generateExampleConst, serializeValue } from './generators/derive-example.js';
export { generateArbitrary } from './generators/generate-arbitrary.js';
//# sourceMappingURL=index.d.ts.map

@@ -1,1353 +0,3 @@

// ../helpers/dist/build-dynamic-ref-map.js
var isSchemaObject = (schema) => {
return typeof schema === "object" && schema !== null && typeof schema !== "boolean";
};
var buildDynamicRefMap = (rootSchema) => {
const map = {};
if (!isSchemaObject(rootSchema) || !("$defs" in rootSchema)) {
return map;
}
const defs = rootSchema.$defs;
for (const [key, value] of Object.entries(defs)) {
if (typeof value === "object" && value !== null && "$dynamicAnchor" in value && typeof value["$dynamicAnchor"] === "string") {
const anchor = value["$dynamicAnchor"];
map[`#${anchor}`] = `#/$defs/${key}`;
}
}
return map;
};
// ../helpers/dist/extract-refs.js
var isResolvableRef = (ref) => {
if (ref === "#")
return false;
if (ref.startsWith("#"))
return true;
if (ref.startsWith("http://") || ref.startsWith("https://"))
return true;
return false;
};
var extractRefs = (schema) => {
const refs = new Set;
const traverse = (obj) => {
if (typeof obj !== "object" || obj === null) {
return;
}
if (Array.isArray(obj)) {
for (const item of obj) {
traverse(item);
}
return;
}
const record = obj;
if ("$ref" in record && typeof record["$ref"] === "string" && isResolvableRef(record["$ref"])) {
refs.add(record["$ref"]);
}
for (const key in record) {
traverse(record[key]);
}
};
traverse(schema);
return refs;
};
// ../helpers/dist/ref-to-filename.js
var toKebabCase = (value) => value.replace(/OAuth/g, "Oauth").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
var uriRefToFilename = (uri) => {
const hashIndex = uri.indexOf("#");
const baseUri = hashIndex === -1 ? uri : uri.slice(0, hashIndex);
const fragment = hashIndex === -1 ? "" : uri.slice(hashIndex + 1);
const withoutProtocol = baseUri.replace(/^https?:\/\/[^/]+\//, "");
const withoutExt = withoutProtocol.replace(/\.json$/, "");
const rawSegments = withoutExt.split("/");
const SKIP_KEYS = new Set(["definitions", "$defs"]);
const segments = [];
for (let i = 0;i < rawSegments.length; i++) {
const s = rawSegments[i];
if (SKIP_KEYS.has(s))
continue;
const prevRaw = rawSegments[i - 1];
if (/^\d+\.\d+/.test(s) && prevRaw !== undefined && SKIP_KEYS.has(prevRaw))
continue;
segments.push(s);
}
const baseName = segments.map((s) => toKebabCase(s).replace(/\./g, "-")).join("-");
if (!fragment)
return baseName;
const fragSegments = fragment.split("/").filter((s) => s && !SKIP_KEYS.has(s) && s !== "properties");
const fragLast = fragSegments[fragSegments.length - 1];
if (!fragLast)
return baseName;
return `${baseName}-${toKebabCase(fragLast)}`;
};
var refToFilename = (ref) => {
if (ref.startsWith("http://") || ref.startsWith("https://")) {
return uriRefToFilename(ref);
}
const segments = ref.split("/");
let filename = segments[segments.length - 1];
if (/[A-Z]/.test(filename)) {
filename = toKebabCase(filename);
}
return filename;
};
// ../helpers/dist/ref-to-name.js
var toKebabCase2 = (value) => value.replace(/OAuth/g, "Oauth").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
var uriRefToFilename2 = (uri) => {
const hashIndex = uri.indexOf("#");
const baseUri = hashIndex === -1 ? uri : uri.slice(0, hashIndex);
const fragment = hashIndex === -1 ? "" : uri.slice(hashIndex + 1);
const withoutProtocol = baseUri.replace(/^https?:\/\/[^/]+\//, "");
const withoutExt = withoutProtocol.replace(/\.json$/, "");
const rawSegments = withoutExt.split("/");
const SKIP_KEYS = new Set(["definitions", "$defs"]);
const segments = [];
for (let i = 0;i < rawSegments.length; i++) {
const s = rawSegments[i];
if (SKIP_KEYS.has(s))
continue;
const prevRaw = rawSegments[i - 1];
if (/^\d+\.\d+/.test(s) && prevRaw !== undefined && SKIP_KEYS.has(prevRaw))
continue;
segments.push(s);
}
const baseName = segments.map((s) => toKebabCase2(s).replace(/\./g, "-")).join("-");
if (!fragment)
return baseName;
const fragSegments = fragment.split("/").filter((s) => s && !SKIP_KEYS.has(s) && s !== "properties");
const fragLast = fragSegments[fragSegments.length - 1];
if (!fragLast)
return baseName;
return `${baseName}-${toKebabCase2(fragLast)}`;
};
var refToFilename2 = (ref) => {
if (ref.startsWith("http://") || ref.startsWith("https://")) {
return uriRefToFilename2(ref);
}
const segments = ref.split("/");
let filename = segments[segments.length - 1];
if (/[A-Z]/.test(filename)) {
filename = toKebabCase2(filename);
}
return filename;
};
var kebabToPascal = (kebab, suffix) => {
const words = kebab.split("-");
let pascalCase = "";
for (const word of words) {
pascalCase += word.charAt(0).toUpperCase() + word.slice(1);
}
return pascalCase + suffix;
};
var refToName = (ref, suffix = "") => kebabToPascal(refToFilename2(ref), suffix);
// ../helpers/dist/resolve-dynamic-refs.js
var resolveDynamicRefs = (schema, dynamicRefMap) => {
if (typeof schema !== "object" || schema === null) {
return schema;
}
if (Object.keys(dynamicRefMap).length === 0) {
return schema;
}
const clone = JSON.parse(JSON.stringify(schema));
const walk = (obj) => {
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
return;
}
const record = obj;
if ("$dynamicRef" in record && typeof record["$dynamicRef"] === "string") {
const resolved = dynamicRefMap[record["$dynamicRef"]];
if (resolved) {
record["$ref"] = resolved;
delete record["$dynamicRef"];
}
}
for (const key in record) {
walk(record[key]);
}
};
walk(clone);
return clone;
};
// ../helpers/dist/resolve-ref.js
var navigatePointer = (pointer, schema) => {
const parts = pointer.split("/").filter(Boolean);
let current = schema;
for (const part of parts) {
const decodedPart = part.replace(/~1/g, "/").replace(/~0/g, "~");
if (current && typeof current === "object" && decodedPart in current) {
const next = current[decodedPart];
if (typeof next === "object" && next !== null) {
current = next;
} else {
return;
}
} else {
return;
}
}
return current;
};
var resolveRef = (ref, rootSchema) => {
if (ref.startsWith("#")) {
return navigatePointer(ref.slice(1), rootSchema);
}
const hashIndex = ref.indexOf("#");
const baseUri = hashIndex === -1 ? ref : ref.slice(0, hashIndex);
const rawFragment = hashIndex === -1 ? "" : ref.slice(hashIndex + 1);
const fragment = rawFragment === "" || rawFragment === "/" ? "" : rawFragment;
const defs = rootSchema["$defs"];
if (typeof defs !== "object" || defs === null)
return;
const defsRecord = defs;
const base = defsRecord[baseUri];
if (typeof base !== "object" || base === null)
return;
if (!fragment)
return base;
const normalizedFragment = fragment.replace(/^\/definitions\//, "/$defs/");
return navigatePointer(normalizedFragment, base);
};
// ../helpers/dist/upgrade-draft07-schema.js
var toKebabCase3 = (value) => value.replace(/OAuth/g, "Oauth").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
var uriRefToFilename3 = (uri) => {
const hashIndex = uri.indexOf("#");
const baseUri = hashIndex === -1 ? uri : uri.slice(0, hashIndex);
const fragment = hashIndex === -1 ? "" : uri.slice(hashIndex + 1);
const withoutProtocol = baseUri.replace(/^https?:\/\/[^/]+\//, "");
const withoutExt = withoutProtocol.replace(/\.json$/, "");
const rawSegments = withoutExt.split("/");
const SKIP_KEYS = new Set(["definitions", "$defs"]);
const segments = [];
for (let i = 0;i < rawSegments.length; i++) {
const s = rawSegments[i];
if (SKIP_KEYS.has(s))
continue;
const prevRaw = rawSegments[i - 1];
if (/^\d+\.\d+/.test(s) && prevRaw !== undefined && SKIP_KEYS.has(prevRaw))
continue;
segments.push(s);
}
const baseName = segments.map((s) => toKebabCase3(s).replace(/\./g, "-")).join("-");
if (!fragment)
return baseName;
const fragSegments = fragment.split("/").filter((s) => s && !SKIP_KEYS.has(s) && s !== "properties");
const fragLast = fragSegments[fragSegments.length - 1];
if (!fragLast)
return baseName;
return `${baseName}-${toKebabCase3(fragLast)}`;
};
var refToFilename3 = (ref) => {
if (ref.startsWith("http://") || ref.startsWith("https://")) {
return uriRefToFilename3(ref);
}
const segments = ref.split("/");
let filename = segments[segments.length - 1];
if (/[A-Z]/.test(filename)) {
filename = toKebabCase3(filename);
}
return filename;
};
var isDraft07Schema = (schema) => typeof schema["$schema"] === "string" && schema["$schema"].includes("draft-07");
var rewriteRefs = (obj, refMap, selfRef) => {
if (typeof obj !== "object" || obj === null)
return obj;
if (Array.isArray(obj))
return obj.map((item) => rewriteRefs(item, refMap, selfRef));
const record = obj;
const result = {};
for (const [key, value] of Object.entries(record)) {
if (key === "$ref" && typeof value === "string") {
if (refMap.has(value)) {
result[key] = refMap.get(value);
} else if (value === "#" && selfRef) {
result[key] = selfRef;
} else {
result[key] = value;
}
} else {
result[key] = rewriteRefs(value, refMap, selfRef);
}
}
return result;
};
var hoistNestedDefs = (defs) => {
const hoisted = {};
for (const [parentName, parentSchema] of Object.entries(defs)) {
if (typeof parentSchema !== "object" || parentSchema === null) {
hoisted[parentName] = parentSchema;
continue;
}
const parentObj = parentSchema;
const nestedDefs = parentObj["$defs"];
if (!nestedDefs || typeof nestedDefs !== "object") {
hoisted[parentName] = parentSchema;
continue;
}
const parentPrefix = parentName.startsWith("http://") || parentName.startsWith("https://") ? refToFilename3(parentName) : parentName;
const localToHoisted = new Map;
for (const localName of Object.keys(nestedDefs)) {
const hoistedName = `${parentPrefix}-${toKebabCase3(localName)}`;
localToHoisted.set(`#/$defs/${localName}`, `#/$defs/${hoistedName}`);
}
const selfRef = `#/$defs/${parentPrefix}`;
const rewrittenParent = rewriteRefs(parentObj, localToHoisted, selfRef);
hoisted[parentName] = rewrittenParent;
for (const [localName, localSchema] of Object.entries(nestedDefs)) {
const hoistedName = `${parentPrefix}-${toKebabCase3(localName)}`;
hoisted[hoistedName] = rewriteRefs(localSchema, localToHoisted, selfRef);
}
}
return hoisted;
};
var upgradeDraft07Schema = (schema) => {
if (!isDraft07Schema(schema))
return schema;
const { definitions, $schema: _, ...rest } = schema;
const rawDefs = definitions ?? {};
const renamedDefs = {};
for (const [key, value] of Object.entries(rawDefs)) {
renamedDefs[key] = renameNestedDefs(value);
}
const hoistedDefs = hoistNestedDefs(renamedDefs);
for (const key of Object.keys(hoistedDefs)) {
if (key.startsWith("http://") || key.startsWith("https://")) {
const shortName = refToFilename3(key);
if (shortName && !(shortName in hoistedDefs)) {
hoistedDefs[shortName] = hoistedDefs[key];
}
}
}
return {
...rest,
$defs: hoistedDefs
};
};
var renameNestedDefs = (obj) => {
if (typeof obj !== "object" || obj === null)
return obj;
if (Array.isArray(obj))
return obj.map(renameNestedDefs);
const record = obj;
const result = {};
for (const [key, value] of Object.entries(record)) {
if (key === "$ref" && typeof value === "string" && value.startsWith("#/definitions/")) {
result[key] = value.replace("#/definitions/", "#/$defs/");
} else {
const outKey = key === "definitions" ? "$defs" : key;
result[outKey] = renameNestedDefs(value);
}
}
return result;
};
// ../helpers/dist/generate-type-definition.js
var isSchemaObject2 = (schema) => {
return typeof schema === "object" && schema !== null && typeof schema !== "boolean";
};
var isObjectSchema = (schema) => {
return isSchemaObject2(schema) && (("type" in schema) && schema.type === "object" || ("properties" in schema));
};
var MJST_EXTENSION_KEY = "x-mjst";
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
var SUPPORTED_PRIMITIVES = new Set(["bigint"]);
var SAFE_BRAND = /^[\w$ -]+$/;
var readExtensionString = (schema, field) => {
if (!isSchemaObject2(schema))
return;
const extension = schema[MJST_EXTENSION_KEY];
if (typeof extension !== "object" || extension === null)
return;
const value = extension[field];
return typeof value === "string" ? value : undefined;
};
var getMjstInstanceOf = (schema) => {
const instanceOf = readExtensionString(schema, "instanceOf");
return instanceOf !== undefined && IDENTIFIER.test(instanceOf) ? instanceOf : undefined;
};
var getMjstPrimitive = (schema) => {
const primitive = readExtensionString(schema, "primitive");
return primitive !== undefined && SUPPORTED_PRIMITIVES.has(primitive) ? primitive : undefined;
};
var getMjstBrand = (schema) => {
const brand = readExtensionString(schema, "brand");
return brand !== undefined && SAFE_BRAND.test(brand) ? brand : undefined;
};
var toKebabCase4 = (value) => value.replace(/OAuth/g, "Oauth").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
var uriRefToFilename4 = (uri) => {
const hashIndex = uri.indexOf("#");
const baseUri = hashIndex === -1 ? uri : uri.slice(0, hashIndex);
const fragment = hashIndex === -1 ? "" : uri.slice(hashIndex + 1);
const withoutProtocol = baseUri.replace(/^https?:\/\/[^/]+\//, "");
const withoutExt = withoutProtocol.replace(/\.json$/, "");
const rawSegments = withoutExt.split("/");
const SKIP_KEYS = new Set(["definitions", "$defs"]);
const segments = [];
for (let i = 0;i < rawSegments.length; i++) {
const s = rawSegments[i];
if (SKIP_KEYS.has(s))
continue;
const prevRaw = rawSegments[i - 1];
if (/^\d+\.\d+/.test(s) && prevRaw !== undefined && SKIP_KEYS.has(prevRaw))
continue;
segments.push(s);
}
const baseName = segments.map((s) => toKebabCase4(s).replace(/\./g, "-")).join("-");
if (!fragment)
return baseName;
const fragSegments = fragment.split("/").filter((s) => s && !SKIP_KEYS.has(s) && s !== "properties");
const fragLast = fragSegments[fragSegments.length - 1];
if (!fragLast)
return baseName;
return `${baseName}-${toKebabCase4(fragLast)}`;
};
var refToFilename4 = (ref) => {
if (ref.startsWith("http://") || ref.startsWith("https://")) {
return uriRefToFilename4(ref);
}
const segments = ref.split("/");
let filename = segments[segments.length - 1];
if (/[A-Z]/.test(filename)) {
filename = toKebabCase4(filename);
}
return filename;
};
var kebabToPascal2 = (kebab, suffix) => {
const words = kebab.split("-");
let pascalCase = "";
for (const word of words) {
pascalCase += word.charAt(0).toUpperCase() + word.slice(1);
}
return pascalCase + suffix;
};
var refToName2 = (ref, suffix = "") => kebabToPascal2(refToFilename4(ref), suffix);
var JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
var safeKey = (key) => {
if (JS_IDENTIFIER.test(key)) {
return key;
}
return `'${key}'`;
};
var getConditionalObjectSchema = (schema) => {
if (!isSchemaObject2(schema)) {
return null;
}
if (!("if" in schema) || !("then" in schema)) {
return null;
}
const ifSchema = schema.if;
const thenSchema = schema.then;
if (!isSchemaObject2(ifSchema) || !isSchemaObject2(thenSchema)) {
return null;
}
const ifProperties = ifSchema.properties;
const thenProperties = thenSchema.properties;
const hasIfProperties = ifProperties && typeof ifProperties === "object";
const hasThenProperties = thenProperties && typeof thenProperties === "object";
if (!hasIfProperties && !hasThenProperties) {
return null;
}
const properties = {
...hasIfProperties ? ifProperties : {},
...hasThenProperties ? thenProperties : {}
};
const required = new Set;
if (Array.isArray(ifSchema.required)) {
for (const key of ifSchema.required) {
required.add(key);
}
}
if (hasIfProperties) {
for (const key in ifProperties) {
required.add(key);
}
}
if (Array.isArray(thenSchema.required)) {
for (const key of thenSchema.required) {
required.add(key);
}
}
if (hasThenProperties) {
for (const key in thenProperties) {
required.add(key);
}
}
const thenRef = typeof thenSchema.$ref === "string" ? thenSchema.$ref : null;
return {
schema: {
type: "object",
properties,
...required.size > 0 ? { required: Array.from(required) } : {}
},
thenRef
};
};
var isObjectLikeSchema = (schema) => {
if (!isSchemaObject2(schema)) {
return false;
}
if (isObjectSchema(schema)) {
return true;
}
return "patternProperties" in schema || "additionalProperties" in schema || "if" in schema && "then" in schema;
};
var getBooleanSubSchemaType = (schema) => {
return schema ? "unknown" : "never";
};
var buildJsDocBlock = (title, description, commentUrl) => {
let block = `/**
`;
block += `* ${title}
`;
block += `*
`;
block += `* ${description}
`;
if (commentUrl?.startsWith("http")) {
block += `*
`;
block += `* @see {@link ${commentUrl}}
`;
}
block += `*/
`;
return block;
};
var getTypeScriptType = (schema, options = {}) => {
const base = getUnbrandedType(schema, options);
const brand = getMjstBrand(schema);
return brand ? `(${base} & { readonly __brand: '${brand}' })` : base;
};
var recordType = (keyType, valueType, options) => options.readonly ? `Readonly<Record<${keyType}, ${valueType}>>` : `Record<${keyType}, ${valueType}>`;
var getUnbrandedType = (schema, options = {}) => {
if (typeof schema === "boolean") {
return getBooleanSubSchemaType(schema);
}
if (typeof schema !== "object" || schema === null) {
return "unknown";
}
const instanceOf = getMjstInstanceOf(schema);
if (instanceOf) {
return instanceOf;
}
const primitive = getMjstPrimitive(schema);
if (primitive) {
return primitive;
}
if (schema.$ref) {
if (!schema.$ref.startsWith("#")) {
return "unknown";
}
return refToName2(schema.$ref, options.typeSuffix);
}
if (schema.$dynamicRef) {
if (schema.$dynamicRef === "#meta") {
return `Schema${options.typeSuffix ?? ""}`;
}
return refToName2(schema.$dynamicRef, options.typeSuffix);
}
if (schema.const !== undefined) {
return JSON.stringify(schema.const);
}
if (schema.enum && schema.enum.length > 0) {
if (schema.enum.length === 1) {
return JSON.stringify(schema.enum[0]);
}
let enumUnion = JSON.stringify(schema.enum[0]);
for (let i = 1;i < schema.enum.length; i++) {
enumUnion += " | " + JSON.stringify(schema.enum[i]);
}
return enumUnion;
}
if (schema.enum && schema.enum.length > 1) {
let multiEnumUnion = JSON.stringify(schema.enum[0]);
for (let i = 1;i < schema.enum.length; i++) {
multiEnumUnion += " | " + JSON.stringify(schema.enum[i]);
}
return multiEnumUnion;
}
if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
let oneOfUnion = getTypeScriptType(schema.oneOf[0], options);
for (let i = 1;i < schema.oneOf.length; i++) {
oneOfUnion += " | " + getTypeScriptType(schema.oneOf[i], options);
}
return oneOfUnion;
}
if (schema.anyOf && Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
let anyOfUnion = getTypeScriptType(schema.anyOf[0], options);
for (let i = 1;i < schema.anyOf.length; i++) {
anyOfUnion += " | " + getTypeScriptType(schema.anyOf[i], options);
}
return anyOfUnion;
}
if (schema.allOf && Array.isArray(schema.allOf) && schema.allOf.length > 0) {
let intersectionTypes = getTypeScriptType(schema.allOf[0], options);
for (let i = 1;i < schema.allOf.length; i++) {
intersectionTypes += " & " + getTypeScriptType(schema.allOf[i], options);
}
return intersectionTypes;
}
const conditionalResult = getConditionalObjectSchema(schema);
if (conditionalResult) {
const baseType = getTypeScriptType(conditionalResult.schema, options);
if (conditionalResult.thenRef) {
return `(${baseType}) & ${refToName2(conditionalResult.thenRef, options.typeSuffix)}`;
}
return baseType;
}
if (!schema.type) {
if (schema.additionalProperties !== undefined) {
if (typeof schema.additionalProperties === "boolean") {
return recordType("string", getBooleanSubSchemaType(schema.additionalProperties), options);
}
return recordType("string", getTypeScriptType(schema.additionalProperties, options), options);
}
if (schema.patternProperties && typeof schema.patternProperties === "object") {
const firstEntry = Object.entries(schema.patternProperties)[0];
if (firstEntry) {
const [pattern, value] = firstEntry;
if (value !== undefined) {
const valueType = typeof value === "boolean" ? getBooleanSubSchemaType(value) : getTypeScriptType(value, options);
if (pattern === "^x-") {
return recordType("`x-${string}`", valueType, options);
}
return recordType("string", valueType, options);
}
}
}
if (schema.default !== undefined) {
if (typeof schema.default === "string") {
return "string";
}
if (typeof schema.default === "number") {
return "number";
}
if (typeof schema.default === "boolean") {
return "boolean";
}
}
return "unknown";
}
if (Array.isArray(schema.type)) {
const mapType = (t) => {
switch (t) {
case "string":
return "string";
case "number":
case "integer":
return "number";
case "boolean":
return "boolean";
case "null":
return "null";
case "array":
return "unknown[]";
case "object":
return "Record<string, unknown>";
default:
return "unknown";
}
};
let typeUnion = mapType(schema.type[0]);
for (let i = 1;i < schema.type.length; i++) {
typeUnion += " | " + mapType(schema.type[i]);
}
return typeUnion;
}
switch (schema.type) {
case "string":
return "string";
case "number":
case "integer":
return "number";
case "boolean":
return "boolean";
case "array":
if (schema.items) {
const itemType = getTypeScriptType(schema.items, options);
const wrappedItemType = itemType.includes(" | ") ? `(${itemType})` : itemType;
return options.readonly ? `readonly ${wrappedItemType}[]` : `${wrappedItemType}[]`;
}
return options.readonly ? "readonly unknown[]" : "unknown[]";
case "object":
if (schema.properties) {
const readonlyPrefix = options.readonly ? "readonly " : "";
const hasDescriptions = Object.values(schema.properties).some((p) => isSchemaObject2(p) && (typeof p.description === "string" || typeof p.$comment === "string"));
if (hasDescriptions) {
let properties2 = "";
let first2 = true;
for (const key in schema.properties) {
const propSchema = schema.properties[key];
const isRequired = schema.required?.includes(key) ?? false;
const optional = isRequired ? "" : "?";
const propType = getTypeScriptType(propSchema, options);
const inlineDescription = isSchemaObject2(propSchema) && typeof propSchema.description === "string" ? propSchema.description : isSchemaObject2(propSchema) && typeof propSchema.$comment === "string" ? propSchema.$comment : undefined;
if (!first2)
properties2 += `
`;
first2 = false;
if (inlineDescription) {
properties2 += " /** " + inlineDescription + ` */
` + readonlyPrefix + safeKey(key) + optional + ": " + propType + ";";
} else {
properties2 += " " + readonlyPrefix + safeKey(key) + optional + ": " + propType + ";";
}
}
return `{
` + properties2 + `
}`;
}
let properties = "";
let first = true;
for (const key in schema.properties) {
const propSchema = schema.properties[key];
const isRequired = schema.required?.includes(key) ?? false;
const optional = isRequired ? "" : "?";
const propType = getTypeScriptType(propSchema, options);
if (!first)
properties += "; ";
properties += readonlyPrefix + safeKey(key) + optional + ": " + propType;
first = false;
}
return "{ " + properties + " }";
}
if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
const additionalPropType = getTypeScriptType(schema.additionalProperties, options);
return recordType("string", additionalPropType, options);
}
if (schema.patternProperties && typeof schema.patternProperties === "object") {
const firstEntry = Object.entries(schema.patternProperties)[0];
if (firstEntry) {
const [pattern, patternVal] = firstEntry;
if (patternVal) {
const valueType = getTypeScriptType(patternVal, options);
if (pattern === "^x-") {
return recordType("`x-${string}`", valueType, options);
}
return recordType("string", valueType, options);
}
}
}
return "object";
default:
return "unknown";
}
};
var generateTypeDefinition = (schema, typeName, options = {}) => {
const readonlyPrefix = options.readonly ? "readonly " : "";
if (!isObjectLikeSchema(schema)) {
const tsType = getTypeScriptType(schema, options);
let result = "";
const topLevelComment = isSchemaObject2(schema) && typeof schema.description === "string" && schema.description || isSchemaObject2(schema) && typeof schema.$comment === "string" && schema.$comment || undefined;
if (topLevelComment) {
result += buildJsDocBlock(typeName, topLevelComment);
}
result += `export type ${typeName} = ${tsType};`;
return result;
}
if (isObjectLikeSchema(schema)) {
const conditionalResult = getConditionalObjectSchema(schema);
const normalizedSchema = conditionalResult?.schema ?? schema;
const conditionalThenRef = conditionalResult?.thenRef ?? null;
let jsDocTitle;
let jsDocDescription;
const topLevelComment = isSchemaObject2(schema) && typeof schema.description === "string" && schema.description || isSchemaObject2(schema) && typeof schema.$comment === "string" && schema.$comment || undefined;
if (topLevelComment) {
jsDocTitle = typeName;
jsDocDescription = topLevelComment;
}
const hasProperties2 = normalizedSchema.properties && Object.keys(normalizedSchema.properties).length > 0;
const hasAdditionalProperties2 = normalizedSchema.additionalProperties && typeof normalizedSchema.additionalProperties === "object";
const hasPatternProperties = normalizedSchema.patternProperties && typeof normalizedSchema.patternProperties === "object" && Object.keys(normalizedSchema.patternProperties).length > 0;
if (!hasProperties2 && hasPatternProperties && normalizedSchema.patternProperties) {
const firstEntry = Object.entries(normalizedSchema.patternProperties)[0];
const firstPattern = firstEntry?.[0];
const firstPatternProperty = firstEntry?.[1];
if (firstPatternProperty === undefined) {
return `export type ${typeName} = Record<string, unknown>;`;
}
const patternPropType = typeof firstPatternProperty === "boolean" ? getBooleanSubSchemaType(firstPatternProperty) : getTypeScriptType(firstPatternProperty, options);
const keyType = firstPattern === "^x-" ? "`x-${string}`" : "string";
let result2 = "";
if (jsDocTitle && jsDocDescription) {
result2 += buildJsDocBlock(jsDocTitle, jsDocDescription);
}
result2 += `export type ${typeName} = ${recordType(keyType, patternPropType, options)};`;
return result2;
}
if (!hasProperties2 && hasAdditionalProperties2 && normalizedSchema.additionalProperties) {
const additionalPropType = getTypeScriptType(normalizedSchema.additionalProperties, options);
let result2 = "";
if (jsDocTitle && jsDocDescription) {
result2 += buildJsDocBlock(jsDocTitle, jsDocDescription);
}
result2 += `export type ${typeName} = {
${readonlyPrefix}[key: string]: ${additionalPropType};
};`;
return result2;
}
const schemaProps = normalizedSchema.properties ?? {};
let properties = "";
let isFirstProp = true;
for (const key in schemaProps) {
const propSchema = schemaProps[key];
const isRequired = normalizedSchema.required?.includes(key) ?? false;
const optional = isRequired ? "" : "?";
const propType = getTypeScriptType(propSchema, options);
const quotedKey = readonlyPrefix + safeKey(key);
if (!isFirstProp)
properties += `
`;
isFirstProp = false;
const inlineDescription = isSchemaObject2(propSchema) && typeof propSchema.description === "string" ? propSchema.description : isSchemaObject2(propSchema) && typeof propSchema.$comment === "string" ? propSchema.$comment : undefined;
if (inlineDescription) {
properties += " /** " + inlineDescription + ` */
` + quotedKey + optional + ": " + propType + ";";
} else {
properties += " " + quotedKey + optional + ": " + propType + ";";
}
}
const allOfIntersections = [];
if (isSchemaObject2(schema) && Array.isArray(schema.allOf)) {
for (const entry of schema.allOf) {
if (isSchemaObject2(entry) && entry.$ref) {
allOfIntersections.push(refToName2(entry.$ref, options.typeSuffix));
}
}
}
if (isSchemaObject2(schema) && typeof schema.$ref === "string" && schema.$ref.startsWith("#")) {
allOfIntersections.push(refToName2(schema.$ref, options.typeSuffix));
}
let result = "";
if (jsDocTitle && jsDocDescription) {
result += buildJsDocBlock(jsDocTitle, jsDocDescription);
}
let typeBody = `{
` + properties + `
}`;
if (conditionalThenRef) {
typeBody += " & " + refToName2(conditionalThenRef, options.typeSuffix);
}
for (const intersectionType of allOfIntersections) {
typeBody += " & " + intersectionType;
}
result += "export type " + typeName + " = " + typeBody + ";";
return result;
}
return "export type " + typeName + " = unknown;";
};
// ../helpers/dist/schema-guards.js
var hasRef = (value) => {
return typeof value === "object" && value !== null && !Array.isArray(value) && "$ref" in value && typeof value.$ref === "string";
};
var isSchemaObject3 = (schema) => {
return typeof schema === "object" && schema !== null && typeof schema !== "boolean";
};
var hasType = (schema) => {
return isSchemaObject3(schema) && "type" in schema && typeof schema.type === "string";
};
var hasProperties = (schema) => {
return isSchemaObject3(schema) && "properties" in schema && typeof schema.properties === "object" && schema.properties !== null;
};
var hasEnum = (schema) => {
return isSchemaObject3(schema) && "enum" in schema && Array.isArray(schema.enum);
};
var hasConst = (schema) => {
return isSchemaObject3(schema) && "const" in schema;
};
var hasPattern = (schema) => {
return isSchemaObject3(schema) && "pattern" in schema && typeof schema.pattern === "string";
};
var hasFormat = (schema) => {
return isSchemaObject3(schema) && "format" in schema && typeof schema.format === "string";
};
var hasDefault = (schema) => {
return isSchemaObject3(schema) && "default" in schema;
};
var hasExamples = (schema) => {
return isSchemaObject3(schema) && "examples" in schema && Array.isArray(schema.examples);
};
var hasOneOf = (schema) => {
return isSchemaObject3(schema) && "oneOf" in schema && Array.isArray(schema.oneOf);
};
var hasAnyOf = (schema) => {
return isSchemaObject3(schema) && "anyOf" in schema && Array.isArray(schema.anyOf);
};
var hasAllOf = (schema) => {
return isSchemaObject3(schema) && "allOf" in schema && Array.isArray(schema.allOf);
};
var hasRequired = (schema) => {
return isSchemaObject3(schema) && "required" in schema && Array.isArray(schema.required);
};
var hasItems = (schema) => {
return isSchemaObject3(schema) && "items" in schema && typeof schema.items === "object" && schema.items !== null && typeof schema.items !== "boolean";
};
var hasAdditionalProperties = (schema) => {
return isSchemaObject3(schema) && "additionalProperties" in schema;
};
var hasMinLength = (schema) => {
return isSchemaObject3(schema) && "minLength" in schema && typeof schema.minLength === "number";
};
var hasMaxLength = (schema) => {
return isSchemaObject3(schema) && "maxLength" in schema && typeof schema.maxLength === "number";
};
var hasMinimum = (schema) => {
return isSchemaObject3(schema) && "minimum" in schema && typeof schema.minimum === "number";
};
var hasMaximum = (schema) => {
return isSchemaObject3(schema) && "maximum" in schema && typeof schema.maximum === "number";
};
var hasExclusiveMinimum = (schema) => {
return isSchemaObject3(schema) && "exclusiveMinimum" in schema && typeof schema.exclusiveMinimum === "number";
};
var hasExclusiveMaximum = (schema) => {
return isSchemaObject3(schema) && "exclusiveMaximum" in schema && typeof schema.exclusiveMaximum === "number";
};
var hasMultipleOf = (schema) => {
return isSchemaObject3(schema) && "multipleOf" in schema && typeof schema.multipleOf === "number";
};
var hasMinItems = (schema) => {
return isSchemaObject3(schema) && "minItems" in schema && typeof schema.minItems === "number";
};
var hasMaxItems = (schema) => {
return isSchemaObject3(schema) && "maxItems" in schema && typeof schema.maxItems === "number";
};
var hasUniqueItems = (schema) => {
return isSchemaObject3(schema) && "uniqueItems" in schema && typeof schema.uniqueItems === "boolean";
};
// src/generators/collect-example-imports.ts
var buildImport = (ref, suffix) => {
const filename = refToFilename(ref);
const typeName = refToName(ref, suffix);
return `import { type ${typeName}, ${typeName}Arbitrary } from './${filename}'`;
};
var collectDirectRefs = (schema) => {
if (typeof schema === "boolean" || schema === null)
return [];
const refs = [];
if (hasRef(schema)) {
refs.push(schema.$ref);
return refs;
}
const propSchemas = "properties" in schema && typeof schema.properties === "object" && schema.properties !== null ? Object.values(schema.properties) : [];
for (const prop of propSchemas) {
if (hasRef(prop))
refs.push(prop.$ref);
if (hasItems(prop) && hasRef(prop.items))
refs.push(prop.items.$ref);
if (hasAdditionalProperties(prop) && hasRef(prop.additionalProperties)) {
refs.push(prop.additionalProperties.$ref);
}
}
if (hasItems(schema) && hasRef(schema.items)) {
refs.push(schema.items.$ref);
}
if (hasAdditionalProperties(schema) && hasRef(schema.additionalProperties)) {
refs.push(schema.additionalProperties.$ref);
}
for (const branch of [
...hasOneOf(schema) ? schema.oneOf : [],
...hasAnyOf(schema) ? schema.anyOf : [],
...hasAllOf(schema) ? schema.allOf : []
]) {
if (hasRef(branch))
refs.push(branch.$ref);
}
return refs;
};
var collectExampleImports = (schema, options) => {
const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : null;
const rootSchema = options?.rootSchema;
const typeSuffix = options?.typeSuffix ?? "";
const refs = collectDirectRefs(schema);
const seen = new Set;
const imports = [];
for (const ref of refs) {
const filename = refToFilename(ref);
if (seen.has(filename))
continue;
if (selfFilename && filename === selfFilename)
continue;
if (rootSchema) {
const resolved = resolveRef(ref, rootSchema);
if (!resolved)
continue;
}
seen.add(filename);
imports.push(buildImport(ref, typeSuffix));
}
return imports;
};
// ../helpers/dist/mjst-extension.js
var isSchemaObject4 = (schema) => {
return typeof schema === "object" && schema !== null && typeof schema !== "boolean";
};
var MJST_EXTENSION_KEY2 = "x-mjst";
var IDENTIFIER2 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
var SUPPORTED_PRIMITIVES2 = new Set(["bigint"]);
var readExtensionString2 = (schema, field) => {
if (!isSchemaObject4(schema))
return;
const extension = schema[MJST_EXTENSION_KEY2];
if (typeof extension !== "object" || extension === null)
return;
const value = extension[field];
return typeof value === "string" ? value : undefined;
};
var getMjstInstanceOf2 = (schema) => {
const instanceOf = readExtensionString2(schema, "instanceOf");
return instanceOf !== undefined && IDENTIFIER2.test(instanceOf) ? instanceOf : undefined;
};
var getMjstPrimitive2 = (schema) => {
const primitive = readExtensionString2(schema, "primitive");
return primitive !== undefined && SUPPORTED_PRIMITIVES2.has(primitive) ? primitive : undefined;
};
// src/generators/derive-example.ts
var lowerFirst = (name) => name.charAt(0).toLowerCase() + name.slice(1);
var exampleName = (typeName) => `${lowerFirst(typeName)}Example`;
var exampleString = (schema) => {
if (hasFormat(schema)) {
switch (schema.format) {
case "email":
return "user@example.com";
case "uuid":
return "00000000-0000-0000-0000-000000000000";
case "uri":
case "url":
return "https://example.com";
case "date-time":
return "1970-01-01T00:00:00.000Z";
case "date":
return "1970-01-01";
}
}
let value = "string";
if (hasMinLength(schema) && value.length < schema.minLength)
value = value.padEnd(schema.minLength, "x");
if (hasMaxLength(schema) && value.length > schema.maxLength)
value = value.slice(0, schema.maxLength);
return value;
};
var deriveExample = (schema, rootSchema, seen = new Set) => {
if (!isSchemaObject3(schema))
return null;
if (hasConst(schema))
return schema.const;
if (hasExamples(schema) && Array.isArray(schema.examples) && schema.examples.length > 0)
return schema.examples[0];
if (hasDefault(schema))
return schema.default;
if (hasEnum(schema) && schema.enum.length > 0)
return schema.enum[0];
if (hasRef(schema)) {
const ref = schema.$ref;
if (seen.has(ref) || !rootSchema)
return null;
const resolved = resolveRef(ref, rootSchema);
if (!resolved)
return null;
return deriveExample(resolved, rootSchema, new Set([...seen, ref]));
}
const instanceOf = getMjstInstanceOf2(schema);
if (instanceOf === "Date")
return new Date(0);
const primitive = getMjstPrimitive2(schema);
if (primitive === "bigint")
return 0n;
if (hasOneOf(schema) && schema.oneOf[0] !== undefined)
return deriveExample(schema.oneOf[0], rootSchema, seen);
if (hasAnyOf(schema) && schema.anyOf[0] !== undefined)
return deriveExample(schema.anyOf[0], rootSchema, seen);
if (!hasType(schema))
return null;
switch (schema.type) {
case "string":
return exampleString(schema);
case "number":
case "integer":
return hasMinimum(schema) ? schema.minimum : 0;
case "boolean":
return true;
case "null":
return null;
case "array": {
const item = hasItems(schema) ? deriveExample(schema.items, rootSchema, seen) : null;
const count = hasMinItems(schema) ? Math.max(schema.minItems, 1) : 1;
return Array.from({ length: count }, () => item);
}
case "object": {
const out = {};
if (hasProperties(schema)) {
for (const [key, propSchema] of Object.entries(schema.properties)) {
out[key] = deriveExample(propSchema, rootSchema, seen);
}
}
return out;
}
default:
return null;
}
};
var serializeValue = (value) => {
if (typeof value === "bigint")
return `${value}n`;
if (value instanceof Date)
return `new Date(${JSON.stringify(value.toISOString())})`;
if (Array.isArray(value))
return `[${value.map(serializeValue).join(", ")}]`;
if (value !== null && typeof value === "object") {
const entries = Object.entries(value).filter(([, v]) => v !== undefined).map(([key, v]) => `${JSON.stringify(key)}: ${serializeValue(v)}`);
return `{ ${entries.join(", ")} }`;
}
return JSON.stringify(value);
};
var generateExampleConst = (schema, typeName, rootSchema) => {
const value = deriveExample(schema, rootSchema);
return `export const ${exampleName(typeName)}: ${typeName} = ${serializeValue(value)}`;
};
// src/generators/generate-arbitrary.ts
var arbitraryName = (typeName) => `${typeName}Arbitrary`;
var stringExpr = (schema) => {
if (hasFormat(schema)) {
switch (schema.format) {
case "email":
return "fc.emailAddress()";
case "uuid":
return "fc.uuid()";
case "uri":
case "url":
return "fc.webUrl()";
case "date-time":
return "fc.date({ noInvalidDate: true }).map((d) => d.toISOString())";
case "date":
return "fc.date({ noInvalidDate: true }).map((d) => d.toISOString().slice(0, 10))";
}
}
if (hasPattern(schema))
return `fc.stringMatching(/${schema.pattern}/)`;
const opts = [];
if (hasMinLength(schema))
opts.push(`minLength: ${schema.minLength}`);
if (hasMaxLength(schema))
opts.push(`maxLength: ${schema.maxLength}`);
return opts.length > 0 ? `fc.string({ ${opts.join(", ")} })` : "fc.string()";
};
var integerExpr = (schema) => {
const opts = [];
if (hasMinimum(schema))
opts.push(`min: ${schema.minimum}`);
else if (hasExclusiveMinimum(schema))
opts.push(`min: ${Number(schema.exclusiveMinimum) + 1}`);
if (hasMaximum(schema))
opts.push(`max: ${schema.maximum}`);
else if (hasExclusiveMaximum(schema))
opts.push(`max: ${Number(schema.exclusiveMaximum) - 1}`);
const base = opts.length > 0 ? `fc.integer({ ${opts.join(", ")} })` : "fc.integer()";
return hasMultipleOf(schema) ? `${base}.filter((n) => n % ${schema.multipleOf} === 0)` : base;
};
var numberExpr = (schema) => {
const opts = ["noNaN: true", "noDefaultInfinity: true"];
if (hasMinimum(schema))
opts.push(`min: ${schema.minimum}`);
else if (hasExclusiveMinimum(schema))
opts.push(`min: ${schema.exclusiveMinimum}`, "minExcluded: true");
if (hasMaximum(schema))
opts.push(`max: ${schema.maximum}`);
else if (hasExclusiveMaximum(schema))
opts.push(`max: ${schema.exclusiveMaximum}`, "maxExcluded: true");
const base = `fc.double({ ${opts.join(", ")} })`;
return hasMultipleOf(schema) ? `${base}.filter((n) => n % ${schema.multipleOf} === 0)` : base;
};
var arrayExpr = (schema, suffix) => {
const items = hasItems(schema) && isSchemaObject3(schema.items) ? arbitraryExpr(schema.items, suffix) : "fc.anything()";
const opts = [];
if (hasMinItems(schema))
opts.push(`minLength: ${schema.minItems}`);
if (hasMaxItems(schema))
opts.push(`maxLength: ${schema.maxItems}`);
const fn = hasUniqueItems(schema) && schema.uniqueItems === true ? "fc.uniqueArray" : "fc.array";
return opts.length > 0 ? `${fn}(${items}, { ${opts.join(", ")} })` : `${fn}(${items})`;
};
var objectExpr = (schema, suffix) => {
if (!hasProperties(schema))
return "fc.object()";
const required = new Set(hasRequired(schema) ? schema.required : []);
const keys = Object.keys(schema.properties);
const entries = Object.entries(schema.properties).map(([key, propSchema]) => `${JSON.stringify(key)}: ${arbitraryExpr(propSchema, suffix)}`);
if (entries.length === 0)
return "fc.record({})";
const model = `{ ${entries.join(", ")} }`;
if (keys.every((key) => required.has(key)))
return `fc.record(${model})`;
const requiredKeys = [...required].map((key) => JSON.stringify(key)).join(", ");
return `fc.record(${model}, { requiredKeys: [${requiredKeys}] })`;
};
var oneofExpr = (branches, suffix) => {
const exprs = branches.map((branch) => arbitraryExpr(branch, suffix));
return `fc.oneof(${exprs.join(", ")})`;
};
var scalarExpr = (type, schema, suffix) => {
switch (type) {
case "string":
return stringExpr(schema);
case "integer":
return integerExpr(schema);
case "number":
return numberExpr(schema);
case "boolean":
return "fc.boolean()";
case "null":
return "fc.constant(null)";
case "array":
return arrayExpr(schema, suffix);
case "object":
return objectExpr(schema, suffix);
default:
return "fc.anything()";
}
};
var arbitraryExpr = (schema, suffix) => {
if (!isSchemaObject3(schema))
return "fc.anything()";
if (hasRef(schema))
return arbitraryName(refToName(schema.$ref, suffix));
if (hasConst(schema))
return `fc.constant(${JSON.stringify(schema.const)})`;
if (hasEnum(schema)) {
const values = schema.enum.map((value) => JSON.stringify(value)).join(", ");
return `fc.constantFrom(${values})`;
}
const instanceOf = getMjstInstanceOf2(schema);
if (instanceOf === "Date")
return "fc.date({ noInvalidDate: true })";
if (instanceOf)
return "fc.anything()";
const primitive = getMjstPrimitive2(schema);
if (primitive === "bigint")
return "fc.bigInt()";
if (primitive)
return "fc.anything()";
if (hasOneOf(schema))
return oneofExpr(schema.oneOf, suffix);
if (hasAnyOf(schema))
return oneofExpr(schema.anyOf, suffix);
if (hasType(schema))
return scalarExpr(schema.type, schema, suffix);
return "fc.anything()";
};
var generateArbitrary = (schema, typeName, suffix = "") => {
const expr = arbitraryExpr(schema, suffix);
return `export const ${arbitraryName(typeName)}: fc.Arbitrary<${typeName}> = ${expr}`;
};
// src/generators/generate-files.ts
var generateExampleFile = (schema, typeName, options) => {
const typeSuffix = options?.typeSuffix ?? "";
const refImports = collectExampleImports(schema, {
selfRef: options?.selfRef,
rootSchema: options?.rootSchema,
typeSuffix
});
const typeDefinition = generateTypeDefinition(schema, typeName, { typeSuffix });
const arbitrary = generateArbitrary(schema, typeName, typeSuffix);
const example = generateExampleConst(schema, typeName, options?.rootSchema);
let result = `import * as fc from 'fast-check'
`;
for (const imp of refImports) {
result += imp + `
`;
}
result += `
`;
result += typeDefinition + `
` + arbitrary + `
` + example + `
`;
return result;
};
// src/generators/build-schema.ts
var buildExampleSchema = async (rootSchema, rootTypeName, typeSuffix = "") => {
rootSchema = upgradeDraft07Schema(rootSchema);
const files = [];
const processedRefs = new Set;
const processedFilenames = new Set;
const refsToProcess = [];
const dynamicRefMap = buildDynamicRefMap(rootSchema);
const processedRootSchema = resolveDynamicRefs(rootSchema, dynamicRefMap);
const rootContent = generateExampleFile(processedRootSchema, rootTypeName, {
rootSchema,
typeSuffix
});
const rootFilename = rootTypeName.toLowerCase();
if (rootFilename !== "index") {
processedFilenames.add(rootFilename);
files.push({ filename: `${rootFilename}.ts`, content: rootContent });
}
const rootRefs = extractRefs(rootSchema);
refsToProcess.push(...rootRefs);
while (refsToProcess.length > 0) {
const ref = refsToProcess.shift();
if (!ref || processedRefs.has(ref))
continue;
processedRefs.add(ref);
const resolvedSchema = resolveRef(ref, rootSchema);
if (!resolvedSchema) {
console.warn(`Warning: Could not resolve ref: ${ref}`);
continue;
}
const typeName = refToName(ref, typeSuffix);
const filename = refToFilename(ref);
const processedSchema = resolveDynamicRefs(resolvedSchema, dynamicRefMap);
const content = generateExampleFile(processedSchema, typeName, {
selfRef: ref,
rootSchema,
typeSuffix
});
if (filename !== "index" && !processedFilenames.has(filename)) {
processedFilenames.add(filename);
files.push({ filename: `${filename}.ts`, content });
}
for (const nestedRef of extractRefs(resolvedSchema)) {
if (!processedRefs.has(nestedRef))
refsToProcess.push(nestedRef);
}
}
const TYPE_EXPORT_RE = /^export type (\w+)/gm;
const CONST_EXPORT_RE = /^export const (\w+)/gm;
const sortedFiles = [...files].sort((a, b) => a.filename.localeCompare(b.filename));
let indexContent = "";
for (const file of sortedFiles) {
const moduleName = file.filename.replace(/\.ts$/, "");
const typeNames = [];
const constNames = [];
for (const match of file.content.matchAll(TYPE_EXPORT_RE))
typeNames.push(match[1]);
for (const match of file.content.matchAll(CONST_EXPORT_RE))
constNames.push(match[1]);
if (typeNames.length === 0 && constNames.length === 0)
continue;
const typeExports = typeNames.map((n) => `type ${n}`);
indexContent += `export { ${[...typeExports, ...constNames].join(", ")} } from './${moduleName}';
`;
}
files.push({ filename: "index.ts", content: indexContent });
return files;
};
export {
serializeValue,
generateExampleConst,
generateArbitrary,
deriveExample,
buildExampleSchema
};
export { buildExampleSchema } from './generators/build-schema.js';
export { deriveExample, generateExampleConst, serializeValue } from './generators/derive-example.js';
export { generateArbitrary } from './generators/generate-arbitrary.js';
{
"name": "@amritk/generate-examples",
"version": "0.1.1",
"version": "0.2.0",
"description": "Generate fast-check arbitraries and example values from JSON Schemas.",

@@ -36,5 +36,5 @@ "module": "./dist/index.js",

"scripts": {
"build": "bun run build:code && bun run build:types",
"build:code": "bun build ./src/index.ts --outdir=dist --target=node",
"build:types": "tsc -p ."
"build": "tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f",
"types:check": "tsgo -p . --noEmit",
"test": "NODE_ENV=production vitest run --root ../.. generate-examples"
},

@@ -46,3 +46,3 @@ "imports": {

".": {
"bun": "./src/index.ts",
"development": "./src/index.ts",
"default": "./dist/index.js",

@@ -54,3 +54,3 @@ "types": "./dist/index.d.ts"

"json-schema-typed": "^8.0.1",
"@amritk/helpers": "0.6.2"
"@amritk/helpers": "0.7.0"
},

@@ -57,0 +57,0 @@ "peerDependencies": {

@@ -1,8 +0,3 @@

import { buildDynamicRefMap } from '@amritk/helpers/build-dynamic-ref-map'
import { extractRefs } from '@amritk/helpers/extract-refs'
import { refToFilename } from '@amritk/helpers/ref-to-filename'
import { refToName } from '@amritk/helpers/ref-to-name'
import { resolveDynamicRefs } from '@amritk/helpers/resolve-dynamic-refs'
import { resolveRef } from '@amritk/helpers/resolve-ref'
import { upgradeDraft07Schema } from '@amritk/helpers/upgrade-draft07-schema'
import { generateIndexBarrel } from '@amritk/helpers/generate-index-barrel'
import { walkRefGraph } from '@amritk/helpers/walk-ref-graph'
import type { JSONSchema } from 'json-schema-typed/draft-2020-12'

@@ -22,3 +17,4 @@

* Builds all TypeScript example files from a JSON Schema by traversing all
* $ref references recursively, mirroring the generate-parsers pipeline.
* `$ref` / `$dynamicRef` references recursively (via the shared
* `@amritk/helpers/walk-ref-graph` walker).
*

@@ -49,81 +45,20 @@ * Each generated file exports:

): Promise<GeneratedFile[]> => {
rootSchema = upgradeDraft07Schema(rootSchema as Record<string, unknown>) as JSONSchema
const files: GeneratedFile[] = []
const processedRefs = new Set<string>()
const processedFilenames = new Set<string>()
const refsToProcess: string[] = []
const dynamicRefMap = buildDynamicRefMap(rootSchema)
walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
// `index` is reserved for the barrel below, so never let a definition of
// that name overwrite it.
if (node.filename === 'index') return
// Root schema
const processedRootSchema = resolveDynamicRefs(rootSchema, dynamicRefMap)
const rootContent = generateExampleFile(processedRootSchema, rootTypeName, {
rootSchema: rootSchema as Record<string, unknown>,
typeSuffix,
})
const rootFilename = rootTypeName.toLowerCase()
if (rootFilename !== 'index') {
processedFilenames.add(rootFilename)
files.push({ filename: `${rootFilename}.ts`, content: rootContent })
}
const rootRefs = extractRefs(rootSchema)
refsToProcess.push(...rootRefs)
while (refsToProcess.length > 0) {
const ref = refsToProcess.shift()
if (!ref || processedRefs.has(ref)) continue
processedRefs.add(ref)
const resolvedSchema = resolveRef(ref, rootSchema as Record<string, unknown>)
if (!resolvedSchema) {
console.warn(`Warning: Could not resolve ref: ${ref}`)
continue
}
const typeName = refToName(ref, typeSuffix)
const filename = refToFilename(ref)
const processedSchema = resolveDynamicRefs(resolvedSchema as JSONSchema, dynamicRefMap)
const content = generateExampleFile(processedSchema, typeName, {
selfRef: ref,
rootSchema: rootSchema as Record<string, unknown>,
const content = generateExampleFile(node.schema, node.typeName, {
rootSchema: node.rootSchema,
typeSuffix,
...(node.ref !== undefined ? { selfRef: node.ref } : {}),
})
files.push({ filename: `${node.filename}.ts`, content })
})
if (filename !== 'index' && !processedFilenames.has(filename)) {
processedFilenames.add(filename)
files.push({ filename: `${filename}.ts`, content })
}
files.push({ filename: 'index.ts', content: generateIndexBarrel(files) })
for (const nestedRef of extractRefs(resolvedSchema as JSONSchema)) {
if (!processedRefs.has(nestedRef)) refsToProcess.push(nestedRef)
}
}
// Generate index.ts barrel
const TYPE_EXPORT_RE = /^export type (\w+)/gm
const CONST_EXPORT_RE = /^export const (\w+)/gm
const sortedFiles = [...files].sort((a, b) => a.filename.localeCompare(b.filename))
let indexContent = ''
for (const file of sortedFiles) {
const moduleName = file.filename.replace(/\.ts$/, '')
const typeNames: string[] = []
const constNames: string[] = []
for (const match of file.content.matchAll(TYPE_EXPORT_RE)) typeNames.push(match[1] as string)
for (const match of file.content.matchAll(CONST_EXPORT_RE)) constNames.push(match[1] as string)
if (typeNames.length === 0 && constNames.length === 0) continue
const typeExports = typeNames.map((n) => `type ${n}`)
indexContent += `export { ${[...typeExports, ...constNames].join(', ')} } from './${moduleName}';\n`
}
files.push({ filename: 'index.ts', content: indexContent })
return files
}