@warlock.js/devtools 5.25.0 → 5.27.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.
@@ -3,10 +3,11 @@ import { registerExplainRoute } from "./explain-route.mjs";
3
3
  import { registerDevtoolsGuard } from "./guard.mjs";
4
4
  import { registerLogRoutes } from "./log-routes.mjs";
5
5
  import { registerMailRoutes } from "./mail-routes.mjs";
6
+ import { registerUiAssets } from "./ui-assets.mjs";
7
+ import { registerOpenApiRoutes } from "./openapi-routes.mjs";
6
8
  import { registerRequestsRoutes } from "./requests-routes.mjs";
7
9
  import { registerRouteRoutes } from "./route-routes.mjs";
8
10
  import { registerStreamRoute } from "./stream-route.mjs";
9
- import { registerUiAssets } from "./ui-assets.mjs";
10
11
 
11
12
  //#region ../devtools/src/server/index.ts
12
13
  /**
@@ -27,6 +28,7 @@ function mountDevtoolsRoutes(server, deps) {
27
28
  registerMailRoutes(instance, deps);
28
29
  registerLogRoutes(instance, deps);
29
30
  registerRouteRoutes(instance, deps);
31
+ registerOpenApiRoutes(instance, deps);
30
32
  registerExplainRoute(instance, deps);
31
33
  registerStreamRoute(instance, deps);
32
34
  }, { prefix: deps.options.path });
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../devtools/src/server/index.ts"],"sourcesContent":["import type { FastifyError, FastifyInstance } from \"fastify\";\nimport { registerBackgroundRoutes } from \"./background-routes\";\nimport { registerExplainRoute } from \"./explain-route\";\nimport { registerDevtoolsGuard } from \"./guard\";\nimport { registerLogRoutes } from \"./log-routes\";\nimport { registerMailRoutes } from \"./mail-routes\";\nimport { registerRequestsRoutes } from \"./requests-routes\";\nimport { registerRouteRoutes } from \"./route-routes\";\nimport { registerStreamRoute } from \"./stream-route\";\nimport type { MountDevtoolsRoutesDeps } from \"./types\";\nimport { registerUiAssets } from \"./ui-assets\";\n\nexport type { DevtoolsRouteRow, MountDevtoolsRoutesDeps } from \"./types\";\n\n/**\n * Mounts the devtools dashboard and its API under `deps.options.path` on\n * `server`. Every route is registered inside a single encapsulated Fastify\n * context so the dev-only, loopback-only guard never reaches the app's\n * other routes.\n */\nexport function mountDevtoolsRoutes(server: FastifyInstance, deps: MountDevtoolsRoutesDeps): void {\n server.register(\n async (instance) => {\n registerDevtoolsGuard(instance, deps);\n\n instance.setErrorHandler((error: FastifyError, _request, reply) => {\n reply.code(error.statusCode ?? 500).send({ error: error.message });\n });\n\n registerUiAssets(instance);\n registerRequestsRoutes(instance, deps);\n registerBackgroundRoutes(instance, deps);\n registerMailRoutes(instance, deps);\n registerLogRoutes(instance, deps);\n registerRouteRoutes(instance, deps);\n registerExplainRoute(instance, deps);\n registerStreamRoute(instance, deps);\n },\n { prefix: deps.options.path },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBAAoB,QAAyB,MAAqC;CAChG,OAAO,SACL,OAAO,aAAa;EAClB,sBAAsB,UAAU,IAAI;EAEpC,SAAS,iBAAiB,OAAqB,UAAU,UAAU;GACjE,MAAM,KAAK,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;EACnE,CAAC;EAED,iBAAiB,QAAQ;EACzB,uBAAuB,UAAU,IAAI;EACrC,yBAAyB,UAAU,IAAI;EACvC,mBAAmB,UAAU,IAAI;EACjC,kBAAkB,UAAU,IAAI;EAChC,oBAAoB,UAAU,IAAI;EAClC,qBAAqB,UAAU,IAAI;EACnC,oBAAoB,UAAU,IAAI;CACpC,GACA,EAAE,QAAQ,KAAK,QAAQ,KAAK,CAC9B;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../devtools/src/server/index.ts"],"sourcesContent":["import type { FastifyError, FastifyInstance } from \"fastify\";\nimport { registerBackgroundRoutes } from \"./background-routes\";\nimport { registerExplainRoute } from \"./explain-route\";\nimport { registerDevtoolsGuard } from \"./guard\";\nimport { registerLogRoutes } from \"./log-routes\";\nimport { registerMailRoutes } from \"./mail-routes\";\nimport { registerOpenApiRoutes } from \"./openapi-routes\";\nimport { registerRequestsRoutes } from \"./requests-routes\";\nimport { registerRouteRoutes } from \"./route-routes\";\nimport { registerStreamRoute } from \"./stream-route\";\nimport type { MountDevtoolsRoutesDeps } from \"./types\";\nimport { registerUiAssets } from \"./ui-assets\";\n\nexport type { DevtoolsRouteRow, MountDevtoolsRoutesDeps } from \"./types\";\n\n/**\n * Mounts the devtools dashboard and its API under `deps.options.path` on\n * `server`. Every route is registered inside a single encapsulated Fastify\n * context so the dev-only, loopback-only guard never reaches the app's\n * other routes.\n */\nexport function mountDevtoolsRoutes(server: FastifyInstance, deps: MountDevtoolsRoutesDeps): void {\n server.register(\n async (instance) => {\n registerDevtoolsGuard(instance, deps);\n\n instance.setErrorHandler((error: FastifyError, _request, reply) => {\n reply.code(error.statusCode ?? 500).send({ error: error.message });\n });\n\n registerUiAssets(instance);\n registerRequestsRoutes(instance, deps);\n registerBackgroundRoutes(instance, deps);\n registerMailRoutes(instance, deps);\n registerLogRoutes(instance, deps);\n registerRouteRoutes(instance, deps);\n registerOpenApiRoutes(instance, deps);\n registerExplainRoute(instance, deps);\n registerStreamRoute(instance, deps);\n },\n { prefix: deps.options.path },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,SAAgB,oBAAoB,QAAyB,MAAqC;CAChG,OAAO,SACL,OAAO,aAAa;EAClB,sBAAsB,UAAU,IAAI;EAEpC,SAAS,iBAAiB,OAAqB,UAAU,UAAU;GACjE,MAAM,KAAK,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;EACnE,CAAC;EAED,iBAAiB,QAAQ;EACzB,uBAAuB,UAAU,IAAI;EACrC,yBAAyB,UAAU,IAAI;EACvC,mBAAmB,UAAU,IAAI;EACjC,kBAAkB,UAAU,IAAI;EAChC,oBAAoB,UAAU,IAAI;EAClC,sBAAsB,UAAU,IAAI;EACpC,qBAAqB,UAAU,IAAI;EACnC,oBAAoB,UAAU,IAAI;CACpC,GACA,EAAE,QAAQ,KAAK,QAAQ,KAAK,CAC9B;AACF"}
@@ -0,0 +1,26 @@
1
+ import { resolveUiRoot } from "../ui-root.mjs";
2
+ import { sendFile } from "./ui-assets.mjs";
3
+ import path from "node:path";
4
+
5
+ //#region ../devtools/src/server/openapi-routes.ts
6
+ /**
7
+ * Registers the API docs: `GET /api/openapi.json` returns the document core
8
+ * builds from the registered routes (`deps.getOpenApiDocument`), and
9
+ * `GET /docs` serves the page that renders it with Scalar. The page uses
10
+ * relative URLs, so it works under whatever base path the dashboard is
11
+ * mounted on.
12
+ */
13
+ function registerOpenApiRoutes(server, deps) {
14
+ server.get("/api/openapi.json", async (_request, reply) => {
15
+ if (!deps.getOpenApiDocument) return reply.code(503).header("Cache-Control", "no-store").send({ error: "The OpenAPI document is not available: the host did not provide a generator." });
16
+ reply.header("Cache-Control", "no-store");
17
+ return deps.getOpenApiDocument();
18
+ });
19
+ server.get("/docs", (_request, reply) => {
20
+ sendFile(reply, path.join(resolveUiRoot(), "docs.html"), "text/html; charset=utf-8");
21
+ });
22
+ }
23
+
24
+ //#endregion
25
+ export { registerOpenApiRoutes };
26
+ //# sourceMappingURL=openapi-routes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-routes.mjs","names":[],"sources":["../../../../../../../devtools/src/server/openapi-routes.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { FastifyInstance } from \"fastify\";\nimport { resolveUiRoot } from \"../ui-root\";\nimport type { MountDevtoolsRoutesDeps } from \"./types\";\nimport { sendFile } from \"./ui-assets\";\n\n/**\n * Registers the API docs: `GET /api/openapi.json` returns the document core\n * builds from the registered routes (`deps.getOpenApiDocument`), and\n * `GET /docs` serves the page that renders it with Scalar. The page uses\n * relative URLs, so it works under whatever base path the dashboard is\n * mounted on.\n */\nexport function registerOpenApiRoutes(\n server: FastifyInstance,\n deps: MountDevtoolsRoutesDeps,\n): void {\n server.get(\"/api/openapi.json\", async (_request, reply) => {\n if (!deps.getOpenApiDocument) {\n return reply.code(503).header(\"Cache-Control\", \"no-store\").send({\n error: \"The OpenAPI document is not available: the host did not provide a generator.\",\n });\n }\n\n reply.header(\"Cache-Control\", \"no-store\");\n\n return deps.getOpenApiDocument();\n });\n\n server.get(\"/docs\", (_request, reply) => {\n sendFile(reply, path.join(resolveUiRoot(), \"docs.html\"), \"text/html; charset=utf-8\");\n });\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,sBACd,QACA,MACM;CACN,OAAO,IAAI,qBAAqB,OAAO,UAAU,UAAU;EACzD,IAAI,CAAC,KAAK,oBACR,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,iBAAiB,UAAU,CAAC,CAAC,KAAK,EAC9D,OAAO,+EACT,CAAC;EAGH,MAAM,OAAO,iBAAiB,UAAU;EAExC,OAAO,KAAK,mBAAmB;CACjC,CAAC;CAED,OAAO,IAAI,UAAU,UAAU,UAAU;EACvC,SAAS,OAAO,KAAK,KAAK,cAAc,GAAG,WAAW,GAAG,0BAA0B;CACrF,CAAC;AACH"}
@@ -1,38 +1,52 @@
1
1
  import { resolveUiRoot } from "../ui-root.mjs";
2
- import { readFileSync } from "node:fs";
3
2
  import path from "node:path";
3
+ import { readFileSync } from "node:fs";
4
4
 
5
5
  //#region ../devtools/src/server/ui-assets.ts
6
- const ASSET_CONTENT_TYPES = {
7
- "app.js": "text/javascript; charset=utf-8",
8
- "app.css": "text/css; charset=utf-8"
6
+ const uiFile = (name) => () => path.join(resolveUiRoot(), name);
7
+ /**
8
+ * The only assets that can be requested. The request's asset name is looked
9
+ * up here and never joined into a filesystem path.
10
+ */
11
+ const UI_ASSETS = {
12
+ "app.js": {
13
+ contentType: "text/javascript; charset=utf-8",
14
+ file: uiFile("app.js")
15
+ },
16
+ "app.css": {
17
+ contentType: "text/css; charset=utf-8",
18
+ file: uiFile("app.css")
19
+ },
20
+ "scalar.js": {
21
+ contentType: "text/javascript; charset=utf-8",
22
+ file: uiFile("vendor/scalar/standalone.js")
23
+ }
9
24
  };
25
+ /** Sends `file` with `contentType` and no caching. */
10
26
  function sendFile(reply, file, contentType) {
11
27
  reply.header("Cache-Control", "no-store").header("Content-Type", contentType).send(readFileSync(file));
12
28
  }
13
29
  /**
14
30
  * Serves the static dashboard: `index.html` at the dashboard root and the
15
- * two named assets. The asset name is matched against a fixed whitelist —
16
- * never joined into a filesystem path — so no request can traverse outside
17
- * `ui/`.
31
+ * whitelisted assets (`app.js`, `app.css`, and the vendored Scalar bundle
32
+ * as `scalar.js`). The asset name is matched against a fixed whitelist — never
33
+ * joined into a filesystem path — so no request can traverse outside it.
18
34
  */
19
35
  function registerUiAssets(server) {
20
36
  const serveIndex = (_request, reply) => {
21
- const uiRoot = resolveUiRoot();
22
- sendFile(reply, path.join(uiRoot, "index.html"), "text/html; charset=utf-8");
37
+ sendFile(reply, path.join(resolveUiRoot(), "index.html"), "text/html; charset=utf-8");
23
38
  };
24
39
  server.get("/", serveIndex);
25
40
  server.get("/assets/:name", (request, reply) => {
26
- const contentType = ASSET_CONTENT_TYPES[request.params.name];
27
- if (!contentType) {
41
+ const asset = Object.hasOwn(UI_ASSETS, request.params.name) ? UI_ASSETS[request.params.name] : void 0;
42
+ if (!asset) {
28
43
  reply.code(404).header("Cache-Control", "no-store").send();
29
44
  return;
30
45
  }
31
- const uiRoot = resolveUiRoot();
32
- sendFile(reply, path.join(uiRoot, request.params.name), contentType);
46
+ sendFile(reply, asset.file(), asset.contentType);
33
47
  });
34
48
  }
35
49
 
36
50
  //#endregion
37
- export { registerUiAssets };
51
+ export { registerUiAssets, sendFile };
38
52
  //# sourceMappingURL=ui-assets.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ui-assets.mjs","names":[],"sources":["../../../../../../../devtools/src/server/ui-assets.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { FastifyInstance, FastifyReply } from \"fastify\";\nimport { resolveUiRoot } from \"../ui-root\";\n\nconst ASSET_CONTENT_TYPES: Record<string, string> = {\n \"app.js\": \"text/javascript; charset=utf-8\",\n \"app.css\": \"text/css; charset=utf-8\",\n};\n\nfunction sendFile(reply: FastifyReply, file: string, contentType: string): void {\n reply\n .header(\"Cache-Control\", \"no-store\")\n .header(\"Content-Type\", contentType)\n .send(readFileSync(file));\n}\n\n/**\n * Serves the static dashboard: `index.html` at the dashboard root and the\n * two named assets. The asset name is matched against a fixed whitelist —\n * never joined into a filesystem path — so no request can traverse outside\n * `ui/`.\n */\nexport function registerUiAssets(server: FastifyInstance): void {\n const serveIndex = (_request: unknown, reply: FastifyReply): void => {\n const uiRoot = resolveUiRoot();\n sendFile(reply, path.join(uiRoot, \"index.html\"), \"text/html; charset=utf-8\");\n };\n\n server.get(\"/\", serveIndex);\n\n server.get<{ Params: { name: string } }>(\"/assets/:name\", (request, reply) => {\n const contentType = ASSET_CONTENT_TYPES[request.params.name];\n\n if (!contentType) {\n reply.code(404).header(\"Cache-Control\", \"no-store\").send();\n return;\n }\n\n const uiRoot = resolveUiRoot();\n sendFile(reply, path.join(uiRoot, request.params.name), contentType);\n });\n}\n"],"mappings":";;;;;AAKA,MAAM,sBAA8C;CAClD,UAAU;CACV,WAAW;AACb;AAEA,SAAS,SAAS,OAAqB,MAAc,aAA2B;CAC9E,MACG,OAAO,iBAAiB,UAAU,CAAC,CACnC,OAAO,gBAAgB,WAAW,CAAC,CACnC,KAAK,aAAa,IAAI,CAAC;AAC5B;;;;;;;AAQA,SAAgB,iBAAiB,QAA+B;CAC9D,MAAM,cAAc,UAAmB,UAA8B;EACnE,MAAM,SAAS,cAAc;EAC7B,SAAS,OAAO,KAAK,KAAK,QAAQ,YAAY,GAAG,0BAA0B;CAC7E;CAEA,OAAO,IAAI,KAAK,UAAU;CAE1B,OAAO,IAAkC,kBAAkB,SAAS,UAAU;EAC5E,MAAM,cAAc,oBAAoB,QAAQ,OAAO;EAEvD,IAAI,CAAC,aAAa;GAChB,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,iBAAiB,UAAU,CAAC,CAAC,KAAK;GACzD;EACF;EAEA,MAAM,SAAS,cAAc;EAC7B,SAAS,OAAO,KAAK,KAAK,QAAQ,QAAQ,OAAO,IAAI,GAAG,WAAW;CACrE,CAAC;AACH"}
1
+ {"version":3,"file":"ui-assets.mjs","names":[],"sources":["../../../../../../../devtools/src/server/ui-assets.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { FastifyInstance, FastifyReply } from \"fastify\";\nimport { resolveUiRoot } from \"../ui-root\";\n\n/**\n * Where an asset's bytes come from: a file in this package's `ui/` folder.\n */\ntype UiAsset = {\n contentType: string;\n file: () => string;\n};\n\nconst uiFile = (name: string) => () => path.join(resolveUiRoot(), name);\n\n/**\n * The only assets that can be requested. The request's asset name is looked\n * up here and never joined into a filesystem path.\n */\nconst UI_ASSETS: Record<string, UiAsset> = {\n \"app.js\": { contentType: \"text/javascript; charset=utf-8\", file: uiFile(\"app.js\") },\n \"app.css\": { contentType: \"text/css; charset=utf-8\", file: uiFile(\"app.css\") },\n \"scalar.js\": {\n contentType: \"text/javascript; charset=utf-8\",\n file: uiFile(\"vendor/scalar/standalone.js\"),\n },\n};\n\n/** Sends `file` with `contentType` and no caching. */\nexport function sendFile(reply: FastifyReply, file: string, contentType: string): void {\n reply\n .header(\"Cache-Control\", \"no-store\")\n .header(\"Content-Type\", contentType)\n .send(readFileSync(file));\n}\n\n/**\n * Serves the static dashboard: `index.html` at the dashboard root and the\n * whitelisted assets (`app.js`, `app.css`, and the vendored Scalar bundle\n * as `scalar.js`). The asset name is matched against a fixed whitelist — never\n * joined into a filesystem path — so no request can traverse outside it.\n */\nexport function registerUiAssets(server: FastifyInstance): void {\n const serveIndex = (_request: unknown, reply: FastifyReply): void => {\n sendFile(reply, path.join(resolveUiRoot(), \"index.html\"), \"text/html; charset=utf-8\");\n };\n\n server.get(\"/\", serveIndex);\n\n server.get<{ Params: { name: string } }>(\"/assets/:name\", (request, reply) => {\n const asset = Object.hasOwn(UI_ASSETS, request.params.name)\n ? UI_ASSETS[request.params.name]\n : undefined;\n\n if (!asset) {\n reply.code(404).header(\"Cache-Control\", \"no-store\").send();\n return;\n }\n\n sendFile(reply, asset.file(), asset.contentType);\n });\n}\n"],"mappings":";;;;;AAaA,MAAM,UAAU,eAAuB,KAAK,KAAK,cAAc,GAAG,IAAI;;;;;AAMtE,MAAM,YAAqC;CACzC,UAAU;EAAE,aAAa;EAAkC,MAAM,OAAO,QAAQ;CAAE;CAClF,WAAW;EAAE,aAAa;EAA2B,MAAM,OAAO,SAAS;CAAE;CAC7E,aAAa;EACX,aAAa;EACb,MAAM,OAAO,6BAA6B;CAC5C;AACF;;AAGA,SAAgB,SAAS,OAAqB,MAAc,aAA2B;CACrF,MACG,OAAO,iBAAiB,UAAU,CAAC,CACnC,OAAO,gBAAgB,WAAW,CAAC,CACnC,KAAK,aAAa,IAAI,CAAC;AAC5B;;;;;;;AAQA,SAAgB,iBAAiB,QAA+B;CAC9D,MAAM,cAAc,UAAmB,UAA8B;EACnE,SAAS,OAAO,KAAK,KAAK,cAAc,GAAG,YAAY,GAAG,0BAA0B;CACtF;CAEA,OAAO,IAAI,KAAK,UAAU;CAE1B,OAAO,IAAkC,kBAAkB,SAAS,UAAU;EAC5E,MAAM,QAAQ,OAAO,OAAO,WAAW,QAAQ,OAAO,IAAI,IACtD,UAAU,QAAQ,OAAO,QACzB;EAEJ,IAAI,CAAC,OAAO;GACV,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,iBAAiB,UAAU,CAAC,CAAC,KAAK;GACzD;EACF;EAEA,SAAS,OAAO,MAAM,KAAK,GAAG,MAAM,WAAW;CACjD,CAAC;AACH"}
package/esm/ui-root.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { existsSync, readFileSync } from "node:fs";
3
2
  import path from "node:path";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
 
5
5
  //#region ../devtools/src/ui-root.ts
6
6
  let cachedUiRoot;
package/llms-full.txt CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ---
10
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.'
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, the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar). 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", "browse my API docs in dev", "see the OpenAPI for my routes". 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
12
  ---
13
13
 
14
14
  # `@warlock.js/devtools` — overview
@@ -53,10 +53,20 @@ of it would fail to resolve there.
53
53
  | **Logs** | Log entries per request, filterable by level and module | a logger channel |
54
54
  | **Cache** | hit / miss / set / removed / `invalidated` (tags, keys removed) | cache `cache.on(...)` |
55
55
  | **Routes** | Every HTTP route with its method, path, name and middleware | core router |
56
+ | **API** | The app's API reference: every route with its parameters, request body, responses and auth | core `getDevelopmentOpenApiDocument()` |
56
57
 
57
58
  Work outside a request (boot, scheduler ticks, queue jobs) goes to a separate
58
59
  background list, so it never clutters a request's timeline.
59
60
 
61
+ ## API docs
62
+
63
+ `/__warlock/docs` renders the app's OpenAPI 3.1 document with [Scalar](https://github.com/scalar/scalar); the dashboard's **API** tab shows the same page. The document comes from `GET /__warlock/api/openapi.json`, built in-process by core's `getDevelopmentOpenApiDocument()` from the routes already registered, so it is the same generator as `warlock generate.openapi` and follows your edits with no CLI run. It covers API routes (not pages): paths, parameters, request bodies from Seal validation, responses from each handler's `responseSchema`, `401`/`422` and bearer/cookie security from `authMiddleware()`. See core's `generate-openapi` topic for what is documented and how to declare it.
64
+
65
+ - It answers `503` from `/__warlock/api/openapi.json` when the host provides no generator.
66
+ - Same guard as the rest of devtools: dev-only and loopback-only.
67
+ - Scalar's prebuilt bundle is vendored and served as `assets/scalar.js`, so apps install nothing extra. The page fetches nothing from a CDN, and Scalar's web fonts, telemetry, AI agent and MCP are turned off.
68
+ - To keep a file for Postman or Insomnia, run `warlock generate.openapi` (default `storage/openapi/openapi.json`) instead.
69
+
60
70
  ## Options
61
71
 
62
72
  `warlock dev` uses the defaults: path `/__warlock`, the last 200 requests
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
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.
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, the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar). 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", "browse my API docs in dev", "see the OpenAPI for my routes". 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 CHANGED
@@ -5,10 +5,10 @@
5
5
  "environment": "server"
6
6
  },
