@sigitex/route 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +7 -0
  3. package/package.json +47 -0
  4. package/src/Assets.ts +4 -0
  5. package/src/CSP.ts +18 -0
  6. package/src/HTTP.ts +83 -0
  7. package/src/Router.ts +100 -0
  8. package/src/RouterError.ts +31 -0
  9. package/src/bun/bun.ts +37 -0
  10. package/src/bun/index.ts +1 -0
  11. package/src/bun/tsconfig.json +8 -0
  12. package/src/cloudflare/cloudflare.ts +43 -0
  13. package/src/cloudflare/index.ts +1 -0
  14. package/src/cloudflare/tsconfig.json +8 -0
  15. package/src/env.d.ts +1 -0
  16. package/src/global.d.ts +1 -0
  17. package/src/handler/app.ts +27 -0
  18. package/src/handler/assets.ts +10 -0
  19. package/src/handler/index.ts +7 -0
  20. package/src/handler/mount.ts +13 -0
  21. package/src/handler/noop.ts +3 -0
  22. package/src/handler/pattern.ts +71 -0
  23. package/src/handler/prefix.ts +32 -0
  24. package/src/handler/use.ts +36 -0
  25. package/src/index.ts +9 -0
  26. package/src/middleware/bodyLimit.ts +43 -0
  27. package/src/middleware/cache.ts +53 -0
  28. package/src/middleware/cookies.ts +76 -0
  29. package/src/middleware/cors.ts +113 -0
  30. package/src/middleware/csp.ts +109 -0
  31. package/src/middleware/csrf.ts +66 -0
  32. package/src/middleware/filter.ts +10 -0
  33. package/src/middleware/frameGuard.ts +15 -0
  34. package/src/middleware/hardened.ts +43 -0
  35. package/src/middleware/hsts.ts +24 -0
  36. package/src/middleware/https.ts +9 -0
  37. package/src/middleware/index.ts +17 -0
  38. package/src/middleware/noSniff.ts +11 -0
  39. package/src/middleware/rateLimit.ts +107 -0
  40. package/src/middleware/referrerPolicy.ts +27 -0
  41. package/src/middleware/requestId.ts +31 -0
  42. package/src/middleware/setHeader.ts +7 -0
  43. package/src/middleware/www.ts +12 -0
  44. package/src/route.ts +21 -0
  45. package/src/router.types.ts +48 -0
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright © 2026 Sigitex
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @sigitex/route
2
+
3
+ `bun add @sigitex/route`
4
+
5
+ A server-side web framework.
6
+
7
+ > **Note:** This package currently exports TypeScript sources directly. A TypeScript-compatible runtime or bundler (Bun, etc.) is required.
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@sigitex/route",
3
+ "type": "module",
4
+ "license": "MIT",
5
+ "author": {
6
+ "name": "Sigitex",
7
+ "url": "http://github.com/sigitex"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/sigitex/route.git"
12
+ },
13
+ "exports": {
14
+ ".": "./src/index.ts",
15
+ "./cloudflare": "./src/cloudflare/index.ts",
16
+ "./bun": "./src/bun/index.ts"
17
+ },
18
+ "devDependencies": {
19
+ "@cloudflare/workers-types": "^4.20250425.0",
20
+ "@commitlint/cli": "^20.5.3",
21
+ "@commitlint/config-conventional": "^20.5.3",
22
+ "@markwylde/semantic-release-gitea": "^2.2.0",
23
+ "@semantic-release/exec": "^7.0.3",
24
+ "@sigitex/bind": "*",
25
+ "@sigitex/ssjs": "*",
26
+ "@types/bun": "1.3.11",
27
+ "@typescript/native-preview": "beta",
28
+ "husky": "^9.1.7",
29
+ "multi-semantic-release": "^3.1.0",
30
+ "oxfmt": "^0.47.0",
31
+ "oxlint": "^1.62.0",
32
+ "semantic-release": "^25.0.3"
33
+ },
34
+ "dependencies": {
35
+ "regexparam": "3.0.0"
36
+ },
37
+ "scripts": {
38
+ "check": "tsgo --noEmit && find . -mindepth 2 -name tsconfig.json -not -path '*/node_modules/*' -exec tsgo --noEmit -p {} \\;",
39
+ "test": "bun test --pass-with-no-tests",
40
+ "prepare": "husky",
41
+ "lint": "oxlint"
42
+ },
43
+ "files": [
44
+ "src"
45
+ ],
46
+ "version": "1.0.0"
47
+ }
package/src/Assets.ts ADDED
@@ -0,0 +1,4 @@
1
+ export type Assets = {
2
+ static(path: string): Promise<Response>
3
+ file(request: Request): Promise<Response | undefined>
4
+ }
package/src/CSP.ts ADDED
@@ -0,0 +1,18 @@
1
+ const nonce = Symbol("csp-nonce")
2
+
3
+ export const CSP = {
4
+ self: "'self'",
5
+ none: "'none'",
6
+ unsafeInline: "'unsafe-inline'",
7
+ unsafeEval: "'unsafe-eval'",
8
+ unsafeHashes: "'unsafe-hashes'",
9
+ strictDynamic: "'strict-dynamic'",
10
+ nonce,
11
+ wildcard: "*",
12
+ data: "data:",
13
+ blob: "blob:",
14
+ https: "https:",
15
+ wss: "wss:",
16
+ } as const
17
+
18
+ export type CspSource = string | typeof nonce
package/src/HTTP.ts ADDED
@@ -0,0 +1,83 @@
1
+ export const HTTP = {
2
+ method: {
3
+ GET: "GET",
4
+ POST: "POST",
5
+ PUT: "PUT",
6
+ PATCH: "PATCH",
7
+ DELETE: "DELETE",
8
+ HEAD: "HEAD",
9
+ OPTIONS: "OPTIONS",
10
+ },
11
+ status: {
12
+ NoContent: 204,
13
+ MovedPermanently: 301,
14
+ Forbidden: 403,
15
+ PayloadTooLarge: 413,
16
+ UnsupportedMediaType: 415,
17
+ TooManyRequests: 429,
18
+ },
19
+ statusText: {
20
+ Forbidden: "Forbidden",
21
+ PayloadTooLarge: "Payload Too Large",
22
+ UnsupportedMediaType: "Unsupported Media Type",
23
+ TooManyRequests: "Too Many Requests",
24
+ },
25
+ header: {
26
+ Accept: "Accept",
27
+ Authorization: "Authorization",
28
+ CacheControl: "Cache-Control",
29
+ ContentLength: "Content-Length",
30
+ ContentType: "Content-Type",
31
+ Cookie: "Cookie",
32
+ Origin: "Origin",
33
+ Referrer: "Referer",
34
+ SetCookie: "Set-Cookie",
35
+ Vary: "Vary",
36
+ XContentTypeOptions: "X-Content-Type-Options",
37
+ XForwardedFor: "X-Forwarded-For",
38
+ XFrameOptions: "X-Frame-Options",
39
+ XRequestId: "X-Request-Id",
40
+ XCsrfToken: "X-CSRF-Token",
41
+ StrictTransportSecurity: "Strict-Transport-Security",
42
+ ReferrerPolicy: "Referrer-Policy",
43
+ AccessControlAllowOrigin: "Access-Control-Allow-Origin",
44
+ AccessControlAllowMethods: "Access-Control-Allow-Methods",
45
+ AccessControlAllowHeaders: "Access-Control-Allow-Headers",
46
+ AccessControlExposeHeaders: "Access-Control-Expose-Headers",
47
+ AccessControlAllowCredentials: "Access-Control-Allow-Credentials",
48
+ AccessControlMaxAge: "Access-Control-Max-Age",
49
+ AccessControlRequestMethod: "Access-Control-Request-Method",
50
+ AccessControlRequestHeaders: "Access-Control-Request-Headers",
51
+ ContentSecurityPolicy: "Content-Security-Policy",
52
+ ContentSecurityPolicyReportOnly: "Content-Security-Policy-Report-Only",
53
+ CFConnectingIP: "CF-Connecting-IP",
54
+ XRateLimitLimit: "X-RateLimit-Limit",
55
+ XRateLimitRemaining: "X-RateLimit-Remaining",
56
+ XRateLimitReset: "X-RateLimit-Reset",
57
+ },
58
+ contentType: {
59
+ json: "application/json",
60
+ urlencoded: "application/x-www-form-urlencoded",
61
+ formData: "multipart/form-data",
62
+ text: "text/plain",
63
+ html: "text/html",
64
+ octetStream: "application/octet-stream",
65
+ },
66
+ referrerPolicy: {
67
+ noReferrer: "no-referrer",
68
+ noReferrerWhenDowngrade: "no-referrer-when-downgrade",
69
+ origin: "origin",
70
+ originWhenCrossOrigin: "origin-when-cross-origin",
71
+ sameOrigin: "same-origin",
72
+ strictOrigin: "strict-origin",
73
+ strictOriginWhenCrossOrigin: "strict-origin-when-cross-origin",
74
+ unsafeUrl: "unsafe-url",
75
+ },
76
+ frameOption: {
77
+ deny: "DENY",
78
+ sameOrigin: "SAMEORIGIN",
79
+ },
80
+ contentTypeOptions: {
81
+ nosniff: "nosniff",
82
+ },
83
+ } as const
package/src/Router.ts ADDED
@@ -0,0 +1,100 @@
1
+ import type { Container } from "@sigitex/bind"
2
+ import type {
3
+ RequestHandler,
4
+ RouterOptions,
5
+ RouteMiddleware,
6
+ RequestContext
7
+ } from "./router.types"
8
+
9
+ export class Router {
10
+ private readonly handlers: RequestHandler[]
11
+ private readonly container: Container | undefined
12
+ private readonly middlewares: RouteMiddleware[]
13
+
14
+ constructor(handlers: RequestHandler[], options: RouterOptions = {}) {
15
+ this.handlers = handlers
16
+ this.container = options.container
17
+ this.middlewares = options.middlewares ?? []
18
+ }
19
+
20
+ async route(
21
+ request: Request,
22
+ env: Env,
23
+ ): Promise<Response> {
24
+ const url = new URL(request.url)
25
+ const context: RequestContext = {
26
+ request,
27
+ env,
28
+ url,
29
+ bind,
30
+ dispatch,
31
+ }
32
+ const container = this.container?.clone()
33
+ try {
34
+ if (container) {
35
+ container.bind(context)
36
+ }
37
+ for (const handler of this.handlers) {
38
+ const result = await dispatch(handler, this.middlewares)
39
+ if (result === undefined) continue
40
+ return result
41
+ }
42
+ return fail(404, "Not found.")
43
+ } catch (error) {
44
+ console.error(error)
45
+ return fail(500, "Internal server error.")
46
+ }
47
+
48
+ // oxlint-disable-next-line typescript/no-explicit-any
49
+ async function bind(bindings: { [key: string]: any }) {
50
+ if (container) {
51
+ container.bind(bindings)
52
+ } else {
53
+ Object.assign(context, bindings)
54
+ }
55
+ }
56
+
57
+ async function dispatch(
58
+ handler: RequestHandler,
59
+ middlewares: RouteMiddleware[],
60
+ ): Promise<Response | undefined> {
61
+ for (const { before } of middlewares) {
62
+ if (!before) continue
63
+ const interrupt = await Promise.resolve(invoke(before))
64
+ if (interrupt !== undefined) return respond(interrupt)
65
+ }
66
+ const result = await Promise.resolve(invoke(handler))
67
+ if (result === undefined) {
68
+ return
69
+ }
70
+ const response = respond(result)
71
+ bind({ response })
72
+ for (const { after } of middlewares) {
73
+ if (!after) continue
74
+ const interrupt = await Promise.resolve(invoke(after))
75
+ if (interrupt !== undefined) return respond(interrupt)
76
+ }
77
+ return respond(result)
78
+ }
79
+
80
+ async function invoke(handler: RequestHandler) {
81
+ if (container) {
82
+ return container.call(handler)
83
+ }
84
+ return handler(context)
85
+ }
86
+ }
87
+ }
88
+
89
+ function fail(status: number, error: string) {
90
+ return new Response(JSON.stringify({ error }), {
91
+ status,
92
+ statusText: error,
93
+ })
94
+ }
95
+
96
+ function respond(result: unknown) {
97
+ return result instanceof Response
98
+ ? result
99
+ : new Response(JSON.stringify(result))
100
+ }
@@ -0,0 +1,31 @@
1
+ export class RouterError extends Error {
2
+ readonly code: number
3
+ constructor(code: number, message: string) {
4
+ super(message)
5
+ this.code = code
6
+ }
7
+ }
8
+
9
+ export class NotFound extends RouterError {
10
+ constructor(message?: string) {
11
+ super(404, message ?? "Not found.")
12
+ }
13
+ }
14
+
15
+ export class ServerError extends Error {
16
+ constructor(message?: string) {
17
+ super(message ?? "Internal error.")
18
+ }
19
+ }
20
+
21
+ export class MethodNotAllowed extends RouterError {
22
+ constructor(message?: string) {
23
+ super(405, message ?? "Method not allowed.")
24
+ }
25
+ }
26
+
27
+ export class InvalidRequest extends RouterError {
28
+ constructor(message?: string) {
29
+ super(400, message ?? "Invalid request.")
30
+ }
31
+ }
package/src/bun/bun.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { join } from "node:path"
2
+ import type { Assets } from "../Assets"
3
+ import type { RequestHandler } from "../router.types"
4
+
5
+ type BunOptions = {
6
+ readonly assets?: string
7
+ }
8
+
9
+ export function bun(options: BunOptions = {}): RequestHandler {
10
+ const assets = bunAssets(options.assets ?? "./assets")
11
+ return ({ bind }: { bind: (bindings: { assets: Assets }) => void }) => {
12
+ bind({ assets })
13
+ }
14
+ }
15
+
16
+ function bunAssets(dir: string): Assets {
17
+ const cache = new Map<string, Response>()
18
+
19
+ return {
20
+ async static(path) {
21
+ const cached = cache.get(path)
22
+ if (cached) return cached.clone() as Response
23
+ const file = Bun.file(join(dir, path))
24
+ const response = new Response(await file.bytes(), {
25
+ headers: { "Content-Type": file.type },
26
+ })
27
+ cache.set(path, response.clone() as Response)
28
+ return response as Response
29
+ },
30
+ async file(request) {
31
+ const path = new URL(request.url).pathname
32
+ const file = Bun.file(join(dir, path))
33
+ if (!await file.exists()) return undefined
34
+ return new Response(file) as Response
35
+ },
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ export * from "./bun"
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["bun"]
5
+ },
6
+ "include": [".", "../env.d.ts"],
7
+ "exclude": []
8
+ }
@@ -0,0 +1,43 @@
1
+ import type { Assets } from "../Assets"
2
+ import type { RequestHandler } from "../router.types"
3
+
4
+ type CloudflareAssets = {
5
+ readonly fetch: (request: Request | URL | string) => Promise<Response>
6
+ }
7
+
8
+ type CloudflareOptions = {
9
+ readonly assets?: string
10
+ }
11
+
12
+ export function cloudflare(options: CloudflareOptions = {}): RequestHandler {
13
+ const assetsKey = options.assets ?? "ASSETS"
14
+ return ({
15
+ env,
16
+ request,
17
+ bind,
18
+ }: {
19
+ env: Env
20
+ request: Request
21
+ bind: (bindings: { assets: Assets }) => void
22
+ }) => {
23
+ // oxlint-disable-next-line typescript/no-explicit-any
24
+ const binding = (env as any)[assetsKey] as CloudflareAssets
25
+ const { origin } = new URL(request.url)
26
+ bind({ assets: cloudflareAssets(binding, origin) })
27
+ }
28
+ }
29
+
30
+ function cloudflareAssets(binding: CloudflareAssets, origin: string): Assets {
31
+ return {
32
+ async static(path) {
33
+ return binding.fetch(new Request(`${origin}${path}`))
34
+ },
35
+ async file(request) {
36
+ const response = await binding.fetch(request)
37
+ if (response.status === 404) {
38
+ return undefined
39
+ }
40
+ return response
41
+ },
42
+ }
43
+ }
@@ -0,0 +1 @@
1
+ export * from "./cloudflare"
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["@cloudflare/workers-types"]
5
+ },
6
+ "include": [".", "../env.d.ts"],
7
+ "exclude": []
8
+ }
package/src/env.d.ts ADDED
@@ -0,0 +1 @@
1
+ interface Env {}
@@ -0,0 +1 @@
1
+ import "@sigitex/ssjs"
@@ -0,0 +1,27 @@
1
+ import * as RegexParam from "regexparam"
2
+ import type { Assets } from "../Assets"
3
+ import type { RequestHandler, RouteTree } from "../router.types"
4
+
5
+ export function app(routes: RouteTree): RequestHandler {
6
+ const patterns: RegExp[] = getAppPatterns(routes)
7
+ return async ({ assets, url }: { assets: Assets, url: URL }) => {
8
+ for (const pattern of patterns) {
9
+ if (pattern.test(url.pathname)) {
10
+ return await assets.static("/index.html")
11
+ }
12
+ }
13
+ return
14
+ }
15
+ }
16
+
17
+ function getAppPatterns(tree: RouteTree) {
18
+ const patterns: RegExp[] = []
19
+ for (const [_key, value] of Object.entries(tree)) {
20
+ if (typeof value === "string") {
21
+ patterns.push(RegexParam.parse(value).pattern)
22
+ } else {
23
+ patterns.push(...getAppPatterns(value))
24
+ }
25
+ }
26
+ return patterns
27
+ }
@@ -0,0 +1,10 @@
1
+ import type { Assets } from "../Assets"
2
+ import type { RequestHandler } from "../router.types"
3
+
4
+ export function assets(): RequestHandler {
5
+ return async ({ assets, request }: { assets: Assets, request: Request }) => {
6
+ const response = await assets.file(request)
7
+ if (!response || response.status === 404) return
8
+ return response
9
+ }
10
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./app"
2
+ export * from "./assets"
3
+ export * from "./mount"
4
+ export * from "./noop"
5
+ export * from "./pattern"
6
+ export * from "./prefix"
7
+ export * from "./use"
@@ -0,0 +1,13 @@
1
+ // oxlint-disable typescript/no-explicit-any
2
+ import type { RequestHandler } from "../router.types"
3
+
4
+ /** Wraps a standard fetch function as a router handler. */
5
+ export function mount(handler: (request: Request) => Promise<Response>): RequestHandler {
6
+ return ({ request, url, container }: any) => {
7
+ const mapped = new Request(url.href, request)
8
+ if ("__mount" in handler && container) {
9
+ return (handler as any).__mount(mapped, container)
10
+ }
11
+ return handler(mapped)
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { RequestHandler } from "../router.types"
2
+
3
+ export const noop: RequestHandler = () => {}
@@ -0,0 +1,71 @@
1
+ import * as RegexParam from "regexparam"
2
+ import { MethodNotAllowed } from "../RouterError"
3
+ import type { RequestContext, RequestHandler, RouteMiddleware } from "../router.types"
4
+
5
+ export function pattern(
6
+ method: string | null,
7
+ path: string,
8
+ handler: RequestHandler,
9
+ ...middlewares: RouteMiddleware[]
10
+ ): RequestHandler {
11
+ const { pattern, keys } = RegexParam.parse(path)
12
+ return ({ url, request, bind, dispatch }: RequestContext) => {
13
+ const match = pattern.exec(url.pathname)
14
+ if (!match) return
15
+ if (method !== null && request.method !== method) {
16
+ return new MethodNotAllowed(`Please use "${method}".`)
17
+ }
18
+ const params: { [key: string]: string } = {}
19
+ if (keys.length > 0) {
20
+ for (let k = 0; k < keys.length; k++) {
21
+ let key = keys[k]
22
+ if (key === "*") {
23
+ key = "path"
24
+ }
25
+ params[key] = match[k + 1]
26
+ }
27
+ }
28
+ bind({ params })
29
+ return dispatch(handler, middlewares)
30
+ }
31
+ }
32
+
33
+ export function get(
34
+ path: string,
35
+ handler: RequestHandler,
36
+ ...middlewares: RouteMiddleware[]
37
+ ): RequestHandler {
38
+ return pattern("GET", path, handler, ...middlewares)
39
+ }
40
+
41
+ export function post(
42
+ path: string,
43
+ handler: RequestHandler,
44
+ ...middlewares: RouteMiddleware[]
45
+ ): RequestHandler {
46
+ return pattern("POST", path, handler, ...middlewares)
47
+ }
48
+
49
+ export function put(
50
+ path: string,
51
+ handler: RequestHandler,
52
+ ...middlewares: RouteMiddleware[]
53
+ ): RequestHandler {
54
+ return pattern("PUT", path, handler, ...middlewares)
55
+ }
56
+
57
+ export function del(
58
+ path: string,
59
+ handler: RequestHandler,
60
+ ...middlewares: RouteMiddleware[]
61
+ ): RequestHandler {
62
+ return pattern("DELETE", path, handler, ...middlewares)
63
+ }
64
+
65
+ export function patch(
66
+ path: string,
67
+ handler: RequestHandler,
68
+ ...middlewares: RouteMiddleware[]
69
+ ): RequestHandler {
70
+ return pattern("PATCH", path, handler, ...middlewares)
71
+ }
@@ -0,0 +1,32 @@
1
+ import type { RequestContext, RequestHandler, RouteMiddleware } from "../router.types"
2
+ import { use } from "./use"
3
+
4
+ export function prefix(
5
+ prefix: string,
6
+ ...handlers: RequestHandler[]
7
+ ): RequestHandler
8
+ export function prefix(
9
+ prefix: string,
10
+ middlewares: RouteMiddleware[],
11
+ ...handlers: RequestHandler[]
12
+ ): RequestHandler
13
+ export function prefix(
14
+ prefix: string,
15
+ head: RouteMiddleware[] | RequestHandler,
16
+ ...tail: RequestHandler[]
17
+ ): RequestHandler {
18
+ const [middlewares, handlers] = Array.isArray(head)
19
+ ? [head, tail]
20
+ : [[], [head, ...tail]]
21
+ const root = prefix.endsWith("/") ? prefix : prefix + "/"
22
+ const handle = use(middlewares, ...handlers)
23
+ return (context: RequestContext) => {
24
+ if (!context.url.pathname.startsWith(root)) {
25
+ return
26
+ }
27
+ const url = new URL(context.url.href)
28
+ url.pathname = url.pathname.slice(root.length - 1) || "/"
29
+ context.bind({ url })
30
+ return handle(context)
31
+ }
32
+ }
@@ -0,0 +1,36 @@
1
+ import type { RequestContext, RequestHandler, RouteMiddleware } from "../router.types"
2
+ import { noop } from "./noop"
3
+
4
+ export function use(
5
+ middlewares: RouteMiddleware[],
6
+ ...handlers: RequestHandler[]
7
+ ): RequestHandler {
8
+ const befores = middlewares.filter(({ before }) => before)
9
+ const afters = middlewares
10
+ .filter(({ after }) => after)
11
+ .map(({ after }) => ({ before: after }) as RouteMiddleware)
12
+ return async ({ dispatch, bind }: RequestContext) => {
13
+ if (befores.length) {
14
+ const interruptBefore = await dispatch(noop, befores)
15
+ if (interruptBefore !== undefined) {
16
+ return interruptBefore
17
+ }
18
+ }
19
+ let response: Response | undefined
20
+ for (const handler of handlers) {
21
+ response = await dispatch(handler, [])
22
+ if (response !== undefined) break
23
+ }
24
+ if (response === undefined) {
25
+ return
26
+ }
27
+ bind({ response })
28
+ if (afters.length) {
29
+ const interruptAfter = await dispatch(noop, afters)
30
+ if (interruptAfter !== undefined) {
31
+ return interruptAfter
32
+ }
33
+ }
34
+ return response
35
+ }
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from "./Assets"
2
+ export * from "./HTTP"
3
+ export * from "./CSP"
4
+ export * from "./router.types"
5
+ export * from "./Router"
6
+ export * from "./RouterError"
7
+ export * from "./route"
8
+ export * from "./handler"
9
+ export * from "./middleware"