@dudousxd/nestjs-durable-dashboard 0.56.0 → 0.58.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.
@@ -32,26 +32,41 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  // src/server/index.ts
33
33
  var index_exports = {};
34
34
  __export(index_exports, {
35
+ AVIARY_CONSOLES_SLOT: () => AVIARY_CONSOLES_SLOT,
35
36
  CONSOLE_AUTHORIZE_DECIDE: () => CONSOLE_AUTHORIZE_DECIDE,
36
37
  CONSOLE_AUTHORIZE_MISSING_WARNING: () => CONSOLE_AUTHORIZE_MISSING_WARNING,
37
38
  CONSOLE_GATE: () => CONSOLE_GATE,
38
39
  CONSOLE_PAGE_METADATA: () => CONSOLE_PAGE_METADATA,
40
+ CONSOLE_PROBE_TIMEOUT_MS: () => CONSOLE_PROBE_TIMEOUT_MS,
41
+ CONSOLE_PUBLIC_METADATA: () => CONSOLE_PUBLIC_METADATA,
39
42
  ConsoleAccessDenied: () => ConsoleAccessDenied,
40
43
  ConsoleAuthorizeFilter: () => ConsoleAuthorizeFilter,
41
44
  ConsoleAuthorizeGuard: () => ConsoleAuthorizeGuard,
42
45
  ConsolePage: () => ConsolePage,
46
+ ConsolePublic: () => ConsolePublic,
43
47
  DASHBOARD_API_PATH: () => DASHBOARD_API_PATH,
44
48
  DASHBOARD_BASE_PATH: () => DASHBOARD_BASE_PATH,
49
+ DEV_TOOLS_SLOT: () => DEV_TOOLS_SLOT,
50
+ DURABLE_CONSOLE_ID: () => DURABLE_CONSOLE_ID,
51
+ DURABLE_DASHBOARD_CONFIG_ID: () => DURABLE_DASHBOARD_CONFIG_ID,
45
52
  DashboardService: () => DashboardService,
46
53
  DurableApiController: () => DurableApiController,
47
54
  DurableApiControllerBase: () => DurableApiControllerBase,
48
55
  DurableApiModule: () => DurableApiModule,
56
+ DurableConsoleRegistration: () => DurableConsoleRegistration,
57
+ DurableConsolesController: () => DurableConsolesController,
49
58
  DurableDashboardModule: () => DurableDashboardModule,
59
+ DurableDevToolsRegistration: () => DurableDevToolsRegistration,
50
60
  DurableUiController: () => DurableUiController,
51
61
  DurableUiControllerBase: () => DurableUiControllerBase,
62
+ MAX_RUN_IDS: () => MAX_RUN_IDS,
52
63
  UseConsoleAuthorize: () => UseConsoleAuthorize,
64
+ anyAuthorize: () => anyAuthorize,
53
65
  authorizeByToken: () => authorizeByToken,
66
+ durableDevTools: () => durableDevTools,
67
+ listVisibleConsoles: () => listVisibleConsoles,
54
68
  normalizeConsoleResult: () => normalizeConsoleResult,
69
+ registerAviaryConsole: () => registerAviaryConsole,
55
70
  resolveConsoleAuthorize: () => resolveConsoleAuthorize
56
71
  });
57
72
  module.exports = __toCommonJS(index_exports);
@@ -91,9 +106,12 @@ var CONSOLE_GATE = Symbol.for("@dudousxd/aviary:console-gate");
91
106
  var PACKAGE = "@dudousxd/nestjs-durable-dashboard";
92
107
  var MODULE = "DurableDashboardModule";
93
108
  var CONSOLE_AUTHORIZE_MISSING_WARNING = `[${PACKAGE}] console has no \`authorize\` configured: it is open outside production and denies every request in production. Set \`authorize\` in ${MODULE}.forRoot().`;
94
- var allow = /* @__PURE__ */ __name(() => ({
109
+ var allow = /* @__PURE__ */ __name((caller) => caller !== void 0 && caller !== "" ? {
110
+ allowed: true,
111
+ caller
112
+ } : {
95
113
  allowed: true
96
- }), "allow");
114
+ }, "allow");
97
115
  var forbidden = /* @__PURE__ */ __name(() => ({
98
116
  allowed: false,
99
117
  reason: "forbidden"
@@ -133,7 +151,9 @@ function normalizeConsoleResult(result) {
133
151
  redirectTo: shape.redirectTo
134
152
  };
135
153
  }
136
- if (shape.allowed === true) return allow();
154
+ if (shape.allowed === true) {
155
+ return allow(typeof shape.caller === "string" ? shape.caller : void 0);
156
+ }
137
157
  return shape.reason === "unauthenticated" ? unauthenticated() : forbidden();
138
158
  }
139
159
  __name(normalizeConsoleResult, "normalizeConsoleResult");
@@ -216,11 +236,11 @@ function classDecider(moduleRef) {
216
236
  }
217
237
  __name(classDecider, "classDecider");
218
238
  async function resolveConsoleAuthorize(authorize, moduleRef, options = {}) {
219
- const logger = options.logger ?? new import_common.Logger(MODULE);
239
+ const logger3 = options.logger ?? new import_common.Logger(MODULE);
220
240
  if (authorize === void 0) {
221
- if (options.warnIfMissing !== false) logger.warn(CONSOLE_AUTHORIZE_MISSING_WARNING);
241
+ if (options.warnIfMissing !== false) logger3.warn(CONSOLE_AUTHORIZE_MISSING_WARNING);
222
242
  const production = isProduction();
223
- return async () => production ? forbidden() : allow();
243
+ return async () => production ? forbidden() : allow("dev-open");
224
244
  }
225
245
  let decide;
226
246
  if (typeof authorize === "string") {
@@ -247,7 +267,7 @@ async function resolveConsoleAuthorize(authorize, moduleRef, options = {}) {
247
267
  try {
248
268
  return await decide(context);
249
269
  } catch (error) {
250
- logger.warn(`console \`authorize\` threw, denying the request: ${error instanceof Error ? error.message : String(error)}`);
270
+ logger3.warn(`console \`authorize\` threw, denying the request: ${error instanceof Error ? error.message : String(error)}`);
251
271
  return forbidden();
252
272
  }
253
273
  };
@@ -260,10 +280,16 @@ function authorizeByToken(options) {
260
280
  throw new Error("authorizeByToken: `token` is empty; set it (e.g. from an env var).");
261
281
  }
262
282
  const expected = sha256(token);
283
+ const caller = `token:${expected.toString("hex").slice(0, 12)}`;
263
284
  return (context) => {
264
285
  const header = context.switchToHttp().getRequest()?.headers?.authorization;
265
286
  const match = typeof header === "string" ? /^Bearer\s+(.+)$/i.exec(header.trim()) : null;
266
- if (match?.[1] && (0, import_node_crypto.timingSafeEqual)(sha256(match[1]), expected)) return true;
287
+ if (match?.[1] && (0, import_node_crypto.timingSafeEqual)(sha256(match[1]), expected)) {
288
+ return {
289
+ allowed: true,
290
+ caller
291
+ };
292
+ }
267
293
  return {
268
294
  allowed: false,
269
295
  reason: "unauthenticated"
@@ -271,6 +297,48 @@ function authorizeByToken(options) {
271
297
  };
272
298
  }
273
299
  __name(authorizeByToken, "authorizeByToken");
300
+ function anyAuthorize(...forms) {
301
+ if (forms.length === 0) throw new Error("anyAuthorize: pass at least one authorize form.");
302
+ let AnyConsoleAuthorizer = class AnyConsoleAuthorizer {
303
+ static {
304
+ __name(this, "AnyConsoleAuthorizer");
305
+ }
306
+ moduleRef;
307
+ #decides;
308
+ constructor(moduleRef) {
309
+ this.moduleRef = moduleRef;
310
+ }
311
+ async authorize(context) {
312
+ this.#decides ??= Promise.all(forms.map((form) => resolveConsoleAuthorize(form, this.moduleRef, {
313
+ warnIfMissing: false
314
+ })));
315
+ this.#decides.catch(() => {
316
+ this.#decides = void 0;
317
+ });
318
+ let unauthenticated2;
319
+ for (const decide of await this.#decides) {
320
+ const decision = await decide(context);
321
+ if (decision.allowed) return decision;
322
+ if (decision.reason === "unauthenticated") unauthenticated2 ??= decision;
323
+ }
324
+ if (unauthenticated2 === void 0) return {
325
+ allowed: false,
326
+ reason: "forbidden"
327
+ };
328
+ return "redirectTo" in unauthenticated2 && unauthenticated2.redirectTo !== void 0 ? {
329
+ redirectTo: unauthenticated2.redirectTo
330
+ } : {
331
+ allowed: false,
332
+ reason: "unauthenticated"
333
+ };
334
+ }
335
+ };
336
+ Reflect.defineMetadata("design:paramtypes", [
337
+ import_core.ModuleRef
338
+ ], AnyConsoleAuthorizer);
339
+ return AnyConsoleAuthorizer;
340
+ }
341
+ __name(anyAuthorize, "anyAuthorize");
274
342
  var CONSOLE_AUTHORIZE_DECIDE = Symbol(`${PACKAGE}:console-authorize`);
275
343
  var CONSOLE_PAGE_METADATA = `${PACKAGE}:console-page`;
276
344
  var ConsolePage = /* @__PURE__ */ __name(() => (target) => {
@@ -282,7 +350,7 @@ var ConsoleAccessDenied = class extends import_common.HttpException {
282
350
  }
283
351
  decision;
284
352
  page;
285
- constructor(decision, page) {
353
+ constructor(decision, page2) {
286
354
  const unauthenticated2 = decision.reason === "unauthenticated";
287
355
  super(unauthenticated2 ? {
288
356
  error: "unauthenticated",
@@ -291,9 +359,13 @@ var ConsoleAccessDenied = class extends import_common.HttpException {
291
359
  } : {}
292
360
  } : {
293
361
  error: "forbidden"
294
- }, unauthenticated2 ? 401 : 403), this.decision = decision, this.page = page;
362
+ }, unauthenticated2 ? 401 : 403), this.decision = decision, this.page = page2;
295
363
  }
296
364
  };
