Skip to content

Commit 960090a

Browse files
committed
exports
1 parent b5e2ead commit 960090a

File tree

2 files changed

+249
-245
lines changed

2 files changed

+249
-245
lines changed

src/index.ts

Lines changed: 4 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -1,245 +1,4 @@
1-
import generate from "@babel/generator";
2-
import * as t from "@babel/types";
3-
4-
import { SchemaTSContext, type SchemaTSOptions } from "./context";
5-
import type { JSONSchema } from "./types";
6-
import { isValidIdentifier, toCamelCase, toPascalCase } from "./utils";
7-
8-
const identifier = (name: string, typeAnnotation: t.TSTypeAnnotation) => {
9-
const i = t.identifier(name);
10-
i.typeAnnotation = typeAnnotation;
11-
return i;
12-
};
13-
14-
const anyOrObjectWithUnknownProps = (ctx: SchemaTSContext) => {
15-
return ctx.options.strictTypeSafety ? t.tsTypeLiteral([
16-
t.tsIndexSignature(
17-
[
18-
identifier('key', t.tsTypeAnnotation(
19-
t.tsStringKeyword()
20-
))
21-
],
22-
t.tsTypeAnnotation(t.tsUnknownKeyword())
23-
)
24-
]) : t.tsAnyKeyword();
25-
}
26-
27-
export function generateTypeScript(schema: JSONSchema, options?: Partial<SchemaTSOptions>): string {
28-
const interfaces = [];
29-
const ctx = new SchemaTSContext(options, schema, schema, []);
30-
31-
try {
32-
// Process both $defs and definitions
33-
const definitions = schema.$defs || schema.definitions || {};
34-
for (const key in definitions) {
35-
interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(key), definitions[key]));
36-
}
37-
} catch (e) {
38-
console.error('Error processing interfaces');
39-
throw e;
40-
}
41-
42-
// Process the main schema
43-
const title = schema.title;
44-
if (!title) {
45-
console.error('schema or options require a title');
46-
return ''; // Ensure there's a return on error condition
47-
}
48-
interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(title), schema));
49-
return generate(t.file(t.program(interfaces))).code;
50-
}
51-
52-
function createInterfaceDeclaration(
53-
ctx: SchemaTSContext,
54-
name: string,
55-
schema: JSONSchema
56-
): t.ExportNamedDeclaration {
57-
// Handle standard properties if they exist
58-
let bodyElements: any = [];
59-
if (schema.properties) {
60-
const properties = schema.properties;
61-
const required = schema.required || [];
62-
bodyElements = Object.keys(properties).map(key => {
63-
const prop = properties[key];
64-
return createPropertySignature(ctx, key, prop, required, schema);
65-
});
66-
}
67-
68-
// Handling additionalProperties if they exist
69-
if (schema.additionalProperties) {
70-
const additionalType = typeof schema.additionalProperties === 'boolean' ?
71-
t.tsStringKeyword() : getTypeForProp(ctx, schema.additionalProperties, [], schema);
72-
const indexSignature = t.tsIndexSignature(
73-
[t.identifier("key")], // index name, can be any valid name
74-
t.tsTypeAnnotation(additionalType)
75-
);
76-
indexSignature.parameters[0].typeAnnotation = t.tsTypeAnnotation(t.tsStringKeyword());
77-
bodyElements.push(indexSignature);
78-
}
79-
80-
// Handling oneOf, anyOf, allOf if properties are not defined
81-
if (!schema.properties && (schema.oneOf || schema.anyOf || schema.allOf)) {
82-
const types = [];
83-
if (schema.oneOf) {
84-
types.push(getTypeForProp(ctx, { oneOf: schema.oneOf }, [], schema));
85-
}
86-
if (schema.anyOf) {
87-
types.push(getTypeForProp(ctx, { anyOf: schema.anyOf }, [], schema));
88-
}
89-
if (schema.allOf) {
90-
types.push(getTypeForProp(ctx, { allOf: schema.allOf }, [], schema));
91-
}
92-
93-
// Create a union type if multiple types are generated
94-
const combinedType = types.length > 1 ? t.tsUnionType(types) : types[0];
95-
96-
// Create a type alias instead of an interface if we're only handling these constructs
97-
const typeAlias = t.tsTypeAliasDeclaration(t.identifier(name), null, combinedType);
98-
return t.exportNamedDeclaration(typeAlias);
99-
}
100-
101-
// Finally, create the interface declaration if there are any body elements
102-
if (bodyElements.length > 0) {
103-
const interfaceDeclaration = t.tsInterfaceDeclaration(
104-
t.identifier(name),
105-
null,
106-
[],
107-
t.tsInterfaceBody(bodyElements)
108-
);
109-
return t.exportNamedDeclaration(interfaceDeclaration);
110-
}
111-
112-
if (schema.type) {
113-
return t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier(name), null, getTypeForProp(ctx, schema, [], schema)));
114-
}
115-
116-
// Fallback to exporting a basic type if nothing else is possible
117-
console.warn(`No properties or type definitions found for ${name}, defaulting to 'any'.`);
118-
return t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier(name), null, t.tsAnyKeyword()));
119-
}
120-
121-
122-
function createPropertySignature(
123-
ctx: SchemaTSContext,
124-
key: string,
125-
prop: JSONSchema,
126-
required: string[],
127-
schema: JSONSchema
128-
): t.TSPropertySignature {
129-
130-
const isIdent = isValidIdentifier(key);
131-
const name = ctx.options.useCamelCase ? toCamelCase(key) : key;
132-
const propType = getTypeForProp(ctx, prop, required, schema);
133-
const identifier = isIdent ? t.identifier(name) : t.stringLiteral(key);
134-
const propSig = t.tsPropertySignature(
135-
identifier,
136-
t.tsTypeAnnotation(propType)
137-
);
138-
propSig.optional = !required.includes(key);
139-
return propSig;
140-
}
141-
142-
function getTypeForProp(ctx: SchemaTSContext, prop: JSONSchema, required: string[], schema: JSONSchema): t.TSType {
143-
if (prop.$ref) {
144-
return resolveRefType(ctx, prop.$ref, schema);
145-
}
146-
147-
if (prop.enum) {
148-
const enumType = prop.enum.map(enumValue => t.tsLiteralType(t.stringLiteral(enumValue)));
149-
return t.tsUnionType(enumType);
150-
}
151-
152-
if (prop.const) {
153-
return t.tsLiteralType(t.stringLiteral(prop.const));
154-
}
155-
156-
if (prop.type) {
157-
if (Array.isArray(prop.type)) {
158-
const arrayType = prop.type.map(type => getTypeForProp(ctx, {type, items: prop.items}, [], schema));
159-
return t.tsUnionType(arrayType);
160-
}
161-
162-
switch (prop.type) {
163-
case 'string':
164-
return t.tsStringKeyword();
165-
case 'number':
166-
case 'integer':
167-
return t.tsNumberKeyword();
168-
case 'boolean':
169-
return t.tsBooleanKeyword();
170-
case 'null':
171-
return t.tsNullKeyword();
172-
case 'array':
173-
if (prop.items) {
174-
return t.tsArrayType(getTypeForProp(ctx, prop.items, required, schema));
175-
} else {
176-
throw new Error('Array items specification is missing');
177-
}
178-
case 'object':
179-
if (prop.properties) {
180-
const nestedProperties = prop.properties;
181-
const nestedRequired = prop.required || [];
182-
const typeElements = Object.keys(nestedProperties).map(nestedKey => {
183-
const nestedProp = nestedProperties[nestedKey];
184-
return createPropertySignature(ctx, nestedKey, nestedProp, nestedRequired, schema);
185-
});
186-
return t.tsTypeLiteral(typeElements);
187-
} else {
188-
// Handle dynamic properties with strict type safety option
189-
return anyOrObjectWithUnknownProps(ctx);
190-
}
191-
default:
192-
return t.tsAnyKeyword();
193-
}
194-
}
195-
196-
// Handling oneOf, anyOf, allOf
197-
if (prop.anyOf) {
198-
const types = prop.anyOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema));
199-
return t.tsUnionType(types);
200-
}
201-
if (prop.allOf) {
202-
const types = prop.allOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema));
203-
return t.tsIntersectionType(types);
204-
}
205-
if (prop.oneOf) {
206-
const types = prop.oneOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema));
207-
return t.tsUnionType(types);
208-
}
209-
210-
// Fallback when no types are defined
211-
return t.tsAnyKeyword()
212-
213-
}
214-
215-
function getTypeReferenceFromSchema(schema: JSONSchema, definitionName: string): t.TSType | null {
216-
if (definitionName) {
217-
if (schema.$defs && schema.$defs[definitionName]) {
218-
return t.tsTypeReference(t.identifier(toPascalCase(definitionName)));
219-
} else if (schema.definitions && schema.definitions[definitionName]) {
220-
return t.tsTypeReference(t.identifier(toPascalCase(definitionName)));
221-
}
222-
}
223-
return null; // Return null if no type reference is found
224-
}
225-
226-
227-
function resolveRefType(ctx: SchemaTSContext, ref: string, schema: JSONSchema): t.TSType {
228-
const path = ref.split('/');
229-
const definitionName = path.pop();
230-
231-
// Try to resolve the type reference from the local schema
232-
const localTypeReference = getTypeReferenceFromSchema(schema, definitionName);
233-
if (localTypeReference) {
234-
return localTypeReference;
235-
}
236-
237-
// Try to resolve the type reference from the root schema
238-
const rootTypeReference = getTypeReferenceFromSchema(ctx.root, definitionName);
239-
if (rootTypeReference) {
240-
return rootTypeReference;
241-
}
242-
243-
// If no definitions are found in either schema, throw an error
244-
throw new Error(`Reference ${ref} not found in definitions or $defs.`);
245-
}
1+
export * from './context';
2+
export * from './schema';
3+
export * from './types';
4+
export * from './utils';

0 commit comments

Comments
 (0)