From e66b1f1b75287efa9ed049c2d39ba07c5f6c750f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Charles?= Date: Tue, 17 Dec 2024 18:02:06 +0100 Subject: [PATCH] chore --- packages/core/src/pipe.ts | 20 +++--- packages/router/package.json | 30 +++++++++ packages/router/src/index.ts | 118 ++++++++++++++++++++++++++++++++++ packages/router/tsconfig.json | 3 + pnpm-lock.yaml | 21 ++++++ 5 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 packages/router/package.json create mode 100644 packages/router/src/index.ts create mode 100644 packages/router/tsconfig.json diff --git a/packages/core/src/pipe.ts b/packages/core/src/pipe.ts index 33372183..2e373176 100644 --- a/packages/core/src/pipe.ts +++ b/packages/core/src/pipe.ts @@ -17,15 +17,17 @@ type AnyMiddleware = T extends UniversalFn ? Fn : never; -type ComposeReturnType = Last extends UniversalHandler - ? UniversalHandler>> - : Last extends UniversalMiddleware - ? UniversalMiddleware>, In>> - : Last extends UniversalFn, infer _> - ? UniversalFn>>, ExtractUF>> - : Last extends UniversalFn, infer _> - ? UniversalFn>, In>>, ExtractUF>> - : never; +type ComposeReturnType = Last extends never + ? T[number] + : Last extends UniversalHandler + ? UniversalHandler>> + : Last extends UniversalMiddleware + ? UniversalMiddleware>, In>> + : Last extends UniversalFn, infer _> + ? UniversalFn>>, ExtractUF>> + : Last extends UniversalFn, infer _> + ? UniversalFn>, In>>, ExtractUF>> + : never; type Cast< T extends AnyMiddleware, diff --git a/packages/router/package.json b/packages/router/package.json new file mode 100644 index 00000000..08466dc9 --- /dev/null +++ b/packages/router/package.json @@ -0,0 +1,30 @@ +{ + "name": "@universal-middleware/router", + "version": "0.1.0", + "type": "module", + "description": "Universal router", + "files": [ + "dist" + ], + "exports": { + ".": "./dist/index.js" + }, + "author": "Joël Charles ", + "repository": "https://github.com/magne4000/universal-middleware", + "license": "MIT", + "scripts": { + "build": "rimraf dist && tsup", + "test": "vitest --typecheck run", + "prepack": "pnpm build", + "test:typecheck": "tsc -p tsconfig.json --noEmit" + }, + "sideEffects": false, + "dependencies": { + "@universal-middleware/core": "workspace:^", + "@universal-middleware/hono": "workspace:^", + "rou3": "^0.5.1" + }, + "devDependencies": { + "hono": "catalog:" + } +} diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts new file mode 100644 index 00000000..8d94c1c0 --- /dev/null +++ b/packages/router/src/index.ts @@ -0,0 +1,118 @@ +import type { UniversalHandler, UniversalMiddleware } from "@universal-middleware/core"; +import { pipe, universalSymbol } from "@universal-middleware/core"; +import { createHandler, createMiddleware } from "@universal-middleware/hono"; +import type { Hono } from "hono"; +import { type RouterContext, addRoute, createRouter, findRoute } from "rou3"; + +export interface RouteDefinition { + method: "get"; + path: string; + handler: UniversalHandler; +} + +export type MiddlewareDefinition = [middleware: UniversalMiddleware, order: number]; + +export interface UniversalRouterInterface { + use(middleware: UniversalMiddleware, order?: number): this; + route(method: RouteDefinition["method"], path: string, handler: UniversalHandler): this; +} + +export class UniversalRouter implements UniversalRouterInterface { + public router: RouterContext; + #middlewares: MiddlewareDefinition[]; + #computedMiddleware?: UniversalMiddleware; + + constructor() { + this.router = createRouter(); + this.#middlewares = []; + } + + use(middleware: UniversalMiddleware, order?: number) { + this.#computedMiddleware = undefined; + this.#middlewares.push([middleware, order ?? 0]); + return this; + } + + // TODO https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods + route(method: RouteDefinition["method"], path: string, handler: UniversalHandler) { + addRoute(this.router, method.toLocaleUpperCase() as Uppercase, path, handler); + return this; + } + + // TODO? routes to adapter? + // Or UniversalRouter extends per adapter, e.g. UniversalHonoRouter + // routes() { + // this.router.root; + // } + // + // middlewares() { + // return ordered(this.#middlewares); + // } + + get [universalSymbol](): UniversalMiddleware { + if (!this.#computedMiddleware && this.#middlewares.length > 0) { + this.#computedMiddleware = pipe(...ordered(this.#middlewares)); + } + return (request, ctx, runtime) => { + // TODO core helper to cache the result + const url = new URL(request.url); + const router = findRoute(this.router, request.method, url.pathname); + + if (router) { + const handler = this.#computedMiddleware ? pipe(this.#computedMiddleware, router.data) : router.data; + return handler(request, ctx, runtime); + } + if (this.#computedMiddleware) { + return this.#computedMiddleware(request, ctx, runtime); + } + // else do nothing + }; + } +} + +export class UniversalHonoRouter implements UniversalRouterInterface { + #app: Hono; + + constructor(app: Hono) { + this.#app = app; + } + + use(middleware: UniversalMiddleware) { + this.#app.use(createMiddleware(() => middleware)()); + return this; + } + + route(method: RouteDefinition["method"], path: string, handler: UniversalHandler) { + this.#app[method](path, createHandler(() => handler)()); + return this; + } +} + +export function apply( + router: UniversalRouterInterface, + routes?: RouteDefinition[], + middlewares?: MiddlewareDefinition[], +) { + if (middlewares) { + const ms = ordered(middlewares); + for (const m of ms) { + router.use(m); + } + } + if (routes) { + for (const r of routes) { + router.route(r.method, r.path, r.handler); + } + } +} + +function ordered(middlewares: MiddlewareDefinition[]) { + return Array.from(middlewares) + .sort((a, b) => a[1] - b[1]) + .map((x) => x[0]); +} + +// middlewares +// order +// routes +// handlers diff --git a/packages/router/tsconfig.json b/packages/router/tsconfig.json new file mode 100644 index 00000000..4082f16a --- /dev/null +++ b/packages/router/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 161170e8..99274448 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -702,6 +702,22 @@ importers: specifier: 'catalog:' version: 2.1.7(@edge-runtime/vm@3.2.0)(@types/node@20.17.9) + packages/router: + dependencies: + '@universal-middleware/core': + specifier: link:../core + version: link:../core + '@universal-middleware/hono': + specifier: link:../adapter-hono + version: link:../adapter-hono + rou3: + specifier: ^0.5.1 + version: 0.5.1 + devDependencies: + hono: + specifier: 'catalog:' + version: 4.6.12 + packages/tests: dependencies: mri: @@ -4177,6 +4193,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rou3@0.5.1: + resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -8649,6 +8668,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.28.1 fsevents: 2.3.3 + rou3@0.5.1: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3