From 8d2a7f0aa82edcfb29472afad48a8cfdecf136f4 Mon Sep 17 00:00:00 2001 From: taha Date: Sat, 19 Sep 2026 20:55:18 +0330 Subject: [PATCH] feat: isolate adapters via subpaths and harden npm publish Split framework adapters into separate package exports so the core entry no longer requires Next.js or other peers at import time. Gate CI publish to main-only pushes and ship a lean tarball with an explicit files allowlist. Co-authored-by: Cursor --- .agents/skills/fusion-architecture/SKILL.md | 56 ++++++-------- .github/workflows/publish.yml | 8 +- .husky/commit-msg | 0 .husky/pre-commit | 0 .npmignore | 13 +++- README.md | 32 +++++--- docs/advanced/architecture.md | 62 +++++++++------ docs/advanced/roadmap.md | 18 ++--- docs/faq.md | 4 +- docs/getting-started/installation.md | 10 ++- docs/guide/express.md | 6 +- docs/guide/nextjs.md | 40 ++++++---- docs/index.md | 4 +- docs/reference/api.md | 48 ++++++++---- package.json | 84 ++++++++++++++++++++- pnpm-workspace.yaml | 4 +- src/adapters/hono/middleware.ts | 4 +- src/core/create-cipher-logger.ts | 82 ++++++++++++++------ src/core/index.ts | 5 +- src/core/load-adapter.ts | 11 +++ src/core/logger.ts | 32 +++++--- src/express.ts | 4 + src/fastify.ts | 4 + src/hono.ts | 4 + src/index.ts | 36 ++++----- src/nest.ts | 4 + src/next.ts | 8 ++ src/nuxt.ts | 6 ++ tsup.config.ts | 25 +++++- 29 files changed, 429 insertions(+), 185 deletions(-) mode change 100644 => 100755 .husky/commit-msg mode change 100644 => 100755 .husky/pre-commit create mode 100644 src/core/load-adapter.ts create mode 100644 src/express.ts create mode 100644 src/fastify.ts create mode 100644 src/hono.ts create mode 100644 src/nest.ts create mode 100644 src/next.ts create mode 100644 src/nuxt.ts 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 +});