Compare commits
28 Commits
v0.1.0
...
b83aa330b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
b83aa330b3
|
|||
|
c28eb7f28d
|
|||
| d7460c4b1d | |||
|
6a0f1c774b
|
|||
| 7327ed5c1b | |||
|
b44bb2ddaf
|
|||
| dd60c2cdc7 | |||
|
6399113e12
|
|||
| 813c734ae6 | |||
|
3707242d27
|
|||
| adc1a4276e | |||
|
71ea4247b3
|
|||
| b624415320 | |||
|
a88b4d112f
|
|||
|
4f2b65049f
|
|||
| c9de4669c7 | |||
|
a1ce30627c
|
|||
|
5118a19aea
|
|||
| 0a09c8c324 | |||
| b9d25f23fc | |||
| 92f09e6e60 | |||
|
03115464e0
|
|||
| 11a2273240 | |||
|
1233a0b720
|
|||
| 195619ca99 | |||
|
9d5db4f414
|
|||
|
16c0053964
|
|||
|
04029f87a3
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CHANGELOG.md merge=ours
|
||||||
@@ -122,3 +122,77 @@ git commit -m "chore(version): bump to 1.2.3"
|
|||||||
```
|
```
|
||||||
|
|
||||||
> Nur die ersten beiden erscheinen im Changelog – der dritte wird **automatisch übersprungen**.
|
> Nur die ersten beiden erscheinen im Changelog – der dritte wird **automatisch übersprungen**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧾 Umgang mit `CHANGELOG.md` beim Mergen und Releasen
|
||||||
|
|
||||||
|
Wenn du automatisiert einen Changelog mit `git-cliff` erzeugst, ist `CHANGELOG.md` ein **generiertes Artefakt** – und kein handgepflegter Quelltext.
|
||||||
|
|
||||||
|
Beim Mergen von Feature-Branches in `main` kann es deshalb zu **unnötigen Konflikten** in dieser Datei kommen, obwohl der Inhalt später sowieso neu erzeugt wird.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧼 Umgang mit `CHANGELOG.md` in Feature-Branches
|
||||||
|
|
||||||
|
Wenn du mit **Feature-Branches** arbeitest, wird `CHANGELOG.md` dort oft automatisch erzeugt.
|
||||||
|
Das kann beim späteren Merge in `main` zu **unnötigen Merge-Konflikten** führen.
|
||||||
|
|
||||||
|
### ✅ Empfohlene Vorgehensweise
|
||||||
|
|
||||||
|
**Bevor du den Branch mit `main` zusammenführst** (Merge oder Cherry-Pick):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git rm CHANGELOG.md
|
||||||
|
git commit -m "chore(changelog): remove generated CHANGELOG.md before merge"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
Dadurch:
|
||||||
|
|
||||||
|
* verhinderst du Merge-Konflikte mit `CHANGELOG.md`
|
||||||
|
* wird die Datei bei Feature-Branches nicht mehr automatisch erzeugt
|
||||||
|
* bleibt deine Historie sauber und konfliktfrei
|
||||||
|
|
||||||
|
> 💡 Der Workflow erzeugt `CHANGELOG.md` automatisch **nur**, wenn:
|
||||||
|
>
|
||||||
|
> * die Datei schon vorhanden ist **oder**
|
||||||
|
> * der Branch `main` heißt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧩 Merge-Konflikte verhindern mit `.gitattributes`
|
||||||
|
|
||||||
|
Damit Git bei Konflikten in `CHANGELOG.md` **automatisch deine Version bevorzugt**, kannst du folgende Zeile in die Datei `.gitattributes` aufnehmen:
|
||||||
|
|
||||||
|
```gitattributes
|
||||||
|
CHANGELOG.md merge=ours
|
||||||
|
```
|
||||||
|
|
||||||
|
Das bedeutet:
|
||||||
|
|
||||||
|
* Beim Merge wird die Version aus dem aktuellen Branch (`ours`) behalten
|
||||||
|
* Änderungen aus dem Ziel-Branch (`theirs`) werden verworfen
|
||||||
|
|
||||||
|
### ✅ So verwendest du es richtig:
|
||||||
|
|
||||||
|
1. **Füge die Regel in `main` hinzu**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "CHANGELOG.md merge=ours" >> .gitattributes
|
||||||
|
git add .gitattributes
|
||||||
|
git commit -m "chore(git): prevent merge conflicts in CHANGELOG.md"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Hole sie in deinen Feature-Branch**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout feature/xyz
|
||||||
|
git rebase origin/main
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ab sofort werden Konflikte in `CHANGELOG.md` automatisch aufgelöst** – lokal.
|
||||||
|
|
||||||
|
> ⚠️ Hinweis: Plattformen wie **Gitea, GitHub oder GitLab ignorieren `.gitattributes` beim Merge über die Web-Oberfläche**.
|
||||||
|
> Führe Merges daher **lokal** durch, wenn du Konflikte verhindern willst.
|
||||||
|
|||||||
@@ -1,42 +1,47 @@
|
|||||||
name: Auto Changelog & Release
|
name: Auto Changelog & Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- '**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
detect-version-change:
|
detect-version-change:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version_changed: ${{ steps.check.outputs.version_changed }}
|
version_changed: ${{ steps.set.outputs.version_changed }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Check if VERSION file changed
|
- name: Check if VERSION file changed
|
||||||
id: check
|
if: github.ref == 'refs/heads/main'
|
||||||
run: |
|
run: |
|
||||||
echo "🔍 Vergleich mit github.event.before:"
|
echo "🔍 Vergleich mit github.event.before:"
|
||||||
echo "Before: ${{ github.event.before }}"
|
echo "Before: ${{ github.event.before }}"
|
||||||
echo "After: ${{ github.sha }}"
|
echo "After: ${{ github.sha }}"
|
||||||
|
|
||||||
echo "📄 Changed files between before and after:"
|
echo "📄 Changed files between before and after:"
|
||||||
git diff --name-only ${{ github.event.before }} ${{ github.sha }} || echo "(diff failed)"
|
git diff --name-only ${{ github.event.before }} ${{ github.sha }} || echo "(diff failed)"
|
||||||
|
|
||||||
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -q '^VERSION$'; then
|
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -q '^VERSION$'; then
|
||||||
echo "✅ VERSION file was changed between before and after"
|
echo "✅ VERSION file was changed"
|
||||||
echo "version_changed=true" >> $GITHUB_OUTPUT
|
echo "VERSION_CHANGED=true" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo "ℹ️ VERSION file not changed between before and after"
|
echo "ℹ️ VERSION file not changed"
|
||||||
echo "version_changed=false" >> $GITHUB_OUTPUT
|
echo "VERSION_CHANGED=false" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Set output (always)
|
||||||
|
id: set
|
||||||
|
run: |
|
||||||
|
echo "version_changed=${VERSION_CHANGED:-false}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
changelog-only:
|
changelog-only:
|
||||||
needs: detect-version-change
|
needs: detect-version-change
|
||||||
if: needs.detect-version-change.outputs.version_changed == 'false'
|
if: github.ref != 'refs/heads/main' || needs.detect-version-change.outputs.version_changed == 'false'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -66,22 +71,28 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cargo install git-cliff --version "${{ steps.cliff_version.outputs.version }}" --features gitea
|
cargo install git-cliff --version "${{ steps.cliff_version.outputs.version }}" --features gitea
|
||||||
|
|
||||||
- name: Generate unreleased changelog
|
- name: Generate unreleased changelog (if file exists or on main)
|
||||||
run: git-cliff -c cliff.toml -o CHANGELOG.md
|
run: |
|
||||||
|
if [[ -f CHANGELOG.md || "${GITHUB_REF##refs/heads/}" == "main" ]]; then
|
||||||
|
echo "Generating CHANGELOG.md..."
|
||||||
|
git-cliff -c cliff.toml -o CHANGELOG.md
|
||||||
|
else
|
||||||
|
echo "CHANGELOG.md does not exist and this is not 'main'. Skipping generation."
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Commit updated CHANGELOG.md
|
- name: Commit updated CHANGELOG
|
||||||
run: |
|
run: |
|
||||||
git add CHANGELOG.md
|
git add CHANGELOG.md
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
echo "No changes to commit"
|
echo "No changes to commit"
|
||||||
else
|
else
|
||||||
git commit -m "chore(changelog): update unreleased changelog"
|
git commit -m "chore(changelog): update unreleased changelog"
|
||||||
git push origin main
|
git push origin "${GITHUB_REF##refs/heads/}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: detect-version-change
|
needs: detect-version-change
|
||||||
if: needs.detect-version-change.outputs.version_changed == 'true'
|
if: needs.detect-version-change.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -142,7 +153,7 @@ jobs:
|
|||||||
echo "changelog_body_path=$BODY" >> $GITHUB_OUTPUT
|
echo "changelog_body_path=$BODY" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
|
||||||
- name: Commit updated CHANGELOG.md
|
- name: Commit updated CHANGELOG
|
||||||
run: |
|
run: |
|
||||||
git add CHANGELOG.md
|
git add CHANGELOG.md
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
|
|||||||
22
.gitea/workflows/test.yml
Normal file
22
.gitea/workflows/test.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Test http-kernel
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-test:
|
||||||
|
name: Run Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Deno
|
||||||
|
uses: denoland/setup-deno@v2
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: deno task test
|
||||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -2,6 +2,38 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [unreleased]
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(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
|
||||||
|
|
||||||
|
- *(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))
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(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
|
## [0.1.0] - 2025-05-08
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|||||||
18
LICENSE
Normal file
18
LICENSE
Normal 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
README.md
Normal file
135
README.md
Normal 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,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@0xmax42/http-kernel",
|
"name": "@0xmax42/http-kernel",
|
||||||
"description": "A simple HTTP kernel for Deno",
|
"description": "A simple HTTP kernel for Deno",
|
||||||
|
"exports": {
|
||||||
|
"./mod.ts": "./src/mod.ts"
|
||||||
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
// "start": "deno run --allow-net --allow-env --unstable-kv --allow-read --allow-write --env-file src/main.ts -- --verbose",
|
|
||||||
// "watch": "deno run --watch --allow-net --allow-env --unstable-kv --allow-read --allow-write --env-file src/main.ts -- --verbose",
|
|
||||||
"test": "deno test --allow-net --allow-env --unstable-kv --allow-read --allow-write --coverage **/__tests__/*.test.ts",
|
"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"
|
"test:watch": "deno test --watch --allow-net --allow-env --unstable-kv --allow-read --allow-write **/__tests__/*.test.ts"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {
|
import type {
|
||||||
IContext,
|
IContext,
|
||||||
IHttpKernel,
|
IHttpKernel,
|
||||||
IHttpKernelConfig,
|
IHttpKernelConfig,
|
||||||
@@ -7,14 +7,14 @@ import {
|
|||||||
IRouteDefinition,
|
IRouteDefinition,
|
||||||
} from './Interfaces/mod.ts';
|
} from './Interfaces/mod.ts';
|
||||||
import {
|
import {
|
||||||
DeepPartial,
|
type DeepPartial,
|
||||||
Handler,
|
type Handler,
|
||||||
HTTP_404_NOT_FOUND,
|
HTTP_404_NOT_FOUND,
|
||||||
HTTP_500_INTERNAL_SERVER_ERROR,
|
HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
HttpStatusTextMap,
|
HttpStatusTextMap,
|
||||||
isHandler,
|
isHandler,
|
||||||
isMiddleware,
|
isMiddleware,
|
||||||
Middleware,
|
type Middleware,
|
||||||
} from './Types/mod.ts';
|
} from './Types/mod.ts';
|
||||||
import { RouteBuilder } from './RouteBuilder.ts';
|
import { RouteBuilder } from './RouteBuilder.ts';
|
||||||
import { createEmptyContext, normalizeError } from './Utils/mod.ts';
|
import { createEmptyContext, normalizeError } from './Utils/mod.ts';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Params, Query, State } from '../Types/mod.ts';
|
import type { Params, Query, State } from '../Types/mod.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the complete context for a single HTTP request,
|
* Represents the complete context for a single HTTP request,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IContext } from '../Interfaces/mod.ts';
|
import type { IContext } from '../Interfaces/mod.ts';
|
||||||
import { HttpErrorHandler, validHttpErrorCodes } from '../Types/mod.ts';
|
import type { HttpErrorHandler, validHttpErrorCodes } from '../Types/mod.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapping of HTTP status codes to their corresponding error handlers.
|
* A mapping of HTTP status codes to their corresponding error handlers.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IContext } from './IContext.ts';
|
import type { IContext } from './IContext.ts';
|
||||||
import { IRouteBuilder } from './IRouteBuilder.ts';
|
import type { IRouteBuilder } from './IRouteBuilder.ts';
|
||||||
import { IRouteDefinition } from './IRouteDefinition.ts';
|
import type { IRouteDefinition } from './IRouteDefinition.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `IHttpKernel` interface defines the public API for a type-safe, middleware-driven HTTP dispatching system.
|
* The `IHttpKernel` interface defines the public API for a type-safe, middleware-driven HTTP dispatching system.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ResponseDecorator } from '../Types/mod.ts';
|
import type { ResponseDecorator } from '../Types/mod.ts';
|
||||||
import { IContext } from './IContext.ts';
|
import type { IContext } from './IContext.ts';
|
||||||
import { IHttpErrorHandlers } from './IHttpErrorHandlers.ts';
|
import type { IHttpErrorHandlers } from './IHttpErrorHandlers.ts';
|
||||||
import { IRouteBuilderFactory } from './IRouteBuilder.ts';
|
import type { IRouteBuilderFactory } from './IRouteBuilder.ts';
|
||||||
|
|
||||||
export interface IHttpKernelConfig<TContext extends IContext = IContext> {
|
export interface IHttpKernelConfig<TContext extends IContext = IContext> {
|
||||||
decorateResponse: ResponseDecorator<TContext>;
|
decorateResponse: ResponseDecorator<TContext>;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Handler, HttpMethod, Middleware } from '../Types/mod.ts';
|
import type { Handler, HttpMethod, Middleware } from '../Types/mod.ts';
|
||||||
import { IContext, IRouteMatcher } from './mod.ts';
|
import type { IContext, IRouteMatcher } from './mod.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an internally registered route within the HttpKernel.
|
* Represents an internally registered route within the HttpKernel.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handler, Middleware } from '../Types/mod.ts';
|
import type { Handler, Middleware } from '../Types/mod.ts';
|
||||||
import { IInternalRoute } from './IInternalRoute.ts';
|
import type { IInternalRoute } from './IInternalRoute.ts';
|
||||||
import { IRouteDefinition } from './IRouteDefinition.ts';
|
import type { IRouteDefinition } from './IRouteDefinition.ts';
|
||||||
import { IContext } from './mod.ts';
|
import type { IContext } from './mod.ts';
|
||||||
|
|
||||||
export interface IRouteBuilderFactory<TContext extends IContext = IContext> {
|
export interface IRouteBuilderFactory<TContext extends IContext = IContext> {
|
||||||
new (
|
new (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HttpMethod, isHttpMethod } from '../Types/mod.ts';
|
import { type HttpMethod, isHttpMethod } from '../Types/mod.ts';
|
||||||
import { IRouteMatcher } from './IRouteMatcher.ts';
|
import type { IRouteMatcher } from './IRouteMatcher.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a static route using a path pattern with optional parameters.
|
* Defines a static route using a path pattern with optional parameters.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Params, Query } from '../Types/mod.ts';
|
import type { Params, Query } from '../Types/mod.ts';
|
||||||
|
|
||||||
export interface IRouteMatch {
|
export interface IRouteMatch {
|
||||||
params?: Params;
|
params?: Params;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Params } from '../Types/mod.ts';
|
import type { IRouteDefinition } from './IRouteDefinition.ts';
|
||||||
import { IRouteDefinition } from './IRouteDefinition.ts';
|
import type { IRouteMatch } from './IRouteMatch.ts';
|
||||||
import { IRouteMatch } from './IRouteMatch.ts';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a route matcher function that evaluates whether a route applies to a given request.
|
* Defines a route matcher function that evaluates whether a route applies to a given request.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { assertEquals } from 'https://deno.land/std@0.204.0/assert/mod.ts';
|
import { assertEquals } from 'https://deno.land/std@0.204.0/assert/mod.ts';
|
||||||
import {
|
import {
|
||||||
IRouteDefinition,
|
type IRouteDefinition,
|
||||||
isDynamicRouteDefinition,
|
isDynamicRouteDefinition,
|
||||||
isStaticRouteDefinition,
|
isStaticRouteDefinition,
|
||||||
} from '../IRouteDefinition.ts';
|
} from '../IRouteDefinition.ts';
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts';
|
import type { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts';
|
||||||
import { IContext, IRouteBuilder, IRouteDefinition } from './Interfaces/mod.ts';
|
import type {
|
||||||
import { Handler, Middleware, RegisterRoute } from './Types/mod.ts';
|
IContext,
|
||||||
|
IRouteBuilder,
|
||||||
|
IRouteDefinition,
|
||||||
|
} from './Interfaces/mod.ts';
|
||||||
|
import type { Handler, Middleware, RegisterRoute } from './Types/mod.ts';
|
||||||
import { createRouteMatcher } from './Utils/createRouteMatcher.ts';
|
import { createRouteMatcher } from './Utils/createRouteMatcher.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IContext } from '../Interfaces/mod.ts';
|
import type { IContext } from '../Interfaces/mod.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a final request handler responsible for producing an HTTP response.
|
* Represents a final request handler responsible for producing an HTTP response.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IContext } from '../Interfaces/mod.ts';
|
import type { IContext } from '../Interfaces/mod.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a handler function for errors that occur during the execution
|
* Defines a handler function for errors that occur during the execution
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IContext } from '../Interfaces/IContext.ts';
|
import type { IContext } from '../Interfaces/IContext.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a middleware function in the HTTP request pipeline.
|
* Represents a middleware function in the HTTP request pipeline.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IContext } from '../Interfaces/IContext.ts';
|
import type { IContext } from '../Interfaces/IContext.ts';
|
||||||
import { IInternalRoute } from '../Interfaces/mod.ts';
|
import type { IInternalRoute } from '../Interfaces/mod.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type alias for the internal route registration function used by the `HttpKernel`.
|
* A type alias for the internal route registration function used by the `HttpKernel`.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IContext } from '../Interfaces/mod.ts';
|
import type { IContext } from '../Interfaces/mod.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that modifies or enriches an outgoing HTTP response before it is returned to the client.
|
* A function that modifies or enriches an outgoing HTTP response before it is returned to the client.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { assertEquals } from 'https://deno.land/std/assert/mod.ts';
|
import { assertEquals } from 'https://deno.land/std/assert/mod.ts';
|
||||||
import { createEmptyContext } from '../createEmptyContext.ts';
|
import { createEmptyContext } from '../createEmptyContext.ts';
|
||||||
import { IContext } from '../../Interfaces/mod.ts';
|
import type { IContext } from '../../Interfaces/mod.ts';
|
||||||
|
|
||||||
Deno.test('createEmptyContext: returns default-initialized context', () => {
|
Deno.test('createEmptyContext: returns default-initialized context', () => {
|
||||||
const request = new Request('http://localhost');
|
const request = new Request('http://localhost');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
assertEquals,
|
assertEquals,
|
||||||
assertStrictEquals,
|
assertStrictEquals,
|
||||||
} from 'https://deno.land/std/assert/mod.ts';
|
} from 'https://deno.land/std/assert/mod.ts';
|
||||||
import { IRouteDefinition } from '../../Interfaces/mod.ts';
|
import type { IRouteDefinition } from '../../Interfaces/mod.ts';
|
||||||
import { createRouteMatcher } from '../../mod.ts';
|
import { createRouteMatcher } from '../../mod.ts';
|
||||||
|
|
||||||
// Dummy request
|
// Dummy request
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IContext } from '../Interfaces/mod.ts';
|
import type { IContext } from '../Interfaces/mod.ts';
|
||||||
import { Params, Query, State } from '../Types/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).
|
* Creates an empty request context suitable for fallback handlers (e.g., 404 or 500 errors).
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// createRouteMatcher.ts
|
// createRouteMatcher.ts
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IRouteDefinition,
|
type IRouteDefinition,
|
||||||
IRouteMatch,
|
type IRouteMatch,
|
||||||
IRouteMatcher,
|
type IRouteMatcher,
|
||||||
isDynamicRouteDefinition,
|
isDynamicRouteDefinition,
|
||||||
} from '../Interfaces/mod.ts';
|
} from '../Interfaces/mod.ts';
|
||||||
import { Params, Query } from '../Types/mod.ts';
|
import type { Params, Query } from '../Types/mod.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms a route definition into a matcher using Deno's URLPattern API.
|
* Transforms a route definition into a matcher using Deno's URLPattern API.
|
||||||
|
|||||||
@@ -22,11 +22,9 @@
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function normalizeError(unknownError: unknown): Error {
|
export function normalizeError(unknownError: unknown): Error {
|
||||||
return unknownError instanceof Error
|
return unknownError instanceof Error ? unknownError : new Error(
|
||||||
? unknownError
|
typeof unknownError === 'string'
|
||||||
: new Error(
|
? unknownError
|
||||||
typeof unknownError === 'string'
|
: JSON.stringify(unknownError),
|
||||||
? unknownError
|
);
|
||||||
: JSON.stringify(unknownError),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { assertEquals } from 'https://deno.land/std@0.204.0/assert/mod.ts';
|
import { assertEquals } from 'https://deno.land/std@0.204.0/assert/mod.ts';
|
||||||
import { HttpKernel } from '../HttpKernel.ts';
|
import { HttpKernel } from '../HttpKernel.ts';
|
||||||
import { IRouteDefinition } from '../Interfaces/mod.ts';
|
import type { IRouteDefinition } from '../Interfaces/mod.ts';
|
||||||
|
|
||||||
Deno.test('HttpKernel: matches static route and executes handler', async () => {
|
Deno.test('HttpKernel: matches static route and executes handler', async () => {
|
||||||
const kernel = new HttpKernel();
|
const kernel = new HttpKernel();
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
assertNotEquals,
|
assertNotEquals,
|
||||||
assertThrows,
|
assertThrows,
|
||||||
} from 'https://deno.land/std@0.204.0/assert/mod.ts';
|
} from 'https://deno.land/std@0.204.0/assert/mod.ts';
|
||||||
import { IInternalRoute, IRouteDefinition } from '../Interfaces/mod.ts';
|
import type { IInternalRoute, IRouteDefinition } from '../Interfaces/mod.ts';
|
||||||
import { RouteBuilder } from '../mod.ts';
|
import { RouteBuilder } from '../mod.ts';
|
||||||
import { Handler, Middleware } from '../Types/mod.ts';
|
import type { Handler, Middleware } from '../Types/mod.ts';
|
||||||
|
|
||||||
// Dummy objects
|
// Dummy objects
|
||||||
// deno-lint-ignore require-await
|
// deno-lint-ignore require-await
|
||||||
@@ -91,7 +91,7 @@ Deno.test('handle: works with no middleware', async () => {
|
|||||||
Deno.test('handle: uses custom matcher factory', () => {
|
Deno.test('handle: uses custom matcher factory', () => {
|
||||||
let called = false;
|
let called = false;
|
||||||
|
|
||||||
const factory = (def: IRouteDefinition) => {
|
const factory = (_def: IRouteDefinition) => {
|
||||||
called = true;
|
called = true;
|
||||||
return dummyMatcher;
|
return dummyMatcher;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user