@warlock.js/devtools 5.25.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +35 -0
  4. package/cjs/index.cjs +694 -0
  5. package/cjs/index.cjs.map +1 -0
  6. package/cjs/server-DHYSVive.cjs +286 -0
  7. package/cjs/server-DHYSVive.cjs.map +1 -0
  8. package/esm/caller-location.mjs +33 -0
  9. package/esm/caller-location.mjs.map +1 -0
  10. package/esm/collector.mjs +185 -0
  11. package/esm/collector.mjs.map +1 -0
  12. package/esm/current-request-id.mjs +24 -0
  13. package/esm/current-request-id.mjs.map +1 -0
  14. package/esm/devtools-connector.d.mts +26 -0
  15. package/esm/devtools-connector.d.mts.map +1 -0
  16. package/esm/devtools-connector.mjs +112 -0
  17. package/esm/devtools-connector.mjs.map +1 -0
  18. package/esm/index.d.mts +3 -0
  19. package/esm/index.mjs +3 -0
  20. package/esm/n-plus-one.mjs +36 -0
  21. package/esm/n-plus-one.mjs.map +1 -0
  22. package/esm/options.mjs +19 -0
  23. package/esm/options.mjs.map +1 -0
  24. package/esm/query-shape.mjs +27 -0
  25. package/esm/query-shape.mjs.map +1 -0
  26. package/esm/safe-value.mjs +63 -0
  27. package/esm/safe-value.mjs.map +1 -0
  28. package/esm/server/background-routes.mjs +9 -0
  29. package/esm/server/background-routes.mjs.map +1 -0
  30. package/esm/server/explain-route.mjs +37 -0
  31. package/esm/server/explain-route.mjs.map +1 -0
  32. package/esm/server/guard.mjs +30 -0
  33. package/esm/server/guard.mjs.map +1 -0
  34. package/esm/server/index.mjs +37 -0
  35. package/esm/server/index.mjs.map +1 -0
  36. package/esm/server/log-routes.mjs +12 -0
  37. package/esm/server/log-routes.mjs.map +1 -0
  38. package/esm/server/mail-routes.mjs +27 -0
  39. package/esm/server/mail-routes.mjs.map +1 -0
  40. package/esm/server/requests-routes.mjs +21 -0
  41. package/esm/server/requests-routes.mjs.map +1 -0
  42. package/esm/server/route-routes.mjs +9 -0
  43. package/esm/server/route-routes.mjs.map +1 -0
  44. package/esm/server/stream-route.mjs +32 -0
  45. package/esm/server/stream-route.mjs.map +1 -0
  46. package/esm/server/ui-assets.mjs +38 -0
  47. package/esm/server/ui-assets.mjs.map +1 -0
  48. package/esm/sources/cache-source.mjs +51 -0
  49. package/esm/sources/cache-source.mjs.map +1 -0
  50. package/esm/sources/log-source.mjs +46 -0
  51. package/esm/sources/log-source.mjs.map +1 -0
  52. package/esm/sources/mail-source.mjs +48 -0
  53. package/esm/sources/mail-source.mjs.map +1 -0
  54. package/esm/sources/query-source.mjs +52 -0
  55. package/esm/sources/query-source.mjs.map +1 -0
  56. package/esm/sources/tracing-source.mjs +47 -0
  57. package/esm/sources/tracing-source.mjs.map +1 -0
  58. package/esm/types.d.mts +213 -0
  59. package/esm/types.d.mts.map +1 -0
  60. package/esm/ui-root.mjs +40 -0
  61. package/esm/ui-root.mjs.map +1 -0
  62. package/llms-full.txt +78 -0
  63. package/llms.txt +9 -0
  64. package/package.json +52 -0
  65. package/skills/devtools-overview/SKILL.md +68 -0
  66. package/ui/app.css +354 -0
  67. package/ui/app.js +520 -0
  68. package/ui/index.html +24 -0
