api-observe 1.0.0

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/express.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = require('./lib/adapters/express');
package/http.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = require('./lib/adapters/http');
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @file lib/adapters/express.js
5
+ * @description Express/Connect middleware adapter for ps-observe.
6
+ *
7
+ * Usage:
8
+ *
9
+ * const express = require('express');
10
+ * const { expressMiddleware } = require('ps-observe/express');
11
+ *
12
+ * const app = express();
13
+ * const observe = expressMiddleware({
14
+ * maxEntries: 500,
15
+ * ttlMs: 24 * 60 * 60 * 1000,
16
+ * sanitize: (data) => data,
17
+ * });
18
+ *
19
+ * app.use(observe);
20
+ *
21
+ * // Attach interceptors to your HTTP clients:
22
+ * observe.attach(myClient);
23
+ * observe.attachToAll(serviceContainer);
24
+ *
25
+ * // Access the store directly:
26
+ * observe.store;
27
+ */
28
+
29
+ const { parse: parseUrl } = require('url');
30
+ const { FailureStore } = require('../failure-store');
31
+ const { attachInterceptor, attachToAll, defaultSanitize } = require('../interceptor');
32
+ const { createRouter } = require('../router');
33
+
34
+ /**
35
+ * Create an Express middleware that captures errors and serves the observe dashboard.
36
+ *
37
+ * @param {{ maxEntries?: number, ttlMs?: number, sanitize?: function }} [opts]
38
+ * @returns {function} Express middleware with .attach(), .attachToAll(), .store, .errorHandler()
39
+ */
40
+ function expressMiddleware(opts = {}) {
41
+ const store = new FailureStore({
42
+ maxEntries: opts.maxEntries,
43
+ ttlMs: opts.ttlMs,
44
+ });
45
+
46
+ const sanitize = opts.sanitize ?? defaultSanitize;
47
+ const sanitizeOpts = opts.sanitize ? { sanitize: opts.sanitize } : {};
48
+ const router = createRouter(store);
49
+
50
+ // ── Main middleware: serves dashboard routes ─────────────────────────────
51
+ function middleware(req, res, next) {
52
+ const parsed = parseUrl(req.url, true);
53
+ const pathname = parsed.pathname;
54
+
55
+ // Only handle /observe routes
56
+ if (!pathname.startsWith('/observe')) {
57
+ next();
58
+ return;
59
+ }
60
+
61
+ const result = router(req.method, pathname, parsed.query);
62
+ if (!result) {
63
+ next();
64
+ return;
65
+ }
66
+
67
+ res.statusCode = result.status;
68
+ for (const [key, value] of Object.entries(result.headers)) {
69
+ res.setHeader(key, value);
70
+ }
71
+
72
+ const body = typeof result.body === 'string' ? result.body : JSON.stringify(result.body);
73
+ res.end(body);
74
+ }
75
+
76
+ // ── Error-capturing middleware (use AFTER your routes) ───────────────────
77
+ // Express error handlers have 4 parameters: (err, req, res, next)
78
+ function errorHandler(err, req, res, next) {
79
+ // Skip /observe routes to avoid recursive captures
80
+ const parsed = parseUrl(req.url);
81
+ if (parsed.pathname.startsWith('/observe')) {
82
+ next(err);
83
+ return;
84
+ }
85
+
86
+ store.add({
87
+ type: 'controller',
88
+ service: req.route?.path ?? req.originalUrl ?? req.url,
89
+ method: req.method,
90
+ url: req.originalUrl ?? req.url,
91
+ correlationId: req.headers['x-correlation-id'] ?? null,
92
+ durationMs: null,
93
+ errorMessage: err.message,
94
+ errorCode: err.code ?? null,
95
+ errorName: err.name ?? 'Error',
96
+ stack: err.stack ?? null,
97
+
98
+ request: {
99
+ headers: sanitize(req.headers),
100
+ params: req.params ?? null,
101
+ query: req.query ?? null,
102
+ body: sanitize(req.body),
103
+ },
104
+
105
+ statusCode: err.statusCode ?? err.status ?? 500,
106
+ response: null,
107
+ });
108
+
109
+ next(err);
110
+ }
111
+
112
+ // ── Attach helpers directly on the middleware function ───────────────────
113
+ middleware.attach = function (client) {
114
+ attachInterceptor(client, store, sanitizeOpts);
115
+ };
116
+
117
+ middleware.attachToAll = function (serviceContainer) {
118
+ attachToAll(serviceContainer, store, sanitizeOpts);
119
+ };
120
+
121
+ middleware.store = store;
122
+ middleware.errorHandler = errorHandler;
123
+
124
+ return middleware;
125
+ }
126
+
127
+ module.exports = { expressMiddleware };
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @file lib/adapters/http.js
5
+ * @description Plain Node.js http handler adapter for ps-observe.
6
+ *
7
+ * Works with any framework that exposes a standard (req, res) interface:
8
+ * plain http.createServer, Koa (via koa-connect), NestJS, Hapi, etc.
9
+ *
10
+ * Usage:
11
+ *
12
+ * const http = require('http');
13
+ * const { createHttpHandler } = require('ps-observe/http');
14
+ *
15
+ * const observe = createHttpHandler({
16
+ * maxEntries: 500,
17
+ * ttlMs: 24 * 60 * 60 * 1000,
18
+ * });
19
+ *
20
+ * const server = http.createServer((req, res) => {
21
+ * // Let observe handle /observe routes
22
+ * if (observe.handle(req, res)) return;
23
+ *
24
+ * // Your app routes...
25
+ * res.end('hello');
26
+ * });
27
+ *
28
+ * // Attach interceptors:
29
+ * observe.attach(myClient);
30
+ * observe.attachToAll(serviceContainer);
31
+ *
32
+ * // Capture an error manually:
33
+ * observe.captureError(err, { method: 'GET', url: '/foo', headers: {} });
34
+ */
35
+
36
+ const { parse: parseUrl } = require('url');
37
+ const { FailureStore } = require('../failure-store');
38
+ const { attachInterceptor, attachToAll, defaultSanitize } = require('../interceptor');
39
+ const { createRouter } = require('../router');
40
+
41
+ /**
42
+ * Create a plain Node.js HTTP handler for observe.
43
+ *
44
+ * @param {{ maxEntries?: number, ttlMs?: number, sanitize?: function }} [opts]
45
+ * @returns {object} Handler with .handle(), .attach(), .attachToAll(), .captureError(), .store
46
+ */
47
+ function createHttpHandler(opts = {}) {
48
+ const store = new FailureStore({
49
+ maxEntries: opts.maxEntries,
50
+ ttlMs: opts.ttlMs,
51
+ });
52
+
53
+ const sanitize = opts.sanitize ?? defaultSanitize;
54
+ const sanitizeOpts = opts.sanitize ? { sanitize: opts.sanitize } : {};
55
+ const router = createRouter(store);
56
+
57
+ /**
58
+ * Handle an incoming request. Returns true if the request was handled
59
+ * (i.e. it was an /observe route), false otherwise.
60
+ *
61
+ * @param {import('http').IncomingMessage} req
62
+ * @param {import('http').ServerResponse} res
63
+ * @returns {boolean}
64
+ */
65
+ function handle(req, res) {
66
+ const parsed = parseUrl(req.url, true);
67
+ const pathname = parsed.pathname;
68
+
69
+ if (!pathname.startsWith('/observe')) {
70
+ return false;
71
+ }
72
+
73
+ const result = router(req.method, pathname, parsed.query);
74
+ if (!result) {
75
+ return false;
76
+ }
77
+
78
+ res.statusCode = result.status;
79
+ for (const [key, value] of Object.entries(result.headers)) {
80
+ res.setHeader(key, value);
81
+ }
82
+
83
+ const body = typeof result.body === 'string' ? result.body : JSON.stringify(result.body);
84
+ res.end(body);
85
+ return true;
86
+ }
87
+
88
+ /**
89
+ * Manually capture an error into the failure store.
90
+ * Use this in frameworks where you handle errors yourself.
91
+ *
92
+ * @param {Error} err
93
+ * @param {{ method?: string, url?: string, headers?: object, params?: object, query?: object, body?: any }} [reqInfo]
94
+ */
95
+ function captureError(err, reqInfo = {}) {
96
+ store.add({
97
+ type: 'controller',
98
+ service: reqInfo.url ?? 'unknown',
99
+ method: reqInfo.method ?? 'UNKNOWN',
100
+ url: reqInfo.url ?? 'unknown',
101
+ correlationId: reqInfo.headers?.['x-correlation-id'] ?? null,
102
+ durationMs: null,
103
+ errorMessage: err.message,
104
+ errorCode: err.code ?? null,
105
+ errorName: err.name ?? 'Error',
106
+ stack: err.stack ?? null,
107
+
108
+ request: {
109
+ headers: sanitize(reqInfo.headers ?? {}),
110
+ params: reqInfo.params ?? null,
111
+ query: reqInfo.query ?? null,
112
+ body: sanitize(reqInfo.body),
113
+ },
114
+
115
+ statusCode: err.statusCode ?? err.status ?? 500,
116
+ response: null,
117
+ });
118
+ }
119
+
120
+ return {
121
+ handle,
122
+ captureError,
123
+ attach(client) {
124
+ attachInterceptor(client, store, sanitizeOpts);
125
+ },
126
+ attachToAll(serviceContainer) {
127
+ attachToAll(serviceContainer, store, sanitizeOpts);
128
+ },
129
+ store,
130
+ };
131
+ }
132
+
133
+ module.exports = { createHttpHandler };