@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/dist/sockets.js CHANGED
@@ -1,4 +1,5 @@
1
- async function* W(e) {
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 g = /* @__PURE__ */ new WeakMap(), k = /* @__PURE__ */ new WeakMap(), S = /* @__PURE__ */ new WeakMap();
12
- async function p(e) {
13
- let t = g.get(e);
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 }), g.set(e, t), t;
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 = S.get(e);
23
- n || (n = /* @__PURE__ */ new Set(), S.set(e, n)), n.add(t);
23
+ let n = w.get(e);
24
+ n || (n = /* @__PURE__ */ new Set(), w.set(e, n)), n.add(t);
24
25
  }
25
- function m(e, t) {
26
- S.get(e)?.delete(t);
26
+ function p(e, t) {
27
+ w.get(e)?.delete(t);
27
28
  }
28
- function _(e, t) {
29
- return b(e, t), () => m(e, t);
29
+ function $(e, t) {
30
+ return b(e, t), () => p(e, t);
30
31
  }
31
- function M(e) {
32
- const t = S.get(e);
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 x(e) {
43
- return k.get(e) ?? /* @__PURE__ */ new Map();
43
+ function L(e) {
44
+ return m.get(e) ?? /* @__PURE__ */ new Map();
44
45
  }
45
- function j(e, t, n) {
46
- const i = [];
47
- let r = null, s = !1, c = !1, o = null;
48
- const l = new AbortController();
49
- b(e, l);
50
- const u = {
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 (; i.length > 0; )
54
- yield i.shift();
55
- if (s) return;
62
+ for (; a.length > 0; )
63
+ yield a.shift();
64
+ if (r) return;
56
65
  await new Promise((f) => {
57
- r = f;
58
- }), r = null;
66
+ s = f;
67
+ }), s = null;
59
68
  }
60
69
  }
61
- }, d = (f) => {
62
- if (!(!o || s))
70
+ }, v = (f) => {
71
+ if (!(!c || r))
63
72
  try {
64
- typeof f == "string" || f instanceof Uint8Array ? o.send(f) : o.send(JSON.stringify(f));
65
- } catch (a) {
66
- console.error("[velojs] socket send failed:", a);
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
- }, w = (f, a) => {
69
- if (o)
78
+ }, j = (f, o) => {
79
+ if (c)
70
80
  try {
71
- o.close(f, a);
81
+ c.close(f, o);
72
82
  } catch {
73
83
  }
74
- }, y = () => {
75
- c = !0;
76
- }, h = () => {
77
- if (s || (s = !0, r?.()), !l.signal.aborted)
84
+ }, A = () => {
85
+ i = !0, d("ok");
86
+ }, g = () => {
87
+ if (r || (r = !0, s?.(), l.close()), !y.signal.aborted)
78
88
  try {
79
- l.abort();
89
+ y.abort();
80
90
  } catch {
81
91
  }
82
- m(e, l);
92
+ p(e, y);
83
93
  };
84
94
  return {
85
95
  events: {
86
- onOpen(f, a) {
87
- o = a, Promise.resolve(
96
+ onOpen(f, o) {
97
+ c = o, Promise.resolve(
88
98
  t({
89
- incoming: u,
90
- send: d,
91
- close: w,
92
- keepOpen: y,
93
- abortSignal: l.signal,
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 (!c && o && !s)
109
+ if (d("ok"), !i && c && !r)
100
110
  try {
101
- o.close();
111
+ c.close();
102
112
  } catch {
103
113
  }
104
- }).catch((v) => {
105
- console.error("[velojs] socket handler threw:", v);
114
+ }).catch((W) => {
115
+ d("error"), console.error("[velojs] socket handler threw:", W);
106
116
  try {
107
- o?.close(1011, "handler error");
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 (s) return;
114
- const a = f.data;
115
- typeof a == "string" ? i.push(a) : a instanceof ArrayBuffer ? i.push(new Uint8Array(a)) : a && typeof a == "object" && "buffer" in a && a.buffer instanceof ArrayBuffer && i.push(new Uint8Array(a.buffer)), r?.();
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
- h();
128
+ g();
119
129
  },
120
130
  onError() {
121
- h();
131
+ g();
122
132
  }
123
133
  },
124
- cleanup: h
134
+ cleanup: g
125
135
  };
126
136
  }
127
- async function A(e, t, n, i = []) {
128
- let r = k.get(e);
129
- r || (r = /* @__PURE__ */ new Map(), k.set(e, r)), r.set(t, { path: t, handler: n, middlewares: i });
130
- const s = await p(e);
131
- if (!s) return;
132
- const c = s.upgradeWebSocket((o) => j(e, n, o).events);
133
- i.length > 0 ? e.on(["GET"], [t], ...i, c) : e.on(["GET"], [t], c);
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 O(e, t, n = []) {
136
- const i = async (r, s) => {
137
- for (const c of r) {
138
- const o = c.module?.metadata?.moduleId, l = [...s, ...c.middlewares ?? []];
139
- if (c.module && o)
140
- for (const u of Object.keys(c.module)) {
141
- if (!u.startsWith("socket_")) continue;
142
- const d = c.module[u];
143
- if (typeof d != "function") {
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 ${o}/${u} is not a function; skipped`
155
+ `[velojs] socket_* export ${i}/${l} is not a function; skipped`
146
156
  );
147
157
  continue;
148
158
  }
149
- const w = u.replace("socket_", ""), y = `/_socket/${o}/${w}`;
159
+ const d = l.replace("socket_", ""), y = `/_socket/${i}/${d}`;
150
160
  try {
151
- d.__path = y;
161
+ h.__path = y;
152
162
  } catch {
153
163
  }
154
164
  try {
155
- d.__isVeloSocket = !0;
165
+ h.__isVeloSocket = !0;
156
166
  } catch {
157
167
  }
158
- await A(e, y, d, l);
168
+ await O(e, y, h, c, {
169
+ moduleId: i,
170
+ name: d
171
+ });
159
172
  }
160
- c.children && await i(c.children, l);
173
+ r.children && await u(r.children, c);
161
174
  }
162
175
  };
163
- await i(t, n);
176
+ await u(t, n);
164
177
  }
165
- async function J(e, t) {
166
- const n = g.get(e);
178
+ async function T(e, t) {
179
+ const n = S.get(e);
167
180
  if (!n) return;
168
- const i = new Proxy(t, {
169
- get(r, s, c) {
170
- return s === "on" || s === "addListener" ? (o, l) => o !== "upgrade" ? r[s](o, l) : r[s]("upgrade", (u, d, w) => {
171
- const y = u.headers?.host ?? "localhost";
172
- let h;
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
- h = new URL(u.url ?? "/", `http://${y}`).pathname;
187
+ k = new URL(l.url ?? "/", `http://${y}`).pathname;
175
188
  } catch {
176
189
  return;
177
190
  }
178
- k.get(e)?.has(h) && l(u, d, w);
179
- }) : Reflect.get(r, s, c);
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(i);
184
- } catch (r) {
185
- console.error("[velojs] injectWebSocket failed:", r);
196
+ n.injectWebSocket(u);
197
+ } catch (a) {
198
+ console.error("[velojs] injectWebSocket failed:", a);
186
199
  }
187
200
  }
188
201
  export {
189
- M as abortAllSocketSessions,
190
- x as getAppSockets,
191
- J as injectWebSocketServer,
192
- W as parseJson,
193
- A as registerSocketHandler,
194
- O as registerSocketRoutes,
195
- _ as trackSocketSession
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>;