@@ -0,0 +1,213 @@
1
+ //#region ../devtools/src/types.d.ts
2
+ /**
3
+ * The records the devtools collector keeps and the dashboard API returns.
4
+ *
5
+ * Every timestamp is epoch milliseconds (fractional allowed); every duration
6
+ * is milliseconds. Nothing here is persisted — the collector is an in-memory
7
+ * ring buffer that is lost on restart.
8
+ */
9
+ /** Options accepted by `devtoolsConnector()`. */
10
+ type DevtoolsOptions = {
11
+ /**
12
+ * URL prefix the dashboard and its API are served under.
13
+ *
14
+ * @default "/__warlock"
15
+ */
16
+ path?: string;
17
+ /**
18
+ * How many requests the ring buffer keeps; the oldest is evicted first.
19
+ *
20
+ * @default 200
21
+ */
22
+ maxRequests?: number;
23
+ /**
24
+ * How many times one query shape may run inside a single request before it
25
+ * is reported as an N+1.
26
+ *
27
+ * @default 5
28
+ */
29
+ nPlusOneThreshold?: number;
30
+ };
31
+ /** `DevtoolsOptions` with every default applied. */
32
+ type ResolvedDevtoolsOptions = Required<DevtoolsOptions>;
33
+ /** A source file position, as shown next to an N+1 warning. */
34
+ type DevtoolsCallerLocation = {
35
+ /** Absolute path, forward slashes. */file: string;
36
+ line: number;
37
+ column: number;
38
+ };
39
+ /** One timed phase of a request (router middleware, loader, render, …). */
40
+ type DevtoolsPhase = {
41
+ name: string;
42
+ startedAt: number;
43
+ durationMs: number;
44
+ attrs?: Record<string, unknown>;
45
+ };
46
+ /** One database query, Postgres or Mongo. */
47
+ type DevtoolsQuery = {
48
+ /** Unique within the collector. */id: string;
49
+ connection: string;
50
+ driver: "postgres" | "mongodb";
51
+ sql?: string; /** Redacted through the logger's redaction rules before storage. */
52
+ bindings?: unknown[];
53
+ collection?: string;
54
+ command?: string;
55
+ pipeline?: unknown;
56
+ startedAt: number;
57
+ durationMs: number;
58
+ rowCount?: number; /** The error message when the query failed. */
59
+ error?: string;
60
+ /**
61
+ * The query with its values stripped — SQL text without bindings, or the
62
+ * Mongo command with every leaf value replaced by `?`. Two queries with the
63
+ * same shape are "the same query" for N+1 detection.
64
+ */
65
+ shape: string; /** The first application frame that issued the query, when resolvable. */
66
+ caller?: DevtoolsCallerLocation;
67
+ };
68
+ /** One cache operation. */
69
+ type DevtoolsCacheOperation = {
70
+ type: "hit" | "miss" | "set" | "removed" | "invalidated" | "flushed";
71
+ driver: string;
72
+ key?: string;
73
+ tags?: string[];
74
+ keys?: string[];
75
+ at: number;
76
+ durationMs?: number;
77
+ };
78
+ /** One log entry. */
79
+ type DevtoolsLogEntry = {
80
+ level: "debug" | "info" | "warn" | "error" | "success";
81
+ module: string;
82
+ action: string;
83
+ message: string;
84
+ at: number; /** Redacted through the logger's redaction rules before storage. */
85
+ context?: unknown;
86
+ };
87
+ /** A mail captured in development (see core's `mailEvents.onCaptured`). */
88
+ type DevtoolsMail = {
89
+ id: string;
90
+ requestId?: string;
91
+ capturedAt: number;
92
+ from?: string;
93
+ to: string[];
94
+ cc: string[];
95
+ bcc: string[];
96
+ subject: string;
97
+ html?: string;
98
+ text?: string;
99
+ headers: Record<string, string>;
100
+ attachments: DevtoolsMailAttachment[];
101
+ };
102
+ /** Attachment metadata only — the bytes are never stored. */
103
+ type DevtoolsMailAttachment = {
104
+ filename: string;
105
+ contentType?: string;
106
+ size?: number;
107
+ };
108
+ /** A problem devtools noticed about a request. */
109
+ type DevtoolsWarning = {
110
+ type: "n+1";
111
+ message: string;
112
+ shape: string;
113
+ count: number;
114
+ caller?: DevtoolsCallerLocation;
115
+ };
116
+ /** Everything recorded for one HTTP request. */
117
+ type DevtoolsRequest = {
118
+ /** The framework's `request.id`. */id: string;
119
+ method: string;
120
+ path: string; /** Matched route pattern, e.g. `/posts/:slug`. */
121
+ route?: string;
122
+ status?: number;
123
+ startedAt: number; /** Undefined while the request is still in flight. */
124
+ durationMs?: number;
125
+ error?: string;
126
+ phases: DevtoolsPhase[];
127
+ queries: DevtoolsQuery[];
128
+ cache: DevtoolsCacheOperation[];
129
+ logs: DevtoolsLogEntry[]; /** Ids of mails captured during this request (see `DevtoolsMail`). */
130
+ mails: string[];
131
+ warnings: DevtoolsWarning[];
132
+ };
133
+ /** The row the request list shows; `GET <path>/api/requests` returns these. */
134
+ type DevtoolsRequestSummary = {
135
+ id: string;
136
+ method: string;
137
+ path: string;
138
+ route?: string;
139
+ status?: number;
140
+ startedAt: number;
141
+ durationMs?: number;
142
+ queryCount: number;
143
+ warningCount: number;
144
+ };
145
+ /** The row the mailbox list shows; `GET <path>/api/mails` returns these. */
146
+ type DevtoolsMailSummary = {
147
+ id: string;
148
+ requestId?: string;
149
+ capturedAt: number;
150
+ to: string[];
151
+ subject: string;
152
+ };
153
+ /**
154
+ * Work that happened outside any request (boot, a scheduler tick, a queue
155
+ * job): kept separately so it never pollutes a request's timeline.
156
+ */
157
+ type DevtoolsBackground = {
158
+ queries: DevtoolsQuery[];
159
+ cache: DevtoolsCacheOperation[];
160
+ logs: DevtoolsLogEntry[];
161
+ };
162
+ /** Server-sent events on `GET <path>/api/stream`. */
163
+ type DevtoolsStreamEvent = {
164
+ type: "request";
165
+ data: DevtoolsRequestSummary;
166
+ } | {
167
+ type: "mail";
168
+ data: DevtoolsMailSummary;
169
+ } | {
170
+ type: "log";
171
+ data: DevtoolsLogEntry;
172
+ };
173
+ /** Unsubscribes a listener or detaches a source. */
174
+ type DevtoolsDisposer = () => void;
175
+ /**
176
+ * The in-memory store every source writes to and the dashboard API reads
177
+ * from. `requestId` is the framework's `request.id`; `undefined` means the
178
+ * work happened outside a request and goes to the background bucket.
179
+ */
180
+ type DevtoolsCollector = {
181
+ startRequest(request: {
182
+ id: string;
183
+ method: string;
184
+ path: string;
185
+ route?: string;
186
+ startedAt: number;
187
+ }): void; /** Settles the request, runs N+1 detection and emits a `request` stream event. */
188
+ endRequest(id: string, result: {
189
+ status?: number;
190
+ durationMs: number;
191
+ error?: string;
192
+ route?: string;
193
+ }): void;
194
+ addPhase(requestId: string, phase: DevtoolsPhase): void; /** Computes `id` and `shape` itself. */
195
+ addQuery(requestId: string | undefined, query: Omit<DevtoolsQuery, "id" | "shape">): DevtoolsQuery;
196
+ addCacheOperation(requestId: string | undefined, operation: DevtoolsCacheOperation): void;
197
+ addLog(requestId: string | undefined, entry: DevtoolsLogEntry): void;
198
+ addMail(mail: DevtoolsMail): void;
199
+ listRequests(): DevtoolsRequestSummary[];
200
+ getRequest(id: string): DevtoolsRequest | undefined;
201
+ listMails(): DevtoolsMailSummary[];
202
+ getMail(id: string): DevtoolsMail | undefined;
203
+ listLogs(filter?: {
204
+ level?: DevtoolsLogEntry["level"];
205
+ module?: string;
206
+ }): DevtoolsLogEntry[];
207
+ getBackground(): DevtoolsBackground;
208
+ subscribe(listener: (event: DevtoolsStreamEvent) => void): DevtoolsDisposer;
209
+ clear(): void;
210
+ };
211
+ //#endregion
212
+ export { DevtoolsBackground, DevtoolsCacheOperation, DevtoolsCallerLocation, DevtoolsCollector, DevtoolsDisposer, DevtoolsLogEntry, DevtoolsMail, DevtoolsMailAttachment, DevtoolsMailSummary, DevtoolsOptions, DevtoolsPhase, DevtoolsQuery, DevtoolsRequest, DevtoolsRequestSummary, DevtoolsStreamEvent, DevtoolsWarning, ResolvedDevtoolsOptions };
213
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../devtools/src/types.ts"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ //#region ../devtools/src/ui-root.ts
6
+ let cachedUiRoot;
7
+ /** Whether `dir` is this package's root: it has `ui/index.html` beside a `package.json` named `@warlock.js/devtools`. */
8
+ function isPackageRoot(dir) {
9
+ const indexHtml = path.join(dir, "ui", "index.html");
10
+ const packageJson = path.join(dir, "package.json");
11
+ if (!existsSync(indexHtml) || !existsSync(packageJson)) return false;
12
+ try {
13
+ return JSON.parse(readFileSync(packageJson, "utf8")).name === "@warlock.js/devtools";
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ /**
19
+ * Locate this package's `ui/` directory by walking up from the current
20
+ * module's directory. Works from `src/` in development and from the built
21
+ * `esm/`/`cjs/` output, since both sit beside `ui/` and `package.json` at
22
+ * the package root.
23
+ */
24
+ function resolveUiRoot() {
25
+ if (cachedUiRoot) return cachedUiRoot;
26
+ let dir = path.dirname(fileURLToPath(import.meta.url));
27
+ while (true) {
28
+ if (isPackageRoot(dir)) {
29
+ cachedUiRoot = path.join(dir, "ui");
30
+ return cachedUiRoot;
31
+ }
32
+ const parent = path.dirname(dir);
33
+ if (parent === dir) throw new Error("Could not locate the devtools UI directory: no ancestor of this module has a ui/index.html beside a package.json named @warlock.js/devtools.");
34
+ dir = parent;
35
+ }
36
+ }
37
+
38
+ //#endregion
39
+ export { resolveUiRoot };
40
+ //# sourceMappingURL=ui-root.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-root.mjs","names":[],"sources":["../../../../../../devtools/src/ui-root.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet cachedUiRoot: string | undefined;\n\n/** Whether `dir` is this package's root: it has `ui/index.html` beside a `package.json` named `@warlock.js/devtools`. */\nfunction isPackageRoot(dir: string): boolean {\n const indexHtml = path.join(dir, \"ui\", \"index.html\");\n const packageJson = path.join(dir, \"package.json\");\n\n if (!existsSync(indexHtml) || !existsSync(packageJson)) {\n return false;\n }\n\n try {\n const parsed = JSON.parse(readFileSync(packageJson, \"utf8\")) as { name?: string };\n return parsed.name === \"@warlock.js/devtools\";\n } catch {\n return false;\n }\n}\n\n/**\n * Locate this package's `ui/` directory by walking up from the current\n * module's directory. Works from `src/` in development and from the built\n * `esm/`/`cjs/` output, since both sit beside `ui/` and `package.json` at\n * the package root.\n */\nexport function resolveUiRoot(): string {\n if (cachedUiRoot) {\n return cachedUiRoot;\n }\n\n let dir = path.dirname(fileURLToPath(import.meta.url));\n\n while (true) {\n if (isPackageRoot(dir)) {\n cachedUiRoot = path.join(dir, \"ui\");\n return cachedUiRoot;\n }\n\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(\n \"Could not locate the devtools UI directory: no ancestor of \" +\n \"this module has a ui/index.html beside a package.json named \" +\n \"@warlock.js/devtools.\",\n );\n }\n\n dir = parent;\n }\n}\n"],"mappings":";;;;;AAIA,IAAI;;AAGJ,SAAS,cAAc,KAAsB;CAC3C,MAAM,YAAY,KAAK,KAAK,KAAK,MAAM,YAAY;CACnD,MAAM,cAAc,KAAK,KAAK,KAAK,cAAc;CAEjD,IAAI,CAAC,WAAW,SAAS,KAAK,CAAC,WAAW,WAAW,GACnD,OAAO;CAGT,IAAI;EAEF,OADe,KAAK,MAAM,aAAa,aAAa,MAAM,CAC9C,CAAC,CAAC,SAAS;CACzB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,gBAAwB;CACtC,IAAI,cACF,OAAO;CAGT,IAAI,MAAM,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC;CAErD,OAAO,MAAM;EACX,IAAI,cAAc,GAAG,GAAG;GACtB,eAAe,KAAK,KAAK,KAAK,IAAI;GAClC,OAAO;EACT;EAEA,MAAM,SAAS,KAAK,QAAQ,GAAG;EAC/B,IAAI,WAAW,KACb,MAAM,IAAI,MACR,8IAGF;EAGF,MAAM;CACR;AACF"}
package/llms-full.txt ADDED
@@ -0,0 +1,78 @@
1
+ # Warlock Devtools — full skills
2
+
3
+ > Package: `@warlock.js/devtools`
4
+
5
+ > Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/devtools/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.
6
+
7
+ ## devtools-overview `@warlock.js/devtools/devtools-overview/SKILL.md`
8
+
9
+ ---
10
+ name: devtools-overview
11
+ description: 'Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations and the route map. Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.'
12
+ ---
13
+
14
+ # `@warlock.js/devtools` — overview
15
+
16
+ A dashboard for **development only**, served by your own app at `/__warlock`.
17
+ It shows what each request did: its timing waterfall, the queries it ran, the
18
+ cache operations, the logs, and any mail it sent.
19
+
20
+ ## Install: one command, no config
21
+
22
+ ```bash
23
+ warlock add devtools # adds @warlock.js/devtools as a DEV dependency
24
+ warlock dev # open http://localhost:<port>/__warlock
25
+ ```
26
+
27
+ There's nothing to register. `warlock dev` loads devtools automatically when
28
+ the package is installed.
29
+
30
+ **Do not** add `devtoolsConnector()` to `warlock.config.ts`. Devtools is a
31
+ dev dependency, so a production install doesn't have it, and a config import
32
+ of it would fail to resolve there.
33
+
34
+ ## Why it can't leak into production
35
+
36
+ 1. **It isn't installed there.** Production installs omit dev dependencies.
37
+ 2. **It refuses to run outside development.** If it's present anyway, it logs
38
+ one warning (`@warlock.js/devtools only runs in development; it is not mounted`)
39
+ and mounts nothing.
40
+ 3. **It only answers loopback clients.** Every dashboard and API route answers
41
+ 404 unless the connection comes from `127.0.0.1` / `::1`. It checks the
42
+ socket address, not `X-Forwarded-For`, so a forwarded-for header can't
43
+ unlock it.
44
+
45
+ ## What each panel shows
46
+
47
+ | Panel | What you see | Where it comes from |
48
+ |---|---|---|
49
+ | **Requests** | Each request with its status, duration and query count. Click one for a waterfall: router middleware, `page.middleware`, loaders, `page.cache` hit/miss, `render.shell`, `defer.settle`, with queries and cache ops placed on the same time axis. | core `registerTracingHooks()` |
50
+ | **Queries** | SQL + bindings or the Mongo command, duration, rows, and an **Explain** button (Postgres `SELECT` only; runs `EXPLAIN (FORMAT JSON)`, never `ANALYZE`) | cascade `dataSourceRegistry.on("query")` |
51
+ | **N+1** | `⚠ N+1: 12× "select … where post_id = $1" — src/app/posts/services/list-posts.service.ts:18`, shown on the request and printed in the terminal | same query shape ≥ 5 times in one request |
52
+ | **Mail** | Every mail sent in development: headers, HTML (sandboxed) and text, attachment names | core `mailEvents.onCaptured()` |
53
+ | **Logs** | Log entries per request, filterable by level and module | a logger channel |
54
+ | **Cache** | hit / miss / set / removed / `invalidated` (tags, keys removed) | cache `cache.on(...)` |
55
+ | **Routes** | Every HTTP route with its method, path, name and middleware | core router |
56
+
57
+ Work outside a request (boot, scheduler ticks, queue jobs) goes to a separate
58
+ background list, so it never clutters a request's timeline.
59
+
60
+ ## Options
61
+
62
+ `warlock dev` uses the defaults: path `/__warlock`, the last 200 requests
63
+ kept, and an N+1 threshold of 5. Everything lives in memory and is gone on
64
+ restart.
65
+
66
+ ## Secrets
67
+
68
+ Query bindings and log context go through the logger's redaction rules
69
+ before they're stored. The data is still real request data, which is exactly
70
+ why the dashboard is development-only and loopback-only.
71
+
72
+ ## Related
73
+
74
+ - Building your own tooling on the same hooks: core `registerTracingHooks()`,
75
+ cascade `dataSourceRegistry.on("query")`, core `mailEvents.onCaptured()` and
76
+ the cache `invalidated` event are public APIs that also work without devtools.
77
+
78
+
package/llms.txt ADDED
@@ -0,0 +1,9 @@
1
+ # Warlock Devtools
2
+
3
+ > Package: `@warlock.js/devtools`
4
+
5
+ > Development-only dashboard for Warlock.js apps at /__warlock: request timeline, queries with EXPLAIN, N+1 detection, dev mailbox, logs, cache operations and the route map. Refuses to run outside development.
6
+
7
+ ## Skills
8
+
9
+ - [devtools-overview](@warlock.js/devtools/devtools-overview/SKILL.md): Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations and the route map. Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@warlock.js/devtools",
3
+ "description": "Development-only dashboard for Warlock.js apps at /__warlock: request timeline, queries with EXPLAIN, N+1 detection, dev mailbox, logs, cache operations and the route map. Refuses to run outside development.",
4
+ "warlock": {
5
+ "environment": "server"
6
+ },
7
+ "peerDependencies": {
8
+ "@warlock.js/cache": "5.25.0",
9
+ "@warlock.js/cascade": "5.25.0",
10
+ "@warlock.js/core": "5.25.0",
11
+ "@warlock.js/logger": "5.25.0"
12
+ },
13
+ "peerDependenciesMeta": {
14
+ "@warlock.js/cache": {
15
+ "optional": true
16
+ },
17
+ "@warlock.js/cascade": {
18
+ "optional": true
19
+ }
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/warlockjs/devtools"
24
+ },
25
+ "keywords": [
26
+ "warlock.js",
27
+ "devtools",
28
+ "debugging",
29
+ "profiler",
30
+ "n+1",
31
+ "timeline",
32
+ "mailbox"
33
+ ],
34
+ "author": "hassanzohdy",
35
+ "license": "MIT",
36
+ "version": "5.25.0",
37
+ "main": "./cjs/index.cjs",
38
+ "module": "./esm/index.mjs",
39
+ "types": "./esm/index.d.mts",
40
+ "exports": {
41
+ ".": {
42
+ "import": {
43
+ "types": "./esm/index.d.mts",
44
+ "default": "./esm/index.mjs"
45
+ },
46
+ "require": {
47
+ "types": "./esm/index.d.mts",
48
+ "default": "./cjs/index.cjs"
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: devtools-overview
3
+ description: 'Development-only dashboard for Warlock.js apps at `/__warlock`: request timeline (router + page middleware, loaders, render, deferred values), every SQL/Mongo query with EXPLAIN, N+1 warnings naming the file and line, a dev mailbox, logs, cache operations and the route map. Use for: "why is this page slow", "which queries does this request run", "find N+1 queries", "see the email my app just sent in dev", "open the dashboard". Install with `warlock add devtools`; never registered in warlock.config.ts. Not this package: production monitoring/APM or OpenTelemetry export — use `http.tracing` hooks in @warlock.js/core; slow-query logging in production — subscribe to cascade `dataSourceRegistry.on("query")` yourself.'
4
+ ---
5
+
6
+ # `@warlock.js/devtools` — overview
7
+
8
+ A dashboard for **development only**, served by your own app at `/__warlock`.
9
+ It shows what each request did: its timing waterfall, the queries it ran, the
10
+ cache operations, the logs, and any mail it sent.
11
+
12
+ ## Install: one command, no config
13
+
14
+ ```bash
15
+ warlock add devtools # adds @warlock.js/devtools as a DEV dependency
16
+ warlock dev # open http://localhost:<port>/__warlock
17
+ ```
18
+
19
+ There's nothing to register. `warlock dev` loads devtools automatically when
20
+ the package is installed.
21
+
22
+ **Do not** add `devtoolsConnector()` to `warlock.config.ts`. Devtools is a
23
+ dev dependency, so a production install doesn't have it, and a config import
24
+ of it would fail to resolve there.
25
+
26
+ ## Why it can't leak into production
27
+
28
+ 1. **It isn't installed there.** Production installs omit dev dependencies.
29
+ 2. **It refuses to run outside development.** If it's present anyway, it logs
30
+ one warning (`@warlock.js/devtools only runs in development; it is not mounted`)
31
+ and mounts nothing.
32
+ 3. **It only answers loopback clients.** Every dashboard and API route answers
33
+ 404 unless the connection comes from `127.0.0.1` / `::1`. It checks the
34
+ socket address, not `X-Forwarded-For`, so a forwarded-for header can't
35
+ unlock it.
36
+
37
+ ## What each panel shows
38
+
39
+ | Panel | What you see | Where it comes from |
40
+ |---|---|---|
41
+ | **Requests** | Each request with its status, duration and query count. Click one for a waterfall: router middleware, `page.middleware`, loaders, `page.cache` hit/miss, `render.shell`, `defer.settle`, with queries and cache ops placed on the same time axis. | core `registerTracingHooks()` |
42
+ | **Queries** | SQL + bindings or the Mongo command, duration, rows, and an **Explain** button (Postgres `SELECT` only; runs `EXPLAIN (FORMAT JSON)`, never `ANALYZE`) | cascade `dataSourceRegistry.on("query")` |
43
+ | **N+1** | `⚠ N+1: 12× "select … where post_id = $1" — src/app/posts/services/list-posts.service.ts:18`, shown on the request and printed in the terminal | same query shape ≥ 5 times in one request |
44
+ | **Mail** | Every mail sent in development: headers, HTML (sandboxed) and text, attachment names | core `mailEvents.onCaptured()` |
45
+ | **Logs** | Log entries per request, filterable by level and module | a logger channel |
46
+ | **Cache** | hit / miss / set / removed / `invalidated` (tags, keys removed) | cache `cache.on(...)` |
47
+ | **Routes** | Every HTTP route with its method, path, name and middleware | core router |
48
+
49
+ Work outside a request (boot, scheduler ticks, queue jobs) goes to a separate
50
+ background list, so it never clutters a request's timeline.
51
+
52
+ ## Options
53
+
54
+ `warlock dev` uses the defaults: path `/__warlock`, the last 200 requests
55
+ kept, and an N+1 threshold of 5. Everything lives in memory and is gone on
56
+ restart.
57
+
58
+ ## Secrets
59
+
60
+ Query bindings and log context go through the logger's redaction rules
61
+ before they're stored. The data is still real request data, which is exactly
62
+ why the dashboard is development-only and loopback-only.
63
+
64
+ ## Related
65
+
66
+ - Building your own tooling on the same hooks: core `registerTracingHooks()`,
67
+ cascade `dataSourceRegistry.on("query")`, core `mailEvents.onCaptured()` and
68
+ the cache `invalidated` event are public APIs that also work without devtools.