diff --git a/README.md b/README.md
index 75a4341..dd3532f 100644
--- a/README.md
+++ b/README.md
@@ -182,6 +182,8 @@ export default defineNitroConfig({
})
```
+> **Note**: For early Nitro v3 support, use `evlog/nitro/v3` instead of `evlog/nitro`.
+
Same API, same wide events:
```typescript
diff --git a/apps/docs/content/1.getting-started/1.introduction.md b/apps/docs/content/1.getting-started/1.introduction.md
index 30c268d..b075a61 100644
--- a/apps/docs/content/1.getting-started/1.introduction.md
+++ b/apps/docs/content/1.getting-started/1.introduction.md
@@ -97,6 +97,8 @@ Structured errors provide actionable context:
::code-group
```typescript [Code]
// server/api/checkout.post.ts
+import { createError } from 'evlog' // Use 'evlog/nitro/v3' for Nitro v3
+
throw createError({
message: 'Payment failed',
status: 402,
diff --git a/apps/docs/content/1.getting-started/2.installation.md b/apps/docs/content/1.getting-started/2.installation.md
index 27609d0..63718da 100644
--- a/apps/docs/content/1.getting-started/2.installation.md
+++ b/apps/docs/content/1.getting-started/2.installation.md
@@ -215,6 +215,10 @@ export default defineNitroConfig({
})
```
+::callout{icon="i-lucide-info" color="info"}
+For early Nitro v3 support, use `evlog/nitro/v3` instead of `evlog/nitro`. When using Nitro v3, import `createError` from `evlog/nitro/v3` instead of `evlog`.
+::
+
## Standalone TypeScript
Install evlog via your preferred package manager:
diff --git a/apps/docs/content/1.getting-started/3.quick-start.md b/apps/docs/content/1.getting-started/3.quick-start.md
index fff728a..321715f 100644
--- a/apps/docs/content/1.getting-started/3.quick-start.md
+++ b/apps/docs/content/1.getting-started/3.quick-start.md
@@ -55,10 +55,14 @@ The logger automatically emits when the request ends. No manual `emit()` call ne
Use `createError()` to throw errors with actionable context:
+::callout{icon="i-lucide-info" color="info"}
+For Nitro v3, import from `evlog/nitro/v3` instead of `evlog`.
+::
+
::code-group
```typescript [Code]
// server/api/checkout.post.ts
-import { createError } from 'evlog'
+import { createError } from 'evlog' // Use 'evlog/nitro/v3' for Nitro v3
throw createError({
message: 'Payment failed',
diff --git a/apps/docs/content/2.core-concepts/2.structured-errors.md b/apps/docs/content/2.core-concepts/2.structured-errors.md
index 33812a4..4cd0e17 100644
--- a/apps/docs/content/2.core-concepts/2.structured-errors.md
+++ b/apps/docs/content/2.core-concepts/2.structured-errors.md
@@ -5,6 +5,10 @@ description: Create errors that explain why they occurred and how to fix them.
evlog provides a `createError()` function that creates errors with rich, actionable context.
+::callout{icon="i-lucide-info" color="info"}
+For Nitro v3, import `createError` from `evlog/nitro/v3` instead of `evlog`.
+::
+
## Why Structured Errors?
Traditional errors are often unhelpful:
@@ -60,7 +64,7 @@ throw createError({
::code-group
```typescript [Code]
// server/api/users/[id].get.ts
-import { createError } from 'evlog'
+import { createError } from 'evlog' // Use 'evlog/nitro/v3' for Nitro v3
throw createError({
message: 'User not found',
diff --git a/apps/nitro-v3-playground/.gitignore b/apps/nitro-v3-playground/.gitignore
new file mode 100644
index 0000000..d547d8a
--- /dev/null
+++ b/apps/nitro-v3-playground/.gitignore
@@ -0,0 +1,8 @@
+node_modules
+dist
+.data
+.nitro
+.cache
+.output
+.env
+.env.local
diff --git a/apps/nitro-v3-playground/README.md b/apps/nitro-v3-playground/README.md
new file mode 100644
index 0000000..ce15a30
--- /dev/null
+++ b/apps/nitro-v3-playground/README.md
@@ -0,0 +1,18 @@
+# Nitro starter
+
+Create your API and deploy it anywhere with this Nitro starter.
+
+## Getting started
+
+```bash
+npm install
+npm run dev
+```
+
+## Deploying
+
+```bash
+npm run build
+```
+
+Then checkout the [Nitro documentation](https://v3.nitro.build/deploy) to learn more about the different deployment presets.
diff --git a/apps/nitro-v3-playground/bun.lock b/apps/nitro-v3-playground/bun.lock
new file mode 100644
index 0000000..a6c7fe5
--- /dev/null
+++ b/apps/nitro-v3-playground/bun.lock
@@ -0,0 +1,171 @@
+{
+ "lockfileVersion": 1,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "devDependencies": {
+ "nitro": "latest",
+ "rolldown": "latest",
+ },
+ },
+ },
+ "packages": {
+ "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
+
+ "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
+
+ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
+
+ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
+
+ "@oxc-minify/binding-android-arm-eabi": ["@oxc-minify/binding-android-arm-eabi@0.110.0", "", { "os": "android", "cpu": "arm" }, "sha512-43fMTO8/5bMlqfOiNSZNKUzIqeLIYuB9Hr1Ohyf58B1wU11S2dPGibTXOGNaWsfgHy99eeZ1bSgeIHy/fEYqbw=="],
+
+ "@oxc-minify/binding-android-arm64": ["@oxc-minify/binding-android-arm64@0.110.0", "", { "os": "android", "cpu": "arm64" }, "sha512-5oQrnn9eK/ccOp80PTrNj0Vq893NPNNRryjGpOIVsYNgWFuoGCfpnKg68oEFcN8bArizYAqw4nvgHljEnar69w=="],
+
+ "@oxc-minify/binding-darwin-arm64": ["@oxc-minify/binding-darwin-arm64@0.110.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dqBDgTG9tF2z2lrZp9E8wU+Godz1i8gCGSei2eFKS2hRploBOD5dmOLp1j4IMornkPvSQmbwB3uSjPq7fjx4EA=="],
+
+ "@oxc-minify/binding-darwin-x64": ["@oxc-minify/binding-darwin-x64@0.110.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U0AqabqaooDOpYmeeOye8wClv8PSScELXgOfYqyqgrwH9J9KrpCE1jL8Rlqgz68QbL4mPw3V6sKiiHssI4CLeQ=="],
+
+ "@oxc-minify/binding-freebsd-x64": ["@oxc-minify/binding-freebsd-x64@0.110.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-H0w8o/Wo1072WSdLfhwwrpFpwZnPpjQODlHuRYkTfsSSSJbTxQtjJd4uxk7YJsRv5RQp69y0I7zvdH6f8Xueyw=="],
+
+ "@oxc-minify/binding-linux-arm-gnueabihf": ["@oxc-minify/binding-linux-arm-gnueabihf@0.110.0", "", { "os": "linux", "cpu": "arm" }, "sha512-qd6sW0AvEVYZhbVVMGtmKZw3b1zDYGIW+54Uh42moWRAj6i4Jhk/LGr6r9YNZpOINeuvZfkFuEeDD/jbu7xPUA=="],
+
+ "@oxc-minify/binding-linux-arm-musleabihf": ["@oxc-minify/binding-linux-arm-musleabihf@0.110.0", "", { "os": "linux", "cpu": "arm" }, "sha512-7WXP0aXMrWSn0ScppUBi3jf68ebfBG0eri8kxLmBOVSBj6jw1repzkHMITJMBeLr5d0tT/51qFEptiAk2EP2iA=="],
+
+ "@oxc-minify/binding-linux-arm64-gnu": ["@oxc-minify/binding-linux-arm64-gnu@0.110.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-LYfADrq5x1W5gs+u9OIbMbDQNYkAECTXX0ufnAuf3oGmO51rF98kGFR5qJqC/6/csokDyT3wwTpxhE0TkcF/Og=="],
+
+ "@oxc-minify/binding-linux-arm64-musl": ["@oxc-minify/binding-linux-arm64-musl@0.110.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-53GjCVY8kvymk9P6qNDh6zyblcehF5QHstq9QgCjv13ONGRnSHjeds0PxIwiihD7h295bxsWs84DN39syLPH4Q=="],
+
+ "@oxc-minify/binding-linux-ppc64-gnu": ["@oxc-minify/binding-linux-ppc64-gnu@0.110.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-li8XcN81dxbJDMBESnTgGhoiAQ+CNIdM0QGscZ4duVPjCry1RpX+5FJySFbGqG3pk4s9ZzlL/vtQtbRzZIZOzg=="],
+
+ "@oxc-minify/binding-linux-riscv64-gnu": ["@oxc-minify/binding-linux-riscv64-gnu@0.110.0", "", { "os": "linux", "cpu": "none" }, "sha512-SweKfsnLKShu6UFV8mwuj1d1wmlNoL/FlAxPUzwjEBgwiT2HQkY24KnjBH+TIA+//1O83kzmWKvvs4OuEhdIEQ=="],
+
+ "@oxc-minify/binding-linux-riscv64-musl": ["@oxc-minify/binding-linux-riscv64-musl@0.110.0", "", { "os": "linux", "cpu": "none" }, "sha512-oH8G4aFMP8XyTsEpdANC5PQyHgSeGlopHZuW1rpyYcaErg5YaK0vXjQ4EM5HVvPm+feBV24JjxgakTnZoF3aOQ=="],
+
+ "@oxc-minify/binding-linux-s390x-gnu": ["@oxc-minify/binding-linux-s390x-gnu@0.110.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-W9na+Vza7XVUlpf8wMt4QBfH35KeTENEmnpPUq3NSlbQHz8lSlSvhAafvo43NcKvHAXV3ckD/mUf2VkqSdbklg=="],
+
+ "@oxc-minify/binding-linux-x64-gnu": ["@oxc-minify/binding-linux-x64-gnu@0.110.0", "", { "os": "linux", "cpu": "x64" }, "sha512-XJdA4mmmXOjJxSRgNJXsDP7Xe8h3gQhmb56hUcCrvq5d+h5UcEi2pR8rxsdIrS8QmkLuBA3eHkGK8E27D7DTgQ=="],
+
+ "@oxc-minify/binding-linux-x64-musl": ["@oxc-minify/binding-linux-x64-musl@0.110.0", "", { "os": "linux", "cpu": "x64" }, "sha512-QqzvALuOTtSckI8x467R4GNArzYDb/yEh6aNzLoeaY1O7vfT7SPDwlOEcchaTznutpeS9Dy8gUS/AfqtUHaufw=="],
+
+ "@oxc-minify/binding-openharmony-arm64": ["@oxc-minify/binding-openharmony-arm64@0.110.0", "", { "os": "none", "cpu": "arm64" }, "sha512-gAMssLs2Q3+uhLZxanh1DF+27Kaug3cf4PXb9AB7XK81DR+LVcKySXaoGYoOs20Co0fFSphd6rRzKge2qDK3dA=="],
+
+ "@oxc-minify/binding-wasm32-wasi": ["@oxc-minify/binding-wasm32-wasi@0.110.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-7Wqi5Zjl022bs2zXq+ICdalDPeDuCH/Nhbi8q2isLihAonMVIT0YH2hqqnNEylRNGYck+FJ6gRZwMpGCgrNxPg=="],
+
+ "@oxc-minify/binding-win32-arm64-msvc": ["@oxc-minify/binding-win32-arm64-msvc@0.110.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZPx+0Tj4dqn41ecyoGotlvekQKy6JxJCixn9Rw7h/dafZ3eDuBcEVh3c2ZoldXXsyMIt5ywI8IWzFZsjNedd5Q=="],
+
+ "@oxc-minify/binding-win32-ia32-msvc": ["@oxc-minify/binding-win32-ia32-msvc@0.110.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-H0Oyd3RWBfpEyvJIrFK94RYiY7KKSQl11Ym7LMDwLEagelIAfRCkt1amHZhFa/S3ZRoaOJFXzEw4YKeSsjVFsg=="],
+
+ "@oxc-minify/binding-win32-x64-msvc": ["@oxc-minify/binding-win32-x64-msvc@0.110.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Hr3nK90+qXKJ2kepXwFIcNfQQIOBecB4FFCyaMMypthoEEhVP08heRynj4eSXZ8NL9hLjs3fQzH8PJXfpznRnQ=="],
+
+ "@oxc-project/types": ["@oxc-project/types@0.110.0", "", {}, "sha512-6Ct21OIlrEnFEJk5LT4e63pk3btsI6/TusD/GStLi7wYlGJNOl1GI9qvXAnRAxQU9zqA2Oz+UwhfTOU2rPZVow=="],
+
+ "@oxc-transform/binding-android-arm-eabi": ["@oxc-transform/binding-android-arm-eabi@0.110.0", "", { "os": "android", "cpu": "arm" }, "sha512-sE9dxvqqAax1YYJ3t7j+h5ZSI9jl6dYuDfngl6ieZUrIy5P89/8JKVgAzgp8o3wQSo7ndpJvYsi1K4ZqrmbP7w=="],
+
+ "@oxc-transform/binding-android-arm64": ["@oxc-transform/binding-android-arm64@0.110.0", "", { "os": "android", "cpu": "arm64" }, "sha512-nqtbP4aMCtsCZ6qpHlHaQoWVHSBtlKzwaAgwEOvR+9DWqHjk31BHvpGiDXlMeed6CVNpl3lCbWgygb3RcSjcfw=="],
+
+ "@oxc-transform/binding-darwin-arm64": ["@oxc-transform/binding-darwin-arm64@0.110.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oeSeHnL4Z4cMXtc8V0/rwoVn0dgwlS9q0j6LcHn9dIhtFEdp3W0iSBF8YmMQA+E7sILeLDjsHmHE4Kp0sOScXw=="],
+
+ "@oxc-transform/binding-darwin-x64": ["@oxc-transform/binding-darwin-x64@0.110.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-nL9K5x7OuZydobAGPylsEW9d4APs2qEkIBLMgQPA+kY8dtVD3IR87QsTbs4l4DBQYyun/+ay6qVCDlxqxdX2Jg=="],
+
+ "@oxc-transform/binding-freebsd-x64": ["@oxc-transform/binding-freebsd-x64@0.110.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-GS29zXXirDQhZEUq8xKJ1azAWMuUy3Ih3W5Bc5ddk12LRthO5wRLFcKIyeHpAXCoXymQ+LmxbMtbPf84GPxouw=="],
+
+ "@oxc-transform/binding-linux-arm-gnueabihf": ["@oxc-transform/binding-linux-arm-gnueabihf@0.110.0", "", { "os": "linux", "cpu": "arm" }, "sha512-glzDHak8ISyZJemCUi7RCvzNSl+MQ1ly9RceT2qRufhUsvNZ4C/2QLJ1HJwd2N6E88bO4laYn+RofdRzNnGGEA=="],
+
+ "@oxc-transform/binding-linux-arm-musleabihf": ["@oxc-transform/binding-linux-arm-musleabihf@0.110.0", "", { "os": "linux", "cpu": "arm" }, "sha512-8JThvgJ2FRoTVfbp7e4wqeZqCZbtudM06SfZmNzND9kPNu/LVYygIR+72RWs+xm4bWkuYHg/islo/boNPtMT5Q=="],
+
+ "@oxc-transform/binding-linux-arm64-gnu": ["@oxc-transform/binding-linux-arm64-gnu@0.110.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-IRh21Ub/g4bkHoErZ0AUWMlWfoZaS0A6EaOVtbcY70RSYIMlrsbjiFwJCzM+b/1DD1rXbH5tsGcH7GweTbfRqg=="],
+
+ "@oxc-transform/binding-linux-arm64-musl": ["@oxc-transform/binding-linux-arm64-musl@0.110.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-e5JN94/oy+wevk76q+LMr+2klTTcO60uXa+Wkq558Ms7mdF2TvkKFI++d/JeiuIwJLTi/BxQ4qdT5FWcsHM/ug=="],
+
+ "@oxc-transform/binding-linux-ppc64-gnu": ["@oxc-transform/binding-linux-ppc64-gnu@0.110.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y3/Tnnz1GvDpmv8FXBIKtdZPsdZklOEPdrL6NHrN5i2u54BOkybFaDSptgWF53wOrJlTrcmAVSE6fRKK9XCM2Q=="],
+
+ "@oxc-transform/binding-linux-riscv64-gnu": ["@oxc-transform/binding-linux-riscv64-gnu@0.110.0", "", { "os": "linux", "cpu": "none" }, "sha512-Y0E35iA9/v9jlkNcP6tMJ+ZFOS0rLsWDqG6rU9z+X2R3fBFJBO9UARIK6ngx8upxk81y1TFR2CmBFhupfYdH6Q=="],
+
+ "@oxc-transform/binding-linux-riscv64-musl": ["@oxc-transform/binding-linux-riscv64-musl@0.110.0", "", { "os": "linux", "cpu": "none" }, "sha512-JOUSYFfHjBUs7xp2FHmZHb8eTYD/oEu0NklS6JgUauqnoXZHiTLPLVW2o2uVCqldnabYHcomuwI2iqVFYJNhTw=="],
+
+ "@oxc-transform/binding-linux-s390x-gnu": ["@oxc-transform/binding-linux-s390x-gnu@0.110.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-7blgoXF9D3Ngzb7eun23pNrHJpoV/TtE6LObwlZ3Nmb4oZ6Z+yMvBVaoW68NarbmvNGfZ95zrOjgm6cVETLYBA=="],
+
+ "@oxc-transform/binding-linux-x64-gnu": ["@oxc-transform/binding-linux-x64-gnu@0.110.0", "", { "os": "linux", "cpu": "x64" }, "sha512-YQ2joGWCVDZVEU2cD/r/w49hVjDm/Qu1BvC/7zs8LvprzdLS/HyMXGF2oA0puw0b+AqgYaz3bhwKB2xexHyITQ=="],
+
+ "@oxc-transform/binding-linux-x64-musl": ["@oxc-transform/binding-linux-x64-musl@0.110.0", "", { "os": "linux", "cpu": "x64" }, "sha512-fkjr5qE632ULmNgvFXWDR/8668WxERz3tU7TQFp6JebPBneColitjSkdx6VKNVXEoMmQnOvBIGeP5tUNT384oA=="],
+
+ "@oxc-transform/binding-openharmony-arm64": ["@oxc-transform/binding-openharmony-arm64@0.110.0", "", { "os": "none", "cpu": "arm64" }, "sha512-HWH9Zj+lMrdSTqFRCZsvDWMz7OnMjbdGsm3xURXWfRZpuaz0bVvyuZNDQXc4FyyhRDsemICaJbU1bgeIpUJDGw=="],
+
+ "@oxc-transform/binding-wasm32-wasi": ["@oxc-transform/binding-wasm32-wasi@0.110.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-ejdxHmYfIcHDPhZUe3WklViLt9mDEJE5BzcW7+R1vc5i/5JFA8D0l7NUSsHBJ7FB8Bu9gF+5iMDm6cXGAgaghw=="],
+
+ "@oxc-transform/binding-win32-arm64-msvc": ["@oxc-transform/binding-win32-arm64-msvc@0.110.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-9VTwpXCZs7xkV+mKhQ62dVk7KLnLXtEUxNS2T4nLz3iMl1IJbA4h5oltK0JoobtiUAnbkV53QmMVGW8+Nh3bDQ=="],
+
+ "@oxc-transform/binding-win32-ia32-msvc": ["@oxc-transform/binding-win32-ia32-msvc@0.110.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-5y0fzuNON7/F2hh2P94vANFaRPJ/3DI1hVl5rseCT8VUVqOGIjWaza0YS/D1g6t1WwycW2LWDMi2raOKoWU5GQ=="],
+
+ "@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.110.0", "", { "os": "win32", "cpu": "x64" }, "sha512-QROrowwlrApI1fEScMknGWKM6GTM/Z2xwMnDqvSaEmzNazBsDUlE08Jasw610hFEsYAVU2K5sp/YaCa9ORdP4A=="],
+
+ "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.1", "", { "os": "android", "cpu": "arm64" }, "sha512-He6ZoCfv5D7dlRbrhNBkuMVIHd0GDnjJwbICE1OWpG7G3S2gmJ+eXkcNLJjzjNDpeI2aRy56ou39AJM9AD8YFA=="],
+
+ "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YzJdn08kSOXnj85ghHauH2iHpOJ6eSmstdRTLyaziDcUxe9SyQJgGyx/5jDIhDvtOcNvMm2Ju7m19+S/Rm1jFg=="],
+
+ "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cIvAbqM+ZVV6lBSKSBtlNqH5iCiW933t1q8j0H66B3sjbe8AxIRetVqfGgcHcJtMzBIkIALlL9fcDrElWLJQcQ=="],
+
+ "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rVt+B1B/qmKwCl1XD02wKfgh3vQPXRXdB/TicV2w6g7RVAM1+cZcpigwhLarqiVCxDObFZ7UgXCxPC7tpDoRog=="],
+
+ "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1", "", { "os": "linux", "cpu": "arm" }, "sha512-69YKwJJBOFprQa1GktPgbuBOfnn+EGxu8sBJ1TjPER+zhSpYeaU4N07uqmyBiksOLGXsMegymuecLobfz03h8Q=="],
+
+ "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-9JDhHUf3WcLfnViFWm+TyorqUtnSAHaCzlSNmMOq824prVuuzDOK91K0Hl8DUcEb9M5x2O+d2/jmBMsetRIn3g=="],
+
+ "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw=="],
+
+ "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.1", "", { "os": "linux", "cpu": "x64" }, "sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A=="],
+
+ "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.1", "", { "os": "linux", "cpu": "x64" }, "sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg=="],
+
+ "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.1", "", { "os": "none", "cpu": "arm64" }, "sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg=="],
+
+ "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-2mOxY562ihHlz9lEXuaGEIDCZ1vI+zyFdtsoa3M62xsEunDXQE+DVPO4S4x5MPK9tKulG/aFcA/IH5eVN257Cw=="],
+
+ "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-oQVOP5cfAWZwRD0Q3nGn/cA9FW3KhMMuQ0NIndALAe6obqjLhqYVYDiGGRGrxvnjJsVbpLwR14gIUYnpIcHR1g=="],
+
+ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Ydsxxx++FNOuov3wCBPaYjZrEvKOOGq3k+BF4BPridhg2pENfitSRD2TEuQ8i33bp5VptuNdC9IzxRKU031z5A=="],
+
+ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.1", "", {}, "sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA=="],
+
+ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
+
+ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
+
+ "crossws": ["crossws@0.4.3", "", { "peerDependencies": { "srvx": ">=0.7.1" }, "optionalPeers": ["srvx"] }, "sha512-lmf5mtwHiToP3HumOx53cqS0T5TK8GMBpsbSCXRB5OuszbltTgGOO4B1WhrDYqTeXOk3BAemibNjJx8E0/ecNw=="],
+
+ "db0": ["db0@0.3.4", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="],
+
+ "h3": ["h3@2.0.1-rc.11", "", { "dependencies": { "rou3": "^0.7.12", "srvx": "^0.10.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-2myzjCqy32c1As9TjZW9fNZXtLqNedjFSrdFy2AjFBQQ3LzrnGoDdFDYfC0tV2e4vcyfJ2Sfo/F6NQhO2Ly/Mw=="],
+
+ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
+ "nf3": ["nf3@0.3.6", "", {}, "sha512-/XRUUILTAyuy1XunyVQuqGp8aEmZ2TfRTn8Rji+FA4xqv20qzL4jV7Reqbuey2XucKgPeRVcEYGScmJM0UnB6Q=="],
+
+ "nitro": ["nitro@3.0.1-alpha.2", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.3", "db0": "^0.3.4", "h3": "^2.0.1-rc.11", "jiti": "^2.6.1", "nf3": "^0.3.5", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.110.0", "oxc-transform": "^0.110.0", "srvx": "^0.10.1", "undici": "^7.18.2", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.5" }, "peerDependencies": { "rolldown": ">=1.0.0-beta.0", "rollup": "^4", "vite": "^7 || ^8 || >=8.0.0-0", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-YviDY5J/trS821qQ1fpJtpXWIdPYiOizC/meHavlm1Hfuhx//H+Egd1+4C5SegJRgtWMnRPW9n//6Woaw81cTQ=="],
+
+ "ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="],
+
+ "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
+
+ "oxc-minify": ["oxc-minify@0.110.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm-eabi": "0.110.0", "@oxc-minify/binding-android-arm64": "0.110.0", "@oxc-minify/binding-darwin-arm64": "0.110.0", "@oxc-minify/binding-darwin-x64": "0.110.0", "@oxc-minify/binding-freebsd-x64": "0.110.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.110.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.110.0", "@oxc-minify/binding-linux-arm64-gnu": "0.110.0", "@oxc-minify/binding-linux-arm64-musl": "0.110.0", "@oxc-minify/binding-linux-ppc64-gnu": "0.110.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.110.0", "@oxc-minify/binding-linux-riscv64-musl": "0.110.0", "@oxc-minify/binding-linux-s390x-gnu": "0.110.0", "@oxc-minify/binding-linux-x64-gnu": "0.110.0", "@oxc-minify/binding-linux-x64-musl": "0.110.0", "@oxc-minify/binding-openharmony-arm64": "0.110.0", "@oxc-minify/binding-wasm32-wasi": "0.110.0", "@oxc-minify/binding-win32-arm64-msvc": "0.110.0", "@oxc-minify/binding-win32-ia32-msvc": "0.110.0", "@oxc-minify/binding-win32-x64-msvc": "0.110.0" } }, "sha512-KWGTzPo83QmGrXC4ml83PM9HDwUPtZFfasiclUvTV4i3/0j7xRRqINVkrL77CbQnoWura3CMxkRofjQKVDuhBw=="],
+
+ "oxc-transform": ["oxc-transform@0.110.0", "", { "optionalDependencies": { "@oxc-transform/binding-android-arm-eabi": "0.110.0", "@oxc-transform/binding-android-arm64": "0.110.0", "@oxc-transform/binding-darwin-arm64": "0.110.0", "@oxc-transform/binding-darwin-x64": "0.110.0", "@oxc-transform/binding-freebsd-x64": "0.110.0", "@oxc-transform/binding-linux-arm-gnueabihf": "0.110.0", "@oxc-transform/binding-linux-arm-musleabihf": "0.110.0", "@oxc-transform/binding-linux-arm64-gnu": "0.110.0", "@oxc-transform/binding-linux-arm64-musl": "0.110.0", "@oxc-transform/binding-linux-ppc64-gnu": "0.110.0", "@oxc-transform/binding-linux-riscv64-gnu": "0.110.0", "@oxc-transform/binding-linux-riscv64-musl": "0.110.0", "@oxc-transform/binding-linux-s390x-gnu": "0.110.0", "@oxc-transform/binding-linux-x64-gnu": "0.110.0", "@oxc-transform/binding-linux-x64-musl": "0.110.0", "@oxc-transform/binding-openharmony-arm64": "0.110.0", "@oxc-transform/binding-wasm32-wasi": "0.110.0", "@oxc-transform/binding-win32-arm64-msvc": "0.110.0", "@oxc-transform/binding-win32-ia32-msvc": "0.110.0", "@oxc-transform/binding-win32-x64-msvc": "0.110.0" } }, "sha512-/fymQNzzUoKZweH0nC5yvbI2eR0yWYusT9TEKDYVgOgYrf9Qmdez9lUFyvxKR9ycx+PTHi/reIOzqf3wkShQsw=="],
+
+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+ "rolldown": ["rolldown@1.0.0-rc.1", "", { "dependencies": { "@oxc-project/types": "=0.110.0", "@rolldown/pluginutils": "1.0.0-rc.1" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.1", "@rolldown/binding-darwin-arm64": "1.0.0-rc.1", "@rolldown/binding-darwin-x64": "1.0.0-rc.1", "@rolldown/binding-freebsd-x64": "1.0.0-rc.1", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.1", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.1", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.1", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.1", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.1", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.1", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.1", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.1", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.1" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-M3AeZjYE6UclblEf531Hch0WfVC/NOL43Cc+WdF3J50kk5/fvouHhDumSGTh0oRjbZ8C4faaVr5r6Nx1xMqDGg=="],
+
+ "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
+
+ "srvx": ["srvx@0.10.1", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-A//xtfak4eESMWWydSRFUVvCTQbSwivnGCEf8YGPe2eHU0+Z6znfUTCPF0a7oV3sObSOcrXHlL6Bs9vVctfXdg=="],
+
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "undici": ["undici@7.19.1", "", {}, "sha512-Gpq0iNm5M6cQWlyHQv9MV+uOj1jWk7LpkoE5vSp/7zjb4zMdAcUD+VL5y0nH4p9EbUklq00eVIIX/XcDHzu5xg=="],
+
+ "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
+
+ "unstorage": ["unstorage@2.0.0-alpha.5", "", { "peerDependencies": { "@azure/app-configuration": "^1.9.0", "@azure/cosmos": "^4.7.0", "@azure/data-tables": "^13.3.1", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.29.1", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.12.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.35.6", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.8.2", "lru-cache": "^11.2.2", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g=="],
+ }
+}
diff --git a/apps/nitro-v3-playground/index.html b/apps/nitro-v3-playground/index.html
new file mode 100644
index 0000000..5485925
--- /dev/null
+++ b/apps/nitro-v3-playground/index.html
@@ -0,0 +1,113 @@
+
+
+
+
+
+ evlog Nitro v3 Playground
+
+
+
+
+
+
+
+ evlog Nitro v3 Playground 🚀
+ Test the evlog integration with different HTTP methods. Check your terminal for wide event logs!
+
+
+
Test API Endpoints
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/nitro-v3-playground/nitro.config.ts b/apps/nitro-v3-playground/nitro.config.ts
new file mode 100644
index 0000000..7e29717
--- /dev/null
+++ b/apps/nitro-v3-playground/nitro.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'nitro'
+
+export default defineConfig({
+ serverDir: './',
+ // TODO: make playground work with evlog/nitro/v3
+ plugins: ['../../packages/evlog/src/nitro-v3/plugin.ts']
+})
diff --git a/apps/nitro-v3-playground/package.json b/apps/nitro-v3-playground/package.json
new file mode 100644
index 0000000..374688f
--- /dev/null
+++ b/apps/nitro-v3-playground/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "evlog-nitro-v3-playground",
+ "type": "module",
+ "scripts": {
+ "build": "nitro build",
+ "dev": "nitro dev",
+ "preview": "npx srvx --prod .output/"
+ },
+ "dependencies": {
+ "evlog": "workspace:*"
+ },
+ "devDependencies": {
+ "nitro": "latest",
+ "rolldown": "latest"
+ }
+}
diff --git a/apps/nitro-v3-playground/public/styles.css b/apps/nitro-v3-playground/public/styles.css
new file mode 100644
index 0000000..61fb186
--- /dev/null
+++ b/apps/nitro-v3-playground/public/styles.css
@@ -0,0 +1,253 @@
+:root {
+ --bg: #020202;
+ --card: #0a0a0a;
+ --text: #fafafa;
+ --muted: #94a3b8;
+ --border: #27272a;
+ --primary: #2853FF;
+ --primary-hover: #3d65ff;
+ --success: #0ea5a4;
+ --warning: #f59e0b;
+ --danger: #ef4444;
+ --purple: #8b5cf6;
+ --code-bg: #000000;
+ --code-text: #e6eef8;
+ --radius: 8px;
+ --section-bg: #0f0f0f;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ height: 100%;
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24px;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ line-height: 1.5;
+}
+
+.card {
+ background: var(--card);
+ padding: 48px;
+ border-radius: 12px;
+ max-width: 920px;
+ width: 100%;
+ max-height: calc(100vh - 48px);
+ overflow-y: auto;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 20px 40px rgba(0, 0, 0, 0.5);
+ border: 1px solid var(--border);
+}
+
+h1 {
+ margin: 0 0 8px 0;
+ font-weight: 700;
+ font-size: 2rem;
+ color: var(--text);
+ letter-spacing: -0.02em;
+ line-height: 1.2;
+}
+
+p {
+ margin: 0 0 24px 0;
+ color: var(--muted);
+ font-size: 1rem;
+}
+
+a {
+ color: var(--primary);
+ text-decoration: none;
+ font-weight: 500;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+code,
+pre {
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
+ font-size: 0.875rem;
+}
+
+pre.api {
+ margin-top: 24px;
+ background: var(--code-bg);
+ color: var(--code-text);
+ padding: 16px 20px;
+ border-radius: var(--radius);
+ overflow: auto;
+ white-space: pre-wrap;
+ word-break: break-word;
+ font-size: 0.8125rem;
+ line-height: 1.6;
+}
+
+.demo-section {
+ margin-top: 40px;
+ padding: 32px;
+ background: var(--section-bg);
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+}
+
+.demo-section h2 {
+ margin: 0 0 24px 0;
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: var(--text);
+ letter-spacing: -0.01em;
+}
+
+.buttons {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 16px;
+ flex-wrap: wrap;
+}
+
+button {
+ padding: 12px 24px;
+ border: none;
+ border-radius: 6px;
+ font-family: inherit;
+ font-size: 0.9375rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
+ flex: 1;
+ min-width: fit-content;
+}
+
+button:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+button:active {
+ transform: translateY(0);
+}
+
+.btn-get {
+ background: var(--success);
+ color: white;
+}
+
+.btn-get:hover {
+ background: #0d9488;
+}
+
+.btn-post {
+ background: var(--purple);
+ color: white;
+}
+
+.btn-post:hover {
+ background: #7c3aed;
+}
+
+.btn-delete {
+ background: var(--danger);
+ color: white;
+}
+
+.btn-delete:hover {
+ background: #dc2626;
+}
+
+.btn-wide {
+ background: var(--primary);
+ color: white;
+ flex: 1 1 100%;
+}
+
+.btn-wide:hover {
+ background: var(--primary-hover);
+}
+
+#result {
+ margin-top: 20px;
+ padding: 20px 24px;
+ background: var(--code-bg);
+ color: var(--code-text);
+ border-radius: var(--radius);
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
+ font-size: 0.8125rem;
+ line-height: 1.7;
+ white-space: pre-wrap;
+ word-break: break-word;
+ min-height: 80px;
+ display: none;
+ border: 1px solid #18181b;
+}
+
+#result.show {
+ display: block;
+ animation: fadeIn 0.2s ease-in;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(4px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.input-group {
+ margin-bottom: 24px;
+}
+
+.input-group label {
+ display: block;
+ margin-bottom: 8px;
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.input-group input {
+ width: 100%;
+ padding: 12px 16px;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ font-family: inherit;
+ font-size: 0.9375rem;
+ background: var(--code-bg);
+ color: var(--text);
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.input-group input:focus {
+ outline: none;
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px rgba(40, 83, 255, 0.1);
+}
+
+.input-group input::placeholder {
+ color: #94a3b8;
+}
+
+/* Subtle separator */
+.separator {
+ height: 1px;
+ background: linear-gradient(to right, transparent, var(--border), transparent);
+ margin: 32px 0;
+}
diff --git a/apps/nitro-v3-playground/routes/api/index.ts b/apps/nitro-v3-playground/routes/api/index.ts
new file mode 100644
index 0000000..4c37660
--- /dev/null
+++ b/apps/nitro-v3-playground/routes/api/index.ts
@@ -0,0 +1,19 @@
+import { useLogger } from 'evlog'
+import { defineHandler } from 'nitro/h3'
+
+export default defineHandler((event) => {
+ const log = useLogger(event)
+ log.set({
+ playground: 'nitro/v3',
+ })
+ return {
+ message: 'evlog Nitro v3 Playground',
+ endpoints: [
+ 'GET /api/test/success',
+ 'POST /api/test/create',
+ 'PUT /api/test/update (always fails)',
+ 'DELETE /api/test/delete',
+ 'GET /api/test/wide-event',
+ ],
+ }
+})
diff --git a/apps/nitro-v3-playground/routes/api/test/create.post.ts b/apps/nitro-v3-playground/routes/api/test/create.post.ts
new file mode 100644
index 0000000..bc3e5c2
--- /dev/null
+++ b/apps/nitro-v3-playground/routes/api/test/create.post.ts
@@ -0,0 +1,29 @@
+import { defineHandler, readBody } from 'nitro/h3'
+import { useLogger } from 'evlog'
+
+export default defineHandler(async (event) => {
+ const log = useLogger(event)
+ const body = await readBody(event) as { name: string; type: string }
+
+ log.set({ user: { id: 456 } })
+ log.set({ item: { name: body.name, type: body.type } })
+
+ // Simulate validation
+ await new Promise(resolve => setTimeout(resolve, 100))
+ log.set({ validation: { passed: true, checks: ['name_length', 'type_valid'] } })
+
+ // Simulate database insert
+ await new Promise(resolve => setTimeout(resolve, 200))
+ const itemId = `item_${Math.random().toString(36).substring(2, 8)}`
+ log.set({ database: { operation: 'insert', duration: '200ms' } })
+
+ // Simulate search indexing
+ await new Promise(resolve => setTimeout(resolve, 80))
+ log.set({ search: { indexed: true, engine: 'elasticsearch' } })
+
+ return {
+ success: true,
+ message: 'Item created',
+ id: itemId,
+ }
+})
diff --git a/apps/nitro-v3-playground/routes/api/test/delete.delete.ts b/apps/nitro-v3-playground/routes/api/test/delete.delete.ts
new file mode 100644
index 0000000..350f187
--- /dev/null
+++ b/apps/nitro-v3-playground/routes/api/test/delete.delete.ts
@@ -0,0 +1,28 @@
+import { defineHandler } from 'nitro/h3'
+import { useLogger } from 'evlog'
+
+export default defineHandler(async (event) => {
+ const log = useLogger(event)
+ const itemId = 'item_123'
+
+ log.set({ user: { id: 789 } })
+ log.set({ action: 'delete_item', itemId })
+
+ // Simulate permission check
+ await new Promise(resolve => setTimeout(resolve, 100))
+ log.set({ permissions: { checked: true, hasDelete: true } })
+
+ // Simulate database delete
+ await new Promise(resolve => setTimeout(resolve, 150))
+ log.set({ database: { operation: 'delete', affected: 1 } })
+
+ // Simulate cache invalidation
+ await new Promise(resolve => setTimeout(resolve, 50))
+ log.set({ cache: { invalidated: true, keys: [`item:${itemId}`] } })
+
+ return {
+ success: true,
+ message: 'Item deleted',
+ deletedId: itemId,
+ }
+})
diff --git a/apps/nitro-v3-playground/routes/api/test/success.get.ts b/apps/nitro-v3-playground/routes/api/test/success.get.ts
new file mode 100644
index 0000000..e5ffdcf
--- /dev/null
+++ b/apps/nitro-v3-playground/routes/api/test/success.get.ts
@@ -0,0 +1,24 @@
+import { defineHandler } from 'nitro/h3'
+import { useLogger } from 'evlog'
+
+export default defineHandler(async (event) => {
+ const log = useLogger(event)
+
+ log.set({ user: { id: 123, plan: 'pro' } })
+ log.set({ action: 'fetch_profile' })
+
+ // Simulate database query
+ await new Promise(resolve => setTimeout(resolve, 150))
+ const profile = { name: 'John Doe', email: 'john@example.com', lastLogin: new Date().toISOString() }
+ log.set({ profile })
+
+ // Simulate cache check
+ await new Promise(resolve => setTimeout(resolve, 50))
+ log.set({ cache: { hit: true, ttl: 3600 } })
+
+ return {
+ success: true,
+ message: 'Profile fetched successfully',
+ data: profile,
+ }
+})
diff --git a/apps/nitro-v3-playground/routes/api/test/update.put.ts b/apps/nitro-v3-playground/routes/api/test/update.put.ts
new file mode 100644
index 0000000..52bd014
--- /dev/null
+++ b/apps/nitro-v3-playground/routes/api/test/update.put.ts
@@ -0,0 +1,27 @@
+import { defineHandler, readBody } from 'nitro/h3'
+import { useLogger } from 'evlog'
+import { createError } from 'evlog/nitro/v3'
+
+export default defineHandler(async (event) => {
+ const log = useLogger(event)
+ const body = await readBody(event) as { name: string; version: string }
+
+ log.set({ user: { id: 999 } })
+ log.set({ action: 'update_item', item: { name: body.name, version: body.version } })
+
+ // Simulate validation check
+ await new Promise(resolve => setTimeout(resolve, 100))
+ log.set({ validation: { checked: true } })
+
+ // Simulate permission check that always fails
+ await new Promise(resolve => setTimeout(resolve, 150))
+ log.set({ permissions: { checked: true, hasUpdate: false, requiredRole: 'admin' } })
+
+ throw createError({
+ message: 'Update failed',
+ status: 403,
+ why: 'Insufficient permissions to update this resource',
+ fix: 'Request admin privileges or contact your team lead',
+ link: 'https://docs.example.com/permissions',
+ })
+})
diff --git a/apps/nitro-v3-playground/routes/api/test/wide-event.get.ts b/apps/nitro-v3-playground/routes/api/test/wide-event.get.ts
new file mode 100644
index 0000000..b5acf65
--- /dev/null
+++ b/apps/nitro-v3-playground/routes/api/test/wide-event.get.ts
@@ -0,0 +1,72 @@
+import { defineHandler } from 'nitro/h3'
+import { useLogger } from 'evlog'
+
+export default defineHandler(async (event) => {
+ const log = useLogger(event)
+
+ // User context
+ await new Promise(resolve => setTimeout(resolve, 80))
+ log.set({
+ user: {
+ id: 'user_789',
+ email: 'demo@example.com',
+ plan: 'enterprise',
+ accountAge: '2 years',
+ role: 'admin',
+ },
+ session: {
+ device: 'desktop',
+ browser: 'Chrome 120',
+ country: 'US',
+ },
+ })
+
+ // Cart information
+ await new Promise(resolve => setTimeout(resolve, 80))
+ log.set({
+ cart: {
+ items: 5,
+ total: 24999,
+ currency: 'USD',
+ discount: {
+ code: 'WINTER25',
+ savings: 8333,
+ },
+ },
+ })
+
+ // Payment processing
+ await new Promise(resolve => setTimeout(resolve, 80))
+ log.set({
+ payment: {
+ method: 'card',
+ cardBrand: 'visa',
+ cardLast4: '4242',
+ },
+ fraud: {
+ score: 12,
+ riskLevel: 'low',
+ passed: true,
+ },
+ })
+
+ // Performance metrics
+ await new Promise(resolve => setTimeout(resolve, 80))
+ log.set({
+ performance: {
+ dbQueries: 8,
+ cacheHits: 12,
+ cacheMisses: 2,
+ },
+ flags: {
+ newCheckoutFlow: true,
+ experimentId: 'exp_checkout_v2',
+ },
+ })
+
+ return {
+ success: true,
+ message: 'Wide event demo - check your terminal!',
+ orderId: 'ord_abc123xyz',
+ }
+})
diff --git a/apps/nitro-v3-playground/tsconfig.json b/apps/nitro-v3-playground/tsconfig.json
new file mode 100644
index 0000000..1099389
--- /dev/null
+++ b/apps/nitro-v3-playground/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": ["nitro/tsconfig"],
+ "compilerOptions": {
+ "paths": {
+ "~/*": ["./*"]
+ }
+ }
+}
diff --git a/bun.lock b/bun.lock
index 8a19cf5..e38cc9d 100644
--- a/bun.lock
+++ b/bun.lock
@@ -38,6 +38,16 @@
"rolldown": "latest",
},
},
+ "apps/nitro-v3-playground": {
+ "name": "evlog-nitro-v3-playground",
+ "dependencies": {
+ "evlog": "workspace:*",
+ },
+ "devDependencies": {
+ "nitro": "latest",
+ "rolldown": "latest",
+ },
+ },
"apps/playground": {
"name": "evlog-playground",
"dependencies": {
@@ -52,6 +62,7 @@
"version": "1.1.0",
"dependencies": {
"@nuxt/kit": "^4.3.0",
+ "ufo": "^1.6.3",
},
"devDependencies": {
"@nuxt/devtools": "^3.1.1",
@@ -59,6 +70,7 @@
"@nuxt/test-utils": "^3.23.0",
"changelogen": "^0.6.2",
"h3": "^1.15.5",
+ "nitro": "^3.0.1-alpha.2",
"nitropack": "^2.13.1",
"nuxt": "^4.3.0",
"typescript": "^5.9.3",
@@ -66,11 +78,13 @@
},
"peerDependencies": {
"h3": "^1.15.5",
+ "nitro": "^3.0.1-alpha.2",
"nitropack": "^2.13.1",
"ofetch": "^1.5.1",
},
"optionalPeers": [
"h3",
+ "nitro",
"nitropack",
"ofetch",
],
@@ -1519,6 +1533,8 @@
"evlog-nitro-playground": ["evlog-nitro-playground@workspace:apps/nitro-playground"],
+ "evlog-nitro-v3-playground": ["evlog-nitro-v3-playground@workspace:apps/nitro-v3-playground"],
+
"evlog-playground": ["evlog-playground@workspace:apps/playground"],
"execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
@@ -2751,7 +2767,7 @@
"unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
- "unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
+ "unstorage": ["unstorage@2.0.0-alpha.5", "", { "peerDependencies": { "@azure/app-configuration": "^1.9.0", "@azure/cosmos": "^4.7.0", "@azure/data-tables": "^13.3.1", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.29.1", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.12.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.35.6", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.8.2", "lru-cache": "^11.2.2", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g=="],
"untun": ["untun@0.1.3", "", { "dependencies": { "citty": "^0.1.5", "consola": "^3.2.3", "pathe": "^1.1.1" }, "bin": { "untun": "bin/untun.mjs" } }, "sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ=="],
@@ -2931,10 +2947,14 @@
"@nuxt/fonts/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+ "@nuxt/fonts/unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
+
"@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@nuxt/nitro-server/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+ "@nuxt/nitro-server/unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
+
"@nuxt/telemetry/@nuxt/kit": ["@nuxt/kit@3.21.0", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^2.1.2", "scule": "^1.3.0", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-KMTLK/dsGaQioZzkYUvgfN9le4grNW54aNcA1jqzgVZLcFVy4jJfrJr5WZio9NT2EMfajdoZ+V28aD7BRr4Zfw=="],
"@nuxt/telemetry/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
@@ -2957,6 +2977,8 @@
"@nuxtjs/i18n/unplugin-vue-router": ["unplugin-vue-router@0.16.2", "", { "dependencies": { "@babel/generator": "^7.28.5", "@vue-macros/common": "^3.1.1", "@vue/language-core": "^3.1.3", "ast-walker-scope": "^0.8.3", "chokidar": "^4.0.3", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.10", "unplugin-utils": "^0.3.1", "yaml": "^2.8.1" }, "peerDependencies": { "@vue/compiler-sfc": "^3.5.17", "vue-router": "^4.6.0" }, "optionalPeers": ["vue-router"] }, "sha512-lE6ZjnHaXfS2vFI/PSEwdKcdOo5RwAbCKUnPBIN9YwLgSWas3x+qivzQvJa/uxhKzJldE6WK43aDKjGj9Rij9w=="],
+ "@nuxtjs/i18n/unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
+
"@nuxtjs/mcp-toolkit/@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="],
"@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
@@ -3043,6 +3065,8 @@
"fontless/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+ "fontless/unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
+
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
@@ -3057,6 +3081,8 @@
"impound/unplugin-utils": ["unplugin-utils@0.2.5", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg=="],
+ "ipx/unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
+
"jsonc-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"jsonc-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
@@ -3085,12 +3111,12 @@
"nitro/ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="],
- "nitro/unstorage": ["unstorage@2.0.0-alpha.5", "", { "peerDependencies": { "@azure/app-configuration": "^1.9.0", "@azure/cosmos": "^4.7.0", "@azure/data-tables": "^13.3.1", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.29.1", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.12.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.35.6", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.8.2", "lru-cache": "^11.2.2", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g=="],
-
"nitropack/cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
"nitropack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+ "nitropack/unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
+
"node-emoji/@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
@@ -3103,6 +3129,8 @@
"nuxt-og-image/execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
+ "nuxt-og-image/unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
+
"nypm/citty": ["citty@0.2.0", "", {}, "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA=="],
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
diff --git a/package.json b/package.json
index 4484843..2eb4553 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"scripts": {
"dev": "turbo run dev --filter=evlog-playground",
"dev:nitro": "turbo run dev --filter=evlog-nitro-playground",
+ "dev:nitro:v3": "turbo run dev --filter=evlog-nitro-v3-playground",
"dev:prepare": "turbo run dev:prepare",
"dev:docs": "turbo run dev --filter=evlog-docs",
"docs": "turbo run dev --filter=evlog-docs",
diff --git a/packages/evlog/build.config.ts b/packages/evlog/build.config.ts
index beba681..bf253d3 100644
--- a/packages/evlog/build.config.ts
+++ b/packages/evlog/build.config.ts
@@ -5,6 +5,7 @@ export default defineBuildConfig({
{ input: 'src/index', name: 'index' },
{ input: 'src/nuxt/module', name: 'nuxt/module' },
{ input: 'src/nitro/plugin', name: 'nitro/plugin' },
+ { input: 'src/nitro-v3/plugin', name: 'nitro/v3/plugin' },
{ input: 'src/runtime/client/log', name: 'runtime/client/log' },
{ input: 'src/runtime/client/plugin', name: 'runtime/client/plugin' },
{ input: 'src/runtime/server/useLogger', name: 'runtime/server/useLogger' },
@@ -32,5 +33,6 @@ export default defineBuildConfig({
'nitropack',
'nitropack/runtime',
'ofetch',
+ 'nitro'
],
})
diff --git a/packages/evlog/package.json b/packages/evlog/package.json
index a1d41ca..4f56d7f 100644
--- a/packages/evlog/package.json
+++ b/packages/evlog/package.json
@@ -34,6 +34,10 @@
"./nitro": {
"types": "./dist/nitro/plugin.d.mts",
"import": "./dist/nitro/plugin.mjs"
+ },
+ "./nitro/v3": {
+ "types": "./dist/nitro/v3/plugin.d.mts",
+ "import": "./dist/nitro/v3/plugin.mjs"
}
},
"main": "./dist/index.mjs",
@@ -48,6 +52,9 @@
],
"nitro": [
"./dist/nitro/plugin.d.mts"
+ ],
+ "nitro/v3": [
+ "./dist/nitro/v3/plugin.d.mts"
]
}
},
@@ -67,7 +74,8 @@
"typecheck": "echo 'Typecheck handled by build'"
},
"dependencies": {
- "@nuxt/kit": "^4.3.0"
+ "@nuxt/kit": "^4.3.0",
+ "ufo": "^1.6.3"
},
"devDependencies": {
"@nuxt/devtools": "^3.1.1",
@@ -78,12 +86,14 @@
"nitropack": "^2.13.1",
"nuxt": "^4.3.0",
"typescript": "^5.9.3",
- "unbuild": "^3.6.1"
+ "unbuild": "^3.6.1",
+ "nitro": "^3.0.1-alpha.2"
},
"peerDependencies": {
"h3": "^1.15.5",
"nitropack": "^2.13.1",
- "ofetch": "^1.5.1"
+ "ofetch": "^1.5.1",
+ "nitro": "^3.0.1-alpha.2"
},
"peerDependenciesMeta": {
"h3": {
@@ -94,6 +104,9 @@
},
"ofetch": {
"optional": true
+ },
+ "nitro": {
+ "optional": true
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/evlog/src/nitro-v3/error.ts b/packages/evlog/src/nitro-v3/error.ts
new file mode 100644
index 0000000..1cfc6dc
--- /dev/null
+++ b/packages/evlog/src/nitro-v3/error.ts
@@ -0,0 +1,114 @@
+import { HTTPError } from 'nitro/h3'
+import type { ErrorOptions } from '../types'
+import { colors, isServer } from '../utils'
+
+/**
+ * Structured error with context for better debugging
+ *
+ * @example
+ * ```ts
+ * throw new EvlogError({
+ * message: 'Failed to sync repository',
+ * status: 503,
+ * why: 'GitHub API rate limit exceeded',
+ * fix: 'Wait 1 hour or use a different token',
+ * link: 'https://docs.github.com/en/rest/rate-limit',
+ * cause: originalError,
+ * })
+ * ```
+ */
+export class EvlogError extends HTTPError {
+
+ readonly status: number
+ readonly why?: string
+ readonly fix?: string
+ readonly link?: string
+
+ constructor(options: ErrorOptions | string) {
+ const opts = typeof options === 'string' ? { message: options } : options
+
+ const body = opts.why || opts.fix || opts.link
+ ? { why: opts.why, fix: opts.fix, link: opts.link }
+ : undefined
+
+ const statusCode = opts.status ?? 500
+
+ super(opts.message, { cause: opts.cause, body, statusCode })
+
+ this.status = statusCode
+ this.why = opts.why
+ this.fix = opts.fix
+ this.link = opts.link
+
+ // Maintain proper stack trace in V8
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, EvlogError)
+ }
+ }
+
+ get statusCode(): number {
+ return this.status
+ }
+
+ override toString(): string {
+ // Use colors only on server (terminal)
+ const useColors = isServer()
+
+ const red = useColors ? colors.red : ''
+ const yellow = useColors ? colors.yellow : ''
+ const cyan = useColors ? colors.cyan : ''
+ const dim = useColors ? colors.dim : ''
+ const reset = useColors ? colors.reset : ''
+ const bold = useColors ? colors.bold : ''
+
+ const lines: string[] = []
+
+ lines.push(`${red}${bold}Error:${reset} ${this.message}`)
+
+ if (this.why) {
+ lines.push(`${yellow}Why:${reset} ${this.why}`)
+ }
+
+ if (this.fix) {
+ lines.push(`${cyan}Fix:${reset} ${this.fix}`)
+ }
+
+ if (this.link) {
+ lines.push(`${dim}More info:${reset} ${this.link}`)
+ }
+
+ if (this.cause) {
+ lines.push(`${dim}Caused by:${reset} ${(this.cause as Error).message}`)
+ }
+
+ return lines.join('\n')
+ }
+
+}
+
+/**
+ * Create a structured error with context for debugging and user-facing messages.
+ *
+ * @param options - Error message string or full options object
+ * @returns EvlogError instance compatible with Nitro's error handling
+ *
+ * @example
+ * ```ts
+ * // Simple error
+ * throw createError('Something went wrong')
+ *
+ * // Structured error with context
+ * throw createError({
+ * message: 'Payment failed',
+ * status: 402,
+ * why: 'Card declined by issuer',
+ * fix: 'Try a different payment method',
+ * link: 'https://docs.example.com/payments',
+ * })
+ * ```
+ */
+export function createError(options: ErrorOptions | string): EvlogError {
+ return new EvlogError(options)
+}
+
+export const createEvlogError = createError
diff --git a/packages/evlog/src/nitro-v3/plugin.ts b/packages/evlog/src/nitro-v3/plugin.ts
new file mode 100644
index 0000000..7272044
--- /dev/null
+++ b/packages/evlog/src/nitro-v3/plugin.ts
@@ -0,0 +1,164 @@
+import { definePlugin } from 'nitro'
+import { useRuntimeConfig } from 'nitro/runtime-config'
+import type { CaptureError } from 'nitro/types'
+import type { HTTPEvent } from 'nitro/h3'
+import { parseURL } from 'ufo'
+import { createRequestLogger, initLogger } from '../logger'
+import { shouldLog } from '../nitro'
+import type { RequestLogger, SamplingConfig, TailSamplingContext, WideEvent } from '../types'
+
+export * from './error'
+
+interface EvlogConfig {
+ env?: Record
+ pretty?: boolean
+ include?: string[]
+ exclude?: string[]
+ sampling?: SamplingConfig
+}
+
+// currently nitro/v3 doesnt export hook types correctly
+// https://github.com/nitrojs/nitro/blob/8882bc9e1dbf2d342e73097f22a2156f70f50575/src/types/runtime/nitro.ts#L48-L53
+interface NitroRuntimeHooks {
+ close: () => void;
+ error: CaptureError;
+ request: (event: HTTPEvent) => void | Promise;
+ response: (res: Response, event: HTTPEvent) => void | Promise;
+ 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise;
+ 'evlog:drain': (ctx: { event: WideEvent; request?: { method?: string; path: string; requestId?: string } }) => void | Promise;
+}
+// Hookable core type not available so we build it our self
+type Hooks = {
+ hook: (
+ name: THookName,
+ listener: NitroRuntimeHooks[THookName]
+ ) => void;
+}
+
+export default definePlugin((nitroApp) => {
+ const config = useRuntimeConfig()
+ const evlogConfig = config.evlog as EvlogConfig | undefined
+
+ initLogger({
+ env: evlogConfig?.env,
+ pretty: evlogConfig?.pretty,
+ sampling: evlogConfig?.sampling,
+ })
+
+ const hooks = nitroApp.hooks as Hooks
+
+
+ hooks.hook('request', (event) => {
+ const e = event
+
+ const { method } = e.req
+ const requestId = e.req.context?.requestId as string | undefined ?? crypto.randomUUID()
+
+ const {
+ pathname
+ } = parseURL(e.req.url)
+
+ // Skip logging for routes not matching include/exclude patterns
+ if (!shouldLog(pathname, evlogConfig?.include, evlogConfig?.exclude)) {
+ return
+ }
+
+ const log = createRequestLogger({
+ method: method,
+ path: pathname,
+ requestId,
+ })
+ if (!e.req.context) {
+ e.req.context = {}
+ }
+ e.req.context.log = log
+ // Store start time for duration calculation in tail sampling
+ e.req.context._evlogStartTime = Date.now()
+ })
+
+ hooks.hook('response', async (res, event) => {
+ const e = event
+ // Skip if already emitted by error hook
+ if (e.req.context?._evlogEmitted) return
+
+ const log = e.req.context?.log as RequestLogger | undefined
+ if (log && e.req.context) {
+ const { status } = res
+ log.set({ status })
+
+ const { pathname } = parseURL(e.req.url)
+ const startTime = e.req.context._evlogStartTime as number | undefined
+ const durationMs = startTime ? Date.now() - startTime : undefined
+
+ const tailCtx: TailSamplingContext = {
+ status,
+ duration: durationMs,
+ path: pathname,
+ method: e.req.method,
+ context: log.getContext(),
+ shouldKeep: false,
+ }
+
+ await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)
+
+ const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })
+
+ // Drain hook integration
+ if (emittedEvent) {
+ try {
+ await nitroApp.hooks.callHook('evlog:drain', {
+ event: emittedEvent,
+ request: { method: e.req.method, path: pathname, requestId: e.req.context.requestId as string | undefined },
+ })
+ } catch (err) {
+ console.error('[evlog] drain failed:', err)
+ }
+ }
+ }
+ })
+
+ hooks.hook('error', async (error, { event }) => {
+ const e = event
+ if (!e) return
+
+ const log = e.req.context?.log as RequestLogger | undefined
+ if (log && e.req.context) {
+ log.error(error as Error)
+
+ // Get the actual error status code
+ const errorStatus = (error as { statusCode?: number }).statusCode ?? 500
+ log.set({ status: errorStatus })
+
+ const { pathname } = parseURL(e.req.url)
+ const startTime = e.req.context._evlogStartTime as number | undefined
+ const durationMs = startTime ? Date.now() - startTime : undefined
+
+ const tailCtx: TailSamplingContext = {
+ status: errorStatus,
+ duration: durationMs,
+ path: pathname,
+ method: e.req.method,
+ context: log.getContext(),
+ shouldKeep: false,
+ }
+
+ await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)
+
+ e.req.context._evlogEmitted = true
+
+ const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })
+
+ // Drain hook integration
+ if (emittedEvent) {
+ try {
+ await nitroApp.hooks.callHook('evlog:drain', {
+ event: emittedEvent,
+ request: { method: e.req.method, path: pathname, requestId: e.req.context.requestId as string | undefined },
+ })
+ } catch (err) {
+ console.error('[evlog] drain failed:', err)
+ }
+ }
+ }
+ })
+})
diff --git a/packages/evlog/src/nitro.ts b/packages/evlog/src/nitro.ts
new file mode 100644
index 0000000..29576ba
--- /dev/null
+++ b/packages/evlog/src/nitro.ts
@@ -0,0 +1,18 @@
+import { matchesPattern } from './utils'
+
+export function shouldLog(path: string, include?: string[], exclude?: string[]): boolean {
+ // Check exclusions first (they take precedence)
+ if (exclude && exclude.length > 0) {
+ if (exclude.some(pattern => matchesPattern(path, pattern))) {
+ return false
+ }
+ }
+
+ // If no include patterns, log everything (that wasn't excluded)
+ if (!include || include.length === 0) {
+ return true
+ }
+
+ // Log only if path matches at least one include pattern
+ return include.some(pattern => matchesPattern(path, pattern))
+}
diff --git a/packages/evlog/src/nitro/plugin.ts b/packages/evlog/src/nitro/plugin.ts
index 2b87cc5..48d04b5 100644
--- a/packages/evlog/src/nitro/plugin.ts
+++ b/packages/evlog/src/nitro/plugin.ts
@@ -2,7 +2,7 @@ import type { NitroApp } from 'nitropack/types'
import { defineNitroPlugin, useRuntimeConfig } from 'nitropack/runtime'
import { createRequestLogger, initLogger } from '../logger'
import type { RequestLogger, SamplingConfig, ServerEvent, TailSamplingContext, WideEvent } from '../types'
-import { matchesPattern } from '../utils'
+import { shouldLog } from '../nitro'
interface EvlogConfig {
env?: Record
@@ -12,23 +12,6 @@ interface EvlogConfig {
sampling?: SamplingConfig
}
-function shouldLog(path: string, include?: string[], exclude?: string[]): boolean {
- // Check exclusions first (they take precedence)
- if (exclude && exclude.length > 0) {
- if (exclude.some(pattern => matchesPattern(path, pattern))) {
- return false
- }
- }
-
- // If no include patterns, log everything (that wasn't excluded)
- if (!include || include.length === 0) {
- return true
- }
-
- // Log only if path matches at least one include pattern
- return include.some(pattern => matchesPattern(path, pattern))
-}
-
function getResponseStatus(event: ServerEvent): number {
// Node.js style
if (event.node?.res?.statusCode) {