wip monorepo
This commit is contained in:
684
packages/dsc-ts/scripts/lib/json-schema-to-effect.ts
Normal file
684
packages/dsc-ts/scripts/lib/json-schema-to-effect.ts
Normal file
@@ -0,0 +1,684 @@
|
||||
/**
|
||||
* JSON Schema to Effect Schema Converter
|
||||
*
|
||||
* Converts JSON Schema to Effect Schema TypeScript code with proper
|
||||
* handling of recursive types using S.suspend() and interface definitions.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
type JsonSchema = {
|
||||
$ref?: string;
|
||||
$defs?: Record<string, JsonSchema>;
|
||||
definitions?: Record<string, JsonSchema>;
|
||||
type?: string | Array<string>;
|
||||
properties?: Record<string, JsonSchema>;
|
||||
required?: Array<string>;
|
||||
items?: JsonSchema;
|
||||
enum?: Array<unknown>;
|
||||
const?: unknown;
|
||||
oneOf?: Array<JsonSchema>;
|
||||
anyOf?: Array<JsonSchema>;
|
||||
allOf?: Array<JsonSchema>;
|
||||
additionalProperties?: boolean | JsonSchema;
|
||||
title?: string;
|
||||
description?: string;
|
||||
default?: unknown;
|
||||
nullable?: boolean;
|
||||
};
|
||||
|
||||
type TypeDefinition = {
|
||||
name: string;
|
||||
schema: JsonSchema;
|
||||
isRecursive: boolean;
|
||||
dependencies: Set<string>;
|
||||
};
|
||||
|
||||
type ConversionContext = {
|
||||
definitions: Map<string, TypeDefinition>;
|
||||
recursiveTypes: Set<string>;
|
||||
currentPath: Array<string>;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Utilities
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Convert a string to PascalCase for type names
|
||||
*/
|
||||
function toPascalCase(str: string): string {
|
||||
return str
|
||||
.split(/[-_\s]+/)
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the definition name from a $ref
|
||||
*/
|
||||
function getRefName(ref: string): string {
|
||||
const parts = ref.split('/');
|
||||
return parts.at(-1) ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a schema represents null
|
||||
*/
|
||||
function isJsonSchemaNull(s: JsonSchema): boolean {
|
||||
return (
|
||||
typeof s === 'object' &&
|
||||
s !== null &&
|
||||
(s.type === 'null' || ('const' in s && s.const === null))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a schema is nullable (has null in type array, nullable: true, or null in oneOf/anyOf)
|
||||
*/
|
||||
function isNullable(schema: JsonSchema): boolean {
|
||||
if (typeof schema !== 'object' || schema === null) return false;
|
||||
if (schema.nullable) return true;
|
||||
if (Array.isArray(schema.type) && schema.type.includes('null')) return true;
|
||||
// Check for null in oneOf/anyOf
|
||||
const items = schema.oneOf || schema.anyOf;
|
||||
if (items) {
|
||||
return items.some(isJsonSchemaNull);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the non-null type from a schema
|
||||
*/
|
||||
function getNonNullType(schema: JsonSchema): string | undefined {
|
||||
if (typeof schema !== 'object' || schema === null) return;
|
||||
if (Array.isArray(schema.type)) {
|
||||
const nonNull = schema.type.filter((t) => t !== 'null');
|
||||
return nonNull.length === 1 ? nonNull[0] : undefined;
|
||||
}
|
||||
return schema.type;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Schema Analysis
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Collect all definitions from the schema
|
||||
*/
|
||||
function collectDefinitions(schema: JsonSchema): Map<string, TypeDefinition> {
|
||||
const definitions = new Map<string, TypeDefinition>();
|
||||
const defs = schema.$defs || schema.definitions || {};
|
||||
|
||||
for (const [name, defSchema] of Object.entries(defs)) {
|
||||
definitions.set(name, {
|
||||
name: toPascalCase(name),
|
||||
schema: defSchema,
|
||||
isRecursive: false,
|
||||
dependencies: new Set(),
|
||||
});
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all $ref dependencies in a schema
|
||||
*/
|
||||
function findDependencies(schema: JsonSchema): Set<string> {
|
||||
const deps = new Set<string>();
|
||||
|
||||
function traverse(s: JsonSchema): void {
|
||||
if (typeof s !== 'object' || s === null) return;
|
||||
if (s.$ref) {
|
||||
deps.add(getRefName(s.$ref));
|
||||
}
|
||||
|
||||
if (s.properties) {
|
||||
for (const propSchema of Object.values(s.properties)) {
|
||||
traverse(propSchema);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.items) {
|
||||
traverse(s.items);
|
||||
}
|
||||
|
||||
if (s.oneOf) {
|
||||
for (const item of s.oneOf) {
|
||||
traverse(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.anyOf) {
|
||||
for (const item of s.anyOf) {
|
||||
traverse(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.allOf) {
|
||||
for (const item of s.allOf) {
|
||||
traverse(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof s.additionalProperties === 'object' && s.additionalProperties) {
|
||||
traverse(s.additionalProperties);
|
||||
}
|
||||
}
|
||||
|
||||
traverse(schema);
|
||||
return deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect recursive types using DFS cycle detection
|
||||
*/
|
||||
function detectRecursiveTypes(
|
||||
definitions: Map<string, TypeDefinition>,
|
||||
): Set<string> {
|
||||
const recursiveTypes = new Set<string>();
|
||||
|
||||
// Build dependency graph
|
||||
for (const [, def] of definitions) {
|
||||
def.dependencies = findDependencies(def.schema);
|
||||
}
|
||||
|
||||
// DFS to detect cycles
|
||||
function hasCycle(
|
||||
name: string,
|
||||
visiting: Set<string>,
|
||||
visited: Set<string>,
|
||||
): boolean {
|
||||
if (visiting.has(name)) {
|
||||
return true;
|
||||
}
|
||||
if (visited.has(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
visiting.add(name);
|
||||
|
||||
const def = definitions.get(name);
|
||||
if (def) {
|
||||
for (const dep of def.dependencies) {
|
||||
if (hasCycle(dep, visiting, visited)) {
|
||||
recursiveTypes.add(name);
|
||||
recursiveTypes.add(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visiting.delete(name);
|
||||
visited.add(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each definition for cycles
|
||||
for (const name of definitions.keys()) {
|
||||
hasCycle(name, new Set(), new Set());
|
||||
}
|
||||
|
||||
// Also check for direct self-references
|
||||
for (const [name, def] of definitions) {
|
||||
if (def.dependencies.has(name)) {
|
||||
recursiveTypes.add(name);
|
||||
def.isRecursive = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all recursive types
|
||||
for (const name of recursiveTypes) {
|
||||
const def = definitions.get(name);
|
||||
if (def) {
|
||||
def.isRecursive = true;
|
||||
}
|
||||
}
|
||||
|
||||
return recursiveTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Topologically sort definitions (dependencies first)
|
||||
*/
|
||||
function sortDefinitions(
|
||||
definitions: Map<string, TypeDefinition>,
|
||||
recursiveTypes: Set<string>,
|
||||
): Array<TypeDefinition> {
|
||||
const sorted: Array<TypeDefinition> = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
function visit(name: string): void {
|
||||
if (visited.has(name)) return;
|
||||
visited.add(name);
|
||||
|
||||
const def = definitions.get(name);
|
||||
if (!def) return;
|
||||
|
||||
// Visit non-recursive dependencies first
|
||||
for (const dep of def.dependencies) {
|
||||
if (!(recursiveTypes.has(dep) && recursiveTypes.has(name))) {
|
||||
visit(dep);
|
||||
}
|
||||
}
|
||||
|
||||
sorted.push(def);
|
||||
}
|
||||
|
||||
for (const name of definitions.keys()) {
|
||||
visit(name);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Code Generation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Generate TypeScript type for a JSON Schema (used for recursive type declarations)
|
||||
*/
|
||||
function generateTsType(schema: JsonSchema, _ctx: ConversionContext): string {
|
||||
if (typeof schema === 'boolean') {
|
||||
return schema ? 'any' : 'never';
|
||||
}
|
||||
if (schema.$ref) {
|
||||
const refName = getRefName(schema.$ref);
|
||||
const pascalName = toPascalCase(refName);
|
||||
return pascalName;
|
||||
}
|
||||
|
||||
const type = getNonNullType(schema);
|
||||
|
||||
if (schema.enum) {
|
||||
return schema.enum.map((v) => JSON.stringify(v)).join(' | ');
|
||||
}
|
||||
|
||||
if (schema.const !== undefined) {
|
||||
return JSON.stringify(schema.const);
|
||||
}
|
||||
|
||||
if (schema.oneOf || schema.anyOf) {
|
||||
const items = schema.oneOf || schema.anyOf || [];
|
||||
const types = items
|
||||
.filter((s) => !isJsonSchemaNull(s))
|
||||
.map((s) => generateTsType(s, _ctx));
|
||||
return types.join(' | ');
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'string':
|
||||
return 'string';
|
||||
case 'number':
|
||||
case 'integer':
|
||||
return 'number';
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
case 'null':
|
||||
return 'null';
|
||||
case 'array':
|
||||
if (schema.items) {
|
||||
return `ReadonlyArray<${generateTsType(schema.items, _ctx)}>`;
|
||||
}
|
||||
return 'ReadonlyArray<unknown>';
|
||||
case 'object':
|
||||
if (schema.properties) {
|
||||
const props = Object.entries(schema.properties)
|
||||
.map(([key, propSchema]) => {
|
||||
const isRequired = schema.required?.includes(key) ?? false;
|
||||
const nullable = isNullable(propSchema);
|
||||
const tsType = generateTsType(propSchema, _ctx);
|
||||
const nullSuffix = nullable ? ' | null' : '';
|
||||
const optionalMark = isRequired ? '' : '?';
|
||||
return ` readonly "${key}"${optionalMark}: ${tsType}${nullSuffix}`;
|
||||
})
|
||||
.join(';\n');
|
||||
return `{\n${props}\n}`;
|
||||
}
|
||||
if (
|
||||
schema.additionalProperties === true ||
|
||||
typeof schema.additionalProperties === 'object'
|
||||
) {
|
||||
const valueType =
|
||||
typeof schema.additionalProperties === 'object'
|
||||
? generateTsType(schema.additionalProperties, _ctx)
|
||||
: 'unknown';
|
||||
return `Record<string, ${valueType}>`;
|
||||
}
|
||||
return 'Record<string, unknown>';
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate TypeScript interface/type declaration for a recursive type
|
||||
*/
|
||||
function generateTypeDeclaration(
|
||||
name: string,
|
||||
schema: JsonSchema,
|
||||
ctx: ConversionContext,
|
||||
): string {
|
||||
const tsType = generateTsType(schema, ctx);
|
||||
if (tsType.startsWith('{')) {
|
||||
return `interface ${name} ${tsType}`;
|
||||
}
|
||||
return `type ${name} = ${tsType}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Effect Schema code for a JSON Schema
|
||||
*/
|
||||
function generateEffectSchema(
|
||||
schema: JsonSchema,
|
||||
ctx: ConversionContext,
|
||||
): string {
|
||||
if (typeof schema === 'boolean') {
|
||||
return schema ? 'S.Unknown' : 'S.Never';
|
||||
}
|
||||
// Handle $ref
|
||||
if (schema.$ref) {
|
||||
const refName = getRefName(schema.$ref);
|
||||
const pascalName = toPascalCase(refName);
|
||||
|
||||
// Check if this is a recursive reference - use S.suspend with explicit return type
|
||||
if (ctx.recursiveTypes.has(refName)) {
|
||||
return `S.suspend((): S.Schema<${pascalName}> => ${pascalName})`;
|
||||
}
|
||||
return pascalName;
|
||||
}
|
||||
|
||||
// Handle nullable types
|
||||
const nullable = isNullable(schema);
|
||||
const type = getNonNullType(schema);
|
||||
|
||||
// Handle enum
|
||||
if (schema.enum) {
|
||||
const literals = schema.enum.map((v) => JSON.stringify(v)).join(', ');
|
||||
const result = `S.Literal(${literals})`;
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
|
||||
// Handle const
|
||||
if (schema.const !== undefined) {
|
||||
const result = `S.Literal(${JSON.stringify(schema.const)})`;
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
|
||||
// Handle oneOf / anyOf (union)
|
||||
if (schema.oneOf || schema.anyOf) {
|
||||
const items = schema.oneOf || schema.anyOf || [];
|
||||
const nonNullItems = items.filter((s) => !isJsonSchemaNull(s));
|
||||
|
||||
if (nonNullItems.length === 0) {
|
||||
return 'S.Null';
|
||||
}
|
||||
|
||||
if (nonNullItems.length === 1) {
|
||||
const firstItem = nonNullItems[0];
|
||||
if (!firstItem) return 'S.Unknown';
|
||||
const innerSchema = generateEffectSchema(firstItem, ctx);
|
||||
const hasNull = items.some(isJsonSchemaNull);
|
||||
return hasNull ? `S.NullOr(${innerSchema})` : innerSchema;
|
||||
}
|
||||
|
||||
const members = nonNullItems
|
||||
.map((s) => generateEffectSchema(s, ctx))
|
||||
.join(', ');
|
||||
const hasNull = items.some(isJsonSchemaNull);
|
||||
return hasNull ? `S.Union(${members}, S.Null)` : `S.Union(${members})`;
|
||||
}
|
||||
|
||||
// Handle allOf (intersection - we merge properties)
|
||||
if (schema.allOf) {
|
||||
// For simplicity, merge all schemas' properties
|
||||
const merged: JsonSchema = { type: 'object', properties: {}, required: [] };
|
||||
for (const item of schema.allOf) {
|
||||
if (item.properties) {
|
||||
merged.properties = { ...merged.properties, ...item.properties };
|
||||
}
|
||||
if (item.required) {
|
||||
merged.required = [...(merged.required || []), ...item.required];
|
||||
}
|
||||
}
|
||||
return generateEffectSchema(merged, ctx);
|
||||
}
|
||||
|
||||
// Handle by type
|
||||
switch (type) {
|
||||
case 'string': {
|
||||
const result = 'S.String';
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
case 'number':
|
||||
case 'integer': {
|
||||
const result = 'S.Number';
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
case 'boolean': {
|
||||
const result = 'S.Boolean';
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
case 'null':
|
||||
return 'S.Null';
|
||||
case 'array': {
|
||||
const itemSchema = schema.items
|
||||
? generateEffectSchema(schema.items, ctx)
|
||||
: 'S.Unknown';
|
||||
const result = `S.Array(${itemSchema})`;
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
case 'object': {
|
||||
if (schema.properties) {
|
||||
const props = Object.entries(schema.properties)
|
||||
.map(([key, propSchema]) => {
|
||||
const isRequired = schema.required?.includes(key) ?? false;
|
||||
const propNullable = isNullable(propSchema);
|
||||
let propCode = generateEffectSchema(propSchema, ctx);
|
||||
|
||||
// Wrap in optional if not required
|
||||
if (!isRequired) {
|
||||
if (propNullable && !propCode.startsWith('S.NullOr(')) {
|
||||
propCode = `S.NullOr(${propCode})`;
|
||||
}
|
||||
propCode = `S.optional(${propCode})`;
|
||||
}
|
||||
|
||||
return ` "${key}": ${propCode}`;
|
||||
})
|
||||
.join(',\n');
|
||||
const result = `S.Struct({\n${props}\n})`;
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
if (
|
||||
schema.additionalProperties === true ||
|
||||
typeof schema.additionalProperties === 'object'
|
||||
) {
|
||||
const valueSchema =
|
||||
typeof schema.additionalProperties === 'object'
|
||||
? generateEffectSchema(schema.additionalProperties, ctx)
|
||||
: 'S.Unknown';
|
||||
const result = `S.Record({ key: S.String, value: ${valueSchema} })`;
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
// Empty object or any object
|
||||
const result = 'S.Record({ key: S.String, value: S.Unknown })';
|
||||
return nullable ? `S.NullOr(${result})` : result;
|
||||
}
|
||||
default:
|
||||
// Unknown or any type
|
||||
return nullable ? 'S.NullOr(S.Unknown)' : 'S.Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate non-recursive schema definition (schema + derived type)
|
||||
*/
|
||||
function generateNonRecursiveDefinition(
|
||||
def: TypeDefinition,
|
||||
ctx: ConversionContext,
|
||||
): string {
|
||||
const lines: Array<string> = [];
|
||||
const { name, schema } = def;
|
||||
|
||||
// Add description as JSDoc comment
|
||||
if (schema.description) {
|
||||
lines.push(`/** ${schema.description} */`);
|
||||
}
|
||||
|
||||
// Check if it's an enum type
|
||||
if (schema.enum) {
|
||||
const literals = schema.enum.map((v) => JSON.stringify(v)).join(',\n ');
|
||||
lines.push(`export const ${name} = S.Literal(\n ${literals}\n);`);
|
||||
lines.push(`export type ${name} = S.Schema.Type<typeof ${name}>;`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Generate the schema
|
||||
const schemaCode = generateEffectSchema(schema, ctx);
|
||||
lines.push(`export const ${name} = ${schemaCode};`);
|
||||
lines.push(`export type ${name} = S.Schema.Type<typeof ${name}>;`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate recursive schema definition (schema with type assertion to bypass structural checks)
|
||||
*/
|
||||
function generateRecursiveSchemaDefinition(
|
||||
def: TypeDefinition,
|
||||
ctx: ConversionContext,
|
||||
): string {
|
||||
const lines: Array<string> = [];
|
||||
const { name, schema } = def;
|
||||
|
||||
// Add description as JSDoc comment
|
||||
if (schema.description) {
|
||||
lines.push(`/** ${schema.description} */`);
|
||||
}
|
||||
|
||||
// Generate the schema with double type assertion to bypass structural checking
|
||||
const schemaCode = generateEffectSchema(schema, ctx);
|
||||
lines.push(
|
||||
`export const ${name} = ${schemaCode} as unknown as S.Schema<${name}>;`,
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main Conversion Function
|
||||
// ============================================================================
|
||||
|
||||
export type ConvertOptions = {
|
||||
/** The JSON Schema to convert */
|
||||
schema: JsonSchema;
|
||||
/** The name for the root type (if the schema has no $ref) */
|
||||
rootName?: string;
|
||||
};
|
||||
|
||||
export type ConvertResult = {
|
||||
/** The generated TypeScript code */
|
||||
code: string;
|
||||
/** Names of all generated types */
|
||||
typeNames: Array<string>;
|
||||
/** Names of recursive types */
|
||||
recursiveTypes: Array<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a JSON Schema to Effect Schema TypeScript code
|
||||
*/
|
||||
export function convert(options: ConvertOptions): ConvertResult {
|
||||
const { schema, rootName = 'Root' } = options;
|
||||
|
||||
// Collect all definitions
|
||||
const definitions = collectDefinitions(schema);
|
||||
|
||||
// If the root schema is not a $ref, add it as a definition
|
||||
if (!schema.$ref && (schema.type === 'object' || schema.properties)) {
|
||||
definitions.set(rootName, {
|
||||
name: toPascalCase(rootName),
|
||||
schema,
|
||||
isRecursive: false,
|
||||
dependencies: new Set(),
|
||||
});
|
||||
}
|
||||
|
||||
// Detect recursive types
|
||||
const recursiveTypes = detectRecursiveTypes(definitions);
|
||||
|
||||
// Create conversion context
|
||||
const ctx: ConversionContext = {
|
||||
definitions,
|
||||
recursiveTypes,
|
||||
currentPath: [],
|
||||
};
|
||||
|
||||
// Sort definitions topologically
|
||||
const sorted = sortDefinitions(definitions, recursiveTypes);
|
||||
|
||||
// Split into non-recursive and recursive definitions
|
||||
const nonRecursive = sorted.filter((d) => !recursiveTypes.has(d.name));
|
||||
const recursive = sorted.filter((d) => recursiveTypes.has(d.name));
|
||||
|
||||
// Generate code
|
||||
const lines: Array<string> = [
|
||||
'// This file is auto-generated. Do not edit manually.',
|
||||
"import * as S from 'effect/Schema';",
|
||||
'',
|
||||
];
|
||||
|
||||
// 1. Generate non-recursive definitions (schema + type)
|
||||
for (const def of nonRecursive) {
|
||||
lines.push(generateNonRecursiveDefinition(def, ctx));
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// 2. Generate type declarations for recursive types (before schemas)
|
||||
if (recursive.length > 0) {
|
||||
lines.push('// Recursive type declarations');
|
||||
for (const def of recursive) {
|
||||
lines.push(generateTypeDeclaration(def.name, def.schema, ctx));
|
||||
}
|
||||
lines.push('');
|
||||
|
||||
// 3. Generate schema definitions for recursive types
|
||||
lines.push('// Recursive schema definitions');
|
||||
for (const def of recursive) {
|
||||
lines.push(generateRecursiveSchemaDefinition(def, ctx));
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle root $ref
|
||||
if (schema.$ref) {
|
||||
const refName = toPascalCase(getRefName(schema.$ref));
|
||||
const exportName = toPascalCase(rootName);
|
||||
if (refName !== exportName) {
|
||||
lines.push(`export { ${refName} as ${exportName} };`);
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: lines.join('\n'),
|
||||
typeNames: sorted.map((d) => d.name),
|
||||
recursiveTypes: Array.from(recursiveTypes).map(toPascalCase),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a JSON Schema string to Effect Schema TypeScript code
|
||||
*/
|
||||
export function convertFromString(
|
||||
jsonSchemaString: string,
|
||||
rootName = 'Root',
|
||||
): ConvertResult {
|
||||
const schema = JSON.parse(jsonSchemaString) as JsonSchema;
|
||||
return convert({ schema, rootName });
|
||||
}
|
||||
Reference in New Issue
Block a user