opencons 0.1.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/LICENSE +21 -0
- package/README.md +382 -0
- package/opencons.d.ts +55 -0
- package/package.json +73 -0
- package/scripts/vendor-d3.js +22 -0
- package/src/core/context.js +44 -0
- package/src/core/index.js +198 -0
- package/src/core/tracer.js +252 -0
- package/src/drivers/db-language.js +207 -0
- package/src/drivers/detect.js +62 -0
- package/src/drivers/drizzle.js +87 -0
- package/src/drivers/index.js +43 -0
- package/src/drivers/mongoose.js +89 -0
- package/src/drivers/mysql2.js +116 -0
- package/src/drivers/pg.js +130 -0
- package/src/drivers/prisma.js +109 -0
- package/src/drivers/record.js +158 -0
- package/src/index.js +28 -0
- package/src/integrations/nest-lifecycle.js +357 -0
- package/src/integrations/nest.js +89 -0
- package/src/interceptors/express.js +270 -0
- package/src/interceptors/require-hook.js +109 -0
- package/src/lib/config.js +139 -0
- package/src/lib/errors.js +54 -0
- package/src/lib/http-response.js +37 -0
- package/src/lib/logger.js +69 -0
- package/src/lib/serialize.js +22 -0
- package/src/server/static.js +165 -0
- package/src/server/ws.js +62 -0
- package/src/store/source-cache.js +120 -0
- package/src/store/trace-store.js +117 -0
- package/src/transform/ast.js +255 -0
- package/src/transform/natural-language.js +146 -0
- package/src/transform/probe.js +161 -0
- package/src/transform/register.js +44 -0
- package/src/utils/label.js +26 -0
- package/src/utils/observable.js +103 -0
- package/widget/app.js +356 -0
- package/widget/db-language.js +90 -0
- package/widget/graph.js +1167 -0
- package/widget/index.html +132 -0
- package/widget/styles.css +773 -0
- package/widget/timeline.js +57 -0
- package/widget/vendor/d3.min.js +2 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getCurrentTracer, getCurrentContext, runWithContext } = require('../core/context');
|
|
4
|
+
const { buildDbNodeLanguage } = require('./db-language');
|
|
5
|
+
|
|
6
|
+
const MAX_QUERY_LEN = 500;
|
|
7
|
+
const MAX_PARAMS = 12;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {unknown} value
|
|
11
|
+
*/
|
|
12
|
+
function safeParams(value) {
|
|
13
|
+
if (value == null) return undefined;
|
|
14
|
+
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return value.slice(0, MAX_PARAMS).map((item) => safeParamValue(item));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof value === 'object') {
|
|
20
|
+
const out = {};
|
|
21
|
+
const entries = Object.entries(value).slice(0, MAX_PARAMS);
|
|
22
|
+
for (const [key, item] of entries) {
|
|
23
|
+
out[key] = safeParamValue(item);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return safeParamValue(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {unknown} value
|
|
33
|
+
*/
|
|
34
|
+
function safeParamValue(value) {
|
|
35
|
+
if (value == null) return value;
|
|
36
|
+
if (typeof value === 'string') return value.length > 80 ? `${value.slice(0, 80)}…` : value;
|
|
37
|
+
if (typeof value === 'number' || typeof value === 'boolean') return value;
|
|
38
|
+
if (value instanceof Date) return value.toISOString();
|
|
39
|
+
if (Buffer.isBuffer(value)) return `[Buffer ${value.length}b]`;
|
|
40
|
+
try {
|
|
41
|
+
const text = JSON.stringify(value);
|
|
42
|
+
return text.length > 120 ? `${text.slice(0, 120)}…` : text;
|
|
43
|
+
} catch {
|
|
44
|
+
return '[unserializable]';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} query
|
|
50
|
+
*/
|
|
51
|
+
function truncateQuery(query) {
|
|
52
|
+
const text = String(query || '').replace(/\s+/g, ' ').trim();
|
|
53
|
+
if (text.length <= MAX_QUERY_LEN) return text;
|
|
54
|
+
return `${text.slice(0, MAX_QUERY_LEN)}…`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {unknown} result
|
|
59
|
+
*/
|
|
60
|
+
function countRows(result) {
|
|
61
|
+
if (result == null) return undefined;
|
|
62
|
+
if (Array.isArray(result)) return result.length;
|
|
63
|
+
if (typeof result === 'object') {
|
|
64
|
+
if ('rowCount' in result && result.rowCount != null) return Number(result.rowCount);
|
|
65
|
+
if ('count' in result && result.count != null) return Number(result.count);
|
|
66
|
+
if ('length' in result && typeof result.length === 'number') return result.length;
|
|
67
|
+
if ('affectedRows' in result) return Number(result.affectedRows);
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {object} payload
|
|
74
|
+
* @param {string} payload.driver
|
|
75
|
+
* @param {string} [payload.operation]
|
|
76
|
+
* @param {string} [payload.query]
|
|
77
|
+
* @param {unknown} [payload.params]
|
|
78
|
+
* @param {number} [payload.rows]
|
|
79
|
+
* @param {number} payload.duration_ms
|
|
80
|
+
* @param {string} [payload.collection]
|
|
81
|
+
* @param {string} [payload.error]
|
|
82
|
+
*/
|
|
83
|
+
function recordDbQuery(payload) {
|
|
84
|
+
const tracer = getCurrentTracer();
|
|
85
|
+
const ctx = getCurrentContext();
|
|
86
|
+
|
|
87
|
+
if (!tracer) return;
|
|
88
|
+
|
|
89
|
+
const record = () => {
|
|
90
|
+
const parentId = ctx?.scopeNodeId || tracer.getLastSequentialNodeId();
|
|
91
|
+
const operation = payload.operation || 'query';
|
|
92
|
+
const language = buildDbNodeLanguage({
|
|
93
|
+
driver: payload.driver,
|
|
94
|
+
operation,
|
|
95
|
+
query: payload.query,
|
|
96
|
+
collection: payload.collection,
|
|
97
|
+
rows: payload.rows,
|
|
98
|
+
duration_ms: payload.duration_ms,
|
|
99
|
+
error: payload.error,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
tracer.addForkNode(parentId, {
|
|
103
|
+
type: 'db',
|
|
104
|
+
label: language.label,
|
|
105
|
+
summary: language.summary,
|
|
106
|
+
db_action: language.db_action,
|
|
107
|
+
db_intent: language.db_intent,
|
|
108
|
+
db_result: language.db_result,
|
|
109
|
+
query: payload.query ? truncateQuery(payload.query) : undefined,
|
|
110
|
+
params: safeParams(payload.params),
|
|
111
|
+
rows: payload.rows,
|
|
112
|
+
duration_ms: payload.duration_ms,
|
|
113
|
+
driver: payload.driver,
|
|
114
|
+
operation,
|
|
115
|
+
collection: payload.collection,
|
|
116
|
+
exit_reason: payload.error,
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (ctx) {
|
|
121
|
+
runWithContext(ctx, record);
|
|
122
|
+
} else {
|
|
123
|
+
record();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {() => Promise<unknown> | unknown} fn
|
|
129
|
+
* @param {object} meta
|
|
130
|
+
*/
|
|
131
|
+
async function traceDbCall(fn, meta) {
|
|
132
|
+
const start = performance.now();
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const result = await fn();
|
|
136
|
+
recordDbQuery({
|
|
137
|
+
...meta,
|
|
138
|
+
rows: meta.rows ?? countRows(result),
|
|
139
|
+
duration_ms: Math.round((performance.now() - start) * 10) / 10,
|
|
140
|
+
});
|
|
141
|
+
return result;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
recordDbQuery({
|
|
144
|
+
...meta,
|
|
145
|
+
duration_ms: Math.round((performance.now() - start) * 10) / 10,
|
|
146
|
+
error: err && err.message ? err.message : String(err),
|
|
147
|
+
});
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
recordDbQuery,
|
|
154
|
+
traceDbCall,
|
|
155
|
+
truncateQuery,
|
|
156
|
+
safeParams,
|
|
157
|
+
countRows,
|
|
158
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Opencons public entry point.
|
|
5
|
+
*
|
|
6
|
+
* Import-time side effects are intentional:
|
|
7
|
+
* - Express prototype patching so handlers registered after setup are wrapped
|
|
8
|
+
* - Optional AST require hook when OPENCONS_TRANSFORM is set
|
|
9
|
+
*
|
|
10
|
+
* Require this module before express() and call opencons() as the first middleware.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { createOpencons } = require('./core');
|
|
14
|
+
const { patchExpressGlobally } = require('./interceptors/express');
|
|
15
|
+
const { applyToNest, createNestMiddleware } = require('./integrations/nest');
|
|
16
|
+
const { label } = require('./utils/label');
|
|
17
|
+
|
|
18
|
+
require('./transform/register');
|
|
19
|
+
patchExpressGlobally();
|
|
20
|
+
|
|
21
|
+
const opencons = createOpencons;
|
|
22
|
+
|
|
23
|
+
opencons.applyToNest = applyToNest;
|
|
24
|
+
opencons.createNestMiddleware = createNestMiddleware;
|
|
25
|
+
opencons.label = label;
|
|
26
|
+
|
|
27
|
+
module.exports = opencons;
|
|
28
|
+
module.exports.default = opencons;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getCurrentTracer, getCurrentContext, runWithContext } = require('../core/context');
|
|
4
|
+
const { traceObservable } = require('../utils/observable');
|
|
5
|
+
|
|
6
|
+
let nestPatched = false;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('../core/tracer').TraceTracer | null | undefined} tracer
|
|
10
|
+
* @param {string} label
|
|
11
|
+
* @param {number} entered
|
|
12
|
+
* @param {boolean} [calledNext]
|
|
13
|
+
* @param {string} [exitReason]
|
|
14
|
+
* @param {'middleware' | 'controller'} [nodeType]
|
|
15
|
+
*/
|
|
16
|
+
function recordStep(tracer, label, entered, calledNext = true, exitReason, nodeType = 'middleware') {
|
|
17
|
+
if (!tracer) return;
|
|
18
|
+
|
|
19
|
+
const duration_ms = Math.round((performance.now() - entered) * 10) / 10;
|
|
20
|
+
|
|
21
|
+
tracer.addNode({
|
|
22
|
+
type: nodeType,
|
|
23
|
+
label,
|
|
24
|
+
duration_ms,
|
|
25
|
+
called_next: calledNext,
|
|
26
|
+
exit_reason: exitReason,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {import('../core/context').TraceContext | undefined} alsContext
|
|
32
|
+
* @param {import('../core/tracer').TraceTracer | null} tracer
|
|
33
|
+
* @param {string} label
|
|
34
|
+
* @param {number} entered
|
|
35
|
+
* @param {boolean} [calledNext]
|
|
36
|
+
* @param {string} [exitReason]
|
|
37
|
+
* @param {'middleware' | 'controller'} [nodeType]
|
|
38
|
+
*/
|
|
39
|
+
function recordStepSafe(alsContext, tracer, label, entered, calledNext, exitReason, nodeType) {
|
|
40
|
+
if (alsContext) {
|
|
41
|
+
const { runWithContext } = require('../core/context');
|
|
42
|
+
runWithContext(alsContext, () =>
|
|
43
|
+
recordStep(tracer, label, entered, calledNext, exitReason, nodeType)
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
recordStep(tracer, label, entered, calledNext, exitReason, nodeType);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {object} interceptor
|
|
53
|
+
*/
|
|
54
|
+
function wrapNestInterceptor(interceptor) {
|
|
55
|
+
if (!interceptor || interceptor.__openconsWrapped) {
|
|
56
|
+
return interceptor;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof interceptor.intercept !== 'function') {
|
|
60
|
+
return interceptor;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const name = resolveNestComponentName(interceptor, 'Interceptor');
|
|
64
|
+
const original = interceptor.intercept.bind(interceptor);
|
|
65
|
+
|
|
66
|
+
interceptor.intercept = function OpenconsIntercept(context, next) {
|
|
67
|
+
const tracer = getCurrentTracer();
|
|
68
|
+
const alsContext = getCurrentContext();
|
|
69
|
+
|
|
70
|
+
if (!tracer) {
|
|
71
|
+
return original(context, next);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const entered = performance.now();
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const result = original(context, next);
|
|
78
|
+
|
|
79
|
+
return traceObservable(
|
|
80
|
+
result,
|
|
81
|
+
(exitReason) => {
|
|
82
|
+
recordStepSafe(alsContext, tracer, name, entered, true, exitReason);
|
|
83
|
+
},
|
|
84
|
+
alsContext
|
|
85
|
+
);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
recordStepSafe(alsContext, tracer, name, entered, false, `error: ${err.message}`);
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
interceptor.__openconsWrapped = true;
|
|
93
|
+
return interceptor;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {object} guard
|
|
98
|
+
*/
|
|
99
|
+
function wrapNestGuard(guard) {
|
|
100
|
+
if (!guard || guard.__openconsWrapped) {
|
|
101
|
+
return guard;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof guard.canActivate !== 'function') {
|
|
105
|
+
return guard;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const name = resolveNestComponentName(guard, 'Guard');
|
|
109
|
+
const original = guard.canActivate.bind(guard);
|
|
110
|
+
|
|
111
|
+
guard.canActivate = async function OpenconsCanActivate(context) {
|
|
112
|
+
const tracer = getCurrentTracer();
|
|
113
|
+
const alsContext = getCurrentContext();
|
|
114
|
+
|
|
115
|
+
if (!tracer) {
|
|
116
|
+
return original(context);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const entered = performance.now();
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const allowed = await original(context);
|
|
123
|
+
recordStepSafe(alsContext, tracer, name, entered, Boolean(allowed), allowed ? undefined : 'denied');
|
|
124
|
+
return allowed;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
recordStepSafe(alsContext, tracer, name, entered, false, `error: ${err.message}`);
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
guard.__openconsWrapped = true;
|
|
132
|
+
return guard;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {object} component
|
|
137
|
+
* @param {string} fallbackSuffix
|
|
138
|
+
*/
|
|
139
|
+
function resolveNestComponentName(component, fallbackSuffix) {
|
|
140
|
+
const ctor = component.constructor?.name;
|
|
141
|
+
if (ctor && ctor !== 'Object' && ctor !== 'Function') {
|
|
142
|
+
return ctor;
|
|
143
|
+
}
|
|
144
|
+
return `Anonymous${fallbackSuffix}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {import('@nestjs/common').ExecutionContext} context
|
|
149
|
+
*/
|
|
150
|
+
function resolveControllerLabel(context) {
|
|
151
|
+
const className = context.getClass()?.name || 'Controller';
|
|
152
|
+
const handler = context.getHandler();
|
|
153
|
+
const handlerName = handler?.name || handler?.displayName || 'handler';
|
|
154
|
+
return `${className}.${handlerName}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Trace controller handlers — must run as the innermost global interceptor.
|
|
159
|
+
*/
|
|
160
|
+
class OpenconsControllerInterceptor {
|
|
161
|
+
intercept(context, next) {
|
|
162
|
+
const tracer = getCurrentTracer();
|
|
163
|
+
const alsContext = getCurrentContext();
|
|
164
|
+
|
|
165
|
+
if (!tracer) {
|
|
166
|
+
return next.handle();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const label = resolveControllerLabel(context);
|
|
170
|
+
const entered = performance.now();
|
|
171
|
+
|
|
172
|
+
const openController = () => {
|
|
173
|
+
const node = tracer.addNode({
|
|
174
|
+
type: 'controller',
|
|
175
|
+
label,
|
|
176
|
+
duration_ms: null,
|
|
177
|
+
called_next: true,
|
|
178
|
+
});
|
|
179
|
+
if (alsContext) {
|
|
180
|
+
alsContext.scopeNodeId = node.id;
|
|
181
|
+
}
|
|
182
|
+
return node.id;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const controllerNodeId = alsContext
|
|
186
|
+
? runWithContext(alsContext, openController)
|
|
187
|
+
: openController();
|
|
188
|
+
|
|
189
|
+
const completeController = (calledNext, exitReason) => {
|
|
190
|
+
tracer.updateNode(controllerNodeId, {
|
|
191
|
+
duration_ms: Math.round((performance.now() - entered) * 10) / 10,
|
|
192
|
+
called_next: calledNext,
|
|
193
|
+
exit_reason: exitReason,
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const result = next.handle();
|
|
199
|
+
|
|
200
|
+
return traceObservable(
|
|
201
|
+
result,
|
|
202
|
+
(exitReason) => {
|
|
203
|
+
if (alsContext) {
|
|
204
|
+
runWithContext(alsContext, () => completeController(true, exitReason));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
completeController(true, exitReason);
|
|
208
|
+
},
|
|
209
|
+
alsContext
|
|
210
|
+
);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (alsContext) {
|
|
213
|
+
runWithContext(alsContext, () => completeController(false, `error: ${err.message}`));
|
|
214
|
+
} else {
|
|
215
|
+
completeController(false, `error: ${err.message}`);
|
|
216
|
+
}
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Patch Nest application methods so globally registered components are traced.
|
|
224
|
+
*/
|
|
225
|
+
function patchNestGlobally() {
|
|
226
|
+
if (nestPatched) return;
|
|
227
|
+
nestPatched = true;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const { NestApplication } = require('@nestjs/core/nest-application');
|
|
231
|
+
|
|
232
|
+
const originalInterceptors = NestApplication.prototype.useGlobalInterceptors;
|
|
233
|
+
NestApplication.prototype.useGlobalInterceptors = function (...interceptors) {
|
|
234
|
+
return originalInterceptors.call(
|
|
235
|
+
this,
|
|
236
|
+
...interceptors.map((item) => wrapNestInterceptor(item))
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const originalGuards = NestApplication.prototype.useGlobalGuards;
|
|
241
|
+
NestApplication.prototype.useGlobalGuards = function (...guards) {
|
|
242
|
+
return originalGuards.call(this, ...guards.map((item) => wrapNestGuard(item)));
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const originalPipes = NestApplication.prototype.useGlobalPipes;
|
|
246
|
+
NestApplication.prototype.useGlobalPipes = function (...pipes) {
|
|
247
|
+
return originalPipes.call(this, ...pipes.map((pipe) => wrapNestPipe(pipe)));
|
|
248
|
+
};
|
|
249
|
+
} catch {
|
|
250
|
+
// @nestjs/core not installed — Express-only mode.
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Register the controller interceptor last so it wraps the route handler directly.
|
|
256
|
+
* @param {import('@nestjs/common').INestApplication} nestApp
|
|
257
|
+
*/
|
|
258
|
+
function attachControllerTracing(nestApp) {
|
|
259
|
+
nestApp.useGlobalInterceptors(new OpenconsControllerInterceptor());
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @param {import('@nestjs/common').INestApplication} nestApp
|
|
264
|
+
*/
|
|
265
|
+
function deferControllerTracingUntilReady(nestApp) {
|
|
266
|
+
let attached = false;
|
|
267
|
+
|
|
268
|
+
const attach = () => {
|
|
269
|
+
if (attached) return;
|
|
270
|
+
attached = true;
|
|
271
|
+
attachControllerTracing(nestApp);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const originalListen = nestApp.listen.bind(nestApp);
|
|
275
|
+
nestApp.listen = function OpenconsListen(...args) {
|
|
276
|
+
attach();
|
|
277
|
+
return originalListen(...args);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
if (typeof nestApp.init === 'function') {
|
|
281
|
+
const originalInit = nestApp.init.bind(nestApp);
|
|
282
|
+
nestApp.init = async function OpenconsInit(...args) {
|
|
283
|
+
attach();
|
|
284
|
+
return originalInit(...args);
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** @deprecated Use OpenconsControllerInterceptor via deferControllerTracingUntilReady */
|
|
290
|
+
class OpenconsNestInterceptor {
|
|
291
|
+
intercept(context, next) {
|
|
292
|
+
return new OpenconsControllerInterceptor().intercept(context, next);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @param {object} pipe
|
|
298
|
+
*/
|
|
299
|
+
function wrapNestPipe(pipe) {
|
|
300
|
+
if (!pipe || pipe.__openconsWrapped) {
|
|
301
|
+
return pipe;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (typeof pipe.transform !== 'function') {
|
|
305
|
+
return pipe;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const name = resolveNestComponentName(pipe, 'Pipe');
|
|
309
|
+
const original = pipe.transform.bind(pipe);
|
|
310
|
+
|
|
311
|
+
pipe.transform = function OpenconsTransform(value, metadata) {
|
|
312
|
+
const tracer = getCurrentTracer();
|
|
313
|
+
const alsContext = getCurrentContext();
|
|
314
|
+
|
|
315
|
+
if (!tracer) {
|
|
316
|
+
return original(value, metadata);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const entered = performance.now();
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const result = original(value, metadata);
|
|
323
|
+
|
|
324
|
+
if (result && typeof result.then === 'function') {
|
|
325
|
+
return result
|
|
326
|
+
.then((resolved) => {
|
|
327
|
+
recordStepSafe(alsContext, tracer, name, entered);
|
|
328
|
+
return resolved;
|
|
329
|
+
})
|
|
330
|
+
.catch((err) => {
|
|
331
|
+
recordStepSafe(alsContext, tracer, name, entered, false, `error: ${err.message}`);
|
|
332
|
+
throw err;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
recordStepSafe(alsContext, tracer, name, entered);
|
|
337
|
+
return result;
|
|
338
|
+
} catch (err) {
|
|
339
|
+
recordStepSafe(alsContext, tracer, name, entered, false, `error: ${err.message}`);
|
|
340
|
+
throw err;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
pipe.__openconsWrapped = true;
|
|
345
|
+
return pipe;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports = {
|
|
349
|
+
patchNestGlobally,
|
|
350
|
+
wrapNestInterceptor,
|
|
351
|
+
wrapNestGuard,
|
|
352
|
+
wrapNestPipe,
|
|
353
|
+
attachControllerTracing,
|
|
354
|
+
deferControllerTracingUntilReady,
|
|
355
|
+
OpenconsControllerInterceptor,
|
|
356
|
+
OpenconsNestInterceptor,
|
|
357
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createOpencons } = require('../core');
|
|
4
|
+
const { patchExpressApp } = require('../interceptors/express');
|
|
5
|
+
const {
|
|
6
|
+
patchNestGlobally,
|
|
7
|
+
deferControllerTracingUntilReady,
|
|
8
|
+
} = require('./nest-lifecycle');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {import('express').Application} expressApp
|
|
12
|
+
* @param {import('express').RequestHandler} handler
|
|
13
|
+
*/
|
|
14
|
+
function prependMiddleware(expressApp, handler) {
|
|
15
|
+
if (typeof expressApp.lazyrouter === 'function') {
|
|
16
|
+
expressApp.lazyrouter();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const router = expressApp._router;
|
|
20
|
+
|
|
21
|
+
if (!router || !Array.isArray(router.stack)) {
|
|
22
|
+
expressApp.use(handler);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Layer = require('express/lib/router/layer');
|
|
27
|
+
const layer = new Layer('/', { sensitive: false, strict: false, end: false }, handler);
|
|
28
|
+
router.stack.unshift(layer);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {import('@nestjs/common').INestApplication} nestApp
|
|
33
|
+
* @param {Parameters<typeof createOpencons>[0]} [options]
|
|
34
|
+
* @returns {ReturnType<typeof createOpencons>}
|
|
35
|
+
*/
|
|
36
|
+
function applyToNest(nestApp, options) {
|
|
37
|
+
patchNestGlobally();
|
|
38
|
+
|
|
39
|
+
const middleware = createOpencons(options);
|
|
40
|
+
const httpAdapter = nestApp.getHttpAdapter();
|
|
41
|
+
|
|
42
|
+
if (!httpAdapter || typeof httpAdapter.getInstance !== 'function') {
|
|
43
|
+
const { OpenconsError } = require('../lib/errors');
|
|
44
|
+
throw new OpenconsError(
|
|
45
|
+
'Nest app must use the Express adapter (@nestjs/platform-express).',
|
|
46
|
+
'NEST_ADAPTER_REQUIRED'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const expressApp = httpAdapter.getInstance();
|
|
51
|
+
patchExpressApp(expressApp);
|
|
52
|
+
prependMiddleware(expressApp, middleware);
|
|
53
|
+
|
|
54
|
+
if (typeof httpAdapter.setOnRequestHook === 'function') {
|
|
55
|
+
const previousHook = httpAdapter.onRequestHook;
|
|
56
|
+
|
|
57
|
+
httpAdapter.setOnRequestHook((req, res, next) => {
|
|
58
|
+
middleware(req, res, () => {
|
|
59
|
+
if (previousHook) {
|
|
60
|
+
previousHook(req, res, next);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
next();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Register controller tracing after the app's own global interceptors (on listen/init).
|
|
69
|
+
deferControllerTracingUntilReady(nestApp);
|
|
70
|
+
|
|
71
|
+
const { logger } = require('../lib/logger');
|
|
72
|
+
logger.info('Attached to Nest (middleware + guards + interceptors + controllers)');
|
|
73
|
+
|
|
74
|
+
return middleware;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {Parameters<typeof createOpencons>[0]} [options]
|
|
79
|
+
* @returns {ReturnType<typeof createOpencons>}
|
|
80
|
+
*/
|
|
81
|
+
function createNestMiddleware(options) {
|
|
82
|
+
return createOpencons(options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
applyToNest,
|
|
87
|
+
createNestMiddleware,
|
|
88
|
+
prependMiddleware,
|
|
89
|
+
};
|