CI: Update Pages (2025-11-23 11:20:49)

This commit is contained in:
2025-11-23 11:20:49 +00:00
commit 24771db506
138 changed files with 7472 additions and 0 deletions

107
v0.2.1/CHANGELOG.md Normal file
View File

@@ -0,0 +1,107 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.2.1](https://git.0xmax42.io/maxp/http-kernel/compare/v0.2.0..v0.2.1) - 2025-11-12
### 🚀 Features
- Export errors, interfaces, types, and utils from main module - ([6d7127a](https://git.0xmax42.io/maxp/http-kernel/commit/6d7127a52f4aecfd178523c8a873ab0b558550f1))
### 🐛 Bug Fixes
- *(workflows)* Remove redundant tag fallback in sync job - ([5686940](https://git.0xmax42.io/maxp/http-kernel/commit/5686940fe26b699bffa62af7fb0efc42cc85a6b3))
### ⚙️ Miscellaneous Tasks
- *(workflows)* Update release workflow for consistency - ([f177746](https://git.0xmax42.io/maxp/http-kernel/commit/f1777467607874f6bc83e1d7e37433298e25607c))
## [0.2.0](https://git.0xmax42.io/maxp/http-kernel/compare/v0.1.0..v0.2.0) - 2025-05-27
### 🚀 Features
- *(workflows)* Add GitHub release sync workflow - ([de6d3ee](https://git.0xmax42.io/maxp/http-kernel/commit/de6d3ee389b0d92c5056e47be85da1d0c41f62af))
- *(ci)* Enhance CI workflow with granular steps and error handling - ([54cfa18](https://git.0xmax42.io/maxp/http-kernel/commit/54cfa1888e13d0872b5411e83d3d45925f2687ee))
- *(route-builder)* Add middleware chain compilation - ([35d83c0](https://git.0xmax42.io/maxp/http-kernel/commit/35d83c073ef8644d657195c332b463d18e856e18))
- *(interfaces)* Add runRoute method to IInternalRoute - ([67ebb43](https://git.0xmax42.io/maxp/http-kernel/commit/67ebb4307a2a1c588b78f8f0c498d1a4276ad09b))
- *(workflows)* Conditionally generate changelog - ([b44bb2d](https://git.0xmax42.io/maxp/http-kernel/commit/b44bb2ddafe99c85b25229d2c4a0dfeacf750052))
- *(workflows)* Add CI for Deno project tests - ([9d5db4f](https://git.0xmax42.io/maxp/http-kernel/commit/9d5db4f414cf961248f2b879f2b132b81a32cb92))
### 🐛 Bug Fixes
- *(workflows)* Ensure version detection output is always set - ([3707242](https://git.0xmax42.io/maxp/http-kernel/commit/3707242d278e15c55a41056bb64810f6824d24b3))
### 🚜 Refactor
- *(kernel)* Simplify middleware and handler execution - ([aea3fb4](https://git.0xmax42.io/maxp/http-kernel/commit/aea3fb45e7c099a38440c85783747e80fca54ba6))
- *(imports)* Use explicit type-only imports across codebase - ([b83aa33](https://git.0xmax42.io/maxp/http-kernel/commit/b83aa330b34523e5102ab98ee61dedbbd62d4656))
- *(workflows)* Rename changelog file for consistency - ([b9d25f2](https://git.0xmax42.io/maxp/http-kernel/commit/b9d25f23fc6ad7696deee319024aa5b1af4d98c0))
### 📚 Documentation
- *(release)* Update guidelines for handling changelog - ([6a0f1c7](https://git.0xmax42.io/maxp/http-kernel/commit/6a0f1c774bc01ab976090612bbc361576feb3942))
- Add README for HttpKernel project - ([a1ce306](https://git.0xmax42.io/maxp/http-kernel/commit/a1ce30627c68a3f869eb6a104308322af8596dc1))
- Add MIT license file - ([5118a19](https://git.0xmax42.io/maxp/http-kernel/commit/5118a19aeaa1102591aa7fe093fdec1aa19dc7f5))
### 🧪 Testing
- *(routebuilder)* Add validation tests for handler and middleware - ([b14e9ac](https://git.0xmax42.io/maxp/http-kernel/commit/b14e9acc5f9617a01886e7734b2ae717b86de03e))
- *(httpkernel)* Enforce compile-time validation for signatures - ([731bba2](https://git.0xmax42.io/maxp/http-kernel/commit/731bba22d88df077b0a39293ddd1a3eec3bf96e8))
- *(bench)* Add parallel benchmarks for HTTP kernel - ([3da34e2](https://git.0xmax42.io/maxp/http-kernel/commit/3da34e268426b92510c7f9b730a2fa297dca6fbf))
### ⚙️ Miscellaneous Tasks
- *(config)* Add CI task for local checks - ([c207dc7](https://git.0xmax42.io/maxp/http-kernel/commit/c207dc7392d9f40e7b7c736eadf6c9c7bbf9b7d4))
- *(gitignore)* Add .local directory to ignored files - ([1b447f5](https://git.0xmax42.io/maxp/http-kernel/commit/1b447f51900b3a1a7f1be9d5192fd5aba37bdbc4))
- *(ci)* Update deno tasks in CI workflow - ([38c00b0](https://git.0xmax42.io/maxp/http-kernel/commit/38c00b035bfd05c83d5898c97c9423a653db0840))
- *(tasks)* Add benchmark, format, and lint commands - ([6e6e616](https://git.0xmax42.io/maxp/http-kernel/commit/6e6e61693fef3b11a81ce260d80bc93edae1e718))
- *(workflows)* Consolidate and update CI configuration - ([ec1697d](https://git.0xmax42.io/maxp/http-kernel/commit/ec1697df94b5378f1766663e278a41d403a64336))
- *(config)* Add exports field to module metadata - ([c28eb7f](https://git.0xmax42.io/maxp/http-kernel/commit/c28eb7f28dfaa8d3fdc540c4bcc306a3a8b9d6f8))
- *(git)* Ignore merge conflicts for CHANGELOG.md - ([6399113](https://git.0xmax42.io/maxp/http-kernel/commit/6399113e122e1207ebf4113aebd250358e31f461))
- *(workflows)* Refine branch handling in release process - ([71ea424](https://git.0xmax42.io/maxp/http-kernel/commit/71ea4247b35dc4afe5090d3c6502bfa936b5a947))
- *(workflows)* Update changelog file extension to .md and revert b9d25f23fc - ([a88b4d1](https://git.0xmax42.io/maxp/http-kernel/commit/a88b4d112f5c07664d41f6e9d03246307551f25d))
- Rename changelog and readme files to use .md extension - ([4f2b650](https://git.0xmax42.io/maxp/http-kernel/commit/4f2b65049f461ef377e7231905fd066cbc3c7fe0))
- *(workflows)* Update test workflow for http-kernel project - ([0311546](https://git.0xmax42.io/maxp/http-kernel/commit/03115464e0fb01b8ca00a2fdabde013d004ae8a2))
- *(workflows)* Update Deno setup action to v2 - ([1233a0b](https://git.0xmax42.io/maxp/http-kernel/commit/1233a0b7204d12a60f4b7bd1199242a4cb7c4579))
- *(workflows)* Remove unused workflow_dispatch trigger - ([16c0053](https://git.0xmax42.io/maxp/http-kernel/commit/16c0053964c72d01e5f555ec8f33c9eead160e69))
- *(tasks)* Remove commented-out start and watch scripts - ([04029f8](https://git.0xmax42.io/maxp/http-kernel/commit/04029f87a3b9dd24e8792b852ead9097e18d23c7))
## [0.1.0] - 2025-05-08
### 🚀 Features
- *(workflows)* Add automated changelog and release workflow - ([bbf78cf](https://git.0xmax42.io/maxp/http-kernel/commit/bbf78cff17be0cae651b8abf3e239103b26354bf))
- *(vscode)* Customize activity bar and peacock colors - ([56633cd](https://git.0xmax42.io/maxp/http-kernel/commit/56633cd95b37a8b2cfd8eb95982d07cd1f9b5126))
- *(workflows)* Add upload assets template for releases - ([7b6eb2b](https://git.0xmax42.io/maxp/http-kernel/commit/7b6eb2b57470198684a1dfa8b668351b8b9a91ae))
- *(config)* Add project metadata and test watch task - ([b009b57](https://git.0xmax42.io/maxp/http-kernel/commit/b009b5763d1824fc94fdc1e3d919fe2597158f84))
- *(http)* Add error handling for invalid HTTP methods - ([ba7aa79](https://git.0xmax42.io/maxp/http-kernel/commit/ba7aa79f56772213bf73b62bc6bf8810f3871127))
- *(http)* Enhance type safety and extend route context - ([a236fa7](https://git.0xmax42.io/maxp/http-kernel/commit/a236fa7c97ae49e6baf560d4ca92c6e83702b3ec))
### 🐛 Bug Fixes
- *(params)* Enforce non-undefined route parameter values - ([b0c6901](https://git.0xmax42.io/maxp/http-kernel/commit/b0c6901d7d272ec98b3d00ef2dd2848482892a25))
### 🚜 Refactor
- *(types)* Unify handler and middleware definitions - ([8235680](https://git.0xmax42.io/maxp/http-kernel/commit/8235680904c7f30f25b98b835d48376431108e91))
- *(core)* [**breaking**] Enhance HttpKernel pipeline and matcher system with full context and error handling - ([b7410b4](https://git.0xmax42.io/maxp/http-kernel/commit/b7410b44dd8720e46ee2871aa1727ce5039ebad4))
- *(httpkernel)* Introduce configuration object for flexibility - ([9059bdd](https://git.0xmax42.io/maxp/http-kernel/commit/9059bdda62081c8e775087cabe4c3406e42065a5))
### 📚 Documentation
- *(gitea)* Add release automation guide and scripts - ([5c03cdf](https://git.0xmax42.io/maxp/http-kernel/commit/5c03cdfb031adeb6ee5d0de0889477d6d1efafef))
- *(httpkernel)* Enhance class and interface documentation - ([6c4420d](https://git.0xmax42.io/maxp/http-kernel/commit/6c4420d32f8e7fe317f7c1b0b45de2dcf8565ef5))
### 🧪 Testing
- *(utils)* Rename and update import paths in test file - ([82a6877](https://git.0xmax42.io/maxp/http-kernel/commit/82a687748558f15c2023861a0cc3a33095c86731))
- *(utils)* Add unit tests for parseQuery function - ([94525fc](https://git.0xmax42.io/maxp/http-kernel/commit/94525fce5299f3417801f0152a475892e1edac30))
### ⚙️ Miscellaneous Tasks
- *(config)* Add default git-cliff configuration - ([661f83d](https://git.0xmax42.io/maxp/http-kernel/commit/661f83d1fd0101aa0d5d06b60f6eeb68efac6ceb))
- *(gitignore)* Add .gitea/COMMIT_GPT.md to ignored files - ([f083856](https://git.0xmax42.io/maxp/http-kernel/commit/f0838567b46822327fe739d8de099722e405dfa3))
- *(settings)* Add exportall configuration for barrel name and message - ([0990cac](https://git.0xmax42.io/maxp/http-kernel/commit/0990cacb225e1cbbbbb2a288501df7de9641294f))
- *(.gitignore)* Add git_log_diff.txt to ignore list - ([fd1c7f4](https://git.0xmax42.io/maxp/http-kernel/commit/fd1c7f4170ffffd55ab276090f8b90ee82b853fc))

18
v0.2.1/LICENSE Normal file
View File

@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2025 0xMax42
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:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
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.

135
v0.2.1/README.md Normal file
View File

@@ -0,0 +1,135 @@
# HttpKernel – A Type-Safe Router & Middleware Kernel for Deno
> Fluent routing • Zero-dependency core • 100 % TypeScript
HttpKernel is a small but powerful dispatching engine that turns an ordinary
`Deno.serve()` loop into a structured, middleware-driven HTTP server.
It focuses on **type safety**, **immutability**, and an **expressive builder API**
while staying framework-agnostic and dependency‑free.
---
## ✨ Key Features
* **Fluent Route Builder** – chain middleware and handlers without side effects
* **Static *and* Dynamic Matching** – use URL patterns *or* custom matcher functions
* **First-Class Generics** – strongly‑typed `ctx.params`, `ctx.query`, and `ctx.state`
* **Pluggable Error Handling** – override 404/500 (and any other status) per kernel
* **Response Decorators** – inject CORS headers, security headers, logging, … in one place
* **100 % Test Coverage** – built‑in unit tests ensure every edge case is covered
---
## 🚀 Quick Start
```ts
// Import directly from your repo or deno.land/x
import { HttpKernel } from "https://deno.land/x/httpkernel/mod.ts";
// 1) Create a kernel (optionally pass overrides)
const kernel = new HttpKernel();
// 2) Register a route with fluent chaining
kernel
.route({ method: "GET", path: "/hello/:name" })
.middleware(async (ctx, next) => {
console.log("Incoming request for", ctx.params.name);
return await next(); // continue pipeline
})
.handle(async (ctx) =>
new Response(`Hello ${ctx.params.name}!`, { status: 200 })
);
// 3) Let Deno serve the kernel
Deno.serve(kernel.handle);
```
Run it:
```bash
deno run --allow-net main.ts
# → GET http://localhost:8000/hello/Isaac
```
---
## 🧩 API Overview
| Method / Type | Purpose | Hints |
| --------------------- | ---------------------------------------------- | ------------------------------------------------------------- |
| `kernel.route(def)` | Begin defining a new route. Returns `RouteBuilder`. | `def` can be `{ method, path }` **or** `{ method, matcher }`. |
| `.middleware(fn)` | Add a middleware to the current builder. | Each call returns a *new* builder (immutability). |
| `.handle(fn)` | Finalise the route and register the handler. | Must be called exactly once per route. |
| `kernel.handle(req)` | Kernel entry point you pass to `Deno.serve()`. | Resolves to a `Response`. |
### Context Shape
```ts
interface Context<S = Record<string, unknown>> {
req: Request; // original request
params: Record<string>; // route params e.g. { id: "42" }
query: Record<string | string[]>; // parsed query string
state: S; // per‑request mutable storage
}
```
Generics let you supply your own param / query / state types for full IntelliSense.
---
## 🛠️ Configuration
```ts
new HttpKernel({
decorateResponse: (res, ctx) => {
// add CORS header globally
const headers = new Headers(res.headers);
headers.set("Access-Control-Allow-Origin", "*");
return new Response(res.body, { ...res, headers });
},
httpErrorHandlers: {
404: () => new Response("Nothing here ☹️", { status: 404 }),
500: (_ctx, err) => {
console.error(err);
return new Response("Custom 500", { status: 500 });
},
},
});
```
Everything is optional – omit what you do not override.
---
## 🧪 Testing
All logic is covered by unit tests using `std@0.204.0/testing`.
Run them with:
```bash
deno test -A
```
The CI suite checks:
* Route guards (`isStaticRouteDefinition`, `isDynamicRouteDefinition`)
* Builder immutability & middleware order
* 404 / 500 fall-backs and error propagation
* Middleware mis-use (double `next()`, wrong signatures, …)
---
## 📦 Roadmap
* 🔌 Adapter helpers for Oak / Fresh / any framework that can delegate to `kernel.handle`
* 🔍 Built‑in logger & timing middleware
* 🔒 CSRF & auth middleware presets
* 📝 OpenAPI route generator
Contributions & ideas are welcome – feel free to open an issue or PR.
---
## 📄 License
[MIT](LICENSE)

1
v0.2.1/VERSION Normal file
View File

@@ -0,0 +1 @@
0.2.1

104
v0.2.1/cliff.toml Normal file
View File

@@ -0,0 +1,104 @@
# CLIFF_VERSION=2.8.0
# git-cliff ~ default configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[remote.gitea]
owner = "maxp"
repo = "http-kernel"
[changelog]
# postprocessors
postprocessors = [
{ pattern = '<GITEA_URL>', replace = "https://git.0xmax42.io" }, # replace gitea url
]
# template for the changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{%- macro remote_url() -%}
<GITEA_URL>/{{ remote.gitea.owner }}/{{ remote.gitea.repo }}
{%- endmacro -%}
{% if version %}\
{% if previous.version %}\
## [{{ version | trim_start_matches(pat="v") }}]\
({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% endif %}\
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }} - \
([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
{% endfor %}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
"""
# remove the leading and trailing s
trim = true
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(changelog\\)", skip = true },
{ message = "^chore\\(version\\)", skip = true },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = ".*", group = "<!-- 10 -->💼 Other" },
]
# Regex to select git tags that represent releases.
tag_pattern = "v[0-9]+\\.[0-9]+\\.[0-9]+"
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "newest"

36
v0.2.1/deno.jsonc Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "@0xmax42/http-kernel",
"description": "A simple HTTP kernel for Deno",
"exports": {
"./mod.ts": "./src/mod.ts"
},
"tasks": {
"test": "deno test --allow-net --allow-env --unstable-kv --allow-read --allow-write --coverage **/__tests__/*.test.ts",
"test:watch": "deno test --watch --allow-net --allow-env --unstable-kv --allow-read --allow-write **/__tests__/*.test.ts",
"benchmark": "deno bench --allow-net --allow-env --unstable-kv --allow-read --allow-write **/__bench__/*.bench.ts",
"fmt": "deno fmt --check",
"lint": "deno lint",
"ci": "deno task fmt && deno task lint && deno task test && deno task benchmark" // For local CI checks
},
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext",
"deno.ns"
],
"strict": true
},
"fmt": {
"useTabs": false,
"lineWidth": 80,
"indentWidth": 4,
"semiColons": true,
"singleQuote": true,
"proseWrap": "preserve",
"include": [
"src/",
"main.ts"
]
}
}

155
v0.2.1/deno.lock generated Normal file
View File

@@ -0,0 +1,155 @@
{
"version": "5",
"redirects": {
"https://deno.land/std/assert/mod.ts": "https://deno.land/std@0.224.0/assert/mod.ts",
"https://deno.land/std/fs/walk.ts": "https://deno.land/std@0.224.0/fs/walk.ts",
"https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts"
},
"remote": {
"https://deno.land/std@0.204.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9",
"https://deno.land/std@0.204.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48",
"https://deno.land/std@0.204.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7",
"https://deno.land/std@0.204.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
"https://deno.land/std@0.204.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c",
"https://deno.land/std@0.204.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9",
"https://deno.land/std@0.204.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227",
"https://deno.land/std@0.204.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7",
"https://deno.land/std@0.204.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6",
"https://deno.land/std@0.204.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63",
"https://deno.land/std@0.204.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c",
"https://deno.land/std@0.204.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c",
"https://deno.land/std@0.204.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b",
"https://deno.land/std@0.204.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4",
"https://deno.land/std@0.204.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848",
"https://deno.land/std@0.204.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b",
"https://deno.land/std@0.204.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754",
"https://deno.land/std@0.204.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22",
"https://deno.land/std@0.204.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0",
"https://deno.land/std@0.204.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad",
"https://deno.land/std@0.204.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54",
"https://deno.land/std@0.204.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057",
"https://deno.land/std@0.204.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265",
"https://deno.land/std@0.204.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c",
"https://deno.land/std@0.204.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd",
"https://deno.land/std@0.204.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
"https://deno.land/std@0.204.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece",
"https://deno.land/std@0.204.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278",
"https://deno.land/std@0.204.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085",
"https://deno.land/std@0.204.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a",
"https://deno.land/std@0.204.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536",
"https://deno.land/std@0.204.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9",
"https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975",
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
"https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293",
"https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7",
"https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74",
"https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd",
"https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff",
"https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46",
"https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b",
"https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c",
"https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491",
"https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68",
"https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3",
"https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7",
"https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29",
"https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a",
"https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a",
"https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8",
"https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693",
"https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31",
"https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5",
"https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8",
"https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb",
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
"https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47",
"https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68",
"https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3",
"https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73",
"https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19",
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
"https://deno.land/std@0.224.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412",
"https://deno.land/std@0.224.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e",
"https://deno.land/std@0.224.0/fs/walk.ts": "cddf87d2705c0163bff5d7767291f05b0f46ba10b8b28f227c3849cace08d303",
"https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6",
"https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2",
"https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e",
"https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8",
"https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2",
"https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c",
"https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c",
"https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
"https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b",
"https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf",
"https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d",
"https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
"https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3",
"https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607",
"https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a",
"https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883",
"https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0",
"https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15",
"https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e",
"https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643",
"https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36",
"https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c",
"https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441",
"https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac",
"https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069",
"https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972",
"https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7",
"https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141",
"https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a",
"https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0",
"https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd",
"https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352",
"https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f",
"https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a",
"https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d",
"https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0",
"https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
"https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1",
"https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00",
"https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2",
"https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1",
"https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40",
"https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f",
"https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede",
"https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
"https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63",
"https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
"https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
"https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91",
"https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
"https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a",
"https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c",
"https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf",
"https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf",
"https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0",
"https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add",
"https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d",
"https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b",
"https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40",
"https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808",
"https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660",
"https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
"https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5",
"https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9",
"https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef",
"https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6",
"https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01",
"https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8",
"https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a",
"https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
"https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
"https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
"https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
"https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780",
"https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
"https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707",
"https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7",
"https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972",
"https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e",
"https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c"
}
}

View File

@@ -0,0 +1,25 @@
/**
* Represents an error thrown when an incoming HTTP method
* is not among the recognized set of valid HTTP methods.
*
* This is typically used in routers or request dispatchers
* to enforce allowed methods and produce 405-like behavior.
*/
export class InvalidHttpMethodError extends Error {
/**
* The invalid method that triggered this error.
*/
public readonly method: unknown;
/**
* A fixed HTTP status code representing "Method Not Allowed".
*/
public readonly status: number = 405;
constructor(method: unknown) {
const label = typeof method === 'string' ? method : '[non-string]';
super(`Unsupported HTTP method: ${label}`);
this.name = 'InvalidHttpMethodError';
this.method = method;
}
}

3
v0.2.1/src/Errors/mod.ts Normal file
View File

@@ -0,0 +1,3 @@
// deno-coverage-ignore-file
export { InvalidHttpMethodError } from './InvalidHttpMethodError.ts';

144
v0.2.1/src/HttpKernel.ts Normal file
View File

@@ -0,0 +1,144 @@
import type {
IContext,
IHttpKernel,
IHttpKernelConfig,
IInternalRoute,
IRouteBuilder,
IRouteDefinition,
} from './Interfaces/mod.ts';
import {
type DeepPartial,
HTTP_404_NOT_FOUND,
HTTP_500_INTERNAL_SERVER_ERROR,
HttpStatusTextMap,
} from './Types/mod.ts';
import { RouteBuilder } from './RouteBuilder.ts';
import { createEmptyContext, normalizeError } from './Utils/mod.ts';
/**
* The `HttpKernel` is the central routing engine that manages the full HTTP request lifecycle.
*
* It enables:
* - Dynamic and static route registration via a fluent API
* - Execution of typed middleware chains and final route handlers
* - Injection of response decorators and factory overrides
* - Fine-grained error handling via typed status-code-based handlers
*
* The kernel is designed with generics for flexible context typing, strong type safety,
* and a clear extension point for advanced routing, DI, or tracing logic.
*
* @typeParam TContext - The global context type used for all requests handled by this kernel.
*/
export class HttpKernel<TContext extends IContext = IContext>
implements IHttpKernel<TContext> {
private cfg: IHttpKernelConfig<TContext>;
/**
* The list of registered route definitions, including method, matcher,
* middleware pipeline, and final handler.
*/
private routes: IInternalRoute<TContext>[] = [];
/**
* Initializes the `HttpKernel` with optional configuration overrides.
*
* Default components such as the route builder factory, response decorator,
* and 404/500 error handlers can be replaced by injecting a partial config.
* Any omitted values fall back to sensible defaults.
*
* @param config - Partial kernel configuration. Missing fields are filled with defaults.
*/
public constructor(
config?: DeepPartial<IHttpKernelConfig<TContext>>,
) {
this.cfg = {
decorateResponse: (res) => res,
routeBuilderFactory: RouteBuilder,
httpErrorHandlers: {
[HTTP_404_NOT_FOUND]: () =>
new Response(HttpStatusTextMap[HTTP_404_NOT_FOUND], {
status: HTTP_404_NOT_FOUND,
}),
[HTTP_500_INTERNAL_SERVER_ERROR]: () =>
new Response(
HttpStatusTextMap[HTTP_500_INTERNAL_SERVER_ERROR],
{
status: HTTP_500_INTERNAL_SERVER_ERROR,
},
),
...(config?.httpErrorHandlers ?? {}),
},
...config,
} as IHttpKernelConfig<TContext>;
this.handle = this.handle.bind(this);
this.registerRoute = this.registerRoute.bind(this);
}
/**
* @inheritdoc
*/
public route<_TContext extends IContext = TContext>(
definition: IRouteDefinition,
): IRouteBuilder<_TContext> {
return new this.cfg.routeBuilderFactory(
this.registerRoute,
definition,
) as IRouteBuilder<_TContext>;
}
/**
* @inheritdoc
*/
public async handle(request: Request): Promise<Response> {
const url = new URL(request.url);
const method = request.method.toUpperCase();
for (const route of this.routes) {
if (route.method !== method) continue;
const match = route.matcher(url, request);
if (match) {
const ctx: TContext = {
req: request,
params: match.params,
query: match.query,
state: {},
} as TContext;
try {
const response = await route.runRoute(ctx);
return this.cfg.decorateResponse(response, ctx);
} catch (e) {
return await this.handleInternalError(ctx, e);
}
}
}
return this.cfg.httpErrorHandlers[HTTP_404_NOT_FOUND](
createEmptyContext<TContext>(request),
);
}
/**
* Finalizes and registers a route within the kernel.
*
* This method is invoked internally by the route builder once
* `.handle()` is called. It appends the route to the internal list.
*
* @param route - A fully constructed internal route object.
*/
private registerRoute<_TContext extends IContext = TContext>(
route: IInternalRoute<_TContext>,
): void {
this.routes.push(route as unknown as IInternalRoute<TContext>);
}
private handleInternalError = (
ctx: TContext,
err?: unknown,
): Response | Promise<Response> => {
return this.cfg.httpErrorHandlers[HTTP_500_INTERNAL_SERVER_ERROR](
ctx,
normalizeError(err),
);
};
}

View File

@@ -0,0 +1,53 @@
import type { Params, Query, State } from '../Types/mod.ts';
/**
* Represents the complete context for a single HTTP request,
* passed through the middleware pipeline and to the final route handler.
*
* This context object encapsulates all relevant runtime data for a request,
* including the original request, path parameters, query parameters,
* and a shared, mutable application state.
*
* @template TState Structured per-request state shared across middlewares and handlers.
* @template TParams Parsed URL path parameters, typically derived from route templates.
* @template TQuery Parsed query string parameters, preserving multi-value semantics.
*/
export interface IContext<
TState extends State = State,
TParams extends Params = Params,
TQuery extends Query = Query,
> {
/**
* The original HTTP request object as received by Deno.
* Contains all standard fields like headers, method, body, etc.
*/
req: Request;
/**
* Route parameters parsed from the URL path, based on route definitions
* that include dynamic segments (e.g., `/users/:id` → `{ id: "123" }`).
*
* These parameters are considered read-only and are set by the router.
*/
params: TParams;
/**
* Query parameters extracted from the request URL's search string.
*
* Values may occur multiple times (e.g., `?tag=ts&tag=deno`), and are therefore
* represented as either a string or an array of strings, depending on occurrence.
*
* Use this field to access filters, flags, pagination info, or similar modifiers.
*/
query: TQuery;
/**
* A typed, mutable object used to pass structured data between middlewares and handlers.
*
* This object is ideal for sharing validated input, user identity, trace information,
* or other contextual state throughout the request lifecycle.
*
* Type-safe access to fields is ensured by the generic `TState` type.
*/
state: TState;
}

View File

@@ -0,0 +1,40 @@
import type { IContext } from '../Interfaces/mod.ts';
import type { HttpErrorHandler, validHttpErrorCodes } from '../Types/mod.ts';
/**
* A mapping of HTTP status codes to their corresponding error handlers.
*
* This interface defines required handlers for common critical status codes (404 and 500)
* and allows optional handlers for all other known error codes defined in `validHttpErrorCodes`.
*
* This hybrid approach ensures predictable handling for key failure cases,
* while remaining flexible for less common codes.
*
* @template TContext - The context type used in all error handlers.
*
* @example
* ```ts
* const errorHandlers: IHttpErrorHandlers = {
* 404: (ctx) => new Response("Not Found", { status: 404 }),
* 500: (ctx, err) => {
* console.error(err);
* return new Response("Internal Server Error", { status: 500 });
* },
* 429: (ctx) => new Response("Too Many Requests", { status: 429 }),
* };
* ```
*/
export interface IHttpErrorHandlers<TContext extends IContext = IContext>
extends
Partial<
Record<
Exclude<typeof validHttpErrorCodes[number], 404 | 500>,
HttpErrorHandler<TContext>
>
> {
/** Required error handler for HTTP 404 (Not Found). */
404: HttpErrorHandler<TContext>;
/** Required error handler for HTTP 500 (Internal Server Error). */
500: HttpErrorHandler<TContext>;
}

View File

@@ -0,0 +1,49 @@
import type { IContext } from './IContext.ts';
import type { IRouteBuilder } from './IRouteBuilder.ts';
import type { IRouteDefinition } from './IRouteDefinition.ts';
/**
* The `IHttpKernel` interface defines the public API for a type-safe, middleware-driven HTTP dispatching system.
*
* Implementations of this interface are responsible for:
* - Registering routes with optional per-route context typing
* - Handling incoming requests by matching and dispatching to appropriate handlers
* - Managing the complete middleware pipeline and final response generation
*
* The kernel operates on a customizable `IContext` type to support strongly typed request parameters, state,
* and query values across the entire routing lifecycle.
*
* @typeParam TContext - The default context type used for all routes unless overridden per-route.
*/
export interface IHttpKernel<TContext extends IContext = IContext> {
/**
* Registers a new HTTP route (static or dynamic) and returns a route builder for middleware/handler chaining.
*
* This method supports contextual polymorphism via the `_TContext` type parameter, enabling fine-grained
* typing of route-specific `params`, `query`, and `state` values. The route is not registered until
* `.handle()` is called on the returned builder.
*
* @typeParam _TContext - An optional override for the context type specific to this route.
* Falls back to the global `TContext` of the kernel if omitted.
*
* @param definition - A route definition specifying the HTTP method and path or custom matcher.
* @returns A fluent builder interface to define middleware and attach a final handler.
*/
route<_TContext extends IContext = TContext>(
definition: IRouteDefinition,
): IRouteBuilder<_TContext>;
/**
* Handles an incoming HTTP request and produces a `Response`.
*
* The kernel matches the request against all registered routes by method and matcher,
* constructs a typed context, and executes the middleware/handler pipeline.
* If no route matches, a 404 error handler is invoked.
*
* This method is designed to be passed directly to `Deno.serve()` or similar server frameworks.
*
* @param request - The incoming HTTP request object.
* @returns A `Promise` resolving to a complete HTTP response.
*/
handle(request: Request): Promise<Response>;
}

View File

@@ -0,0 +1,10 @@
import type { ResponseDecorator } from '../Types/mod.ts';
import type { IContext } from './IContext.ts';
import type { IHttpErrorHandlers } from './IHttpErrorHandlers.ts';
import type { IRouteBuilderFactory } from './IRouteBuilder.ts';
export interface IHttpKernelConfig<TContext extends IContext = IContext> {
decorateResponse: ResponseDecorator<TContext>;
routeBuilderFactory: IRouteBuilderFactory;
httpErrorHandlers: IHttpErrorHandlers<TContext>;
}

View File

@@ -0,0 +1,64 @@
import type { Handler, HttpMethod, Middleware } from '../Types/mod.ts';
import type { IContext, IRouteMatcher } from './mod.ts';
/**
* Represents an internally registered route within the HttpKernel.
*
* Contains all data required to match an incoming request and dispatch it
* through the associated middleware chain and final handler.
*/
export interface IInternalRoute<TContext extends IContext = IContext> {
/**
* The HTTP method (e.g. 'GET', 'POST') that this route responds to.
* The method should always be in uppercase.
*/
method: HttpMethod;
/**
* A matcher function used to determine whether this route matches a given request.
*
* If the matcher returns `null`, the route does not apply to the request.
* If it returns a params object, the route is considered matched and the extracted
* parameters are passed into the request context.
*
* @param url - The parsed URL object from the incoming request.
* @param req - The original Request object.
* @returns An object with extracted path parameters, or `null` if not matched.
*/
matcher: IRouteMatcher;
/**
* An ordered list of middleware functions to be executed before the handler.
*/
middlewares: Middleware<TContext>[];
/**
* The final handler that generates the HTTP response after all middleware has run.
*/
handler: Handler<TContext>;
/**
* The fully compiled execution pipeline for this route.
*
* This function is generated at route registration time and encapsulates the
* entire middleware chain as well as the final handler. It is called by the
* HttpKernel during request dispatch when a route has been matched.
*
* Internally, `runRoute` ensures that each middleware is invoked in the correct order
* and receives a `next()` callback to pass control downstream. The final handler is
* invoked once all middleware has completed or short-circuited the pipeline.
*
* It is guaranteed that:
* - The function is statically compiled and does not perform dynamic dispatching.
* - Each middleware can only call `next()` once; repeated invocations will throw.
* - The return value is either a `Response` or a Promise resolving to one.
*
* @param ctx - The context object carrying route, request, response and other scoped data.
* @returns A `Response` object or a Promise resolving to a `Response`.
*
* @throws {Error} If a middleware calls `next()` more than once.
*/
runRoute: (
ctx: TContext,
) => Promise<Response> | Response;
}

View File

@@ -0,0 +1,39 @@
import type { Handler, Middleware } from '../Types/mod.ts';
import type { IInternalRoute } from './IInternalRoute.ts';
import type { IRouteDefinition } from './IRouteDefinition.ts';
import type { IContext } from './mod.ts';
export interface IRouteBuilderFactory<TContext extends IContext = IContext> {
new (
registerRoute: (route: IInternalRoute<TContext>) => void,
def: IRouteDefinition,
mws?: Middleware<TContext>[],
): IRouteBuilder<TContext>;
}
/**
* Provides a fluent API to build a single route configuration by chaining
* middleware and setting the final request handler.
*/
export interface IRouteBuilder<TContext extends IContext = IContext> {
/**
* Adds a middleware to the current route.
* Middleware will be executed in the order of registration.
*
* @param mw - A middleware function.
* @returns The route builder for further chaining.
*/
middleware(
mw: Middleware<TContext>,
): IRouteBuilder<TContext>;
/**
* Sets the final request handler for the route.
* Calling this finalizes the route and registers it in the kernel.
*
* @param handler - The function to execute when this route is matched.
*/
handle(
handler: Handler<TContext>,
): void;
}

View File

@@ -0,0 +1,91 @@
import { type HttpMethod, isHttpMethod } from '../Types/mod.ts';
import type { IRouteMatcher } from './IRouteMatcher.ts';
/**
* Defines a static route using a path pattern with optional parameters.
*
* Suitable for conventional routes like "/users/:id", which can be parsed
* into named parameters using a path-matching library.
*/
export interface IStaticRouteDefinition {
/**
* The HTTP method this route should match (e.g. "GET", "POST").
*/
method: HttpMethod;
/**
* A static path pattern for the route, which may include named parameters
* (e.g. "/caches/:id"). Internally, this can be converted to a regex matcher.
*/
path: string;
}
/**
* Defines a dynamic route using a custom matcher function instead of a static path.
*
* Useful for complex URL structures that cannot easily be expressed using a static pattern,
* such as routes with variable prefixes or conditional segment logic.
*/
export interface IDynamicRouteDefinition {
/**
* The HTTP method this route should match (e.g. "GET", "POST").
*/
method: HttpMethod;
/**
* A custom matcher function that receives the parsed URL and raw request.
* If the function returns `null`, the route does not match.
* If the function returns a params object, the route is considered matched.
*/
matcher: IRouteMatcher;
}
/**
* A route definition can either be a conventional static route with a path pattern,
* or a dynamic route with a custom matcher function for advanced matching logic.
*/
export type IRouteDefinition = IStaticRouteDefinition | IDynamicRouteDefinition;
/**
* Type guard to check whether a route definition is a valid static route definition.
*
* Ensures that the object:
* - has a `method` property of type `HttpMethod`
* - has a `path` property of type `string`
* - does NOT have a `matcher` function (to avoid ambiguous mixed types)
*/
export function isStaticRouteDefinition(
def: IRouteDefinition,
): def is IStaticRouteDefinition {
return (
def &&
typeof def === 'object' &&
'method' in def &&
isHttpMethod(def.method) &&
'path' in def &&
typeof (def as { path?: unknown }).path === 'string' &&
!('matcher' in def)
);
}
/**
* Type guard to check whether a route definition is a valid dynamic route definition.
*
* Ensures that the object:
* - has a `method` property of type `HttpMethod`
* - has a `matcher` property of type `function`
* - does NOT have a `path` property (to avoid ambiguous mixed types)
*/
export function isDynamicRouteDefinition(
def: IRouteDefinition,
): def is IDynamicRouteDefinition {
return (
def &&
typeof def === 'object' &&
'method' in def &&
isHttpMethod(def.method) &&
'matcher' in def &&
typeof (def as { matcher?: unknown }).matcher === 'function' &&
!('path' in def)
);
}

View File

@@ -0,0 +1,6 @@
import type { Params, Query } from '../Types/mod.ts';
export interface IRouteMatch {
params?: Params;
query?: Query;
}

View File

@@ -0,0 +1,35 @@
import type { IRouteDefinition } from './IRouteDefinition.ts';
import type { IRouteMatch } from './IRouteMatch.ts';
/**
* Defines a route matcher function that evaluates whether a route applies to a given request.
*
* If the route matches, the matcher returns an object containing extracted route parameters.
* Otherwise, it returns `null`.
*/
export interface IRouteMatcher {
/**
* Evaluates whether the given URL and request match a defined route.
*
* @param url - The full URL of the incoming request.
* @param req - The raw Request object (may be used for context or headers).
* @returns An object containing path parameters if matched, or `null` if not matched.
*/
(url: URL, req: Request): null | IRouteMatch;
}
/**
* Represents a factory for creating route matcher functions from route definitions.
*
* This allows the matcher logic to be injected or replaced (e.g. for testing,
* pattern libraries, or advanced routing scenarios).
*/
export interface IRouteMatcherFactory {
/**
* Creates a matcher function based on a given route definition.
*
* @param def - The route definition (static or dynamic).
* @returns A matcher function that checks if a request matches and extracts parameters.
*/
(def: IRouteDefinition): IRouteMatcher;
}

View File

@@ -0,0 +1,43 @@
import { assertEquals } from 'https://deno.land/std@0.204.0/assert/mod.ts';
import {
type IRouteDefinition,
isDynamicRouteDefinition,
isStaticRouteDefinition,
} from '../IRouteDefinition.ts';
Deno.test('isStaticRouteDefinition returns true for static route', () => {
const staticDef: IRouteDefinition = {
method: 'GET',
path: '/users/:id',
};
assertEquals(isStaticRouteDefinition(staticDef), true);
assertEquals(isDynamicRouteDefinition(staticDef), false);
});
Deno.test('isDynamicRouteDefinition returns true for dynamic route', () => {
const dynamicDef: IRouteDefinition = {
method: 'POST',
matcher: (_url, _req) => ({ params: {} }),
};
assertEquals(isDynamicRouteDefinition(dynamicDef), true);
assertEquals(isStaticRouteDefinition(dynamicDef), false);
});
Deno.test('isStaticRouteDefinition returns false for invalid object', () => {
const invalidDef = {
method: 'GET',
} as unknown as IRouteDefinition;
assertEquals(isStaticRouteDefinition(invalidDef), false);
});
Deno.test('isDynamicRouteDefinition returns false for object with no matcher', () => {
const def = {
method: 'DELETE',
path: '/something',
};
assertEquals(isDynamicRouteDefinition(def as IRouteDefinition), false);
});

View File

@@ -0,0 +1,19 @@
// deno-coverage-ignore-file
export type { IContext } from './IContext.ts';
export type { IHttpErrorHandlers } from './IHttpErrorHandlers.ts';
export type { IHttpKernel } from './IHttpKernel.ts';
export type { IHttpKernelConfig } from './IHttpKernelConfig.ts';
export type { IInternalRoute } from './IInternalRoute.ts';
export type { IRouteBuilder, IRouteBuilderFactory } from './IRouteBuilder.ts';
export {
isDynamicRouteDefinition,
isStaticRouteDefinition,
} from './IRouteDefinition.ts';
export type {
IDynamicRouteDefinition,
IRouteDefinition,
IStaticRouteDefinition,
} from './IRouteDefinition.ts';
export type { IRouteMatch } from './IRouteMatch.ts';
export type { IRouteMatcher, IRouteMatcherFactory } from './IRouteMatcher.ts';

148
v0.2.1/src/RouteBuilder.ts Normal file
View File

@@ -0,0 +1,148 @@
import type { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts';
import type {
IContext,
IInternalRoute,
IRouteBuilder,
IRouteDefinition,
} from './Interfaces/mod.ts';
import { isHandler } from './Types/Handler.ts';
import {
type Handler,
isMiddleware,
type Middleware,
type RegisterRoute,
} from './Types/mod.ts';
import { createRouteMatcher } from './Utils/createRouteMatcher.ts';
/**
* Provides a fluent builder interface for defining a single route,
* including HTTP method, path or matcher, middleware chain and final handler.
*
* This builder is stateless and immutable; each chained call returns a new instance.
*/
export class RouteBuilder<TContext extends IContext = IContext>
implements IRouteBuilder<TContext> {
/**
* Constructs a new instance of the route builder.
*
* @param registerRoute - A delegate used to register the finalized route definition.
* @param def - The route definition (static path or dynamic matcher).
* @param mws - The list of middleware functions collected so far (default: empty).
*/
constructor(
private readonly registerRoute: RegisterRoute<TContext>,
private readonly def: IRouteDefinition,
private readonly mws: Middleware<TContext>[] = [],
private readonly matcherFactory: IRouteMatcherFactory =
createRouteMatcher,
) {}
/**
* Adds a middleware function to the current route definition.
*
* Middleware is executed in the order it is added.
* Returns a new builder instance with the additional middleware appended.
*
* @param mw - A middleware function to be executed before the handler.
* @returns A new `RouteBuilder` instance for continued chaining.
*/
middleware(
mw: Middleware<TContext>,
): IRouteBuilder<TContext> {
return new RouteBuilder<TContext>(
this.registerRoute,
this.def,
[...this.mws, mw],
);
}
/**
* Finalizes the route by assigning the handler and registering the route.
*
* Internally constructs a matcher function from the route definition
* and passes all route data to the registration delegate.
*
* @param handler - The final request handler for this route.
*/
handle(
handler: Handler<TContext>,
): void {
const matcher = this.matcherFactory(this.def);
this.registerRoute({
method: this.def.method,
matcher,
middlewares: this.mws,
handler: handler,
runRoute: this.compile({
middlewares: this.mws,
handler: handler,
}),
});
}
/**
* Compiles the middleware chain and handler into a single executable function.
*
* This method constructs a statically linked function chain by reducing all middleware
* and the final handler into one composed `runRoute` function. Each middleware receives
* a `next()` callback that invokes the next function in the chain.
*
* Additionally, the returned function ensures that `next()` can only be called once
* per middleware. If `next()` is invoked multiple times within the same middleware,
* a runtime `Error` is thrown, preventing unintended double-processing.
*
* Type safety is enforced at compile time:
* - If the final handler does not match the expected signature, a `TypeError` is thrown.
* - If any middleware does not conform to the middleware interface, a `TypeError` is thrown.
*
* @param route - A partial route object containing middleware and handler,
* excluding `matcher`, `method`, and `runRoute`.
* @returns A composed route execution function that takes a context object
* and returns a `Promise<Response>`.
*
* @throws {TypeError} If the handler or any middleware function is invalid.
* @throws {Error} If a middleware calls `next()` more than once during execution.
*/
private compile(
route: Omit<
IInternalRoute<TContext>,
'runRoute' | 'matcher' | 'method'
>,
): (
ctx: TContext,
) => Promise<Response> {
if (!isHandler<TContext>(route.handler)) {
throw new TypeError(
'Route handler must be a function returning a Promise<Response>.',
);
}
let composed = route.handler;
for (let i = route.middlewares.length - 1; i >= 0; i--) {
if (!isMiddleware<TContext>(route.middlewares[i])) {
throw new TypeError(
`Middleware at index ${i} is not a valid function.`,
);
}
const current = route.middlewares[i];
const next = composed;
composed = async (ctx: TContext): Promise<Response> => {
let called = false;
return await current(ctx, async () => {
if (called) {
throw new Error(
`next() called multiple times in middleware at index ${i}`,
);
}
called = true;
return await next(ctx);
});
};
}
return composed;
}
}

View File

@@ -0,0 +1,4 @@
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]>
: T[P];
};

View File

@@ -0,0 +1,57 @@
import type { IContext } from '../Interfaces/mod.ts';
/**
* Represents a final request handler responsible for producing an HTTP response.
*
* The handler is the terminal stage of the middleware pipeline and is responsible
* for processing the incoming request and generating the final `Response`.
*
* It receives the fully-typed request context, which includes the original request,
* parsed route parameters, query parameters, and any shared state populated by prior middleware.
*
* @template TContext The specific context type for this handler, including typed `state`, `params`, and `query`.
*/
type Handler<TContext extends IContext = IContext> = (
ctx: TContext,
) => Promise<Response>;
/**
* Represents a handler function with an associated name.
*
* This is useful for debugging, logging, or when you need to reference
* the handler by name in your application.
*
* @template TContext The specific context type for this handler, including typed `state`, `params`, and `query`.
*/
type NamedHandler<TContext extends IContext = IContext> =
& Handler<TContext>
& { name?: string };
export type { NamedHandler as Handler };
/**
* Type guard to determine whether a given value is a valid `IHandler` function.
*
* This function checks whether the input is a function and whether it returns
* a `Promise<Response>` when called. Due to TypeScript's structural typing and
* the lack of runtime type information, only minimal runtime validation is possible.
*
* @param value - The value to test.
* @returns `true` if the value is a function that appears to conform to `IHandler`.
*
* @example
* ```ts
* const candidate = async (ctx: IContext) => new Response("ok");
* if (isHandler(candidate)) {
* // candidate is now typed as IHandler<IContext>
* }
* ```
*/
export function isHandler<TContext extends IContext = IContext>(
value: unknown,
): value is Handler<TContext> {
return (
typeof value === 'function' &&
value.length === 1 // ctx
);
}

View File

@@ -0,0 +1,28 @@
import type { IContext } from '../Interfaces/mod.ts';
/**
* Defines a handler function for errors that occur during the execution
* of middleware or route handlers within the HTTP kernel.
*
* This function receives both the request context and the thrown error,
* and is responsible for producing an appropriate HTTP `Response`.
*
* Typical use cases include:
* - Mapping known error types to specific HTTP status codes.
* - Generating structured error responses (e.g. JSON error payloads).
* - Logging errors centrally with request metadata.
*
* The handler may return the response synchronously or asynchronously.
*
* @template TContext - The specific request context type, allowing typed access to route parameters,
* query parameters, and per-request state when formatting error responses.
*
* @param context - The active request context at the time the error occurred.
* @param error - The exception or error that was thrown during request processing.
*
* @returns A `Response` object or a `Promise` resolving to one, to be sent to the client.
*/
export type HttpErrorHandler<TContext extends IContext = IContext> = (
context?: Partial<TContext>,
error?: Error,
) => Promise<Response> | Response;

View File

@@ -0,0 +1,52 @@
/**
* A constant list of all supported HTTP methods according to RFC 7231 and RFC 5789.
*
* This array serves both as a runtime value list for validation
* and as the basis for deriving the `HttpMethod` union type.
*
* Note: The list is immutable and should not be modified at runtime.
*/
export const validHttpMethods = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'HEAD',
'OPTIONS',
] as const;
/**
* A union type representing all valid HTTP methods recognized by this application.
*
* This type is derived directly from the `validHttpMethods` constant,
* ensuring type safety and consistency between type system and runtime checks.
*
* Example:
* ```ts
* const method: HttpMethod = 'POST'; // ✅ valid
* const method: HttpMethod = 'FOO'; // ❌ Type error
* ```
*/
export type HttpMethod = typeof validHttpMethods[number];
/**
* Type guard to verify whether a given value is a valid HTTP method.
*
* This function checks both the type and content of the value
* and is suitable for runtime validation of inputs (e.g., from HTTP requests).
*
* Example:
* ```ts
* if (isHttpMethod(input)) {
* // input is now typed as HttpMethod
* }
* ```
*
* @param value - The value to test (typically a string from a request).
* @returns `true` if the value is a valid `HttpMethod`, otherwise `false`.
*/
export function isHttpMethod(value: unknown): value is HttpMethod {
return typeof value === 'string' &&
validHttpMethods.includes(value as HttpMethod);
}

View File

@@ -0,0 +1,189 @@
// Informational responses
/** Indicates that the request was received and the client can continue. */
export const HTTP_100_CONTINUE = 100;
/** The server is switching protocols as requested by the client. */
export const HTTP_101_SWITCHING_PROTOCOLS = 101;
/** The server has received and is processing the request, but no response is available yet. */
export const HTTP_102_PROCESSING = 102;
// Successful responses
/** The request has succeeded. */
export const HTTP_200_OK = 200;
/** The request has succeeded and a new resource has been created as a result. */
export const HTTP_201_CREATED = 201;
/** The request has been accepted for processing, but the processing is not complete. */
export const HTTP_202_ACCEPTED = 202;
/** The server has successfully fulfilled the request and there is no content to send. */
export const HTTP_204_NO_CONTENT = 204;
// Redirection messages
/** The resource has been moved permanently to a new URI. */
export const HTTP_301_MOVED_PERMANENTLY = 301;
/** The resource resides temporarily under a different URI. */
export const HTTP_302_FOUND = 302;
/** Indicates that the resource has not been modified since the last request. */
export const HTTP_304_NOT_MODIFIED = 304;
// Client error responses
/** The server could not understand the request due to invalid syntax. */
export const HTTP_400_BAD_REQUEST = 400;
/** The request requires user authentication. */
export const HTTP_401_UNAUTHORIZED = 401;
/** The server understood the request but refuses to authorize it. */
export const HTTP_403_FORBIDDEN = 403;
/** The server cannot find the requested resource. */
export const HTTP_404_NOT_FOUND = 404;
/** The request method is known by the server but is not supported by the target resource. */
export const HTTP_405_METHOD_NOT_ALLOWED = 405;
/** The request could not be completed due to a conflict with the current state of the resource. */
export const HTTP_409_CONFLICT = 409;
/** The server understands the content type but was unable to process the contained instructions. */
export const HTTP_422_UNPROCESSABLE_ENTITY = 422;
/** The user has sent too many requests in a given amount of time. */
export const HTTP_429_TOO_MANY_REQUESTS = 429;
// Server error responses
/** The server encountered an unexpected condition that prevented it from fulfilling the request. */
export const HTTP_500_INTERNAL_SERVER_ERROR = 500;
/** The server does not support the functionality required to fulfill the request. */
export const HTTP_501_NOT_IMPLEMENTED = 501;
/** The server, while acting as a gateway or proxy, received an invalid response from the upstream server. */
export const HTTP_502_BAD_GATEWAY = 502;
/** The server is not ready to handle the request, often due to maintenance or overload. */
export const HTTP_503_SERVICE_UNAVAILABLE = 503;
/** The server is acting as a gateway and cannot get a response in time. */
export const HTTP_504_GATEWAY_TIMEOUT = 504;
/**
* A constant list of supported HTTP status codes used by this application.
*
* These constants are grouped by category and used to construct the union type `HttpStatusCode`.
*/
export const validHttpStatusCodes = [
// Informational
HTTP_100_CONTINUE,
HTTP_101_SWITCHING_PROTOCOLS,
HTTP_102_PROCESSING,
// Successful
HTTP_200_OK,
HTTP_201_CREATED,
HTTP_202_ACCEPTED,
HTTP_204_NO_CONTENT,
// Redirection
HTTP_301_MOVED_PERMANENTLY,
HTTP_302_FOUND,
HTTP_304_NOT_MODIFIED,
// Client Errors
HTTP_400_BAD_REQUEST,
HTTP_401_UNAUTHORIZED,
HTTP_403_FORBIDDEN,
HTTP_404_NOT_FOUND,
HTTP_405_METHOD_NOT_ALLOWED,
HTTP_409_CONFLICT,
HTTP_422_UNPROCESSABLE_ENTITY,
HTTP_429_TOO_MANY_REQUESTS,
// Server Errors
HTTP_500_INTERNAL_SERVER_ERROR,
HTTP_501_NOT_IMPLEMENTED,
HTTP_502_BAD_GATEWAY,
HTTP_503_SERVICE_UNAVAILABLE,
HTTP_504_GATEWAY_TIMEOUT,
] as const;
/**
* A constant list of HTTP error codes that are commonly used in the application.
*/
export const validHttpErrorCodes = [
// Client Errors
HTTP_400_BAD_REQUEST,
HTTP_401_UNAUTHORIZED,
HTTP_403_FORBIDDEN,
HTTP_404_NOT_FOUND,
HTTP_405_METHOD_NOT_ALLOWED,
HTTP_409_CONFLICT,
HTTP_422_UNPROCESSABLE_ENTITY,
HTTP_429_TOO_MANY_REQUESTS,
// Server Errors
HTTP_500_INTERNAL_SERVER_ERROR,
HTTP_501_NOT_IMPLEMENTED,
HTTP_502_BAD_GATEWAY,
HTTP_503_SERVICE_UNAVAILABLE,
HTTP_504_GATEWAY_TIMEOUT,
] as const;
/**
* Maps each supported HTTP status code to its standard status message.
*
* Useful for logging, diagnostics, or building custom error responses.
*/
export const HttpStatusTextMap: Record<
typeof validHttpStatusCodes[number],
string
> = {
[HTTP_100_CONTINUE]: 'Continue',
[HTTP_101_SWITCHING_PROTOCOLS]: 'Switching Protocols',
[HTTP_102_PROCESSING]: 'Processing',
[HTTP_200_OK]: 'OK',
[HTTP_201_CREATED]: 'Created',
[HTTP_202_ACCEPTED]: 'Accepted',
[HTTP_204_NO_CONTENT]: 'No Content',
[HTTP_301_MOVED_PERMANENTLY]: 'Moved Permanently',
[HTTP_302_FOUND]: 'Found',
[HTTP_304_NOT_MODIFIED]: 'Not Modified',
[HTTP_400_BAD_REQUEST]: 'Bad Request',
[HTTP_401_UNAUTHORIZED]: 'Unauthorized',
[HTTP_403_FORBIDDEN]: 'Forbidden',
[HTTP_404_NOT_FOUND]: 'Not Found',
[HTTP_405_METHOD_NOT_ALLOWED]: 'Method Not Allowed',
[HTTP_409_CONFLICT]: 'Conflict',
[HTTP_422_UNPROCESSABLE_ENTITY]: 'Unprocessable Entity',
[HTTP_429_TOO_MANY_REQUESTS]: 'Too Many Requests',
[HTTP_500_INTERNAL_SERVER_ERROR]: 'Internal Server Error',
[HTTP_501_NOT_IMPLEMENTED]: 'Not Implemented',
[HTTP_502_BAD_GATEWAY]: 'Bad Gateway',
[HTTP_503_SERVICE_UNAVAILABLE]: 'Service Unavailable',
[HTTP_504_GATEWAY_TIMEOUT]: 'Gateway Timeout',
};
/**
* A union type representing commonly used HTTP status codes.
*
* This type ensures consistency between runtime and type-level status code handling.
*
* Example:
* ```ts
* const status: HttpStatusCode = 404; // ✅ valid
* const status: HttpStatusCode = 418; // ❌ Type error (unless added to list)
* ```
*/
export type HttpStatusCode = typeof validHttpStatusCodes[number];
/**
* Type guard to check whether a given value is a valid HTTP status code.
*
* This is useful for validating numeric values received from external input,
* ensuring they conform to known HTTP semantics.
*
* Example:
* ```ts
* if (isHttpStatusCode(value)) {
* // value is now typed as HttpStatusCode
* }
* ```
*
* @param value - The numeric value to check.
* @returns `true` if the value is a recognized HTTP status code, otherwise `false`.
*/
export function isHttpStatusCode(value: unknown): value is HttpStatusCode {
return typeof value === 'number' &&
validHttpStatusCodes.includes(value as HttpStatusCode);
}

View File

@@ -0,0 +1,51 @@
import type { IContext } from '../Interfaces/IContext.ts';
/**
* Represents a middleware function in the HTTP request pipeline.
*
* Middleware is a core mechanism to intercept, observe, or modify the request lifecycle.
* It can be used for tasks such as logging, authentication, input validation,
* metrics collection, or response transformation.
*
* Each middleware receives a fully-typed request context and a `next()` function
* to invoke the next stage of the pipeline. Middleware may choose to short-circuit
* the pipeline by returning a `Response` early.
*
* @template TContext The specific context type for this middleware, including state, params, and query information.
*/
type Middleware<TContext extends IContext = IContext> = (
ctx: TContext,
next: () => Promise<Response>,
) => Promise<Response>;
/**
* Represents a middleware function with an associated name.
*
* This is useful for debugging, logging, or when you need to reference
* the middleware by name in your application.
*
* @template TContext The specific context type for this middleware, including state, params, and query information.
*/
type NamedMiddleware<TContext extends IContext = IContext> =
& Middleware<TContext>
& { name?: string };
export type { NamedMiddleware as Middleware };
/**
* Type guard to verify whether a given value is a valid `IMiddleware` function.
*
* This guard checks whether the input is a function that accepts exactly two arguments.
* Note: This is a structural check and cannot fully guarantee the semantics of a middleware.
*
* @param value - The value to test.
* @returns `true` if the value is structurally a valid middleware function.
*/
export function isMiddleware<TContext extends IContext = IContext>(
value: unknown,
): value is Middleware<TContext> {
return (
typeof value === 'function' &&
value.length === 2 // ctx, next
);
}

View File

@@ -0,0 +1,10 @@
/**
* Represents route parameters parsed from dynamic segments in the URL path.
*
* This type is typically derived from route definitions with placeholders,
* such as `/users/:id`, which would yield `{ id: "123" }`.
*
* All values are strings and should be considered read-only, as they are
* extracted by the router and should not be modified by application code.
*/
export type Params = Record<string, string>;

12
v0.2.1/src/Types/Query.ts Normal file
View File

@@ -0,0 +1,12 @@
/**
* Represents the parsed query parameters from the request URL.
*
* Query parameters originate from the URL search string (e.g. `?filter=active&tags=ts&tags=deno`)
* and may contain single or multiple values per key.
*
* All values are expressed as strings or arrays of strings, depending on how often
* the key occurs. This structure preserves the raw semantics of the query.
*
* For normalized single-value access, prefer custom DTOs or wrapper utilities.
*/
export type Query = Record<string, string | string[]>;

View File

@@ -0,0 +1,16 @@
import type { IContext } from '../Interfaces/IContext.ts';
import type { IInternalRoute } from '../Interfaces/mod.ts';
/**
* A type alias for the internal route registration function used by the `HttpKernel`.
*
* This function accepts a fully constructed internal route, including method, matcher,
* middleware chain, and final handler, and registers it for dispatching.
*
* Typically passed into `RouteBuilder` instances to enable fluent API chaining.
*
* @template TContext The context type associated with the route being registered.
*/
export type RegisterRoute<TContext extends IContext = IContext> = (
route: IInternalRoute<TContext>,
) => void;

View File

@@ -0,0 +1,30 @@
import type { IContext } from '../Interfaces/mod.ts';
/**
* A function that modifies or enriches an outgoing HTTP response before it is returned to the client.
*
* This decorator can be used to inject headers (e.g., CORS, security), apply global transformations,
* or wrap responses for logging, analytics, or debugging purposes.
*
* It is called exactly once at the end of the middleware/handler pipeline,
* allowing central response customization without interfering with business logic.
*
* @param res - The original `Response` object produced by the route handler or middleware chain.
* @returns A modified or wrapped `Response` object to be sent back to the client.
*
* @example
* ```ts
* const addCors: ResponseDecorator = (res) => {
* const headers = new Headers(res.headers);
* headers.set("Access-Control-Allow-Origin", "*");
* return new Response(res.body, {
* status: res.status,
* headers,
* });
* };
* ```
*/
export type ResponseDecorator<TContext extends IContext = IContext> = (
res: Response,
ctx: TContext,
) => Response;

View File

@@ -0,0 +1,9 @@
/**
* Represents the per-request state object shared across the middleware pipeline.
*
* This type defines the base structure for custom state definitions,
* which can be extended with concrete fields like user data, request metadata, etc.
*
* Custom `TState` types must extend this base to ensure compatibility.
*/
export type State = Record<string, unknown>;

View File

@@ -0,0 +1,40 @@
import { assertEquals } from 'https://deno.land/std/assert/mod.ts';
import { isHttpMethod, validHttpMethods } from '../HttpMethod.ts';
Deno.test('isHttpMethod: returns true for all valid methods', () => {
for (const method of validHttpMethods) {
const result = isHttpMethod(method);
assertEquals(result, true, `Expected "${method}" to be valid`);
}
});
Deno.test('isHttpMethod: returns false for lowercase or unknown strings', () => {
const invalid = [
'get',
'post',
'FETCH',
'TRACE',
'CONNECT',
'INVALID',
'',
' ',
];
for (const method of invalid) {
const result = isHttpMethod(method);
assertEquals(result, false, `Expected "${method}" to be invalid`);
}
});
Deno.test('isHttpMethod: returns false for non-string inputs', () => {
const invalidInputs = [null, undefined, 123, {}, [], true, Symbol('GET')];
for (const input of invalidInputs) {
const result = isHttpMethod(input);
assertEquals(
result,
false,
`Expected non-string input to be invalid: ${String(input)}`,
);
}
});

View File

@@ -0,0 +1,35 @@
// src/Types/__tests__/HttpStatusCode.test.ts
import { assertEquals } from 'https://deno.land/std@0.204.0/assert/mod.ts';
import { isHttpStatusCode, validHttpStatusCodes } from '../HttpStatusCode.ts';
Deno.test('isHttpStatusCode: returns true for all valid status codes', () => {
for (const code of validHttpStatusCodes) {
assertEquals(
isHttpStatusCode(code),
true,
`Expected ${code} to be valid`,
);
}
});
Deno.test('isHttpStatusCode: returns false for invalid status codes', () => {
const invalidInputs = [99, 600, 1234, -1, 0, 999];
for (const val of invalidInputs) {
assertEquals(
isHttpStatusCode(val),
false,
`Expected ${val} to be invalid`,
);
}
});
Deno.test('isHttpStatusCode: returns false for non-numeric values', () => {
const invalid = ['200', null, undefined, {}, [], true];
for (const val of invalid) {
assertEquals(
isHttpStatusCode(val),
false,
`Expected ${val} to be invalid`,
);
}
});

45
v0.2.1/src/Types/mod.ts Normal file
View File

@@ -0,0 +1,45 @@
// deno-coverage-ignore-file
export type { DeepPartial } from './DeepPartial.ts';
export { isHandler } from './Handler.ts';
export type { Handler } from './Handler.ts';
export type { HttpErrorHandler } from './HttpErrorHandler.ts';
export { isHttpMethod, validHttpMethods } from './HttpMethod.ts';
export type { HttpMethod } from './HttpMethod.ts';
export {
HTTP_100_CONTINUE,
HTTP_101_SWITCHING_PROTOCOLS,
HTTP_102_PROCESSING,
HTTP_200_OK,
HTTP_201_CREATED,
HTTP_202_ACCEPTED,
HTTP_204_NO_CONTENT,
HTTP_301_MOVED_PERMANENTLY,
HTTP_302_FOUND,
HTTP_304_NOT_MODIFIED,
HTTP_400_BAD_REQUEST,
HTTP_401_UNAUTHORIZED,
HTTP_403_FORBIDDEN,
HTTP_404_NOT_FOUND,
HTTP_405_METHOD_NOT_ALLOWED,
HTTP_409_CONFLICT,
HTTP_422_UNPROCESSABLE_ENTITY,
HTTP_429_TOO_MANY_REQUESTS,
HTTP_500_INTERNAL_SERVER_ERROR,
HTTP_501_NOT_IMPLEMENTED,
HTTP_502_BAD_GATEWAY,
HTTP_503_SERVICE_UNAVAILABLE,
HTTP_504_GATEWAY_TIMEOUT,
HttpStatusTextMap,
isHttpStatusCode,
validHttpErrorCodes,
validHttpStatusCodes,
} from './HttpStatusCode.ts';
export type { HttpStatusCode } from './HttpStatusCode.ts';
export { isMiddleware } from './Middleware.ts';
export type { Middleware } from './Middleware.ts';
export type { Params } from './Params.ts';
export type { Query } from './Query.ts';
export type { RegisterRoute } from './RegisterRoute.ts';
export type { ResponseDecorator } from './ResponseDecorator.ts';
export type { State } from './State.ts';

View File

@@ -0,0 +1,28 @@
import { assertEquals } from 'https://deno.land/std/assert/mod.ts';
import { createEmptyContext } from '../createEmptyContext.ts';
import type { IContext } from '../../Interfaces/mod.ts';
Deno.test('createEmptyContext: returns default-initialized context', () => {
const request = new Request('http://localhost');
const ctx = createEmptyContext(request);
assertEquals(ctx.req, request);
assertEquals(ctx.params, {});
assertEquals(ctx.query, {});
assertEquals(ctx.state, {});
});
Deno.test('createEmptyContext: preserves generic type compatibility', () => {
interface MyContext
extends
IContext<{ userId: string }, { id: string }, { verbose: string }> {}
const req = new Request('http://localhost');
const ctx = createEmptyContext<MyContext>(req);
// All properties exist and are empty
assertEquals(ctx.params, {} as MyContext['params']);
assertEquals(ctx.query, {} as MyContext['query']);
assertEquals(ctx.state, {} as MyContext['state']);
assertEquals(ctx.req, req);
});

View File

@@ -0,0 +1,118 @@
import {
assert,
assertEquals,
assertStrictEquals,
} from 'https://deno.land/std/assert/mod.ts';
import type { IRouteDefinition } from '../../Interfaces/mod.ts';
import { createRouteMatcher } from '../../mod.ts';
// Dummy request
const dummyRequest = new Request('http://localhost');
Deno.test('createRouteMatcher: static route matches and extracts params', () => {
const def: IRouteDefinition = { method: 'GET', path: '/users/:id' };
const matcher = createRouteMatcher(def);
const result = matcher(new URL('http://localhost/users/42'), dummyRequest);
assert(result);
assertEquals(result.params, { id: '42' });
});
Deno.test('createRouteMatcher: static route with multiple params', () => {
const def: IRouteDefinition = { method: 'GET', path: '/repo/:owner/:name' };
const matcher = createRouteMatcher(def);
const result = matcher(
new URL('http://localhost/repo/max/wiki'),
dummyRequest,
);
assert(result);
assertEquals(result.params, { owner: 'max', name: 'wiki' });
});
Deno.test('createRouteMatcher: static route does not match wrong path', () => {
const def: IRouteDefinition = { method: 'GET', path: '/users/:id' };
const matcher = createRouteMatcher(def);
const result = matcher(new URL('http://localhost/posts/42'), dummyRequest);
assertStrictEquals(result, null);
});
Deno.test('createRouteMatcher: uses custom matcher if provided', () => {
const def: IRouteDefinition = {
method: 'GET',
matcher: (url) => url.pathname === '/ping' ? { params: {} } : null,
};
const matcher = createRouteMatcher(def);
const result = matcher(new URL('http://localhost/ping'), dummyRequest);
assert(result);
assertEquals(result.params, {});
});
Deno.test('createRouteMatcher: extracts single query param', () => {
const def: IRouteDefinition = { method: 'GET', path: '/search' };
const matcher = createRouteMatcher(def);
const url = new URL('http://localhost/search?q=deno');
const result = matcher(url, dummyRequest);
assert(result);
assertEquals(result.params, {}); // no path params
assertEquals(result.query, { q: 'deno' }); // single key → string
});
Deno.test('createRouteMatcher: duplicate query keys become array', () => {
const def: IRouteDefinition = { method: 'GET', path: '/tags' };
const matcher = createRouteMatcher(def);
const url = new URL('http://localhost/tags?tag=js&tag=ts&tag=deno');
const result = matcher(url, dummyRequest);
assert(result);
assertEquals(result.params, {});
assertEquals(result.query, { tag: ['js', 'ts', 'deno'] }); // multi → string[]
});
Deno.test('createRouteMatcher: mix of single and duplicate keys', () => {
const def: IRouteDefinition = { method: 'GET', path: '/filter/:type' };
const matcher = createRouteMatcher(def);
const url = new URL('http://localhost/filter/repo?lang=ts&lang=js&page=2');
const result = matcher(url, dummyRequest);
assert(result);
assertEquals(result.params, { type: 'repo' });
assertEquals(result.query, {
lang: ['ts', 'js'], // duplicated
page: '2', // single
});
});
Deno.test('createRouteMatcher: no query parameters returns empty object', () => {
const def: IRouteDefinition = { method: 'GET', path: '/info' };
const matcher = createRouteMatcher(def);
const url = new URL('http://localhost/info');
const result = matcher(url, dummyRequest);
assert(result);
assertEquals(result.params, {});
assertEquals(result.query, {}); // empty
});
Deno.test('createRouteMatcher: retains array order of duplicate keys', () => {
const def: IRouteDefinition = { method: 'GET', path: '/order' };
const matcher = createRouteMatcher(def);
const url = new URL(
'http://localhost/order?item=first&item=second&item=third',
);
const result = matcher(url, dummyRequest);
assert(result);
assertEquals(result.query?.item, ['first', 'second', 'third']);
});

View File

@@ -0,0 +1,35 @@
import {
assertEquals,
assertInstanceOf,
} from 'https://deno.land/std/assert/mod.ts';
import { normalizeError } from '../normalizeError.ts';
Deno.test('normalizeError: preserves Error instances', () => {
const original = new Error('original');
const result = normalizeError(original);
assertInstanceOf(result, Error);
assertEquals(result, original);
});
Deno.test('normalizeError: converts string to Error', () => {
const result = normalizeError('something went wrong');
assertInstanceOf(result, Error);
assertEquals(result.message, 'something went wrong');
});
Deno.test('normalizeError: converts number to Error', () => {
const result = normalizeError(404);
assertInstanceOf(result, Error);
assertEquals(result.message, '404');
});
Deno.test('normalizeError: converts plain object to Error', () => {
const input = { error: true, msg: 'Invalid' };
const result = normalizeError(input);
assertInstanceOf(result, Error);
assertEquals(result.message, JSON.stringify(input));
});

View File

@@ -0,0 +1,30 @@
import type { IContext } from '../Interfaces/mod.ts';
import type { Params, Query, State } from '../Types/mod.ts';
/**
* Creates an empty request context suitable for fallback handlers (e.g., 404 or 500 errors).
*
* This function is primarily intended for cases where no route matched, but a context-compatible
* object is still needed to invoke a generic error handler. All context fields are initialized
* to their default empty values (`{}` for params, query, and state).
*
* @template TContext - The expected context type, typically extending `IContext`.
* @param req - The original HTTP request object from `Deno.serve()`.
* @returns A minimal context object compatible with `TContext`.
*
* @example
* ```ts
* const ctx = createEmptyContext<MyContext>(request);
* return httpErrorHandlers[404](ctx);
* ```
*/
export function createEmptyContext<TContext extends IContext = IContext>(
req: Request,
): TContext {
return {
req,
params: {} as Params,
query: {} as Query,
state: {} as State,
} as TContext;
}

View File

@@ -0,0 +1,54 @@
// createRouteMatcher.ts
import {
type IRouteDefinition,
type IRouteMatch,
type IRouteMatcher,
isDynamicRouteDefinition,
} from '../Interfaces/mod.ts';
import type { Params, Query } from '../Types/mod.ts';
/**
* Transforms a route definition into a matcher using Deno's URLPattern API.
*
* @param def - Static path pattern or custom matcher.
* @returns IRouteMatcher that returns `{ params, query }` or `null`.
*/
export function createRouteMatcher(
def: IRouteDefinition,
): IRouteMatcher {
// 1. Allow users to provide their own matcher
if (isDynamicRouteDefinition(def)) {
return def.matcher;
}
// 2. Build URLPattern; supports :id, *wildcards, regex groups, etc.
const pattern = new URLPattern({ pathname: def.path });
// 3. The actual matcher closure
return (url: URL): IRouteMatch | null => {
const result = pattern.exec(url);
// 3a. Path did not match
if (!result) return null;
// 3b. Extract route params
const params: Params = {};
for (const [key, value] of Object.entries(result.pathname.groups)) {
if (value) {
params[key] = value;
}
}
// 3c. Extract query parameters – keep duplicates as arrays
const query: Query = {};
for (const key of url.searchParams.keys()) {
const values = url.searchParams.getAll(key); // → string[]
query[key] = values.length === 1
? values[0] // single → "foo"
: values; // multi → ["foo","bar"]
}
return { params, query };
};
}

5
v0.2.1/src/Utils/mod.ts Normal file
View File

@@ -0,0 +1,5 @@
// deno-coverage-ignore-file
export { createEmptyContext } from './createEmptyContext.ts';
export { createRouteMatcher } from './createRouteMatcher.ts';
export { normalizeError } from './normalizeError.ts';

View File

@@ -0,0 +1,30 @@
/**
* Normalizes any thrown value to a proper `Error` instance.
*
* This is useful when handling unknown thrown values that may be:
* - strings (e.g. `throw "oops"`)
* - numbers (e.g. `throw 404`)
* - objects that are not instances of `Error`
*
* Ensures that downstream error handling logic always receives a consistent `Error` object.
*
* @param unknownError - Any value that might have been thrown.
* @returns A valid `Error` instance wrapping the original input.
*
* @example
* ```ts
* try {
* throw "something went wrong";
* } catch (e) {
* const err = normalizeError(e);
* console.error(err.message); // "something went wrong"
* }
* ```
*/
export function normalizeError(unknownError: unknown): Error {
return unknownError instanceof Error ? unknownError : new Error(
typeof unknownError === 'string'
? unknownError
: JSON.stringify(unknownError),
);
}

View File

@@ -0,0 +1,87 @@
import type { IRouteDefinition } from '../Interfaces/mod.ts';
import { HttpKernel } from '../mod.ts';
const CONCURRENT_REQUESTS = 10000;
// Deno.bench('Simple request', async (b) => {
// const kernel = new HttpKernel();
// const def: IRouteDefinition = { method: 'GET', path: '/hello' };
// kernel.route(def).handle((_ctx) => {
// return Promise.resolve(new Response('OK', { status: 200 }));
// });
// b.start();
// await kernel.handle(
// new Request('http://localhost/hello', { method: 'GET' }),
// );
// b.end();
// });
Deno.bench('Simple request (parallel)', async (b) => {
const kernel = new HttpKernel();
const def: IRouteDefinition = { method: 'GET', path: '/hello' };
kernel.route(def).handle((_ctx) => {
return Promise.resolve(new Response('OK', { status: 200 }));
});
const requests = Array.from(
{ length: CONCURRENT_REQUESTS },
() =>
kernel.handle(
new Request('http://localhost/hello', { method: 'GET' }),
),
);
b.start();
await Promise.all(requests);
b.end();
});
// Deno.bench('Complex request', async (b) => {
// const kernel = new HttpKernel();
// kernel.route({ method: 'GET', path: '/test' })
// .middleware(async (_ctx, next) => {
// return await next();
// })
// .middleware(async (_ctx, next) => {
// return await next();
// })
// .handle((_ctx) => {
// return Promise.resolve(new Response('done'));
// });
// b.start();
// await kernel.handle(
// new Request('http://localhost/test', { method: 'GET' }),
// );
// b.end();
// });
Deno.bench('Complex request (parallel)', async (b) => {
const kernel = new HttpKernel();
kernel.route({ method: 'GET', path: '/test' })
.middleware(async (_ctx, next) => {
return await next();
})
.middleware(async (_ctx, next) => {
return await next();
})
.handle((_ctx) => {
return Promise.resolve(new Response('done'));
});
const requests = Array.from(
{ length: CONCURRENT_REQUESTS },
() =>
kernel.handle(
new Request('http://localhost/test', { method: 'GET' }),
),
);
b.start();
await Promise.all(requests);
b.end();
});

View File

@@ -0,0 +1,185 @@
import {
assertEquals,
assertThrows,
} from 'https://deno.land/std@0.204.0/assert/mod.ts';
import { HttpKernel } from '../HttpKernel.ts';
import type { IRouteDefinition } from '../Interfaces/mod.ts';
Deno.test('HttpKernel: matches static route and executes handler', async () => {
const kernel = new HttpKernel();
const def: IRouteDefinition = { method: 'GET', path: '/hello' };
let called = false;
kernel.route(def).handle((_ctx) => {
called = true;
return Promise.resolve(new Response('OK', { status: 200 }));
});
const res = await kernel.handle(
new Request('http://localhost/hello', { method: 'GET' }),
);
assertEquals(res.status, 200);
assertEquals(await res.text(), 'OK');
assertEquals(called, true);
});
Deno.test('HttpKernel: supports dynamic matcher', async () => {
const kernel = new HttpKernel();
const def: IRouteDefinition = {
method: 'GET',
matcher: (url) => url.pathname === '/dyn' ? { params: {} } : null,
};
kernel.route(def).handle((_ctx) =>
Promise.resolve(new Response('Dyn', { status: 200 }))
);
const res = await kernel.handle(new Request('http://localhost/dyn'));
assertEquals(res.status, 200);
assertEquals(await res.text(), 'Dyn');
});
Deno.test('HttpKernel: calls middleware in order and passes to handler', async () => {
const kernel = new HttpKernel();
const calls: string[] = [];
kernel.route({ method: 'GET', path: '/test' })
.middleware(async (_ctx, next) => {
calls.push('mw1');
return await next();
})
.middleware(async (_ctx, next) => {
calls.push('mw2');
return await next();
})
.handle((_ctx) => {
calls.push('handler');
return Promise.resolve(new Response('done'));
});
const res = await kernel.handle(
new Request('http://localhost/test', { method: 'GET' }),
);
assertEquals(await res.text(), 'done');
assertEquals(calls, ['mw1', 'mw2', 'handler']);
});
Deno.test('HttpKernel: middleware short-circuits pipeline', async () => {
const kernel = new HttpKernel();
const calls: string[] = [];
kernel.route({ method: 'GET', path: '/stop' })
.middleware((_ctx, _next) => {
calls.push('mw1');
return Promise.resolve(new Response('blocked', { status: 403 }));
})
.middleware((_ctx, _next) => {
calls.push('mw2');
return Promise.resolve(new Response('should-not-call'));
})
.handle((_ctx) => {
calls.push('handler');
return Promise.resolve(new Response('ok'));
});
const res = await kernel.handle(
new Request('http://localhost/stop', { method: 'GET' }),
);
assertEquals(res.status, 403);
assertEquals(await res.text(), 'blocked');
assertEquals(calls, ['mw1']);
});
Deno.test('HttpKernel: invalid middleware or handler signature throws at compile time', () => {
const kernel = new HttpKernel();
// Middleware with wrong signature (missing ctx, next)
assertThrows(
() => {
kernel.route({ method: 'GET', path: '/bad-mw' })
// @ts-expect-error invalid middleware
.middleware(() => new Response('invalid'))
.handle((_ctx) => Promise.resolve(new Response('ok')));
},
TypeError,
'Middleware at index 0 is not a valid function.',
);
// Handler with wrong signature (no ctx)
assertThrows(
() => {
kernel.route({ method: 'GET', path: '/bad-handler' })
.middleware(async (_ctx, next) => await next())
// @ts-expect-error invalid handler
.handle(() => new Response('invalid'));
},
TypeError,
'Route handler must be a function returning a Promise<Response>.',
);
});
Deno.test('HttpKernel: 404 for unmatched route', async () => {
const kernel = new HttpKernel();
const res = await kernel.handle(new Request('http://localhost/nothing'));
assertEquals(res.status, 404);
});
Deno.test('HttpKernel: skips route with wrong method', async () => {
const kernel = new HttpKernel();
kernel.route({ method: 'POST', path: '/only-post' })
.handle((_ctx) => Promise.resolve(new Response('nope')));
const res = await kernel.handle(
new Request('http://localhost/only-post', { method: 'GET' }),
);
assertEquals(res.status, 404);
});
Deno.test('HttpKernel: throws on next() called twice', async () => {
const kernel = new HttpKernel();
kernel.route({ method: 'GET', path: '/bad' })
.middleware(async (_ctx, next) => {
await next();
await next(); // ❌
return new Response('should never reach');
})
.handle((_ctx) => Promise.resolve(new Response('OK')));
const res = await kernel.handle(new Request('http://localhost/bad'));
assertEquals(res.status, 500);
assertEquals(await res.text(), 'Internal Server Error');
});
Deno.test('HttpKernel: handler throws → error propagates', async () => {
const kernel = new HttpKernel();
kernel.route({ method: 'GET', path: '/throw' })
.handle((_ctx) => {
throw new Error('fail!');
});
const res = await kernel.handle(new Request('http://localhost/throw'));
assertEquals(res.status, 500);
assertEquals(await res.text(), 'Internal Server Error');
});
Deno.test('HttpKernel: returns 500 if no handler or middleware defined', async () => {
const kernel = new HttpKernel();
// Force-manual Registrierung mit `handler: undefined`
// Umgehen des Builders zur Simulation dieses Edge-Cases
kernel['routes'].push({
method: 'GET',
matcher: (url) => url.pathname === '/fail' ? { params: {} } : null,
middlewares: [],
// @ts-expect-error absichtlich ungültiger Handler
handler: undefined,
});
const res = await kernel.handle(new Request('http://localhost/fail'));
assertEquals(res.status, 500);
assertEquals(await res.text(), 'Internal Server Error');
});

View File

@@ -0,0 +1,140 @@
import {
assert,
assertEquals,
assertNotEquals,
assertThrows,
} from 'https://deno.land/std@0.204.0/assert/mod.ts';
import type { IInternalRoute, IRouteDefinition } from '../Interfaces/mod.ts';
import { RouteBuilder } from '../mod.ts';
import type { Handler, Middleware } from '../Types/mod.ts';
// Dummy objects
// deno-lint-ignore require-await
const dummyHandler: Handler = async (_) => new Response('ok');
// deno-lint-ignore require-await
const wrongHandler: Handler = async () => new Response('ok'); // Wrong signature, no ctx
const dummyMiddleware: Middleware = async (_, next) => await next();
// deno-lint-ignore require-await
const wrongMiddleware: Middleware = async () => new Response('ok'); // Wrong signature, no ctx, next
const dummyDef: IRouteDefinition = { method: 'GET', path: '/hello' };
const dummyMatcher = () => ({ params: {} });
Deno.test('middleware: throws if middleware signature is wrong', () => {
const builder = new RouteBuilder(() => {}, dummyDef);
assertThrows(
() => builder.middleware(wrongMiddleware).handle(dummyHandler),
TypeError,
'Middleware at index 0 is not a valid function.',
);
});
Deno.test('middleware: single middleware is registered correctly', () => {
let registered: IInternalRoute | null = null as IInternalRoute | null;
const builder = new RouteBuilder((r) => registered = r, dummyDef)
.middleware(dummyMiddleware);
builder.handle(dummyHandler);
assert(registered);
assertEquals(registered?.middlewares.length, 1);
assertEquals(registered?.middlewares[0], dummyMiddleware);
});
Deno.test('middleware: middleware is chained immutably', () => {
const builder1 = new RouteBuilder(() => {}, dummyDef);
const builder2 = builder1.middleware(dummyMiddleware);
assertNotEquals(builder1, builder2);
});
Deno.test('middleware: preserves order of middleware', () => {
const mw1: Middleware = async (_, next) => await next();
const mw2: Middleware = async (_, next) => await next();
let result: IInternalRoute | null = null as IInternalRoute | null;
const builder = new RouteBuilder((r) => result = r, dummyDef)
.middleware(mw1)
.middleware(mw2);
builder.handle(dummyHandler);
assert(result);
assertEquals(result!.middlewares, [mw1, mw2]);
});
Deno.test('handle: throws if handler signature is wrong', () => {
const builder = new RouteBuilder(() => {}, dummyDef);
assertThrows(
() => builder.handle(wrongHandler),
TypeError,
'Route handler must be a function returning a Promise<Response>.',
);
});
Deno.test('handle: uppercases method', () => {
let result: IInternalRoute | null = null as IInternalRoute | null;
new RouteBuilder((r) => result = r, { method: 'POST', path: '/x' })
.handle(dummyHandler);
assertEquals(result?.method, 'POST');
});
Deno.test('handle: works with no middleware', async () => {
let route: IInternalRoute | null = null as IInternalRoute | null;
const builder = new RouteBuilder((r) => route = r, dummyDef);
builder.handle(dummyHandler);
assert(route);
assertEquals(route?.middlewares.length, 0);
const request = new Request('http://localhost');
const res1 = await route?.handler({
req: request,
params: {},
state: {},
query: {},
});
const res2 = await dummyHandler({
req: request,
params: {},
state: {},
query: {},
});
assertEquals(res1?.status, res2?.status);
assertEquals(await res1?.text(), await res2?.text());
});
Deno.test('handle: uses custom matcher factory', () => {
let called = false;
const factory = (_def: IRouteDefinition) => {
called = true;
return dummyMatcher;
};
let route: IInternalRoute | null = null as IInternalRoute | null;
new RouteBuilder((r) => route = r, dummyDef, [], factory).handle(
dummyHandler,
);
assert(called);
assert(route);
assertEquals(route!.matcher, dummyMatcher);
});
Deno.test('handle: throws if matcher factory throws', () => {
const faultyFactory = () => {
throw new Error('matcher fail');
};
const builder = new RouteBuilder(() => {}, dummyDef, [], faultyFactory);
assertThrows(() => builder.handle(dummyHandler), Error, 'matcher fail');
});

16
v0.2.1/src/mod.ts Normal file
View File

@@ -0,0 +1,16 @@
// deno-coverage-ignore-file
export { HttpKernel } from './HttpKernel.ts';
export { RouteBuilder } from './RouteBuilder.ts';
export { createRouteMatcher } from './Utils/createRouteMatcher.ts';
// Errors
export * from './Errors/mod.ts';
// Interfaces
export * from './Interfaces/mod.ts';
// Types
export * from './Types/mod.ts';
// Utils
export * from './Utils/mod.ts';