@mauroandre/velojs 0.0.58 → 0.0.59
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 +86 -0
- package/agent/skills/velojs-telemetry/SKILL.md +182 -0
- package/dist/channels.d.ts +8 -1
- package/dist/channels.js +282 -227
- package/dist/cli.js +46 -32
- package/dist/events.d.ts +9 -1
- package/dist/events.js +161 -152
- package/dist/server.js +242 -197
- package/dist/sockets.d.ts +4 -1
- package/dist/sockets.js +115 -102
- package/dist/telemetry-cli.d.ts +67 -0
- package/dist/telemetry-cli.js +265 -0
- package/dist/telemetry-node.d.ts +38 -0
- package/dist/telemetry-node.js +93 -0
- package/dist/telemetry.d.ts +204 -0
- package/dist/telemetry.js +240 -0
- package/package.json +1 -1
package/dist/sockets.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { trace as _, byteLength as M } from "./telemetry.js";
|
|
2
|
+
async function* N(e) {
|
|
2
3
|
for await (const t of e) {
|
|
3
4
|
const n = typeof t == "string" ? t : new TextDecoder().decode(t);
|
|
4
5
|
try {
|
|
@@ -8,28 +9,28 @@ async function* W(e) {
|
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
|
-
const
|
|
12
|
-
async function
|
|
13
|
-
let t =
|
|
12
|
+
const S = /* @__PURE__ */ new WeakMap(), m = /* @__PURE__ */ new WeakMap(), w = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
async function x(e) {
|
|
14
|
+
let t = S.get(e);
|
|
14
15
|
if (t) return t;
|
|
15
16
|
try {
|
|
16
|
-
return t = (await import("@hono/node-ws")).createNodeWebSocket({ app: e }),
|
|
17
|
+
return t = (await import("@hono/node-ws")).createNodeWebSocket({ app: e }), S.set(e, t), t;
|
|
17
18
|
} catch (n) {
|
|
18
19
|
return console.error("[velojs] failed to load @hono/node-ws:", n), null;
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
function b(e, t) {
|
|
22
|
-
let n =
|
|
23
|
-
n || (n = /* @__PURE__ */ new Set(),
|
|
23
|
+
let n = w.get(e);
|
|
24
|
+
n || (n = /* @__PURE__ */ new Set(), w.set(e, n)), n.add(t);
|
|
24
25
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
26
|
+
function p(e, t) {
|
|
27
|
+
w.get(e)?.delete(t);
|
|
27
28
|
}
|
|
28
|
-
function
|
|
29
|
-
return b(e, t), () =>
|
|
29
|
+
function $(e, t) {
|
|
30
|
+
return b(e, t), () => p(e, t);
|
|
30
31
|
}
|
|
31
|
-
function
|
|
32
|
-
const t =
|
|
32
|
+
function E(e) {
|
|
33
|
+
const t = w.get(e);
|
|
33
34
|
if (t) {
|
|
34
35
|
for (const n of t)
|
|
35
36
|
try {
|
|
@@ -39,158 +40,170 @@ function M(e) {
|
|
|
39
40
|
t.clear();
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
function
|
|
43
|
-
return
|
|
43
|
+
function L(e) {
|
|
44
|
+
return m.get(e) ?? /* @__PURE__ */ new Map();
|
|
44
45
|
}
|
|
45
|
-
function
|
|
46
|
-
const
|
|
47
|
-
let
|
|
48
|
-
const l =
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
function I(e, t, n, u) {
|
|
47
|
+
const a = [];
|
|
48
|
+
let s = null, r = !1, i = !1, c = null;
|
|
49
|
+
const l = _("socket-connect", {
|
|
50
|
+
route: u.path,
|
|
51
|
+
module: u.moduleId,
|
|
52
|
+
name: u.name
|
|
53
|
+
});
|
|
54
|
+
let h = !1;
|
|
55
|
+
const d = (f) => {
|
|
56
|
+
h || (h = !0, l.end({ status: f }));
|
|
57
|
+
}, y = new AbortController();
|
|
58
|
+
b(e, y);
|
|
59
|
+
const k = {
|
|
51
60
|
async *[Symbol.asyncIterator]() {
|
|
52
61
|
for (; ; ) {
|
|
53
|
-
for (;
|
|
54
|
-
yield
|
|
55
|
-
if (
|
|
62
|
+
for (; a.length > 0; )
|
|
63
|
+
yield a.shift();
|
|
64
|
+
if (r) return;
|
|
56
65
|
await new Promise((f) => {
|
|
57
|
-
|
|
58
|
-
}),
|
|
66
|
+
s = f;
|
|
67
|
+
}), s = null;
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
|
-
},
|
|
62
|
-
if (!(!
|
|
70
|
+
}, v = (f) => {
|
|
71
|
+
if (!(!c || r))
|
|
63
72
|
try {
|
|
64
|
-
typeof f == "string" || f instanceof Uint8Array ?
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
const o = typeof f == "string" || f instanceof Uint8Array ? f : JSON.stringify(f);
|
|
74
|
+
c.send(o), l.enabled && l.delivery(typeof o == "string" ? M(o) : o.byteLength);
|
|
75
|
+
} catch (o) {
|
|
76
|
+
console.error("[velojs] socket send failed:", o);
|
|
67
77
|
}
|
|
68
|
-
},
|
|
69
|
-
if (
|
|
78
|
+
}, j = (f, o) => {
|
|
79
|
+
if (c)
|
|
70
80
|
try {
|
|
71
|
-
|
|
81
|
+
c.close(f, o);
|
|
72
82
|
} catch {
|
|
73
83
|
}
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
if (
|
|
84
|
+
}, A = () => {
|
|
85
|
+
i = !0, d("ok");
|
|
86
|
+
}, g = () => {
|
|
87
|
+
if (r || (r = !0, s?.(), l.close()), !y.signal.aborted)
|
|
78
88
|
try {
|
|
79
|
-
|
|
89
|
+
y.abort();
|
|
80
90
|
} catch {
|
|
81
91
|
}
|
|
82
|
-
|
|
92
|
+
p(e, y);
|
|
83
93
|
};
|
|
84
94
|
return {
|
|
85
95
|
events: {
|
|
86
|
-
onOpen(f,
|
|
87
|
-
|
|
96
|
+
onOpen(f, o) {
|
|
97
|
+
c = o, Promise.resolve(
|
|
88
98
|
t({
|
|
89
|
-
incoming:
|
|
90
|
-
send:
|
|
91
|
-
close:
|
|
92
|
-
keepOpen:
|
|
93
|
-
abortSignal:
|
|
99
|
+
incoming: k,
|
|
100
|
+
send: v,
|
|
101
|
+
close: j,
|
|
102
|
+
keepOpen: A,
|
|
103
|
+
abortSignal: y.signal,
|
|
94
104
|
c: n,
|
|
95
105
|
params: n.req.param(),
|
|
96
106
|
query: n.req.query()
|
|
97
107
|
})
|
|
98
108
|
).then(() => {
|
|
99
|
-
if (!
|
|
109
|
+
if (d("ok"), !i && c && !r)
|
|
100
110
|
try {
|
|
101
|
-
|
|
111
|
+
c.close();
|
|
102
112
|
} catch {
|
|
103
113
|
}
|
|
104
|
-
}).catch((
|
|
105
|
-
console.error("[velojs] socket handler threw:",
|
|
114
|
+
}).catch((W) => {
|
|
115
|
+
d("error"), console.error("[velojs] socket handler threw:", W);
|
|
106
116
|
try {
|
|
107
|
-
|
|
117
|
+
c?.close(1011, "handler error");
|
|
108
118
|
} catch {
|
|
109
119
|
}
|
|
110
|
-
});
|
|
120
|
+
}), typeof setImmediate == "function" ? setImmediate(() => d("ok")) : setTimeout(() => d("ok"), 0);
|
|
111
121
|
},
|
|
112
122
|
onMessage(f) {
|
|
113
|
-
if (
|
|
114
|
-
const
|
|
115
|
-
typeof
|
|
123
|
+
if (r) return;
|
|
124
|
+
const o = f.data;
|
|
125
|
+
typeof o == "string" ? a.push(o) : o instanceof ArrayBuffer ? a.push(new Uint8Array(o)) : o && typeof o == "object" && "buffer" in o && o.buffer instanceof ArrayBuffer && a.push(new Uint8Array(o.buffer)), s?.();
|
|
116
126
|
},
|
|
117
127
|
onClose() {
|
|
118
|
-
|
|
128
|
+
g();
|
|
119
129
|
},
|
|
120
130
|
onError() {
|
|
121
|
-
|
|
131
|
+
g();
|
|
122
132
|
}
|
|
123
133
|
},
|
|
124
|
-
cleanup:
|
|
134
|
+
cleanup: g
|
|
125
135
|
};
|
|
126
136
|
}
|
|
127
|
-
async function
|
|
128
|
-
let
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
132
|
-
const
|
|
133
|
-
|
|
137
|
+
async function O(e, t, n, u = [], a = {}) {
|
|
138
|
+
let s = m.get(e);
|
|
139
|
+
s || (s = /* @__PURE__ */ new Map(), m.set(e, s)), s.set(t, { path: t, handler: n, middlewares: u });
|
|
140
|
+
const r = await x(e);
|
|
141
|
+
if (!r) return;
|
|
142
|
+
const i = r.upgradeWebSocket((c) => I(e, n, c, { path: t, ...a }).events);
|
|
143
|
+
u.length > 0 ? e.on(["GET"], [t], ...u, i) : e.on(["GET"], [t], i);
|
|
134
144
|
}
|
|
135
|
-
async function
|
|
136
|
-
const
|
|
137
|
-
for (const
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
140
|
-
for (const
|
|
141
|
-
if (!
|
|
142
|
-
const
|
|
143
|
-
if (typeof
|
|
145
|
+
async function R(e, t, n = []) {
|
|
146
|
+
const u = async (a, s) => {
|
|
147
|
+
for (const r of a) {
|
|
148
|
+
const i = r.module?.metadata?.moduleId, c = [...s, ...r.middlewares ?? []];
|
|
149
|
+
if (r.module && i)
|
|
150
|
+
for (const l of Object.keys(r.module)) {
|
|
151
|
+
if (!l.startsWith("socket_")) continue;
|
|
152
|
+
const h = r.module[l];
|
|
153
|
+
if (typeof h != "function") {
|
|
144
154
|
console.warn(
|
|
145
|
-
`[velojs] socket_* export ${
|
|
155
|
+
`[velojs] socket_* export ${i}/${l} is not a function; skipped`
|
|
146
156
|
);
|
|
147
157
|
continue;
|
|
148
158
|
}
|
|
149
|
-
const
|
|
159
|
+
const d = l.replace("socket_", ""), y = `/_socket/${i}/${d}`;
|
|
150
160
|
try {
|
|
151
|
-
|
|
161
|
+
h.__path = y;
|
|
152
162
|
} catch {
|
|
153
163
|
}
|
|
154
164
|
try {
|
|
155
|
-
|
|
165
|
+
h.__isVeloSocket = !0;
|
|
156
166
|
} catch {
|
|
157
167
|
}
|
|
158
|
-
await
|
|
168
|
+
await O(e, y, h, c, {
|
|
169
|
+
moduleId: i,
|
|
170
|
+
name: d
|
|
171
|
+
});
|
|
159
172
|
}
|
|
160
|
-
|
|
173
|
+
r.children && await u(r.children, c);
|
|
161
174
|
}
|
|
162
175
|
};
|
|
163
|
-
await
|
|
176
|
+
await u(t, n);
|
|
164
177
|
}
|
|
165
|
-
async function
|
|
166
|
-
const n =
|
|
178
|
+
async function T(e, t) {
|
|
179
|
+
const n = S.get(e);
|
|
167
180
|
if (!n) return;
|
|
168
|
-
const
|
|
169
|
-
get(
|
|
170
|
-
return s === "on" || s === "addListener" ? (
|
|
171
|
-
const y =
|
|
172
|
-
let
|
|
181
|
+
const u = new Proxy(t, {
|
|
182
|
+
get(a, s, r) {
|
|
183
|
+
return s === "on" || s === "addListener" ? (i, c) => i !== "upgrade" ? a[s](i, c) : a[s]("upgrade", (l, h, d) => {
|
|
184
|
+
const y = l.headers?.host ?? "localhost";
|
|
185
|
+
let k;
|
|
173
186
|
try {
|
|
174
|
-
|
|
187
|
+
k = new URL(l.url ?? "/", `http://${y}`).pathname;
|
|
175
188
|
} catch {
|
|
176
189
|
return;
|
|
177
190
|
}
|
|
178
|
-
|
|
179
|
-
}) : Reflect.get(
|
|
191
|
+
m.get(e)?.has(k) && c(l, h, d);
|
|
192
|
+
}) : Reflect.get(a, s, r);
|
|
180
193
|
}
|
|
181
194
|
});
|
|
182
195
|
try {
|
|
183
|
-
n.injectWebSocket(
|
|
184
|
-
} catch (
|
|
185
|
-
console.error("[velojs] injectWebSocket failed:",
|
|
196
|
+
n.injectWebSocket(u);
|
|
197
|
+
} catch (a) {
|
|
198
|
+
console.error("[velojs] injectWebSocket failed:", a);
|
|
186
199
|
}
|
|
187
200
|
}
|
|
188
201
|
export {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
202
|
+
E as abortAllSocketSessions,
|
|
203
|
+
L as getAppSockets,
|
|
204
|
+
T as injectWebSocketServer,
|
|
205
|
+
N as parseJson,
|
|
206
|
+
O as registerSocketHandler,
|
|
207
|
+
R as registerSocketRoutes,
|
|
208
|
+
$ as trackSocketSession
|
|
196
209
|
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `velojs telemetry` — the inaugural consumption surface over the collection.
|
|
3
|
+
*
|
|
4
|
+
* Reads the JSONL the runtime writes (one line per trace) and prints the
|
|
5
|
+
* ranking by route/action/channel — count, errors, p50/p95 and volume in bytes
|
|
6
|
+
* — plus the timeline. The window is the whole file by default, recut with
|
|
7
|
+
* `--last <duration>` (`--last 60m`). Without a path, the destination comes
|
|
8
|
+
* from `VELO_TELEMETRY_FILE` in the environment or in the project's `.env`,
|
|
9
|
+
* including the instance and rotation files that derive from it; an explicit
|
|
10
|
+
* path reads a JSONL copied from production.
|
|
11
|
+
*
|
|
12
|
+
* No UI, no server route: the operator's terminal is the whole surface.
|
|
13
|
+
*/
|
|
14
|
+
import { type TelemetryEvent } from "./telemetry.js";
|
|
15
|
+
export interface TelemetryCliIo {
|
|
16
|
+
cwd?: string | undefined;
|
|
17
|
+
env?: Record<string, string | undefined> | undefined;
|
|
18
|
+
write?: ((line: string) => void) | undefined;
|
|
19
|
+
writeError?: ((line: string) => void) | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface RankRow {
|
|
22
|
+
key: string;
|
|
23
|
+
count: number;
|
|
24
|
+
errors: number;
|
|
25
|
+
p50: number | null;
|
|
26
|
+
p95: number | null;
|
|
27
|
+
bytes: number;
|
|
28
|
+
}
|
|
29
|
+
export interface TimelineEntry {
|
|
30
|
+
ts: string;
|
|
31
|
+
type: string;
|
|
32
|
+
label: string;
|
|
33
|
+
duration: number | null;
|
|
34
|
+
status: string | null;
|
|
35
|
+
bytes: number | null;
|
|
36
|
+
deliveries: number | null;
|
|
37
|
+
}
|
|
38
|
+
export interface TelemetryReport {
|
|
39
|
+
files: string[];
|
|
40
|
+
events: TelemetryEvent[];
|
|
41
|
+
routes: RankRow[];
|
|
42
|
+
actions: RankRow[];
|
|
43
|
+
channels: RankRow[];
|
|
44
|
+
timeline: TimelineEntry[];
|
|
45
|
+
}
|
|
46
|
+
/** `--last` accepts `ms`, `s`, `m`, `h`, `d`; a bare number counts seconds. */
|
|
47
|
+
export declare function parseDuration(raw: string): number;
|
|
48
|
+
/**
|
|
49
|
+
* The files a destination resolves to. The effective file of each instance
|
|
50
|
+
* carries a pid/hostname suffix (`telemetry.123-host.jsonl`) and rotation adds
|
|
51
|
+
* a numeric tail (`…jsonl.1`), so a base path collects its whole family. A
|
|
52
|
+
* directory collects every JSONL inside it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function collectTelemetryFiles(target: string, basePath: boolean): string[];
|
|
55
|
+
/** Reads every JSONL file, skipping malformed lines, in chronological order. */
|
|
56
|
+
export declare function readTelemetryEvents(files: string[]): TelemetryEvent[];
|
|
57
|
+
/** Keeps the events inside the `--last` window (everything without `lastMs`). */
|
|
58
|
+
export declare function windowEvents(events: TelemetryEvent[], lastMs: number | null, nowMs: number): TelemetryEvent[];
|
|
59
|
+
/** The whole analysis, as data: rankings + timeline. */
|
|
60
|
+
export declare function buildTelemetryReport(files: string[], events: TelemetryEvent[]): TelemetryReport;
|
|
61
|
+
/** Renders the human report — the CLI's whole stdout. */
|
|
62
|
+
export declare function formatTelemetryReport(report: TelemetryReport, lastMs: number | null, nowMs: number, lastLabel?: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Runs the `telemetry` command. Throws (legibly) on a missing destination;
|
|
65
|
+
* the CLI turns that into exit code 1.
|
|
66
|
+
*/
|
|
67
|
+
export declare function runTelemetryCli(args: string[], io?: TelemetryCliIo): Promise<void>;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import d from "node:fs";
|
|
2
|
+
import a from "node:path";
|
|
3
|
+
import { expandTelemetryValue as w } from "./telemetry.js";
|
|
4
|
+
import { readProjectEnv as k } from "./telemetry-node.js";
|
|
5
|
+
function j(e) {
|
|
6
|
+
const o = e.trim().match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/);
|
|
7
|
+
if (!o)
|
|
8
|
+
throw new Error(
|
|
9
|
+
`[velojs] invalid duration "${e}" — use a unit: 500ms, 30s, 60m, 2h, 7d.`
|
|
10
|
+
);
|
|
11
|
+
const n = Number(o[1]), i = o[2] ?? "s";
|
|
12
|
+
return n * (i === "ms" ? 1 : i === "s" ? 1e3 : i === "m" ? 6e4 : i === "h" ? 36e5 : 864e5);
|
|
13
|
+
}
|
|
14
|
+
function g(e) {
|
|
15
|
+
try {
|
|
16
|
+
return d.statSync(e);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function x(e, o) {
|
|
22
|
+
const n = g(e);
|
|
23
|
+
if (n?.isDirectory())
|
|
24
|
+
return d.readdirSync(e).filter((u) => u.includes(".jsonl")).sort().map((u) => a.join(e, u));
|
|
25
|
+
if (n?.isFile()) return [e];
|
|
26
|
+
if (!o)
|
|
27
|
+
throw new Error(`[velojs] velojs telemetry: file not found — ${e}`);
|
|
28
|
+
const i = a.dirname(e), t = a.basename(e), r = t.lastIndexOf("."), s = r > 0 ? t.slice(0, r) : t, l = r > 0 ? t.slice(r) : "";
|
|
29
|
+
if (!g(i))
|
|
30
|
+
throw new Error(
|
|
31
|
+
`[velojs] velojs telemetry: no telemetry files at ${e} — the directory does not exist.`
|
|
32
|
+
);
|
|
33
|
+
const h = d.readdirSync(i).filter((u) => u === t ? !0 : u.startsWith(`${s}.`) ? l === "" || u.includes(l) : !1).sort().map((u) => a.join(i, u));
|
|
34
|
+
if (h.length === 0)
|
|
35
|
+
throw new Error(
|
|
36
|
+
`[velojs] velojs telemetry: no telemetry files found for ${e} — set VELO_TELEMETRY_FILE (see .env.example) or pass the JSONL path explicitly.`
|
|
37
|
+
);
|
|
38
|
+
return h;
|
|
39
|
+
}
|
|
40
|
+
function M(e, o, n, i) {
|
|
41
|
+
if (e) {
|
|
42
|
+
const l = w(e, { ...n, ...o }), h = a.isAbsolute(l) ? l : a.resolve(i, l);
|
|
43
|
+
return x(h, !1);
|
|
44
|
+
}
|
|
45
|
+
const t = o.VELO_TELEMETRY_FILE ?? n.VELO_TELEMETRY_FILE ?? "";
|
|
46
|
+
if (!t.trim())
|
|
47
|
+
throw new Error(
|
|
48
|
+
"[velojs] velojs telemetry: no destination — set VELO_TELEMETRY_FILE in the environment or in the project's .env (see .env.example), or pass the JSONL path as an argument."
|
|
49
|
+
);
|
|
50
|
+
const r = w(t.trim(), { ...n, ...o }), s = a.isAbsolute(r) ? r : a.resolve(i, r);
|
|
51
|
+
return x(s, !0);
|
|
52
|
+
}
|
|
53
|
+
function O(e) {
|
|
54
|
+
const o = [];
|
|
55
|
+
for (const n of e) {
|
|
56
|
+
let i;
|
|
57
|
+
try {
|
|
58
|
+
i = d.readFileSync(n, "utf-8");
|
|
59
|
+
} catch (t) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`[velojs] velojs telemetry: cannot read ${n} — ${t instanceof Error ? t.message : String(t)}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
for (const t of i.split(`
|
|
65
|
+
`)) {
|
|
66
|
+
const r = t.trim();
|
|
67
|
+
if (r)
|
|
68
|
+
try {
|
|
69
|
+
const s = JSON.parse(r);
|
|
70
|
+
s && typeof s == "object" && typeof s.type == "string" && o.push(s);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return o.sort((n, i) => {
|
|
76
|
+
const t = Date.parse(n.ts ?? ""), r = Date.parse(i.ts ?? "");
|
|
77
|
+
return (Number.isFinite(t) ? t : 0) - (Number.isFinite(r) ? r : 0);
|
|
78
|
+
}), o;
|
|
79
|
+
}
|
|
80
|
+
function N(e, o, n) {
|
|
81
|
+
if (o === null) return e;
|
|
82
|
+
const i = n - o;
|
|
83
|
+
return e.filter((t) => {
|
|
84
|
+
const r = Date.parse(t.ts ?? "");
|
|
85
|
+
return Number.isFinite(r) && r >= i;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function L(e, o) {
|
|
89
|
+
if (e.length === 0) return null;
|
|
90
|
+
const n = [...e].sort((s, l) => s - l);
|
|
91
|
+
if (n.length === 1) return n[0];
|
|
92
|
+
const i = (n.length - 1) * o, t = Math.floor(i), r = Math.ceil(i);
|
|
93
|
+
return t === r ? n[t] : n[t] + (n[r] - n[t]) * (i - t);
|
|
94
|
+
}
|
|
95
|
+
function y(e, o, n) {
|
|
96
|
+
const i = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const t of e) {
|
|
98
|
+
if (!o(t)) continue;
|
|
99
|
+
const r = n(t);
|
|
100
|
+
let s = i.get(r);
|
|
101
|
+
s || (s = { durations: [], count: 0, errors: 0, bytes: 0 }, i.set(r, s)), s.count += 1, t.status === "error" && (s.errors += 1), typeof t.duration == "number" && Number.isFinite(t.duration) && s.durations.push(t.duration), typeof t.bytes == "number" && Number.isFinite(t.bytes) && (s.bytes += t.bytes);
|
|
102
|
+
}
|
|
103
|
+
return [...i.entries()].map(([t, r]) => ({
|
|
104
|
+
key: t,
|
|
105
|
+
count: r.count,
|
|
106
|
+
errors: r.errors,
|
|
107
|
+
p50: L(r.durations, 0.5),
|
|
108
|
+
p95: L(r.durations, 0.95),
|
|
109
|
+
bytes: r.bytes
|
|
110
|
+
})).sort((t, r) => r.count - t.count || (t.key < r.key ? -1 : t.key > r.key ? 1 : 0));
|
|
111
|
+
}
|
|
112
|
+
const R = (e) => e.type === "page" || e.type === "data" || e.type === "endpoint", D = (e) => {
|
|
113
|
+
const o = e.route ?? "(no route)";
|
|
114
|
+
return e.type === "endpoint" && e.method ? `${e.method} ${o}` : o;
|
|
115
|
+
}, S = (e) => `${e.module ?? "(no module)"}/${e.name ?? "(no name)"}`, _ = (e) => typeof e.channel == "string";
|
|
116
|
+
function B(e) {
|
|
117
|
+
switch (e.type) {
|
|
118
|
+
case "page":
|
|
119
|
+
case "data":
|
|
120
|
+
case "endpoint":
|
|
121
|
+
return `${e.method ? `${e.method} ` : ""}${e.route ?? "(no route)"}`;
|
|
122
|
+
case "action":
|
|
123
|
+
return S(e);
|
|
124
|
+
case "emit":
|
|
125
|
+
return `${e.channel ?? "?"} (${e.mode ?? "?"}${e.connections !== void 0 ? `, ${e.connections} conn` : ""})`;
|
|
126
|
+
case "delivery":
|
|
127
|
+
return `${e.channel ?? "?"} #${e.deliveries ?? "?"}`;
|
|
128
|
+
case "close":
|
|
129
|
+
return `${e.channel ?? "?"} ${e.deliveries ?? 0} deliveries`;
|
|
130
|
+
case "stream-connect":
|
|
131
|
+
return `${e.route ?? "?"}${e.channel ? ` [${e.channel}]` : ""}`;
|
|
132
|
+
case "socket-connect":
|
|
133
|
+
return e.route ?? "?";
|
|
134
|
+
case "channel-connect":
|
|
135
|
+
return `${e.channel ?? "?"} [${e.module ?? "?"}]`;
|
|
136
|
+
case "boot":
|
|
137
|
+
return `version ${e.version ?? "?"} env ${e.env ?? "?"}`;
|
|
138
|
+
default:
|
|
139
|
+
return e.route ?? e.channel ?? "?";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function I(e, o) {
|
|
143
|
+
return {
|
|
144
|
+
files: e,
|
|
145
|
+
events: o,
|
|
146
|
+
routes: y(o, R, D),
|
|
147
|
+
actions: y(o, (n) => n.type === "action", S),
|
|
148
|
+
channels: y(o, _, (n) => n.channel),
|
|
149
|
+
timeline: o.map((n) => ({
|
|
150
|
+
ts: n.ts ?? "",
|
|
151
|
+
type: n.type,
|
|
152
|
+
label: B(n),
|
|
153
|
+
duration: typeof n.duration == "number" ? n.duration : null,
|
|
154
|
+
status: n.status ?? null,
|
|
155
|
+
bytes: typeof n.bytes == "number" ? n.bytes : null,
|
|
156
|
+
deliveries: typeof n.deliveries == "number" ? n.deliveries : null
|
|
157
|
+
}))
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const V = "—";
|
|
161
|
+
function p(e) {
|
|
162
|
+
return e === null ? V : e >= 1e3 ? `${(e / 1e3).toFixed(2)}s` : `${e.toFixed(1)}ms`;
|
|
163
|
+
}
|
|
164
|
+
function E(e) {
|
|
165
|
+
return e <= 0 ? "0" : e >= 1024 * 1024 ? `${(e / (1024 * 1024)).toFixed(1)}MB` : e >= 1024 ? `${(e / 1024).toFixed(1)}KB` : `${e}B`;
|
|
166
|
+
}
|
|
167
|
+
function $(e, o) {
|
|
168
|
+
const n = [e];
|
|
169
|
+
if (o.length === 0)
|
|
170
|
+
return n.push(" (nothing)"), n;
|
|
171
|
+
const i = {
|
|
172
|
+
key: Math.max(3, ...o.map((s) => s.key.length)),
|
|
173
|
+
count: Math.max(5, ...o.map((s) => String(s.count).length)),
|
|
174
|
+
errors: Math.max(6, ...o.map((s) => String(s.errors).length)),
|
|
175
|
+
p50: Math.max(4, ...o.map((s) => p(s.p50).length)),
|
|
176
|
+
p95: Math.max(4, ...o.map((s) => p(s.p95).length)),
|
|
177
|
+
bytes: Math.max(5, ...o.map((s) => E(s.bytes).length))
|
|
178
|
+
}, t = (s, l) => s.padEnd(l), r = (s, l) => s.padStart(l);
|
|
179
|
+
n.push(
|
|
180
|
+
` ${t("key", i.key)} ${r("count", i.count)} ${r("errors", i.errors)} ${r("p50", i.p50)} ${r("p95", i.p95)} ${r("bytes", i.bytes)}`
|
|
181
|
+
);
|
|
182
|
+
for (const s of o)
|
|
183
|
+
n.push(
|
|
184
|
+
` ${t(s.key, i.key)} ${r(String(s.count), i.count)} ${r(String(s.errors), i.errors)} ${r(p(s.p50), i.p50)} ${r(p(s.p95), i.p95)} ${r(E(s.bytes), i.bytes)}`
|
|
185
|
+
);
|
|
186
|
+
return n;
|
|
187
|
+
}
|
|
188
|
+
function J(e) {
|
|
189
|
+
const o = ["Timeline"];
|
|
190
|
+
if (e.length === 0)
|
|
191
|
+
return o.push(" (nothing)"), o;
|
|
192
|
+
for (const n of e) {
|
|
193
|
+
const i = n.ts.replace("T", " ").replace("Z", ""), t = n.duration === null ? "" : ` ${p(n.duration)}`, r = n.status ? ` ${n.status}` : "", s = n.bytes === null ? "" : ` ${E(n.bytes)}`;
|
|
194
|
+
o.push(` ${i} ${n.type.padEnd(15)} ${n.label}${t}${r}${s}`);
|
|
195
|
+
}
|
|
196
|
+
return o;
|
|
197
|
+
}
|
|
198
|
+
function v(e, o, n, i) {
|
|
199
|
+
const t = ["VeloJS telemetry"];
|
|
200
|
+
t.push(`files: ${e.files.length}`);
|
|
201
|
+
for (const s of e.files) t.push(` ${s}`);
|
|
202
|
+
const r = o === null ? "the whole file" : `last ${i ?? W(o)} — since ${new Date(n - o).toISOString()}`;
|
|
203
|
+
return t.push(`window: ${r}`), t.push(`events: ${e.events.length}`), t.push(""), t.push(...$("By route", e.routes)), t.push(""), t.push(...$("By action", e.actions)), t.push(""), t.push(...$("By channel", e.channels)), t.push(""), t.push(...J(e.timeline)), t.join(`
|
|
204
|
+
`);
|
|
205
|
+
}
|
|
206
|
+
function W(e) {
|
|
207
|
+
return e % 864e5 === 0 ? `${e / 864e5}d` : e % 36e5 === 0 ? `${e / 36e5}h` : e % 6e4 === 0 ? `${e / 6e4}m` : e % 1e3 === 0 ? `${e / 1e3}s` : `${e}ms`;
|
|
208
|
+
}
|
|
209
|
+
const b = `Usage: velojs telemetry [path] [--last <duration>]
|
|
210
|
+
|
|
211
|
+
Ranks the JSONL written by the native telemetry (count, errors, p50/p95,
|
|
212
|
+
bytes) by route, action and channel, and prints the timeline.
|
|
213
|
+
|
|
214
|
+
path JSONL file copied from production (or a directory of them).
|
|
215
|
+
Without it, VELO_TELEMETRY_FILE from the environment or the
|
|
216
|
+
project's .env is used, including the instance/rotation
|
|
217
|
+
files derived from it.
|
|
218
|
+
--last <dur> Window: 500ms, 30s, 60m, 2h, 7d (default: the whole file).
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
velojs telemetry
|
|
222
|
+
velojs telemetry --last 60m
|
|
223
|
+
velojs telemetry ./telemetry.prod.jsonl`;
|
|
224
|
+
async function U(e, o = {}) {
|
|
225
|
+
const n = o.cwd ?? process.cwd(), i = o.env ?? process.env, t = o.write ?? ((f) => console.log(f));
|
|
226
|
+
let r = null, s = null, l;
|
|
227
|
+
for (let f = 0; f < e.length; f += 1) {
|
|
228
|
+
const c = e[f];
|
|
229
|
+
if (c === "--help" || c === "-h") {
|
|
230
|
+
t(b);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (c === "--last") {
|
|
234
|
+
const m = e[f + 1];
|
|
235
|
+
if (m === void 0)
|
|
236
|
+
throw new Error("[velojs] velojs telemetry: --last requires a duration (e.g. --last 60m).");
|
|
237
|
+
s = j(m), l = m, f += 1;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (c.startsWith("--last=")) {
|
|
241
|
+
l = c.slice(7), s = j(l);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (c.startsWith("-"))
|
|
245
|
+
throw new Error(`[velojs] velojs telemetry: unknown flag "${c}".
|
|
246
|
+
|
|
247
|
+
${b}`);
|
|
248
|
+
if (r !== null)
|
|
249
|
+
throw new Error(`[velojs] velojs telemetry: unexpected argument "${c}".
|
|
250
|
+
|
|
251
|
+
${b}`);
|
|
252
|
+
r = c;
|
|
253
|
+
}
|
|
254
|
+
const h = k(n), u = M(r, i, h, n), T = N(O(u), s, Date.now()), F = I(u, T);
|
|
255
|
+
t(v(F, s, Date.now(), l));
|
|
256
|
+
}
|
|
257
|
+
export {
|
|
258
|
+
I as buildTelemetryReport,
|
|
259
|
+
x as collectTelemetryFiles,
|
|
260
|
+
v as formatTelemetryReport,
|
|
261
|
+
j as parseDuration,
|
|
262
|
+
O as readTelemetryEvents,
|
|
263
|
+
U as runTelemetryCli,
|
|
264
|
+
N as windowEvents
|
|
265
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native telemetry — the Node adapter.
|
|
3
|
+
*
|
|
4
|
+
* The client-safe core (`telemetry.ts`) holds the event model, the trace API
|
|
5
|
+
* and the destination resolution; this module owns everything that touches the
|
|
6
|
+
* filesystem, the host and the network: the append-only JSONL writer with
|
|
7
|
+
* size-based rotation and the instance suffix, the optional HTTP sink, the
|
|
8
|
+
* reading of the project's `.env` and of the app version for the boot record.
|
|
9
|
+
*
|
|
10
|
+
* Only server-side entry points import it (`server.tsx`, `telemetry-cli.ts`),
|
|
11
|
+
* which is what keeps the Node builtins out of the client bundle.
|
|
12
|
+
*/
|
|
13
|
+
import { type TelemetryWriter, type TelemetryWriterInput } from "./telemetry.js";
|
|
14
|
+
/**
|
|
15
|
+
* Reads the project's `.env` — the runtime fallback for the destination envs,
|
|
16
|
+
* and the source the CLI resolves a missing `VELO_TELEMETRY_FILE` from.
|
|
17
|
+
* Missing or unreadable file → empty map; the process environment always wins.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readProjectEnv(cwd?: string): Record<string, string>;
|
|
20
|
+
/** `pid.hostname`, sanitized for a filename — one file per process replica. */
|
|
21
|
+
export declare function nodeInstanceLabel(): string;
|
|
22
|
+
/** The version of the app served — the project's `package.json`. */
|
|
23
|
+
export declare function readAppVersion(cwd?: string): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Builds the destination writer: append-only JSONL with size-based rotation,
|
|
26
|
+
* plus an optional HTTP POST per event. Failures are logged and never
|
|
27
|
+
* propagate — a telemetry destination down must not affect the request that
|
|
28
|
+
* produced the event.
|
|
29
|
+
*/
|
|
30
|
+
export declare function nodeTelemetryWriter(input: TelemetryWriterInput): Promise<TelemetryWriter>;
|
|
31
|
+
/**
|
|
32
|
+
* Starts the collection with the Node adapter: process env plus the project's
|
|
33
|
+
* `.env`, the pid/hostname suffix, the app version for the boot record. This
|
|
34
|
+
* is what `createApp` calls; without a destination it is a no-op.
|
|
35
|
+
*/
|
|
36
|
+
export declare function initNodeTelemetry(opts?: {
|
|
37
|
+
instanceLabel?: string;
|
|
38
|
+
}): Promise<void>;
|