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 @@

Fusion Snippet @@ -16,20 +16,20 @@

npm version - license + license node version typescript

--- -**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 +});