UNPKG

next-ts-api

Version:

A powerful TypeScript-first API client generator for Next.js applications with full type safety and automatic api type generation.

121 lines (120 loc) 5.27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.findRouteFiles = findRouteFiles; exports.generateTypeDefinitions = generateTypeDefinitions; exports.writeNextTsApi = writeNextTsApi; const tslib_1 = require("tslib"); const fs_1 = tslib_1.__importStar(require("fs")); const path_1 = tslib_1.__importStar(require("path")); const logger_1 = require("./logger"); const utils_1 = require("./utils"); function getRelativePath(from, to) { const relativePath = path_1.default.relative(path_1.default.dirname(from), to) .replace(/\\/g, '/') // Convert Windows paths to Unix .replace(/\.ts$/, ''); return relativePath.startsWith('.') ? relativePath : `./${relativePath}`; } function findRouteFiles(dir, routes = [], basePath = []) { const entries = fs_1.default.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path_1.default.join(dir, entry.name); if (entry.isDirectory()) { // Skip special directories if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue; findRouteFiles(fullPath, routes, [...basePath, entry.name]); } else if (entry.name === 'route.ts') { const fileContent = fs_1.default.readFileSync(fullPath, 'utf-8'); const methods = {}; // Regex to find exported HTTP method handlers (both function and const) const exportRegex = /export\s+(?:(?:async\s+)?function|const)\s+(GET|POST|PUT|DELETE|PATCH)/g; let match; while ((match = exportRegex.exec(fileContent)) !== null) { const method = match[1]; methods[method] = { importPath: fullPath, exportName: method, }; } if (Object.keys(methods).length > 0) { routes.push({ path: basePath.join('/'), methods, }); } } } return routes; } function generateTypeDefinitions(routes, options) { let imports = 'import type { ExtractNextBody, ExtractNextQuery, ExtractNextResponse, ExtractNextParams } from \'next-ts-api\';\n'; let importCounter = 1; const importMap = new Map(); // Collect imports routes.forEach(route => { Object.entries(route.methods).forEach(([method, info]) => { const importPath = getRelativePath((0, path_1.join)(options.outDir, options.outFile), info.importPath); if (!importMap.has(importPath)) { importMap.set(importPath, { counter: importCounter++, methods: new Set([method]) }); } else { importMap.get(importPath).methods.add(method); } }); }); // Generate import statements importMap.forEach((value, importPath) => { const methods = Array.from(value.methods) .map(method => `${method} as ${method}_${value.counter}`) .join(', '); imports += `import type { ${methods} } from '${importPath}';\n`; }); // Generate type definition let typeDefinition = '\nexport type ApiRoutes = {\n'; routes.forEach(route => { const routePath = route.path || 'root'; typeDefinition += ` '${routePath}': {\n`; Object.entries(route.methods).forEach(([method, info]) => { const importPath = getRelativePath((0, path_1.join)(options.outDir, options.outFile), info.importPath); const counter = importMap.get(importPath).counter; typeDefinition += ` ${method}: {\n`; if (!['GET', 'DELETE'].includes(method)) { typeDefinition += ` body: ExtractNextBody<typeof ${method}_${counter}>\n`; } typeDefinition += ` response: ExtractNextResponse<typeof ${method}_${counter}>\n`; typeDefinition += ` query: ExtractNextQuery<typeof ${method}_${counter}>\n`; typeDefinition += ` params: ExtractNextParams<typeof ${method}_${counter}>\n`; typeDefinition += ` },\n`; }); typeDefinition += ` };\n`; }); typeDefinition += '};\n'; return imports + typeDefinition; } function writeNextTsApi(options) { const PROJECT_ROOT = process.cwd(); const API_DIR = path_1.default.join((0, utils_1.getAppDirectory)(), 'api'); const TYPES_DIR = path_1.default.join(PROJECT_ROOT, 'types'); const defaultOptions = { dir: options?.dir ?? API_DIR, outDir: options?.outDir ?? TYPES_DIR, outFile: options?.outFile ?? "next-ts-api.ts", }; const opts = { ...defaultOptions, ...options, }; if (opts.outDir && !(0, fs_1.existsSync)(opts.outDir)) { (0, fs_1.mkdirSync)(opts.outDir, { recursive: true }); } try { const routes = findRouteFiles(API_DIR); const typeDefinitions = generateTypeDefinitions(routes, opts); const outputFilepath = (0, path_1.join)(opts.outDir, opts.outFile); fs_1.default.writeFileSync(outputFilepath, typeDefinitions); } catch (error) { logger_1.logger.error('Error generating API type definitions:', error); } }