7
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"
8
+ "@warlock.js/cache": "5.27.0",
9
+ "@warlock.js/cascade": "5.27.0",
10
+ "@warlock.js/core": "5.27.0",
11
+ "@warlock.js/logger": "5.27.0"
12
12
  },
13
13
  "peerDependenciesMeta": {
14
14
  "@warlock.js/cache": {
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "author": "hassanzohdy",
35
35
  "license": "MIT",
36
- "version": "5.25.0",
36
+ "version": "5.27.0",
37
37
  "main": "./cjs/index.cjs",
38
38
  "module": "./esm/index.mjs",
39
39
  "types": "./esm/index.d.mts",
@@ -1,6 +1,6 @@
1
1
  ---
2
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.'
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, the route map and an API docs page (`/__warlock/docs`, the OpenAPI 3.1 document of the app rendered with Scalar). 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", "browse my API docs in dev", "see the OpenAPI for my routes". 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
4
  ---
5
5
 
6
6
  # `@warlock.js/devtools` — overview
@@ -45,10 +45,20 @@ of it would fail to resolve there.
45
45
  | **Logs** | Log entries per request, filterable by level and module | a logger channel |
46
46
  | **Cache** | hit / miss / set / removed / `invalidated` (tags, keys removed) | cache `cache.on(...)` |
47
47
  | **Routes** | Every HTTP route with its method, path, name and middleware | core router |
48
+ | **API** | The app's API reference: every route with its parameters, request body, responses and auth | core `getDevelopmentOpenApiDocument()` |
48
49
 
49
50
  Work outside a request (boot, scheduler ticks, queue jobs) goes to a separate
50
51
  background list, so it never clutters a request's timeline.
51
52
 
53
+ ## API docs
54
+
55
+ `/__warlock/docs` renders the app's OpenAPI 3.1 document with [Scalar](https://github.com/scalar/scalar); the dashboard's **API** tab shows the same page. The document comes from `GET /__warlock/api/openapi.json`, built in-process by core's `getDevelopmentOpenApiDocument()` from the routes already registered, so it is the same generator as `warlock generate.openapi` and follows your edits with no CLI run. It covers API routes (not pages): paths, parameters, request bodies from Seal validation, responses from each handler's `responseSchema`, `401`/`422` and bearer/cookie security from `authMiddleware()`. See core's `generate-openapi` topic for what is documented and how to declare it.
56
+
57
+ - It answers `503` from `/__warlock/api/openapi.json` when the host provides no generator.
58
+ - Same guard as the rest of devtools: dev-only and loopback-only.
59
+ - Scalar's prebuilt bundle is vendored and served as `assets/scalar.js`, so apps install nothing extra. The page fetches nothing from a CDN, and Scalar's web fonts, telemetry, AI agent and MCP are turned off.
60
+ - To keep a file for Postman or Insomnia, run `warlock generate.openapi` (default `storage/openapi/openapi.json`) instead.
61
+
52
62
  ## Options
53
63
 
54
64
  `warlock dev` uses the defaults: path `/__warlock`, the last 200 requests
package/ui/app.css CHANGED
@@ -330,6 +330,14 @@ details summary {
330
330
  .log-success {
331
331
  color: var(--good);
332
332
  }
333
+ .docs-frame {
334
+ display: block;
335
+ width: 100%;
336
+ height: calc(100vh - 110px);
337
+ border: 1px solid var(--line);
338
+ border-radius: 7px;
339
+ background: var(--surface);
340
+ }
333
341
  @media (max-width: 700px) {
334
342
  .topbar {
335
343
  gap: 10px;
package/ui/app.js CHANGED
@@ -467,6 +467,10 @@ async function routesView() {
467
467
  return section;
468
468
  }
469
469
 
470
+ function apiView() {
471
+ return el("iframe", { class: "docs-frame", src: "docs", title: "API reference" });
472
+ }
473
+
470
474
  async function render() {
471
475
  try {
472
476
  const view =
@@ -478,7 +482,9 @@ async function render() {
478
482
  ? await mailView()
479
483
  : state.tab === "logs"
480
484
  ? await logsView()
481
- : await routesView();
485
+ : state.tab === "routes"
486
+ ? await routesView()
487
+ : apiView();
482
488
  app.replaceChildren(view);
483
489
  } catch (error) {
484
490
  app.replaceChildren(errorView(error));
package/ui/docs.html ADDED
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>API reference</title>
7
+ </head>
8
+ <body>
9
+ <!-- Scalar reads its settings from this element. Every URL is relative to /docs, so the page works under any dashboard base path. Fonts, telemetry, the AI agent and MCP are off: this is a loopback dev tool and must not call out. -->
10
+ <script
11
+ id="api-reference"
12
+ data-url="api/openapi.json"
13
+ data-configuration='{"withDefaultFonts":false,"telemetry":false,"showDeveloperTools":"never","agent":{"disabled":true},"mcp":{"disabled":true}}'
14
+ ></script>
15
+ <script src="assets/scalar.js"></script>
16
+ </body>
17
+ </html>
package/ui/index.html CHANGED
@@ -15,6 +15,7 @@
15
15
  <button class="tab" data-tab="mail">Mail</button>
16
16
  <button class="tab" data-tab="logs">Logs</button>
17
17
  <button class="tab" data-tab="routes">Routes</button>
18
+ <button class="tab" data-tab="api">API</button>
18
19
  </nav>
19
20
  <button id="clear" class="clear" type="button">Clear</button>
20
21
  </header>
@@ -0,0 +1,34 @@
1
+ # Scalar API Reference (vendored)
2
+
3
+ `standalone.js` is the unmodified prebuilt browser bundle of
4
+ [`@scalar/api-reference`](https://github.com/scalar/scalar) **1.72.2**
5
+ (`dist/browser/standalone.js` in the npm package), vendored so apps that add
6
+ `@warlock.js/devtools` install nothing extra. It is served only by the
7
+ devtools API docs page (`/__warlock/docs`), in development, to loopback clients.
8
+
9
+ To update: `npm pack @scalar/api-reference@<version>`, extract
10
+ `package/dist/browser/standalone.js` over this file, and update the version above.
11
+
12
+ ## License
13
+
14
+ MIT License
15
+
16
+ Copyright (c) Scalar (https://github.com/scalar)
17
+
18
+ Permission is hereby granted, free of charge, to any person obtaining a copy
19
+ of this software and associated documentation files (the "Software"), to deal
20
+ in the Software without restriction, including without limitation the rights
21
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22
+ copies of the Software, and to permit persons to whom the Software is
23
+ furnished to do so, subject to the following conditions:
24
+
25
+ The above copyright notice and this permission notice shall be included in all
26
+ copies or substantial portions of the Software.
27
+
28
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34
+ SOFTWARE.