diff --git a/.agents/skills/fusion-architecture/SKILL.md b/.agents/skills/fusion-architecture/SKILL.md
index 17a7212..8ed31c4 100644
--- a/.agents/skills/fusion-architecture/SKILL.md
+++ b/.agents/skills/fusion-architecture/SKILL.md
@@ -2,42 +2,32 @@
Only when the user needs to modify the CipherLogger package itself (not a project
that uses it) — for example, bug fixes, adding a new optional field, or writing an
-adapter for another framework (like Fastify or Hono).
+adapter for another framework.
## Folder structure
```
cipher-logger/
├── src/
-│ ├── core/ # core — field configuration and log construction
-│ │ ├── logger.ts
-│ │ ├── types.ts
-│ │ ├── build-request-log.ts
-│ │ └── create-cipher-logger.ts
-│ ├── express/ # Express adapter
-│ │ └── middleware.ts
-│ ├── next/ # Next.js adapter
-│ │ └── middleware.ts
-│ └── index.ts # public entry point
+│ ├── core/ # field config, Logger, createCipherLogger
+│ ├── adapters/
+│ │ ├── express/
+│ │ ├── next/
+│ │ ├── nuxt/
+│ │ ├── fastify/
+│ │ ├── nest/
+│ │ └── hono/
+│ ├── index.ts # core public entry
+│ ├── express.ts # cipher-logger/express
+│ ├── next.ts # cipher-logger/next
+│ └── … # other subpath entries
```
## Data flow
-```
- Core
- fields config → buildRequestLog
- │
- ┌─────────┴─────────┐
- ▼ ▼
- Express Next.js
-middleware middleware
-```
-
-Both adapters (`express/middleware.ts` and `next/middleware.ts`) rely on the
-same `buildRequestLog` core — only the extraction of request/response fields from
-the framework differs. The difference in `status`/`duration` behavior between the
-two adapters stems from how each adapter calls the core, not from `buildRequestLog`
-itself.
+Core (`buildRequestLog`) is shared. Each adapter only extracts framework-specific
+request/response fields. Subpath entries keep peer frameworks out of the main
+`cipher-logger` bundle until that adapter is imported or lazy-loaded.
## Local development
@@ -48,17 +38,17 @@ pnpm install
pnpm run build
```
-## Adding a new adapter (e.g. Fastify)
+## Adding a new adapter
-Create a new file such as `src/fastify/middleware.ts` that calls the same
-`buildRequestLog` from `core/build-request-log.ts` and only differs in how fields
-are extracted from the framework — do not reimplement the log-construction logic
-inside the new adapter.
+1. Add `src/adapters//middleware.ts` that calls `cipher.logRequest(...)`.
+2. Add `src/.ts` re-exporting the factory.
+3. Register the entry in `tsup.config.ts` and `package.json` `exports`.
+4. Wire a lazy method on `createCipherLogger` via `loadAdapter("")`.
## Contribution rules (from README)
- Commits must follow [Conventional Commits](https://www.conventionalcommits.org/)
- Run `pnpm run build` before opening a PR
- Keep changes focused and small
-- Update README for any API changes
-- License: MIT © Cipher Unit
+- Update README/docs for any API changes
+- License: BSD-3-Clause © Cipher Unit
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 127bee4..f8a4888 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -11,7 +11,11 @@ permissions:
jobs:
publish:
- if: ${{ github.event.workflow_run.conclusion == 'success' }}
+ # Only publish successful pushes to main — never PRs or other branches.
+ if: >
+ github.event.workflow_run.conclusion == 'success' &&
+ github.event.workflow_run.event == 'push' &&
+ github.event.workflow_run.head_branch == 'main'
runs-on: ubuntu-latest
steps:
@@ -57,4 +61,4 @@ jobs:
exit 0
fi
- npm publish --access public
\ No newline at end of file
+ npm publish --access public --provenance
diff --git a/.husky/commit-msg b/.husky/commit-msg
old mode 100644
new mode 100755
diff --git a/.husky/pre-commit b/.husky/pre-commit
old mode 100644
new mode 100755
diff --git a/.npmignore b/.npmignore
index 8a49621..c0eaabd 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,10 +1,18 @@
-# Source & tooling (published package only ships dist + docs via "files")
+# Backup ignore list; package.json "files" is the publish allowlist.
src/
tsconfig.json
tsup.config.ts
pnpm-lock.yaml
pnpm-workspace.yaml
-CipherScope_Roadmap.md
+eslint.config.mjs
+mkdocs.yml
+zensical.toml
+docs/
+.agents/
+.github/
+.husky/
+assets/
+examples/
test/
# Local artifacts
@@ -21,4 +29,3 @@ Thumbs.db
*.swp
*.swo
Backups
-examples
\ No newline at end of file
diff --git a/README.md b/README.md
index 5305ac2..8907ff6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
@@ -16,20 +16,20 @@
-
+
---
-**Cipher Logger** is a lightweight, production-ready HTTP request logging library for Node.js. It captures every request in your app — **Express**, **Next.js**, and more frameworks on the way — with full control over which fields get logged.
+**Cipher Logger** is a lightweight, production-ready HTTP request logging library for Node.js. It captures every request with adapters for **Express**, **Next.js**, **Fastify**, **Hono**, **NestJS**, and **Nuxt**, with full control over which fields get logged.
## Highlights
- **TypeScript-first**, fully typed API
- **Configurable fields** — required fields always logged, optional fields opt-in
-- **Framework adapters** for Express and Next.js, with more in progress
+- **Framework adapters** via subpath imports (`cipher-logger/express`, `cipher-logger/next`, …)
- **Zero heavy dependencies** — only your framework as an optional peer dependency
- **Node.js 18+**
@@ -47,20 +47,28 @@ yarn add cipher-logger
```ts
import { createCipherLogger } from "cipher-logger";
+import { createExpressMiddleware } from "cipher-logger/express";
const cipher = createCipherLogger({
fields: { ip: true, userAgent: true, query: true },
level: "info",
});
-app.use(cipher.express());
+app.use(createExpressMiddleware(cipher));
+// or: app.use(cipher.express());
```
## Documentation
-Full documentation — configuration reference, framework guides (Express, Next.js, and upcoming adapters), log schema, API reference, and architecture — lives on the docs site:
+Full documentation — configuration reference, framework guides, log schema, API reference, and architecture — lives on the docs site:
-**[docs.cipherunit.xyz](https://cipherunits.github.io/CipherLogger/)**
+| Resource | URL |
+|----------|-----|
+| Npm Package | [npmjs.com](https://npmjs.com/package/cipher-logger) |
+| Documentation | [cipherunits.github.io/CipherLogger](https://cipherunits.github.io/CipherLogger/) |
+| GitHub Org | [github.com/cipherunits](https://github.com/cipherunits/CipherLogger) |
+
+---
## Contributing
@@ -72,12 +80,18 @@ Contributions are welcome.
4. Run `pnpm run build` before submitting
5. Open a Pull Request
-See the [full contributing guide](https://docs.cipherunit.xyz/contributing) on the docs site for details.
+See the [full contributing guide](https://cipherunits.github.io/CipherLogger/) on the docs site for details.
## License
[BSD-3-Clause](./LICENSE) © [Cipher Unit](https://cipherunit.xyz)
+
+
+
+
+
+
Made with ❤️ for developers by CipherUnits
-
\ No newline at end of file
+
diff --git a/docs/advanced/architecture.md b/docs/advanced/architecture.md
index cd1e670..7ed0157 100644
--- a/docs/advanced/architecture.md
+++ b/docs/advanced/architecture.md
@@ -1,22 +1,25 @@
# Architecture
-CipherLogger is split into a framework-agnostic **core** and thin, per-framework **adapters**. This keeps the dependency footprint small — you only pull in the adapter for the framework you actually use — and keeps the logging logic itself easy to test in isolation.
+CipherLogger is split into a framework-agnostic **core** and thin, per-framework **adapters**. Adapters ship as separate entry points so importing the core package does not load Express, Next.js, or other peers.
## Package layout
```text
cipher-logger/
├── src/
-│ ├── core/ # Core — field config & log building
-│ │ ├── logger.ts
-│ │ ├── types.ts
-│ │ ├── build-request-log.ts
-│ │ └── create-cipher-logger.ts
-│ ├── express/ # Express adapter
-│ │ └── middleware.ts
-│ ├── next/ # Next.js adapter
-│ │ └── middleware.ts
-│ └── index.ts # Public entry point
+│ ├── core/ # Field config, Logger, createCipherLogger
+│ ├── adapters/
+│ │ ├── express/
+│ │ ├── next/
+│ │ ├── nuxt/
+│ │ ├── fastify/
+│ │ ├── nest/
+│ │ └── hono/
+│ ├── index.ts # Public core entry
+│ ├── express.ts # Subpath: cipher-logger/express
+│ ├── next.ts # Subpath: cipher-logger/next
+│ └── … # Other adapter entries
+└── dist/ # Built CJS + ESM + types
```
## Data flow
@@ -25,22 +28,33 @@ cipher-logger/
flowchart TB
A[fields config] --> B[buildRequestLog]
B --> C{Adapter}
- C --> D[Express middleware]
+ C --> D[Express / Nest]
C --> E[Next.js middleware]
- D --> F[res.finish → accurate status/duration]
- E --> G[middleware execution → see timing caveat]
+ C --> F[withCipherLogger route handler]
+ C --> G[Fastify / Hono / Nuxt]
+ D --> H[res.finish → accurate status/duration]
+ F --> I[real Response status/duration]
+ E --> J[middleware timing caveat]
```
-1. **Core** owns the `fields` configuration and `buildRequestLog`, which assembles a `RequestLog` object from raw request/response data and whatever optional fields are enabled.
-2. **Adapters** are responsible only for extracting framework-specific data (headers, timing hooks, request/response objects) and handing it to core in a normalized shape.
-3. Each adapter decides *when* logging happens — Express logs on `res.finish` (after the real response), while the Next.js adapter currently logs during middleware execution (see the [timing caveat](../guide/nextjs.md#timing-caveat)).
+1. **Core** owns `fields` configuration and `buildRequestLog`.
+2. **Adapters** extract framework-specific data and call `cipher.logRequest(...)`.
+3. Convenience methods like `cipher.express()` lazy-load the matching `dist/` chunk at call time so unused peers are never required.
## Why this split?
-- **Small surface area per adapter.** Adding a new framework (Fastify, Hono, NestJS, Nuxt — see the [Roadmap](roadmap.md)) means writing a thin file that maps that framework's request lifecycle onto core, not reimplementing field logic.
-- **Zero unnecessary dependencies.** `express` and `next` are optional peer dependencies — installing CipherLogger doesn't pull in either unless you import that adapter.
-- **Testable core.** `buildRequestLog` and `Logger` have no framework dependencies, so they're covered by plain unit tests independent of any HTTP server.
-
-## Public entry point
-
-`src/index.ts` re-exports everything documented in the [API Reference](../reference/api.md): `createCipherLogger`, `Logger`, and every public type. Adapters are not imported eagerly — `cipher.express()` and `cipher.next()` are resolved lazily so that, for example, requiring `next` doesn't happen in a pure-Express project.
+- **Small surface area per adapter.** Adding a framework means a thin adapter file plus a subpath entry.
+- **Optional peers stay optional.** `require("cipher-logger")` / `import "cipher-logger"` does not load `next` or `express`.
+- **Typed imports when you need them.** Prefer `import { createExpressMiddleware } from "cipher-logger/express"` for full framework types.
+
+## Public entry points
+
+| Import | Contents |
+| ------ | -------- |
+| `cipher-logger` | `createCipherLogger`, `Logger`, core types |
+| `cipher-logger/express` | `createExpressMiddleware` |
+| `cipher-logger/next` | `createNextMiddleware`, `withCipherLogger` |
+| `cipher-logger/fastify` | `createFastifyMiddleware` |
+| `cipher-logger/hono` | `createHonoMiddleware` |
+| `cipher-logger/nest` | `createNestMiddleware` |
+| `cipher-logger/nuxt` | `createNuxtMiddleware` |
diff --git a/docs/advanced/roadmap.md b/docs/advanced/roadmap.md
index 2eb2c3f..67916c3 100644
--- a/docs/advanced/roadmap.md
+++ b/docs/advanced/roadmap.md
@@ -7,20 +7,16 @@ CipherLogger is under active development. This page tracks what's planned so you
| Framework | Status |
| --------- | ------ |
| Express | :material-check-circle:{ style="color: #4caf50" } Stable |
-| Next.js | :material-check-circle:{ style="color: #4caf50" } Stable (see [timing caveat](../guide/nextjs.md#timing-caveat)) |
-| Fastify | :material-clock-outline: Planned |
-| Hono | :material-clock-outline: Planned |
-| NestJS | :material-clock-outline: Planned |
-| Nuxt | :material-clock-outline: Planned |
-
-## Accurate Next.js response logging
-
-Today, the Next.js adapter logs during middleware execution, so `status` and `duration` don't reflect the final route response (full explanation in the [Next.js guide](../guide/nextjs.md#timing-caveat)). Route-handler wrappers that log the *actual* final response are planned, mirroring how the Express adapter already works via `res.finish`.
+| Next.js | :material-check-circle:{ style="color: #4caf50" } Stable — middleware + [`withCipherLogger`](../guide/nextjs.md#accurate-route-handler-logging) |
+| Fastify | :material-check-circle:{ style="color: #4caf50" } Available (`cipher-logger/fastify`) |
+| Hono | :material-check-circle:{ style="color: #4caf50" } Available (`cipher-logger/hono`) |
+| NestJS | :material-check-circle:{ style="color: #4caf50" } Available — Express-compatible middleware (`cipher-logger/nest`) |
+| Nuxt | :material-check-circle:{ style="color: #4caf50" } Available — Node-style middleware (`cipher-logger/nuxt`) |
## Package structure
-A restructure of `src/` into clearer `core/` and `adapters/` directories is planned, so that adding a new framework adapter is a self-contained addition rather than a change scattered across the package. Subpath exports (e.g. `cipher-logger/express`, `cipher-logger/next`) are also being considered, so importing one adapter doesn't pull in code for frameworks you don't use.
+Core and adapters live under `src/core/` and `src/adapters/`. Subpath exports (`cipher-logger/express`, `cipher-logger/next`, …) keep peer framework code out of the main entry until you import (or call) that adapter.
## Contributing to the roadmap
-Have a framework you'd like supported, or a field you think should be built in? Open an issue on [GitHub](https://github.com/cipherunits/CipherLogger/issues) — see the [Contributing](../index.md) section of the README for the process.
+Have a framework you'd like supported, or a field you think should be built in? Open an issue on [GitHub](https://github.com/cipherunits/CipherLogger/issues).
diff --git a/docs/faq.md b/docs/faq.md
index cd9a02e..b6baea4 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -6,7 +6,7 @@ Not really — they solve different problems. Winston and Pino are general-purpo
## Why is `duration` wrong in my Next.js logs?
-This is expected with the current Next.js adapter — see the [timing caveat](guide/nextjs.md#timing-caveat). Middleware runs before your route handler, so the adapter can only measure its own execution time, not the full response. A fix (route-handler wrappers) is on the [Roadmap](advanced/roadmap.md).
+This is expected with `createNextMiddleware` / `cipher.next()` — see the [timing caveat](guide/nextjs.md#timing-caveat). Middleware runs before your route handler. Use [`withCipherLogger`](guide/nextjs.md#accurate-route-handler-logging) on App Router handlers for accurate status/duration.
## Can I use CipherLogger without Express or Next.js?
@@ -22,7 +22,7 @@ Each enabled field does a small amount of extra work per request (header read, q
## Is CommonJS supported, or only ESM?
-Both. CipherLogger ships dual ESM/CJS builds, so `import` and `require` both work out of the box.
+Both. CipherLogger ships dual ESM/CJS builds with an `exports` map, so `import` and `require` both resolve correctly — including subpaths like `cipher-logger/express`.
## Where do I report a bug or request a framework adapter?
diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md
index 140364b..e285ae9 100644
--- a/docs/getting-started/installation.md
+++ b/docs/getting-started/installation.md
@@ -22,7 +22,7 @@
## Peer dependencies
-CipherLogger only requires the framework you actually use — everything else stays out of your `node_modules`.
+CipherLogger only requires the framework you actually use — everything else stays out of your `node_modules`. Import the matching subpath (for example `cipher-logger/express`) or call the lazy convenience method (`cipher.express()`).
=== "Express"
@@ -36,6 +36,12 @@ CipherLogger only requires the framework you actually use — everything else st
npm install next
```
+=== "Fastify / Hono / Nest / Nuxt"
+
+ ```bash
+ npm install fastify # or hono / @nestjs/common / nuxt
+ ```
+
If you only use the framework-agnostic [`Logger`](../reference/api.md#logger) class, no peer dependency is required at all.
## Requirements
@@ -43,7 +49,7 @@ If you only use the framework-agnostic [`Logger`](../reference/api.md#logger) cl
| Requirement | Version |
| ----------- | ------- |
| Node.js | 18 or later |
-| TypeScript | 5.x (optional — CipherLogger ships its own `.d.ts` files) |
+| TypeScript | 5.x or 6.x (optional — CipherLogger ships its own `.d.ts` files) |
| Module system | ESM and CommonJS both supported |
## Verifying the install
diff --git a/docs/guide/express.md b/docs/guide/express.md
index ec32237..29841c2 100644
--- a/docs/guide/express.md
+++ b/docs/guide/express.md
@@ -1,10 +1,11 @@
# Express
-`cipher.express()` returns a standard Express middleware. Mount it before your routes so every request passes through it.
+Mount CipherLogger before your routes so every request passes through it. Prefer the typed subpath import, or use `cipher.express()` for the same middleware.
```ts
import express from "express";
import { createCipherLogger } from "cipher-logger";
+import { createExpressMiddleware } from "cipher-logger/express";
const app = express();
@@ -20,7 +21,8 @@ const cipher = createCipherLogger({
});
// Mount before your routes
-app.use(cipher.express());
+app.use(createExpressMiddleware(cipher));
+// or: app.use(cipher.express());
app.get("/users", (req, res) => {
res.json({ users: [] });
diff --git a/docs/guide/nextjs.md b/docs/guide/nextjs.md
index 16a4299..ca0c321 100644
--- a/docs/guide/nextjs.md
+++ b/docs/guide/nextjs.md
@@ -1,12 +1,13 @@
# Next.js
-CipherLogger integrates as `middleware.ts` at your project root (or `src/middleware.ts`).
+CipherLogger integrates as `middleware.ts` at your project root (or `src/middleware.ts`), and as a route-handler wrapper for accurate status/duration.
## Full setup
```ts
// middleware.ts
import { createCipherLogger } from "cipher-logger";
+import { createNextMiddleware } from "cipher-logger/next";
import type { NextRequest } from "next/server";
const cipher = createCipherLogger({
@@ -19,8 +20,10 @@ const cipher = createCipherLogger({
level: "info",
});
+const cipherMiddleware = createNextMiddleware(cipher);
+
export function middleware(request: NextRequest) {
- return cipher.next()(request);
+ return cipherMiddleware(request);
}
export const config = {
@@ -35,8 +38,6 @@ export const config = {
## Concise form
-If you don't need a custom `middleware` function, export the adapter directly:
-
```ts
// middleware.ts
import { createCipherLogger } from "cipher-logger";
@@ -52,26 +53,35 @@ export const config = {
};
```
-## Scoping with `matcher`
+## Accurate route-handler logging
-Use the `matcher` config to avoid logging static assets, prefetches, and other noise. A narrower matcher (e.g. `/api/:path*`) also reduces overhead on high-traffic routes you don't care about.
+For App Router route handlers, use `withCipherLogger` so `status` and `duration` match the real response:
-## Timing caveat
+```ts
+// app/api/users/route.ts
+import { withCipherLogger } from "cipher-logger/next";
+import { createCipherLogger } from "cipher-logger";
-!!! warning "`status` and `duration` reflect middleware execution, not the final route response"
- Next.js middleware runs **before** the route handler. Because of this, the `status` and `duration` fields recorded by the Next.js adapter describe the middleware's own execution — not what the route handler eventually returns to the client.
+const cipher = createCipherLogger({ fields: { ip: true } });
- In practice this means:
+export const GET = withCipherLogger(cipher, async () => {
+ return Response.json({ users: [] });
+});
+```
+
+## Scoping with `matcher`
- - `duration` will usually be very small (middleware execution time only)
- - `status` may not match the status code the browser ultimately receives
+Use the `matcher` config to avoid logging static assets, prefetches, and other noise. A narrower matcher (e.g. `/api/:path*`) also reduces overhead on high-traffic routes you don't care about.
+
+## Timing caveat
- **Workaround for accurate response-level logging today:** wrap your route handlers directly (e.g. call `logger.logRequest(...)` manually at the end of the handler, or use the Express adapter if you're running a custom server).
+!!! warning "`createNextMiddleware` reflects middleware execution, not the final route response"
+ Next.js middleware runs **before** the route handler. Because of this, the `status` and `duration` fields recorded by `createNextMiddleware` / `cipher.next()` describe the middleware's own execution — not what the route handler eventually returns to the client.
- Route handler wrappers that log the true final response are planned — see the [Roadmap](../advanced/roadmap.md).
+ Prefer [`withCipherLogger`](#accurate-route-handler-logging) on App Router handlers when you need accurate response-level logging.
## Next steps
- [Configuration](configuration.md) — every `fields` flag explained
- [Express](express.md) — the adapter with response-accurate `status`/`duration` today
-- [Roadmap](../advanced/roadmap.md) — planned framework support and route-handler wrappers
+- [Roadmap](../advanced/roadmap.md) — adapter status overview
diff --git a/docs/index.md b/docs/index.md
index 894fe46..de9a633 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -15,7 +15,7 @@ npm install cipher-logger
- :material-tune:{ .lg .middle } **Configurable fields** — required fields are always recorded, optional fields are opt-in
-- :material-view-grid-plus:{ .lg .middle } **Framework adapters** — Express and Next.js today, Fastify/Hono/NestJS/Nuxt on the way
+- :material-view-grid-plus:{ .lg .middle } **Framework adapters** — Express, Next.js, Fastify, Hono, NestJS, and Nuxt via subpath exports
- :material-weight-lifter:{ .lg .middle } **Zero heavy dependencies** — only your framework as an optional peer dependency
@@ -69,7 +69,7 @@ Ready for more? Follow the [Quick Start guide](getting-started/quick-start.md).
| ------------------- | ----------------------- | ------------------------------- | ------------------------------------------ |
| **Schema** | None — every call differs | Format-defined, not HTTP-aware | Fixed `RequestLog` shape, HTTP-first |
| **Field control** | Manual, per call | Global formatter | Per-field opt-in (`fields` config) |
-| **Framework wiring** | Manual | Separate middleware packages | Built-in `express()` / `next()` adapters |
+| **Framework wiring** | Manual | Separate middleware packages | Built-in adapters + `cipher-logger/` subpaths |
| **TypeScript** | N/A | Varies | Fully typed, exported types for every shape |
| **Accurate timing** | Manual | Manual | Built-in, per-adapter (see [caveats](guide/nextjs.md#timing-caveat)) |
diff --git a/docs/reference/api.md b/docs/reference/api.md
index 8c3d134..ae0dc54 100644
--- a/docs/reference/api.md
+++ b/docs/reference/api.md
@@ -17,15 +17,21 @@ const cipher = createCipherLogger(config);
| Method | Description |
| --------------------------- | ----------------------------------------- |
| `cipher.logRequest(input)` | Manually log an HTTP request |
-| `cipher.express()` | Returns an Express middleware |
-| `cipher.next()` | Returns a Next.js middleware |
+| `cipher.express()` | Lazy-loads Express middleware |
+| `cipher.next()` | Lazy-loads Next.js middleware |
+| `cipher.fastify()` | Lazy-loads Fastify middleware |
+| `cipher.hono()` | Lazy-loads Hono middleware |
+| `cipher.nest()` | Lazy-loads Nest (Express-style) middleware |
+| `cipher.nuxt()` | Lazy-loads Nuxt middleware |
-#### `cipher.logRequest(input: RequestLogInput): void`
+For **fully typed** adapters, prefer the subpath imports (for example `cipher-logger/express`) instead of the convenience methods.
-Logs a single HTTP request. Useful when you're not going through a supported framework adapter, or when you want to log a request from inside a route handler with final, accurate values.
+#### `cipher.logRequest(input: RequestLogInput): RequestLog`
+
+Logs a single HTTP request and returns the resolved `RequestLog`. Useful when you're not going through a supported framework adapter, or when you want to log a request from inside a route handler with final, accurate values.
```ts
-cipher.logRequest({
+const log = cipher.logRequest({
method: "POST",
path: "/orders",
status: 201,
@@ -35,13 +41,24 @@ cipher.logRequest({
`RequestLogInput` accepts the [required fields](log-schema.md#required-fields) plus any [optional fields](log-schema.md#optional-fields) you've enabled in `fields`.
-#### `cipher.express(): ExpressMiddleware`
+#### `cipher.express()` / subpath
+
+```ts
+import { createExpressMiddleware } from "cipher-logger/express";
+
+app.use(createExpressMiddleware(cipher));
+// equivalent: app.use(cipher.express());
+```
+
+Records `status` and `duration` on `res.finish`. See the [Express guide](../guide/express.md).
-Returns middleware compatible with `app.use()`. Records `status` and `duration` on `res.finish`. See the [Express guide](../guide/express.md).
+#### `cipher.next()` / subpath
-#### `cipher.next(): NextMiddleware`
+```ts
+import { createNextMiddleware, withCipherLogger } from "cipher-logger/next";
+```
-Returns a function compatible with a Next.js `middleware.ts` default export. See the [Next.js guide](../guide/nextjs.md) — including the current [timing caveat](../guide/nextjs.md#timing-caveat).
+See the [Next.js guide](../guide/nextjs.md) — including the [timing caveat](../guide/nextjs.md#timing-caveat) and accurate route-handler wrapping.
---
@@ -86,7 +103,7 @@ Each method accepts a `message: string` and an optional `meta: Record Promise,
-) => Response | Promise;
+) => void | Promise;
function getHeader(request: any, name: string): string | undefined {
if (!request) {
@@ -78,7 +78,5 @@ export function createHonoMiddleware(cipher: CipherLogger): HonoMiddleware {
query: parseQuery(url),
requestId: getHeader(req, "x-request-id"),
});
-
- return (cres as any) ?? new Response(null, { status });
};
}
diff --git a/src/core/create-cipher-logger.ts b/src/core/create-cipher-logger.ts
index e616d8c..23c1dd1 100644
--- a/src/core/create-cipher-logger.ts
+++ b/src/core/create-cipher-logger.ts
@@ -1,33 +1,51 @@
-import { createExpressMiddleware } from "../adapters/express/middleware";
-import { createNextMiddleware } from "../adapters/next/middleware";
-import { createFastifyMiddleware } from "../adapters/fastify/middleware";
-import { createNestMiddleware } from "../adapters/nest/middleware";
-import { createHonoMiddleware } from "../adapters/hono/middleware";
-import { createNuxtMiddleware } from "../adapters/nuxt/middleware";
import { Logger } from "./logger";
import { buildRequestLog, resolveFieldConfig } from "./build-request-log";
+import { loadAdapter } from "./load-adapter";
import type {
CipherLoggerConfig,
RequestLog,
RequestLogInput,
} from "./types";
-import type { ExpressMiddleware } from "../adapters/express/middleware";
-import type { NextMiddleware } from "../adapters/next/middleware";
-import type { FastifyMiddleware } from "../adapters/fastify/middleware";
-import type { NestMiddleware } from "../adapters/nest/middleware";
-import type { HonoMiddleware } from "../adapters/hono/middleware";
-import type { NuxtMiddleware } from "../adapters/nuxt/middleware";
+
+/** Untyped middleware so the core entry does not pull in peer framework types. */
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type FrameworkMiddleware = (...args: any[]) => any;
export interface CipherLogger {
logRequest(input: RequestLogInput): RequestLog;
- express(): ExpressMiddleware;
- next(): NextMiddleware;
- nuxt(): NuxtMiddleware;
- fastify(): FastifyMiddleware;
- nest(): NestMiddleware;
- hono(): HonoMiddleware;
+ /** Prefer `import { createExpressMiddleware } from "cipher-logger/express"` for typed Express middleware. */
+ express(): FrameworkMiddleware;
+ /** Prefer `import { createNextMiddleware } from "cipher-logger/next"` for typed Next.js middleware. */
+ next(): FrameworkMiddleware;
+ /** Prefer `import { createNuxtMiddleware } from "cipher-logger/nuxt"` for typed Nuxt middleware. */
+ nuxt(): FrameworkMiddleware;
+ /** Prefer `import { createFastifyMiddleware } from "cipher-logger/fastify"` for typed Fastify hooks. */
+ fastify(): FrameworkMiddleware;
+ /** Prefer `import { createNestMiddleware } from "cipher-logger/nest"` for typed Nest middleware. */
+ nest(): FrameworkMiddleware;
+ /** Prefer `import { createHonoMiddleware } from "cipher-logger/hono"` for typed Hono middleware. */
+ hono(): FrameworkMiddleware;
}
+type ExpressAdapter = {
+ createExpressMiddleware: (cipher: CipherLogger) => FrameworkMiddleware;
+};
+type NextAdapter = {
+ createNextMiddleware: (cipher: CipherLogger) => FrameworkMiddleware;
+};
+type NuxtAdapter = {
+ createNuxtMiddleware: (cipher: CipherLogger) => FrameworkMiddleware;
+};
+type FastifyAdapter = {
+ createFastifyMiddleware: (cipher: CipherLogger) => FrameworkMiddleware;
+};
+type NestAdapter = {
+ createNestMiddleware: (cipher: CipherLogger) => FrameworkMiddleware;
+};
+type HonoAdapter = {
+ createHonoMiddleware: (cipher: CipherLogger) => FrameworkMiddleware;
+};
+
export function createCipherLogger(
config: CipherLoggerConfig = {},
): CipherLogger {
@@ -45,23 +63,39 @@ export function createCipherLogger(
},
express() {
- return createExpressMiddleware(cipherLogger);
+ return loadAdapter("express").createExpressMiddleware(
+ cipherLogger,
+ );
},
next() {
- return createNextMiddleware(cipherLogger);
+ return loadAdapter("next").createNextMiddleware(
+ cipherLogger,
+ );
},
+
nuxt() {
- return createNuxtMiddleware(cipherLogger);
+ return loadAdapter("nuxt").createNuxtMiddleware(
+ cipherLogger,
+ );
},
+
fastify() {
- return createFastifyMiddleware(cipherLogger);
+ return loadAdapter("fastify").createFastifyMiddleware(
+ cipherLogger,
+ );
},
+
nest() {
- return createNestMiddleware(cipherLogger);
+ return loadAdapter("nest").createNestMiddleware(
+ cipherLogger,
+ );
},
+
hono() {
- return createHonoMiddleware(cipherLogger);
+ return loadAdapter("hono").createHonoMiddleware(
+ cipherLogger,
+ );
},
};
diff --git a/src/core/index.ts b/src/core/index.ts
index 6018d61..4df60f9 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -1,7 +1,10 @@
export { buildRequestLog, resolveFieldConfig } from "./build-request-log";
export { createCipherLogger } from "./create-cipher-logger";
export { Logger } from "./logger";
-export type { CipherLogger } from "./create-cipher-logger";
+export type {
+ CipherLogger,
+ FrameworkMiddleware,
+} from "./create-cipher-logger";
export type { LogLevel, LogMeta, LoggerOptions } from "./logger";
export {
OPTIONAL_REQUEST_FIELDS,
diff --git a/src/core/load-adapter.ts b/src/core/load-adapter.ts
new file mode 100644
index 0000000..735709a
--- /dev/null
+++ b/src/core/load-adapter.ts
@@ -0,0 +1,11 @@
+import { createRequire } from "node:module";
+import { join } from "node:path";
+
+// Resolve sibling adapter chunks from the built `dist/` directory at runtime.
+// Using createRequire + __dirname avoids esbuild rewriting `require("./" + id)`
+// into an in-bundle glob that cannot see separate entry files.
+const requireFromDist = createRequire(__filename);
+
+export function loadAdapter(id: string): T {
+ return requireFromDist(join(__dirname, `${id}.js`)) as T;
+}
diff --git a/src/core/logger.ts b/src/core/logger.ts
index 76b4ec6..c41b503 100644
--- a/src/core/logger.ts
+++ b/src/core/logger.ts
@@ -48,27 +48,35 @@ export class Logger {
switch (level) {
case "error":
- meta
- ? console.error(output, meta)
- : console.error(output);
+ if (meta) {
+ console.error(output, meta);
+ } else {
+ console.error(output);
+ }
break;
case "warn":
- meta
- ? console.warn(output, meta)
- : console.warn(output);
+ if (meta) {
+ console.warn(output, meta);
+ } else {
+ console.warn(output);
+ }
break;
case "debug":
- meta
- ? console.debug(output, meta)
- : console.debug(output);
+ if (meta) {
+ console.debug(output, meta);
+ } else {
+ console.debug(output);
+ }
break;
case "info":
- meta
- ? console.info(output, meta)
- : console.info(output);
+ if (meta) {
+ console.info(output, meta);
+ } else {
+ console.info(output);
+ }
break;
}
}
diff --git a/src/express.ts b/src/express.ts
new file mode 100644
index 0000000..71cb035
--- /dev/null
+++ b/src/express.ts
@@ -0,0 +1,4 @@
+export {
+ createExpressMiddleware,
+ type ExpressMiddleware,
+} from "./adapters/express/middleware";
diff --git a/src/fastify.ts b/src/fastify.ts
new file mode 100644
index 0000000..57a2ea2
--- /dev/null
+++ b/src/fastify.ts
@@ -0,0 +1,4 @@
+export {
+ createFastifyMiddleware,
+ type FastifyMiddleware,
+} from "./adapters/fastify/middleware";
diff --git a/src/hono.ts b/src/hono.ts
new file mode 100644
index 0000000..23eb1a7
--- /dev/null
+++ b/src/hono.ts
@@ -0,0 +1,4 @@
+export {
+ createHonoMiddleware,
+ type HonoMiddleware,
+} from "./adapters/hono/middleware";
diff --git a/src/index.ts b/src/index.ts
index 4deddff..1c0b3bf 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,21 +1,21 @@
import { Logger } from "./core/logger";
export const logger = new Logger();
-export { Logger, createCipherLogger } from "./core";
-export { createExpressMiddleware } from "./adapters/express/middleware";
-export { createNextMiddleware } from "./adapters/next/middleware";
-export { createNuxtMiddleware } from "./adapters/nuxt/middleware";
-export { createFastifyMiddleware } from "./adapters/fastify/middleware";
-export { createNestMiddleware } from "./adapters/nest/middleware";
-export { createHonoMiddleware } from "./adapters/hono/middleware";
-export type { LogLevel, LogMeta, LoggerOptions } from "./core/logger";
-export type { CipherLogger, CipherLoggerConfig, OptionalRequestField, RequestLog, RequestLogInput,} from "./core";
-export type { ExpressMiddleware } from "./adapters/express/middleware";
-export type { NextMiddleware } from "./adapters/next/middleware";
-export type { NuxtMiddleware } from "./adapters/nuxt/middleware";
-export type { FastifyMiddleware } from "./adapters/fastify/middleware";
-export type { NestMiddleware } from "./adapters/nest/middleware";
-export type { HonoMiddleware } from "./adapters/hono/middleware";
-export { withCipherLogger } from "./adapters/next/with-cipher-logger";
-export type { RouteHandler } from "./adapters/next/with-cipher-logger";
-export type { RequestLog as request } from "./core";
\ No newline at end of file
+
+export {
+ Logger,
+ createCipherLogger,
+ OPTIONAL_REQUEST_FIELDS,
+} from "./core";
+
+export type {
+ CipherLogger,
+ FrameworkMiddleware,
+ CipherLoggerConfig,
+ OptionalRequestField,
+ RequestLog,
+ RequestLogInput,
+ LogLevel,
+ LogMeta,
+ LoggerOptions,
+} from "./core";
diff --git a/src/nest.ts b/src/nest.ts
new file mode 100644
index 0000000..f21867d
--- /dev/null
+++ b/src/nest.ts
@@ -0,0 +1,4 @@
+export {
+ createNestMiddleware,
+ type NestMiddleware,
+} from "./adapters/nest/middleware";
diff --git a/src/next.ts b/src/next.ts
new file mode 100644
index 0000000..d313ba6
--- /dev/null
+++ b/src/next.ts
@@ -0,0 +1,8 @@
+export {
+ createNextMiddleware,
+ type NextMiddleware,
+} from "./adapters/next/middleware";
+export {
+ withCipherLogger,
+ type RouteHandler,
+} from "./adapters/next/with-cipher-logger";
diff --git a/src/nuxt.ts b/src/nuxt.ts
new file mode 100644
index 0000000..1692ce0
--- /dev/null
+++ b/src/nuxt.ts
@@ -0,0 +1,6 @@
+export {
+ createNuxtMiddleware,
+ type NuxtMiddleware,
+ type NuxtRequest,
+ type NuxtResponse,
+} from "./adapters/nuxt/middleware";
diff --git a/tsup.config.ts b/tsup.config.ts
index 23ca315..b290e8d 100644
--- a/tsup.config.ts
+++ b/tsup.config.ts
@@ -1,10 +1,31 @@
import { defineConfig } from "tsup";
+
+const peerExternals = [
+ "express",
+ "next",
+ "next/server",
+ "fastify",
+ "hono",
+ "nuxt",
+ "@nestjs/common",
+ "@nestjs/core",
+];
+
export default defineConfig({
format: ["cjs", "esm"],
- entry: { index: "./src/index.ts" },
+ entry: {
+ index: "./src/index.ts",
+ express: "./src/express.ts",
+ next: "./src/next.ts",
+ nuxt: "./src/nuxt.ts",
+ fastify: "./src/fastify.ts",
+ nest: "./src/nest.ts",
+ hono: "./src/hono.ts",
+ },
dts: true,
shims: true,
skipNodeModulesBundle: true,
clean: true,
+ external: peerExternals,
tsconfig: "./tsconfig.json",
-});
\ No newline at end of file
+});