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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RouteGrapher Contributors
|
|
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
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# Opencons
|
|
2
|
+
|
|
3
|
+
**Live runtime execution tracing for Node.js / Express**
|
|
4
|
+
|
|
5
|
+
Opencons automatically captures and visualises the complete execution path of every HTTP request passing through your Express application — in real time, with zero instrumentation code in your handlers.
|
|
6
|
+
|
|
7
|
+
> **Development only.** Opencons is disabled when `NODE_ENV=production` unless you explicitly pass `enabled: true`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of contents
|
|
12
|
+
|
|
13
|
+
- [Features](#features)
|
|
14
|
+
- [Prerequisites](#prerequisites)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Quick start](#quick-start)
|
|
17
|
+
- [Configuration](#configuration)
|
|
18
|
+
- [Environment variables](#environment-variables)
|
|
19
|
+
- [NestJS integration](#nestjs-integration)
|
|
20
|
+
- [Branch tracing (AST)](#branch-tracing-ast)
|
|
21
|
+
- [Database capture](#database-capture)
|
|
22
|
+
- [Widget API](#widget-api)
|
|
23
|
+
- [Trace data model](#trace-data-model)
|
|
24
|
+
- [Project structure](#project-structure)
|
|
25
|
+
- [Run the example](#run-the-example)
|
|
26
|
+
- [Contributing](#contributing)
|
|
27
|
+
- [Roadmap](#roadmap)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
| Capability | Description |
|
|
35
|
+
|------------|-------------|
|
|
36
|
+
| Request tracing | Method, URL, status, duration per request |
|
|
37
|
+
| Middleware chain | `next()` detection, early-exit reasons, async errors |
|
|
38
|
+
| Branch probing | `if` / `switch` / loops / `try` as diamond nodes (CommonJS `.js`) |
|
|
39
|
+
| DB capture | Drizzle, `pg`, `mysql2`, `mongoose`, Prisma |
|
|
40
|
+
| Live widget | Request list, D3 execution graph, waterfall timeline |
|
|
41
|
+
| NestJS | Guards, interceptors, pipes, controllers |
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Prerequisites
|
|
47
|
+
|
|
48
|
+
- Node.js **18.0.0** or newer
|
|
49
|
+
- An Express 4.x application (or NestJS with the Express adapter)
|
|
50
|
+
- For Nest interceptor tracing: `rxjs` ^7
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install --save-dev opencons
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick start
|
|
63
|
+
|
|
64
|
+
Require Opencons **before** creating your Express app, and register it as the **first** middleware:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const opencons = require('opencons'); // before express()
|
|
68
|
+
const express = require('express');
|
|
69
|
+
|
|
70
|
+
const app = express();
|
|
71
|
+
|
|
72
|
+
app.use(opencons()); // must be first
|
|
73
|
+
|
|
74
|
+
app.use(express.json());
|
|
75
|
+
app.use('/api', require('./routes'));
|
|
76
|
+
|
|
77
|
+
app.listen(3000);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Open the widget while your app runs:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
http://localhost:7331
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If port 7331 is busy, Opencons tries the next port and logs the actual URL.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
app.use(opencons({
|
|
94
|
+
port: 7331, // widget + WebSocket port
|
|
95
|
+
enabled: undefined, // set true to force enable in production (not recommended)
|
|
96
|
+
enableWidget: true, // set false in automated tests
|
|
97
|
+
exclude: ['/health'], // routes to ignore
|
|
98
|
+
captureBody: false, // snapshot request bodies on the trace
|
|
99
|
+
captureResponse: false, // snapshot response bodies (res.json / res.send)
|
|
100
|
+
maxTraces: 100, // in-memory ring buffer size
|
|
101
|
+
drivers: {
|
|
102
|
+
mongoose: true,
|
|
103
|
+
drizzle: true,
|
|
104
|
+
pg: true,
|
|
105
|
+
prisma: true,
|
|
106
|
+
mysql2: true,
|
|
107
|
+
},
|
|
108
|
+
transform: {
|
|
109
|
+
enabled: false, // AST branch probing (Phase 2)
|
|
110
|
+
projectRoot: process.cwd(),
|
|
111
|
+
exclude: ['vendor/**'],
|
|
112
|
+
},
|
|
113
|
+
}));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Invalid options throw a `ConfigurationError` at startup with a descriptive message.
|
|
117
|
+
|
|
118
|
+
### Programmatic access
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
const middleware = opencons({ enableWidget: false });
|
|
122
|
+
const traces = middleware.getTraces();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Environment variables
|
|
128
|
+
|
|
129
|
+
Copy [.env.example](.env.example) into your host application. Load env **before** importing Opencons when using transform variables.
|
|
130
|
+
|
|
131
|
+
| Variable | Default | Description |
|
|
132
|
+
|----------|---------|-------------|
|
|
133
|
+
| `NODE_ENV` | — | `production` disables tracing unless `enabled: true` |
|
|
134
|
+
| `OPENCONS_TRANSFORM` | — | `1` or `true` installs AST hook on import |
|
|
135
|
+
| `OPENCONS_ROOT` | `process.cwd()` | Project root for source transforms |
|
|
136
|
+
| `OPENCONS_TRANSFORM_EXCLUDE` | — | Comma-separated globs to skip |
|
|
137
|
+
| `OPENCONS_LOG_LEVEL` | `info` | Set to `debug` for verbose library logs |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## NestJS integration
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// main.ts — import Opencons before NestFactory.create()
|
|
145
|
+
import { NestFactory } from '@nestjs/core';
|
|
146
|
+
import { NestExpressApplication } from '@nestjs/platform-express';
|
|
147
|
+
import opencons from 'opencons';
|
|
148
|
+
import { AppModule } from './app.module';
|
|
149
|
+
|
|
150
|
+
async function bootstrap() {
|
|
151
|
+
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
|
152
|
+
|
|
153
|
+
opencons.applyToNest(app, {
|
|
154
|
+
port: 7331,
|
|
155
|
+
exclude: ['/health'],
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await app.listen(3000);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
bootstrap();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**What gets traced**
|
|
165
|
+
|
|
166
|
+
| Layer | Traced? |
|
|
167
|
+
|-------|---------|
|
|
168
|
+
| HTTP request / response | Yes |
|
|
169
|
+
| Express middleware | Yes |
|
|
170
|
+
| Nest controllers | Yes |
|
|
171
|
+
| Nest guards / interceptors / pipes | Yes |
|
|
172
|
+
|
|
173
|
+
### Alternative: `MiddlewareConsumer`
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
consumer
|
|
177
|
+
.apply(opencons.createNestMiddleware({ port: 7331 }))
|
|
178
|
+
.forRoutes('*');
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Prefer `applyToNest()` in `main.ts` immediately after `NestFactory.create()`.
|
|
182
|
+
|
|
183
|
+
### Naming middleware
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
app.use(opencons.label('bullAuth', bullAuth));
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Branch tracing (AST)
|
|
192
|
+
|
|
193
|
+
Every `if` / `switch` / `while` / `for` / `try` in **CommonJS `.js` files** under your project root can be probed at load time. Branch decisions appear as diamond nodes in the graph.
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
app.use(opencons({
|
|
197
|
+
transform: {
|
|
198
|
+
enabled: true,
|
|
199
|
+
projectRoot: process.cwd(),
|
|
200
|
+
exclude: ['dist/vendor/**'],
|
|
201
|
+
},
|
|
202
|
+
}));
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
For **NestJS** (TypeScript compiled to `dist/`), enable via `.env` so the hook runs when `Opencons` is imported:
|
|
206
|
+
|
|
207
|
+
```env
|
|
208
|
+
OPENCONS_TRANSFORM=1
|
|
209
|
+
OPENCONS_ROOT=dist/apps/api
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import './load-env'; // loads .env first
|
|
214
|
+
import opencons from 'opencons';
|
|
215
|
+
import { AppModule } from './app.module';
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Or use `node -r opencons/register-transform` or `require('opencons/register-transform')()` before other imports.
|
|
219
|
+
|
|
220
|
+
Skip a file with `// opencons-skip` at the top.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Database capture
|
|
225
|
+
|
|
226
|
+
Database queries appear as **blue fork nodes** off the handler that triggered them.
|
|
227
|
+
|
|
228
|
+
| Driver | Package |
|
|
229
|
+
|--------|---------|
|
|
230
|
+
| Drizzle ORM | `drizzle-orm` |
|
|
231
|
+
| PostgreSQL | `pg` |
|
|
232
|
+
| MySQL | `mysql2` |
|
|
233
|
+
| MongoDB | `mongoose` |
|
|
234
|
+
| Prisma | `@prisma/client` |
|
|
235
|
+
|
|
236
|
+
Load `Opencons` before creating database clients. When `drizzle-orm` is installed, Opencons captures at the ORM layer and skips raw `pg`/`mysql2` to avoid duplicates.
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
opencons.applyToNest(app, {
|
|
240
|
+
drivers: { drizzle: true, mongoose: false },
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Widget API
|
|
247
|
+
|
|
248
|
+
The dev widget server exposes a minimal HTTP API (no authentication — local dev only).
|
|
249
|
+
|
|
250
|
+
### `GET /api/source`
|
|
251
|
+
|
|
252
|
+
Returns a source snippet for branch peek in the graph.
|
|
253
|
+
|
|
254
|
+
| Query param | Required | Description |
|
|
255
|
+
|-------------|----------|-------------|
|
|
256
|
+
| `file` | Yes | Project-relative path or basename |
|
|
257
|
+
| `line` | No | 1-based line number (default: 1) |
|
|
258
|
+
|
|
259
|
+
**Responses**
|
|
260
|
+
|
|
261
|
+
| Status | Body |
|
|
262
|
+
|--------|------|
|
|
263
|
+
| 200 | `{ file, line, startLine, lines: [{ number, text, highlight }] }` |
|
|
264
|
+
| 400 | `{ error, code: "MISSING_FILE_PARAM" }` |
|
|
265
|
+
| 404 | `{ error, code: "SOURCE_NOT_FOUND" }` |
|
|
266
|
+
|
|
267
|
+
### WebSocket protocol
|
|
268
|
+
|
|
269
|
+
Connect to `ws://localhost:<port>` (same port as the widget).
|
|
270
|
+
|
|
271
|
+
**Client → server**
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{ "type": "get_history", "limit": 50 }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Server → client**
|
|
278
|
+
|
|
279
|
+
| Type | When |
|
|
280
|
+
|------|------|
|
|
281
|
+
| `trace_start` | Request begins |
|
|
282
|
+
| `trace_update` | Live progress |
|
|
283
|
+
| `trace` | Request completed |
|
|
284
|
+
| `history` | Response to `get_history` |
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Trace data model
|
|
289
|
+
|
|
290
|
+
Each request produces a directed acyclic graph (DAG):
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"id": "req_a1b2c3",
|
|
295
|
+
"method": "GET",
|
|
296
|
+
"url": "/api/users/1",
|
|
297
|
+
"status": 200,
|
|
298
|
+
"duration_ms": 12.4,
|
|
299
|
+
"body": null,
|
|
300
|
+
"response": { "id": 1, "name": "Ada" },
|
|
301
|
+
"nodes": [
|
|
302
|
+
{ "id": "n1", "type": "request", "label": "GET /api/users/1" },
|
|
303
|
+
{ "id": "n2", "type": "middleware", "label": "authMiddleware", "duration_ms": 0.5, "called_next": true },
|
|
304
|
+
{ "id": "n3", "type": "response", "label": "200" }
|
|
305
|
+
],
|
|
306
|
+
"edges": [
|
|
307
|
+
{ "from": "n1", "to": "n2" },
|
|
308
|
+
{ "from": "n2", "to": "n3" }
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Project structure
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
open-route/
|
|
319
|
+
├── src/
|
|
320
|
+
│ ├── index.js # Package entry — Express patch on import
|
|
321
|
+
│ ├── core/ # Middleware, tracer, AsyncLocalStorage
|
|
322
|
+
│ ├── lib/ # Config, logger, errors, HTTP helpers
|
|
323
|
+
│ ├── interceptors/ # Express + require-hook patches
|
|
324
|
+
│ ├── transform/ # Babel branch probe injection
|
|
325
|
+
│ ├── store/ # Trace ring buffer + source cache
|
|
326
|
+
│ ├── server/ # Widget HTTP + WebSocket
|
|
327
|
+
│ ├── drivers/ # DB driver patches
|
|
328
|
+
│ ├── integrations/ # NestJS helpers
|
|
329
|
+
│ └── utils/ # label(), observable()
|
|
330
|
+
├── widget/ # Browser dev UI
|
|
331
|
+
├── examples/sample-app/ # Runnable Express demo
|
|
332
|
+
├── test/ # Node.js test runner
|
|
333
|
+
├── scripts/ # Maintenance scripts
|
|
334
|
+
├── CONTRIBUTING.md
|
|
335
|
+
├── .env.example
|
|
336
|
+
└── opencons.d.ts
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Run the example
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
npm install
|
|
345
|
+
npm run start:example
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
1. Open http://localhost:7331 for the widget
|
|
349
|
+
2. Fire requests against http://localhost:3000:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
curl http://localhost:3000/api/public
|
|
353
|
+
curl http://localhost:3000/api/users/42
|
|
354
|
+
curl -H "Authorization: Bearer dev" http://localhost:3000/api/users/42
|
|
355
|
+
curl -X POST http://localhost:3000/api/orders \
|
|
356
|
+
-H "Content-Type: application/json" \
|
|
357
|
+
-H "Authorization: Bearer dev" \
|
|
358
|
+
-d "{\"items\":[1]}"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Contributing
|
|
364
|
+
|
|
365
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup, coding conventions, and test instructions.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Roadmap
|
|
370
|
+
|
|
371
|
+
| Phase | Feature | Status |
|
|
372
|
+
|-------|---------|--------|
|
|
373
|
+
| 1 | Request boundary, middleware chain, widget | Done |
|
|
374
|
+
| 2 | AST branch probing, source peek | Done |
|
|
375
|
+
| 3 | Database driver capture | Done |
|
|
376
|
+
| 4 | TypeScript / ESM transforms, replay mode | Planned |
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## License
|
|
381
|
+
|
|
382
|
+
MIT — see [LICENSE](LICENSE).
|
package/opencons.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { RequestHandler } from 'express';
|
|
2
|
+
|
|
3
|
+
export interface OpenconsOptions {
|
|
4
|
+
port?: number;
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
enableWidget?: boolean;
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
captureBody?: boolean;
|
|
9
|
+
captureResponse?: boolean;
|
|
10
|
+
maxTraces?: number;
|
|
11
|
+
drivers?: {
|
|
12
|
+
mongoose?: boolean;
|
|
13
|
+
drizzle?: boolean;
|
|
14
|
+
pg?: boolean;
|
|
15
|
+
prisma?: boolean;
|
|
16
|
+
mysql2?: boolean;
|
|
17
|
+
};
|
|
18
|
+
transform?: {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
projectRoot?: string;
|
|
21
|
+
exclude?: string[];
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TraceGraph {
|
|
26
|
+
id: string;
|
|
27
|
+
method: string;
|
|
28
|
+
url: string;
|
|
29
|
+
status: number | null;
|
|
30
|
+
duration_ms: number;
|
|
31
|
+
body?: unknown;
|
|
32
|
+
response?: unknown;
|
|
33
|
+
nodes: object[];
|
|
34
|
+
edges: object[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OpenconsMiddleware extends RequestHandler {
|
|
38
|
+
getTraces: () => TraceGraph[];
|
|
39
|
+
options: OpenconsOptions;
|
|
40
|
+
__openconsEntry: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Opencons {
|
|
44
|
+
(options?: OpenconsOptions): OpenconsMiddleware;
|
|
45
|
+
applyToNest(
|
|
46
|
+
nestApp: { getHttpAdapter(): { getInstance(): unknown } },
|
|
47
|
+
options?: OpenconsOptions,
|
|
48
|
+
): OpenconsMiddleware;
|
|
49
|
+
createNestMiddleware(options?: OpenconsOptions): OpenconsMiddleware;
|
|
50
|
+
label<T extends Function>(name: string, handler: T): T;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
declare const opencons: Opencons;
|
|
54
|
+
|
|
55
|
+
export default opencons;
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencons",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Opencons — live runtime execution tracer for Node.js / Express",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "opencons.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./opencons.d.ts",
|
|
10
|
+
"require": "./src/index.js",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./register-transform": {
|
|
14
|
+
"require": "./src/transform/register.js",
|
|
15
|
+
"default": "./src/transform/register.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"widget",
|
|
21
|
+
"opencons.d.ts",
|
|
22
|
+
"scripts/vendor-d3.js"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test test/*.test.js",
|
|
26
|
+
"start:example": "node examples/sample-app/server.js",
|
|
27
|
+
"postinstall": "node scripts/vendor-d3.js"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"express",
|
|
31
|
+
"tracing",
|
|
32
|
+
"debugging",
|
|
33
|
+
"middleware",
|
|
34
|
+
"developer-tools"
|
|
35
|
+
],
|
|
36
|
+
"author": "patiencemanzen",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/patiencemanzen/opencons.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/patiencemanzen/opencons#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/patiencemanzen/opencons/issues"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@babel/generator": "^7.27.0",
|
|
51
|
+
"@babel/parser": "^7.27.0",
|
|
52
|
+
"@babel/traverse": "^7.27.0",
|
|
53
|
+
"@babel/types": "^7.27.0",
|
|
54
|
+
"ws": "^8.17.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"express": "^4.18.0",
|
|
58
|
+
"rxjs": "^7.0.0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"express": {
|
|
62
|
+
"optional": false
|
|
63
|
+
},
|
|
64
|
+
"rxjs": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"d3": "^7.9.0",
|
|
70
|
+
"express": "^4.21.0",
|
|
71
|
+
"rxjs": "^7.8.1"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copy D3 into widget/vendor so the dev widget works offline.
|
|
5
|
+
* Falls back to CDN in index.html when this file is missing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const source = path.join(__dirname, '..', 'node_modules', 'd3', 'dist', 'd3.min.js');
|
|
12
|
+
const targetDir = path.join(__dirname, '..', 'widget', 'vendor');
|
|
13
|
+
const target = path.join(targetDir, 'd3.min.js');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(source)) {
|
|
16
|
+
console.warn('[vendor-d3] d3 not installed — widget will use CDN fallback');
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
21
|
+
fs.copyFileSync(source, target);
|
|
22
|
+
console.log('[vendor-d3] Copied d3.min.js to widget/vendor/');
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
4
|
+
|
|
5
|
+
const storage = new AsyncLocalStorage();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} TraceContext
|
|
9
|
+
* @property {string} id
|
|
10
|
+
* @property {number} startTime
|
|
11
|
+
* @property {import('./tracer').TraceTracer} tracer
|
|
12
|
+
* @property {string} [scopeNodeId]
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run a function within an isolated trace context.
|
|
17
|
+
* @param {TraceContext} context
|
|
18
|
+
* @param {() => void} fn
|
|
19
|
+
*/
|
|
20
|
+
function runWithContext(context, fn) {
|
|
21
|
+
return storage.run(context, fn);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @returns {TraceContext | undefined}
|
|
26
|
+
*/
|
|
27
|
+
function getCurrentContext() {
|
|
28
|
+
return storage.getStore();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @returns {import('./tracer').TraceTracer | null}
|
|
33
|
+
*/
|
|
34
|
+
function getCurrentTracer() {
|
|
35
|
+
const ctx = getCurrentContext();
|
|
36
|
+
return ctx ? ctx.tracer : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
storage,
|
|
41
|
+
runWithContext,
|
|
42
|
+
getCurrentContext,
|
|
43
|
+
getCurrentTracer,
|
|
44
|
+
};
|