@hakam-aldeen-kh/blix 0.4.2 → 0.6.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 +298 -37
- package/dist/{DevToolsPanel-TXMT43YN.js → DevToolsPanel-QTVFBNU5.js} +3651 -2178
- package/dist/capture/index.d.ts +168 -18
- package/dist/capture/index.js +4 -2
- package/dist/chunk-4JZEVUOR.js +735 -0
- package/dist/{chunk-MRTSIXR7.js → chunk-FIYP47QZ.js} +202 -118
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -4
- package/package.json +2 -1
- package/dist/chunk-OQHKPRNI.js +0 -287
package/dist/capture/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { AxiosInstance } from 'axios';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Blix — HTTP capture entry point.
|
|
5
3
|
*
|
|
@@ -8,19 +6,122 @@ import { AxiosInstance } from 'axios';
|
|
|
8
6
|
* encryption or auth interceptors, so this interceptor runs last in axios's
|
|
9
7
|
* LIFO request stack and therefore sees the plaintext request body.
|
|
10
8
|
*
|
|
9
|
+
* Typed structurally rather than against axios: axios is an optional peer, and
|
|
10
|
+
* an app that only uses `fetch` must be able to type-check Blix without it.
|
|
11
|
+
*
|
|
11
12
|
* No-op when `process.env.NODE_ENV !== "development"`.
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The part of an axios interceptor manager Blix uses.
|
|
16
|
+
*
|
|
17
|
+
* The callbacks are typed `any`, and have to be: axios types them against its
|
|
18
|
+
* own config and response types, and a callback's parameter is checked
|
|
19
|
+
* contravariantly — `unknown` there would reject a real axios instance. `use`
|
|
20
|
+
* and `eject` themselves use method syntax, whose parameters are checked
|
|
21
|
+
* bivariantly, which is what lets `AxiosInterceptorManager<V>` satisfy this
|
|
22
|
+
* for any `V`.
|
|
23
|
+
*/
|
|
24
|
+
interface InterceptorManagerLike {
|
|
25
|
+
use(onFulfilled?: ((value: any) => any) | null, onRejected?: ((error: any) => any) | null): number;
|
|
26
|
+
eject(id: number): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What `attachHttpMonitor` needs from an axios instance: `interceptors`, and
|
|
30
|
+
* nothing else. Structural, like `HttpClientLike` — Blix does not import
|
|
31
|
+
* axios's types, so a fetch-only app never needs axios installed to type-check
|
|
32
|
+
* against Blix, and a real `AxiosInstance` still passes with no cast.
|
|
33
|
+
*/
|
|
34
|
+
interface HttpMonitorTarget {
|
|
35
|
+
interceptors: {
|
|
36
|
+
request: InterceptorManagerLike;
|
|
37
|
+
response: InterceptorManagerLike;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
14
40
|
/**
|
|
15
41
|
* Attaches HTTP-capture interceptors to an axios instance.
|
|
16
42
|
*
|
|
17
43
|
* Call this at module init time (e.g. at the bottom of your `axios.ts`),
|
|
18
44
|
* after registering any encryption interceptors so that the monitor sees the
|
|
19
45
|
* plaintext body first.
|
|
46
|
+
*
|
|
47
|
+
* Idempotent per instance, and returns a disposer that ejects both
|
|
48
|
+
* interceptors. A second install used to register a second pair: every request
|
|
49
|
+
* then started two entries, both response interceptors settled the second, and
|
|
50
|
+
* the first stayed pending for the rest of the session.
|
|
20
51
|
*/
|
|
21
|
-
declare function attachHttpMonitor(instance:
|
|
52
|
+
declare function attachHttpMonitor(instance: HttpMonitorTarget, options?: {
|
|
22
53
|
dbName?: string;
|
|
23
|
-
}): void;
|
|
54
|
+
}): () => void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Blix — HTTP capture for native `fetch`.
|
|
58
|
+
*
|
|
59
|
+
* `attachHttpMonitor` only sees requests made through an axios instance, so an
|
|
60
|
+
* app that calls `fetch` directly — the default in a Next.js App Router project
|
|
61
|
+
* — got an empty Network tab and a reasonable conclusion that Blix was broken.
|
|
62
|
+
* This wraps `globalThis.fetch` and feeds the same store with the same
|
|
63
|
+
* `MonitorEntry` shape, marked `client: "fetch"`.
|
|
64
|
+
*
|
|
65
|
+
* ## The rules this file is built around
|
|
66
|
+
*
|
|
67
|
+
* - **The caller's bodies are never consumed.** `Request` and `Response` bodies
|
|
68
|
+
* are one-shot streams. Everything Blix reads it reads from a `clone()`, taken
|
|
69
|
+
* before the body can be touched: the request's before `fetch` is called, the
|
|
70
|
+
* response's before the caller is handed it.
|
|
71
|
+
* - **Nothing is buffered without bound.** Bodies are read through a byte
|
|
72
|
+
* counter on every read and abandoned past `maxBodyBytes`, whatever
|
|
73
|
+
* `Content-Length` claimed. That header is the *encoded* length; the clone
|
|
74
|
+
* yields decoded bytes, so a small gzip can inflate far past it.
|
|
75
|
+
* - **Nothing the caller passed is mutated, and no error is swallowed or
|
|
76
|
+
* replaced.** A failure anywhere in capture degrades to "not captured", never
|
|
77
|
+
* to a broken request.
|
|
78
|
+
* - **Browser only.** Next.js patches `fetch` on the server for its data cache;
|
|
79
|
+
* Blix never touches that one.
|
|
80
|
+
*
|
|
81
|
+
* `captureEncrypted` does not apply here. It correlates by an object the host
|
|
82
|
+
* holds inside its own interceptor, and `fetch` has no interceptor stage.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* A rule that keeps a request out of the log. A string matches as a substring,
|
|
86
|
+
* and a regular expression is tested, against the URL's path and query
|
|
87
|
+
* (`/api/items?page=2`); a function receives the resolved `URL`.
|
|
88
|
+
*/
|
|
89
|
+
type FetchIgnoreRule = string | RegExp | ((url: URL) => boolean);
|
|
90
|
+
interface FetchMonitorOptions {
|
|
91
|
+
/** IndexedDB database name — see `attachHttpMonitor`. */
|
|
92
|
+
dbName?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Largest body Blix will read, request or response, in bytes. Default
|
|
95
|
+
* 5 MB. A larger body is still logged — status, headers, timing — with the
|
|
96
|
+
* body marked as not captured, and why. `0` captures no bodies at all.
|
|
97
|
+
*/
|
|
98
|
+
maxBodyBytes?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Requests to leave out of the log entirely. An array **replaces** the
|
|
101
|
+
* defaults; a function receives the defaults and returns the list to use,
|
|
102
|
+
* which is how to extend them. `[]` captures everything.
|
|
103
|
+
*
|
|
104
|
+
* The defaults drop framework dev traffic that would otherwise bury the app's
|
|
105
|
+
* own requests: Next.js assets, HMR and data requests (`/_next/`), the
|
|
106
|
+
* Next.js dev overlay (`/__nextjs`), App Router navigation and prefetch
|
|
107
|
+
* payloads (`?_rsc=`), and webpack HMR (`.hot-update.`, `/__webpack_hmr`).
|
|
108
|
+
*/
|
|
109
|
+
ignore?: readonly FetchIgnoreRule[] | ((defaults: readonly FetchIgnoreRule[]) => readonly FetchIgnoreRule[]);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Wraps `globalThis.fetch` so every request the page makes is captured, and
|
|
113
|
+
* returns a disposer that unwraps it.
|
|
114
|
+
*
|
|
115
|
+
* Call once, as early as possible — before the first `fetch` you want to see.
|
|
116
|
+
* A no-op in production, on the server, and when Blix's wrapper is already
|
|
117
|
+
* installed (the returned disposer is then a no-op too).
|
|
118
|
+
*
|
|
119
|
+
* The disposer restores the exact original `fetch`, but only while Blix's
|
|
120
|
+
* wrapper is still what `globalThis.fetch` points at. If something else has
|
|
121
|
+
* patched over it since, restoring would silently remove that patch as well,
|
|
122
|
+
* so it is left alone and a development warning explains why.
|
|
123
|
+
*/
|
|
124
|
+
declare function attachFetchMonitor(options?: FetchMonitorOptions): () => void;
|
|
24
125
|
|
|
25
126
|
/**
|
|
26
127
|
* Blix — encrypted-payload capture (opt-in).
|
|
@@ -304,7 +405,7 @@ interface ReduxActionMeta {
|
|
|
304
405
|
}
|
|
305
406
|
/** Query/mutation only: the cache row's current lifecycle state. Deliberately
|
|
306
407
|
* not folded into `MonitorState` — `MonitorState` drives the shared state
|
|
307
|
-
* filter and
|
|
408
|
+
* filter and row colour, and pending/success/error covers this fine;
|
|
308
409
|
* the richer status (fresh/stale/fetching/removed) lives here instead. */
|
|
309
410
|
interface QueryMeta {
|
|
310
411
|
sub: "query" | "mutation";
|
|
@@ -353,14 +454,13 @@ interface MonitorEntry {
|
|
|
353
454
|
/**
|
|
354
455
|
* Start/end as absolute epoch ms (`timeOrigin + startTime`).
|
|
355
456
|
*
|
|
356
|
-
*
|
|
357
|
-
* relative to a per-page-load origin, so a
|
|
358
|
-
* meaningless against the current load's.
|
|
457
|
+
* Anything comparing two entries in time must use these, never
|
|
458
|
+
* `startTime`/`endTime`: those are relative to a per-page-load origin, so a
|
|
459
|
+
* persisted entry's `startTime` is meaningless against the current load's.
|
|
359
460
|
*/
|
|
360
461
|
startAbs: number;
|
|
361
462
|
endAbs?: number;
|
|
362
|
-
/** Which page load captured this. Drives the divider rows
|
|
363
|
-
* waterfall timeline. */
|
|
463
|
+
/** Which page load captured this. Drives the divider rows in the list. */
|
|
364
464
|
loadId: string;
|
|
365
465
|
status?: number;
|
|
366
466
|
state: MonitorState;
|
|
@@ -409,6 +509,15 @@ interface MonitorEntry {
|
|
|
409
509
|
redux?: ReduxActionMeta;
|
|
410
510
|
/** Query only: the cache row's key, status and observer count. */
|
|
411
511
|
query?: QueryMeta;
|
|
512
|
+
/**
|
|
513
|
+
* HTTP only: which capture path recorded this request — `attachHttpMonitor`
|
|
514
|
+
* (`"axios"`) or `attachFetchMonitor` (`"fetch"`). Deliberately not
|
|
515
|
+
* `transport`, which is realtime-only and is rendered in place of the method.
|
|
516
|
+
*
|
|
517
|
+
* Absent on entries captured before this field existed. Absent means
|
|
518
|
+
* unknown — never read it as "axios".
|
|
519
|
+
*/
|
|
520
|
+
client?: "axios" | "fetch";
|
|
412
521
|
/** HTTP only: id of the query/mutation entry that caused this request,
|
|
413
522
|
* when it could be determined — see `monitorContext.ts`. Absence means
|
|
414
523
|
* "unknown", never "not caused by a query". */
|
|
@@ -437,10 +546,15 @@ interface Pos {
|
|
|
437
546
|
* Everything the panel remembers between sessions.
|
|
438
547
|
*
|
|
439
548
|
* Stored in IndexedDB (`meta.prefs`) as the source of truth, mirrored into
|
|
440
|
-
* `localStorage["
|
|
441
|
-
*
|
|
549
|
+
* `localStorage["<dbName>:prefs"]` — `blix:checkout:prefs` for
|
|
550
|
+
* `dbName="checkout"` — purely as a synchronous read cache so the panel can
|
|
551
|
+
* restore its geometry without a frame of flash. The mirror is expendable —
|
|
442
552
|
* any host that clears web storage wipes it, and the IndexedDB copy silently
|
|
443
553
|
* restores it on next boot.
|
|
554
|
+
*
|
|
555
|
+
* Both halves are scoped to the database name: web storage, like IndexedDB, is
|
|
556
|
+
* origin-scoped, so an unscoped key meant every app on one origin shared one
|
|
557
|
+
* set of preferences.
|
|
444
558
|
*/
|
|
445
559
|
interface MonitorPrefs {
|
|
446
560
|
/** Bumped on every write; used to reconcile the two copies. */
|
|
@@ -481,10 +595,12 @@ interface MonitorPrefs {
|
|
|
481
595
|
dataFormat: string;
|
|
482
596
|
/** Active top-level section — `"network"` (HTTP) or `"realtime"` (sockets). */
|
|
483
597
|
section: string;
|
|
598
|
+
/** Sources rail shown as icons only. A deliberate choice, kept separate from
|
|
599
|
+
* the automatic collapse a narrow panel applies — re-widening the panel
|
|
600
|
+
* restores the rail unless the developer collapsed it themselves. */
|
|
601
|
+
railCollapsed: boolean;
|
|
484
602
|
/** Row height preset: compact | normal | comfy. */
|
|
485
603
|
density: string;
|
|
486
|
-
/** User-resized column widths, in px, keyed by column id. */
|
|
487
|
-
columnWidths: Record<string, number>;
|
|
488
604
|
}
|
|
489
605
|
|
|
490
606
|
/**
|
|
@@ -542,12 +658,42 @@ declare function tapRealtimeAdapter<T extends RealtimeAdapterLike>(adapter: T, t
|
|
|
542
658
|
*
|
|
543
659
|
* So `withInitiatorCapture` wraps the axios instance, takes the stack
|
|
544
660
|
* synchronously in the caller's own frame, and stashes it on the config for
|
|
545
|
-
* `beginMonitor` to read.
|
|
661
|
+
* `beginMonitor` to read. The fetch wrapper needs no such proxy: it *is* the
|
|
662
|
+
* call site, so it calls `captureFrames` directly.
|
|
663
|
+
*
|
|
664
|
+
* ## Why Blix's own frames are dropped by depth
|
|
665
|
+
*
|
|
666
|
+
* Both callers know exactly how many of their own frames sit above the caller's
|
|
667
|
+
* code, and pass that as `skip`. The alternative — recognizing Blix's frames by
|
|
668
|
+
* module name — does not survive bundling: the published package is a handful
|
|
669
|
+
* of hashed `dist/chunk-*.js` files, none of them named after the source
|
|
670
|
+
* module, so in a real install every Blix frame slipped through and the
|
|
671
|
+
* Initiator column pointed at Blix itself. Do not add module-name rules for
|
|
672
|
+
* Blix's own code to `NOISE`.
|
|
546
673
|
*
|
|
547
674
|
* The whole module is behind a statically-foldable `NODE_ENV` check at its call
|
|
548
675
|
* site, so it is dead-code-eliminated from production builds.
|
|
549
676
|
*/
|
|
550
677
|
|
|
678
|
+
/**
|
|
679
|
+
* What `withInitiatorCapture` needs from an HTTP client — declared
|
|
680
|
+
* structurally, so axios's types are never imported and an app that only uses
|
|
681
|
+
* `fetch` never needs axios installed to type-check against Blix.
|
|
682
|
+
*
|
|
683
|
+
* `request` is the one entry point every axios-style client has; the verb
|
|
684
|
+
* helpers are wrapped when present. Method syntax on purpose: method parameters
|
|
685
|
+
* are compared bivariantly, which is what lets axios's generic, config-typed
|
|
686
|
+
* signatures satisfy these `unknown`-typed ones without a cast.
|
|
687
|
+
*/
|
|
688
|
+
interface InitiatorCaptureTarget {
|
|
689
|
+
request(config: unknown): unknown;
|
|
690
|
+
get?(url: string, config?: unknown): unknown;
|
|
691
|
+
delete?(url: string, config?: unknown): unknown;
|
|
692
|
+
head?(url: string, config?: unknown): unknown;
|
|
693
|
+
post?(url: string, data?: unknown, config?: unknown): unknown;
|
|
694
|
+
put?(url: string, data?: unknown, config?: unknown): unknown;
|
|
695
|
+
patch?(url: string, data?: unknown, config?: unknown): unknown;
|
|
696
|
+
}
|
|
551
697
|
/**
|
|
552
698
|
* Wraps the axios instance so every call site records its own stack.
|
|
553
699
|
*
|
|
@@ -555,7 +701,11 @@ declare function tapRealtimeAdapter<T extends RealtimeAdapterLike>(adapter: T, t
|
|
|
555
701
|
* `put`, `patch`, `delete` and `head`. Other entry points (`options`, the
|
|
556
702
|
* `*Form` helpers) pass through unwrapped — requests made through them are
|
|
557
703
|
* still captured, they just carry no initiator stack.
|
|
704
|
+
*
|
|
705
|
+
* Generic over the instance, so what the caller gets back is its own type —
|
|
706
|
+
* an `AxiosInstance` stays an `AxiosInstance`, with `get<T>`, `defaults` and
|
|
707
|
+
* `interceptors` intact — rather than the minimal shape it was checked against.
|
|
558
708
|
*/
|
|
559
|
-
declare function withInitiatorCapture(instance:
|
|
709
|
+
declare function withInitiatorCapture<T extends InitiatorCaptureTarget>(instance: T): T;
|
|
560
710
|
|
|
561
|
-
export { type CacheLike, type Corner, type DiffOp, type DockMode, type EncryptedPayload, type FrameDirection, type InitiatorFrame, type MiddlewareApiLike, type MiddlewareLike, type MonitorEntry, type MonitorKind, type MonitorPrefs, type MonitorState, type PersistedEntry, type Pos, type QueryClientLike, type QueryMeta, type RealtimeAdapterLike, type ReduxActionMeta, type ReduxCaptureOptions, type Size, type StateDiff, type StateDiffEntry, type TimingMarks, type WsFrame, attachHttpMonitor, captureEncrypted, createReduxMonitorMiddleware, tapQueryClient, tapRealtimeAdapter, withInitiatorCapture };
|
|
711
|
+
export { type CacheLike, type Corner, type DiffOp, type DockMode, type EncryptedPayload, type FetchIgnoreRule, type FetchMonitorOptions, type FrameDirection, type InitiatorFrame, type MiddlewareApiLike, type MiddlewareLike, type MonitorEntry, type MonitorKind, type MonitorPrefs, type MonitorState, type PersistedEntry, type Pos, type QueryClientLike, type QueryMeta, type RealtimeAdapterLike, type ReduxActionMeta, type ReduxCaptureOptions, type Size, type StateDiff, type StateDiffEntry, type TimingMarks, type WsFrame, attachFetchMonitor, attachHttpMonitor, captureEncrypted, createReduxMonitorMiddleware, tapQueryClient, tapRealtimeAdapter, withInitiatorCapture };
|
package/dist/capture/index.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
+
attachFetchMonitor,
|
|
2
3
|
attachHttpMonitor,
|
|
3
4
|
captureEncrypted,
|
|
4
5
|
tapRealtimeAdapter
|
|
5
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-4JZEVUOR.js";
|
|
6
7
|
import {
|
|
7
8
|
createReduxMonitorMiddleware,
|
|
8
9
|
tapQueryClient,
|
|
9
10
|
withInitiatorCapture
|
|
10
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-FIYP47QZ.js";
|
|
11
12
|
export {
|
|
13
|
+
attachFetchMonitor,
|
|
12
14
|
attachHttpMonitor,
|
|
13
15
|
captureEncrypted,
|
|
14
16
|
createReduxMonitorMiddleware,
|