diff --git a/integration/core/test.ts b/integration/core/test.ts new file mode 100644 index 0000000..5730c65 --- /dev/null +++ b/integration/core/test.ts @@ -0,0 +1,198 @@ +import { Typescript } from "@duplojs/data-parser-tools"; +import { resolveTsconfig } from "@duplojs/http/codeGenerator"; +import { getCurrentWorkDirectoryOrThrow, SF } from "@duplojs/server-utils"; +import { A, type AnyTuple, asyncPipe, E, G, innerPipe, isType, justExec, O, Path, pipe, promiseAll, S } from "@duplojs/utils"; + +const tsconfig = E.unwrapByInformationOrThrow( + resolveTsconfig( + getCurrentWorkDirectoryOrThrow(), + "core/tsconfig.json", + ), + "success", +); + +const compilerOptions = { + ...tsconfig.options, + noEmit: false, + declaration: true, + emitDeclarationOnly: true, + declarationDir: "core/temp", +}; + +await SF.remove(compilerOptions.declarationDir, { recursive: true }); + +const program = Typescript.createProgram({ + rootNames: tsconfig.fileNames, + options: compilerOptions, + projectReferences: tsconfig.projectReferences, +}); + +const checker = program.getTypeChecker(); + +let routeIndex = 0; +const printer = Typescript.createPrinter(); +const virtualFiles = await asyncPipe( + tsconfig.fileNames, + A.chunk(10), + G.asyncMap( + innerPipe( + A.map((fileName) => { + const sourceFile = program.getSourceFile(fileName); + if (!sourceFile) { + return null; + } + + const newStatement = A.flatMap( + sourceFile.statements, + (statement) => { + if ( + !Typescript.isExpressionStatement(statement) + || !Typescript.isCallExpression(statement.expression) + || !Typescript.isPropertyAccessExpression(statement.expression.expression) + || statement.expression.expression.name.text !== "handler" + ) { + return statement; + } + + const signature = checker.getResolvedSignature(statement.expression); + if (!signature) { + return statement; + } + + const returnType = checker.getReturnTypeOfSignature(signature); + const typeSymbol = returnType.getSymbol(); + if (!typeSymbol) { + return statement; + } + if (typeSymbol.getName() !== "Route") { + return statement; + } + + const typeIdentifier = Typescript.factory.createIdentifier( + `Route_${routeIndex++}`, + ); + + return [ + Typescript.factory.createVariableStatement( + undefined, + Typescript.factory.createVariableDeclarationList( + [ + Typescript.factory.createVariableDeclaration( + typeIdentifier, + undefined, + undefined, + statement.expression, + ), + ], + Typescript.NodeFlags.Const, + ), + ), + Typescript.factory.createTypeAliasDeclaration( + [Typescript.factory.createToken(Typescript.SyntaxKind.ExportKeyword)], + typeIdentifier, + undefined, + Typescript.factory.createImportTypeNode( + Typescript.factory.createLiteralTypeNode( + Typescript.factory.createStringLiteral("@duplojs/http/codeGenerator"), + ), + undefined, + Typescript.factory.createIdentifier("RouteToClientRoute"), + [ + Typescript.factory.createTypeQueryNode( + typeIdentifier, + undefined, + ), + ], + false, + ), + ), + ]; + }, + ); + + return O.entry( + fileName, + printer.printFile( + Typescript.factory.updateSourceFile( + sourceFile, + newStatement, + ), + ), + ); + }), + promiseAll, + ), + ), + G.asyncFlat, + G.asyncFilter(isType("array")), + A.from, + (value) => new Map(value), +); + +const host = Typescript.createCompilerHost(compilerOptions); +const originalGetSourceFile = host.getSourceFile.bind(host); +host.getSourceFile = ( + fileName, + languageVersion, + onError, + shouldCreateNewSourceFile, +) => { + const content = virtualFiles.get( + fileName, + ); + + if (content !== undefined) { + return Typescript.createSourceFile( + fileName, + content, + languageVersion, + true, + ); + } + + return originalGetSourceFile( + fileName, + languageVersion, + onError, + shouldCreateNewSourceFile, + ); +}; + +const newProgram = Typescript.createProgram({ + rootNames: tsconfig.fileNames, + options: compilerOptions, + projectReferences: tsconfig.projectReferences, + host, +}); + +// // généré l'index +// // génére une deuxéime fois mais en résolvant le type pars inférence pour le rendre plus simple +// // résoudre le path typescript +// // threeshaker les type pour garder que le néccésaire + +const result: any[] = []; + +newProgram.emit( + undefined, + (fileName, content) => { + console.log(fileName); + + result.push( + justExec(async() => { + const folder = Path.getParentFolderPath(fileName); + + if (folder === null) { + return SF.writeTextFile(fileName, content); + } + + await SF.makeDirectory(folder, { recursive: true }); + + return SF.writeTextFile(fileName, content); + }), + ); + }, + undefined, + true, +); + +await promiseAll(result); diff --git a/integration/package.json b/integration/package.json index 0d03323..18df14a 100644 --- a/integration/package.json +++ b/integration/package.json @@ -1,6 +1,7 @@ { "name": "integrations", "license": "ISC", + "type": "module", "scripts": { "test:types": "./.commands/test-types.sh" }, diff --git a/scripts/core/process/index.ts b/scripts/core/process/index.ts index 4a0b9e8..aa691cc 100644 --- a/scripts/core/process/index.ts +++ b/scripts/core/process/index.ts @@ -27,8 +27,8 @@ export type ProcessSteps = ( declare const SymbolProcessExportValue: unique symbol; export interface ProcessDefinition { - steps: readonly ProcessSteps[]; - options?: Record; + readonly steps: readonly ProcessSteps[]; + readonly options?: Record; readonly hooks: readonly HookRouteLifeCycle[]; readonly metadata: readonly Metadata[]; [SymbolProcessExportValue]?: Floor; diff --git a/scripts/core/response/contract.ts b/scripts/core/response/contract.ts index c457f62..cc7f997 100644 --- a/scripts/core/response/contract.ts +++ b/scripts/core/response/contract.ts @@ -255,11 +255,8 @@ export namespace ResponseContract { GenericContract["code"], GenericContract["information"], { - [Prop in keyof GenericContract["events"]]: [ - Extract, - DP.Output, - ] - }[keyof GenericContract["events"]] + [Prop in keyof GenericContract["events"]]: DP.Output + } > : GenericContract extends StreamContract ? StreamPredictedResponse< diff --git a/scripts/core/serverSentEvents.ts b/scripts/core/serverSentEvents.ts index 441ee5d..b9f6bdf 100644 --- a/scripts/core/serverSentEvents.ts +++ b/scripts/core/serverSentEvents.ts @@ -3,7 +3,7 @@ import { type MaybePromise, type MillisecondInString, stringToMillisecond } from import { Stream } from "./stream"; export namespace ServerSentEvents { - export type DefinitionShape = [string, unknown]; + export type DefinitionShape = Record; export interface SendParams { id?: string; @@ -14,17 +14,17 @@ export namespace ServerSentEvents { GenericEvents extends DefinitionShape = DefinitionShape, > extends Stream.StartSendingParams { send( - ...args: GenericEvents extends any - ? [ - event: GenericEvents[0], + ...args: { + [Event in keyof GenericEvents]: [ + event: Event, ...( - GenericEvents[1] extends undefined - ? [data?: GenericEvents[1]] - : [data: GenericEvents[1]] + GenericEvents[Event] extends undefined + ? [data?: GenericEvents[Event]] + : [data: GenericEvents[Event]] ), params?: SendParams, ] - : never + }[keyof GenericEvents] ): Promise; readonly lastId: string | null; } diff --git a/scripts/core/steps/checker.ts b/scripts/core/steps/checker.ts index bfe625d..165771a 100644 --- a/scripts/core/steps/checker.ts +++ b/scripts/core/steps/checker.ts @@ -1,5 +1,5 @@ import { createCoreLibKind } from "@core/kind"; -import { pipe, type Kind } from "@duplojs/utils"; +import { type DP, pipe, type Kind } from "@duplojs/utils"; import { type StepKind, stepKind } from "./kind"; import { type Checker } from "@core/checker"; import { type Floor } from "@core/floor"; @@ -12,7 +12,7 @@ export interface CheckerStepDefinition { readonly indexing?: string; input(input: Floor): unknown; readonly options?: Record | ((input: any) => Record); - readonly responseContract: ResponseContract.Contract; + readonly responseContract: ResponseContract.Contract; readonly metadata: readonly Metadata[]; } diff --git a/scripts/plugins/codeGenerator/aggregateStepContract.ts b/scripts/plugins/codeGenerator/aggregateStepContract.ts index 1568d70..65b0f72 100644 --- a/scripts/plugins/codeGenerator/aggregateStepContract.ts +++ b/scripts/plugins/codeGenerator/aggregateStepContract.ts @@ -43,6 +43,9 @@ export function aggregateStepContract( A.filter(stepIdentifier(processStepKind)), A.filter( (step) => A.find( + step.definition.metadata, + IgnoreByCodeGeneratorMetadata.is, + ) === undefined && A.find( step.definition.process.definition.metadata, IgnoreByCodeGeneratorMetadata.is, ) === undefined, diff --git a/scripts/plugins/codeGenerator/byInference/findRouteTypeNodes.ts b/scripts/plugins/codeGenerator/byInference/findRouteTypeNodes.ts new file mode 100644 index 0000000..561c09f --- /dev/null +++ b/scripts/plugins/codeGenerator/byInference/findRouteTypeNodes.ts @@ -0,0 +1,63 @@ +import { Typescript } from "@duplojs/data-parser-tools"; +import { SF } from "@duplojs/server-utils"; +import { E, unwrap, type AnyTuple } from "@duplojs/utils"; + +export interface FindRoutesParams { + includesFolders: AnyTuple; +} + +export async function findRouteTypeNodes( + program: Typescript.Program, + checker: Typescript.TypeChecker, + params: FindRoutesParams, +) { + const result = new Set(); + + for (const path of params.includesFolders) { + const maybeWalker = await SF.walkDirectory(path); + + if (E.isLeft(maybeWalker)) { + return E.left("failed-to-read-directory", { path }); + } + + for (const entry of unwrap(maybeWalker)) { + if (!SF.isFileInterface(entry)) { + continue; + } + + const programFile = program.getSourceFile(entry.path); + + if (programFile === undefined) { + continue; + } + + programFile.forEachChild((node) => { + if ( + Typescript.isExpressionStatement(node) + && Typescript.isCallExpression(node.expression) + && Typescript.isPropertyAccessExpression(node.expression.expression) + && node.expression.expression.name.text === "handler" + ) { + const signature = checker.getResolvedSignature(node.expression); + if (signature === undefined) { + return signature; + } + + const returnType = checker.getReturnTypeOfSignature(signature); + const returnTypeNode = checker.typeToTypeNode( + returnType, + node.expression, + Typescript.NodeBuilderFlags.NoTruncation + | Typescript.TypeFormatFlags.InTypeAlias, + ); + + if (returnTypeNode) { + result.add(returnTypeNode); + } + } + }); + } + } + + return E.success(result); +} diff --git a/scripts/plugins/codeGenerator/byInference/index.ts b/scripts/plugins/codeGenerator/byInference/index.ts new file mode 100644 index 0000000..e830b57 --- /dev/null +++ b/scripts/plugins/codeGenerator/byInference/index.ts @@ -0,0 +1,2 @@ +export * from "./findRouteTypeNodes"; +export * from "./resolveTsconfig"; diff --git a/scripts/plugins/codeGenerator/byInference/resolveTsconfig.ts b/scripts/plugins/codeGenerator/byInference/resolveTsconfig.ts new file mode 100644 index 0000000..b9e4b04 --- /dev/null +++ b/scripts/plugins/codeGenerator/byInference/resolveTsconfig.ts @@ -0,0 +1,53 @@ +import { Typescript } from "@duplojs/data-parser-tools"; +import { A, pipe, E, Path } from "@duplojs/utils"; + +export function resolveTsconfig( + currentWorkDir: string, + tsconfigPath: string, +) { + const absolutePath = Path.resolveFrom(currentWorkDir, [tsconfigPath]); + + if (absolutePath === null) { + return E.left("failed-to-resolve-path"); + } + + const readResult = Typescript.readConfigFile( + absolutePath, + Typescript.sys.readFile, + ); + + if (readResult.error) { + return E.left( + "failed-to-read-tsconfig", + { message: Typescript.flattenDiagnosticMessageText(readResult.error.messageText, "\n") }, + ); + } + + const parentDir = Path.getParentFolderPath(absolutePath); + if (parentDir === null) { + return E.left("failed-to-resolve-parent-folder"); + } + + const parsed = Typescript.parseJsonConfigFileContent( + readResult.config, + Typescript.sys, + parentDir, + undefined, + absolutePath, + ); + + if (A.minElements(parsed.errors, 1)) { + return E.left( + "failed-to-resolve-tsconfig", + { + message: pipe( + parsed.errors, + A.map((error) => Typescript.flattenDiagnosticMessageText(error.messageText, "\n")), + A.join("\n"), + ), + }, + ); + } + + return E.success(parsed); +} diff --git a/scripts/plugins/codeGenerator/index.ts b/scripts/plugins/codeGenerator/index.ts index 4049fc8..754a69c 100644 --- a/scripts/plugins/codeGenerator/index.ts +++ b/scripts/plugins/codeGenerator/index.ts @@ -6,3 +6,4 @@ export * from "./plugin"; export * from "./routeToDataParser"; export * from "./aggregateStepContract"; export * from "./metadata"; +export * from "./byInference"; diff --git a/scripts/plugins/codeGenerator/plugin.ts b/scripts/plugins/codeGenerator/plugin.ts index fcad7ef..d8e700b 100644 --- a/scripts/plugins/codeGenerator/plugin.ts +++ b/scripts/plugins/codeGenerator/plugin.ts @@ -6,7 +6,6 @@ import { SF } from "@duplojs/server-utils"; import { typescriptTransformers } from "./typescriptTransformer"; import { dataParserHasIdentifier, findIdentifiedDataParserInSteps } from "./findIdentifiedDataParserInSteps"; import { DataParserFinder, DataParserToDataParser } from "@duplojs/data-parser-tools"; -import { Typescript } from "@duplojs/data-parser-tools/typescript"; import { type Route } from "@core/route"; import { createSubDataParserBuildedContext } from "./createSubDataParserBuildedContext"; diff --git a/scripts/plugins/codeGenerator/types/RouteToClientRoute.ts b/scripts/plugins/codeGenerator/types/RouteToClientRoute.ts new file mode 100644 index 0000000..7a4cde6 --- /dev/null +++ b/scripts/plugins/codeGenerator/types/RouteToClientRoute.ts @@ -0,0 +1,245 @@ +import { type RouteSteps, type Route, type RoutePath } from "@core/route"; +import { type ServerSentEventsPredictedResponse, type Response, type StreamPredictedResponse, type StreamTextPredictedResponse, type ResponseContract } from "@core/response"; +import { type PresetCheckerStep, type CheckerStep, type CutStep, type HandlerStep, type ProcessStep, type ExtractStep, type ExtractShape } from "@core/steps"; +import { type NeverCoalescing, type DP, type IsNever, type SimplifyTopLevel, type D, type IsEqual, type UnionToIntersection } from "@duplojs/utils"; +import { type IgnoreByCodeGeneratorMetadata } from "@plugin-codeGenerator/metadata"; +import { type defaultExtractContract } from "@core/hub"; +import { type FileInterface } from "@duplojs/server-utils/file"; + +export interface OverrideDefaultExtractEndpointContract { + +} + +export type DefaultExtractEndpointContract = OverrideDefaultExtractEndpointContract extends infer InferredValue + ? "override" extends keyof InferredValue + ? NeverCoalescing< + Extract, + typeof defaultExtractContract + > + : typeof defaultExtractContract + : never; + +type RoutePayloadToClientPayload< + GenericPayload extends unknown, +> = IsEqual extends true + ? D.SerializedTheTime | D.TheTime + : GenericPayload extends Date + ? D.SerializedTheDate | D.TheDate + : GenericPayload extends FileInterface + ? File + : GenericPayload extends object & Record + ? { + [Prop in keyof GenericPayload]: RoutePayloadToClientPayload< + GenericPayload[Prop] + > + } + : GenericPayload; + +type TreatExtractShape< + GenericShape extends ExtractShape[keyof ExtractShape], +> = GenericShape extends DP.DataParser + ? RoutePayloadToClientPayload> + : GenericShape extends Record + ? { + [Prop in keyof GenericShape]: RoutePayloadToClientPayload> + } + : never; + +export interface RouteStepToClientValue< + GenericRouteStep extends RouteSteps = RouteSteps, +> { + checkerStep: GenericRouteStep extends CheckerStep + ? { + headers: unknown; + params: unknown; + query: unknown; + body: unknown; + responses: { + code: GenericRouteStep["definition"]["responseContract"]["code"]; + information: GenericRouteStep["definition"]["responseContract"]["information"]; + body?: undefined; + }; + } + : never; + presetCheckerStep: GenericRouteStep extends PresetCheckerStep + ? { + headers: unknown; + params: unknown; + query: unknown; + body: unknown; + responses: { + code: GenericRouteStep["definition"]["presetChecker"]["definition"]["responseContract"]["code"]; + information: GenericRouteStep["definition"]["presetChecker"]["definition"]["responseContract"]["information"]; + body?: undefined; + }; + } + : never; + cutStep: GenericRouteStep extends CutStep + ? { + headers: unknown; + params: unknown; + query: unknown; + body: unknown; + responses: Awaited> extends infer InferredValue + ? InferredValue extends Response + ? { + code: InferredCode; + information: InferredInformation; + body: RoutePayloadToClientPayload; + } + : never + : never; + } + : never; + processStep: GenericRouteStep extends ProcessStep + ? IsNever< + Extract< + GenericRouteStep["definition"]["process"]["definition"]["metadata"][number], + ReturnType + > + > extends true + ? RouteStepsToClientValues + : never + : never; + handlerStep: GenericRouteStep extends HandlerStep + ? { + headers: unknown; + params: unknown; + query: unknown; + body: unknown; + responses: Awaited> extends infer InferredValue + ? InferredValue extends ServerSentEventsPredictedResponse< + infer InferredCode, + infer InferredInformation, + infer InferredEvents + > + ? { + code: InferredCode; + information: InferredInformation; + events: RoutePayloadToClientPayload; + } + : InferredValue extends StreamPredictedResponse< + infer InferredCode, + infer InferredInformation + > + ? { + code: InferredCode; + information: InferredInformation; + flux: Uint8Array; + } + : InferredValue extends StreamTextPredictedResponse< + infer InferredCode, + infer InferredInformation + > + ? { + code: InferredCode; + information: InferredInformation; + flux: string; + } + : InferredValue extends Response< + infer InferredCode, + infer InferredInformation, + infer InferredBody + > + ? { + code: InferredCode; + information: InferredInformation; + body: RoutePayloadToClientPayload; + } + : never + : never; + } + : never; + extractStep: GenericRouteStep extends ExtractStep + ? { + headers: NeverCoalescing< + TreatExtractShape, + unknown + >; + params: NeverCoalescing< + TreatExtractShape, + unknown + >; + query: NeverCoalescing< + TreatExtractShape, + unknown + >; + body: NeverCoalescing< + TreatExtractShape, + unknown + >; + responses: GenericRouteStep["definition"]["responseContract"] extends ResponseContract.Contract + ? { + code: GenericRouteStep["definition"]["responseContract"]["code"]; + information: GenericRouteStep["definition"]["responseContract"]["information"]; + body?: undefined; + } + : { + code: DefaultExtractEndpointContract["code"]; + information: DefaultExtractEndpointContract["information"]; + body?: undefined; + }; + } + : never; +} + +export type RouteStepsToClientValues< + GenericRouteSteps extends readonly RouteSteps[], +> = Exclude extends infer InferredKeys extends keyof GenericRouteSteps + ? { + [Prop in InferredKeys]: GenericRouteSteps[Prop] extends RouteSteps + ? IsNever< + Extract< + GenericRouteSteps[Prop]["definition"]["metadata"][number], + ReturnType + > + > extends true + ? RouteStepToClientValue extends infer InferredResult extends object + ? InferredResult[keyof InferredResult] + : never + : never + : never + }[InferredKeys] + : never; + +type ExpectShape = Record<"headers" | "params" | "query" | "body" | "responses", unknown>; + +export type RouteToClientRoute< + GenericRoute extends Route, +> = GenericRoute extends any + ? RouteStepsToClientValues extends infer InferredValues extends ExpectShape + ? UnionToIntersection extends infer InferredIntersectionValues extends ExpectShape + ? GenericRoute["definition"]["paths"][number] extends infer InferredPath + ? InferredPath extends RoutePath + ? SimplifyTopLevel< + & { + path: InferredPath; + method: GenericRoute["definition"]["method"]; + responses: InferredValues["responses"]; + } + & ( + unknown extends InferredIntersectionValues["headers"] + ? {} + : { headers: InferredIntersectionValues["headers"] } + ) + & ( + unknown extends InferredIntersectionValues["params"] + ? {} + : { params: InferredIntersectionValues["params"] } + ) + & ( + unknown extends InferredIntersectionValues["query"] + ? {} + : { query: InferredIntersectionValues["query"] } + ) + & ( + unknown extends InferredIntersectionValues["body"] + ? {} + : { body: InferredIntersectionValues["body"] } + ) + > + : never + : never + : never + : never + : never; diff --git a/scripts/plugins/codeGenerator/types/index.ts b/scripts/plugins/codeGenerator/types/index.ts index d451b31..ae41f0c 100644 --- a/scripts/plugins/codeGenerator/types/index.ts +++ b/scripts/plugins/codeGenerator/types/index.ts @@ -1 +1,2 @@ export * from "./entrypointKey"; +export * from "./RouteToClientRoute"; diff --git a/scripts/plugins/codeGenerator/typescriptTransformer.ts b/scripts/plugins/codeGenerator/typescriptTransformer.ts index 7d653b5..babcd08 100644 --- a/scripts/plugins/codeGenerator/typescriptTransformer.ts +++ b/scripts/plugins/codeGenerator/typescriptTransformer.ts @@ -14,14 +14,16 @@ export const dateTransformer = createTransformer( addImport("@duplojs/utils/date", "TheDate"); addImport("@duplojs/utils/date", "SerializedTheDate"); - return success(Typescript.factory.createUnionTypeNode([ - Typescript.factory.createTypeReferenceNode( - Typescript.factory.createIdentifier("SerializedTheDate"), - ), - Typescript.factory.createTypeReferenceNode( - Typescript.factory.createIdentifier("TheDate"), - ), - ])); + return success( + Typescript.factory.createUnionTypeNode([ + Typescript.factory.createTypeReferenceNode( + Typescript.factory.createIdentifier("SerializedTheDate"), + ), + Typescript.factory.createTypeReferenceNode( + Typescript.factory.createIdentifier("TheDate"), + ), + ]), + ); }, ); @@ -31,14 +33,16 @@ export const timeTransformer = createTransformer( addImport("@duplojs/utils/date", "TheTime"); addImport("@duplojs/utils/date", "SerializedTheTime"); - return success(Typescript.factory.createUnionTypeNode([ - Typescript.factory.createTypeReferenceNode( - Typescript.factory.createIdentifier("SerializedTheTime"), - ), - Typescript.factory.createTypeReferenceNode( - Typescript.factory.createIdentifier("TheTime"), - ), - ])); + return success( + Typescript.factory.createUnionTypeNode([ + Typescript.factory.createTypeReferenceNode( + Typescript.factory.createIdentifier("SerializedTheTime"), + ), + Typescript.factory.createTypeReferenceNode( + Typescript.factory.createIdentifier("TheTime"), + ), + ]), + ); }, ); diff --git a/tests/core/builders/route/handler.test.ts b/tests/core/builders/route/handler.test.ts index 79c1e0a..e01a28e 100644 --- a/tests/core/builders/route/handler.test.ts +++ b/tests/core/builders/route/handler.test.ts @@ -265,13 +265,13 @@ describe("route builder handler method", () => { floor: {}, param: HandlerStepFunctionParams< | PredictedResponse<"200", "test", string> - | ServerSentEventsPredictedResponse<"200", "sse", ["message", string]> + | ServerSentEventsPredictedResponse<"200", "sse", { message: string }> | StreamPredictedResponse<"200", "stream", number> | StreamTextPredictedResponse<"200", "streamText"> > ): MaybePromise< | PredictedResponse<"200", "test", string> - | ServerSentEventsPredictedResponse<"200", "sse", ["message", string]> + | ServerSentEventsPredictedResponse<"200", "sse", { message: string }> | StreamPredictedResponse<"200", "stream", number> | StreamTextPredictedResponse<"200", "streamText"> >; diff --git a/tests/plugins/codeGenerator/plugin.test.ts b/tests/plugins/codeGenerator/plugin.test.ts index de71edf..da8571d 100644 --- a/tests/plugins/codeGenerator/plugin.test.ts +++ b/tests/plugins/codeGenerator/plugin.test.ts @@ -1,7 +1,7 @@ import { createHub, launchHookServer, ResponseContract, useRouteBuilder } from "@core"; import { type AnyFunction, DPE, E } from "@duplojs/utils"; import { TESTImplementation, setEnvironment } from "@duplojs/server-utils"; -import { codeGeneratorPlugin } from "@plugin-codeGenerator"; +import { codeGeneratorPlugin, type RouteToClientRoute } from "@plugin-codeGenerator"; describe("plugin implementation", () => { setEnvironment("TEST");