@expressots/adapter-express 4.0.0-preview.1 → 4.0.0-preview.3

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 (36) hide show
  1. package/LICENSE.md +21 -21
  2. package/README.md +61 -61
  3. package/lib/CHANGELOG.md +10 -5
  4. package/lib/README.md +61 -61
  5. package/lib/cjs/adapter-express/application-express.js +401 -45
  6. package/lib/cjs/adapter-express/express-utils/decorators.js +44 -15
  7. package/lib/cjs/adapter-express/express-utils/inversify-express-server.js +20 -4
  8. package/lib/cjs/adapter-express/express-utils/path-pattern-compat.js +129 -0
  9. package/lib/cjs/adapter-express/express-utils/route-constraints.js +12 -3
  10. package/lib/cjs/adapter-express/micro-api/application-express-micro.js +5 -9
  11. package/lib/cjs/adapter-express/micro-api/micro.js +96 -41
  12. package/lib/cjs/adapter-express/studio/index.js +2 -1
  13. package/lib/cjs/adapter-express/studio/studio-integration.js +64 -11
  14. package/lib/cjs/types/adapter-express/application-express.d.ts +51 -9
  15. package/lib/cjs/types/adapter-express/express-utils/path-pattern-compat.d.ts +66 -0
  16. package/lib/cjs/types/adapter-express/express-utils/route-constraints.d.ts +12 -3
  17. package/lib/cjs/types/adapter-express/micro-api/micro.d.ts +19 -2
  18. package/lib/cjs/types/adapter-express/studio/index.d.ts +1 -1
  19. package/lib/cjs/types/adapter-express/studio/studio-integration.d.ts +78 -0
  20. package/lib/esm/adapter-express/application-express.js +402 -46
  21. package/lib/esm/adapter-express/express-utils/decorators.js +44 -15
  22. package/lib/esm/adapter-express/express-utils/inversify-express-server.js +20 -4
  23. package/lib/esm/adapter-express/express-utils/path-pattern-compat.js +125 -0
  24. package/lib/esm/adapter-express/express-utils/route-constraints.js +12 -3
  25. package/lib/esm/adapter-express/micro-api/application-express-micro.js +6 -10
  26. package/lib/esm/adapter-express/micro-api/micro.js +97 -42
  27. package/lib/esm/adapter-express/studio/index.js +1 -1
  28. package/lib/esm/adapter-express/studio/studio-integration.js +63 -11
  29. package/lib/esm/types/adapter-express/application-express.d.ts +51 -9
  30. package/lib/esm/types/adapter-express/express-utils/path-pattern-compat.d.ts +66 -0
  31. package/lib/esm/types/adapter-express/express-utils/route-constraints.d.ts +12 -3
  32. package/lib/esm/types/adapter-express/micro-api/micro.d.ts +19 -2
  33. package/lib/esm/types/adapter-express/studio/index.d.ts +1 -1
  34. package/lib/esm/types/adapter-express/studio/studio-integration.d.ts +78 -0
  35. package/lib/package.json +24 -10
  36. package/package.json +25 -11
