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) {