{"_id":"@common-sense/ping-iq","_rev":"5-6e13a6ab50026f8ef3d4a84ca70741ce","name":"@common-sense/ping-iq","dist-tags":{"latest":"0.3.2"},"versions":{"0.1.0":{"name":"@common-sense/ping-iq","version":"0.1.0","keywords":["health","diagnostics","metrics","prometheus","express","fastify","koa","nestjs"],"author":"","license":"MIT","_id":"@common-sense/ping-iq@0.1.0","maintainers":[{"name":"jacobspc","email":"jacob.pcyr@gmail.com"}],"dist":{"shasum":"25f0378b492fc76de6d94e0cae6f12c580858083","tarball":"https://registry.npmjs.org/@common-sense/ping-iq/-/ping-iq-0.1.0.tgz","fileCount":26,"integrity":"sha512-vmJvqzZfpF2s7AzdCZ8eOQoW1mmJuzQIGdiHQl9C1+4haw7TpFQgjUOXttphXzjwrHu3jdY5Egzzg0rzlJ9giw==","signatures":[{"sig":"MEUCIAIpkT1UNsC6rfU/tnrmCwwMiQZlzxZdyMx0SZ/Kf53AAiEA6DObSFmx6agZVuJ7gMpZ/a5Tnw7PT/e1vaCrei9eZoI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57728},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=16"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js","require":"./dist/index.js"},"./koa":{"types":"./dist/integrations/koa.d.ts","default":"./dist/integrations/koa.js","require":"./dist/integrations/koa.js"},"./nest":{"types":"./dist/integrations/nest.d.ts","default":"./dist/integrations/nest.js","require":"./dist/integrations/nest.js"},"./express":{"types":"./dist/integrations/express.d.ts","default":"./dist/integrations/express.js","require":"./dist/integrations/express.js"},"./fastify":{"types":"./dist/integrations/fastify.d.ts","default":"./dist/integrations/fastify.js","require":"./dist/integrations/fastify.js"}},"gitHead":"b0bc580955ae4d5a5237b5be84fbe82a427fb097","scripts":{"build":"tsc -p tsconfig.json","clean":"rimraf dist || rm -rf dist","build:all":"npm run build && npm run build:packages","typecheck":"tsc --noEmit","typecheck:all":"npm run typecheck && npm run typecheck:packages","build:packages":"npm -ws --if-present run build","prepublishOnly":"npm run build","typecheck:packages":"npm -ws --if-present run typecheck"},"_npmUser":{"name":"jacobspc","email":"jacob.pcyr@gmail.com"},"repository":{"url":"","type":"git"},"workspaces":["packages/*"],"_npmVersion":"10.8.2","description":"Framework-agnostic health, diagnostics, and metrics endpoints for Node.js","directories":{},"sideEffects":false,"_nodeVersion":"20.19.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.5.4","@types/node":"^20.14.10","@types/express":"^4.17.21"},"peerDependencies":{"koa":">=2","express":">=4 || >=5","fastify":">=4","@nestjs/core":">=9","@nestjs/common":">=9"},"peerDependenciesMeta":{"koa":{"optional":true},"express":{"optional":true},"fastify":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ping-iq_0.1.0_1757365032849_0.7017219905733119","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@common-sense/ping-iq","version":"0.2.0","keywords":["health","diagnostics","metrics","prometheus","express","fastify","koa","nestjs"],"author":"","license":"MIT","_id":"@common-sense/ping-iq@0.2.0","maintainers":[{"name":"jacobspc","email":"jacob.pcyr@gmail.com"}],"dist":{"shasum":"ad1ca2e47a8fb6bceb3edbd22784e6a17864737a","tarball":"https://registry.npmjs.org/@common-sense/ping-iq/-/ping-iq-0.2.0.tgz","fileCount":26,"integrity":"sha512-gVGzi5uOjyCmXdVFTZ3kBaZkvxpDrPkRzXtoepJQCR6WRzsNfuUfjIW0eoI7qqGJiPkdwzzNjyHCFTEiAEhTdA==","signatures":[{"sig":"MEQCIG5MDcGS82zEaAsTZxYmxNjFOrx+9r0oluGSXzMYdmEdAiBkh3QwqYcHUhv5XZVvhw12LlWJDQURgLCZHjdTs55adQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57901},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=16"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js","require":"./dist/index.js"},"./koa":{"types":"./dist/integrations/koa.d.ts","default":"./dist/integrations/koa.js","require":"./dist/integrations/koa.js"},"./nest":{"types":"./dist/integrations/nest.d.ts","default":"./dist/integrations/nest.js","require":"./dist/integrations/nest.js"},"./express":{"types":"./dist/integrations/express.d.ts","default":"./dist/integrations/express.js","require":"./dist/integrations/express.js"},"./fastify":{"types":"./dist/integrations/fastify.d.ts","default":"./dist/integrations/fastify.js","require":"./dist/integrations/fastify.js"}},"gitHead":"85730fc1197d8a2070c792a7fffddcb3277066c8","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","clean":"rimraf dist || rm -rf dist","coverage":"vitest run --coverage","build:all":"npm run build && npm run build:packages","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:all":"npm run typecheck && npm run typecheck:packages","build:packages":"npm -ws --if-present run build","prepublishOnly":"npm run build","typecheck:packages":"npm -ws --if-present run typecheck"},"_npmUser":{"name":"jacobspc","email":"jacob.pcyr@gmail.com"},"repository":{"url":"","type":"git"},"workspaces":["packages/*"],"_npmVersion":"10.8.2","description":"Framework-agnostic health, diagnostics, and metrics endpoints for Node.js","directories":{},"sideEffects":false,"_nodeVersion":"20.19.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.9","typescript":"^5.5.4","@types/node":"^20.14.10","@types/express":"^4.17.21"},"peerDependencies":{"koa":">=2","express":">=4 || >=5","fastify":">=4","@nestjs/core":">=9","@nestjs/common":">=9"},"peerDependenciesMeta":{"koa":{"optional":true},"express":{"optional":true},"fastify":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ping-iq_0.2.0_1757508316345_0.043522877344600586","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@common-sense/ping-iq","version":"0.3.0","keywords":["health","diagnostics","metrics","prometheus","express","fastify","koa","nestjs"],"author":"","license":"MIT","_id":"@common-sense/ping-iq@0.3.0","maintainers":[{"name":"jacobspc","email":"jacob.pcyr@gmail.com"}],"dist":{"shasum":"c3505c0de1221d0bc310e43cfcdabcc239f9503a","tarball":"https://registry.npmjs.org/@common-sense/ping-iq/-/ping-iq-0.3.0.tgz","fileCount":28,"integrity":"sha512-e5zQotdNplTqz8GRHEUnQ6w/lSqueZ6lLZiBYxzPK5cQEvjDFNxq5YqCl20Mgou+coIxGXS3H67Qdf1qn7zOCw==","signatures":[{"sig":"MEQCIFDsDJgGHnsR/UKMIrfVwF7yvB2Wz5kmPA5apRm5kcpOAiAnj8wcR8DXLY91Oi3SoB0v5DYmhu2domqDqjWEKGCcBg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":68181},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=16"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js","require":"./dist/index.js"},"./koa":{"types":"./dist/integrations/koa.d.ts","default":"./dist/integrations/koa.js","require":"./dist/integrations/koa.js"},"./nest":{"types":"./dist/integrations/nest.d.ts","default":"./dist/integrations/nest.js","require":"./dist/integrations/nest.js"},"./express":{"types":"./dist/integrations/express.d.ts","default":"./dist/integrations/express.js","require":"./dist/integrations/express.js"},"./fastify":{"types":"./dist/integrations/fastify.d.ts","default":"./dist/integrations/fastify.js","require":"./dist/integrations/fastify.js"}},"gitHead":"82d65e625986613963677004c4618609fd6212c0","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","clean":"rimraf dist || rm -rf dist","coverage":"vitest run --coverage","build:all":"npm run build && npm run build:packages","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:all":"npm run typecheck && npm run typecheck:packages","build:packages":"npm -ws --if-present run build","prepublishOnly":"npm run build","typecheck:packages":"npm -ws --if-present run typecheck"},"_npmUser":{"name":"jacobspc","email":"jacob.pcyr@gmail.com"},"repository":{"url":"","type":"git"},"workspaces":["packages/*"],"_npmVersion":"10.8.2","description":"Framework-agnostic health, diagnostics, and metrics endpoints for Node.js","directories":{},"sideEffects":false,"_nodeVersion":"20.19.2","dependencies":{"prom-client":"^15.1.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.9","typescript":"^5.5.4","@types/node":"^20.14.10","@types/express":"^4.17.21"},"peerDependencies":{"koa":">=2","express":">=4 || >=5","fastify":">=4","@nestjs/core":">=9","@nestjs/common":">=9"},"peerDependenciesMeta":{"koa":{"optional":true},"express":{"optional":true},"fastify":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ping-iq_0.3.0_1758074057771_0.4428967149420424","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@common-sense/ping-iq","version":"0.3.1","keywords":["health","diagnostics","metrics","prometheus","express","fastify","koa","nestjs"],"author":"","license":"MIT","_id":"@common-sense/ping-iq@0.3.1","maintainers":[{"name":"jacobspc","email":"jacob.pcyr@gmail.com"}],"dist":{"shasum":"38d9e971384563e454f1c03901af2cc5e9c23f63","tarball":"https://registry.npmjs.org/@common-sense/ping-iq/-/ping-iq-0.3.1.tgz","fileCount":28,"integrity":"sha512-JWaCYK74CdkYNvmTb1jguvmWKYHYsq3772QkLhGc5cpsFayl2DQ3A209Umr+lA/vFWxCO5qRvS0+e+yz0se92w==","signatures":[{"sig":"MEUCIBVwsyGjWqMUYpT2KEua6qQjCt9KBaA/HrcNsjDJTTLdAiEA61jDCb/tMIYQzel1fUFkJSdOn97z/Cz6ruVAB9RvD/g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":68447},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=16"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js","require":"./dist/index.js"},"./koa":{"types":"./dist/integrations/koa.d.ts","default":"./dist/integrations/koa.js","require":"./dist/integrations/koa.js"},"./nest":{"types":"./dist/integrations/nest.d.ts","default":"./dist/integrations/nest.js","require":"./dist/integrations/nest.js"},"./express":{"types":"./dist/integrations/express.d.ts","default":"./dist/integrations/express.js","require":"./dist/integrations/express.js"},"./fastify":{"types":"./dist/integrations/fastify.d.ts","default":"./dist/integrations/fastify.js","require":"./dist/integrations/fastify.js"}},"gitHead":"8c09e185d5fb033bd3d2613e1e716c631d6be025","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","clean":"rimraf dist || rm -rf dist","coverage":"vitest run --coverage","build:all":"npm run build && npm run build:packages","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:all":"npm run typecheck && npm run typecheck:packages","build:packages":"npm -ws --if-present run build","prepublishOnly":"npm run build","typecheck:packages":"npm -ws --if-present run typecheck"},"_npmUser":{"name":"jacobspc","email":"jacob.pcyr@gmail.com"},"repository":{"url":"","type":"git"},"workspaces":["packages/*"],"_npmVersion":"10.8.2","description":"Framework-agnostic health, diagnostics, and metrics endpoints for Node.js","directories":{},"sideEffects":false,"_nodeVersion":"20.19.2","dependencies":{"prom-client":"^15.1.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.9","typescript":"^5.5.4","@types/node":"^20.14.10","@types/express":"^4.17.21"},"peerDependencies":{"koa":">=2","express":">=4 || >=5","fastify":">=4","@nestjs/core":">=9","@nestjs/common":">=9"},"peerDependenciesMeta":{"koa":{"optional":true},"express":{"optional":true},"fastify":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ping-iq_0.3.1_1758074591603_0.7440015635112154","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@common-sense/ping-iq","version":"0.3.2","description":"Framework-agnostic health, diagnostics, and metrics endpoints for Node.js","main":"dist/index.js","module":"dist/index.js","types":"dist/index.d.ts","publishConfig":{"access":"public"},"workspaces":["packages/*"],"scripts":{"build":"tsc -p tsconfig.json","build:packages":"npm -ws --if-present run build","build:all":"npm run build && npm run build:packages","clean":"rimraf dist || rm -rf dist","prepublishOnly":"npm run build","typecheck":"tsc --noEmit","typecheck:packages":"npm -ws --if-present run typecheck","typecheck:all":"npm run typecheck && npm run typecheck:packages","test":"vitest run","test:watch":"vitest","coverage":"vitest run --coverage"},"keywords":["health","diagnostics","metrics","prometheus","express","fastify","koa","nestjs"],"sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","require":"./dist/index.js","default":"./dist/index.js"},"./express":{"types":"./dist/integrations/express.d.ts","require":"./dist/integrations/express.js","default":"./dist/integrations/express.js"},"./fastify":{"types":"./dist/integrations/fastify.d.ts","require":"./dist/integrations/fastify.js","default":"./dist/integrations/fastify.js"},"./koa":{"types":"./dist/integrations/koa.d.ts","require":"./dist/integrations/koa.js","default":"./dist/integrations/koa.js"},"./nest":{"types":"./dist/integrations/nest.d.ts","require":"./dist/integrations/nest.js","default":"./dist/integrations/nest.js"}},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/JacobPC/ping-iq.git"},"homepage":"https://github.com/JacobPC/ping-iq#readme","author":{"name":"JacobPC"},"engines":{"node":">=16"},"devDependencies":{"@types/express":"^4.17.21","@types/node":"^20.14.10","typescript":"^5.5.4","vitest":"^2.1.9"},"dependencies":{"prom-client":"^15.1.3"},"peerDependencies":{"@nestjs/common":">=9","@nestjs/core":">=9","express":">=4 || >=5","fastify":">=4","koa":">=2"},"peerDependenciesMeta":{"express":{"optional":true},"fastify":{"optional":true},"koa":{"optional":true},"@nestjs/common":{"optional":true},"@nestjs/core":{"optional":true}},"_id":"@common-sense/ping-iq@0.3.2","gitHead":"970fba1be9844dabfe05d8097491a5a9ea31dd0e","bugs":{"url":"https://github.com/JacobPC/ping-iq/issues"},"_nodeVersion":"20.19.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-BqbUkaG/xCDeEHW5QSBTmTOQB+HX3ZzAJ3TNAVo+2FCiTXGg42pM9vWQQvuts6F+6xMqzqTFlxG0IKibMCJbWA==","shasum":"ddef0d4559c42ecff3f1c70c38d66d39ef6f1c2e","tarball":"https://registry.npmjs.org/@common-sense/ping-iq/-/ping-iq-0.3.2.tgz","fileCount":28,"unpackedSize":68555,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFAPY6YRqKKpDUoF5GOaF758JIstGdu2SZ2SWWuS9NCdAiEApwZoGXz869ha7AwDzPPxt4MVpty3NDHOWHsJyj8qjms="}]},"_npmUser":{"name":"jacobspc","email":"jacob.pcyr@gmail.com"},"directories":{},"maintainers":[{"name":"jacobspc","email":"jacob.pcyr@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ping-iq_0.3.2_1758114611377_0.971694200753539"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-08T20:57:12.746Z","modified":"2025-09-17T13:10:11.744Z","0.1.0":"2025-09-08T20:57:13.068Z","0.2.0":"2025-09-10T12:45:16.653Z","0.3.0":"2025-09-17T01:54:17.967Z","0.3.1":"2025-09-17T02:03:11.773Z","0.3.2":"2025-09-17T13:10:11.561Z"},"license":"MIT","keywords":["health","diagnostics","metrics","prometheus","express","fastify","koa","nestjs"],"repository":{"type":"git","url":"git+https://github.com/JacobPC/ping-iq.git"},"description":"Framework-agnostic health, diagnostics, and metrics endpoints for Node.js","maintainers":[{"name":"jacobspc","email":"jacob.pcyr@gmail.com"}],"readme":"### PingIQ\n\nFramework-agnostic health, diagnostics, and metrics endpoints for Node.js.\n\nLightweight, secure-by-default, and production-ready. Works with Express, Fastify, Koa, and NestJS.\n\n---\n\n## Why PingIQ\n\n- Fast to adopt: plug-and-play endpoints and clients; value in minutes.\n- Framework-agnostic: Express, Fastify, Koa, NestJS; same core.\n- Lightweight: tiny surface area, zero-config defaults, tree-shakable.\n- Secure by default: no env leakage, rate-limited diagnostics, simple auth hook.\n- Extensible: custom readiness checks, logging hooks, pluggable metrics.\n\n## Installation\n\n```bash\nnpm i @common-sense/ping-iq\n# or\nyarn add @common-sense/ping-iq\n```\n\n## How to use in 15 minutes\n\n1) Install and mount under `/_status`.\n\n```ts\n// Express example\nimport express from 'express';\nimport { createPingIQ } from '@common-sense/ping-iq';\n\nconst app = express();\nconst pingIQ = createPingIQ({ info: { name: 'my-service', version: '1.0.0' } });\napp.use('/_status', pingIQ.express());\napp.listen(3000);\n```\n\n2) Add readiness checks (optional):\n\n```ts\nimport { createPingIQ, booleanCheck, timedCheck } from '@common-sense/ping-iq';\n\nconst pingIQ = createPingIQ({\n  readinessChecks: [\n    booleanCheck('database', async () => true),\n    timedCheck('cache', async () => {/* throw on failure */}),\n  ],\n});\n```\n\n3) Secure (optional but recommended):\n\n```ts\nconst pingIQ = createPingIQ({\n  authCheck: ({ headers }) => headers['x-api-key'] === process.env.STATUS_API_KEY,\n  env: { enabled: false },\n});\n```\n\n4) Add a client (optional):\n\n```ts\nimport { createPingIQClient } from '@common-sense/ping-iq-client-fetch';\nconst client = createPingIQClient({ baseUrl: '/_status' });\nconst health = await client.health();\n```\n\n## What you get\n\n- **/**: liveness; returns 200 OK with body \"OK\"\n- **/ping**: heartbeat that returns { status, message: \"pong\", timestamp }\n- **/time**: server UTC timestamp\n- **/info**: service metadata (name, version, environment, extra)\n- **/health**: simple liveness (200 OK, body \"ok\"); Kubernetes-friendly\n- **/readiness**: detailed readiness with async checks; supports `application/health+json`\n- **/metrics**: Prometheus exposition (uptime, memory, request counts)\n- **/diagnostics/network**: downloadable binary payload for throughput tests (rate-limited)\n- **/diagnostics/latency**: tiny binary payload for RTT latency (rate-limited)\n- **/env**: whitelisted environment variables (disabled by default)\n- **/openapi.json**: optional OpenAPI 3 document for all endpoints\n\nSecurity features: optional auth hook for all endpoints, rate limiting for diagnostics, strict defaults.\n\n---\n\n## Quick start\n\nPick your framework adapter and mount under a path like `/_status`.\n\n### Express\n\nRecommended: mount at a prefix and keep default `basePath: '/'`.\n\n```ts\nimport express from 'express';\nimport { createPingIQ } from '@common-sense/ping-iq';\n\nconst app = express();\nconst pingIQ = createPingIQ({\n  // leave basePath default ('/') when mounting with a prefix\n  info: { name: 'my-service', version: '1.0.0', environment: 'prod' },\n});\n\napp.use('/_status', pingIQ.express());\napp.listen(3000, () => console.log('http://localhost:3000/_status/ping'));\n```\n\nEndpoints: `/ping`, `/time`, `/info`, `/health`, `/readiness`, `/metrics`, `/diagnostics/network`, `/diagnostics/latency`, `/env` (opt‑in) under `/_status/*`.\n\n### Fastify\n\nUse Fastify's `prefix` when registering.\n\n```ts\nimport Fastify from 'fastify';\nimport { createPingIQ } from '@common-sense/ping-iq';\n\nconst app = Fastify();\nconst pingIQ = createPingIQ();\n\napp.register(pingIQ.fastify(), { prefix: '/_status' });\napp.listen({ port: 3000 });\n```\n\n### Koa\n\nKoa lacks a built-in router prefix for middleware, so set `basePath`.\n\n```ts\nimport Koa from 'koa';\nimport { createPingIQ } from '@common-sense/ping-iq';\n\nconst app = new Koa();\nconst pingIQ = createPingIQ({ basePath: '/_status/' });\n\napp.use(pingIQ.koa());\napp.listen(3000);\n```\n\n### NestJS\n\nAttach the middleware in your bootstrap (or via a module's consumer).\n\n```ts\n// main.ts\nimport { NestFactory } from '@nestjs/core';\nimport { AppModule } from './app.module';\nimport { createPingIQ } from '@common-sense/ping-iq';\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule);\n  const pingIQ = createPingIQ();\n  const { path, middleware } = pingIQ.nest();\n  app.use('/_status', middleware); // mount at prefix\n  await app.listen(3000);\n}\nbootstrap();\n```\n\n---\n\n## Configuration\n\n```ts\ntype HealthStatus = 'ok' | 'degraded' | 'fail';\n\ntype ReadinessCheck = () =>\n  | void\n  | { name: string; status: HealthStatus; durationMs?: number; error?: string; details?: Record<string, unknown> }\n  | Promise<void | { name: string; status: HealthStatus; durationMs?: number; error?: string; details?: Record<string, unknown> }>;\n\ninterface PingIQOptions {\n  basePath?: string; // default '/'\n  info?: {\n    name?: string;\n    version?: string;\n    environment?: string;\n    extra?: Record<string, unknown>; // e.g., commit, buildId\n  };\n  readinessChecks?: ReadinessCheck[]; // default: liveness ok\n  env?: { enabled?: boolean; whitelist?: string[] }; // default disabled\n  diagnostics?: { enableThroughput?: boolean; maxPayloadBytes?: number }; // default: throughput off, 1MB max\n  rateLimit?: { capacity: number; refillPerSecond: number }; // default: 5 tokens, 0.2 rps refill\n  authCheck?: (ctx: { method: string; url: string; headers: Record<string, string | string[]>; ip?: string }) => boolean | Promise<boolean>;\n  logging?: {\n    onRequest?: (ctx: { method: string; url: string; headers: Record<string, string | string[]>; ip?: string }) => void;\n    onResponse?: (ctx: { method: string; url: string; headers: Record<string, string | string[]>; ip?: string }, statusCode: number) => void;\n    onError?: (ctx: { method: string; url: string; headers: Record<string, string | string[]>; ip?: string }, error: unknown) => void;\n  };\n  openapi?: {\n    enabled?: boolean; // default false\n    title?: string;\n    version?: string;\n    description?: string;\n    servers?: { url: string; description?: string }[];\n  };\n  livenessMetrics?: boolean; // default false\n}\n```\n\nExample with readiness checks (using helpers), env allowlist, and auth:\n\n```ts\nimport { createPingIQ, booleanCheck, timedCheck, httpGetCheck, tcpPortCheck, clientPingCheck } from '@common-sense/ping-iq';\n\n// Example clients (replace with your own instances)\nconst db = { ping: async () => true };\nconst cache = { ping: async () => {} };\nconst s3 = { ping: async () => {} };\n\nconst pingIQ = createPingIQ({\n  readinessChecks: [\n    // Returns ok/fail based on boolean result\n    booleanCheck('database', async () => db.ping()),\n\n    // Measures duration and sets ok on success, fail on throw\n    timedCheck('cache', async () => cache.ping()),\n\n    // HTTP GET with timeout (uses global fetch by default)\n    httpGetCheck('external-api', 'https://status.example.com/health', 1500),\n\n    // TCP port connectivity (e.g., Redis)\n    tcpPortCheck('redis', '127.0.0.1', 6379, 1000),\n\n    // Wrap any client call that should succeed\n    clientPingCheck('storage', async () => { await s3.ping(); }),\n  ],\n  env: { enabled: true, whitelist: ['NODE_ENV', 'COMMIT_SHA', 'BUILD_ID'] },\n  diagnostics: { enableThroughput: true, maxPayloadBytes: 2 * 1024 * 1024 },\n  rateLimit: { capacity: 5, refillPerSecond: 0.2 },\n  authCheck: ({ headers }) => headers['x-api-key'] === process.env.STATUS_API_KEY,\n  openapi: { enabled: true, title: 'My Service - Status', servers: [{ url: 'https://api.example.com/_status' }] },\n});\n```\n\n---\n\n## Endpoints\n\n- **GET /**\n  - Response: plain text `OK`\n\n- **GET /ping**\n  - Response: `{ status: 'ok', message: 'pong', timestamp }`\n\n- **GET /time**\n  - Response: `{ timestamp }`\n\n- **GET /info**\n  - Response: `{ name, version, environment, ...extra }`\n\n- **GET /health**\n  - Liveness endpoint. Returns `200` with body `ok` (Kubernetes-friendly).\n\n- **GET /readiness**\n  - Runs all `readinessChecks` and reports combined status.\n  - Content negotiation:\n    - `Accept: application/health+json` → returns `{ status: 'pass'|'warn'|'fail' }`\n    - otherwise returns detailed JSON with checks.\n  - Response:\n    ```json\n    {\n      \"status\": \"ok|degraded|fail\",\n      \"timestamp\": \"2024-01-01T00:00:00.000Z\",\n      \"checks\": [\n        { \"name\": \"database\", \"status\": \"ok\", \"durationMs\": 12 }\n      ]\n    }\n    ```\n\n- **GET /metrics**\n  - Content-Type: `text/plain; version=0.0.4`\n  - Example exposition:\n    ```\n    # HELP pingiq_requests_total Total requests to PingIQ endpoints\n    # TYPE pingiq_requests_total counter\n    pingiq_requests_total{endpoint=\"ping\"} 10\n\n    # HELP pingiq_process_uptime_seconds Node.js process uptime in seconds\n    # TYPE pingiq_process_uptime_seconds gauge\n    pingiq_process_uptime_seconds 123.45\n    ```\n\n- **GET /diagnostics/network**\n  - Query params:\n    - `payload` (bytes, default 65536): requested download size (capped by `maxPayloadBytes`).\n  - Response: `application/octet-stream` with headers:\n    - `Content-Length`: exact size\n    - `X-PingIQ-Server-Duration-Ms`: server processing duration to subtract client-side\n  - Rate-limited by default (HTTP 429 on excess).\n\n- **GET /diagnostics/latency**\n  - Returns a 1‑byte `application/octet-stream` body for RTT measurement.\n  - Headers:\n    - `X-PingIQ-Server-Duration-Ms`: server processing duration\n\n- **GET /env** (disabled by default)\n  - Only returns whitelisted variables.\n  - Enable via `env: { enabled: true, whitelist: [...] }`.\n\n---\n\n## Standards\n\n- **Kubernetes/Health Check Compatibility**: Readiness supports `application/health+json` with `status: pass|warn|fail`.\n- **Prometheus Metrics**: `/metrics` returns Prometheus text exposition format (`text/plain; version=0.0.4`).\n- **OpenAPI 3**: Optional `/_status/openapi.json` provides a machine-readable contract.\n\n---\n\n## Security & Best Practices\n\n- Set `authCheck` to enforce JWT, API key, or custom logic across all endpoints.\n- Keep `/env` disabled unless you need it; always restrict via a whitelist.\n- Consider mounting under a non-guessable prefix in production (e.g., `/_internal/status`).\n- Place the adapter behind your existing auth/ACL when possible.\n\nExample API key guard:\n\n```ts\nconst pingIQ = createPingIQ({\n  authCheck: ({ headers }) => headers['x-api-key'] === process.env.STATUS_API_KEY,\n});\n```\n\n---\n\n## Clients (frontend)\n\nPick your preferred client and optionally React hooks.\n\n### Fetch client\n\n```ts\nimport { createPingIQClient } from '@common-sense/ping-iq-client-fetch';\nconst client = createPingIQClient({ baseUrl: '/_status' });\nconst { status } = await client.health();\n\n// Measure throughput\nconst d1 = await client.diagnosticsNetwork({ payload: 500_000 });\nconsole.log(d1.throughputMbps, 'Mbps');\n\n// Measure RTT latency\nconst l1 = await client.diagnosticsLatency();\nconsole.log(l1.rttMs, 'ms');\n```\n\n### Axios client\n\n```ts\nimport axios from 'axios';\nimport { createPingIQAxiosClient } from '@common-sense/ping-iq-client-axios';\nconst client = createPingIQAxiosClient({ baseUrl: '/_status', axios });\nconst info = await client.info();\n\nconst d2 = await client.diagnosticsNetwork({ payload: 1_000_000 });\nconsole.log(d2.throughputMbps, 'Mbps');\n\nconst l2 = await client.diagnosticsLatency();\nconsole.log(l2.rttMs, 'ms');\n```\n\n### React hooks\n\n```tsx\nimport { createPingIQClient } from '@common-sense/ping-iq-client-fetch';\nimport { createPingIQHooks } from '@common-sense/ping-iq-react';\n\nconst client = createPingIQClient({ baseUrl: '/_status' });\nconst { useHealth } = createPingIQHooks(client);\n\nfunction HealthWidget() {\n  const { data, loading, error } = useHealth();\n  if (loading) return <>Loading...</>;\n  if (error) return <>Error</>;\n  return <>Status: {data?.status}</>;\n}\n```\n\n---\n\n## Subpath imports (server adapters)\n\nIf you prefer importing adapters directly:\n\n```ts\nimport router from '@common-sense/ping-iq/express';\nimport plugin from '@common-sense/ping-iq/fastify';\nimport middleware from '@common-sense/ping-iq/koa';\nimport nest from '@common-sense/ping-iq/nest';\n```\n\nNote: the main factory `createPingIQ()` already returns bound adapters; subpaths are optional.\n\n---\n\n## OpenAPI\n\nEnable the built-in OpenAPI document and serve it at `/_status/openapi.json`:\n\n```ts\nconst pingIQ = createPingIQ({ openapi: { enabled: true, title: 'My Service - Status' } });\n```\n\nYou can also generate the spec manually:\n\n```ts\nimport { createPingIQ, generateOpenAPISpec } from '@common-sense/ping-iq';\nconst pingIQ = createPingIQ({ openapi: { enabled: true } });\n// get handlers' resolved options indirectly is not exposed; use the endpoint or mirror config.\n```\n\nUse this doc with Swagger UI, Redocly, or your preferred API portal.\n\n---\n\n## What makes PingIQ different (the pitch)\n\n- Drop-in reliability endpoints: consistent contract across services and teams.\n- Observe without exposing: production-safe defaults that don’t leak secrets.\n- Scale-friendly: Prometheus metrics and diagnostics suited for SRE workflows.\n- DX-first: adapters, clients, and hooks so your team ships in minutes.\n\nIf you need just health and metrics, it’s tiny. If you want richer diagnostics later, turn them on with one flag.\n\n---\n\n## Notes on basePath vs mount path\n\n- Express/Fastify: Prefer mounting the adapter at a path prefix (e.g., `/_status`) and keep `basePath` as '/'.\n- Koa: Use `basePath` (e.g., `/_status/`) since middleware is applied globally.\n- NestJS: Use framework mounting (e.g., `app.use('/_status', middleware)`), keep `basePath` as '/'.\n\nUsing both a mount prefix and a non-root `basePath` will duplicate segments (e.g., `/_status/_status/ping`).\n\n---\n\n## License\n\nMIT\n\n\n","readmeFilename":"README.md","homepage":"https://github.com/JacobPC/ping-iq#readme","author":{"name":"JacobPC"},"bugs":{"url":"https://github.com/JacobPC/ping-iq/issues"}}