diff --git a/fixtures/install/playground/superface/types/starwars/character-information.ts b/fixtures/install/playground/superface/types/starwars/character-information.ts index 313a5cbb..5e3183b7 100644 --- a/fixtures/install/playground/superface/types/starwars/character-information.ts +++ b/fixtures/install/playground/superface/types/starwars/character-information.ts @@ -1,4 +1,4 @@ -import { typeHelper } from '@superfaceai/one-sdk'; +import { typeHelper, TypedProfile } from '@superfaceai/one-sdk'; /** Starwars **/ export interface RetrieveCharacterInformationInput { characterName?: unknown; @@ -9,8 +9,10 @@ export interface RetrieveCharacterInformationResult { weight?: unknown; yearOfBirth?: unknown; } -export const starwarsCharacterInformation = { - "starwars/character-information": { - "RetrieveCharacterInformation": typeHelper() - } +export const profile = { + "RetrieveCharacterInformation": typeHelper() +}; +export type StarwarsCharacterInformationProfile = TypedProfile; +export const starwars/character-information = { + "starwars/character-information": profile }; diff --git a/src/logic/generate.ts b/src/logic/generate.ts index 4bd718bb..6c153251 100644 --- a/src/logic/generate.ts +++ b/src/logic/generate.ts @@ -8,6 +8,7 @@ import { } from '@superfaceai/parser'; import { InterfaceDeclaration, + isExportDeclaration, isIdentifier, isImportDeclaration, isObjectLiteralExpression, @@ -25,8 +26,10 @@ import { camelize, capitalize, createSource, + getExportText, getImportText, getVariableName, + id, interfaceType, literalUnion, namedImport, @@ -35,9 +38,12 @@ import { pascalize, propertyAssignment, propertySignature, + reexport, typeAlias, typedClientStatement, typeDefinitions, + typeQuery, + typeReference, variableStatement, variableType, } from './generate.utils'; @@ -119,10 +125,10 @@ export function createUsecaseTypes( return [...inputs, ...results]; } -export function createUsecasesType( +export function createProfileType( profileName: string, usecaseNames: string[] -): Statement { +): Statement[] { const usecases = usecaseNames.map(usecaseName => { const pascalizedUsecaseName = pascalize(usecaseName); const helperCall = callExpression( @@ -133,20 +139,28 @@ export function createUsecasesType( return propertyAssignment(usecaseName, helperCall); }); - const profile = objectLiteral([ - propertyAssignment(profileName, objectLiteral(usecases)), - ]); - return variableStatement(camelize(profileName), profile); + const profile = variableStatement('profile', objectLiteral(usecases)); + const profileType = typeAlias( + pascalize(profileName) + 'Profile', + typeReference('TypedProfile', [typeQuery('profile')]) + ); + const profileWrapper = variableStatement( + profileName, + objectLiteral([propertyAssignment(profileName, id('profile'))]) + ); + + return [profile, profileType, profileWrapper]; } export function generateTypesFile( profiles: string[], typesFile?: string ): string { - const imports = profiles.map(profile => - namedImport([camelize(profile)], './types/' + profile) - ); + const imports = profiles.flatMap(profile => [ + namedImport([camelize(profile)], './types/' + profile), + reexport([pascalize(profile) + 'Profile'], './types/' + profile), + ]); const statements = [ namedImport(['createTypedClient'], '@superfaceai/one-sdk'), ...imports, @@ -180,7 +194,9 @@ export function updateTypesFile( const originalStatements = parseSource(typesFile); const newImports = statements.filter(isImportDeclaration); + const newExports = statements.filter(isExportDeclaration); const originalImports = originalStatements.filter(isImportDeclaration); + const originalExports = originalStatements.filter(isExportDeclaration); const newTypeDefinition = statements .filter(isVariableStatement) .find(node => getVariableName(node) === 'typeDefinitions'); @@ -188,7 +204,10 @@ export function updateTypesFile( .filter(isVariableStatement) .find(node => getVariableName(node) === 'typeDefinitions'); - const mergedStatements: Statement[] = [...originalImports]; + const mergedStatements: Statement[] = [ + ...originalImports, + ...originalExports, + ]; for (const newImport of newImports) { const importText = getImportText(newImport); @@ -202,6 +221,18 @@ export function updateTypesFile( } } + for (const newExport of newExports) { + const exportText = getExportText(newExport); + if ( + exportText !== undefined && + originalExports.find( + originalExport => getExportText(originalExport) === exportText + ) === undefined + ) { + mergedStatements.push(newExport); + } + } + if (newTypeDefinition === undefined && originalTypeDefinition !== undefined) { mergedStatements.push(originalTypeDefinition); } else if ( @@ -240,14 +271,14 @@ export function generateTypingsForProfile( profileAST: ProfileDocumentNode ): string { const output = getProfileOutput(profileAST); - const inputTypes = output.usecases.map(usecase => + const inputTypes = output.usecases.flatMap(usecase => createUsecaseTypes(usecase, 'unknown') ); const statements = [ - namedImport(['typeHelper'], '@superfaceai/one-sdk'), - ...inputTypes.reduce((acc, input) => [...acc, ...input], []), - createUsecasesType( + namedImport(['typeHelper', 'TypedProfile'], '@superfaceai/one-sdk'), + ...inputTypes, + ...createProfileType( profileName, output.usecases.map(usecase => usecase.useCaseName) ), diff --git a/src/logic/generate.utils.ts b/src/logic/generate.utils.ts index 3588693f..22158fe4 100644 --- a/src/logic/generate.utils.ts +++ b/src/logic/generate.utils.ts @@ -7,14 +7,17 @@ import { createPrinter, createSourceFile, EmitHint, + ExportDeclaration, Expression, factory, FalseLiteral, Identifier, ImportDeclaration, InterfaceDeclaration, + isExportSpecifier, isIdentifier, isImportSpecifier, + isNamedExports, isNamedImports, KeywordTypeNode, LiteralTypeNode, @@ -37,6 +40,7 @@ import { TypeElement, TypeLiteralNode, TypeNode, + TypeQueryNode, TypeReferenceNode, UnionTypeNode, VariableStatement, @@ -307,8 +311,11 @@ export function asExpression(name: string, asType: string): AsExpression { ); } -export function typeReference(name: string): TypeReferenceNode { - return factory.createTypeReferenceNode(name); +export function typeReference( + name: string, + typeArguments?: TypeNode[] +): TypeReferenceNode { + return factory.createTypeReferenceNode(name, typeArguments); } export function callExpression( @@ -318,7 +325,7 @@ export function callExpression( ): CallExpression { return factory.createCallExpression( id(functionName), - typeArguments?.map(typeReference) ?? [], + typeArguments?.map(argument => typeReference(argument)) ?? [], functionArguments ); } @@ -383,7 +390,7 @@ export function createSource(statements: Statement[]): string { export function getImportText(node: ImportDeclaration): string | undefined { if ( - node.importClause?.namedBindings && + node.importClause?.namedBindings !== undefined && isNamedImports(node.importClause.namedBindings) ) { return node.importClause.namedBindings.elements.find(isImportSpecifier) @@ -393,6 +400,14 @@ export function getImportText(node: ImportDeclaration): string | undefined { return undefined; } +export function getExportText(node: ExportDeclaration): string | undefined { + if (node.exportClause !== undefined && isNamedExports(node.exportClause)) { + return node.exportClause.elements.find(isExportSpecifier)?.name.text; + } + + return undefined; +} + export function getVariableName(node: VariableStatement): string | undefined { if ( node.declarationList.declarations.length > 0 && @@ -403,3 +418,19 @@ export function getVariableName(node: VariableStatement): string | undefined { return undefined; } + +export function typeQuery(name: string): TypeQueryNode { + return factory.createTypeQueryNode(id(name)); +} + +export function reexport(names: string[], from: string): ExportDeclaration { + return factory.createExportDeclaration( + undefined, + undefined, + false, + factory.createNamedExports( + names.map(name => factory.createExportSpecifier(undefined, name)) + ), + stringLiteral(from) + ); +}