@atolljs/devtools 0.1.7

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.
@@ -0,0 +1,474 @@
1
+ // ../../src/devtools.ts
2
+ var THREAD = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
3
+ var sink = null;
4
+ var setDevtoolsSink = (custom) => {
5
+ sink = custom;
6
+ };
7
+ var emitDevtools = (event) => {
8
+ if (sink) sink({ ...event, at: performance.now(), thread: THREAD });
9
+ };
10
+ var PROBED = /* @__PURE__ */ Symbol.for("atoll.devtools.fetch");
11
+ var BODY_PREVIEW = 8 * 1024;
12
+ var TEXTISH = /text|json|javascript|xml|html|css|svg|csv|urlencoded|graphql/;
13
+ var headerList = (h) => h ? [...h.entries()].slice(0, 64) : void 0;
14
+ var bodyPreview = (body) => {
15
+ if (body === null || body === void 0) return void 0;
16
+ if (typeof body === "string") return body.slice(0, BODY_PREVIEW);
17
+ if (body instanceof URLSearchParams) return String(body).slice(0, BODY_PREVIEW);
18
+ if (body instanceof Blob) return `[${body.type || "blob"} ${body.size}B]`;
19
+ if (body instanceof ArrayBuffer) return `[ArrayBuffer ${body.byteLength}B]`;
20
+ if (ArrayBuffer.isView(body)) return `[${body.constructor.name} ${body.byteLength}B]`;
21
+ if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
22
+ return "[stream]";
23
+ };
24
+ var readPreview = async (res, cap) => {
25
+ const reader = res.body?.getReader();
26
+ if (!reader) return void 0;
27
+ const chunks = [];
28
+ let n = 0;
29
+ try {
30
+ while (n < cap) {
31
+ const { done, value } = await reader.read();
32
+ if (done || !value) break;
33
+ chunks.push(value);
34
+ n += value.byteLength;
35
+ }
36
+ } catch {
37
+ }
38
+ if (!n) return void 0;
39
+ const all = new Uint8Array(n);
40
+ let off = 0;
41
+ for (const c of chunks) {
42
+ all.set(c, off);
43
+ off += c.byteLength;
44
+ }
45
+ return new TextDecoder().decode(all) + (n >= cap ? "\u2026" : "");
46
+ };
47
+ var findTiming = async (absUrl, t0) => {
48
+ const perf = globalThis.performance;
49
+ if (!perf?.getEntriesByType) return void 0;
50
+ for (let tries = 0; tries < 12; tries++) {
51
+ let best;
52
+ for (const e of perf.getEntriesByType("resource")) {
53
+ if (e.name !== absUrl || e.startTime < t0 - 5) continue;
54
+ if (!best || e.startTime < best.startTime) best = e;
55
+ }
56
+ if (best) {
57
+ const seg = (a, b) => b > a && a > 0 ? b - a : 0;
58
+ return {
59
+ queue: seg(best.fetchStart, best.domainLookupStart),
60
+ dns: seg(best.domainLookupStart, best.domainLookupEnd),
61
+ tcp: best.secureConnectionStart > 0 ? seg(best.connectStart, best.secureConnectionStart) : seg(best.connectStart, best.connectEnd),
62
+ tls: best.secureConnectionStart > 0 ? seg(best.secureConnectionStart, best.connectEnd) : 0,
63
+ wait: seg(best.requestStart, best.responseStart),
64
+ download: seg(best.responseStart, best.responseEnd),
65
+ transferSize: best.transferSize,
66
+ encodedSize: best.encodedBodySize,
67
+ decodedSize: best.decodedBodySize
68
+ };
69
+ }
70
+ await new Promise((r) => setTimeout(r, 250));
71
+ }
72
+ return void 0;
73
+ };
74
+ var installFetchProbe = (scope) => {
75
+ const original = scope.fetch;
76
+ if (!original || original[PROBED]) return;
77
+ let seq = 0;
78
+ const wrapped = async (input, init) => {
79
+ const t0 = performance.now();
80
+ const id = ++seq;
81
+ const isReq = typeof Request !== "undefined" && input instanceof Request;
82
+ const url = isReq ? input.url : String(input);
83
+ const abs = (() => {
84
+ try {
85
+ return new URL(url, self.location?.href).href;
86
+ } catch {
87
+ return url;
88
+ }
89
+ })();
90
+ const method = (init?.method ?? (isReq ? input.method : "GET")).toUpperCase();
91
+ const reqClone = isReq && !init?.body ? input.clone() : null;
92
+ const reqHeaders = headerList(
93
+ init?.headers ? new Headers(init.headers) : isReq ? input.headers : void 0
94
+ );
95
+ let reqBody = bodyPreview(init?.body);
96
+ try {
97
+ const res = await original.call(scope, input, init);
98
+ const len = res.headers.get("content-length");
99
+ emitDevtools({
100
+ type: "net:fetch",
101
+ id,
102
+ url: url.slice(0, 300),
103
+ method,
104
+ status: res.status,
105
+ ms: performance.now() - t0,
106
+ bytes: len === null ? void 0 : Number(len),
107
+ reqHeaders,
108
+ reqBody
109
+ });
110
+ const resClone = res.clone();
111
+ void (async () => {
112
+ reqBody ??= reqClone ? await reqClone.text().then((t2) => t2.slice(0, BODY_PREVIEW)).catch(() => void 0) : void 0;
113
+ const ct = res.headers.get("content-type") ?? "";
114
+ const resBody = TEXTISH.test(ct) ? await readPreview(resClone, BODY_PREVIEW).catch(() => void 0) : void 0;
115
+ const t = await findTiming(abs, t0);
116
+ emitDevtools({
117
+ type: "net:fetch-detail",
118
+ id,
119
+ reqBody,
120
+ resHeaders: headerList(res.headers),
121
+ resBody,
122
+ timing: t,
123
+ transferSize: t?.transferSize,
124
+ encodedSize: t?.encodedSize,
125
+ decodedSize: t?.decodedSize
126
+ });
127
+ })();
128
+ return res;
129
+ } catch (err) {
130
+ emitDevtools({
131
+ type: "net:fetch",
132
+ id,
133
+ url: url.slice(0, 300),
134
+ method,
135
+ ms: performance.now() - t0,
136
+ error: err instanceof Error ? err.message : String(err),
137
+ reqHeaders,
138
+ reqBody
139
+ });
140
+ throw err;
141
+ }
142
+ };
143
+ wrapped[PROBED] = true;
144
+ scope.fetch = wrapped;
145
+ };
146
+ var memProbed = /* @__PURE__ */ new WeakSet();
147
+ var installMemoryProbe = (scope, intervalMs = 2500) => {
148
+ if (memProbed.has(scope)) return;
149
+ const perf = scope.performance;
150
+ if (!perf) return;
151
+ const mem = perf.memory;
152
+ const inWorker = typeof WorkerGlobalScope !== "undefined" && globalThis.self instanceof WorkerGlobalScope;
153
+ const measure = perf.measureUserAgentSpecificMemory?.bind(perf);
154
+ const procMem = globalThis.process?.memoryUsage;
155
+ if (!mem && (!measure || inWorker) && !procMem) return;
156
+ memProbed.add(scope);
157
+ if (measure && !inWorker) {
158
+ const timer2 = setInterval(() => {
159
+ measure().then((res) => {
160
+ const contexts = (res.breakdown ?? []).map((b) => ({
161
+ bytes: b.bytes,
162
+ scope: b.attribution?.[0]?.scope,
163
+ url: b.attribution?.[0]?.url
164
+ })).sort((a, b) => b.bytes - a.bytes).slice(0, 24);
165
+ emitDevtools({
166
+ type: "runtime:memory",
167
+ heapBytes: res.bytes,
168
+ heapLimitBytes: mem?.jsHeapSizeLimit,
169
+ contexts
170
+ });
171
+ }).catch(() => {
172
+ if (mem) {
173
+ emitDevtools({
174
+ type: "runtime:memory",
175
+ heapBytes: mem.usedJSHeapSize,
176
+ heapLimitBytes: mem.jsHeapSizeLimit
177
+ });
178
+ }
179
+ });
180
+ }, Math.max(intervalMs, 4e3));
181
+ timer2.unref?.();
182
+ return;
183
+ }
184
+ const timer = setInterval(() => {
185
+ if (mem) {
186
+ emitDevtools({
187
+ type: "runtime:memory",
188
+ heapBytes: mem.usedJSHeapSize,
189
+ heapLimitBytes: mem.jsHeapSizeLimit
190
+ });
191
+ } else if (procMem) {
192
+ const u = procMem();
193
+ emitDevtools({ type: "runtime:memory", heapBytes: u.heapUsed, rssBytes: u.rss });
194
+ }
195
+ }, intervalMs);
196
+ timer.unref?.();
197
+ };
198
+
199
+ // src/protocol.ts
200
+ var DEVTOOLS_CHANNEL = "atoll-devtools";
201
+
202
+ // src/broadcast.ts
203
+ function connectBroadcast(opts, session) {
204
+ const bc = new BroadcastChannel(DEVTOOLS_CHANNEL);
205
+ const cap = opts.bufferCap ?? 5e4;
206
+ const tailCap = opts.replayBatches ?? 500;
207
+ const pending = [];
208
+ const tail = [];
209
+ let closed = false;
210
+ const post = (msg) => {
211
+ try {
212
+ bc.postMessage(msg);
213
+ } catch {
214
+ }
215
+ };
216
+ const hello = () => post({ type: "hello", session });
217
+ const flush = () => {
218
+ if (closed || pending.length === 0) return;
219
+ const events = pending.splice(0, pending.length);
220
+ tail.push(events);
221
+ if (tail.length > tailCap) tail.shift();
222
+ post({ type: "batch", session, events });
223
+ };
224
+ bc.onmessage = (m) => {
225
+ if (m.data?.type === "view") {
226
+ hello();
227
+ for (const events of tail) post({ type: "batch", session, events });
228
+ }
229
+ };
230
+ const timer = setInterval(flush, opts.flushMs ?? 100);
231
+ timer.unref?.();
232
+ const bye = () => {
233
+ flush();
234
+ post({ type: "bye", sessionId: session.id });
235
+ };
236
+ const onHide = () => bye();
237
+ if (typeof addEventListener === "function") addEventListener("pagehide", onHide);
238
+ if (opts.network !== false) installFetchProbe(globalThis);
239
+ if (opts.memory !== false) installMemoryProbe(globalThis);
240
+ setDevtoolsSink((event) => {
241
+ if (closed) return;
242
+ pending.push(event);
243
+ if (pending.length > cap) pending.shift();
244
+ if (pending.length >= 1e3) flush();
245
+ });
246
+ hello();
247
+ return {
248
+ session,
249
+ get open() {
250
+ return !closed;
251
+ },
252
+ close() {
253
+ if (closed) return;
254
+ closed = true;
255
+ bye();
256
+ setDevtoolsSink(null);
257
+ clearInterval(timer);
258
+ if (typeof removeEventListener === "function") removeEventListener("pagehide", onHide);
259
+ bc.close();
260
+ }
261
+ };
262
+ }
263
+
264
+ // src/client.ts
265
+ var sessionSeq = 0;
266
+ var makeSession = (opts) => {
267
+ const runtime = typeof window !== "undefined" && typeof window.document !== "undefined" ? "browser" : "node";
268
+ const hint = opts.session?.hint ?? (runtime === "browser" ? globalThis.location?.href : globalThis.process?.title);
269
+ return {
270
+ id: `s-${Date.now().toString(36)}-${++sessionSeq}`,
271
+ name: opts.session?.name,
272
+ runtime,
273
+ hint
274
+ };
275
+ };
276
+ var autoBroadcast = () => typeof BroadcastChannel !== "undefined" && typeof window !== "undefined" && typeof window.document !== "undefined";
277
+ function connectDevtools(opts = {}) {
278
+ const session = makeSession(opts);
279
+ const t = opts.url !== void 0 ? "websocket" : opts.transport ?? "auto";
280
+ const useBc = t === "broadcast" ? typeof BroadcastChannel !== "undefined" : t === "auto" && autoBroadcast();
281
+ return useBc ? connectBroadcast(opts, session) : connectWebSocket(opts, session);
282
+ }
283
+ function connectWebSocket(opts, session) {
284
+ const url = opts.url ?? "ws://127.0.0.1:4780/events";
285
+ const cap = opts.bufferCap ?? 5e4;
286
+ const pending = [];
287
+ let closed = false;
288
+ let sock = null;
289
+ const send = (msg) => {
290
+ if (sock?.open) sock.send(JSON.stringify(msg));
291
+ };
292
+ const flush = () => {
293
+ if (!sock?.open || pending.length === 0) return;
294
+ const events = pending.splice(0, pending.length);
295
+ send({ type: "batch", events });
296
+ };
297
+ const onOpen = () => {
298
+ send({ type: "hello", session });
299
+ flush();
300
+ };
301
+ const isNode = typeof window === "undefined" || typeof window.document === "undefined";
302
+ if (typeof WebSocket !== "undefined") {
303
+ const ws = new WebSocket(url);
304
+ sock = {
305
+ get open() {
306
+ return ws.readyState === ws.OPEN;
307
+ },
308
+ send: (t) => ws.send(t),
309
+ close: () => ws.close()
310
+ };
311
+ ws.onopen = onOpen;
312
+ ws.onclose = () => clearInterval(timer);
313
+ ws.onerror = () => ws.close();
314
+ } else if (isNode) {
315
+ const mod = "./nodeWs.ts";
316
+ void import(
317
+ /* @vite-ignore */
318
+ mod
319
+ ).then((m) => {
320
+ if (closed) return;
321
+ const ws = m.connectNodeWebSocket(url);
322
+ sock = ws;
323
+ ws.onOpen = onOpen;
324
+ ws.onClose = () => clearInterval(timer);
325
+ }).catch(() => {
326
+ });
327
+ }
328
+ const timer = setInterval(flush, opts.flushMs ?? 100);
329
+ timer.unref?.();
330
+ if (opts.network !== false) installFetchProbe(globalThis);
331
+ if (opts.memory !== false) installMemoryProbe(globalThis);
332
+ setDevtoolsSink((event) => {
333
+ if (closed) return;
334
+ pending.push(event);
335
+ if (pending.length > cap) pending.shift();
336
+ if (pending.length >= 1e3) flush();
337
+ });
338
+ return {
339
+ session,
340
+ get open() {
341
+ return sock?.open ?? false;
342
+ },
343
+ close() {
344
+ closed = true;
345
+ setDevtoolsSink(null);
346
+ flush();
347
+ sock?.close();
348
+ clearInterval(timer);
349
+ }
350
+ };
351
+ }
352
+
353
+ // src/overlay.ts
354
+ var BTN_STYLE = "position:fixed;z-index:2147483646;font:11px ui-monospace,monospace;color:#c9d1d9;background:#161b22;border:1px solid #30363d;border-radius:6px;padding:5px 9px;cursor:pointer";
355
+ var PANEL_STYLE = "position:fixed;z-index:2147483646;display:flex;flex-direction:column;background:#0d1117;border:1px solid #30363d;border-radius:8px;box-shadow:0 12px 40px #000c;overflow:hidden;min-width:340px;min-height:220px";
356
+ var ANCHOR = {
357
+ topleft: (m) => `left:${m}px;top:${m}px`,
358
+ topcenter: (m) => `left:50%;top:${m}px;transform:translateX(-50%)`,
359
+ topright: (m) => `right:${m}px;top:${m}px`,
360
+ bottomleft: (m) => `left:${m}px;bottom:${m}px`,
361
+ bottomcenter: (m) => `left:50%;bottom:${m}px;transform:translateX(-50%)`,
362
+ bottomright: (m) => `right:${m}px;bottom:${m}px`
363
+ };
364
+ function mountDevtoolsOverlay(opts = {}) {
365
+ const src = opts.src ?? "/__atoll/?mini=1";
366
+ const W = opts.width ?? 760;
367
+ const H = opts.height ?? 580;
368
+ const anchor = ANCHOR[opts.position ?? "bottomright"];
369
+ let open = false;
370
+ const btn = document.createElement("button");
371
+ btn.textContent = "atoll devtools";
372
+ btn.style.cssText = `${BTN_STYLE};${anchor(12)}`;
373
+ const panel = document.createElement("div");
374
+ panel.style.cssText = `${PANEL_STYLE};width:${W}px;height:${H}px;display:none;${anchor(16)}`;
375
+ const head = document.createElement("div");
376
+ head.style.cssText = "display:flex;align-items:center;gap:8px;padding:5px 10px;flex:none;border-bottom:1px solid #21262d;font:11px ui-monospace,monospace;color:#8b949e;background:#161b22;cursor:move;user-select:none";
377
+ head.textContent = "atoll devtools";
378
+ const pop = document.createElement("a");
379
+ pop.href = "/__atoll/";
380
+ pop.target = "_blank";
381
+ pop.rel = "noopener";
382
+ pop.textContent = "full page \u2197";
383
+ pop.style.cssText = "color:#58a6ff;text-decoration:none;margin-left:auto";
384
+ const x = document.createElement("button");
385
+ x.textContent = "\u2014";
386
+ x.title = "collapse";
387
+ x.style.cssText = "color:#8b949e;background:none;border:none;cursor:pointer;font:inherit;padding:0 2px";
388
+ head.append(pop, x);
389
+ const frame = document.createElement("iframe");
390
+ frame.src = src;
391
+ frame.style.cssText = "flex:1;border:0;background:#0d1117;min-height:0";
392
+ const grip = document.createElement("div");
393
+ grip.style.cssText = "position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;background:linear-gradient(135deg,transparent 50%,#30363d 50%);border-radius:0 0 7px";
394
+ panel.append(head, frame, grip);
395
+ document.body.append(panel, btn);
396
+ const drag = (e, move) => {
397
+ e.preventDefault();
398
+ let lx = e.clientX, ly = e.clientY;
399
+ const mm = (ev) => {
400
+ move(ev.clientX - lx, ev.clientY - ly);
401
+ lx = ev.clientX;
402
+ ly = ev.clientY;
403
+ };
404
+ const up = () => {
405
+ removeEventListener("pointermove", mm);
406
+ removeEventListener("pointerup", up);
407
+ };
408
+ addEventListener("pointermove", mm);
409
+ addEventListener("pointerup", up);
410
+ };
411
+ head.onpointerdown = (e) => {
412
+ if (e.target.closest("a,button")) return;
413
+ drag(e, (dx, dy) => {
414
+ const r = panel.getBoundingClientRect();
415
+ const left = Math.min(Math.max(0, r.left + dx), innerWidth - 60);
416
+ const top = Math.min(Math.max(0, r.top + dy), innerHeight - 40);
417
+ panel.style.left = `${left}px`;
418
+ panel.style.top = `${top}px`;
419
+ panel.style.right = "auto";
420
+ panel.style.bottom = "auto";
421
+ panel.style.transform = "none";
422
+ });
423
+ };
424
+ grip.onpointerdown = (e) => {
425
+ e.stopPropagation();
426
+ drag(e, (dx, dy) => {
427
+ const r = panel.getBoundingClientRect();
428
+ panel.style.width = `${Math.max(340, r.width + dx)}px`;
429
+ panel.style.height = `${Math.max(220, r.height + dy)}px`;
430
+ });
431
+ };
432
+ const set = (v) => {
433
+ open = v;
434
+ panel.style.display = v ? "flex" : "none";
435
+ btn.style.display = v ? "none" : "block";
436
+ };
437
+ btn.onclick = () => set(true);
438
+ x.onclick = () => set(false);
439
+ set(opts.startOpen === true);
440
+ return {
441
+ open: () => set(true),
442
+ close: () => set(false),
443
+ toggle: () => set(!open),
444
+ unmount: () => {
445
+ panel.remove();
446
+ btn.remove();
447
+ }
448
+ };
449
+ }
450
+
451
+ // src/init.ts
452
+ var DEVTOOLS_PARAM = "__atoll_devtools";
453
+ var devtoolsEnabled2 = (enabled) => {
454
+ if (enabled !== void 0) return enabled;
455
+ if (typeof location !== "undefined" && typeof location.search === "string") {
456
+ return new URLSearchParams(location.search).has(DEVTOOLS_PARAM);
457
+ }
458
+ return !!globalThis.process?.env?.ATOLL_DEVTOOLS;
459
+ };
460
+ function initDevtools(opts = {}) {
461
+ if (!devtoolsEnabled2(opts.enabled)) return null;
462
+ const conn = connectDevtools(opts);
463
+ const inWindow = typeof window !== "undefined" && typeof window.document !== "undefined";
464
+ const overlay = inWindow && opts.overlay !== false ? mountDevtoolsOverlay(typeof opts.overlay === "object" ? opts.overlay : {}) : null;
465
+ return { conn, overlay };
466
+ }
467
+
468
+ export {
469
+ connectDevtools,
470
+ mountDevtoolsOverlay,
471
+ DEVTOOLS_PARAM,
472
+ devtoolsEnabled2 as devtoolsEnabled,
473
+ initDevtools
474
+ };
@@ -0,0 +1,34 @@
1
+ import type { SessionInfo } from './protocol.js';
2
+ export interface ConnectDevtoolsOptions {
3
+ /**
4
+ * Transport. Default `'auto'`: BroadcastChannel in a browser window
5
+ * (pure client — dashboards open on the app's own origin via /__atoll/),
6
+ * WebSocket everywhere else (Node, workers, non-BC browsers).
7
+ * `'websocket'` or setting `url` opts into the aggregate server.
8
+ */
9
+ transport?: 'auto' | 'broadcast' | 'websocket';
10
+ /** Server ingest endpoint — implies websocket transport. */
11
+ url?: string;
12
+ /** Session identity shown in the dashboard. */
13
+ session?: {
14
+ name?: string;
15
+ hint?: string;
16
+ };
17
+ /** Batch flush interval ms (default 100). */
18
+ flushMs?: number;
19
+ /** Events buffered while disconnected (default 50_000 — oldest dropped). */
20
+ bufferCap?: number;
21
+ /** Batches kept for replay to late-joining dashboards (broadcast, default 500). */
22
+ replayBatches?: number;
23
+ /** Patch global fetch to log requests (default true). Worker fetch is probed via the pool's devtools INIT flag. */
24
+ network?: boolean;
25
+ /** Sample JS heap usage periodically — Chrome/Blink only (default true). */
26
+ memory?: boolean;
27
+ }
28
+ export interface DevtoolsConnection {
29
+ readonly open: boolean;
30
+ readonly session: SessionInfo;
31
+ /** Uninstall the sink and close the socket. */
32
+ close(): void;
33
+ }
34
+ export declare function connectDevtools(opts?: ConnectDevtoolsOptions): DevtoolsConnection;
@@ -0,0 +1,7 @@
1
+ export { connectDevtools } from './client.js';
2
+ export type { ConnectDevtoolsOptions, DevtoolsConnection } from './client.js';
3
+ export { mountDevtoolsOverlay } from './overlay.js';
4
+ export type { DevtoolsOverlay, DevtoolsOverlayOptions } from './overlay.js';
5
+ export { DEVTOOLS_PARAM, devtoolsEnabled, initDevtools } from './init.js';
6
+ export type { InitDevtools, InitDevtoolsOptions } from './init.js';
7
+ export type { SessionInfo } from './protocol.js';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import {
2
+ DEVTOOLS_PARAM,
3
+ connectDevtools,
4
+ devtoolsEnabled,
5
+ initDevtools,
6
+ mountDevtoolsOverlay
7
+ } from "./chunk-4N3NBFL3.js";
8
+ export {
9
+ DEVTOOLS_PARAM,
10
+ connectDevtools,
11
+ devtoolsEnabled,
12
+ initDevtools,
13
+ mountDevtoolsOverlay
14
+ };
package/dist/init.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * URL-gated devtools: `initDevtools()` is a no-op unless the app was
3
+ * opened with `?__atoll_devtools` in the URL (Node: `ATOLL_DEVTOOLS`
4
+ * env). When enabled it connects the broadcast transport and mounts the
5
+ * overlay flyout — one line in the app, nothing when uninvited:
6
+ *
7
+ * import { initDevtools } from '@atolljs/devtools';
8
+ * initDevtools({ session: { name: 'my-app' } });
9
+ *
10
+ * my-app/?__atoll_devtools → devtools on
11
+ * my-app/ → zero cost, no sink installed
12
+ */
13
+ import { type ConnectDevtoolsOptions, type DevtoolsConnection } from './client.js';
14
+ import { type DevtoolsOverlay, type DevtoolsOverlayOptions } from './overlay.js';
15
+ export declare const DEVTOOLS_PARAM = "__atoll_devtools";
16
+ /** Whether devtools should activate in this context. */
17
+ export declare const devtoolsEnabled: (enabled?: boolean) => boolean;
18
+ export interface InitDevtoolsOptions extends ConnectDevtoolsOptions {
19
+ /** Force on/off — defaults to the ?__atoll_devtools URL param. */
20
+ enabled?: boolean;
21
+ /** Overlay flyout — default true in a browser window; pass options or false. */
22
+ overlay?: boolean | DevtoolsOverlayOptions;
23
+ }
24
+ export interface InitDevtools {
25
+ conn: DevtoolsConnection;
26
+ overlay: DevtoolsOverlay | null;
27
+ }
28
+ export declare function initDevtools(opts?: InitDevtoolsOptions): InitDevtools | null;
package/dist/node.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@atolljs/devtools/node` — the Node-facing entry.
3
+ *
4
+ * Same API as the browser entry, minus the browser-only pieces: there is no
5
+ * BroadcastChannel transport (WebSocket is the only path — the global
6
+ * WebSocket on Node ≥ 22, the bundled dependency-free client below it) and
7
+ * no overlay flyout (there's no page to mount it in). `initDevtools` gates
8
+ * on the ATOLL_DEVTOOLS env var instead of the ?__atoll_devtools URL param;
9
+ * the dashboard is the standalone aggregate server (`atoll devtools`).
10
+ *
11
+ * import { initDevtools } from '@atolljs/devtools/node';
12
+ * initDevtools({ session: { name: 'my-api' } });
13
+ */
14
+ export { connectDevtools } from './client.js';
15
+ export type { ConnectDevtoolsOptions, DevtoolsConnection } from './client.js';
16
+ export { initDevtools, devtoolsEnabled, DEVTOOLS_PARAM } from './init.js';
17
+ export type { InitDevtools, InitDevtoolsOptions } from './init.js';
18
+ export type { SessionInfo } from './protocol.js';
package/dist/node.js ADDED
@@ -0,0 +1,12 @@
1
+ import {
2
+ DEVTOOLS_PARAM,
3
+ connectDevtools,
4
+ devtoolsEnabled,
5
+ initDevtools
6
+ } from "./chunk-4N3NBFL3.js";
7
+ export {
8
+ DEVTOOLS_PARAM,
9
+ connectDevtools,
10
+ devtoolsEnabled,
11
+ initDevtools
12
+ };
@@ -0,0 +1,9 @@
1
+ export interface NodeWebSocket {
2
+ readonly open: boolean;
3
+ send(text: string): void;
4
+ close(): void;
5
+ onOpen: (() => void) | null;
6
+ onClose: (() => void) | null;
7
+ }
8
+ /** Connect a WebSocket to `url` — handshake, then framed text IO. */
9
+ export declare function connectNodeWebSocket(url: string): NodeWebSocket;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * In-app devtools overlay: a floating flyout hosting the mini dashboard
3
+ * in a same-origin iframe (default /__atoll/?mini=1, served by the vite
4
+ * plugin). Same-origin means the iframe shares the app's BroadcastChannel
5
+ * — the pure-client transport needs no other wiring.
6
+ *
7
+ * import { connectDevtools, mountDevtoolsOverlay } from '@atolljs/devtools';
8
+ * if (import.meta.env.DEV) {
9
+ * connectDevtools();
10
+ * mountDevtoolsOverlay();
11
+ * }
12
+ */
13
+ export type DevtoolsOverlayPosition = 'topleft' | 'topcenter' | 'topright' | 'bottomleft' | 'bottomcenter' | 'bottomright';
14
+ export interface DevtoolsOverlayOptions {
15
+ /** Dashboard URL — default '/__atoll/?mini=1' (vite-plugin mount, mini layout). */
16
+ src?: string;
17
+ /** Start expanded (default false — a small corner toggle shows first). */
18
+ startOpen?: boolean;
19
+ /** Initial size (default 760×580). */
20
+ width?: number;
21
+ height?: number;
22
+ /** Where the flyout (and its collapsed toggle) anchors — default 'bottomright'. */
23
+ position?: DevtoolsOverlayPosition;
24
+ }
25
+ export interface DevtoolsOverlay {
26
+ open(): void;
27
+ close(): void;
28
+ toggle(): void;
29
+ unmount(): void;
30
+ }
31
+ export declare function mountDevtoolsOverlay(opts?: DevtoolsOverlayOptions): DevtoolsOverlay;