@nifrajs/devtools 4.0.0 → 5.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/README.md +7 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +52 -31
package/README.md
CHANGED
|
@@ -16,6 +16,13 @@ The plugin is enabled by default only when `NODE_ENV=development`. It records a
|
|
|
16
16
|
and serves the overlay stream at `/_nifra/devtools`; remote hosts and cross-origin access stay denied
|
|
17
17
|
unless explicitly allowed. Leave it unregistered in production for zero request-path overhead.
|
|
18
18
|
|
|
19
|
+
Each SSE connection also has a finite pending-byte budget (`maxPendingBytes`, default 64 KiB),
|
|
20
|
+
including keep-alive frames. A slow reader that fills that budget is closed and releases its
|
|
21
|
+
connection slot. Already queued frames drain before EOF; the overflow frame is dropped. Browser
|
|
22
|
+
`EventSource` reconnects automatically. Initial delivery includes only the newest history suffix
|
|
23
|
+
that fits the byte budget. This is a recent trace view, so reconnects can have event-id gaps when
|
|
24
|
+
older events no longer fit the history or connection budget; it does not guarantee lossless replay.
|
|
25
|
+
|
|
19
26
|
## For AI agents
|
|
20
27
|
|
|
21
28
|
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface DevToolsOptions {
|
|
|
31
31
|
readonly maxEvents?: number | undefined;
|
|
32
32
|
/** Maximum simultaneous SSE connections (default 5). */
|
|
33
33
|
readonly maxConnections?: number | undefined;
|
|
34
|
+
/** Maximum queued SSE bytes per connection (default 64 KiB). Overflow closes that connection. */
|
|
35
|
+
readonly maxPendingBytes?: number | undefined;
|
|
34
36
|
/** SSE path (default `/_nifra/devtools`). */
|
|
35
37
|
readonly path?: string | undefined;
|
|
36
38
|
/** Explicit enable switch. Defaults to true only when `NODE_ENV === "development"`. */
|
|
@@ -56,7 +58,7 @@ export interface DevToolsClientOptions {
|
|
|
56
58
|
* DevTools plugin. Its observation adapter projects the single request span into a
|
|
57
59
|
* `DevToolsEvent`; its middleware only owns the secured SSE transport.
|
|
58
60
|
* When configuring `tracing()` yourself, register it before this plugin so DevTools attaches to
|
|
59
|
-
* that request owner.
|
|
61
|
+
* that request owner, and pass it `rawUrlPath: true` to list raw paths instead of route templates.
|
|
60
62
|
*/
|
|
61
63
|
/** Query for a DevTools state snapshot: an optional `path` prefix and a most-recent-`limit`. */
|
|
62
64
|
export interface DevToolsQuery {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAcH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,wDAAwD;IACxD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5C,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,uFAAuF;IACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACtC,oDAAoD;IACpD,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC1C,gGAAgG;IAChG,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IACvD,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAA;IACnF;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7C;AAED,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAgBD;;;;;GAKG;AACH,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,KAAK,GAAE,aAAkB,GACxB,aAAa,EAAE,CASjB;AAED,wBAAgB,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAcH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,wDAAwD;IACxD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5C,iGAAiG;IACjG,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7C,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,uFAAuF;IACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACtC,oDAAoD;IACpD,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC1C,gGAAgG;IAChG,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IACvD,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAA;IACnF;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7C;AAED,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAgBD;;;;;GAKG;AACH,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,KAAK,GAAE,aAAkB,GACxB,aAAa,EAAE,CASjB;AAED,wBAAgB,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,iDA4N7D;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAuGhF"}
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,9 @@ export function filterDevToolsEvents(events, query = {}) {
|
|
|
40
40
|
export function devtools(options) {
|
|
41
41
|
const maxEvents = options?.maxEvents ?? 200;
|
|
42
42
|
const maxConnections = Math.max(1, options?.maxConnections ?? 5);
|
|
43
|
+
const maxPendingBytes = options?.maxPendingBytes ?? 64 * 1024;
|
|
44
|
+
if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < 1)
|
|
45
|
+
throw new RangeError("DevTools maxPendingBytes must be a positive safe integer");
|
|
43
46
|
const endpoint = options?.path ?? "/_nifra/devtools";
|
|
44
47
|
// A one-shot JSON snapshot of the ring buffer, alongside the live SSE stream. The SSE path is for a
|
|
45
48
|
// human watching the overlay; this is for an agent that made a request and wants to READ what happened.
|
|
@@ -68,12 +71,7 @@ export function devtools(options) {
|
|
|
68
71
|
return;
|
|
69
72
|
pingTimer = setInterval(() => {
|
|
70
73
|
for (const ctrl of controllers) {
|
|
71
|
-
|
|
72
|
-
ctrl.enqueue(pingFrame);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
controllers.delete(ctrl);
|
|
76
|
-
}
|
|
74
|
+
enqueue(ctrl, pingFrame);
|
|
77
75
|
}
|
|
78
76
|
if (controllers.size === 0)
|
|
79
77
|
stopPing();
|
|
@@ -85,15 +83,28 @@ export function devtools(options) {
|
|
|
85
83
|
if (controllers.size === 0)
|
|
86
84
|
stopPing();
|
|
87
85
|
}
|
|
86
|
+
function enqueue(ctrl, data) {
|
|
87
|
+
if (!controllers.has(ctrl))
|
|
88
|
+
return;
|
|
89
|
+
try {
|
|
90
|
+
if (ctrl.desiredSize === null || data.byteLength > ctrl.desiredSize) {
|
|
91
|
+
// Accepted frames drain before EOF; the overflow frame is never retained. EventSource can
|
|
92
|
+
// reconnect to the recent history without a stalled reader keeping its connection slot.
|
|
93
|
+
ctrl.close();
|
|
94
|
+
dropController(ctrl);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
ctrl.enqueue(data);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// A cancelled or already closed stream no longer has a usable controller.
|
|
101
|
+
dropController(ctrl);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
88
104
|
function broadcast(event) {
|
|
89
105
|
const data = encoder.encode(`data: ${JSON.stringify(event)}\n\n`);
|
|
90
106
|
for (const ctrl of controllers) {
|
|
91
|
-
|
|
92
|
-
ctrl.enqueue(data);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
dropController(ctrl);
|
|
96
|
-
}
|
|
107
|
+
enqueue(ctrl, data);
|
|
97
108
|
}
|
|
98
109
|
}
|
|
99
110
|
const adapter = {
|
|
@@ -133,7 +144,8 @@ export function devtools(options) {
|
|
|
133
144
|
return app;
|
|
134
145
|
// Mounted as side effects: DevTools stays a type identity, so `c.trace` is not threaded here.
|
|
135
146
|
// Apps that want the typed trace context add `.use(tracing())` themselves.
|
|
136
|
-
|
|
147
|
+
// A local request log is only useful with the paths actually requested.
|
|
148
|
+
app.use(tracing({ adapters: [adapter], rawUrlPath: true }));
|
|
137
149
|
app.use({
|
|
138
150
|
name: "devtools-stream",
|
|
139
151
|
async onRequest(request, platform) {
|
|
@@ -191,21 +203,25 @@ export function devtools(options) {
|
|
|
191
203
|
activeController = controller;
|
|
192
204
|
controllers.add(controller);
|
|
193
205
|
startPing();
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
206
|
+
// A new client cannot pull during start(). Send only the newest history suffix that
|
|
207
|
+
// fits its budget, so a large snapshot does not cause an immediate reconnect loop.
|
|
208
|
+
const initial = [];
|
|
209
|
+
let initialBytes = 0;
|
|
210
|
+
for (let index = buffer.length - 1; index >= 0; index--) {
|
|
211
|
+
const frame = encoder.encode(`data: ${JSON.stringify(buffer[index])}\n\n`);
|
|
212
|
+
if (frame.byteLength > maxPendingBytes - initialBytes)
|
|
200
213
|
break;
|
|
201
|
-
|
|
214
|
+
initial.push(frame);
|
|
215
|
+
initialBytes += frame.byteLength;
|
|
202
216
|
}
|
|
217
|
+
for (const frame of initial.reverse())
|
|
218
|
+
enqueue(controller, frame);
|
|
203
219
|
},
|
|
204
220
|
cancel() {
|
|
205
221
|
if (activeController !== undefined)
|
|
206
222
|
dropController(activeController);
|
|
207
223
|
},
|
|
208
|
-
});
|
|
224
|
+
}, new ByteLengthQueuingStrategy({ highWaterMark: maxPendingBytes }));
|
|
209
225
|
return new Response(stream, {
|
|
210
226
|
headers: {
|
|
211
227
|
"content-type": "text/event-stream",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAIL,OAAO,GACR,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAIL,OAAO,GACR,MAAM,eAAe,CAAA;AAkDtB,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;AAEjC,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,UAAU,GACd,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;IACrF,OAAO,CACL,UAAU,KAAK,KAAK,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC,CAC9F,CAAA;AACH,CAAC;AAcD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAgC,EAChC,QAAuB,EAAE;IAEzB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,CAAA;IAC7B,MAAM,OAAO,GACX,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;IAC1F,+FAA+F;IAC/F,wFAAwF;IACxF,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,IAAI,CAAC;QACtC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;QACpD,CAAC,CAAC,OAAO,CAAA;AACb,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAqC;IAC5D,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,GAAG,CAAA;IAC3C,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,IAAI,CAAC,CAAC,CAAA;IAChE,MAAM,eAAe,GAAG,OAAO,EAAE,eAAe,IAAI,EAAE,GAAG,IAAI,CAAA;IAC7D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,IAAI,eAAe,GAAG,CAAC;QAC/D,MAAM,IAAI,UAAU,CAAC,0DAA0D,CAAC,CAAA;IAClF,MAAM,QAAQ,GAAG,OAAO,EAAE,IAAI,IAAI,kBAAkB,CAAA;IACpD,oGAAoG;IACpG,wGAAwG;IACxG,MAAM,SAAS,GAAG,GAAG,QAAQ,QAAQ,CAAA;IACrC,oGAAoG;IACpG,6EAA6E;IAC7E,MAAM,OAAO,GACX,OAAO,EAAE,OAAO;QACf,UAA4D,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ;YAClF,aAAa,CAAA;IACjB,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,KAAK,CAAA;IACjD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,cAAc,IAAI,EAAE,CAAC,CAAA;IAC7D,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,IAAI,MAAM,CAAC,CAAA;IACrE,MAAM,MAAM,GAAoB,EAAE,CAAA;IAClC,MAAM,WAAW,GAAG,IAAI,GAAG,EAA+C,CAAA;IAC1E,IAAI,YAAY,GAAG,CAAC,CAAA;IACpB,IAAI,SAAqD,CAAA;IAEzD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAE9C,SAAS,QAAQ;QACf,IAAI,SAAS,KAAK,SAAS;YAAE,OAAM;QACnC,aAAa,CAAC,SAAS,CAAC,CAAA;QACxB,SAAS,GAAG,SAAS,CAAA;IACvB,CAAC;IAED,SAAS,SAAS;QAChB,IAAI,SAAS,KAAK,SAAS;YAAE,OAAM;QACnC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;YAC3B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;gBAC/B,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;YAC1B,CAAC;YACD,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC;gBAAE,QAAQ,EAAE,CAAA;QACxC,CAAC,EAAE,cAAc,CAAC,CAEjB;QAAC,SAAoC,CAAC,KAAK,EAAE,EAAE,CAAA;IAClD,CAAC;IAED,SAAS,cAAc,CAAC,IAAiD;QACvE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACxB,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC;YAAE,QAAQ,EAAE,CAAA;IACxC,CAAC;IAED,SAAS,OAAO,CAAC,IAAiD,EAAE,IAAgB;QAClF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAM;QAClC,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;gBACpE,0FAA0F;gBAC1F,wFAAwF;gBACxF,IAAI,CAAC,KAAK,EAAE,CAAA;gBACZ,cAAc,CAAC,IAAI,CAAC,CAAA;gBACpB,OAAM;YACR,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;YAC1E,cAAc,CAAC,IAAI,CAAC,CAAA;QACtB,CAAC;IACH,CAAC;IAED,SAAS,SAAS,CAAC,KAAoB;QACrC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACjE,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAuB;QAClC,KAAK,CAAC,IAAe;YACnB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAA;YACrD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;YACxC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,2BAA2B,CAAC,CAAA;YAC3D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACzF,OAAM;YACR,CAAC;YACD,IAAI,IAAI,KAAK,QAAQ;gBAAE,OAAM;YAE7B,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAA;YACrD,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAA;YAC5D,MAAM,KAAK,GAAkB;gBAC3B,EAAE,EAAE,MAAM,CAAC,EAAE,YAAY,CAAC;gBAC1B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,MAAM;gBACN,IAAI;gBACJ,MAAM;gBACN,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG;gBAC1D,SAAS,EAAE,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;gBAChE,SAAS,EAAE,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;aACzD,CAAA;YAED,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAClB,IAAI,MAAM,CAAC,MAAM,GAAG,SAAS;gBAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;YAC1E,SAAS,CAAC,KAAK,CAAC,CAAA;QAClB,CAAC;KACF,CAAA;IAED,OAAO,kBAAkB,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE;QAC5C,8FAA8F;QAC9F,IAAI,CAAC,OAAO;YAAE,OAAO,GAAG,CAAA;QACxB,8FAA8F;QAC9F,2EAA2E;QAC3E,wEAAwE;QACxE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC3D,GAAG,CAAC,GAAG,CAAC;YACN,IAAI,EAAE,iBAAiB;YAEvB,KAAK,CAAC,SAAS,CAAC,OAAgB,EAAE,QAAQ;gBACxC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;gBAChC,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;oBAAE,OAAO,SAAS,CAAA;gBAE7E,4FAA4F;gBAC5F,2FAA2F;gBAC3F,wFAAwF;gBACxF,8FAA8F;gBAC9F,+FAA+F;gBAC/F,2FAA2F;gBAC3F,MAAM,YAAY,GAChB,GAAG,CAAC,QAAQ,KAAK,WAAW;oBAC5B,GAAG,CAAC,QAAQ,KAAK,WAAW;oBAC5B,GAAG,CAAC,QAAQ,KAAK,OAAO;oBACxB,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;gBACrC,MAAM,QAAQ,GACZ,YAAY,IAAI,CAAC,QAAQ,EAAE,QAAQ,KAAK,SAAS,IAAI,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAA;gBACzF,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAC9B,OAAO,IAAI,QAAQ,CAAC,gDAAgD,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;gBACxF,CAAC;gBACD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;gBAC5C,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC5E,OAAO,IAAI,QAAQ,CAAC,oBAAoB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;gBAC5D,CAAC;gBACD,IAAI,OAAO,EAAE,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;oBAC5E,OAAO,IAAI,QAAQ,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;gBACtD,CAAC;gBAED,iGAAiG;gBACjG,+FAA+F;gBAC/F,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC/B,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;oBAC9C,MAAM,KAAK,GACT,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;oBACvF,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE;wBAC1C,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS;wBAC/C,KAAK;qBACN,CAAC,CAAA;oBACF,OAAO,QAAQ,CAAC,IAAI,CAClB,EAAE,MAAM,EAAE,EACV;wBACE,OAAO,EAAE;4BACP,eAAe,EAAE,UAAU;4BAC3B,wBAAwB,EAAE,SAAS;4BACnC,kBAAkB,EAAE,MAAM;yBAC3B;qBACF,CACF,CAAA;gBACH,CAAC;gBAED,uBAAuB;gBACvB,IAAI,WAAW,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;oBACvC,OAAO,IAAI,QAAQ,CAAC,+BAA+B,EAAE;wBACnD,MAAM,EAAE,GAAG;wBACX,OAAO,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE;qBAChC,CAAC,CAAA;gBACJ,CAAC;gBAED,IAAI,gBAAyE,CAAA;gBAC7E,MAAM,MAAM,GAAG,IAAI,cAAc,CAC/B;oBACE,KAAK,CAAC,UAAU;wBACd,gBAAgB,GAAG,UAAU,CAAA;wBAC7B,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;wBAC3B,SAAS,EAAE,CAAA;wBACX,oFAAoF;wBACpF,mFAAmF;wBACnF,MAAM,OAAO,GAAiB,EAAE,CAAA;wBAChC,IAAI,YAAY,GAAG,CAAC,CAAA;wBACpB,KAAK,IAAI,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;4BACxD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAA;4BAC1E,IAAI,KAAK,CAAC,UAAU,GAAG,eAAe,GAAG,YAAY;gCAAE,MAAK;4BAC5D,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;4BACnB,YAAY,IAAI,KAAK,CAAC,UAAU,CAAA;wBAClC,CAAC;wBACD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE;4BAAE,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;oBACnE,CAAC;oBACD,MAAM;wBACJ,IAAI,gBAAgB,KAAK,SAAS;4BAAE,cAAc,CAAC,gBAAgB,CAAC,CAAA;oBACtE,CAAC;iBACF,EACD,IAAI,yBAAyB,CAAC,EAAE,aAAa,EAAE,eAAe,EAAE,CAAC,CAClE,CAAA;gBAED,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE;oBAC1B,OAAO,EAAE;wBACP,cAAc,EAAE,mBAAmB;wBACnC,eAAe,EAAE,UAAU;wBAC3B,UAAU,EAAE,YAAY;wBACxB,yBAAyB,EAAE,oBAAoB;wBAC/C,wBAAwB,EAAE,SAAS;wBACnC,kBAAkB,EAAE,MAAM;qBAC3B;iBACF,CAAC,CAAA;YACJ,CAAC;YAED,YAAY,CAAC,OAAO;gBAClB,CAAC;gBAAC,OAAgE,CAAC,WAAW,EAAE,UAAU,CACxF,OAAO,CACR,CAAA;gBACD,OAAO,SAAS,CAAA;YAClB,CAAC;SACF,CAAC,CAAA;QACF,OAAO,GAAG,CAAA;IACZ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAiC,EAAE;IACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,IAAI,kBAAkB,CAAC,CAAA;IACnE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA8FkB,QAAQ;;;;;;MAM7B,CAAA;AACN,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/devtools",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Interactive DevTools overlay for nifra dev - loader traces, ISR status, route metadata.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@nifrajs/otel": "
|
|
48
|
+
"@nifrajs/otel": "5.0.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@nifrajs/core": "^
|
|
51
|
+
"@nifrajs/core": "^5.0.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@nifrajs/core": "workspace:*"
|
package/src/index.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface DevToolsOptions {
|
|
|
45
45
|
readonly maxEvents?: number | undefined
|
|
46
46
|
/** Maximum simultaneous SSE connections (default 5). */
|
|
47
47
|
readonly maxConnections?: number | undefined
|
|
48
|
+
/** Maximum queued SSE bytes per connection (default 64 KiB). Overflow closes that connection. */
|
|
49
|
+
readonly maxPendingBytes?: number | undefined
|
|
48
50
|
/** SSE path (default `/_nifra/devtools`). */
|
|
49
51
|
readonly path?: string | undefined
|
|
50
52
|
/** Explicit enable switch. Defaults to true only when `NODE_ENV === "development"`. */
|
|
@@ -86,7 +88,7 @@ function isLoopbackPeer(address: string): boolean {
|
|
|
86
88
|
* DevTools plugin. Its observation adapter projects the single request span into a
|
|
87
89
|
* `DevToolsEvent`; its middleware only owns the secured SSE transport.
|
|
88
90
|
* When configuring `tracing()` yourself, register it before this plugin so DevTools attaches to
|
|
89
|
-
* that request owner.
|
|
91
|
+
* that request owner, and pass it `rawUrlPath: true` to list raw paths instead of route templates.
|
|
90
92
|
*/
|
|
91
93
|
/** Query for a DevTools state snapshot: an optional `path` prefix and a most-recent-`limit`. */
|
|
92
94
|
export interface DevToolsQuery {
|
|
@@ -116,6 +118,9 @@ export function filterDevToolsEvents(
|
|
|
116
118
|
export function devtools(options?: DevToolsOptions | undefined) {
|
|
117
119
|
const maxEvents = options?.maxEvents ?? 200
|
|
118
120
|
const maxConnections = Math.max(1, options?.maxConnections ?? 5)
|
|
121
|
+
const maxPendingBytes = options?.maxPendingBytes ?? 64 * 1024
|
|
122
|
+
if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < 1)
|
|
123
|
+
throw new RangeError("DevTools maxPendingBytes must be a positive safe integer")
|
|
119
124
|
const endpoint = options?.path ?? "/_nifra/devtools"
|
|
120
125
|
// A one-shot JSON snapshot of the ring buffer, alongside the live SSE stream. The SSE path is for a
|
|
121
126
|
// human watching the overlay; this is for an agent that made a request and wants to READ what happened.
|
|
@@ -130,7 +135,7 @@ export function devtools(options?: DevToolsOptions | undefined) {
|
|
|
130
135
|
const allowedOrigins = new Set(options?.allowedOrigins ?? [])
|
|
131
136
|
const pingIntervalMs = Math.max(1, options?.pingIntervalMs ?? 15_000)
|
|
132
137
|
const buffer: DevToolsEvent[] = []
|
|
133
|
-
const controllers = new Set<ReadableStreamDefaultController
|
|
138
|
+
const controllers = new Set<ReadableStreamDefaultController<Uint8Array>>()
|
|
134
139
|
let eventCounter = 0
|
|
135
140
|
let pingTimer: ReturnType<typeof setInterval> | undefined
|
|
136
141
|
|
|
@@ -146,11 +151,7 @@ export function devtools(options?: DevToolsOptions | undefined) {
|
|
|
146
151
|
if (pingTimer !== undefined) return
|
|
147
152
|
pingTimer = setInterval(() => {
|
|
148
153
|
for (const ctrl of controllers) {
|
|
149
|
-
|
|
150
|
-
ctrl.enqueue(pingFrame)
|
|
151
|
-
} catch {
|
|
152
|
-
controllers.delete(ctrl)
|
|
153
|
-
}
|
|
154
|
+
enqueue(ctrl, pingFrame)
|
|
154
155
|
}
|
|
155
156
|
if (controllers.size === 0) stopPing()
|
|
156
157
|
}, pingIntervalMs)
|
|
@@ -158,19 +159,32 @@ export function devtools(options?: DevToolsOptions | undefined) {
|
|
|
158
159
|
;(pingTimer as { unref?: () => void }).unref?.()
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
function dropController(ctrl: ReadableStreamDefaultController): void {
|
|
162
|
+
function dropController(ctrl: ReadableStreamDefaultController<Uint8Array>): void {
|
|
162
163
|
controllers.delete(ctrl)
|
|
163
164
|
if (controllers.size === 0) stopPing()
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
function enqueue(ctrl: ReadableStreamDefaultController<Uint8Array>, data: Uint8Array): void {
|
|
168
|
+
if (!controllers.has(ctrl)) return
|
|
169
|
+
try {
|
|
170
|
+
if (ctrl.desiredSize === null || data.byteLength > ctrl.desiredSize) {
|
|
171
|
+
// Accepted frames drain before EOF; the overflow frame is never retained. EventSource can
|
|
172
|
+
// reconnect to the recent history without a stalled reader keeping its connection slot.
|
|
173
|
+
ctrl.close()
|
|
174
|
+
dropController(ctrl)
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
ctrl.enqueue(data)
|
|
178
|
+
} catch {
|
|
179
|
+
// A cancelled or already closed stream no longer has a usable controller.
|
|
180
|
+
dropController(ctrl)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
166
184
|
function broadcast(event: DevToolsEvent): void {
|
|
167
185
|
const data = encoder.encode(`data: ${JSON.stringify(event)}\n\n`)
|
|
168
186
|
for (const ctrl of controllers) {
|
|
169
|
-
|
|
170
|
-
ctrl.enqueue(data)
|
|
171
|
-
} catch {
|
|
172
|
-
dropController(ctrl)
|
|
173
|
-
}
|
|
187
|
+
enqueue(ctrl, data)
|
|
174
188
|
}
|
|
175
189
|
}
|
|
176
190
|
|
|
@@ -211,7 +225,8 @@ export function devtools(options?: DevToolsOptions | undefined) {
|
|
|
211
225
|
if (!enabled) return app
|
|
212
226
|
// Mounted as side effects: DevTools stays a type identity, so `c.trace` is not threaded here.
|
|
213
227
|
// Apps that want the typed trace context add `.use(tracing())` themselves.
|
|
214
|
-
|
|
228
|
+
// A local request log is only useful with the paths actually requested.
|
|
229
|
+
app.use(tracing({ adapters: [adapter], rawUrlPath: true }))
|
|
215
230
|
app.use({
|
|
216
231
|
name: "devtools-stream",
|
|
217
232
|
|
|
@@ -273,25 +288,31 @@ export function devtools(options?: DevToolsOptions | undefined) {
|
|
|
273
288
|
})
|
|
274
289
|
}
|
|
275
290
|
|
|
276
|
-
let activeController: ReadableStreamDefaultController | undefined
|
|
277
|
-
const stream = new ReadableStream(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
291
|
+
let activeController: ReadableStreamDefaultController<Uint8Array> | undefined
|
|
292
|
+
const stream = new ReadableStream<Uint8Array>(
|
|
293
|
+
{
|
|
294
|
+
start(controller) {
|
|
295
|
+
activeController = controller
|
|
296
|
+
controllers.add(controller)
|
|
297
|
+
startPing()
|
|
298
|
+
// A new client cannot pull during start(). Send only the newest history suffix that
|
|
299
|
+
// fits its budget, so a large snapshot does not cause an immediate reconnect loop.
|
|
300
|
+
const initial: Uint8Array[] = []
|
|
301
|
+
let initialBytes = 0
|
|
302
|
+
for (let index = buffer.length - 1; index >= 0; index--) {
|
|
303
|
+
const frame = encoder.encode(`data: ${JSON.stringify(buffer[index])}\n\n`)
|
|
304
|
+
if (frame.byteLength > maxPendingBytes - initialBytes) break
|
|
305
|
+
initial.push(frame)
|
|
306
|
+
initialBytes += frame.byteLength
|
|
288
307
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
308
|
+
for (const frame of initial.reverse()) enqueue(controller, frame)
|
|
309
|
+
},
|
|
310
|
+
cancel() {
|
|
311
|
+
if (activeController !== undefined) dropController(activeController)
|
|
312
|
+
},
|
|
293
313
|
},
|
|
294
|
-
|
|
314
|
+
new ByteLengthQueuingStrategy({ highWaterMark: maxPendingBytes }),
|
|
315
|
+
)
|
|
295
316
|
|
|
296
317
|
return new Response(stream, {
|
|
297
318
|
headers: {
|