365
+ var CONSOLE_PUBLIC_METADATA = `${PACKAGE}:console-public`;
366
+ var ConsolePublic = /* @__PURE__ */ __name(() => (_target, _key, descriptor) => {
367
+ Reflect.defineMetadata(CONSOLE_PUBLIC_METADATA, true, descriptor.value);
368
+ }, "ConsolePublic");
297
369
  var ConsoleAuthorizeGuard = class {
298
370
  static {
299
371
  __name(this, "ConsoleAuthorizeGuard");
@@ -303,6 +375,7 @@ var ConsoleAuthorizeGuard = class {
303
375
  this.decide = decide;
304
376
  }
305
377
  async canActivate(context) {
378
+ if (Reflect.getMetadata(CONSOLE_PUBLIC_METADATA, context.getHandler()) === true) return true;
306
379
  const decision = await this.decide(context);
307
380
  if (decision.allowed) return true;
308
381
  throw new ConsoleAccessDenied(decision, isPageNavigation(context));
@@ -338,12 +411,12 @@ var ConsoleAuthorizeFilter = class {
338
411
  catch(exception, host) {
339
412
  const adapter = this.adapterHost.httpAdapter;
340
413
  const response = host.switchToHttp().getResponse();
341
- const { decision, page } = exception;
342
- if (page && decision.redirectTo) {
414
+ const { decision, page: page2 } = exception;
415
+ if (page2 && decision.redirectTo) {
343
416
  adapter.redirect(response, 302, decision.redirectTo);
344
417
  return;
345
418
  }
346
- if (page) {
419
+ if (page2) {
347
420
  const forbiddenPage = decision.reason === "forbidden";
348
421
  adapter.setHeader(response, "Content-Type", "text/html; charset=utf-8");
349
422
  adapter.reply(response, forbiddenPage ? DENIED_PAGE : SIGN_IN_PAGE, forbiddenPage ? 403 : 401);
@@ -415,7 +488,7 @@ var DashboardService = class {
415
488
  */
416
489
  controlPlane() {
417
490
  if (!this.store || !this.engine) {
418
- throw new Error("This durable dashboard operation requires the control plane (not available on a tenant deployment).");
491
+ throw new import_common2.NotImplementedException("This durable dashboard operation requires the control plane (not available on a tenant deployment).");
419
492
  }
420
493
  return {
421
494
  store: this.store,
@@ -515,31 +588,31 @@ var DashboardService = class {
515
588
  async workerHealth() {
516
589
  return this.gateway.workerHealth();
517
590
  }
518
- getRunDetail(runId, options) {
519
- return this.gateway.getRunDetail(runId, options);
591
+ getRunDetail(runId2, options) {
592
+ return this.gateway.getRunDetail(runId2, options);
520
593
  }
521
594
  /** A page of one fan's members, or null when the run is unknown or the gateway can't page fans. */
522
- async getFanMembers(runId, group, query) {
523
- return await this.gateway.getFanMembers?.(runId, group, query) ?? null;
595
+ async getFanMembers(runId2, group, query) {
596
+ return await this.gateway.getFanMembers?.(runId2, group, query) ?? null;
524
597
  }
525
598
  /** Fix-and-replay a dead/failed run with a corrected input — a fresh linked run. Returns its id. */
526
- retryWithInput(runId, input) {
527
- return this.gateway.retryWithInput(runId, input);
599
+ retryWithInput(runId2, input) {
600
+ return this.gateway.retryWithInput(runId2, input);
528
601
  }
529
602
  /** Re-run a failed/incomplete run; completed steps replay from their checkpoints. */
530
- retry(runId) {
531
- return this.gateway.retry(runId);
603
+ retry(runId2) {
604
+ return this.gateway.retry(runId2);
532
605
  }
533
- cancel(runId, opts) {
534
- return this.gateway.cancel(runId, opts);
606
+ cancel(runId2, opts) {
607
+ return this.gateway.cancel(runId2, opts);
535
608
  }
536
609
  /**
537
610
  * Publish a settled run's lifecycle event again, to its subscribers that have not started (or to
538
611
  * `subscriber` alone). Undefined when the gateway cannot republish (a tenant proxy).
539
612
  */
540
- async republishLifecycle(runId, subscriber) {
613
+ async republishLifecycle(runId2, subscriber) {
541
614
  return this.gateway.republishLifecycle?.({
542
- runId,
615
+ runId: runId2,
543
616
  ...subscriber !== void 0 ? {
544
617
  subscriber
545
618
  } : {}
@@ -607,12 +680,12 @@ var DashboardService = class {
607
680
  };
608
681
  }
609
682
  /** Resume a run paused at a `ctx.breakpoint` (the "continue" button). */
610
- continue(runId) {
611
- return this.gateway.continue(runId);
683
+ continue(runId2) {
684
+ return this.gateway.continue(runId2);
612
685
  }
613
686
  /** Re-dispatch a run's stuck `pending` remote steps — recovery for a lost step dispatch. */
614
- redispatch(runId) {
615
- return this.gateway.redispatchPending(runId);
687
+ redispatch(runId2) {
688
+ return this.gateway.redispatchPending(runId2);
616
689
  }
617
690
  /**
618
691
  * Deliver a `ctx.webhook()` callback: turn an inbound POST (token + body) into the signal the
@@ -641,13 +714,39 @@ var DashboardService = class {
641
714
  status: fresh?.status ?? run.status
642
715
  };
643
716
  }
717
+ /**
718
+ * What this deployment can serve: `controlPlane` (signals, run events, updates, metrics) and
719
+ * `schedules` (persisted schedules). Both are false on a store-less tenant.
720
+ */
721
+ capabilities() {
722
+ const controlPlane = this.store !== void 0 && this.engine !== void 0;
723
+ return {
724
+ controlPlane,
725
+ schedules: controlPlane && this.engine?.schedules?.supported === true
726
+ };
727
+ }
728
+ /** Whether `runId` names a run the caller may see (tenancy applies). */
729
+ async runExists(runId2) {
730
+ if (this.gateway.getRun) return await this.gateway.getRun(runId2) !== null;
731
+ return await this.gateway.getRunDetail(runId2, {
732
+ summarizeFansOver: 0
733
+ }) !== null;
734
+ }
735
+ /**
736
+ * Deliver `payload` to the run waiting on `token` (`engine.signal`). Null when no run the caller
737
+ * may see waits on it yet: the engine then buffers the payload for the run that waits next.
738
+ * Control-plane-only.
739
+ */
740
+ signal(token, payload) {
741
+ return this.controlPlane().engine.signal(token, payload);
742
+ }
644
743
  /** Side-effect-free read of a value a run published via `ctx.setEvent` (a live query). Control-plane-only. */
645
- async getEvent(runId, key) {
646
- return this.controlPlane().engine.getEvent(runId, key);
744
+ async getEvent(runId2, key) {
745
+ return this.controlPlane().engine.getEvent(runId2, key);
647
746
  }
648
747
  /** Deliver a validated `ctx.onUpdate` to a run; the validator may reject it (see UpdateResult). Control-plane-only. */
649
- async update(runId, name, arg) {
650
- return this.controlPlane().engine.update(runId, name, arg);
748
+ async update(runId2, name, arg) {
749
+ return this.controlPlane().engine.update(runId2, name, arg);
651
750
  }
652
751
  /**
653
752
  * Live stream of a run's lifecycle events for SSE. Backed by `gateway.subscribe`, which — on the
@@ -655,9 +754,9 @@ var DashboardService = class {
655
754
  * executing on a worker pod), and on a tenant round-trips over the transport. Either way the
656
755
  * gateway already filters to `runId`; the dashboard just wires the events through.
657
756
  */
658
- streamRun(runId) {
757
+ streamRun(runId2) {
659
758
  return new import_rxjs.Observable((subscriber) => {
660
- const off = this.gateway.subscribe(runId, (event) => subscriber.next({
759
+ const off = this.gateway.subscribe(runId2, (event) => subscriber.next({
661
760
  data: event
662
761
  }));
663
762
  return () => off();
@@ -905,7 +1004,7 @@ var RunQueryDraft2 = class {
905
1004
  /** The predicates minus the page window and the axes a facet count reports on — what
906
1005
  * `runFacets`/`runValueFacets` are taken over. */
907
1006
  facetQuery() {
908
- const { status, statuses, origin, origins, limit, offset, ...facetQuery } = this.query;
1007
+ const { status, statuses, origin, origins, limit: limit2, offset, ...facetQuery } = this.query;
909
1008
  return facetQuery;
910
1009
  }
911
1010
  };
@@ -1110,10 +1209,10 @@ var RunQueryAdapter = class {
1110
1209
  }
1111
1210
  unsupported(clause, "equals, notEquals, gt, gte, lt, lte, in");
1112
1211
  }
1113
- applyOffsetPagination(qb, page, size) {
1212
+ applyOffsetPagination(qb, page2, size) {
1114
1213
  this.draft(qb).narrow({
1115
1214
  limit: size,
1116
- offset: (page - 1) * size
1215
+ offset: (page2 - 1) * size
1117
1216
  });
1118
1217
  }
1119
1218
  /**
@@ -1340,9 +1439,9 @@ var RunFilter = class extends import_nestjs_filter.BaseFilter {
1340
1439
  }
1341
1440
  }
1342
1441
  applyLimit(value) {
1343
- const limit = bound(value);
1344
- if (limit !== void 0) this.$query.narrow({
1345
- limit
1442
+ const limit2 = bound(value);
1443
+ if (limit2 !== void 0) this.$query.narrow({
1444
+ limit: limit2
1346
1445
  });
1347
1446
  }
1348
1447
  applyOffset(value) {
@@ -1524,7 +1623,7 @@ var DurableApiControllerBase = class {
1524
1623
  * The persisted schedules (`engine.schedules`), soonest-due first, optionally narrowed by
1525
1624
  * `namespace`/`workflow`/`tag`/`paused`. Empty on a tenant or a store without schedules.
1526
1625
  */
1527
- schedules(namespace, workflow, tag, paused, limit) {
1626
+ schedules(namespace, workflow, tag, paused, limit2) {
1528
1627
  return this.dashboard.listSchedules({
1529
1628
  ...namespace ? {
1530
1629
  namespace
@@ -1538,7 +1637,7 @@ var DurableApiControllerBase = class {
1538
1637
  ...paused === "true" || paused === "false" ? {
1539
1638
  paused: paused === "true"
1540
1639
  } : {},
1541
- limit: Math.min(Number(limit) || 200, 1e3)
1640
+ limit: Math.min(Number(limit2) || 200, 1e3)
1542
1641
  });
1543
1642
  }
1544
1643
  pauseSchedule(id) {
@@ -1588,17 +1687,17 @@ var DurableApiControllerBase = class {
1588
1687
  return body;
1589
1688
  }
1590
1689
  /** One page of a summarized fan's members, in seq order. Versioned like {@link run}. */
1591
- async fanMembers(id, group, offset, limit, failed, ifNoneMatch, response) {
1592
- const page = await this.dashboard.getFanMembers(id, group, {
1690
+ async fanMembers(id, group, offset, limit2, failed, ifNoneMatch, response) {
1691
+ const page2 = await this.dashboard.getFanMembers(id, group, {
1593
1692
  offset: offset === void 0 ? void 0 : Number(offset),
1594
- limit: limit === void 0 ? void 0 : Number(limit),
1693
+ limit: limit2 === void 0 ? void 0 : Number(limit2),
1595
1694
  failedOnly: failed === "true",
1596
1695
  ...ifNoneMatch && {
1597
1696
  ifNoneMatch
1598
1697
  }
1599
1698
  });
1600
- if (!page) throw new import_common6.NotFoundException(`fan ${group} of run ${id} not found`);
1601
- const { etag, notModified, ...body } = page;
1699
+ if (!page2) throw new import_common6.NotFoundException(`fan ${group} of run ${id} not found`);
1700
+ const { etag, notModified, ...body } = page2;
1602
1701
  if (etag) {
1603
1702
  if (notModified) throw new NotModifiedException(etag);
1604
1703
  setEtag(response, etag);
@@ -1623,7 +1722,7 @@ var DurableApiControllerBase = class {
1623
1722
  * exactly the set the operator was looking at rather than a wider one.
1624
1723
  */
1625
1724
  bulk(action, draft, compensate) {
1626
- const { limit, offset, ...filter } = draft.query;
1725
+ const { limit: limit2, offset, ...filter } = draft.query;
1627
1726
  return this.dashboard.bulk(action === "cancel" ? "cancel" : "retry", filter, {
1628
1727
  compensate: compensate === "true"
1629
1728
  });
@@ -1670,9 +1769,9 @@ var DurableApiControllerBase = class {
1670
1769
  }
1671
1770
  /**
1672
1771
  * Public callback endpoint for `ctx.webhook()`: a third party POSTs here (the url handed to it),
1673
- * and the body resumes the waiting run. NOTE: this is reachable by external systems — protect it
1674
- * by treating the token as a secret (it embeds runId:seq) and/or fronting it with signature
1675
- * verification in your own middleware.
1772
+ * and the body resumes the waiting run. Not a console route: the console's `authorize` does not
1773
+ * apply, the token is the secret. The app's `decorators` still do. Treat the token as a secret
1774
+ * and/or front it with signature verification in your own middleware.
1676
1775
  */
1677
1776
  async webhook(token, body) {
1678
1777
  const result = await this.dashboard.deliverWebhook(token, body);
@@ -1910,6 +2009,7 @@ _ts_decorate6([
1910
2009
  _ts_metadata5("design:returntype", Promise)
1911
2010
  ], DurableApiControllerBase.prototype, "redispatch", null);
1912
2011
  _ts_decorate6([
2012
+ ConsolePublic(),
1913
2013
  (0, import_common6.Post)("webhooks/:token"),
1914
2014
  _ts_param3(0, (0, import_common6.Param)("token")),
1915
2015
  _ts_param3(1, (0, import_common6.Body)()),
@@ -1980,9 +2080,196 @@ DurableApiController = _ts_decorate6([
1980
2080
  // src/server/durable-dashboard.module.ts
1981
2081
  var import_reflect_metadata3 = require("reflect-metadata");
1982
2082
  var import_nestjs_filter3 = require("@dudousxd/nestjs-filter");
1983
- var import_common8 = require("@nestjs/common");
2083
+ var import_common10 = require("@nestjs/common");
1984
2084
  var import_core2 = require("@nestjs/core");
1985
2085
 
2086
+ // src/server/console-switcher.ts
2087
+ var import_common7 = require("@nestjs/common");
2088
+
2089
+ // src/server/tokens.ts
2090
+ var import_nestjs_durable_core3 = require("@dudousxd/nestjs-durable-core");
2091
+ var DASHBOARD_DEV_TOOLS = Symbol("DASHBOARD_DEV_TOOLS");
2092
+ var DASHBOARD_BASE_PATH = Symbol("DASHBOARD_BASE_PATH");
2093
+ var DASHBOARD_API_PATH = Symbol("DASHBOARD_API_PATH");
2094
+
2095
+ // src/server/console-switcher.ts
2096
+ function _ts_decorate7(decorators, target, key, desc) {
2097
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2098
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2099
+ r = Reflect.decorate(decorators, target, key, desc);
2100
+ } else {
2101
+ for (var i = decorators.length - 1; i >= 0; i--) {
2102
+ if (d = decorators[i]) {
2103
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2104
+ }
2105
+ }
2106
+ }
2107
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2108
+ }
2109
+ __name(_ts_decorate7, "_ts_decorate");
2110
+ function _ts_metadata6(metadataKey, metadataValue) {
2111
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2112
+ return Reflect.metadata(metadataKey, metadataValue);
2113
+ }
2114
+ }
2115
+ __name(_ts_metadata6, "_ts_metadata");
2116
+ function _ts_param4(paramIndex, decorator) {
2117
+ return function(target, key) {
2118
+ decorator(target, key, paramIndex);
2119
+ };
2120
+ }
2121
+ __name(_ts_param4, "_ts_param");
2122
+ var AVIARY_CONSOLES_SLOT = Symbol.for("@dudousxd/aviary:consoles");
2123
+ var ID = /^[a-z][a-z0-9-]*$/;
2124
+ var ICONS = /* @__PURE__ */ new Set([
2125
+ "workflow",
2126
+ "telescope",
2127
+ "media",
2128
+ "payments",
2129
+ "agent",
2130
+ "identity",
2131
+ "generic"
2132
+ ]);
2133
+ var CONSOLE_PROBE_TIMEOUT_MS = 300;
2134
+ function isSameOriginPath(path) {
2135
+ return typeof path === "string" && path.startsWith("/") && !path.startsWith("//");
2136
+ }
2137
+ __name(isSameOriginPath, "isSameOriginPath");
2138
+ function readAviaryConsoles() {
2139
+ const value = globalThis[AVIARY_CONSOLES_SLOT];
2140
+ if (value?.version !== 1 || !Array.isArray(value.entries)) return void 0;
2141
+ return value;
2142
+ }
2143
+ __name(readAviaryConsoles, "readAviaryConsoles");
2144
+ function registerAviaryConsole(entry, logger3) {
2145
+ if (!ID.test(entry.id)) throw new Error(`console id ${JSON.stringify(entry.id)} is invalid`);
2146
+ if (!isSameOriginPath(entry.path)) {
2147
+ throw new Error(`console path ${JSON.stringify(entry.path)} must start with a single "/"`);
2148
+ }
2149
+ const holder = globalThis;
2150
+ if (holder[AVIARY_CONSOLES_SLOT] === void 0) {
2151
+ const fresh = {
2152
+ version: 1,
2153
+ entries: []
2154
+ };
2155
+ holder[AVIARY_CONSOLES_SLOT] = fresh;
2156
+ }
2157
+ const registry = readAviaryConsoles();
2158
+ if (registry === void 0) return void 0;
2159
+ if (registry.entries.some((existing) => existing.id === entry.id)) {
2160
+ logger3?.warn(`console id "${entry.id}" is already registered`);
2161
+ return void 0;
2162
+ }
2163
+ registry.entries.push(entry);
2164
+ return () => {
2165
+ const index = registry.entries.indexOf(entry);
2166
+ if (index !== -1) registry.entries.splice(index, 1);
2167
+ };
2168
+ }
2169
+ __name(registerAviaryConsole, "registerAviaryConsole");
2170
+ function consoleSwitcherConfig(selfId, endpoint) {
2171
+ const registry = readAviaryConsoles();
2172
+ if (!registry?.entries.some((entry) => entry.id === selfId)) return void 0;
2173
+ const others = registry.entries.filter((entry) => entry.id !== selfId).length;
2174
+ return others > 0 ? {
2175
+ endpoint,
2176
+ others
2177
+ } : void 0;
2178
+ }
2179
+ __name(consoleSwitcherConfig, "consoleSwitcherConfig");
2180
+ async function allows(entry, context, ms) {
2181
+ let timer;
2182
+ const timeout = new Promise((resolve2) => {
2183
+ timer = setTimeout(() => resolve2(false), ms);
2184
+ });
2185
+ try {
2186
+ return await Promise.race([
2187
+ Promise.resolve().then(() => entry.authorize(context)).then((decision) => decision?.allowed === true, () => false),
2188
+ timeout
2189
+ ]);
2190
+ } finally {
2191
+ clearTimeout(timer);
2192
+ }
2193
+ }
2194
+ __name(allows, "allows");
2195
+ async function listVisibleConsoles(registry, selfId, context, timeoutMs = CONSOLE_PROBE_TIMEOUT_MS) {
2196
+ const others = (registry?.entries ?? []).filter((entry) => entry.id !== selfId && isSameOriginPath(entry.path)).sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
2197
+ const verdicts = await Promise.all(others.map((entry) => allows(entry, context, timeoutMs)));
2198
+ return {
2199
+ version: 1,
2200
+ current: selfId,
2201
+ consoles: others.filter((_, index) => verdicts[index]).map((entry) => ({
2202
+ id: entry.id,
2203
+ title: entry.title,
2204
+ icon: ICONS.has(entry.icon) ? entry.icon : "generic",
2205
+ href: entry.path
2206
+ }))
2207
+ };
2208
+ }
2209
+ __name(listVisibleConsoles, "listVisibleConsoles");
2210
+ var DURABLE_CONSOLE_ID = "durable";
2211
+ var logger = new import_common7.Logger("DurableDashboardModule");
2212
+ var DurableConsoleRegistration = class {
2213
+ static {
2214
+ __name(this, "DurableConsoleRegistration");
2215
+ }
2216
+ basePath;
2217
+ decide;
2218
+ #unregister;
2219
+ constructor(basePath, decide) {
2220
+ this.basePath = basePath;
2221
+ this.decide = decide;
2222
+ }
2223
+ onModuleInit() {
2224
+ this.#unregister = registerAviaryConsole({
2225
+ id: DURABLE_CONSOLE_ID,
2226
+ title: "Workflows",
2227
+ icon: "workflow",
2228
+ order: 10,
2229
+ path: this.basePath,
2230
+ authorize: /* @__PURE__ */ __name((context) => this.decide(context), "authorize")
2231
+ }, logger);
2232
+ }
2233
+ onModuleDestroy() {
2234
+ this.#unregister?.();
2235
+ this.#unregister = void 0;
2236
+ }
2237
+ };
2238
+ DurableConsoleRegistration = _ts_decorate7([
2239
+ (0, import_common7.Injectable)(),
2240
+ _ts_param4(0, (0, import_common7.Inject)(DASHBOARD_BASE_PATH)),
2241
+ _ts_param4(1, (0, import_common7.Inject)(CONSOLE_AUTHORIZE_DECIDE)),
2242
+ _ts_metadata6("design:type", Function),
2243
+ _ts_metadata6("design:paramtypes", [
2244
+ String,
2245
+ typeof ConsoleDecide === "undefined" ? Object : ConsoleDecide
2246
+ ])
2247
+ ], DurableConsoleRegistration);
2248
+ var CurrentExecutionContext = (0, import_common7.createParamDecorator)((_data, context) => ({
2249
+ context
2250
+ }));
2251
+ var DurableConsolesController = class {
2252
+ static {
2253
+ __name(this, "DurableConsolesController");
2254
+ }
2255
+ consoles(current) {
2256
+ return listVisibleConsoles(readAviaryConsoles(), DURABLE_CONSOLE_ID, current.context);
2257
+ }
2258
+ };
2259
+ _ts_decorate7([
2260
+ (0, import_common7.Get)("consoles"),
2261
+ (0, import_common7.Header)("Cache-Control", "private, no-store"),
2262
+ _ts_param4(0, CurrentExecutionContext()),
2263
+ _ts_metadata6("design:type", Function),
2264
+ _ts_metadata6("design:paramtypes", [
2265
+ typeof CurrentContext === "undefined" ? Object : CurrentContext
2266
+ ]),
2267
+ _ts_metadata6("design:returntype", typeof Promise === "undefined" ? Object : Promise)
2268
+ ], DurableConsolesController.prototype, "consoles", null);
2269
+ DurableConsolesController = _ts_decorate7([
2270
+ (0, import_common7.Controller)()
2271
+ ], DurableConsolesController);
2272
+
1986
2273
  // src/server/controller-decorators.ts
1987
2274
  var import_reflect_metadata2 = require("reflect-metadata");
1988
2275
  var CAN_ANYONE_METADATA = "authorization:can-anyone";
@@ -2009,12 +2296,785 @@ function controllerFor(base, decorators) {
2009
2296
  }
2010
2297
  __name(controllerFor, "controllerFor");
2011
2298
 
2299
+ // src/server/dev-tools.ts
2300
+ var import_common8 = require("@nestjs/common");
2301
+ function _ts_decorate8(decorators, target, key, desc) {
2302
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2303
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2304
+ r = Reflect.decorate(decorators, target, key, desc);
2305
+ } else {
2306
+ for (var i = decorators.length - 1; i >= 0; i--) {
2307
+ if (d = decorators[i]) {
2308
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2309
+ }
2310
+ }
2311
+ }
2312
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2313
+ }
2314
+ __name(_ts_decorate8, "_ts_decorate");
2315
+ function _ts_metadata7(metadataKey, metadataValue) {
2316
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2317
+ return Reflect.metadata(metadataKey, metadataValue);
2318
+ }
2319
+ }
2320
+ __name(_ts_metadata7, "_ts_metadata");
2321
+ function _ts_param5(paramIndex, decorator) {
2322
+ return function(target, key) {
2323
+ decorator(target, key, paramIndex);
2324
+ };
2325
+ }
2326
+ __name(_ts_param5, "_ts_param");
2327
+ var DEV_TOOLS_SLOT = Symbol.for("@dudousxd/aviary:dev-tools");
2328
+ function devToolsSlot() {
2329
+ const holder = globalThis;
2330
+ const existing = holder[DEV_TOOLS_SLOT];
2331
+ if (existing !== void 0) return existing;
2332
+ const libs = [];
2333
+ const slot = {
2334
+ version: 1,
2335
+ libs,
2336
+ register(lib) {
2337
+ if (libs.some((entry) => entry.lib === lib.lib)) {
2338
+ throw new Error(`dev tools: the lib "${lib.lib}" is already registered`);
2339
+ }
2340
+ libs.push(lib);
2341
+ return () => {
2342
+ const index = libs.indexOf(lib);
2343
+ if (index !== -1) libs.splice(index, 1);
2344
+ };
2345
+ }
2346
+ };
2347
+ holder[DEV_TOOLS_SLOT] = slot;
2348
+ return slot;
2349
+ }
2350
+ __name(devToolsSlot, "devToolsSlot");
2351
+ var DEFAULT_LIMIT = 20;
2352
+ var MAX_LIMIT = 100;
2353
+ var MAX_RUN_IDS = 100;
2354
+ var ERROR_SUMMARY_CHARS = 300;
2355
+ var RUN_STATUSES = [
2356
+ "pending",
2357
+ "running",
2358
+ "suspended",
2359
+ "completed",
2360
+ "failed",
2361
+ "cancelling",
2362
+ "cancelled",
2363
+ "dead",
2364
+ "blocked"
2365
+ ];
2366
+ var COUNT_FIELDS = [
2367
+ "status",
2368
+ "workflow",
2369
+ "origin",
2370
+ "namespace",
2371
+ "tag",
2372
+ "attributeKey",
2373
+ "attributeValue"
2374
+ ];
2375
+ var limit = {
2376
+ type: "integer",
2377
+ description: `Rows per page (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}).`,
2378
+ minimum: 1,
2379
+ maximum: MAX_LIMIT
2380
+ };
2381
+ var cursor = {
2382
+ type: "string",
2383
+ description: "Pass the `next` of the previous page."
2384
+ };
2385
+ var runId = {
2386
+ type: "string",
2387
+ description: "Run id."
2388
+ };
2389
+ var runIds = {
2390
+ type: "array",
2391
+ items: {
2392
+ type: "string"
2393
+ },
2394
+ description: `Several run ids (1 to ${MAX_RUN_IDS}), instead of \`id\`.`
2395
+ };
2396
+ var scheduleKey = {
2397
+ type: "string",
2398
+ description: "Schedule id."
2399
+ };
2400
+ var runFilters = {
2401
+ workflow: {
2402
+ type: "string",
2403
+ description: "Workflow name."
2404
+ },
2405
+ tag: {
2406
+ type: "string",
2407
+ description: "A tag the run carries."
2408
+ },
2409
+ namespace: {
2410
+ type: "string",
2411
+ description: "Worker pool (namespace)."
2412
+ },
2413
+ attr: {
2414
+ type: "array",
2415
+ items: {
2416
+ type: "string"
2417
+ },
2418
+ description: "Search-attribute predicates `key:op:value` (ops: eq, ne, gt, gte, lt, lte, in with `|`), ANDed."
2419
+ }
2420
+ };
2421
+ var str = /* @__PURE__ */ __name((value) => typeof value === "string" && value !== "" ? value : void 0, "str");
2422
+ function limitOf(input) {
2423
+ const value = input.limit;
2424
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? Math.min(value, MAX_LIMIT) : DEFAULT_LIMIT;
2425
+ }
2426
+ __name(limitOf, "limitOf");
2427
+ function offsetOf(input) {
2428
+ const value = str(input.cursor);
2429
+ if (value === void 0) return 0;
2430
+ if (!/^\d+$/.test(value)) {
2431
+ throw new Error(`"cursor" must be the \`next\` of a previous page, got "${value}"`);
2432
+ }
2433
+ return Number(value);
2434
+ }
2435
+ __name(offsetOf, "offsetOf");
2436
+ function required(input, key) {
2437
+ const value = str(input[key]);
2438
+ if (value === void 0) throw new Error(`"${key}" is required`);
2439
+ return value;
2440
+ }
2441
+ __name(required, "required");
2442
+ function page(rows, offset, size) {
2443
+ return {
2444
+ items: rows.slice(0, size),
2445
+ next: rows.length > size ? String(offset + size) : null
2446
+ };
2447
+ }
2448
+ __name(page, "page");
2449
+ function runPredicates(input) {
2450
+ const query = {};
2451
+ const workflow = str(input.workflow);
2452
+ if (workflow !== void 0) query.workflow = workflow;
2453
+ const tag = str(input.tag);
2454
+ if (tag !== void 0) query.tag = tag;
2455
+ const namespace = str(input.namespace);
2456
+ if (namespace !== void 0) query.namespace = namespace;
2457
+ const attr = Array.isArray(input.attr) ? input.attr.filter((entry) => typeof entry === "string" && entry !== "") : [];
2458
+ if (attr.length > 0) {
2459
+ const attributes = parseAttrFilters(attr);
2460
+ if (attributes === void 0 || attributes.length !== attr.length) {
2461
+ throw new Error(`"attr" entries must read \`key:op:value\`, got ${JSON.stringify(attr)}`);
2462
+ }
2463
+ query.attributes = attributes;
2464
+ }
2465
+ return query;
2466
+ }
2467
+ __name(runPredicates, "runPredicates");
2468
+ function statusOf(input) {
2469
+ const status = str(input.status);
2470
+ if (status === void 0) return void 0;
2471
+ if (!RUN_STATUSES.includes(status)) {
2472
+ throw new Error(`"status" must be one of ${RUN_STATUSES.join(", ")}, got "${status}"`);
2473
+ }
2474
+ return status;
2475
+ }
2476
+ __name(statusOf, "statusOf");
2477
+ function listRow(run) {
2478
+ const row = {
2479
+ ...toRunListRow(run)
2480
+ };
2481
+ const message = run.error?.message;
2482
+ if (typeof message === "string" && message !== "") {
2483
+ const line = message.split("\n", 1)[0] ?? "";
2484
+ row.error = line.length > ERROR_SUMMARY_CHARS ? `${line.slice(0, ERROR_SUMMARY_CHARS)}\u2026` : line;
2485
+ }
2486
+ return row;
2487
+ }
2488
+ __name(listRow, "listRow");
2489
+ async function call(what, run) {
2490
+ try {
2491
+ return await run();
2492
+ } catch (error) {
2493
+ if (error instanceof import_common8.NotFoundException || error instanceof Error && error.name === "ScheduleNotFoundError") {
2494
+ throw new Error(`${what()} not found`);
2495
+ }
2496
+ if (error instanceof import_common8.HttpException) throw new Error(error.message);
2497
+ throw error;
2498
+ }
2499
+ }
2500
+ __name(call, "call");
2501
+ function found(value, what) {
2502
+ if (value === null || value === void 0) throw new Error(`${what} not found`);
2503
+ return value;
2504
+ }
2505
+ __name(found, "found");
2506
+ function idsOf(input) {
2507
+ const one = str(input.id);
2508
+ const many = Array.isArray(input.ids) ? input.ids.filter((id) => typeof id === "string" && id !== "") : void 0;
2509
+ if (one !== void 0 && many !== void 0) throw new Error('pass "id" or "ids", not both');
2510
+ if (one !== void 0) return {
2511
+ ids: [
2512
+ one
2513
+ ],
2514
+ many: false
2515
+ };
2516
+ if (many === void 0 || many.length === 0) throw new Error('"id" or "ids" is required');
2517
+ if (many.length > MAX_RUN_IDS) {
2518
+ throw new Error(`"ids" lists ${many.length} runs; at most ${MAX_RUN_IDS} per call`);
2519
+ }
2520
+ return {
2521
+ ids: many,
2522
+ many: true
2523
+ };
2524
+ }
2525
+ __name(idsOf, "idsOf");
2526
+ async function each(input, act) {
2527
+ const { ids, many } = idsOf(input);
2528
+ if (!many) return act(ids[0]);
2529
+ const applied = [];
2530
+ for (const [index, id] of ids.entries()) {
2531
+ try {
2532
+ applied.push(await act(id));
2533
+ } catch (error) {
2534
+ return {
2535
+ applied,
2536
+ failed: {
2537
+ id,
2538
+ error: error instanceof Error ? error.message : String(error)
2539
+ },
2540
+ skipped: ids.slice(index + 1)
2541
+ };
2542
+ }
2543
+ }
2544
+ return {
2545
+ applied
2546
+ };
2547
+ }
2548
+ __name(each, "each");
2549
+ function starvedGroups(groups) {
2550
+ return groups.filter((group) => group.depth > 0 && group.liveWorkers.length === 0).sort((a, b) => b.depth - a.depth).map(({ group, depth, kind }) => ({
2551
+ group,
2552
+ depth,
2553
+ ...kind ? {
2554
+ kind
2555
+ } : {}
2556
+ }));
2557
+ }
2558
+ __name(starvedGroups, "starvedGroups");
2559
+ function durableDevTools(service) {
2560
+ const controlPlane = /* @__PURE__ */ __name(() => service.capabilities().controlPlane, "controlPlane");
2561
+ const schedules = /* @__PURE__ */ __name(() => service.capabilities().schedules, "schedules");
2562
+ return [
2563
+ {
2564
+ name: "durable_list_runs",
2565
+ kind: "read",
2566
+ description: "Workflow runs, newest first: id, workflow, status, what a suspended run waits on, times, tags and a one-line error summary (no input or output). Filters: status, workflow, tag, namespace, origin, attr. Returns { items, next }; pass next as cursor.",
2567
+ input: {
2568
+ type: "object",
2569
+ properties: {
2570
+ status: {
2571
+ type: "string",
2572
+ enum: RUN_STATUSES,
2573
+ description: "Run status."
2574
+ },
2575
+ ...runFilters,
2576
+ origin: {
2577
+ type: "string",
2578
+ description: "The package the run came from."
2579
+ },
2580
+ cursor,
2581
+ limit
2582
+ }
2583
+ },
2584
+ async run(input) {
2585
+ const size = limitOf(input);
2586
+ const offset = offsetOf(input);
2587
+ const status = statusOf(input);
2588
+ const origin = str(input.origin);
2589
+ const query = {
2590
+ ...runPredicates(input),
2591
+ ...status !== void 0 ? {
2592
+ status
2593
+ } : {},
2594
+ ...origin !== void 0 ? {
2595
+ origin
2596
+ } : {},
2597
+ offset,
2598
+ limit: size + 1
2599
+ };
2600
+ const runs = await call(() => "runs", () => service.listRuns(query));
2601
+ return page(runs.map(listRow), offset, size);
2602
+ }
2603
+ },
2604
+ {
2605
+ name: "durable_get_run",
2606
+ kind: "read",
2607
+ description: 'One run: its input, output, error, step timeline, children, fans (summarized over 50 members unless full) and lifecycle links. `field` returns that one field only (e.g. "steps"). An error when no run has that id.',
2608
+ input: {
2609
+ type: "object",
2610
+ properties: {
2611
+ id: runId,
2612
+ full: {
2613
+ type: "boolean",
2614
+ description: "Every fan member inline (default false)."
2615
+ },
2616
+ field: {
2617
+ type: "string",
2618
+ description: "Return only this top-level field."
2619
+ }
2620
+ },
2621
+ required: [
2622
+ "id"
2623
+ ]
2624
+ },
2625
+ async run(input) {
2626
+ const id = required(input, "id");
2627
+ const detail = found(await call(() => `run ${id}`, () => service.getRunDetail(id, input.full === true ? void 0 : {
2628
+ summarizeFansOver: 50
2629
+ })), `run ${id}`);
2630
+ const { etag: _etag, notModified: _notModified, ...body } = detail;
2631
+ const field = str(input.field);
2632
+ if (field === void 0) return body;
2633
+ if (!Object.hasOwn(body, field)) {
2634
+ throw new Error(`run ${id} has no field "${field}"; fields: ${Object.keys(body).join(", ")}`);
2635
+ }
2636
+ return {
2637
+ id,
2638
+ [field]: body[field]
2639
+ };
2640
+ }
2641
+ },
2642
+ {
2643
+ name: "durable_list_fan_members",
2644
+ kind: "read",
2645
+ description: "One fan's members (the steps or children of a fan-out group), in order. Filters: failed (only failed members). Returns { items, total, next }; pass next as cursor. An error when the run or the fan does not exist.",
2646
+ input: {
2647
+ type: "object",
2648
+ properties: {
2649
+ id: runId,
2650
+ group: {
2651
+ type: "string",
2652
+ description: "The fan group, as the run detail names it."
2653
+ },
2654
+ failed: {
2655
+ type: "boolean",
2656
+ description: "Only failed members."
2657
+ },
2658
+ cursor,
2659
+ limit
2660
+ },
2661
+ required: [
2662
+ "id",
2663
+ "group"
2664
+ ]
2665
+ },
2666
+ async run(input) {
2667
+ const id = required(input, "id");
2668
+ const group = required(input, "group");
2669
+ const size = limitOf(input);
2670
+ const offset = offsetOf(input);
2671
+ const fan = found(await call(() => `fan ${group} of run ${id}`, () => service.getFanMembers(id, group, {
2672
+ offset,
2673
+ limit: size,
2674
+ failedOnly: input.failed === true
2675
+ })), `fan ${group} of run ${id}`);
2676
+ const end = offset + fan.items.length;
2677
+ return {
2678
+ items: fan.items,
2679
+ total: fan.total,
2680
+ next: fan.items.length > 0 && end < fan.total ? String(end) : null
2681
+ };
2682
+ }
2683
+ },
2684
+ {
2685
+ name: "durable_count_runs_by",
2686
+ kind: "read",
2687
+ description: "How many runs have each value of one field (status, workflow, origin, namespace, tag, attributeKey, or attributeValue with key), most frequent first. Filters: workflow, tag, namespace, attr. Returns { items: [{ value, count }], next }; pass next as cursor. Empty when the store cannot count that field.",
2688
+ input: {
2689
+ type: "object",
2690
+ properties: {
2691
+ field: {
2692
+ type: "string",
2693
+ enum: COUNT_FIELDS,
2694
+ description: "The field to count by."
2695
+ },
2696
+ key: {
2697
+ type: "string",
2698
+ description: "The search attribute, for attributeValue."
2699
+ },
2700
+ ...runFilters,
2701
+ cursor,
2702
+ limit
2703
+ },
2704
+ required: [
2705
+ "field"
2706
+ ]
2707
+ },
2708
+ async run(input) {
2709
+ const field = required(input, "field");
2710
+ if (!COUNT_FIELDS.includes(field)) {
2711
+ throw new Error(`"field" must be one of ${COUNT_FIELDS.join(", ")}, got "${field}"`);
2712
+ }
2713
+ const size = limitOf(input);
2714
+ const offset = offsetOf(input);
2715
+ const predicates = runPredicates(input);
2716
+ if (field === "status" || field === "origin") {
2717
+ const rows2 = await call(() => "runs", () => service.runFacets(predicates));
2718
+ const counts = /* @__PURE__ */ new Map();
2719
+ for (const row of rows2) {
2720
+ const value = field === "status" ? row.status : row.origin;
2721
+ counts.set(value, (counts.get(value) ?? 0) + row.count);
2722
+ }
2723
+ const items = [
2724
+ ...counts
2725
+ ].map(([value, count]) => ({
2726
+ value,
2727
+ count
2728
+ })).sort((a, b) => b.count - a.count || String(a.value).localeCompare(String(b.value)));
2729
+ return page(items.slice(offset), offset, size);
2730
+ }
2731
+ const axis = field === "attributeValue" ? {
2732
+ field,
2733
+ key: required(input, "key")
2734
+ } : {
2735
+ field
2736
+ };
2737
+ const rows = await call(() => "runs", () => service.runValueFacets(axis, predicates, {
2738
+ offset,
2739
+ limit: size + 1
2740
+ }));
2741
+ return page(rows, offset, size);
2742
+ }
2743
+ },
2744
+ {
2745
+ name: "durable_get_health",
2746
+ kind: "read",
2747
+ description: "The fleet at a glance: run counts by status (pending backlog, dead, blocked: a run no compatible worker can take), queues with backlog and no live worker (starved), live worker count, and this deployment's role (control plane or tenant).",
2748
+ input: {
2749
+ type: "object",
2750
+ properties: {}
2751
+ },
2752
+ async run() {
2753
+ const [facets, groups] = await Promise.all([
2754
+ call(() => "runs", () => service.runFacets({})),
2755
+ call(() => "workers", () => service.workerHealth())
2756
+ ]);
2757
+ const runs = {};
2758
+ for (const row of facets) runs[row.status] = (runs[row.status] ?? 0) + row.count;
2759
+ const workers = /* @__PURE__ */ new Set();
2760
+ for (const group of groups) {
2761
+ for (const worker of group.liveWorkers) workers.add(worker.instanceId);
2762
+ }
2763
+ const starved = starvedGroups(groups);
2764
+ return {
2765
+ topology: service.topology(),
2766
+ runs,
2767
+ backlog: runs.pending ?? 0,
2768
+ dead: runs.dead ?? 0,
2769
+ blocked: runs.blocked ?? 0,
2770
+ queues: groups.length,
2771
+ liveWorkers: workers.size,
2772
+ starved,
2773
+ healthy: starved.length === 0 && (runs.blocked ?? 0) === 0
2774
+ };
2775
+ }
2776
+ },
2777
+ {
2778
+ name: "durable_list_workers",
2779
+ kind: "read",
2780
+ description: "Worker groups (queues): group, kind (workflow or step), queue depth and the live workers with their heartbeats. A group with depth > 0 and no live worker is starved. Returns { items, next: null }.",
2781
+ input: {
2782
+ type: "object",
2783
+ properties: {}
2784
+ },
2785
+ async run() {
2786
+ const groups = await call(() => "workers", () => service.workerHealth());
2787
+ return {
2788
+ items: groups,
2789
+ next: null
2790
+ };
2791
+ }
2792
+ },
2793
+ {
2794
+ name: "durable_list_schedules",
2795
+ kind: "read",
2796
+ description: "Persisted schedules, soonest due first: id, workflow, cadence (cron, every or rrule), paused, nextFireAt, lastFireAt, lastRunId, fires, lastError. Filters: workflow, tag, namespace, paused. Returns { items, next }; pass next as cursor.",
2797
+ input: {
2798
+ type: "object",
2799
+ properties: {
2800
+ workflow: {
2801
+ type: "string",
2802
+ description: "Workflow name."
2803
+ },
2804
+ tag: {
2805
+ type: "string",
2806
+ description: "A tag the schedule carries."
2807
+ },
2808
+ namespace: {
2809
+ type: "string",
2810
+ description: "Worker pool (namespace)."
2811
+ },
2812
+ paused: {
2813
+ type: "boolean",
2814
+ description: "Only paused (true) or active (false)."
2815
+ },
2816
+ cursor,
2817
+ limit
2818
+ }
2819
+ },
2820
+ available: schedules,
2821
+ async run(input) {
2822
+ const size = limitOf(input);
2823
+ const offset = offsetOf(input);
2824
+ const workflow = str(input.workflow);
2825
+ const tag = str(input.tag);
2826
+ const namespace = str(input.namespace);
2827
+ const rows = await call(() => "schedules", () => service.listSchedules({
2828
+ ...workflow !== void 0 ? {
2829
+ workflow
2830
+ } : {},
2831
+ ...tag !== void 0 ? {
2832
+ tag
2833
+ } : {},
2834
+ ...namespace !== void 0 ? {
2835
+ namespace
2836
+ } : {},
2837
+ ...typeof input.paused === "boolean" ? {
2838
+ paused: input.paused
2839
+ } : {},
2840
+ offset,
2841
+ limit: size + 1
2842
+ }));
2843
+ return page(rows, offset, size);
2844
+ }
2845
+ },
2846
+ {
2847
+ name: "durable_get_run_event",
2848
+ kind: "read",
2849
+ description: "The latest value a run published for `key` with ctx.setEvent (a live query, no side effect). Returns { id, key, value }; value is null when the run never published that key. An error when no run has that id.",
2850
+ input: {
2851
+ type: "object",
2852
+ properties: {
2853
+ id: runId,
2854
+ key: {
2855
+ type: "string",
2856
+ description: "Event key."
2857
+ }
2858
+ },
2859
+ required: [
2860
+ "id",
2861
+ "key"
2862
+ ]
2863
+ },
2864
+ available: controlPlane,
2865
+ async run(input) {
2866
+ const id = required(input, "id");
2867
+ const key = required(input, "key");
2868
+ if (!await call(() => `run ${id}`, () => service.runExists(id))) {
2869
+ throw new Error(`run ${id} not found`);
2870
+ }
2871
+ const value = await call(() => `run ${id}`, () => service.getEvent(id, key));
2872
+ return {
2873
+ id,
2874
+ key,
2875
+ value: value ?? null
2876
+ };
2877
+ }
2878
+ },
2879
+ {
2880
+ name: "durable_retry_run",
2881
+ kind: "action",
2882
+ description: `Re-runs a failed, dead or cancelled run; completed steps replay from their checkpoints. Returns { runId, status }. With ids (at most ${MAX_RUN_IDS}), runs them in order and stops at the first that fails: { applied, failed: { id, error }, skipped }. An error when no run has that id.`,
2883
+ input: {
2884
+ type: "object",
2885
+ properties: {
2886
+ id: runId,
2887
+ ids: runIds
2888
+ }
2889
+ },
2890
+ async run(input) {
2891
+ return each(input, async (id) => found(await call(() => `run ${id}`, () => service.retry(id)), `run ${id}`));
2892
+ }
2893
+ },
2894
+ {
2895
+ name: "durable_cancel_run",
2896
+ kind: "action",
2897
+ destructive: true,
2898
+ description: `Cancels a run that has not finished (with compensate: true, its completed steps' compensations run first). Returns { runId, status }. With ids (at most ${MAX_RUN_IDS}), cancels them in order and stops at the first that fails: { applied, failed: { id, error }, skipped }. An error when no run has that id.`,
2899
+ input: {
2900
+ type: "object",
2901
+ properties: {
2902
+ id: runId,
2903
+ ids: runIds,
2904
+ compensate: {
2905
+ type: "boolean",
2906
+ description: "Run compensations (default false)."
2907
+ }
2908
+ }
2909
+ },
2910
+ async run(input) {
2911
+ const compensate = input.compensate === true;
2912
+ return each(input, async (id) => found(await call(() => `run ${id}`, () => service.cancel(id, {
2913
+ compensate
2914
+ })), `run ${id}`));
2915
+ }
2916
+ },
2917
+ {
2918
+ name: "durable_continue_run",
2919
+ kind: "action",
2920
+ description: "Resumes a run paused at a ctx.breakpoint. Returns { runId, status }. An error when no run has that id, or when it is not paused at a breakpoint.",
2921
+ input: {
2922
+ type: "object",
2923
+ properties: {
2924
+ id: runId
2925
+ },
2926
+ required: [
2927
+ "id"
2928
+ ]
2929
+ },
2930
+ async run(input) {
2931
+ const id = required(input, "id");
2932
+ const what = /* @__PURE__ */ __name(() => `run ${id}`, "what");
2933
+ const result = await call(what, () => service.continue(id));
2934
+ if (result) return result;
2935
+ if (!await call(what, () => service.runExists(id))) throw new Error(`run ${id} not found`);
2936
+ throw new Error(`run ${id} is not paused at a breakpoint`);
2937
+ }
2938
+ },
2939
+ {
2940
+ name: "durable_signal_run",
2941
+ kind: "action",
2942
+ description: "Sends a signal: delivers payload (JSON text; omitted is null) to the run waiting on token (ctx.waitForSignal or a ctx.webhook token). Returns { delivered: true, runId, status }, or { delivered: false } when no run you can see waits on the token yet: the engine then buffers the payload for the next run that waits on it.",
2943
+ input: {
2944
+ type: "object",
2945
+ properties: {
2946
+ token: {
2947
+ type: "string",
2948
+ description: "The signal token the run waits on."
2949
+ },
2950
+ payload: {
2951
+ type: "string",
2952
+ description: "The payload, as JSON text."
2953
+ }
2954
+ },
2955
+ required: [
2956
+ "token"
2957
+ ]
2958
+ },
2959
+ available: controlPlane,
2960
+ async run(input) {
2961
+ const token = required(input, "token");
2962
+ let payload = null;
2963
+ if (typeof input.payload === "string" && input.payload.trim() !== "") {
2964
+ try {
2965
+ payload = JSON.parse(input.payload);
2966
+ } catch {
2967
+ throw new Error('"payload" must be JSON text (quote a plain string: "\\"ok\\"")');
2968
+ }
2969
+ }
2970
+ const result = await call(() => `token ${token}`, () => service.signal(token, payload));
2971
+ return result ? {
2972
+ delivered: true,
2973
+ runId: result.runId,
2974
+ status: result.status
2975
+ } : {
2976
+ delivered: false
2977
+ };
2978
+ }
2979
+ },
2980
+ {
2981
+ name: "durable_trigger_schedule",
2982
+ kind: "action",
2983
+ description: "Starts one run of a schedule now, outside its cadence (it does not count as a fire). Returns { runId, status }. An error when no schedule has that id.",
2984
+ input: {
2985
+ type: "object",
2986
+ properties: {
2987
+ key: scheduleKey
2988
+ },
2989
+ required: [
2990
+ "key"
2991
+ ]
2992
+ },
2993
+ available: schedules,
2994
+ async run(input) {
2995
+ const key = required(input, "key");
2996
+ return call(() => `schedule ${key}`, () => service.triggerSchedule(key));
2997
+ }
2998
+ },
2999
+ {
3000
+ name: "durable_pause_schedule",
3001
+ kind: "action",
3002
+ description: "Pauses a schedule (paused: true, the default) or resumes it from its next window (paused: false). Returns the schedule. An error when no schedule has that id.",
3003
+ input: {
3004
+ type: "object",
3005
+ properties: {
3006
+ key: scheduleKey,
3007
+ paused: {
3008
+ type: "boolean",
3009
+ description: "false resumes it (default true)."
3010
+ }
3011
+ },
3012
+ required: [
3013
+ "key"
3014
+ ]
3015
+ },
3016
+ available: schedules,
3017
+ async run(input) {
3018
+ const key = required(input, "key");
3019
+ return call(() => `schedule ${key}`, () => input.paused === false ? service.resumeSchedule(key) : service.pauseSchedule(key));
3020
+ }
3021
+ }
3022
+ ];
3023
+ }
3024
+ __name(durableDevTools, "durableDevTools");
3025
+ var logger2 = new import_common8.Logger("DurableDashboardModule");
3026
+ var DurableDevToolsRegistration = class {
3027
+ static {
3028
+ __name(this, "DurableDevToolsRegistration");
3029
+ }
3030
+ enabled;
3031
+ service;
3032
+ decide;
3033
+ #unregister;
3034
+ constructor(enabled, service, decide) {
3035
+ this.enabled = enabled;
3036
+ this.service = service;
3037
+ this.decide = decide;
3038
+ }
3039
+ onModuleInit() {
3040
+ if (!this.enabled) return;
3041
+ const slot = devToolsSlot();
3042
+ if (slot.version !== 1 || typeof slot.register !== "function") return;
3043
+ try {
3044
+ this.#unregister = slot.register({
3045
+ lib: "durable",
3046
+ title: "Durable workflows",
3047
+ authorize: /* @__PURE__ */ __name((context) => this.decide(context), "authorize"),
3048
+ tools: durableDevTools(this.service)
3049
+ });
3050
+ } catch (error) {
3051
+ logger2.warn(`durable dev tools not registered: ${error instanceof Error ? error.message : String(error)}`);
3052
+ }
3053
+ }
3054
+ onModuleDestroy() {
3055
+ this.#unregister?.();
3056
+ this.#unregister = void 0;
3057
+ }
3058
+ };
3059
+ DurableDevToolsRegistration = _ts_decorate8([
3060
+ (0, import_common8.Injectable)(),
3061
+ _ts_param5(0, (0, import_common8.Inject)(DASHBOARD_DEV_TOOLS)),
3062
+ _ts_param5(1, (0, import_common8.Inject)(DashboardService)),
3063
+ _ts_param5(2, (0, import_common8.Inject)(CONSOLE_AUTHORIZE_DECIDE)),
3064
+ _ts_metadata7("design:type", Function),
3065
+ _ts_metadata7("design:paramtypes", [
3066
+ Boolean,
3067
+ typeof DashboardService === "undefined" ? Object : DashboardService,
3068
+ typeof ConsoleDecide === "undefined" ? Object : ConsoleDecide
3069
+ ])
3070
+ ], DurableDevToolsRegistration);
3071
+
2012
3072
  // src/server/durable-ui.controller.ts
2013
3073
  var import_node_fs = require("fs");
2014
3074
  var import_node_path = require("path");
2015
3075
  var import_node_url = require("url");
2016
- var import_common7 = require("@nestjs/common");
2017
- function _ts_decorate7(decorators, target, key, desc) {
3076
+ var import_common9 = require("@nestjs/common");
3077
+ function _ts_decorate9(decorators, target, key, desc) {
2018
3078
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2019
3079
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2020
3080
  r = Reflect.decorate(decorators, target, key, desc);
@@ -2027,21 +3087,20 @@ function _ts_decorate7(decorators, target, key, desc) {
2027
3087
  }
2028
3088
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2029
3089
  }
2030
- __name(_ts_decorate7, "_ts_decorate");
2031
- function _ts_metadata6(metadataKey, metadataValue) {
3090
+ __name(_ts_decorate9, "_ts_decorate");
3091
+ function _ts_metadata8(metadataKey, metadataValue) {
2032
3092
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2033
3093
  return Reflect.metadata(metadataKey, metadataValue);
2034
3094
  }
2035
3095
  }
2036
- __name(_ts_metadata6, "_ts_metadata");
2037
- function _ts_param4(paramIndex, decorator) {
3096
+ __name(_ts_metadata8, "_ts_metadata");
3097
+ function _ts_param6(paramIndex, decorator) {
2038
3098
  return function(target, key) {
2039
3099
  decorator(target, key, paramIndex);
2040
3100
  };
2041
3101
  }
2042
- __name(_ts_param4, "_ts_param");
2043
- var DASHBOARD_BASE_PATH = Symbol("DASHBOARD_BASE_PATH");
2044
- var DASHBOARD_API_PATH = Symbol("DASHBOARD_API_PATH");
3102
+ __name(_ts_param6, "_ts_param");
3103
+ var DURABLE_DASHBOARD_CONFIG_ID = "durable-dashboard-config";
2045
3104
  var BUILD_BASE = "/durable";
2046
3105
  function spaDir() {
2047
3106
  return (0, import_node_url.fileURLToPath)(new URL("../spa", __importMetaUrl));
@@ -2072,51 +3131,65 @@ var DurableUiControllerBase = class {
2072
3131
  index() {
2073
3132
  const indexPath = (0, import_node_path.join)(this.dir, "index.html");
2074
3133
  if (!(0, import_node_fs.existsSync)(indexPath)) {
2075
- throw new import_common7.NotFoundException("Dashboard is not built. Run the package build.");
3134
+ throw new import_common9.NotFoundException("Dashboard is not built. Run the package build.");
2076
3135
  }
2077
3136
  const html = (0, import_node_fs.readFileSync)(indexPath, "utf8").replaceAll(`="${BUILD_BASE}/`, `="${this.basePath}/`);
2078
- const inject = `<script>window.__DURABLE_BASE__='${this.basePath}';window.__DURABLE_API__='${this.apiBasePath}';</script>`;
3137
+ const inject = `<script>window.__DURABLE_BASE__='${this.basePath}';window.__DURABLE_API__='${this.apiBasePath}';</script>${this.configBlock()}`;
2079
3138
  return html.includes("</head>") ? html.replace("</head>", `${inject}</head>`) : inject + html;
2080
3139
  }
3140
+ /**
3141
+ * The page's JSON config block (`durable-dashboard-config`, CSP-safe: data, not script). It
3142
+ * carries `switcher` only when another Aviary console is registered.
3143
+ */
3144
+ configBlock() {
3145
+ const switcher = consoleSwitcherConfig(DURABLE_CONSOLE_ID, `${this.apiBasePath}/consoles`);
3146
+ const json = JSON.stringify({
3147
+ apiBase: this.apiBasePath,
3148
+ ...switcher !== void 0 ? {
3149
+ switcher
3150
+ } : {}
3151
+ }).replaceAll("<", "\\u003c");
3152
+ return `<script type="application/json" id="${DURABLE_DASHBOARD_CONFIG_ID}">${json}</script>`;
3153
+ }
2081
3154
  asset(file) {
2082
3155
  const safe = (0, import_node_path.basename)(file);
2083
- if (safe !== file) throw new import_common7.NotFoundException();
3156
+ if (safe !== file) throw new import_common9.NotFoundException();
2084
3157
  const root = (0, import_node_path.resolve)(this.dir, "assets");
2085
3158
  const assetPath = (0, import_node_path.resolve)(root, safe);
2086
3159
  if (!assetPath.startsWith(root + import_node_path.sep) || !(0, import_node_fs.existsSync)(assetPath)) {
2087
- throw new import_common7.NotFoundException();
3160
+ throw new import_common9.NotFoundException();
2088
3161
  }
2089
3162
  const type = CONTENT_TYPES[(0, import_node_path.extname)(safe)] ?? "application/octet-stream";
2090
- return new import_common7.StreamableFile((0, import_node_fs.readFileSync)(assetPath), {
3163
+ return new import_common9.StreamableFile((0, import_node_fs.readFileSync)(assetPath), {
2091
3164
  type
2092
3165
  });
2093
3166
  }
2094
3167
  };
2095
- _ts_decorate7([
2096
- (0, import_common7.Get)(),
2097
- (0, import_common7.Header)("Content-Type", "text/html; charset=utf-8"),
2098
- (0, import_common7.Header)("Cache-Control", "no-store, must-revalidate"),
2099
- _ts_metadata6("design:type", Function),
2100
- _ts_metadata6("design:paramtypes", []),
2101
- _ts_metadata6("design:returntype", String)
3168
+ _ts_decorate9([
3169
+ (0, import_common9.Get)(),
3170
+ (0, import_common9.Header)("Content-Type", "text/html; charset=utf-8"),
3171
+ (0, import_common9.Header)("Cache-Control", "no-store, must-revalidate"),
3172
+ _ts_metadata8("design:type", Function),
3173
+ _ts_metadata8("design:paramtypes", []),
3174
+ _ts_metadata8("design:returntype", String)
2102
3175
  ], DurableUiControllerBase.prototype, "index", null);
2103
- _ts_decorate7([
2104
- (0, import_common7.Get)("assets/:file"),
2105
- (0, import_common7.Header)("Cache-Control", "public, max-age=31536000, immutable"),
2106
- _ts_param4(0, (0, import_common7.Param)("file")),
2107
- _ts_metadata6("design:type", Function),
2108
- _ts_metadata6("design:paramtypes", [
3176
+ _ts_decorate9([
3177
+ (0, import_common9.Get)("assets/:file"),
3178
+ (0, import_common9.Header)("Cache-Control", "public, max-age=31536000, immutable"),
3179
+ _ts_param6(0, (0, import_common9.Param)("file")),
3180
+ _ts_metadata8("design:type", Function),
3181
+ _ts_metadata8("design:paramtypes", [
2109
3182
  String
2110
3183
  ]),
2111
- _ts_metadata6("design:returntype", typeof import_common7.StreamableFile === "undefined" ? Object : import_common7.StreamableFile)
3184
+ _ts_metadata8("design:returntype", typeof import_common9.StreamableFile === "undefined" ? Object : import_common9.StreamableFile)
2112
3185
  ], DurableUiControllerBase.prototype, "asset", null);
2113
- DurableUiControllerBase = _ts_decorate7([
2114
- (0, import_common7.Controller)(),
3186
+ DurableUiControllerBase = _ts_decorate9([
3187
+ (0, import_common9.Controller)(),
2115
3188
  ConsolePage(),
2116
- _ts_param4(0, (0, import_common7.Inject)(DASHBOARD_BASE_PATH)),
2117
- _ts_param4(1, (0, import_common7.Inject)(DASHBOARD_API_PATH)),
2118
- _ts_metadata6("design:type", Function),
2119
- _ts_metadata6("design:paramtypes", [
3189
+ _ts_param6(0, (0, import_common9.Inject)(DASHBOARD_BASE_PATH)),
3190
+ _ts_param6(1, (0, import_common9.Inject)(DASHBOARD_API_PATH)),
3191
+ _ts_metadata8("design:type", Function),
3192
+ _ts_metadata8("design:paramtypes", [
2120
3193
  String,
2121
3194
  String
2122
3195
  ])
@@ -2126,12 +3199,12 @@ var DurableUiController = class extends DurableUiControllerBase {
2126
3199
  __name(this, "DurableUiController");
2127
3200
  }
2128
3201
  };
2129
- DurableUiController = _ts_decorate7([
2130
- (0, import_common7.Controller)()
3202
+ DurableUiController = _ts_decorate9([
3203
+ (0, import_common9.Controller)()
2131
3204
  ], DurableUiController);
2132
3205
 
2133
3206
  // src/server/durable-dashboard.module.ts
2134
- function _ts_decorate8(decorators, target, key, desc) {
3207
+ function _ts_decorate10(decorators, target, key, desc) {
2135
3208
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2136
3209
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2137
3210
  r = Reflect.decorate(decorators, target, key, desc);
@@ -2144,9 +3217,10 @@ function _ts_decorate8(decorators, target, key, desc) {
2144
3217
  }
2145
3218
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2146
3219
  }
2147
- __name(_ts_decorate8, "_ts_decorate");
3220
+ __name(_ts_decorate10, "_ts_decorate");
2148
3221
  markOpenToAuthorization(DurableApiController);
2149
3222
  markOpenToAuthorization(DurableUiController);
3223
+ markOpenToAuthorization(DurableConsolesController);
2150
3224
  function assertNothingToDecorate(options) {
2151
3225
  if (options.decorators === void 0) return;
2152
3226
  throw new Error([
@@ -2180,10 +3254,16 @@ var DurableApiModule = class _DurableApiModule {
2180
3254
  ...options.imports ?? []
2181
3255
  ],
2182
3256
  controllers: routes ? [
2183
- consoleController(DurableApiController, options.decorators)
3257
+ consoleController(DurableApiController, options.decorators),
3258
+ consoleController(DurableConsolesController, options.decorators)
2184
3259
  ] : [],
2185
3260
  providers: [
2186
3261
  DashboardService,
3262
+ {
3263
+ provide: DASHBOARD_DEV_TOOLS,
3264
+ useValue: options.devTools ?? routes
3265
+ },
3266
+ DurableDevToolsRegistration,
2187
3267
  // Resolved at boot: a gate name without nestjs-authz, or a policy without
2188
3268
  // @nestjs/authorization, fails here rather than on the first request.
2189
3269
  {
@@ -2241,8 +3321,8 @@ var DurableApiModule = class _DurableApiModule {
2241
3321
  };
2242
3322
  }
2243
3323
  };
2244
- DurableApiModule = _ts_decorate8([
2245
- (0, import_common8.Module)({})
3324
+ DurableApiModule = _ts_decorate10([
3325
+ (0, import_common10.Module)({})
2246
3326
  ], DurableApiModule);
2247
3327
  var DurableDashboardModule = class _DurableDashboardModule {
2248
3328
  static {
@@ -2270,6 +3350,9 @@ var DurableDashboardModule = class _DurableDashboardModule {
2270
3350
  ...options.authorize !== void 0 ? {
2271
3351
  authorize: options.authorize
2272
3352
  } : {},
3353
+ ...options.devTools !== void 0 ? {
3354
+ devTools: options.devTools
3355
+ } : {},
2273
3356
  routes: false
2274
3357
  });
2275
3358
  return {
@@ -2306,6 +3389,9 @@ var DurableDashboardModule = class _DurableDashboardModule {
2306
3389
  } : {},
2307
3390
  ...options.authorize !== void 0 ? {
2308
3391
  authorize: options.authorize
3392
+ } : {},
3393
+ ...options.devTools !== void 0 ? {
3394
+ devTools: options.devTools
2309
3395
  } : {}
2310
3396
  }),
2311
3397
  import_core2.RouterModule.register([
@@ -2322,7 +3408,11 @@ var DurableDashboardModule = class _DurableDashboardModule {
2322
3408
  controllers: [
2323
3409
  consoleController(DurableUiController, options.decorators)
2324
3410
  ],
2325
- providers: pathProviders,
3411
+ // The console switcher lists this console only where it serves its own page.
3412
+ providers: [
3413
+ ...pathProviders,
3414
+ DurableConsoleRegistration
3415
+ ],
2326
3416
  // Re-export the API module so its DashboardService reaches importers (e.g. flip's own controllers).
2327
3417
  exports: [
2328
3418
  DurableApiModule
@@ -2330,31 +3420,46 @@ var DurableDashboardModule = class _DurableDashboardModule {
2330
3420
  };
2331
3421
  }
2332
3422
  };
2333
- DurableDashboardModule = _ts_decorate8([
2334
- (0, import_common8.Module)({})
3423
+ DurableDashboardModule = _ts_decorate10([
3424
+ (0, import_common10.Module)({})
2335
3425
  ], DurableDashboardModule);
2336
3426
  // Annotate the CommonJS export names for ESM import in node:
2337
3427
  0 && (module.exports = {
3428
+ AVIARY_CONSOLES_SLOT,
2338
3429
  CONSOLE_AUTHORIZE_DECIDE,
2339
3430
  CONSOLE_AUTHORIZE_MISSING_WARNING,
2340
3431
  CONSOLE_GATE,
2341
3432
  CONSOLE_PAGE_METADATA,
3433
+ CONSOLE_PROBE_TIMEOUT_MS,
3434
+ CONSOLE_PUBLIC_METADATA,
2342
3435
  ConsoleAccessDenied,
2343
3436
  ConsoleAuthorizeFilter,
2344
3437
  ConsoleAuthorizeGuard,
2345
3438
  ConsolePage,
3439
+ ConsolePublic,
2346
3440
  DASHBOARD_API_PATH,
2347
3441
  DASHBOARD_BASE_PATH,
3442
+ DEV_TOOLS_SLOT,
3443
+ DURABLE_CONSOLE_ID,
3444
+ DURABLE_DASHBOARD_CONFIG_ID,
2348
3445
  DashboardService,
2349
3446
  DurableApiController,
2350
3447
  DurableApiControllerBase,
2351
3448
  DurableApiModule,
3449
+ DurableConsoleRegistration,
3450
+ DurableConsolesController,
2352
3451
  DurableDashboardModule,
3452
+ DurableDevToolsRegistration,
2353
3453
  DurableUiController,
2354
3454
  DurableUiControllerBase,
3455
+ MAX_RUN_IDS,
2355
3456
  UseConsoleAuthorize,
3457
+ anyAuthorize,
2356
3458
  authorizeByToken,
3459
+ durableDevTools,
3460
+ listVisibleConsoles,
2357
3461
  normalizeConsoleResult,
3462
+ registerAviaryConsole,
2358
3463
  resolveConsoleAuthorize
2359
3464
  });
2360
3465
  //# sourceMappingURL=index.cjs.map