@@ -32,27 +32,56 @@ var __importStar = (this && this.__importStar) || function (mod) {
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
33
  exports.initializeStudio = initializeStudio;
34
34
  exports.reportStudioRuntimeInfo = reportStudioRuntimeInfo;
35
+ exports.rescanStudioRoutes = rescanStudioRoutes;
35
36
  exports.stopStudio = stopStudio;
36
37
  exports.isStudioEnabled = isStudioEnabled;
37
38
  exports.getStudioAgent = getStudioAgent;
39
+ const core_1 = require("@expressots/core");
40
+ // Lazy logger accessor so `new Logger()` only fires the first time we
41
+ // actually emit a Studio message. Routing through Logger means the
42
+ // framework's log-level configuration (e.g. `LOG_LEVEL=WARN`) silences
43
+ // the "listening" line as expected, instead of `console.log` always
44
+ // printing it. Lazy construction also keeps consumers that mock
45
+ // `@expressots/core` (test environments) from blowing up at module
46
+ // load when their Logger mock omits `.withContext`.
47
+ let _studioLogger = null;
48
+ function logger() {
49
+ if (!_studioLogger) {
50
+ _studioLogger = new core_1.Logger().withContext("studio");
51
+ }
52
+ return _studioLogger;
53
+ }
38
54
  let studioAgent = null;
39
55
  let studioEnabled = false;
40
56
  /**
41
- * Check if @expressots/studio-agent is installed
57
+ * Check if `@expressots/studio-agent` is installed and importable from the
58
+ * current process. Uses a dynamic `import()` rather than `require.resolve`
59
+ * because the latter is unavailable in pure-ESM consumers — adapter-express
60
+ * is published as a dual CJS/ESM build and this helper is exercised by both
61
+ * targets.
62
+ *
63
+ * The result is cached on first hit (success) so we avoid paying for the
64
+ * import twice. On failure we always retry, since the user may install the
65
+ * package mid-session in `expressots dev` workflows.
42
66
  */
67
+ let _studioAgentModule = null;
43
68
  async function isStudioAgentInstalled() {
44
69
  const debug = process.env.EXPRESSOTS_STUDIO_DEBUG === "true";
70
+ if (_studioAgentModule !== null)
71
+ return true;
45
72
  try {
46
- // Try to resolve the module first (works for both CJS and ESM)
47
- const resolved = require.resolve("@expressots/studio-agent");
73
+ // Dynamic import works for both CJS (returns module.exports) and ESM
74
+ // (returns the ESM namespace). If the package isn't installed, Node
75
+ // throws ERR_MODULE_NOT_FOUND / MODULE_NOT_FOUND, which we treat as
76
+ // "agent not present" — adapter-express continues without Studio.
77
+ _studioAgentModule = await Promise.resolve().then(() => __importStar(require("@expressots/studio-agent")));
48
78
  if (debug)
49
- console.log("[Studio] Resolved studio-agent at:", resolved);
79
+ console.log("[Studio] Loaded studio-agent successfully");
50
80
  return true;
51
81
  }
52
82
  catch (error) {
53
83
  if (debug)
54
- console.log("[Studio] Cannot resolve studio-agent:", error instanceof Error ? error.message : error);
55
- // Module not installed
84
+ console.log("[Studio] Cannot load studio-agent:", error instanceof Error ? error.message : error);
56
85
  return false;
57
86
  }
58
87
  }
@@ -108,9 +137,9 @@ async function initializeStudio(app, config = {}, appContainer) {
108
137
  const studioAgentModuleAny = studioAgentModule;
109
138
  const StudioAgent = studioAgentModuleAny.StudioAgent || studioAgentModuleAny.default?.StudioAgent;
110
139
  if (!StudioAgent) {
111
- console.warn("⚠️ Studio Agent module found but StudioAgent class not exported");
140
+ logger().warn("Studio Agent module found but StudioAgent class not exported");
112
141
  if (debug)
113
- console.log("[Studio] Module contents:", studioAgentModule);
142
+ logger().debug(`Module contents: ${Object.keys(studioAgentModule).join(", ")}`);
114
143
  return false;
115
144
  }
116
145
  if (debug)
@@ -133,7 +162,7 @@ async function initializeStudio(app, config = {}, appContainer) {
133
162
  // Start the agent (this also scans routes)
134
163
  await studioAgent.start();
135
164
  studioEnabled = true;
136
- console.log(`[ExpressoTS] Studio Agent listening on ws://localhost:${agentOptions.port}`);
165
+ logger().info(`Studio Agent listening on ws://localhost:${agentOptions.port}`);
137
166
  return true;
138
167
  }
139
168
  catch (error) {
@@ -161,12 +190,12 @@ async function initializeStudio(app, config = {}, appContainer) {
161
190
  // Friendlier message for the most common failure mode: hot-reload
162
191
  // race left the port in TIME_WAIT.
163
192
  if (errorCode === "EADDRINUSE") {
164
- console.warn(`⚠️ Studio Agent could not bind its WebSocket port ` +
193
+ logger().warn(`Studio Agent could not bind its WebSocket port ` +
165
194
  `(${errorMessage}). The host app will continue without Studio. ` +
166
195
  `If this happened during hot-reload, the next restart should recover.`);
167
196
  return false;
168
197
  }
169
- console.warn("⚠️ Failed to initialize Studio Agent:", errorMessage);
198
+ logger().warn(`Failed to initialize Studio Agent: ${errorMessage}`);
170
199
  return false;
171
200
  }
172
201
  }
@@ -190,6 +219,30 @@ function reportStudioRuntimeInfo(patch) {
190
219
  // Best-effort — never break the host on a status-page update.
191
220
  }
192
221
  }
222
+ /**
223
+ * Re-trigger the Studio Agent's route discovery. Used by the host
224
+ * after `app.listen()` so that the agent's runtime route scanner sees
225
+ * the fully-populated Express `_router` stack (controllers are bound
226
+ * by `InversifyExpressServer.build()` AFTER `initializeStudio()` runs,
227
+ * so the agent's first scan only catches static-source routes).
228
+ *
229
+ * No-ops when:
230
+ * - Studio isn't enabled, or
231
+ * - the installed agent is too old to expose `scanRoutes()`.
232
+ */
233
+ async function rescanStudioRoutes() {
234
+ if (!studioAgent)
235
+ return;
236
+ if (typeof studioAgent.scanRoutes !== "function")
237
+ return;
238
+ try {
239
+ await studioAgent.scanRoutes();
240
+ }
241
+ catch (error) {
242
+ // Best-effort — never break the host on a Studio rescan.
243
+ logger().warn(`Studio route rescan failed: ${error instanceof Error ? error.message : String(error)}`);
244
+ }
245
+ }
193
246
  /**
194
247
  * Stop the Studio Agent
195
248
  */
@@ -1,5 +1,5 @@
1
1
  import { Server as HTTPServer } from "http";
2
- import { AppContainer, IConsoleMessage, IMiddleware, ProviderManager, BannerConfig } from "@expressots/core";
2
+ import { AppContainer, IConsoleMessage, IMiddleware, Logger, ProviderManager, BannerConfig } from "@expressots/core";
3
3
  import { IWebServerPublic, RenderEngine, Server } from "@expressots/shared";
4
4
  import { interfaces } from "@expressots/core";
5
5
  /**
@@ -14,7 +14,7 @@ import { interfaces } from "@expressots/core";
14
14
  * @method isDevelopment - Verifies if the current environment is development.
15
15
  */
16
16
  export declare class AppExpress implements Server.IWebServer {
17
- private logger;
17
+ protected logger: Logger;
18
18
  private console;
19
19
  private app;
20
20
  private serverInstance;
@@ -51,9 +51,7 @@ export declare class AppExpress implements Server.IWebServer {
51
51
  private static originalStderrWrite;
52
52
  private static logBuffer;
53
53
  private static isBuffering;
54
- private static bufferingInitialized;
55
54
  private static originalGlobalConsole;
56
- private static initBuffering;
57
55
  /**
58
56
  * Disable log buffering. Called by micro() to restore normal console output
59
57
  * since micro API doesn't use the banner system.
@@ -61,11 +59,17 @@ export declare class AppExpress implements Server.IWebServer {
61
59
  */
62
60
  static disableBuffering(): void;
63
61
  /**
64
- * Start buffering all console output.
65
- * This captures both console.log and direct process.stdout.write calls.
66
- * @private
62
+ * Start buffering all console output for the banner-first display flow.
63
+ * Captures both `console.*` and direct `process.stdout.write` / `process.stderr.write`
64
+ * calls so they can be flushed in the correct order after the banner displays.
65
+ *
66
+ * Idempotent: calling this multiple times is safe.
67
+ *
68
+ * @public API — called by `bootstrap()` so logs emitted during container
69
+ * setup are captured before the `AppExpress` instance exists. Also called
70
+ * automatically inside the constructor as a safety net.
67
71
  */
68
- private static startLogBuffering;
72
+ static startLogBuffering(): void;
69
73
  /**
70
74
  * Stop buffering but keep the buffered logs for later flushing.
71
75
  * This restores normal console/stdout output.
@@ -390,9 +394,47 @@ export declare class AppExpress implements Server.IWebServer {
390
394
  * harvested (keeps the WS payload small).
391
395
  */
392
396
  private collectStudioRuntimeItems;
397
+ /**
398
+ * Harvest controller- and route-scoped middleware bindings from
399
+ * Reflect metadata. Each entry describes a single edge the Studio
400
+ * architecture map should draw, e.g. "AuthMiddleware → UserController
401
+ * (route POST /users/:id)".
402
+ *
403
+ * The middleware values stored on `ControllerMetadata.middleware` are
404
+ * a polymorphic union (class, function, registered name, conditional
405
+ * config, …). We normalise each to a display name; entries we can't
406
+ * name (anonymous arrow functions, plain object configs without a
407
+ * `name` field) are omitted. The agent's static scan picks up the
408
+ * remaining named cases via decorator parsing — between the two
409
+ * sources Studio sees a complete graph for the common patterns.
410
+ */
411
+ private collectMiddlewareBindings;
412
+ /**
413
+ * Combine a controller's base path with a route path, normalising
414
+ * leading/trailing slashes. Mirrors the simpler logic Studio uses to
415
+ * build `RouteInfo.path` so the bindings line up with route entries.
416
+ */
417
+ private joinRoutePath;
418
+ /**
419
+ * Collect the ordered middleware pipeline from the Middleware service
420
+ * for forwarding to Studio. Uses feature-detection so older core
421
+ * versions that lack `getPipelineInfo()` won't break.
422
+ */
423
+ private collectMiddlewarePipelineItems;
424
+ /**
425
+ * Build the middleware preset info snapshot for Studio. Reads the
426
+ * last applied preset from the Middleware service and transforms it
427
+ * into the shape Studio expects.
428
+ */
429
+ private collectMiddlewarePresetInfo;
393
430
  /**
394
431
  * Display middleware startup logs after the banner.
395
- * This makes startup logging transparent to the user - no need for manual code in postServerInitialization().
432
+ *
433
+ * Warnings (e.g. missing optional packages like `helmet`) are always surfaced
434
+ * so the developer can act on them. Informational entries (e.g. "Security
435
+ * configured", "Applied preset: api") are demoted to `debug` since the
436
+ * dashboard already shows the active middleware count; set `LOG_LEVEL=DEBUG`
437
+ * to see the full breakdown.
396
438
  * @private
397
439
  */
398
440
  private displayMiddlewareStartupLogs;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Express 5 / path-to-regexp v8 compatibility for the `:name(regex)`
3
+ * inline-constraint syntax.
4
+ *
5
+ * `path-to-regexp` v8 (the parser Express 5 ships with) removed the
6
+ * inline regex form entirely — `/users/:id(\\d+)` now throws
7
+ *
8
+ * Unexpected ( at index 10: /users/:id(\\d+)
9
+ *
10
+ * That breaks two things we ship as public API:
11
+ *
12
+ * 1. The {@link Patterns} / {@link pattern} helpers in
13
+ * `route-constraints.ts`, which were introduced specifically to
14
+ * encourage that pattern.
15
+ * 2. Hand-written controller routes upgraded from v3, where users
16
+ * relied on Express 4 inline regex.
17
+ *
18
+ * Rather than break those at the surface of preview-3, we keep the
19
+ * authoring-time syntax and translate it at decorator time:
20
+ *
21
+ * - {@link splitPathConstraints} parses the path into a
22
+ * plain-`:name`-only form plus a list of `(name, regex)` pairs.
23
+ * - {@link createPathConstraintMiddleware} returns a middleware that
24
+ * runs at request time and 404s when any captured `req.params[name]`
25
+ * fails to match its constraint.
26
+ *
27
+ * The middleware emits an HTTP 404 (not 400) so the behaviour matches
28
+ * Express 4's "no route matched" semantics — under v6 of path-to-regexp,
29
+ * a non-matching `:id(\\d+)` simply meant the route wasn't selected and
30
+ * the request fell through to the framework's NotFound handler.
31
+ */
32
+ import type { RequestHandler } from "express";
33
+ export interface PathConstraint {
34
+ /** The `:name` placeholder, without the leading colon. */
35
+ paramName: string;
36
+ /** Compiled regex. Anchored with `^...$` to match the whole segment. */
37
+ regex: RegExp;
38
+ /** The original raw regex text, for diagnostics. */
39
+ rawPattern: string;
40
+ }
41
+ export interface SplitPath {
42
+ /** Path string ready to hand to Express 5 / path-to-regexp v8. */
43
+ path: string;
44
+ /** Param-level regex constraints, in path declaration order. */
45
+ constraints: Array<PathConstraint>;
46
+ }
47
+ /**
48
+ * Split `:name(regex)` segments out of an Express-style route path.
49
+ *
50
+ * The walker honours balanced parens inside the regex (e.g.
51
+ * `(\\d{4})` or `((a|b)+)`), which is more forgiving than a naive
52
+ * single-pass regex match would be. Returns the original path and an
53
+ * empty constraints list when no inline patterns are found, so this is
54
+ * a no-op for the common case.
55
+ */
56
+ export declare function splitPathConstraints(path: string): SplitPath;
57
+ /**
58
+ * Build a middleware that enforces the given param-level regex
59
+ * constraints on `req.params`. Returns `null` when the list is empty
60
+ * (so callers can avoid wiring an unnecessary middleware).
61
+ *
62
+ * When a constraint fails, the middleware delegates to `next()` without
63
+ * a value; the framework's NotFound handler then converts that into a
64
+ * 404 — same observable behaviour as Express 4's "no route matched".
65
+ */
66
+ export declare function createPathConstraintMiddleware(constraints: Array<PathConstraint>): RequestHandler | null;
@@ -1,6 +1,15 @@
1
1
  /**
2
2
  * Route parameter patterns for common use cases.
3
- * These are Express regex patterns that can be used in route paths.
3
+ *
4
+ * Express 5 / path-to-regexp v8 dropped the inline-regex form
5
+ * (`:id(\\d+)`), so the framework no longer hands these patterns to
6
+ * the underlying matcher verbatim. Instead, the HTTP-method decorators
7
+ * (`@Get`, `@Post`, …) parse the constraint out of the path at decorator
8
+ * time, register the route under a plain `:id` placeholder, and inject
9
+ * a small validator middleware that 404s when the captured value
10
+ * doesn't match. The user-facing semantics are unchanged: a path that
11
+ * uses `Patterns.NUMERIC_ID` still rejects `/users/abc` and only
12
+ * dispatches the handler for matches like `/users/123`.
4
13
  *
5
14
  * @example
6
15
  * ```typescript
@@ -8,12 +17,12 @@
8
17
  *
9
18
  * @Get(`/users/${pattern("id", Patterns.NUMERIC_ID)}`)
10
19
  * getUserById(@param("id") id: number) {
11
- * // Only matches numeric IDs like /users/123
20
+ * // Only dispatches for numeric IDs like /users/123
12
21
  * }
13
22
  *
14
23
  * @Get(`/documents/${pattern("uuid", Patterns.UUID)}`)
15
24
  * getDocument(@param("uuid") uuid: string) {
16
- * // Only matches valid UUIDs
25
+ * // Only dispatches for valid UUIDs
17
26
  * }
18
27
  * ```
19
28
  *
@@ -12,6 +12,19 @@ export interface MicroConfig {
12
12
  globalPrefix?: string;
13
13
  /** Show startup banner (default: true) */
14
14
  showBanner?: boolean;
15
+ /** Application environment. Auto-detected from NODE_ENV if not provided. */
16
+ environment?: string;
17
+ /** Studio Agent configuration. Auto-enabled in development when the package is installed. */
18
+ studio?: {
19
+ /** Explicitly enable/disable Studio (default: auto-detect in development) */
20
+ enabled?: boolean;
21
+ /** WebSocket port for the Studio Agent (default: 3334) */
22
+ port?: number;
23
+ /** Path to the SQLite database (default: ".studio/studio.db") */
24
+ dbPath?: string;
25
+ /** Service name shown in Studio (default: "expressots-micro") */
26
+ serviceName?: string;
27
+ };
15
28
  }
16
29
  /**
17
30
  * Route handler that can return a value or use res directly
@@ -43,10 +56,14 @@ export interface MicroApp {
43
56
  setErrorHandler(handler: express.ErrorRequestHandler): this;
44
57
  /** Start listening for requests */
45
58
  listen(port: number | string, appInfo?: IConsoleMessage): Promise<void>;
46
- /** Get the underlying HTTP server (available after listen) */
47
- getHttpServer(): Server;
59
+ /** Get the underlying HTTP server (null before listen resolves) */
60
+ getHttpServer(): Server | null;
48
61
  /** Get the Express app instance (for advanced use) */
49
62
  getApp(): express.Application;
63
+ /** Configure Studio integration (call before listen) */
64
+ setStudio(config: NonNullable<MicroConfig["studio"]>): this;
65
+ /** Check if Studio Agent is currently enabled */
66
+ isStudioEnabled(): boolean;
50
67
  }
51
68
  /**
52
69
  * Create a new micro API instance
@@ -1 +1 @@
1
- export { initializeStudio, stopStudio, isStudioEnabled, getStudioAgent, reportStudioRuntimeInfo, } from "./studio-integration.js";
1
+ export { initializeStudio, stopStudio, isStudioEnabled, getStudioAgent, reportStudioRuntimeInfo, rescanStudioRoutes, } from "./studio-integration.js";
@@ -23,6 +23,7 @@ interface StudioAgentInstance {
23
23
  providerCount?: number;
24
24
  middlewareCount?: number;
25
25
  runtimeItems?: StudioRuntimeItems;
26
+ middlewarePreset?: StudioMiddlewarePresetInfo;
26
27
  }): void;
27
28
  }
28
29
  /**
@@ -45,6 +46,70 @@ export interface StudioRuntimeItems {
45
46
  priority?: number;
46
47
  source?: string;
47
48
  }>;
49
+ middleware?: Array<{
50
+ name: string;
51
+ category: string;
52
+ type: "built-in" | "custom";
53
+ order: number;
54
+ path?: string;
55
+ }>;
56
+ /**
57
+ * Controller- and route-scoped middleware bindings, harvested from
58
+ * `ControllerMetadata.middleware` Reflect entries after
59
+ * `app.listen()`. Used by the agent to draw scope-aware
60
+ * "middleware → controller / route" edges on the architecture map.
61
+ *
62
+ * Mirrors `MiddlewareBinding` in `@expressots/studio-agent`.
63
+ */
64
+ middlewareBindings?: Array<{
65
+ middlewareName: string;
66
+ scope: "controller" | "route";
67
+ controllerName: string;
68
+ controllerMethod?: string;
69
+ httpMethod?: string;
70
+ routePath?: string;
71
+ }>;
72
+ }
73
+ /**
74
+ * Middleware preset info forwarded to the Studio Agent. Mirrors
75
+ * `MiddlewarePresetInfo` in `@expressots/studio-agent` so the adapter
76
+ * doesn't need to import from the studio package.
77
+ */
78
+ export interface StudioMiddlewarePresetInfo {
79
+ name: string;
80
+ hasOverrides: boolean;
81
+ parse?: {
82
+ json?: {
83
+ limit?: string;
84
+ };
85
+ urlencoded?: {
86
+ limit?: string;
87
+ extended?: boolean;
88
+ };
89
+ cookies?: boolean;
90
+ };
91
+ security?: {
92
+ tier?: string;
93
+ helmet?: boolean;
94
+ cors?: {
95
+ origin?: boolean | string;
96
+ credentials?: boolean;
97
+ methods?: Array<string>;
98
+ allowedHeaders?: Array<string>;
99
+ };
100
+ rateLimit?: {
101
+ windowMs?: number;
102
+ max?: number;
103
+ } | false;
104
+ };
105
+ compress?: {
106
+ enabled: boolean;
107
+ level?: number;
108
+ };
109
+ logger?: {
110
+ enabled: boolean;
111
+ implementation?: string;
112
+ };
48
113
  }
49
114
  interface StudioIntegrationConfig {
50
115
  enabled?: boolean;
@@ -76,7 +141,20 @@ export declare function reportStudioRuntimeInfo(patch: {
76
141
  providerCount?: number;
77
142
  middlewareCount?: number;
78
143
  runtimeItems?: StudioRuntimeItems;
144
+ middlewarePreset?: StudioMiddlewarePresetInfo;
79
145
  }): void;
146
+ /**
147
+ * Re-trigger the Studio Agent's route discovery. Used by the host
148
+ * after `app.listen()` so that the agent's runtime route scanner sees
149
+ * the fully-populated Express `_router` stack (controllers are bound
150
+ * by `InversifyExpressServer.build()` AFTER `initializeStudio()` runs,
151
+ * so the agent's first scan only catches static-source routes).
152
+ *
153
+ * No-ops when:
154
+ * - Studio isn't enabled, or
155
+ * - the installed agent is too old to expose `scanRoutes()`.
156
+ */
157
+ export declare function rescanStudioRoutes(): Promise<void>;
80
158
  /**
81
159
  * Stop the Studio Agent
82
160
  */