@pulse-labs/server-pulse 0.0.0-stage → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.0.2] - 2026-10-15
10
+
11
+ ### Changed
12
+
13
+ - README: step-by-step guide for using the package in a project (install, Express CommonJS/ESM/TypeScript, NestJS, dashboard credentials via environment variables, opening the dashboard, mounting rules, updating).
14
+
15
+ ## [0.0.1] - 2026-10-14
16
+
17
+ First public preview on npm as `@pulse-labs/server-pulse`. Scaffold only: the middleware is a pass-through. (Briefly published as `@mukesh-singh9862/server-pulse`, which was unpublished.)
18
+
19
+ ### Added
20
+
21
+ - Project scaffold: TypeScript build (tsup, CJS + ESM + types), Vitest 4, ESLint, Prettier, CI workflow.
22
+ - `npm run smoke`: runner-free smoke test of the built package (also runs on Node 18.15 in CI).
23
+ - Pass-through `serverPulse()` middleware with the public API surface.
24
+ - `examples/express4-basic`.
25
+ - MIT license; published publicly to npm under the `@pulse-labs` scope.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pulse-labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,201 @@
1
- # Temporary Holding Version
1
+ # server-pulse
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Live server monitoring dashboard for Node.js apps, served from your own base URL (for example `https://api.example.com/monitor`).
4
+
5
+ > **Status:** early preview (`0.0.x`, milestone M0 — project scaffold). The middleware is currently a pass-through and does not collect anything yet. Do not use it in production before `1.0.0`. Sections marked _TODO_ are completed in later milestones.
6
+
7
+ ## Use it in your project (step by step)
8
+
9
+ > In `0.0.x` the setup below already installs and runs, but the dashboard at `/monitor` is not served yet. Your setup code stays the same when the full version is released.
10
+
11
+ ### Step 1: Check your Node.js version
12
+
13
+ ```bash
14
+ node -v
15
+ ```
16
+
17
+ You need Node.js **18.15 or newer**.
18
+
19
+ ### Step 2: Install the package
20
+
21
+ Run this in your project folder (the one that contains your project's `package.json`):
22
+
23
+ ```bash
24
+ npm install @pulse-labs/server-pulse
25
+ ```
26
+
27
+ The package has **no runtime dependencies** and ships its own TypeScript types, so you don't need a separate `@types` package.
28
+
29
+ ### Step 3: Add it to your app
30
+
31
+ Works with **Express 4 and Express 5** (the two current major versions of Express). The code is the same for both.
32
+
33
+ **Express (CommonJS, `require`)**
34
+
35
+ ```js
36
+ const express = require('express');
37
+ const { serverPulse } = require('@pulse-labs/server-pulse');
38
+
39
+ const app = express();
40
+
41
+ const pulse = serverPulse({
42
+ auth: { username: process.env.PULSE_USER, password: process.env.PULSE_PASS },
43
+ });
44
+ app.use(pulse); // 1. must be the FIRST middleware
45
+
46
+ app.use(express.json()); // 2. your other middleware comes after it
47
+ app.get('/api/products', (req, res) => res.json([]));
48
+ // ... your routes ...
49
+
50
+ app.use(pulse.errorHandler()); // 3. optional, AFTER your routes: shows error messages in "Recent errors"
51
+
52
+ app.listen(3000);
53
+ ```
54
+
55
+ **Express with ESM or TypeScript (`import`)**
56
+
57
+ ```ts
58
+ import express from 'express';
59
+ import { serverPulse } from '@pulse-labs/server-pulse';
60
+
61
+ const app = express();
62
+ const pulse = serverPulse({
63
+ auth: { username: process.env.PULSE_USER!, password: process.env.PULSE_PASS! },
64
+ });
65
+ app.use(pulse);
66
+ // ... your routes ...
67
+ app.use(pulse.errorHandler());
68
+ app.listen(3000);
69
+ ```
70
+
71
+ **NestJS** (in `main.ts`; NestJS uses Express by default)
72
+
73
+ ```ts
74
+ import { NestFactory } from '@nestjs/core';
75
+ import { serverPulse } from '@pulse-labs/server-pulse';
76
+ import { AppModule } from './app.module';
77
+
78
+ async function bootstrap() {
79
+ const app = await NestFactory.create(AppModule);
80
+ const pulse = serverPulse({
81
+ auth: { username: process.env.PULSE_USER!, password: process.env.PULSE_PASS! },
82
+ });
83
+ app.use(pulse);
84
+ // optional: call pulse.captureError(err, req) inside your global exception filter
85
+ await app.listen(3000);
86
+ }
87
+ bootstrap();
88
+ ```
89
+
90
+ ### Step 4: Set the dashboard login
91
+
92
+ Never write the username and password in your code. Use environment variables:
93
+
94
+ | Variable | Purpose |
95
+ | -------------- | -------------------------------------------------------------------------------------------------------- |
96
+ | `PULSE_USER` | Dashboard username |
97
+ | `PULSE_PASS` | Dashboard password (use a strong one) |
98
+ | `PULSE_SECRET` | Long random string that signs login sessions. If it is not set, everyone is logged out on every restart. |
99
+
100
+ PowerShell (Windows):
101
+
102
+ ```powershell
103
+ $env:PULSE_USER="admin"
104
+ $env:PULSE_PASS="a-strong-password"
105
+ $env:PULSE_SECRET="a-long-random-string"
106
+ node server.js
107
+ ```
108
+
109
+ macOS / Linux:
110
+
111
+ ```bash
112
+ PULSE_USER=admin PULSE_PASS=a-strong-password PULSE_SECRET=a-long-random-string node server.js
113
+ ```
114
+
115
+ Or in a `.env` file, if your project uses [dotenv](https://www.npmjs.com/package/dotenv). Do not commit the `.env` file to git.
116
+
117
+ ```dotenv
118
+ PULSE_USER=admin
119
+ PULSE_PASS=a-strong-password
120
+ PULSE_SECRET=a-long-random-string
121
+ ```
122
+
123
+ To create a random secret: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
124
+
125
+ ### Step 5: Open the dashboard
126
+
127
+ ```text
128
+ http://localhost:3000/monitor
129
+ ```
130
+
131
+ Sign in with `PULSE_USER` / `PULSE_PASS`. In production this is your API's own URL, for example `https://api.example.com/monitor`. Serve it over HTTPS.
132
+
133
+ ### Rules to follow
134
+
135
+ 1. Register `app.use(pulse)` **first**, at the application root, before any other middleware, especially before `compression()` (compression buffers the live updates).
136
+ 2. Do **not** mount it under a sub-path: `app.use('/admin', pulse)` is not supported. To change the URL, use the `basePath` option instead:
137
+
138
+ ```js
139
+ serverPulse({ basePath: '/admin/monitor', auth: { username: '...', password: '...' } });
140
+ ```
141
+
142
+ 3. Call `serverPulse()` only once per process and reuse the returned `pulse` object.
143
+
144
+ ### Update to a newer version
145
+
146
+ ```bash
147
+ npm install @pulse-labs/server-pulse@latest # update
148
+ npm ls @pulse-labs/server-pulse # show the installed version
149
+ ```
150
+
151
+ ## PM2 cluster
152
+
153
+ _TODO (M9). The monitor process is started with `./node_modules/@pulse-labs/server-pulse/dist/cli.cjs monitor --port 9100`._
154
+
155
+ ## Options
156
+
157
+ _TODO (M2)._
158
+
159
+ ## Environment variables
160
+
161
+ _TODO (M2)._
162
+
163
+ ## Nginx
164
+
165
+ _TODO._
166
+
167
+ ## Security notes
168
+
169
+ _TODO._
170
+
171
+ ## Troubleshooting
172
+
173
+ _TODO._
174
+
175
+ ## Development
176
+
177
+ ```bash
178
+ npm install
179
+ npm run build # tsup + copy src/ui to dist/ui
180
+ npm test # vitest
181
+ npm run lint
182
+ npm run typecheck
183
+ npm run smoke # smoke test of the built package
184
+ ```
185
+
186
+ ## Publishing
187
+
188
+ Published publicly to npm as `@pulse-labs/server-pulse` (MIT).
189
+
190
+ ```bash
191
+ npm login # once per machine
192
+ npm whoami # your account must be a member of the pulse-labs npm organization
193
+ npm pack --dry-run # review the files that will be published
194
+ npm publish # runs lint, typecheck and tests first (prepublishOnly)
195
+ ```
196
+
197
+ Next release: `npm version patch|minor|major` (updates package.json and creates a git tag), update CHANGELOG.md, then `npm publish`. A published version number can never be reused, even after unpublishing.
198
+
199
+ ## License
200
+
201
+ MIT, see [LICENSE](LICENSE).
package/dist/cli.cjs ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // package.json
5
+ var version = "0.0.2";
6
+
7
+ // src/cli.ts
8
+ var USAGE = `server-pulse ${version}
9
+
10
+ Usage:
11
+ server-pulse monitor [--port 9100] [--host 127.0.0.1]
12
+ `;
13
+ function main(argv) {
14
+ const [command] = argv;
15
+ if (command === "--version" || command === "-v") {
16
+ process.stdout.write(version + "\n");
17
+ return 0;
18
+ }
19
+ if (command === "monitor") {
20
+ process.stderr.write("server-pulse: the monitor command is not implemented yet.\n");
21
+ return 1;
22
+ }
23
+ process.stdout.write(USAGE);
24
+ return command === void 0 || command === "--help" || command === "-h" ? 0 : 1;
25
+ }
26
+ process.exitCode = main(process.argv.slice(2));
27
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../package.json","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"@pulse-labs/server-pulse\",\n \"version\": \"0.0.2\",\n \"license\": \"MIT\",\n \"description\": \"Live server monitoring dashboard for Node.js apps, served from your own base URL.\",\n \"engines\": {\n \"node\": \">=18.15\"\n },\n \"type\": \"commonjs\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.mts\",\n \"default\": \"./dist/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"bin\": {\n \"server-pulse\": \"dist/cli.cjs\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup && node scripts/copy-ui.mjs\",\n \"prepare\": \"npm run build\",\n \"prepublishOnly\": \"npm run lint && npm run typecheck && npm test\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint .\",\n \"format\": \"prettier --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"smoke\": \"node scripts/smoke-dist.mjs\",\n \"test:pm2\": \"vitest run test/integration/pm2.test.ts\",\n \"test:ui\": \"node test/ui/run.mjs\",\n \"bench\": \"node bench/overhead.mjs\"\n },\n \"peerDependencies\": {\n \"pm2\": \">=5\"\n },\n \"peerDependenciesMeta\": {\n \"pm2\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.5\",\n \"@types/express\": \"^5.0.6\",\n \"@types/node\": \"^20.19.43\",\n \"@types/supertest\": \"^7.2.1\",\n \"@vitest/coverage-v8\": \"^4.1.11\",\n \"eslint\": \"^9.39.5\",\n \"express4\": \"npm:express@^4.22.3\",\n \"express5\": \"npm:express@^5.3.0\",\n \"globals\": \"^17.13.0\",\n \"prettier\": \"^3.9.10\",\n \"supertest\": \"^7.3.1\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"~5.9.3\",\n \"typescript-eslint\": \"^8.71.2\",\n \"vite\": \"^6.4.4\",\n \"vitest\": \"^4.1.11\"\n },\n \"overrides\": {\n \"tsup\": {\n \"esbuild\": \"^0.28.2\",\n \"bundle-require\": {\n \"esbuild\": \"^0.28.2\"\n }\n }\n },\n \"keywords\": [\n \"monitoring\",\n \"dashboard\",\n \"express\",\n \"nestjs\",\n \"pm2\",\n \"metrics\",\n \"sse\"\n ]\n}\n","#!/usr/bin/env node\n// `server-pulse monitor [--port 9100] [--host 127.0.0.1]` — the PM2 monitor process (spec 11.4).\n// M0 scaffold: argument handling only; the monitor itself is implemented in M9.\nimport { version } from '../package.json';\n\nconst USAGE = `server-pulse ${version}\n\nUsage:\n server-pulse monitor [--port 9100] [--host 127.0.0.1]\n`;\n\nfunction main(argv: string[]): number {\n const [command] = argv;\n if (command === '--version' || command === '-v') {\n process.stdout.write(version + '\\n');\n return 0;\n }\n if (command === 'monitor') {\n process.stderr.write('server-pulse: the monitor command is not implemented yet.\\n');\n return 1;\n }\n process.stdout.write(USAGE);\n return command === undefined || command === '--help' || command === '-h' ? 0 : 1;\n}\n\nprocess.exitCode = main(process.argv.slice(2));\n"],"mappings":";;;;AAEE,cAAW;;;ACGb,IAAM,QAAQ,gBAAgB,OAAO;AAAA;AAAA;AAAA;AAAA;AAMrC,SAAS,KAAK,MAAwB;AACpC,QAAM,CAAC,OAAO,IAAI;AAClB,MAAI,YAAY,eAAe,YAAY,MAAM;AAC/C,YAAQ,OAAO,MAAM,UAAU,IAAI;AACnC,WAAO;AAAA,EACT;AACA,MAAI,YAAY,WAAW;AACzB,YAAQ,OAAO,MAAM,6DAA6D;AAClF,WAAO;AAAA,EACT;AACA,UAAQ,OAAO,MAAM,KAAK;AAC1B,SAAO,YAAY,UAAa,YAAY,YAAY,YAAY,OAAO,IAAI;AACjF;AAEA,QAAQ,WAAW,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC;","names":[]}
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.mjs ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res, err) => function __init() {
4
+ if (err) throw err[0];
5
+ try {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ } catch (e) {
8
+ throw err = [e], e;
9
+ }
10
+ };
11
+ var __commonJS = (cb, mod) => function __require() {
12
+ try {
13
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
14
+ } catch (e) {
15
+ throw mod = 0, e;
16
+ }
17
+ };
18
+
19
+ // node_modules/tsup/assets/esm_shims.js
20
+ import path from "path";
21
+ import { fileURLToPath } from "url";
22
+ var init_esm_shims = __esm({
23
+ "node_modules/tsup/assets/esm_shims.js"() {
24
+ "use strict";
25
+ }
26
+ });
27
+
28
+ // package.json
29
+ var version;
30
+ var init_package = __esm({
31
+ "package.json"() {
32
+ version = "0.0.2";
33
+ }
34
+ });
35
+
36
+ // src/cli.ts
37
+ var require_cli = __commonJS({
38
+ "src/cli.ts"() {
39
+ init_esm_shims();
40
+ init_package();
41
+ var USAGE = `server-pulse ${version}
42
+
43
+ Usage:
44
+ server-pulse monitor [--port 9100] [--host 127.0.0.1]
45
+ `;
46
+ function main(argv) {
47
+ const [command] = argv;
48
+ if (command === "--version" || command === "-v") {
49
+ process.stdout.write(version + "\n");
50
+ return 0;
51
+ }
52
+ if (command === "monitor") {
53
+ process.stderr.write("server-pulse: the monitor command is not implemented yet.\n");
54
+ return 1;
55
+ }
56
+ process.stdout.write(USAGE);
57
+ return command === void 0 || command === "--help" || command === "-h" ? 0 : 1;
58
+ }
59
+ process.exitCode = main(process.argv.slice(2));
60
+ }
61
+ });
62
+ export default require_cli();
63
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../package.json","../src/cli.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","{\n \"name\": \"@pulse-labs/server-pulse\",\n \"version\": \"0.0.2\",\n \"license\": \"MIT\",\n \"description\": \"Live server monitoring dashboard for Node.js apps, served from your own base URL.\",\n \"engines\": {\n \"node\": \">=18.15\"\n },\n \"type\": \"commonjs\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.mts\",\n \"default\": \"./dist/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"bin\": {\n \"server-pulse\": \"dist/cli.cjs\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup && node scripts/copy-ui.mjs\",\n \"prepare\": \"npm run build\",\n \"prepublishOnly\": \"npm run lint && npm run typecheck && npm test\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint .\",\n \"format\": \"prettier --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"smoke\": \"node scripts/smoke-dist.mjs\",\n \"test:pm2\": \"vitest run test/integration/pm2.test.ts\",\n \"test:ui\": \"node test/ui/run.mjs\",\n \"bench\": \"node bench/overhead.mjs\"\n },\n \"peerDependencies\": {\n \"pm2\": \">=5\"\n },\n \"peerDependenciesMeta\": {\n \"pm2\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.5\",\n \"@types/express\": \"^5.0.6\",\n \"@types/node\": \"^20.19.43\",\n \"@types/supertest\": \"^7.2.1\",\n \"@vitest/coverage-v8\": \"^4.1.11\",\n \"eslint\": \"^9.39.5\",\n \"express4\": \"npm:express@^4.22.3\",\n \"express5\": \"npm:express@^5.3.0\",\n \"globals\": \"^17.13.0\",\n \"prettier\": \"^3.9.10\",\n \"supertest\": \"^7.3.1\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"~5.9.3\",\n \"typescript-eslint\": \"^8.71.2\",\n \"vite\": \"^6.4.4\",\n \"vitest\": \"^4.1.11\"\n },\n \"overrides\": {\n \"tsup\": {\n \"esbuild\": \"^0.28.2\",\n \"bundle-require\": {\n \"esbuild\": \"^0.28.2\"\n }\n }\n },\n \"keywords\": [\n \"monitoring\",\n \"dashboard\",\n \"express\",\n \"nestjs\",\n \"pm2\",\n \"metrics\",\n \"sse\"\n ]\n}\n","#!/usr/bin/env node\n// `server-pulse monitor [--port 9100] [--host 127.0.0.1]` — the PM2 monitor process (spec 11.4).\n// M0 scaffold: argument handling only; the monitor itself is implemented in M9.\nimport { version } from '../package.json';\n\nconst USAGE = `server-pulse ${version}\n\nUsage:\n server-pulse monitor [--port 9100] [--host 127.0.0.1]\n`;\n\nfunction main(argv: string[]): number {\n const [command] = argv;\n if (command === '--version' || command === '-v') {\n process.stdout.write(version + '\\n');\n return 0;\n }\n if (command === 'monitor') {\n process.stderr.write('server-pulse: the monitor command is not implemented yet.\\n');\n return 1;\n }\n process.stdout.write(USAGE);\n return command === undefined || command === '--help' || command === '-h' ? 0 : 1;\n}\n\nprocess.exitCode = main(process.argv.slice(2));\n"],"mappings":";;;;;;;;;;;;;;;;;;;AACA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAF9B;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAEE;AAFF;AAAA;AAEE,cAAW;AAAA;AAAA;;;ACFb;AAAA;AAAA;AAGA;AAEA,QAAM,QAAQ,gBAAgB,OAAO;AAAA;AAAA;AAAA;AAAA;AAMrC,aAAS,KAAK,MAAwB;AACpC,YAAM,CAAC,OAAO,IAAI;AAClB,UAAI,YAAY,eAAe,YAAY,MAAM;AAC/C,gBAAQ,OAAO,MAAM,UAAU,IAAI;AACnC,eAAO;AAAA,MACT;AACA,UAAI,YAAY,WAAW;AACzB,gBAAQ,OAAO,MAAM,6DAA6D;AAClF,eAAO;AAAA,MACT;AACA,cAAQ,OAAO,MAAM,KAAK;AAC1B,aAAO,YAAY,UAAa,YAAY,YAAY,YAAY,OAAO,IAAI;AAAA,IACjF;AAEA,YAAQ,WAAW,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC;AAAA;AAAA;","names":[]}
package/dist/index.cjs ADDED
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ serverPulse: () => serverPulse
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+ var import_node_os = __toESM(require("os"));
37
+
38
+ // package.json
39
+ var version = "0.0.2";
40
+
41
+ // src/index.ts
42
+ var DEFAULT_THRESHOLDS = {
43
+ cpuPercent: [70, 85],
44
+ memoryPercent: [75, 90],
45
+ diskPercent: [80, 90],
46
+ heapPercent: [70, 85],
47
+ eventLoopLagMs: [50, 100],
48
+ errorRatePercent: [1, 3]
49
+ };
50
+ var BUCKETS_WIRE = [5, 10, 25, 50, 75, 100, 150, 250, 350, 500, 750, 1e3, 1500, 2500, 5e3, 1e4, 3e4, null];
51
+ function serverPulse(_options) {
52
+ const startedAt = Date.now();
53
+ const middleware = ((_req, _res, next) => {
54
+ next();
55
+ });
56
+ middleware.errorHandler = () => (err, _req, _res, next) => next(err);
57
+ middleware.captureError = () => {
58
+ };
59
+ middleware.snapshot = () => ({
60
+ meta: {
61
+ version,
62
+ title: import_node_os.default.hostname(),
63
+ host: import_node_os.default.hostname(),
64
+ node: process.version,
65
+ platform: process.platform,
66
+ mode: "memory",
67
+ workers: 1,
68
+ appStartedAt: Math.round(Date.now() - process.uptime() * 1e3),
69
+ monitorStartedAt: startedAt,
70
+ cores: import_node_os.default.availableParallelism?.() ?? import_node_os.default.cpus().length
71
+ },
72
+ config: {
73
+ buckets: [...BUCKETS_WIRE],
74
+ defaultTargetMs: 500,
75
+ apdexThresholdMs: 300,
76
+ thresholds: structuredClone(DEFAULT_THRESHOLDS),
77
+ windowSeconds: 900,
78
+ user: ""
79
+ },
80
+ totals: { requests: 0, since: startedAt },
81
+ history: [],
82
+ routes: [],
83
+ workers: [],
84
+ errors: []
85
+ });
86
+ middleware.stop = async () => {
87
+ };
88
+ Object.defineProperty(middleware, "mode", { value: "disabled", enumerable: true });
89
+ return middleware;
90
+ }
91
+ // Annotate the CommonJS export names for ESM import in node:
92
+ 0 && (module.exports = {
93
+ serverPulse
94
+ });
95
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../package.json"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport os from 'node:os';\nimport type { ServerPulseOptions, Thresholds } from './options';\nimport type { InitPayload, TickPayload } from './engine/payloads';\nimport { version } from '../package.json';\n\nexport interface ServerPulseMiddleware {\n (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void): void;\n /** Express error middleware that records the error, then calls next(err). */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n errorHandler(): (err: unknown, req: any, res: any, next: (err?: unknown) => void) => void;\n /** Record an error manually (e.g. from a NestJS exception filter). */\n captureError(err: unknown, req?: IncomingMessage): void;\n /** Current snapshot (same shape as the SSE init payload). For tests and scripts. */\n snapshot(): InitPayload;\n /** Stop timers, close SSE connections, disable event loop monitoring. */\n stop(): Promise<void>;\n /** Resolved mode: 'memory' | 'pm2' | 'disabled'. */\n readonly mode: 'memory' | 'pm2' | 'disabled';\n}\n\n// M0 scaffold: pass-through middleware. Collection, dashboard and PM2 mode arrive in later milestones.\nconst DEFAULT_THRESHOLDS: Thresholds = {\n cpuPercent: [70, 85],\n memoryPercent: [75, 90],\n diskPercent: [80, 90],\n heapPercent: [70, 85],\n eventLoopLagMs: [50, 100],\n errorRatePercent: [1, 3],\n};\n\nconst BUCKETS_WIRE = [5, 10, 25, 50, 75, 100, 150, 250, 350, 500, 750, 1000, 1500, 2500, 5000, 10000, 30000, null];\n\nexport function serverPulse(_options?: ServerPulseOptions): ServerPulseMiddleware {\n const startedAt = Date.now();\n\n const middleware = ((_req: IncomingMessage, _res: ServerResponse, next: (err?: unknown) => void) => {\n next();\n }) as ServerPulseMiddleware;\n\n middleware.errorHandler = () => (err, _req, _res, next) => next(err);\n middleware.captureError = () => {};\n middleware.snapshot = () => ({\n meta: {\n version,\n title: os.hostname(),\n host: os.hostname(),\n node: process.version,\n platform: process.platform,\n mode: 'memory',\n workers: 1,\n appStartedAt: Math.round(Date.now() - process.uptime() * 1000),\n monitorStartedAt: startedAt,\n cores: os.availableParallelism?.() ?? os.cpus().length,\n },\n config: {\n buckets: [...BUCKETS_WIRE],\n defaultTargetMs: 500,\n apdexThresholdMs: 300,\n thresholds: structuredClone(DEFAULT_THRESHOLDS),\n windowSeconds: 900,\n user: '',\n },\n totals: { requests: 0, since: startedAt },\n history: [],\n routes: [],\n workers: [],\n errors: [],\n });\n middleware.stop = async () => {};\n Object.defineProperty(middleware, 'mode', { value: 'disabled', enumerable: true });\n\n return middleware;\n}\n\nexport type { ServerPulseOptions, InitPayload, TickPayload };\n","{\n \"name\": \"@pulse-labs/server-pulse\",\n \"version\": \"0.0.2\",\n \"license\": \"MIT\",\n \"description\": \"Live server monitoring dashboard for Node.js apps, served from your own base URL.\",\n \"engines\": {\n \"node\": \">=18.15\"\n },\n \"type\": \"commonjs\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.mts\",\n \"default\": \"./dist/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"bin\": {\n \"server-pulse\": \"dist/cli.cjs\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup && node scripts/copy-ui.mjs\",\n \"prepare\": \"npm run build\",\n \"prepublishOnly\": \"npm run lint && npm run typecheck && npm test\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint .\",\n \"format\": \"prettier --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"smoke\": \"node scripts/smoke-dist.mjs\",\n \"test:pm2\": \"vitest run test/integration/pm2.test.ts\",\n \"test:ui\": \"node test/ui/run.mjs\",\n \"bench\": \"node bench/overhead.mjs\"\n },\n \"peerDependencies\": {\n \"pm2\": \">=5\"\n },\n \"peerDependenciesMeta\": {\n \"pm2\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.5\",\n \"@types/express\": \"^5.0.6\",\n \"@types/node\": \"^20.19.43\",\n \"@types/supertest\": \"^7.2.1\",\n \"@vitest/coverage-v8\": \"^4.1.11\",\n \"eslint\": \"^9.39.5\",\n \"express4\": \"npm:express@^4.22.3\",\n \"express5\": \"npm:express@^5.3.0\",\n \"globals\": \"^17.13.0\",\n \"prettier\": \"^3.9.10\",\n \"supertest\": \"^7.3.1\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"~5.9.3\",\n \"typescript-eslint\": \"^8.71.2\",\n \"vite\": \"^6.4.4\",\n \"vitest\": \"^4.1.11\"\n },\n \"overrides\": {\n \"tsup\": {\n \"esbuild\": \"^0.28.2\",\n \"bundle-require\": {\n \"esbuild\": \"^0.28.2\"\n }\n }\n },\n \"keywords\": [\n \"monitoring\",\n \"dashboard\",\n \"express\",\n \"nestjs\",\n \"pm2\",\n \"metrics\",\n \"sse\"\n ]\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,qBAAe;;;ACCb,cAAW;;;ADoBb,IAAM,qBAAiC;AAAA,EACrC,YAAY,CAAC,IAAI,EAAE;AAAA,EACnB,eAAe,CAAC,IAAI,EAAE;AAAA,EACtB,aAAa,CAAC,IAAI,EAAE;AAAA,EACpB,aAAa,CAAC,IAAI,EAAE;AAAA,EACpB,gBAAgB,CAAC,IAAI,GAAG;AAAA,EACxB,kBAAkB,CAAC,GAAG,CAAC;AACzB;AAEA,IAAM,eAAe,CAAC,GAAG,IAAI,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAM,MAAM,MAAM,KAAM,KAAO,KAAO,IAAI;AAE1G,SAAS,YAAY,UAAsD;AAChF,QAAM,YAAY,KAAK,IAAI;AAE3B,QAAM,cAAc,CAAC,MAAuB,MAAsB,SAAkC;AAClG,SAAK;AAAA,EACP;AAEA,aAAW,eAAe,MAAM,CAAC,KAAK,MAAM,MAAM,SAAS,KAAK,GAAG;AACnE,aAAW,eAAe,MAAM;AAAA,EAAC;AACjC,aAAW,WAAW,OAAO;AAAA,IAC3B,MAAM;AAAA,MACJ;AAAA,MACA,OAAO,eAAAA,QAAG,SAAS;AAAA,MACnB,MAAM,eAAAA,QAAG,SAAS;AAAA,MAClB,MAAM,QAAQ;AAAA,MACd,UAAU,QAAQ;AAAA,MAClB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,QAAQ,OAAO,IAAI,GAAI;AAAA,MAC7D,kBAAkB;AAAA,MAClB,OAAO,eAAAA,QAAG,uBAAuB,KAAK,eAAAA,QAAG,KAAK,EAAE;AAAA,IAClD;AAAA,IACA,QAAQ;AAAA,MACN,SAAS,CAAC,GAAG,YAAY;AAAA,MACzB,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,MAClB,YAAY,gBAAgB,kBAAkB;AAAA,MAC9C,eAAe;AAAA,MACf,MAAM;AAAA,IACR;AAAA,IACA,QAAQ,EAAE,UAAU,GAAG,OAAO,UAAU;AAAA,IACxC,SAAS,CAAC;AAAA,IACV,QAAQ,CAAC;AAAA,IACT,SAAS,CAAC;AAAA,IACV,QAAQ,CAAC;AAAA,EACX;AACA,aAAW,OAAO,YAAY;AAAA,EAAC;AAC/B,SAAO,eAAe,YAAY,QAAQ,EAAE,OAAO,YAAY,YAAY,KAAK,CAAC;AAEjF,SAAO;AACT;","names":["os"]}
@@ -0,0 +1,244 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+
3
+ /** Warning / danger thresholds used for colours and the health badge (spec 4.3). */
4
+ interface Thresholds {
5
+ /** Default [70, 85]. */
6
+ cpuPercent: [warn: number, danger: number];
7
+ /** Default [75, 90]. */
8
+ memoryPercent: [warn: number, danger: number];
9
+ /** Default [80, 90]. */
10
+ diskPercent: [warn: number, danger: number];
11
+ /** Default [70, 85] (of heap_size_limit). */
12
+ heapPercent: [warn: number, danger: number];
13
+ /** Default [50, 100] (p99 per second). */
14
+ eventLoopLagMs: [warn: number, danger: number];
15
+ /** Default [1, 3] (5xx share). */
16
+ errorRatePercent: [warn: number, danger: number];
17
+ }
18
+ interface AuthCommon {
19
+ sessionSecret?: string;
20
+ sessionTtlMinutes?: number;
21
+ secureCookie?: 'auto' | boolean;
22
+ }
23
+ interface ServerPulseOptions {
24
+ /** Turn everything off (middleware becomes a pass-through). Default: true. Env: PULSE_ENABLED=false */
25
+ enabled?: boolean;
26
+ /** URL path of the dashboard. Default: '/monitor'. Must start with '/', no trailing slash. */
27
+ basePath?: string;
28
+ /** 'auto' | 'memory' | 'pm2'. Default: 'auto' (section 11.1). Env: PULSE_MODE */
29
+ mode?: 'auto' | 'memory' | 'pm2';
30
+ /**
31
+ * Dashboard credentials. Either one user, a list of users, or false.
32
+ * Env fallbacks: PULSE_USER, PULSE_PASS, PULSE_SECRET.
33
+ * false is only allowed when NODE_ENV !== 'production'.
34
+ */
35
+ auth?: ({
36
+ username: string;
37
+ password: string;
38
+ } & AuthCommon) | ({
39
+ users: Array<{
40
+ username: string;
41
+ password: string;
42
+ }>;
43
+ } & AuthCommon) | false;
44
+ /** Only these client IPs may reach the dashboard (exact IPv4/IPv6 or IPv4 CIDR). Others get 404. Default: allow all. */
45
+ ipAllowList?: string[];
46
+ /** Trust X-Forwarded-For / X-Forwarded-Proto (set true behind Nginx or a load balancer). Default: false. */
47
+ trustProxy?: boolean;
48
+ /** Extra requests to exclude from statistics. basePath is always excluded. */
49
+ ignore?: Array<string | RegExp | ((req: IncomingMessage) => boolean)>;
50
+ /** Apply the default ignore list (/health, /favicon.ico, static files). Default: true. */
51
+ ignoreDefaults?: boolean;
52
+ routes?: {
53
+ /** Per-route response-time targets in ms, keyed "METHOD /normalized/path". */
54
+ targets?: Record<string, number>;
55
+ /** Target for routes not listed above. Default: 500. */
56
+ defaultTargetMs?: number;
57
+ /** Maximum distinct routes tracked; extra routes go to "OTHER". Default: 500. */
58
+ maxRoutes?: number;
59
+ /** Custom normalizer; return null to fall back to the built-in one. */
60
+ normalize?: (req: IncomingMessage) => string | null;
61
+ };
62
+ /** Apdex threshold T in ms. Default: 300. */
63
+ apdexThresholdMs?: number;
64
+ /** Warning / danger thresholds used for colours and the health badge (section 4.3). */
65
+ thresholds?: Partial<Thresholds>;
66
+ /** Path used for the disk metric. Default: '/' (Windows: drive of process.cwd()). */
67
+ diskPath?: string;
68
+ pm2?: {
69
+ /** Monitor process host and port. Defaults: '127.0.0.1', 9100. Env: PULSE_MONITOR_HOST, PULSE_MONITOR_PORT */
70
+ monitorHost?: string;
71
+ monitorPort?: number;
72
+ };
73
+ ui?: {
74
+ /** Title shown in the header. Default: os.hostname(). */
75
+ title?: string;
76
+ /** Load IBM Plex Sans from Google Fonts. Default: false (system font stack, no external requests). */
77
+ webFonts?: boolean;
78
+ };
79
+ /** Optional health endpoint, outside basePath, no auth. Default: false. */
80
+ health?: false | {
81
+ path?: string;
82
+ checks?: Record<string, () => boolean | Promise<boolean>>;
83
+ };
84
+ /** Logger. Default: console with "[server-pulse]" prefix. */
85
+ logger?: Pick<Console, 'info' | 'warn' | 'error'>;
86
+ }
87
+
88
+ /** Histogram counts, length 18, in BUCKETS order. */
89
+ type Hist = number[];
90
+ /** One second of aggregated data (spec 7.1). */
91
+ interface Tick {
92
+ /** Epoch ms, start of the second. */
93
+ t: number;
94
+ req: {
95
+ /** Finished requests in this second. */
96
+ total: number;
97
+ c2: number;
98
+ c3: number;
99
+ c4: number;
100
+ c5: number;
101
+ aborted: number;
102
+ /** Value at the end of the second. */
103
+ inFlight: number;
104
+ };
105
+ /** Per-second histograms. */
106
+ hist: {
107
+ all: Hist;
108
+ ok: Hist;
109
+ fail: Hist;
110
+ };
111
+ apdex: {
112
+ s: number;
113
+ t: number;
114
+ f: number;
115
+ };
116
+ sys: {
117
+ /** Percent. */
118
+ cpu: number;
119
+ /** Bytes. */
120
+ memUsed: number;
121
+ memTotal: number;
122
+ memKind: 'system' | 'container';
123
+ /** Bytes; null while the disk metric is unavailable (see DECISIONS.md). */
124
+ diskUsed: number | null;
125
+ diskTotal: number | null;
126
+ load1: number | null;
127
+ };
128
+ /** Aggregated for the resources panel. */
129
+ proc: {
130
+ /** Busiest worker. */
131
+ heapPct: number;
132
+ /** Bytes, busiest worker. */
133
+ heapUsed: number;
134
+ heapLimit: number;
135
+ /** Ms, worst worker. */
136
+ lagP99: number;
137
+ };
138
+ }
139
+ interface RouteRow {
140
+ key: string;
141
+ method: string;
142
+ path: string;
143
+ count: number;
144
+ rps: number;
145
+ avg: number;
146
+ p95: number | null;
147
+ target: number;
148
+ errRate: number;
149
+ }
150
+ interface WorkerRow {
151
+ /** pm_id */
152
+ id: number;
153
+ pid: number;
154
+ status: 'online' | 'blocked' | 'stale' | 'stopped' | 'errored';
155
+ /** % of one core. */
156
+ cpu: number;
157
+ heapUsed: number;
158
+ heapLimit: number;
159
+ lagP99: number;
160
+ /** Last 10 s. */
161
+ rps: number;
162
+ /** pm2_env.restart_time */
163
+ restarts: number;
164
+ uptimeSec: number;
165
+ }
166
+ interface ErrorEntry {
167
+ t: number;
168
+ method: string;
169
+ route: string;
170
+ status: number;
171
+ message: string;
172
+ worker: number | null;
173
+ }
174
+ /** SSE `init` event and `GET basePath/api/snapshot` (spec 7.2). */
175
+ interface InitPayload {
176
+ meta: {
177
+ /** Package version. */
178
+ version: string;
179
+ /** ui.title or hostname. */
180
+ title: string;
181
+ host: string;
182
+ node: string;
183
+ platform: string;
184
+ mode: 'memory' | 'pm2';
185
+ /** 1 in memory mode. */
186
+ workers: number;
187
+ /** Epoch ms (oldest worker in PM2 mode). */
188
+ appStartedAt: number;
189
+ /** Epoch ms ("Requests since start" counts from here). */
190
+ monitorStartedAt: number;
191
+ cores: number;
192
+ };
193
+ config: {
194
+ /** BUCKETS, Infinity sent as null. */
195
+ buckets: Array<number | null>;
196
+ defaultTargetMs: number;
197
+ apdexThresholdMs: number;
198
+ thresholds: Thresholds;
199
+ windowSeconds: 900;
200
+ /** Logged-in username, shown in header menu. */
201
+ user: string;
202
+ };
203
+ totals: {
204
+ requests: number;
205
+ since: number;
206
+ };
207
+ /** Up to 900, oldest first. */
208
+ history: Tick[];
209
+ routes: RouteRow[];
210
+ /** Empty in memory mode. */
211
+ workers: WorkerRow[];
212
+ /** Newest first, up to 50. */
213
+ errors: ErrorEntry[];
214
+ }
215
+ /** SSE `tick` event, every second (spec 7.3). */
216
+ interface TickPayload {
217
+ tick: Tick;
218
+ totals: {
219
+ requests: number;
220
+ since: number;
221
+ };
222
+ /** Full table (≤ maxRoutes rows). */
223
+ routes: RouteRow[];
224
+ workers: WorkerRow[];
225
+ /** Errors since previous tick. */
226
+ newErrors: ErrorEntry[];
227
+ }
228
+
229
+ interface ServerPulseMiddleware {
230
+ (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void): void;
231
+ /** Express error middleware that records the error, then calls next(err). */
232
+ errorHandler(): (err: unknown, req: any, res: any, next: (err?: unknown) => void) => void;
233
+ /** Record an error manually (e.g. from a NestJS exception filter). */
234
+ captureError(err: unknown, req?: IncomingMessage): void;
235
+ /** Current snapshot (same shape as the SSE init payload). For tests and scripts. */
236
+ snapshot(): InitPayload;
237
+ /** Stop timers, close SSE connections, disable event loop monitoring. */
238
+ stop(): Promise<void>;
239
+ /** Resolved mode: 'memory' | 'pm2' | 'disabled'. */
240
+ readonly mode: 'memory' | 'pm2' | 'disabled';
241
+ }
242
+ declare function serverPulse(_options?: ServerPulseOptions): ServerPulseMiddleware;
243
+
244
+ export { type InitPayload, type ServerPulseMiddleware, type ServerPulseOptions, type TickPayload, serverPulse };
@@ -0,0 +1,244 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+
3
+ /** Warning / danger thresholds used for colours and the health badge (spec 4.3). */
4
+ interface Thresholds {
5
+ /** Default [70, 85]. */
6
+ cpuPercent: [warn: number, danger: number];
7
+ /** Default [75, 90]. */
8
+ memoryPercent: [warn: number, danger: number];
9
+ /** Default [80, 90]. */
10
+ diskPercent: [warn: number, danger: number];
11
+ /** Default [70, 85] (of heap_size_limit). */
12
+ heapPercent: [warn: number, danger: number];
13
+ /** Default [50, 100] (p99 per second). */
14
+ eventLoopLagMs: [warn: number, danger: number];
15
+ /** Default [1, 3] (5xx share). */
16
+ errorRatePercent: [warn: number, danger: number];
17
+ }
18
+ interface AuthCommon {
19
+ sessionSecret?: string;
20
+ sessionTtlMinutes?: number;
21
+ secureCookie?: 'auto' | boolean;
22
+ }
23
+ interface ServerPulseOptions {
24
+ /** Turn everything off (middleware becomes a pass-through). Default: true. Env: PULSE_ENABLED=false */
25
+ enabled?: boolean;
26
+ /** URL path of the dashboard. Default: '/monitor'. Must start with '/', no trailing slash. */
27
+ basePath?: string;
28
+ /** 'auto' | 'memory' | 'pm2'. Default: 'auto' (section 11.1). Env: PULSE_MODE */
29
+ mode?: 'auto' | 'memory' | 'pm2';
30
+ /**
31
+ * Dashboard credentials. Either one user, a list of users, or false.
32
+ * Env fallbacks: PULSE_USER, PULSE_PASS, PULSE_SECRET.
33
+ * false is only allowed when NODE_ENV !== 'production'.
34
+ */
35
+ auth?: ({
36
+ username: string;
37
+ password: string;
38
+ } & AuthCommon) | ({
39
+ users: Array<{
40
+ username: string;
41
+ password: string;
42
+ }>;
43
+ } & AuthCommon) | false;
44
+ /** Only these client IPs may reach the dashboard (exact IPv4/IPv6 or IPv4 CIDR). Others get 404. Default: allow all. */
45
+ ipAllowList?: string[];
46
+ /** Trust X-Forwarded-For / X-Forwarded-Proto (set true behind Nginx or a load balancer). Default: false. */
47
+ trustProxy?: boolean;
48
+ /** Extra requests to exclude from statistics. basePath is always excluded. */
49
+ ignore?: Array<string | RegExp | ((req: IncomingMessage) => boolean)>;
50
+ /** Apply the default ignore list (/health, /favicon.ico, static files). Default: true. */
51
+ ignoreDefaults?: boolean;
52
+ routes?: {
53
+ /** Per-route response-time targets in ms, keyed "METHOD /normalized/path". */
54
+ targets?: Record<string, number>;
55
+ /** Target for routes not listed above. Default: 500. */
56
+ defaultTargetMs?: number;
57
+ /** Maximum distinct routes tracked; extra routes go to "OTHER". Default: 500. */
58
+ maxRoutes?: number;
59
+ /** Custom normalizer; return null to fall back to the built-in one. */
60
+ normalize?: (req: IncomingMessage) => string | null;
61
+ };
62
+ /** Apdex threshold T in ms. Default: 300. */
63
+ apdexThresholdMs?: number;
64
+ /** Warning / danger thresholds used for colours and the health badge (section 4.3). */
65
+ thresholds?: Partial<Thresholds>;
66
+ /** Path used for the disk metric. Default: '/' (Windows: drive of process.cwd()). */
67
+ diskPath?: string;
68
+ pm2?: {
69
+ /** Monitor process host and port. Defaults: '127.0.0.1', 9100. Env: PULSE_MONITOR_HOST, PULSE_MONITOR_PORT */
70
+ monitorHost?: string;
71
+ monitorPort?: number;
72
+ };
73
+ ui?: {
74
+ /** Title shown in the header. Default: os.hostname(). */
75
+ title?: string;
76
+ /** Load IBM Plex Sans from Google Fonts. Default: false (system font stack, no external requests). */
77
+ webFonts?: boolean;
78
+ };
79
+ /** Optional health endpoint, outside basePath, no auth. Default: false. */
80
+ health?: false | {
81
+ path?: string;
82
+ checks?: Record<string, () => boolean | Promise<boolean>>;
83
+ };
84
+ /** Logger. Default: console with "[server-pulse]" prefix. */
85
+ logger?: Pick<Console, 'info' | 'warn' | 'error'>;
86
+ }
87
+
88
+ /** Histogram counts, length 18, in BUCKETS order. */
89
+ type Hist = number[];
90
+ /** One second of aggregated data (spec 7.1). */
91
+ interface Tick {
92
+ /** Epoch ms, start of the second. */
93
+ t: number;
94
+ req: {
95
+ /** Finished requests in this second. */
96
+ total: number;
97
+ c2: number;
98
+ c3: number;
99
+ c4: number;
100
+ c5: number;
101
+ aborted: number;
102
+ /** Value at the end of the second. */
103
+ inFlight: number;
104
+ };
105
+ /** Per-second histograms. */
106
+ hist: {
107
+ all: Hist;
108
+ ok: Hist;
109
+ fail: Hist;
110
+ };
111
+ apdex: {
112
+ s: number;
113
+ t: number;
114
+ f: number;
115
+ };
116
+ sys: {
117
+ /** Percent. */
118
+ cpu: number;
119
+ /** Bytes. */
120
+ memUsed: number;
121
+ memTotal: number;
122
+ memKind: 'system' | 'container';
123
+ /** Bytes; null while the disk metric is unavailable (see DECISIONS.md). */
124
+ diskUsed: number | null;
125
+ diskTotal: number | null;
126
+ load1: number | null;
127
+ };
128
+ /** Aggregated for the resources panel. */
129
+ proc: {
130
+ /** Busiest worker. */
131
+ heapPct: number;
132
+ /** Bytes, busiest worker. */
133
+ heapUsed: number;
134
+ heapLimit: number;
135
+ /** Ms, worst worker. */
136
+ lagP99: number;
137
+ };
138
+ }
139
+ interface RouteRow {
140
+ key: string;
141
+ method: string;
142
+ path: string;
143
+ count: number;
144
+ rps: number;
145
+ avg: number;
146
+ p95: number | null;
147
+ target: number;
148
+ errRate: number;
149
+ }
150
+ interface WorkerRow {
151
+ /** pm_id */
152
+ id: number;
153
+ pid: number;
154
+ status: 'online' | 'blocked' | 'stale' | 'stopped' | 'errored';
155
+ /** % of one core. */
156
+ cpu: number;
157
+ heapUsed: number;
158
+ heapLimit: number;
159
+ lagP99: number;
160
+ /** Last 10 s. */
161
+ rps: number;
162
+ /** pm2_env.restart_time */
163
+ restarts: number;
164
+ uptimeSec: number;
165
+ }
166
+ interface ErrorEntry {
167
+ t: number;
168
+ method: string;
169
+ route: string;
170
+ status: number;
171
+ message: string;
172
+ worker: number | null;
173
+ }
174
+ /** SSE `init` event and `GET basePath/api/snapshot` (spec 7.2). */
175
+ interface InitPayload {
176
+ meta: {
177
+ /** Package version. */
178
+ version: string;
179
+ /** ui.title or hostname. */
180
+ title: string;
181
+ host: string;
182
+ node: string;
183
+ platform: string;
184
+ mode: 'memory' | 'pm2';
185
+ /** 1 in memory mode. */
186
+ workers: number;
187
+ /** Epoch ms (oldest worker in PM2 mode). */
188
+ appStartedAt: number;
189
+ /** Epoch ms ("Requests since start" counts from here). */
190
+ monitorStartedAt: number;
191
+ cores: number;
192
+ };
193
+ config: {
194
+ /** BUCKETS, Infinity sent as null. */
195
+ buckets: Array<number | null>;
196
+ defaultTargetMs: number;
197
+ apdexThresholdMs: number;
198
+ thresholds: Thresholds;
199
+ windowSeconds: 900;
200
+ /** Logged-in username, shown in header menu. */
201
+ user: string;
202
+ };
203
+ totals: {
204
+ requests: number;
205
+ since: number;
206
+ };
207
+ /** Up to 900, oldest first. */
208
+ history: Tick[];
209
+ routes: RouteRow[];
210
+ /** Empty in memory mode. */
211
+ workers: WorkerRow[];
212
+ /** Newest first, up to 50. */
213
+ errors: ErrorEntry[];
214
+ }
215
+ /** SSE `tick` event, every second (spec 7.3). */
216
+ interface TickPayload {
217
+ tick: Tick;
218
+ totals: {
219
+ requests: number;
220
+ since: number;
221
+ };
222
+ /** Full table (≤ maxRoutes rows). */
223
+ routes: RouteRow[];
224
+ workers: WorkerRow[];
225
+ /** Errors since previous tick. */
226
+ newErrors: ErrorEntry[];
227
+ }
228
+
229
+ interface ServerPulseMiddleware {
230
+ (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void): void;
231
+ /** Express error middleware that records the error, then calls next(err). */
232
+ errorHandler(): (err: unknown, req: any, res: any, next: (err?: unknown) => void) => void;
233
+ /** Record an error manually (e.g. from a NestJS exception filter). */
234
+ captureError(err: unknown, req?: IncomingMessage): void;
235
+ /** Current snapshot (same shape as the SSE init payload). For tests and scripts. */
236
+ snapshot(): InitPayload;
237
+ /** Stop timers, close SSE connections, disable event loop monitoring. */
238
+ stop(): Promise<void>;
239
+ /** Resolved mode: 'memory' | 'pm2' | 'disabled'. */
240
+ readonly mode: 'memory' | 'pm2' | 'disabled';
241
+ }
242
+ declare function serverPulse(_options?: ServerPulseOptions): ServerPulseMiddleware;
243
+
244
+ export { type InitPayload, type ServerPulseMiddleware, type ServerPulseOptions, type TickPayload, serverPulse };
package/dist/index.mjs ADDED
@@ -0,0 +1,60 @@
1
+ // src/index.ts
2
+ import os from "os";
3
+
4
+ // package.json
5
+ var version = "0.0.2";
6
+
7
+ // src/index.ts
8
+ var DEFAULT_THRESHOLDS = {
9
+ cpuPercent: [70, 85],
10
+ memoryPercent: [75, 90],
11
+ diskPercent: [80, 90],
12
+ heapPercent: [70, 85],
13
+ eventLoopLagMs: [50, 100],
14
+ errorRatePercent: [1, 3]
15
+ };
16
+ var BUCKETS_WIRE = [5, 10, 25, 50, 75, 100, 150, 250, 350, 500, 750, 1e3, 1500, 2500, 5e3, 1e4, 3e4, null];
17
+ function serverPulse(_options) {
18
+ const startedAt = Date.now();
19
+ const middleware = ((_req, _res, next) => {
20
+ next();
21
+ });
22
+ middleware.errorHandler = () => (err, _req, _res, next) => next(err);
23
+ middleware.captureError = () => {
24
+ };
25
+ middleware.snapshot = () => ({
26
+ meta: {
27
+ version,
28
+ title: os.hostname(),
29
+ host: os.hostname(),
30
+ node: process.version,
31
+ platform: process.platform,
32
+ mode: "memory",
33
+ workers: 1,
34
+ appStartedAt: Math.round(Date.now() - process.uptime() * 1e3),
35
+ monitorStartedAt: startedAt,
36
+ cores: os.availableParallelism?.() ?? os.cpus().length
37
+ },
38
+ config: {
39
+ buckets: [...BUCKETS_WIRE],
40
+ defaultTargetMs: 500,
41
+ apdexThresholdMs: 300,
42
+ thresholds: structuredClone(DEFAULT_THRESHOLDS),
43
+ windowSeconds: 900,
44
+ user: ""
45
+ },
46
+ totals: { requests: 0, since: startedAt },
47
+ history: [],
48
+ routes: [],
49
+ workers: [],
50
+ errors: []
51
+ });
52
+ middleware.stop = async () => {
53
+ };
54
+ Object.defineProperty(middleware, "mode", { value: "disabled", enumerable: true });
55
+ return middleware;
56
+ }
57
+ export {
58
+ serverPulse
59
+ };
60
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../package.json"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport os from 'node:os';\nimport type { ServerPulseOptions, Thresholds } from './options';\nimport type { InitPayload, TickPayload } from './engine/payloads';\nimport { version } from '../package.json';\n\nexport interface ServerPulseMiddleware {\n (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void): void;\n /** Express error middleware that records the error, then calls next(err). */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n errorHandler(): (err: unknown, req: any, res: any, next: (err?: unknown) => void) => void;\n /** Record an error manually (e.g. from a NestJS exception filter). */\n captureError(err: unknown, req?: IncomingMessage): void;\n /** Current snapshot (same shape as the SSE init payload). For tests and scripts. */\n snapshot(): InitPayload;\n /** Stop timers, close SSE connections, disable event loop monitoring. */\n stop(): Promise<void>;\n /** Resolved mode: 'memory' | 'pm2' | 'disabled'. */\n readonly mode: 'memory' | 'pm2' | 'disabled';\n}\n\n// M0 scaffold: pass-through middleware. Collection, dashboard and PM2 mode arrive in later milestones.\nconst DEFAULT_THRESHOLDS: Thresholds = {\n cpuPercent: [70, 85],\n memoryPercent: [75, 90],\n diskPercent: [80, 90],\n heapPercent: [70, 85],\n eventLoopLagMs: [50, 100],\n errorRatePercent: [1, 3],\n};\n\nconst BUCKETS_WIRE = [5, 10, 25, 50, 75, 100, 150, 250, 350, 500, 750, 1000, 1500, 2500, 5000, 10000, 30000, null];\n\nexport function serverPulse(_options?: ServerPulseOptions): ServerPulseMiddleware {\n const startedAt = Date.now();\n\n const middleware = ((_req: IncomingMessage, _res: ServerResponse, next: (err?: unknown) => void) => {\n next();\n }) as ServerPulseMiddleware;\n\n middleware.errorHandler = () => (err, _req, _res, next) => next(err);\n middleware.captureError = () => {};\n middleware.snapshot = () => ({\n meta: {\n version,\n title: os.hostname(),\n host: os.hostname(),\n node: process.version,\n platform: process.platform,\n mode: 'memory',\n workers: 1,\n appStartedAt: Math.round(Date.now() - process.uptime() * 1000),\n monitorStartedAt: startedAt,\n cores: os.availableParallelism?.() ?? os.cpus().length,\n },\n config: {\n buckets: [...BUCKETS_WIRE],\n defaultTargetMs: 500,\n apdexThresholdMs: 300,\n thresholds: structuredClone(DEFAULT_THRESHOLDS),\n windowSeconds: 900,\n user: '',\n },\n totals: { requests: 0, since: startedAt },\n history: [],\n routes: [],\n workers: [],\n errors: [],\n });\n middleware.stop = async () => {};\n Object.defineProperty(middleware, 'mode', { value: 'disabled', enumerable: true });\n\n return middleware;\n}\n\nexport type { ServerPulseOptions, InitPayload, TickPayload };\n","{\n \"name\": \"@pulse-labs/server-pulse\",\n \"version\": \"0.0.2\",\n \"license\": \"MIT\",\n \"description\": \"Live server monitoring dashboard for Node.js apps, served from your own base URL.\",\n \"engines\": {\n \"node\": \">=18.15\"\n },\n \"type\": \"commonjs\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.mts\",\n \"default\": \"./dist/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"bin\": {\n \"server-pulse\": \"dist/cli.cjs\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup && node scripts/copy-ui.mjs\",\n \"prepare\": \"npm run build\",\n \"prepublishOnly\": \"npm run lint && npm run typecheck && npm test\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint .\",\n \"format\": \"prettier --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"smoke\": \"node scripts/smoke-dist.mjs\",\n \"test:pm2\": \"vitest run test/integration/pm2.test.ts\",\n \"test:ui\": \"node test/ui/run.mjs\",\n \"bench\": \"node bench/overhead.mjs\"\n },\n \"peerDependencies\": {\n \"pm2\": \">=5\"\n },\n \"peerDependenciesMeta\": {\n \"pm2\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.5\",\n \"@types/express\": \"^5.0.6\",\n \"@types/node\": \"^20.19.43\",\n \"@types/supertest\": \"^7.2.1\",\n \"@vitest/coverage-v8\": \"^4.1.11\",\n \"eslint\": \"^9.39.5\",\n \"express4\": \"npm:express@^4.22.3\",\n \"express5\": \"npm:express@^5.3.0\",\n \"globals\": \"^17.13.0\",\n \"prettier\": \"^3.9.10\",\n \"supertest\": \"^7.3.1\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"~5.9.3\",\n \"typescript-eslint\": \"^8.71.2\",\n \"vite\": \"^6.4.4\",\n \"vitest\": \"^4.1.11\"\n },\n \"overrides\": {\n \"tsup\": {\n \"esbuild\": \"^0.28.2\",\n \"bundle-require\": {\n \"esbuild\": \"^0.28.2\"\n }\n }\n },\n \"keywords\": [\n \"monitoring\",\n \"dashboard\",\n \"express\",\n \"nestjs\",\n \"pm2\",\n \"metrics\",\n \"sse\"\n ]\n}\n"],"mappings":";AACA,OAAO,QAAQ;;;ACCb,cAAW;;;ADoBb,IAAM,qBAAiC;AAAA,EACrC,YAAY,CAAC,IAAI,EAAE;AAAA,EACnB,eAAe,CAAC,IAAI,EAAE;AAAA,EACtB,aAAa,CAAC,IAAI,EAAE;AAAA,EACpB,aAAa,CAAC,IAAI,EAAE;AAAA,EACpB,gBAAgB,CAAC,IAAI,GAAG;AAAA,EACxB,kBAAkB,CAAC,GAAG,CAAC;AACzB;AAEA,IAAM,eAAe,CAAC,GAAG,IAAI,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAM,MAAM,MAAM,KAAM,KAAO,KAAO,IAAI;AAE1G,SAAS,YAAY,UAAsD;AAChF,QAAM,YAAY,KAAK,IAAI;AAE3B,QAAM,cAAc,CAAC,MAAuB,MAAsB,SAAkC;AAClG,SAAK;AAAA,EACP;AAEA,aAAW,eAAe,MAAM,CAAC,KAAK,MAAM,MAAM,SAAS,KAAK,GAAG;AACnE,aAAW,eAAe,MAAM;AAAA,EAAC;AACjC,aAAW,WAAW,OAAO;AAAA,IAC3B,MAAM;AAAA,MACJ;AAAA,MACA,OAAO,GAAG,SAAS;AAAA,MACnB,MAAM,GAAG,SAAS;AAAA,MAClB,MAAM,QAAQ;AAAA,MACd,UAAU,QAAQ;AAAA,MAClB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,QAAQ,OAAO,IAAI,GAAI;AAAA,MAC7D,kBAAkB;AAAA,MAClB,OAAO,GAAG,uBAAuB,KAAK,GAAG,KAAK,EAAE;AAAA,IAClD;AAAA,IACA,QAAQ;AAAA,MACN,SAAS,CAAC,GAAG,YAAY;AAAA,MACzB,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,MAClB,YAAY,gBAAgB,kBAAkB;AAAA,MAC9C,eAAe;AAAA,MACf,MAAM;AAAA,IACR;AAAA,IACA,QAAQ,EAAE,UAAU,GAAG,OAAO,UAAU;AAAA,IACxC,SAAS,CAAC;AAAA,IACV,QAAQ,CAAC;AAAA,IACT,SAAS,CAAC;AAAA,IACV,QAAQ,CAAC;AAAA,EACX;AACA,aAAW,OAAO,YAAY;AAAA,EAAC;AAC/B,SAAO,eAAe,YAAY,QAAQ,EAAE,OAAO,YAAY,YAAY,KAAK,CAAC;AAEjF,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,95 @@
1
1
  {
2
2
  "name": "@pulse-labs/server-pulse",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.2",
4
+ "license": "MIT",
5
+ "description": "Live server monitoring dashboard for Node.js apps, served from your own base URL.",
6
+ "engines": {
7
+ "node": ">=18.15"
8
+ },
9
+ "type": "commonjs",
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.mts",
17
+ "default": "./dist/index.mjs"
18
+ },
19
+ "require": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.cjs"
22
+ }
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "bin": {
27
+ "server-pulse": "dist/cli.cjs"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "CHANGELOG.md"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup && node scripts/copy-ui.mjs",
39
+ "prepare": "npm run build",
40
+ "prepublishOnly": "npm run lint && npm run typecheck && npm test",
41
+ "dev": "tsup --watch",
42
+ "lint": "eslint .",
43
+ "format": "prettier --write .",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "smoke": "node scripts/smoke-dist.mjs",
48
+ "test:pm2": "vitest run test/integration/pm2.test.ts",
49
+ "test:ui": "node test/ui/run.mjs",
50
+ "bench": "node bench/overhead.mjs"
51
+ },
52
+ "peerDependencies": {
53
+ "pm2": ">=5"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "pm2": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "@eslint/js": "^9.39.5",
62
+ "@types/express": "^5.0.6",
63
+ "@types/node": "^20.19.43",
64
+ "@types/supertest": "^7.2.1",
65
+ "@vitest/coverage-v8": "^4.1.11",
66
+ "eslint": "^9.39.5",
67
+ "express4": "npm:express@^4.22.3",
68
+ "express5": "npm:express@^5.3.0",
69
+ "globals": "^17.13.0",
70
+ "prettier": "^3.9.10",
71
+ "supertest": "^7.3.1",
72
+ "tsup": "^8.5.1",
73
+ "typescript": "~5.9.3",
74
+ "typescript-eslint": "^8.71.2",
75
+ "vite": "^6.4.4",
76
+ "vitest": "^4.1.11"
77
+ },
78
+ "overrides": {
79
+ "tsup": {
80
+ "esbuild": "^0.28.2",
81
+ "bundle-require": {
82
+ "esbuild": "^0.28.2"
83
+ }
84
+ }
85
+ },
86
+ "keywords": [
87
+ "monitoring",
88
+ "dashboard",
89
+ "express",
90
+ "nestjs",
91
+ "pm2",
92
+ "metrics",
93
+ "sse"
94
+ ]
95
+ }