diff --git a/package.json b/package.json index b0a9ff6..dbb27c5 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,9 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "format": "prettier --write .", - "format:check": "prettier --check ." + "format:check": "prettier --check .", + "watch:test": "vitest watch", + "watch:build": "tsc --noEmit --watch" }, "dependencies": { "@koa/router": "^15.1.1", diff --git a/src/Http/Request/types.ts b/src/Http/Request/types.ts index a36c0cd..5eb3d46 100644 --- a/src/Http/Request/types.ts +++ b/src/Http/Request/types.ts @@ -4,8 +4,9 @@ import type { JsonValue } from 'type-fest'; export type UploadedFile = File; export type UploadedFilesMap = Record; +export type HttpRequestBase = Request; -export interface HttpRequest extends Request { +export interface HttpRequest extends HttpRequestBase { body?: Record & JsonValue; files?: UploadedFilesMap; params: Record; diff --git a/src/Http/Scope/types.ts b/src/Http/Scope/types.ts index a7f461a..0008c85 100644 --- a/src/Http/Scope/types.ts +++ b/src/Http/Scope/types.ts @@ -1,9 +1,9 @@ -import type { Context, DefaultState, Next, Request } from 'koa'; -import { type HttpRequest } from '../Request'; +import type { Context, DefaultState, Next } from 'koa'; +import { type HttpRequest, type HttpRequestBase } from '../Request'; import { type HttpResponse } from '../Response'; import { type User } from '@/Security/types'; -export interface HttpScope extends Context { +export interface HttpScope extends Context { request: TRequest; response: HttpResponse; user?: TUser; @@ -11,7 +11,7 @@ export interface HttpScope = ( +export type HttpMiddleware = ( scope: HttpScope, next: NextMiddleware, ) => Promise; diff --git a/src/Testing/TestAgentFactory.ts b/src/Testing/TestAgentFactory.ts index 9695fb1..80c1abd 100644 --- a/src/Testing/TestAgentFactory.ts +++ b/src/Testing/TestAgentFactory.ts @@ -2,10 +2,14 @@ import { create } from '@/application/create-application'; import { type KoalaConfig } from '@/config/koala-config'; import { type HttpMiddleware, type HttpScope, type NextMiddleware } from '@/Http'; import { type User } from '@/Security/types'; +import type { Request } from 'koa'; import supertest from 'supertest'; import { type TestAgent } from './types'; -export function createTestAgent(config: KoalaConfig, agentConfig?: { actAs?: User }): TestAgent { +export function createTestAgent( + config: KoalaConfig, + agentConfig?: { actAs?: User }, +): TestAgent { const globalMiddleware = config.globalMiddleware ?? []; const testConfig = { ...config }; diff --git a/src/application/create-application.test.ts b/src/application/create-application.test.ts index 4e122b4..601ef5a 100644 --- a/src/application/create-application.test.ts +++ b/src/application/create-application.test.ts @@ -1,7 +1,7 @@ import { create } from '@/application/create-application'; import { koalaDefaultConfig } from '@/config/default-config'; import { Get, Route, RouteGroup } from '@/routing'; -import { exclusiveRoutingModeError } from '@/routing/verify-routing-mode'; +import { exclusiveRoutingModeError } from '@/routing/deprecated-decorator/verify-routing-mode'; import { expect, test } from 'vitest'; test('create app with default config', () => { diff --git a/src/application/create-application.ts b/src/application/create-application.ts index be225a5..fffe043 100644 --- a/src/application/create-application.ts +++ b/src/application/create-application.ts @@ -4,13 +4,14 @@ import { serveStaticFiles } from '@/Http/Files'; import { applyConfiguredGlobalMiddleware } from '@/Http/middleware/apply-configured-global-middleware'; import { initializeRequestScopeStorage } from '@/Http/Scope/request-scope-storage'; import { registerEventSubscribers } from '@/Kernel'; -import { registerLegacyRoutes } from '@/routing/decorator/legacy-router'; +import { registerLegacyRoutes } from '@/routing/deprecated-decorator/legacy-router'; +import { verifyRoutingMode } from '@/routing/deprecated-decorator/verify-routing-mode'; import { registerRoutes } from '@/routing/registration/register-routes'; -import { verifyRoutingMode } from '@/routing/verify-routing-mode'; +import type { Request } from 'koa'; import Koa from 'koa'; import { type Application } from './application'; -export function create(config: KoalaConfig): Application { +export function create(config: KoalaConfig): Application { const app = new Koa() as Application; const controllers = config.controllers ?? []; diff --git a/src/config/koala-config.ts b/src/config/koala-config.ts index b36329a..9860bfb 100644 --- a/src/config/koala-config.ts +++ b/src/config/koala-config.ts @@ -3,8 +3,7 @@ import { type StaticFilesOptions } from '@/Http/Files'; import { type EventSubscriber } from '@/Kernel'; import type { RouteSource } from '@/routing'; import type { DotenvConfigOptions } from 'dotenv'; -import type { HttpRequest } from '@/Http'; -import type { Request } from 'koa'; +import type { HttpRequest, HttpRequestBase } from '@/Http'; /** * @deprecated Use function-first routes from `@koala-ts/framework/routing` with `KoalaConfig.routes` instead. @@ -13,7 +12,7 @@ export type Controller = new (...args: unknown[]) => unknown; export type KoalaDotenvOptions = Pick; -export interface KoalaConfig { +export interface KoalaConfig { /** * @deprecated Use `routes` with `Route` from `@koala-ts/framework/routing` instead. */ diff --git a/src/index.ts b/src/index.ts index 74f40f9..f877075 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ // Application export { create } from '@/application/create-application'; export type { Application } from '@/application/application'; -import { getLegacyRoutes, registerLegacyRoutes } from '@/routing/decorator/legacy-router'; -import type { RouteMetadata as LegacyRouteMetadata } from '@/routing/decorator/route-metadata'; +import { getLegacyRoutes, registerLegacyRoutes } from '@/routing/deprecated-decorator/legacy-router'; +import type { RouteMetadata as LegacyRouteMetadata } from '@/routing/deprecated-decorator/route-metadata'; // Core modules export type * from '@/config/koala-config'; @@ -12,10 +12,10 @@ export * from '@/Http'; export * from '@/Kernel'; // Routing -export * from '@/routing/decorator/route'; -export type { HttpMethod } from '@/routing/http-method'; -export type { RouteOptions } from '@/routing/route-options'; -export type { RouterMethod } from '@/routing/router-method'; +export * from '@/routing/deprecated-decorator/route'; +export type { HttpMethod } from '@/routing/http-method.type'; +export type { RouteOptions } from '@/routing/declaration/route-options.type'; +export type { RouterMethod } from '@/routing/declaration/router-method.type'; /** * @deprecated Use `Route` from `@koala-ts/framework/routing` instead. */ diff --git a/src/routing/helpers/http-verb-helpers.test.ts b/src/routing/declaration/http-verb-helpers.test.ts similarity index 100% rename from src/routing/helpers/http-verb-helpers.test.ts rename to src/routing/declaration/http-verb-helpers.test.ts diff --git a/src/routing/helpers/http-verb-helpers.ts b/src/routing/declaration/http-verb-helpers.ts similarity index 66% rename from src/routing/helpers/http-verb-helpers.ts rename to src/routing/declaration/http-verb-helpers.ts index ed92cad..6538ad0 100644 --- a/src/routing/helpers/http-verb-helpers.ts +++ b/src/routing/declaration/http-verb-helpers.ts @@ -1,27 +1,26 @@ -import type { HttpMiddleware, HttpRequest } from '@/Http'; -import type { HttpMethod } from '@/routing/http-method'; -import type { RouteDefinition } from '@/routing/definition/route-definition'; -import { Route } from '@/routing/route'; -import type { Request } from 'koa'; +import type { HttpMiddleware, HttpRequest, HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; +import { Route } from '@/routing/declaration/route'; +import type { HttpMethod } from '@/routing/http-method.type'; -type RouteHandler = HttpMiddleware; -type MiddlewareAndHandler = [ +type RouteHandler = HttpMiddleware; +type MiddlewareAndHandler = [ ...middleware: HttpMiddleware[], handler: RouteHandler, ]; -type NamedMiddlewareAndHandler = [ +type NamedMiddlewareAndHandler = [ name: string, ...middlewareAndHandler: MiddlewareAndHandler, ]; -type VerbHelperRouteArguments = +type VerbHelperRouteArguments = | MiddlewareAndHandler | NamedMiddlewareAndHandler; -type UnsafeVerbHelperRouteArguments = +type UnsafeVerbHelperRouteArguments = | VerbHelperRouteArguments | [name: string] | []; -interface VerbHelperArguments { +interface VerbHelperArguments { path: string; name?: string; middleware: HttpMiddleware[]; @@ -29,19 +28,19 @@ interface VerbHelperArguments { } type NamedVerbHelper = { - ( + ( path: string, ...middlewareAndHandler: MiddlewareAndHandler - ): RouteDefinition; - ( + ): NormalizedRouteProps; + ( path: string, name: string, ...middlewareAndHandler: MiddlewareAndHandler - ): RouteDefinition; + ): NormalizedRouteProps; }; function createVerbHelper(method: HttpMethod): NamedVerbHelper { - return ( + return ( path: string, ...routeArguments: UnsafeVerbHelperRouteArguments ) => @@ -51,7 +50,7 @@ function createVerbHelper(method: HttpMethod): NamedVerbHelper { }); } -function resolveVerbHelperArguments( +function resolveVerbHelperArguments( path: string, routeArguments: UnsafeVerbHelperRouteArguments, ): VerbHelperArguments { @@ -71,13 +70,13 @@ function resolveVerbHelperArguments( }; } -function isNamedVerbHelperRouteArguments( +function isNamedVerbHelperRouteArguments( routeArguments: UnsafeVerbHelperRouteArguments, ): routeArguments is NamedMiddlewareAndHandler | [name: string] { return typeof routeArguments[0] === 'string'; } -function requireVerbHelperHandler( +function requireVerbHelperHandler( middlewareAndHandler: MiddlewareAndHandler | [], isNamedRoute: boolean, ): RouteHandler { diff --git a/src/routing/declaration/normalized-route-props.type.ts b/src/routing/declaration/normalized-route-props.type.ts new file mode 100644 index 0000000..0f5234a --- /dev/null +++ b/src/routing/declaration/normalized-route-props.type.ts @@ -0,0 +1,13 @@ +import type { HttpMiddleware, HttpRequest, HttpRequestBase } from '@/Http'; +import type { RouteBodyOptions } from '@/routing/declaration/route-body-options.type'; +import type { RouterMethod } from '@/routing/declaration/router-method.type'; + +export interface NormalizedRouteProps { + name?: string; + path: string; + methods: RouterMethod[]; + handler: HttpMiddleware; + middleware: HttpMiddleware[]; + parseBody: boolean; + bodyOptions: RouteBodyOptions; +} diff --git a/src/routing/declaration/route-body-options.type.ts b/src/routing/declaration/route-body-options.type.ts new file mode 100644 index 0000000..a0b4edd --- /dev/null +++ b/src/routing/declaration/route-body-options.type.ts @@ -0,0 +1,4 @@ +export interface RouteBodyOptions { + multipart?: boolean; + [option: string]: unknown; +} diff --git a/src/routing/helpers/route-group.test.ts b/src/routing/declaration/route-group.test.ts similarity index 93% rename from src/routing/helpers/route-group.test.ts rename to src/routing/declaration/route-group.test.ts index 87e54c6..a1a1af0 100644 --- a/src/routing/helpers/route-group.test.ts +++ b/src/routing/declaration/route-group.test.ts @@ -1,5 +1,5 @@ +import { Get } from '@/routing/declaration/http-verb-helpers'; import { describe, expect, test } from 'vitest'; -import { Get } from '@/routing/helpers/http-verb-helpers'; import { RouteGroup } from './route-group'; describe('route group', () => { diff --git a/src/routing/declaration/route-group.ts b/src/routing/declaration/route-group.ts new file mode 100644 index 0000000..0875cd9 --- /dev/null +++ b/src/routing/declaration/route-group.ts @@ -0,0 +1,33 @@ +import type { HttpMiddleware, HttpRequest, HttpRequestBase } from '@/Http'; + +import { RouteProps } from '@/routing/declaration/route-props.type'; +import type { RouteSource } from '@/routing/declaration/route-source.type'; + +export type RouteConfigOverlay = Pick< + RouteProps, + 'middleware' | 'options' +>; + +export interface RouteGroupOptions { + prefix?: string; + namePrefix?: string; + middleware?: HttpMiddleware[]; + routeConfig?: Record>; +} + +export interface RouteGroupDefinition { + kind: 'route-group'; + options: RouteGroupOptions; + resolveRoutes: () => RouteSource[]; +} + +export function RouteGroup( + options: RouteGroupOptions, + resolveRoutes: () => RouteSource[], +): RouteGroupDefinition { + return { + kind: 'route-group', + options, + resolveRoutes, + }; +} diff --git a/src/routing/declaration/route-options.type.ts b/src/routing/declaration/route-options.type.ts new file mode 100644 index 0000000..49d9af1 --- /dev/null +++ b/src/routing/declaration/route-options.type.ts @@ -0,0 +1,7 @@ +import type { RouteBodyOptions } from '@/routing/declaration/route-body-options.type'; + +export type RouteOptions = Partial< + RouteBodyOptions & { + parseBody?: boolean; + } +>; diff --git a/src/routing/declaration/route-props.type.ts b/src/routing/declaration/route-props.type.ts new file mode 100644 index 0000000..9592dcd --- /dev/null +++ b/src/routing/declaration/route-props.type.ts @@ -0,0 +1,12 @@ +import type { HttpMiddleware, HttpRequest, HttpRequestBase } from '@/Http'; +import type { RouteOptions } from '@/routing/declaration/route-options.type'; +import type { HttpMethod } from '@/routing/http-method.type'; + +export interface RouteProps { + name?: string; + path: string; + method: HttpMethod | HttpMethod[]; + handler: HttpMiddleware; + middleware?: HttpMiddleware[]; + options?: RouteOptions; +} diff --git a/src/routing/declaration/route-source.type.ts b/src/routing/declaration/route-source.type.ts new file mode 100644 index 0000000..3462eab --- /dev/null +++ b/src/routing/declaration/route-source.type.ts @@ -0,0 +1,7 @@ +import type { HttpRequest, HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; +import type { RouteGroupDefinition } from '@/routing/declaration/route-group'; + +export type RouteSource = + | NormalizedRouteProps + | RouteGroupDefinition; diff --git a/src/routing/definition/create-route-definition.test.ts b/src/routing/declaration/route.test.ts similarity index 66% rename from src/routing/definition/create-route-definition.test.ts rename to src/routing/declaration/route.test.ts index fdd8ffd..be31e95 100644 --- a/src/routing/definition/create-route-definition.test.ts +++ b/src/routing/declaration/route.test.ts @@ -1,15 +1,11 @@ import { describe, expect, test, vi } from 'vitest'; -import { createRouteDefinition } from './create-route-definition'; +import { Route } from './route'; -describe('create route definition', () => { +describe('routing route', () => { test('it creates a route definition for a single method', () => { const handler = vi.fn(async () => undefined); - const route = createRouteDefinition({ - method: 'GET', - path: '/users', - handler, - }); + const route = Route({ method: 'GET', path: '/users', handler }); expect(route).toEqual({ path: '/users', @@ -25,7 +21,7 @@ describe('create route definition', () => { const handler = vi.fn(async () => undefined); const middleware = [vi.fn(async () => undefined)]; - const route = createRouteDefinition({ + const route = Route({ name: 'users.create', method: 'POST', path: '/users', @@ -53,11 +49,7 @@ describe('create route definition', () => { test('it qualifies multiple methods', () => { const handler = vi.fn(async () => undefined); - const route = createRouteDefinition({ - method: ['GET', 'POST'], - path: '/users', - handler, - }); + const route = Route({ method: ['GET', 'POST'], path: '/users', handler }); expect(route.methods).toEqual(['get', 'post']); }); @@ -65,11 +57,7 @@ describe('create route definition', () => { test('it normalizes any and all to all', () => { const handler = vi.fn(async () => undefined); - const route = createRouteDefinition({ - method: ['ANY', 'ALL'], - path: '/users', - handler, - }); + const route = Route({ method: ['ANY', 'ALL'], path: '/users', handler }); expect(route.methods).toEqual(['all']); }); @@ -77,23 +65,15 @@ describe('create route definition', () => { test('it normalizes any with specific methods to all', () => { const handler = vi.fn(async () => undefined); - const route = createRouteDefinition({ - method: ['ANY', 'GET'], - path: '/users', - handler, - }); + const route = Route({ method: ['ANY', 'GET'], path: '/users', handler }); expect(route.methods).toEqual(['all']); }); - test('it removes duplicate specific methods case-insensitively', () => { + test('it removes duplicate specific methods case insensitively', () => { const handler = vi.fn(async () => undefined); - const route = createRouteDefinition({ - method: ['GET', 'get', 'POST'], - path: '/users', - handler, - }); + const route = Route({ method: ['GET', 'get', 'POST'], path: '/users', handler }); expect(route.methods).toEqual(['get', 'post']); }); diff --git a/src/routing/declaration/route.ts b/src/routing/declaration/route.ts new file mode 100644 index 0000000..6a1bb16 --- /dev/null +++ b/src/routing/declaration/route.ts @@ -0,0 +1,10 @@ +import type { HttpRequest, HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; +import { RouteProps } from '@/routing/declaration/route-props.type'; +import { normalizeRouteProps } from '@/routing/normalization/normalize-route-props'; + +export function Route( + route: RouteProps, +): NormalizedRouteProps { + return normalizeRouteProps(route); +} diff --git a/src/routing/router-method.ts b/src/routing/declaration/router-method.type.ts similarity index 100% rename from src/routing/router-method.ts rename to src/routing/declaration/router-method.type.ts diff --git a/src/routing/definition/create-route-definition.ts b/src/routing/definition/create-route-definition.ts deleted file mode 100644 index 09ec1a0..0000000 --- a/src/routing/definition/create-route-definition.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { HttpMiddleware, HttpRequest } from '@/Http'; -import type { HttpMethod } from '@/routing/http-method'; -import type { RouteOptions } from '@/routing/route-options'; -import { resolveRouteOptions } from '@/routing/definition/resolve-route-options'; -import type { RouterMethod } from '@/routing/router-method'; -import type { RouteDefinition } from './route-definition'; -import type { Request } from 'koa'; - -interface RouteDefinitionInput { - name?: string; - method: HttpMethod | HttpMethod[]; - path: string; - handler: HttpMiddleware; - middleware?: HttpMiddleware[]; - options?: RouteOptions; -} - -export function createRouteDefinition({ - name, - method, - path, - handler, - middleware = [], - options = {}, -}: RouteDefinitionInput): RouteDefinition { - const routeOptions = resolveRouteOptions(options); - - return { - name, - path, - methods: qualifyMethods(method), - handler, - middleware, - ...routeOptions, - }; -} - -function qualifyMethods(method: HttpMethod | HttpMethod[]): RouterMethod[] { - const methods = Array.isArray(method) ? method : [method]; - const qualifiedMethods: RouterMethod[] = methods.map(method => { - const lower = method.toLowerCase() as RouterMethod; - - return ['any', 'all'].includes(lower) ? 'all' : lower; - }); - - return qualifiedMethods.includes('all') ? ['all'] : [...new Set(qualifiedMethods)]; -} diff --git a/src/routing/definition/resolve-route-options.test.ts b/src/routing/definition/resolve-route-options.test.ts deleted file mode 100644 index 9ee967a..0000000 --- a/src/routing/definition/resolve-route-options.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { mergeRouteOptions, resolveRouteOptions } from './resolve-route-options'; - -describe('resolve route options', () => { - test('it resolves route definition options from route options', () => { - const routeOptions = resolveRouteOptions({ - multipart: true, - parseBody: false, - }); - - expect(routeOptions).toEqual({ - parseBody: false, - bodyOptions: { - multipart: true, - }, - }); - }); - - test('it merges overlay options into an existing route definition', () => { - const routeOptions = mergeRouteOptions( - { - path: '/users', - methods: ['post'], - handler: async () => undefined, - middleware: [], - parseBody: true, - bodyOptions: { - jsonLimit: '1mb', - }, - }, - { - multipart: true, - parseBody: false, - }, - ); - - expect(routeOptions).toEqual({ - parseBody: false, - bodyOptions: { - jsonLimit: '1mb', - multipart: true, - }, - }); - }); - - test('it keeps the current parse body setting when the overlay omits it', () => { - const routeOptions = mergeRouteOptions( - { - path: '/users', - methods: ['post'], - handler: async () => undefined, - middleware: [], - parseBody: false, - bodyOptions: { - jsonLimit: '1mb', - }, - }, - { - multipart: true, - }, - ); - - expect(routeOptions).toEqual({ - parseBody: false, - bodyOptions: { - jsonLimit: '1mb', - multipart: true, - }, - }); - }); -}); diff --git a/src/routing/definition/resolve-route-options.ts b/src/routing/definition/resolve-route-options.ts deleted file mode 100644 index b5a6d28..0000000 --- a/src/routing/definition/resolve-route-options.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { RouteOptions } from '@/routing/route-options'; -import type { RouteDefinition } from './route-definition'; - -export function resolveRouteOptions(options: RouteOptions): Pick { - return { - parseBody: options.parseBody ?? true, - bodyOptions: extractBodyOptions(options), - }; -} - -export function mergeRouteOptions( - route: RouteDefinition, - options: RouteOptions, -): Pick { - return { - parseBody: options.parseBody ?? route.parseBody, - bodyOptions: { - ...route.bodyOptions, - ...extractBodyOptions(options), - }, - }; -} - -function extractBodyOptions(options: RouteOptions): RouteDefinition['bodyOptions'] { - const { parseBody: _parseBody, ...bodyOptions } = options; - - return bodyOptions as RouteDefinition['bodyOptions']; -} diff --git a/src/routing/definition/route-definition.ts b/src/routing/definition/route-definition.ts deleted file mode 100644 index 8be96c9..0000000 --- a/src/routing/definition/route-definition.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { HttpRequest, HttpMiddleware } from '@/Http'; -import type { RouterMethod } from '@/routing/router-method'; -import type { Request } from 'koa'; -import type { KoaBodyMiddlewareOptions } from 'koa-body'; - -export interface RouteDefinition { - name?: string; - path: string; - methods: RouterMethod[]; - handler: HttpMiddleware; - middleware: HttpMiddleware[]; - parseBody: boolean; - bodyOptions: Partial; -} diff --git a/src/routing/decorator/legacy-router.test.ts b/src/routing/deprecated-decorator/legacy-router.test.ts similarity index 100% rename from src/routing/decorator/legacy-router.test.ts rename to src/routing/deprecated-decorator/legacy-router.test.ts diff --git a/src/routing/decorator/legacy-router.ts b/src/routing/deprecated-decorator/legacy-router.ts similarity index 91% rename from src/routing/decorator/legacy-router.ts rename to src/routing/deprecated-decorator/legacy-router.ts index bd1d483..79937f8 100644 --- a/src/routing/decorator/legacy-router.ts +++ b/src/routing/deprecated-decorator/legacy-router.ts @@ -1,7 +1,7 @@ import { type Application } from '@/application/application'; import { type HttpMiddleware } from '@/Http'; -import { createRouteDefinition } from '@/routing/definition/create-route-definition'; -import type { RouteDefinition } from '@/routing/definition/route-definition'; +import { Route as createRouteDefinition } from '@/routing'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; import { registerRoutes } from '@/routing/registration/register-routes'; import 'reflect-metadata'; import type { Route } from './route'; @@ -23,7 +23,7 @@ export function getLegacyRoutes(): RouteMetadata[] { return (Reflect.getMetadata(legacyRouteMetadataKey, Reflect) ?? []) as RouteMetadata[]; } -export function getLegacyRouteDefinitions(): RouteDefinition[] { +export function getLegacyRouteDefinitions(): NormalizedRouteProps[] { return getLegacyRoutes().map(route => createRouteDefinition({ method: route.methods, diff --git a/src/routing/decorator/route-metadata.ts b/src/routing/deprecated-decorator/route-metadata.ts similarity index 68% rename from src/routing/decorator/route-metadata.ts rename to src/routing/deprecated-decorator/route-metadata.ts index 5257621..cabb213 100644 --- a/src/routing/decorator/route-metadata.ts +++ b/src/routing/deprecated-decorator/route-metadata.ts @@ -1,6 +1,6 @@ import type { HttpMiddleware } from '@/Http'; -import type { RouteOptions } from '@/routing/route-options'; -import type { RouterMethod } from '@/routing/router-method'; +import type { RouteOptions } from '@/routing/declaration/route-options.type'; +import type { RouterMethod } from '@/routing/declaration/router-method.type'; /** * @deprecated Legacy decorator routing metadata. Use `@koala-ts/framework/routing` instead. diff --git a/src/routing/decorator/route.ts b/src/routing/deprecated-decorator/route.ts similarity index 78% rename from src/routing/decorator/route.ts rename to src/routing/deprecated-decorator/route.ts index 77e909e..ce4ffcd 100644 --- a/src/routing/decorator/route.ts +++ b/src/routing/deprecated-decorator/route.ts @@ -1,6 +1,6 @@ import type { HttpMiddleware } from '@/Http'; -import type { HttpMethod } from '@/routing/http-method'; -import type { RouteOptions } from '@/routing/route-options'; +import type { RouteOptions } from '@/routing/declaration/route-options.type'; +import type { HttpMethod } from '@/routing/http-method.type'; import { createLegacyRouteDecorator } from './legacy-router'; /** diff --git a/src/routing/verify-routing-mode.test.ts b/src/routing/deprecated-decorator/verify-routing-mode.test.ts similarity index 100% rename from src/routing/verify-routing-mode.test.ts rename to src/routing/deprecated-decorator/verify-routing-mode.test.ts diff --git a/src/routing/verify-routing-mode.ts b/src/routing/deprecated-decorator/verify-routing-mode.ts similarity index 72% rename from src/routing/verify-routing-mode.ts rename to src/routing/deprecated-decorator/verify-routing-mode.ts index 8fa2ec5..f1c16a8 100644 --- a/src/routing/verify-routing-mode.ts +++ b/src/routing/deprecated-decorator/verify-routing-mode.ts @@ -1,9 +1,10 @@ import type { KoalaConfig } from '@/config/koala-config'; +import type { HttpRequestBase } from '@/Http'; export const exclusiveRoutingModeError = 'Koala routing mode is exclusive. Use either legacy controllers from @koala-ts/framework or routes from @koala-ts/framework/routing.'; -export function verifyRoutingMode(config: KoalaConfig): void { +export function verifyRoutingMode(config: KoalaConfig): void { const controllers = config.controllers ?? []; if (config.routes !== undefined && controllers.length > 0) { diff --git a/src/routing/helpers/route-group.ts b/src/routing/helpers/route-group.ts deleted file mode 100644 index aef2803..0000000 --- a/src/routing/helpers/route-group.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { HttpMiddleware, HttpRequest } from '@/Http'; -import type { RouteDeclaration } from '@/routing/route'; -import type { RouteSource } from '@/routing/source/route-source'; -import type { Request } from 'koa'; - -export type RouteConfigOverlay = Pick; - -export interface RouteGroupOptions { - prefix?: string; - namePrefix?: string; - middleware?: HttpMiddleware[]; - routeConfig?: Record; -} - -export interface RouteGroupDefinition { - kind: 'route-group'; - options: RouteGroupOptions; - resolveRoutes: () => RouteSource[]; -} - -export function RouteGroup( - options: RouteGroupOptions, - resolveRoutes: () => RouteSource[], -): RouteGroupDefinition { - return { - kind: 'route-group', - options, - resolveRoutes, - }; -} diff --git a/src/routing/http-method.ts b/src/routing/http-method.type.ts similarity index 100% rename from src/routing/http-method.ts rename to src/routing/http-method.type.ts diff --git a/src/routing/index.ts b/src/routing/index.ts index 5c5e4d8..5d8006d 100644 --- a/src/routing/index.ts +++ b/src/routing/index.ts @@ -1,9 +1,6 @@ -export { Route } from './route'; -export { createPathFor } from './helpers/create-path-for'; -export { RouteGroup } from './helpers/route-group'; -export { Any, Delete, Get, Head, Options, Patch, Post, Put } from './helpers/http-verb-helpers'; -export type { RouteDeclaration } from './route'; -export type { RouteConfigOverlay, RouteGroupDefinition, RouteGroupOptions } from './helpers/route-group'; -export type { RouteSource } from './source/route-source'; -export type { HttpMethod } from './http-method'; -export type { RouteOptions } from './route-options'; +export type { HttpMethod } from '@/routing/http-method.type'; +export type { RouteSource } from '@/routing/declaration/route-source.type'; +export { Route } from './declaration/route'; +export { createPathFor } from './path/create-path-for'; +export { RouteGroup } from './declaration/route-group'; +export { Any, Delete, Get, Head, Options, Patch, Post, Put } from '@/routing/declaration/http-verb-helpers'; diff --git a/src/routing/normalization/normalize-route-props.ts b/src/routing/normalization/normalize-route-props.ts new file mode 100644 index 0000000..6b71b33 --- /dev/null +++ b/src/routing/normalization/normalize-route-props.ts @@ -0,0 +1,43 @@ +import type { HttpRequest, HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; +import type { RouteOptions } from '@/routing/declaration/route-options.type'; +import { RouteProps } from '@/routing/declaration/route-props.type'; +import type { RouterMethod } from '@/routing/declaration/router-method.type'; +import type { HttpMethod } from '@/routing/http-method.type'; + +function resolveRouteOptions(options: RouteOptions): Pick { + return { + parseBody: options.parseBody ?? true, + bodyOptions: extractBodyOptions(options), + }; +} + +function extractBodyOptions(options: RouteOptions): NormalizedRouteProps['bodyOptions'] { + const { parseBody: _parseBody, ...bodyOptions } = options; + + return bodyOptions as NormalizedRouteProps['bodyOptions']; +} + +function qualifyMethods(method: HttpMethod | HttpMethod[]): RouterMethod[] { + const methods = Array.isArray(method) ? method : [method]; + const qualifiedMethods: RouterMethod[] = methods.map(method => { + const lower = method.toLowerCase() as RouterMethod; + + return ['any', 'all'].includes(lower) ? 'all' : lower; + }); + + return qualifiedMethods.includes('all') ? ['all'] : [...new Set(qualifiedMethods)]; +} + +export function normalizeRouteProps( + routeProps: RouteProps, +): NormalizedRouteProps { + return { + name: routeProps.name, + path: routeProps.path, + methods: qualifyMethods(routeProps.method), + handler: routeProps.handler, + middleware: routeProps.middleware ?? [], + ...resolveRouteOptions(routeProps.options ?? {}), + }; +} diff --git a/src/routing/source/normalize-route-sources.test.ts b/src/routing/normalization/normalize-route-sources.test.ts similarity index 98% rename from src/routing/source/normalize-route-sources.test.ts rename to src/routing/normalization/normalize-route-sources.test.ts index 7d97393..0fc6145 100644 --- a/src/routing/source/normalize-route-sources.test.ts +++ b/src/routing/normalization/normalize-route-sources.test.ts @@ -1,6 +1,6 @@ +import { Get } from '@/routing/declaration/http-verb-helpers'; +import { RouteGroup } from '@/routing/declaration/route-group'; import { describe, expect, test, vi } from 'vitest'; -import { Get } from '@/routing/helpers/http-verb-helpers'; -import { RouteGroup } from '@/routing/helpers/route-group'; import { normalizeRouteSources } from './normalize-route-sources'; describe('normalize route sources', () => { diff --git a/src/routing/source/normalize-route-sources.ts b/src/routing/normalization/normalize-route-sources.ts similarity index 54% rename from src/routing/source/normalize-route-sources.ts rename to src/routing/normalization/normalize-route-sources.ts index a1ee210..477d8fb 100644 --- a/src/routing/source/normalize-route-sources.ts +++ b/src/routing/normalization/normalize-route-sources.ts @@ -1,25 +1,26 @@ -import type { RouteDefinition } from '@/routing/definition/route-definition'; -import { mergeRouteOptions } from '@/routing/definition/resolve-route-options'; -import type { RouteGroupDefinition } from '@/routing/helpers/route-group'; -import type { RouteSource } from './route-source'; +import type { HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; +import type { RouteGroupDefinition } from '@/routing/declaration/route-group'; +import type { RouteSource } from '@/routing/declaration/route-source.type'; +import { mergeRouteOptions } from '@/routing/normalization/resolve-route-options'; -interface NormalizationContext { +interface NormalizationContext { prefix: string; namePrefix: string; - middleware: RouteDefinition['middleware']; + middleware: NormalizedRouteProps['middleware']; } -const defaultNormalizationContext: NormalizationContext = { +const defaultNormalizationContext = { prefix: '', namePrefix: '', middleware: [], }; -export function normalizeRouteSources( - routeSources: RouteSource[], - context: NormalizationContext = defaultNormalizationContext, -): RouteDefinition[] { - const routes: RouteDefinition[] = []; +export function normalizeRouteSources( + routeSources: RouteSource[], + context: NormalizationContext = defaultNormalizationContext, +): NormalizedRouteProps[] { + const routes: NormalizedRouteProps[] = []; for (const routeSource of routeSources) { if (isRouteGroupDefinition(routeSource)) { @@ -33,13 +34,19 @@ export function normalizeRouteSources( return routes; } -function normalizeRouteGroup(group: RouteGroupDefinition, parentContext: NormalizationContext): RouteDefinition[] { +function normalizeRouteGroup( + group: RouteGroupDefinition, + parentContext: NormalizationContext, +): NormalizedRouteProps[] { const context = createChildContext(group, parentContext); return normalizeRouteSources(applyRouteConfig(group.resolveRoutes(), group), context); } -function createChildContext(group: RouteGroupDefinition, parentContext: NormalizationContext): NormalizationContext { +function createChildContext( + group: RouteGroupDefinition, + parentContext: NormalizationContext, +): NormalizationContext { return { prefix: group.options.prefix === undefined @@ -50,7 +57,10 @@ function createChildContext(group: RouteGroupDefinition, parentContext: Normaliz }; } -function normalizeRouteDefinition(route: RouteDefinition, context: NormalizationContext): RouteDefinition { +function normalizeRouteDefinition( + route: NormalizedRouteProps, + context: NormalizationContext, +): NormalizedRouteProps { return { ...route, path: joinRoutePath(context.prefix, route.path), @@ -59,7 +69,10 @@ function normalizeRouteDefinition(route: RouteDefinition, context: Normalization }; } -function applyRouteConfig(routeSources: RouteSource[], group: RouteGroupDefinition): RouteSource[] { +function applyRouteConfig( + routeSources: RouteSource[], + group: RouteGroupDefinition, +): RouteSource[] { return routeSources.map(routeSource => { if (isRouteGroupDefinition(routeSource)) { return routeSource; @@ -79,14 +92,16 @@ function applyRouteConfig(routeSources: RouteSource[], group: RouteGroupDefiniti }); } -function resolveRouteConfigOptions( - route: RouteDefinition, - routeConfig: NonNullable[string], -): Partial> { +function resolveRouteConfigOptions( + route: NormalizedRouteProps, + routeConfig: NonNullable['options']['routeConfig']>[string], +): Partial, 'parseBody' | 'bodyOptions'>> { return routeConfig.options ? mergeRouteOptions(route, routeConfig.options) : {}; } -function isRouteGroupDefinition(routeSource: RouteSource): routeSource is RouteGroupDefinition { +function isRouteGroupDefinition( + routeSource: RouteSource, +): routeSource is RouteGroupDefinition { return 'kind' in routeSource && routeSource.kind === 'route-group'; } diff --git a/src/routing/normalization/resolve-route-options.ts b/src/routing/normalization/resolve-route-options.ts new file mode 100644 index 0000000..dc4cabf --- /dev/null +++ b/src/routing/normalization/resolve-route-options.ts @@ -0,0 +1,22 @@ +import type { HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; +import type { RouteOptions } from '@/routing/declaration/route-options.type'; + +function extractBodyOptions(options: RouteOptions): NormalizedRouteProps['bodyOptions'] { + const { parseBody: _parseBody, ...bodyOptions } = options; + + return bodyOptions as NormalizedRouteProps['bodyOptions']; +} + +export function mergeRouteOptions( + route: NormalizedRouteProps, + options: RouteOptions, +): Pick, 'parseBody' | 'bodyOptions'> { + return { + parseBody: options.parseBody ?? route.parseBody, + bodyOptions: { + ...route.bodyOptions, + ...extractBodyOptions(options), + }, + }; +} diff --git a/src/routing/source/create-path-catalog.test.ts b/src/routing/path/create-path-catalog.test.ts similarity index 92% rename from src/routing/source/create-path-catalog.test.ts rename to src/routing/path/create-path-catalog.test.ts index 7aed663..67752de 100644 --- a/src/routing/source/create-path-catalog.test.ts +++ b/src/routing/path/create-path-catalog.test.ts @@ -1,7 +1,7 @@ +import { Get } from '@/routing/declaration/http-verb-helpers'; +import { Route } from '@/routing/declaration/route'; +import { RouteGroup } from '@/routing/declaration/route-group'; import { describe, expect, test, vi } from 'vitest'; -import { Get } from '@/routing/helpers/http-verb-helpers'; -import { RouteGroup } from '@/routing/helpers/route-group'; -import { Route } from '@/routing/route'; import { createPathCatalog } from './create-path-catalog'; describe('create path catalog', () => { diff --git a/src/routing/source/create-path-catalog.ts b/src/routing/path/create-path-catalog.ts similarity index 72% rename from src/routing/source/create-path-catalog.ts rename to src/routing/path/create-path-catalog.ts index fd3d1e0..c862eb6 100644 --- a/src/routing/source/create-path-catalog.ts +++ b/src/routing/path/create-path-catalog.ts @@ -1,5 +1,5 @@ -import { normalizeRouteSources } from '@/routing/source/normalize-route-sources'; -import type { RouteSource } from '@/routing/source/route-source'; +import type { RouteSource } from '@/routing/declaration/route-source.type'; +import { normalizeRouteSources } from '@/routing/normalization/normalize-route-sources'; export function createPathCatalog(routeSources: RouteSource[]): Map { const catalog = new Map(); diff --git a/src/routing/helpers/create-path-for.test.ts b/src/routing/path/create-path-for.test.ts similarity index 93% rename from src/routing/helpers/create-path-for.test.ts rename to src/routing/path/create-path-for.test.ts index dbc2f8f..a6867cd 100644 --- a/src/routing/helpers/create-path-for.test.ts +++ b/src/routing/path/create-path-for.test.ts @@ -1,7 +1,7 @@ +import { Get } from '@/routing/declaration/http-verb-helpers'; import { describe, expect, test, vi } from 'vitest'; +import { RouteGroup } from '../declaration/route-group'; import { createPathFor } from './create-path-for'; -import { Get } from './http-verb-helpers'; -import { RouteGroup } from './route-group'; describe('create path for', () => { describe('path resolution', () => { diff --git a/src/routing/helpers/create-path-for.ts b/src/routing/path/create-path-for.ts similarity index 75% rename from src/routing/helpers/create-path-for.ts rename to src/routing/path/create-path-for.ts index 7d49c72..c8f0eb0 100644 --- a/src/routing/helpers/create-path-for.ts +++ b/src/routing/path/create-path-for.ts @@ -1,6 +1,6 @@ -import { createPathCatalog } from '@/routing/source/create-path-catalog'; -import { createPathTemplateResolver } from '@/routing/source/resolve-path-template'; -import type { RouteSource } from '@/routing/source/route-source'; +import type { RouteSource } from '@/routing/declaration/route-source.type'; +import { createPathCatalog } from './create-path-catalog'; +import { createPathTemplateResolver } from './resolve-path-template'; type PathParamValue = string | number | boolean; type PathParams = Record; diff --git a/src/routing/source/resolve-path-template.test.ts b/src/routing/path/resolve-path-template.test.ts similarity index 100% rename from src/routing/source/resolve-path-template.test.ts rename to src/routing/path/resolve-path-template.test.ts diff --git a/src/routing/source/resolve-path-template.ts b/src/routing/path/resolve-path-template.ts similarity index 100% rename from src/routing/source/resolve-path-template.ts rename to src/routing/path/resolve-path-template.ts diff --git a/src/routing/registration/expand-route-definitions.test.ts b/src/routing/registration/create-route-registrations.test.ts similarity index 79% rename from src/routing/registration/expand-route-definitions.test.ts rename to src/routing/registration/create-route-registrations.test.ts index 63873ae..f0a2dde 100644 --- a/src/routing/registration/expand-route-definitions.test.ts +++ b/src/routing/registration/create-route-registrations.test.ts @@ -1,11 +1,11 @@ import { describe, expect, test, vi } from 'vitest'; -import { expandRouteDefinitions } from './expand-route-definitions'; +import { createRouteRegistrations } from './create-route-registrations'; -describe('expand route definitions', () => { - test('it expands route definitions into route registrations', () => { +describe('create route registrations', () => { + test('it creates route registrations from normalized routes', () => { const handler = vi.fn(async () => undefined); - const registrations = expandRouteDefinitions([ + const registrations = createRouteRegistrations([ { path: '/users', methods: ['get', 'post'], @@ -33,7 +33,7 @@ describe('expand route definitions', () => { test('it prepends koa body middleware when body parsing is enabled', () => { const handler = vi.fn(async () => undefined); - const registrations = expandRouteDefinitions([ + const registrations = createRouteRegistrations([ { path: '/users', methods: ['post'], diff --git a/src/routing/registration/create-route-registrations.ts b/src/routing/registration/create-route-registrations.ts new file mode 100644 index 0000000..8346c75 --- /dev/null +++ b/src/routing/registration/create-route-registrations.ts @@ -0,0 +1,38 @@ +import type { HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; +import type { RouteRegistration } from '@/routing/registration/route-registration'; +import { koaBody } from 'koa-body'; + +export function createRouteRegistrations( + routes: NormalizedRouteProps[], +): RouteRegistration[] { + const registrations: RouteRegistration[] = []; + + for (const route of routes) { + registrations.push(...createRouteRegistration(route)); + } + + return registrations; +} + +function createRouteRegistration( + route: NormalizedRouteProps, +): RouteRegistration[] { + const middleware = resolveRouteMiddleware(route); + + return route.methods.map(method => ({ + method, + path: route.path, + middleware, + })); +} + +function resolveRouteMiddleware( + route: NormalizedRouteProps, +): RouteRegistration['middleware'] { + const middlewareStack = [...route.middleware, route.handler]; + + return route.parseBody + ? [koaBody(route.bodyOptions as Parameters[0]), ...middlewareStack] + : middlewareStack; +} diff --git a/src/routing/registration/expand-route-definitions.ts b/src/routing/registration/expand-route-definitions.ts deleted file mode 100644 index cea4d49..0000000 --- a/src/routing/registration/expand-route-definitions.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { koaBody } from 'koa-body'; -import type { RouteDefinition } from '@/routing/definition/route-definition'; -import type { RouteRegistration } from '@/routing/registration/route-registration'; - -export function expandRouteDefinitions(routes: RouteDefinition[]): RouteRegistration[] { - const registrations: RouteRegistration[] = []; - - for (const route of routes) { - registrations.push(...expandRouteDefinition(route)); - } - - return registrations; -} - -function expandRouteDefinition(route: RouteDefinition): RouteRegistration[] { - const middleware = resolveRouteMiddleware(route); - - return route.methods.map(method => ({ - method, - path: route.path, - middleware, - })); -} - -function resolveRouteMiddleware(route: RouteDefinition): RouteRegistration['middleware'] { - const middlewareStack = [...route.middleware, route.handler]; - - return route.parseBody ? [koaBody(route.bodyOptions), ...middlewareStack] : middlewareStack; -} diff --git a/src/routing/registration/register-routes.test.ts b/src/routing/registration/register-routes.test.ts index 5729922..a84355c 100644 --- a/src/routing/registration/register-routes.test.ts +++ b/src/routing/registration/register-routes.test.ts @@ -1,11 +1,11 @@ +import { type Application } from '@/application/application'; +import { Get } from '@/routing/declaration/http-verb-helpers'; +import { Route } from '@/routing/declaration/route'; +import { RouteGroup } from '@/routing/declaration/route-group'; import Koa from 'koa'; import supertest from 'supertest'; import { describe, expect, test } from 'vitest'; -import { type Application } from '@/application/application'; -import { Get } from '@/routing/helpers/http-verb-helpers'; import { registerRoutes } from './register-routes'; -import { RouteGroup } from '@/routing/helpers/route-group'; -import { Route } from '@/routing/route'; describe('register routes', () => { test('it registers a function-first route', async () => { diff --git a/src/routing/registration/register-routes.ts b/src/routing/registration/register-routes.ts index f70f9ea..daa3452 100644 --- a/src/routing/registration/register-routes.ts +++ b/src/routing/registration/register-routes.ts @@ -1,13 +1,16 @@ import { type Application } from '@/application/application'; -import { type HttpScope } from '@/Http'; -import { expandRouteDefinitions } from '@/routing/registration/expand-route-definitions'; -import { normalizeRouteSources } from '@/routing/source/normalize-route-sources'; -import type { RouteSource } from '@/routing/source/route-source'; -import { validateRouteDefinitions } from '@/routing/validation/validate-route-definitions'; -import { type DefaultContext, type DefaultState, type Middleware } from 'koa'; +import { type HttpRequestBase, type HttpScope } from '@/Http'; +import type { RouteSource } from '@/routing/declaration/route-source.type'; +import { normalizeRouteSources } from '@/routing/normalization/normalize-route-sources'; +import { createRouteRegistrations } from '@/routing/registration/create-route-registrations'; +import { validateRouteRegistrations } from '@/routing/registration/validate-route-registrations'; import Router, { type RouterInstance } from '@koa/router'; +import { type DefaultContext, type DefaultState, type Middleware } from 'koa'; -export function registerRoutes(app: Application, routes: RouteSource[] = []): Application { +export function registerRoutes( + app: Application, + routes: RouteSource[] = [], +): Application { const router = createRouter(routes); app.use(router.routes() as unknown as Middleware); @@ -16,15 +19,18 @@ export function registerRoutes(app: Application, routes: RouteSource[] = []): Ap return app; } -function createRouter(routeSources: RouteSource[]): RouterInstance { +function createRouter(routeSources: RouteSource[]): RouterInstance { const router = new Router(); const routes = normalizeRouteSources(routeSources); - const registrations = expandRouteDefinitions(routes); + const registrations = createRouteRegistrations(routes); - validateRouteDefinitions(routes, registrations); + validateRouteRegistrations(routes, registrations); for (const route of registrations) { - router[route.method](route.path, ...(route.middleware as Middleware[])); + router[route.method]( + route.path, + ...(route.middleware as unknown as Middleware[]), + ); } return router; diff --git a/src/routing/registration/route-registration.ts b/src/routing/registration/route-registration.ts index cf0af3e..77b9cba 100644 --- a/src/routing/registration/route-registration.ts +++ b/src/routing/registration/route-registration.ts @@ -1,8 +1,13 @@ -import type { RouteDefinition } from '@/routing/definition/route-definition'; -import type { RouterMethod } from '@/routing/router-method'; +import type { HttpRequest, HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; +import type { RouterMethod } from '@/routing/declaration/router-method.type'; -export interface RouteRegistration { +type RouteRegistrationMiddleware = + | NormalizedRouteProps['middleware'][number] + | NormalizedRouteProps['handler']; + +export interface RouteRegistration { method: RouterMethod; path: string; - middleware: Array; + middleware: RouteRegistrationMiddleware[]; } diff --git a/src/routing/validation/validate-route-definitions.test.ts b/src/routing/registration/validate-route-registrations.test.ts similarity index 92% rename from src/routing/validation/validate-route-definitions.test.ts rename to src/routing/registration/validate-route-registrations.test.ts index 73c7ac9..980c310 100644 --- a/src/routing/validation/validate-route-definitions.test.ts +++ b/src/routing/registration/validate-route-registrations.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test, vi } from 'vitest'; -import { validateRouteDefinitions } from './validate-route-definitions'; +import { validateRouteRegistrations } from './validate-route-registrations'; -describe('validate route definitions', () => { +describe('validate route registrations', () => { test('it rejects duplicate route signatures', () => { const handler = vi.fn(async () => undefined); expect(() => - validateRouteDefinitions( + validateRouteRegistrations( [ { path: '/users', @@ -45,7 +45,7 @@ describe('validate route definitions', () => { const handler = vi.fn(async () => undefined); expect(() => - validateRouteDefinitions( + validateRouteRegistrations( [ { name: 'users.list', @@ -86,7 +86,7 @@ describe('validate route definitions', () => { const handler = vi.fn(async () => undefined); expect(() => - validateRouteDefinitions( + validateRouteRegistrations( [ { name: 'users.list', diff --git a/src/routing/validation/validate-route-definitions.ts b/src/routing/registration/validate-route-registrations.ts similarity index 58% rename from src/routing/validation/validate-route-definitions.ts rename to src/routing/registration/validate-route-registrations.ts index 2284733..ecdee74 100644 --- a/src/routing/validation/validate-route-definitions.ts +++ b/src/routing/registration/validate-route-registrations.ts @@ -1,12 +1,18 @@ -import type { RouteDefinition } from '@/routing/definition/route-definition'; +import type { HttpRequestBase } from '@/Http'; +import type { NormalizedRouteProps } from '@/routing/declaration/normalized-route-props.type'; import type { RouteRegistration } from '@/routing/registration/route-registration'; -export function validateRouteDefinitions(routes: RouteDefinition[], registrations: RouteRegistration[]): void { +export function validateRouteRegistrations( + routes: NormalizedRouteProps[], + registrations: RouteRegistration[], +): void { validateUniqueRouteNames(routes); validateUniqueRouteSignatures(registrations); } -function validateUniqueRouteSignatures(registrations: RouteRegistration[]): void { +function validateUniqueRouteSignatures( + registrations: RouteRegistration[], +): void { const signatures = new Set(); for (const registration of registrations) { @@ -20,7 +26,7 @@ function validateUniqueRouteSignatures(registrations: RouteRegistration[]): void } } -function validateUniqueRouteNames(routes: RouteDefinition[]): void { +function validateUniqueRouteNames(routes: NormalizedRouteProps[]): void { const routeNames = new Set(); for (const route of routes) { diff --git a/src/routing/route-options.ts b/src/routing/route-options.ts deleted file mode 100644 index d0d7b71..0000000 --- a/src/routing/route-options.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { KoaBodyMiddlewareOptions } from 'koa-body'; - -export type RouteOptions = Partial< - KoaBodyMiddlewareOptions & { - parseBody?: boolean; - } ->; diff --git a/src/routing/route.test.ts b/src/routing/route.test.ts deleted file mode 100644 index 066a4ee..0000000 --- a/src/routing/route.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, expect, test, vi } from 'vitest'; -import { Route } from './route'; - -describe('routing route', () => { - test('it creates a function first route definition', () => { - const handler = vi.fn(async () => undefined); - - const route = Route({ - method: 'GET', - path: '/users', - handler, - }); - - expect(route).toEqual({ - bodyOptions: {}, - handler, - methods: ['get'], - middleware: [], - parseBody: true, - path: '/users', - }); - }); - - test('it keeps the route name when provided', () => { - const handler = vi.fn(async () => undefined); - - const route = Route({ - name: 'users.list', - method: 'GET', - path: '/users', - handler, - }); - - expect(route.name).toBe('users.list'); - }); -}); diff --git a/src/routing/route.ts b/src/routing/route.ts deleted file mode 100644 index 3a6a74f..0000000 --- a/src/routing/route.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { HttpMiddleware, HttpRequest } from '@/Http'; -import { createRouteDefinition } from '@/routing/definition/create-route-definition'; -import type { RouteDefinition } from '@/routing/definition/route-definition'; -import type { HttpMethod } from '@/routing/http-method'; -import type { RouteOptions } from '@/routing/route-options'; -import type { Request } from 'koa'; - -export interface RouteDeclaration { - name?: string; - path: string; - method: HttpMethod | HttpMethod[]; - handler: HttpMiddleware; - middleware?: HttpMiddleware[]; - options?: RouteOptions; -} - -export function Route( - route: RouteDeclaration, -): RouteDefinition { - return createRouteDefinition(route); -} diff --git a/src/routing/source/route-source.ts b/src/routing/source/route-source.ts deleted file mode 100644 index 4e805e7..0000000 --- a/src/routing/source/route-source.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { RouteDefinition } from '@/routing/definition/route-definition'; -import type { RouteGroupDefinition } from '@/routing/helpers/route-group'; -import type { HttpRequest } from '@/Http'; -import type { Request } from 'koa'; - -export type RouteSource = - | RouteDefinition - | RouteGroupDefinition; diff --git a/tests/function-first-routing.e2e.test.ts b/tests/function-first-routing.e2e.test.ts index 086af08..5596bbb 100644 --- a/tests/function-first-routing.e2e.test.ts +++ b/tests/function-first-routing.e2e.test.ts @@ -1,10 +1,10 @@ +import { koalaDefaultConfig } from '@/config/default-config'; +import type { HttpMiddleware, HttpRequest, HttpScope, NextMiddleware, UploadedFile } from '@/Http'; +import { Any, Get, Post, Route, RouteGroup } from '@/routing'; +import { exclusiveRoutingModeError } from '@/routing/deprecated-decorator/verify-routing-mode'; +import { createTestAgent } from '@/Testing'; import { text } from 'node:stream/consumers'; import { describe, expect, test } from 'vitest'; -import { koalaDefaultConfig } from '../src/config/default-config'; -import type { HttpMiddleware, HttpRequest, HttpScope, NextMiddleware, UploadedFile } from '../src/Http'; -import { Any, Get, Post, Route, RouteGroup } from '../src/routing'; -import { createTestAgent } from '../src/Testing/TestAgentFactory'; -import { exclusiveRoutingModeError } from '../src/routing/verify-routing-mode'; interface FunctionFirstRoutingRequest extends HttpRequest { body: { name: string }; diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 0000000..45e73fa --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": ".." + }, + "include": ["../src/**/*.ts", "../src/**/*.d.ts", "./**/*.ts", "./**/*.d.ts"], + "exclude": ["../node_modules", "../dist", "../coverage"] +}