@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 +25 -0
- package/LICENSE +21 -0
- package/README.md +200 -2
- package/dist/cli.cjs +27 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +63 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +95 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +244 -0
- package/dist/index.d.ts +244 -0
- package/dist/index.mjs +60 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +93 -4
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
|
-
#
|
|
1
|
+
# server-pulse
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/dist/cli.cjs.map
ADDED
|
@@ -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
|
package/dist/cli.mjs.map
ADDED
|
@@ -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"]}
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
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
|
+
}
|