@dudousxd/nestjs-durable-dashboard 0.40.0 → 0.42.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.
@@ -1,5 +1,5 @@
1
1
  import * as _dudousxd_nestjs_durable_core from '@dudousxd/nestjs-durable-core';
2
- import { RunGateway, StateStore, WorkflowEngine, RunQuery, RunListItem, DurableTopology, GroupHealth, RunDetail, RunResult, UpdateResult, EngineEvent, RunStatus } from '@dudousxd/nestjs-durable-core';
2
+ import { RunGateway, StateStore, WorkflowEngine, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, DurableTopology, GroupHealth, RunDetail, RunResult, UpdateResult, EngineEvent, RunStatus } from '@dudousxd/nestjs-durable-core';
3
3
  export { RunDetail } from '@dudousxd/nestjs-durable-core';
4
4
  import * as rxjs from 'rxjs';
5
5
  import { Observable } from 'rxjs';
@@ -134,6 +134,9 @@ declare class DashboardService {
134
134
  */
135
135
  metrics(): Promise<string>;
136
136
  listRuns(query: RunQuery): Promise<RunListItem[]>;
137
+ /** `(status, origin)` counts over the same predicates {@link listRuns} pages — what keeps the
138
+ * console's chips exact while its list is bounded to a page. */
139
+ runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
137
140
  /** This deployment's durable role (control plane vs tenant) + tenant name — for the dashboard header
138
141
  * badge. Routes through the gateway, which knows its own topology (store-backed = operator, proxy =
139
142
  * tenant). Works on both topologies (no store/engine needed). */
@@ -154,11 +157,15 @@ declare class DashboardService {
154
157
  compensate?: boolean;
155
158
  }): Promise<RunResult | null>;
156
159
  /**
157
- * Apply `retry` or `cancel` to every run matching a filter (status / tag / workflow) — e.g. "retry
158
- * every `dead` run tagged `type:mel`". Capped at 500 runs per call; runs that can't be acted on
160
+ * Apply `retry` or `cancel` to every run matching a filter (status / tag / workflow / namespace /
161
+ * origin) — e.g. "retry every `dead` run tagged `type:mel`". The filter is the same one the run
162
+ * list uses, so a bulk action can never reach wider than the list it was launched from — but note
163
+ * an ABSENT predicate still means "every value on that axis" (an omitted `namespace` is every
164
+ * tenant), which is what makes passing the operator's full filter through mandatory, not optional.
165
+ * Capped at 500 runs per call; runs that can't be acted on
159
166
  * (already terminal) are skipped. Returns how many matched and how many the action applied to.
160
167
  */
161
- bulk(action: 'retry' | 'cancel', filter: Pick<RunQuery, 'status' | 'tag' | 'workflow' | 'attributes'>, opts?: {
168
+ bulk(action: 'retry' | 'cancel', filter: Pick<RunQuery, 'status' | 'tag' | 'workflow' | 'attributes' | 'namespace' | 'origin'>, opts?: {
162
169
  compensate?: boolean;
163
170
  }): Promise<{
164
171
  matched: number;
@@ -191,11 +198,40 @@ declare class DashboardService {
191
198
  }>;
192
199
  }
193
200
 
201
+ /**
202
+ * A run as the CONSOLE'S LIST needs it: every field a row or its derived state reads, and none of the
203
+ * payloads only the detail view opens.
204
+ *
205
+ * `input`, `output` and `error` are dropped. On a real control plane they dominate the response —
206
+ * measured against a 9.5k-run deployment, `error` alone (stack traces on every failed run) was 63% of
207
+ * a 12 MB listing — while no list row, and no `deriveRunState` input, ever reads them. The detail
208
+ * endpoint (`GET runs/:id`) still returns the whole run, which is where those three are actually
209
+ * rendered.
210
+ */
211
+ type RunListRow = Omit<RunListItem, 'input' | 'output' | 'error'>;
212
+
194
213
  /** JSON API consumed by the control-plane SPA. Mounted at `apiBasePath` (set by RouterModule). */
195
214
  declare class DurableApiController {
196
215
  private readonly dashboard;
197
216
  constructor(dashboard: DashboardService);
198
- runs(status?: RunStatus, workflow?: string, tag?: string, attr?: string | string[]): Promise<_dudousxd_nestjs_durable_core.RunListItem[]>;
217
+ /**
218
+ * The run list, filtered by whatever the operator asked for. Every predicate is OPTIONAL and an
219
+ * omitted one means "don't narrow on this axis" — in particular `namespace`, whose absence keeps
220
+ * the historical, deliberate behaviour that read paths are NOT namespace-scoped (core,
221
+ * `WorkflowRun.namespace`): the console shows every tenant's runs until an operator chooses one.
222
+ * A blank param (`?namespace=`, from a cleared filter box) is the same as an absent one; passing
223
+ * `''` through would be an exact match on a tenant nobody has, i.e. a silently empty console.
224
+ */
225
+ runs(status?: RunStatus | RunStatus[], workflow?: string, tag?: string, attr?: string | string[], namespace?: string, origin?: string, unattributed?: string, limit?: string, offset?: string): Promise<RunListRow[]>;
226
+ /**
227
+ * `(status, origin)` counts for the runs matching the same tag/tenant/attribute predicates as
228
+ * `GET runs`. This is what lets that listing be PAGED at all: the page bounds what the console
229
+ * renders, this bounds nothing and keeps its status and origin chips exact.
230
+ *
231
+ * `status`, `origin` and the page bounds are deliberately NOT accepted — they are the axes being
232
+ * counted, so narrowing by them would report the answer back to itself.
233
+ */
234
+ facets(workflow?: string, tag?: string, attr?: string | string[], namespace?: string): Promise<_dudousxd_nestjs_durable_core.RunFacetRow[]>;
199
235
  /** Prometheus-text metrics (runs/steps by outcome, per-workflow counts) for a scrape. */
200
236
  metrics(): Promise<string>;
201
237
  /** Per-group worker health (queue backlog + live worker heartbeats) for the Workers panel. */
@@ -215,8 +251,12 @@ declare class DurableApiController {
215
251
  }): Promise<{
216
252
  runId: string;
217
253
  } | null>;
218
- /** Bulk retry/cancel every run matching a filter (status / tag / workflow). */
219
- bulk(action: 'retry' | 'cancel', status?: RunStatus, tag?: string, workflow?: string, attr?: string | string[], compensate?: string): Promise<{
254
+ /**
255
+ * Bulk retry/cancel every run matching a filter (status / tag / workflow / namespace / origin).
256
+ * The filter takes the SAME params as `GET runs` and is parsed identically, so "retry all" acts on
257
+ * exactly the set the operator was looking at rather than a wider one.
258
+ */
259
+ bulk(action: 'retry' | 'cancel', status?: RunStatus, tag?: string, workflow?: string, attr?: string | string[], compensate?: string, namespace?: string, origin?: string, unattributed?: string): Promise<{
220
260
  matched: number;
221
261
  applied: number;
222
262
  }>;
@@ -1,5 +1,5 @@
1
1
  import * as _dudousxd_nestjs_durable_core from '@dudousxd/nestjs-durable-core';
2
- import { RunGateway, StateStore, WorkflowEngine, RunQuery, RunListItem, DurableTopology, GroupHealth, RunDetail, RunResult, UpdateResult, EngineEvent, RunStatus } from '@dudousxd/nestjs-durable-core';
2
+ import { RunGateway, StateStore, WorkflowEngine, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, DurableTopology, GroupHealth, RunDetail, RunResult, UpdateResult, EngineEvent, RunStatus } from '@dudousxd/nestjs-durable-core';
3
3
  export { RunDetail } from '@dudousxd/nestjs-durable-core';
4
4
  import * as rxjs from 'rxjs';
5
5
  import { Observable } from 'rxjs';
@@ -134,6 +134,9 @@ declare class DashboardService {
134
134
  */
135
135
  metrics(): Promise<string>;
136
136
  listRuns(query: RunQuery): Promise<RunListItem[]>;
137
+ /** `(status, origin)` counts over the same predicates {@link listRuns} pages — what keeps the
138
+ * console's chips exact while its list is bounded to a page. */
139
+ runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
137
140
  /** This deployment's durable role (control plane vs tenant) + tenant name — for the dashboard header
138
141
  * badge. Routes through the gateway, which knows its own topology (store-backed = operator, proxy =
139
142
  * tenant). Works on both topologies (no store/engine needed). */
@@ -154,11 +157,15 @@ declare class DashboardService {
154
157
  compensate?: boolean;
155
158
  }): Promise<RunResult | null>;
156
159
  /**
157
- * Apply `retry` or `cancel` to every run matching a filter (status / tag / workflow) — e.g. "retry
158
- * every `dead` run tagged `type:mel`". Capped at 500 runs per call; runs that can't be acted on
160
+ * Apply `retry` or `cancel` to every run matching a filter (status / tag / workflow / namespace /
161
+ * origin) — e.g. "retry every `dead` run tagged `type:mel`". The filter is the same one the run
162
+ * list uses, so a bulk action can never reach wider than the list it was launched from — but note
163
+ * an ABSENT predicate still means "every value on that axis" (an omitted `namespace` is every
164
+ * tenant), which is what makes passing the operator's full filter through mandatory, not optional.
165
+ * Capped at 500 runs per call; runs that can't be acted on
159
166
  * (already terminal) are skipped. Returns how many matched and how many the action applied to.
160
167
  */
161
- bulk(action: 'retry' | 'cancel', filter: Pick<RunQuery, 'status' | 'tag' | 'workflow' | 'attributes'>, opts?: {
168
+ bulk(action: 'retry' | 'cancel', filter: Pick<RunQuery, 'status' | 'tag' | 'workflow' | 'attributes' | 'namespace' | 'origin'>, opts?: {
162
169
  compensate?: boolean;
163
170
  }): Promise<{
164
171
  matched: number;
@@ -191,11 +198,40 @@ declare class DashboardService {
191
198
  }>;
192
199
  }
193
200
 
201
+ /**
202
+ * A run as the CONSOLE'S LIST needs it: every field a row or its derived state reads, and none of the
203
+ * payloads only the detail view opens.
204
+ *
205
+ * `input`, `output` and `error` are dropped. On a real control plane they dominate the response —
206
+ * measured against a 9.5k-run deployment, `error` alone (stack traces on every failed run) was 63% of
207
+ * a 12 MB listing — while no list row, and no `deriveRunState` input, ever reads them. The detail
208
+ * endpoint (`GET runs/:id`) still returns the whole run, which is where those three are actually
209
+ * rendered.
210
+ */
211
+ type RunListRow = Omit<RunListItem, 'input' | 'output' | 'error'>;
212
+
194
213
  /** JSON API consumed by the control-plane SPA. Mounted at `apiBasePath` (set by RouterModule). */
195
214
  declare class DurableApiController {
196
215
  private readonly dashboard;
197
216
  constructor(dashboard: DashboardService);
198
- runs(status?: RunStatus, workflow?: string, tag?: string, attr?: string | string[]): Promise<_dudousxd_nestjs_durable_core.RunListItem[]>;
217
+ /**
218
+ * The run list, filtered by whatever the operator asked for. Every predicate is OPTIONAL and an
219
+ * omitted one means "don't narrow on this axis" — in particular `namespace`, whose absence keeps
220
+ * the historical, deliberate behaviour that read paths are NOT namespace-scoped (core,
221
+ * `WorkflowRun.namespace`): the console shows every tenant's runs until an operator chooses one.
222
+ * A blank param (`?namespace=`, from a cleared filter box) is the same as an absent one; passing
223
+ * `''` through would be an exact match on a tenant nobody has, i.e. a silently empty console.
224
+ */
225
+ runs(status?: RunStatus | RunStatus[], workflow?: string, tag?: string, attr?: string | string[], namespace?: string, origin?: string, unattributed?: string, limit?: string, offset?: string): Promise<RunListRow[]>;
226
+ /**
227
+ * `(status, origin)` counts for the runs matching the same tag/tenant/attribute predicates as
228
+ * `GET runs`. This is what lets that listing be PAGED at all: the page bounds what the console
229
+ * renders, this bounds nothing and keeps its status and origin chips exact.
230
+ *
231
+ * `status`, `origin` and the page bounds are deliberately NOT accepted — they are the axes being
232
+ * counted, so narrowing by them would report the answer back to itself.
233
+ */
234
+ facets(workflow?: string, tag?: string, attr?: string | string[], namespace?: string): Promise<_dudousxd_nestjs_durable_core.RunFacetRow[]>;
199
235
  /** Prometheus-text metrics (runs/steps by outcome, per-workflow counts) for a scrape. */
200
236
  metrics(): Promise<string>;
201
237
  /** Per-group worker health (queue backlog + live worker heartbeats) for the Workers panel. */
@@ -215,8 +251,12 @@ declare class DurableApiController {
215
251
  }): Promise<{
216
252
  runId: string;
217
253
  } | null>;
218
- /** Bulk retry/cancel every run matching a filter (status / tag / workflow). */
219
- bulk(action: 'retry' | 'cancel', status?: RunStatus, tag?: string, workflow?: string, attr?: string | string[], compensate?: string): Promise<{
254
+ /**
255
+ * Bulk retry/cancel every run matching a filter (status / tag / workflow / namespace / origin).
256
+ * The filter takes the SAME params as `GET runs` and is parsed identically, so "retry all" acts on
257
+ * exactly the set the operator was looking at rather than a wider one.
258
+ */
259
+ bulk(action: 'retry' | 'cancel', status?: RunStatus, tag?: string, workflow?: string, attr?: string | string[], compensate?: string, namespace?: string, origin?: string, unattributed?: string): Promise<{
220
260
  matched: number;
221
261
  applied: number;
222
262
  }>;
@@ -101,6 +101,11 @@ var DashboardService = class {
101
101
  listRuns(query) {
102
102
  return this.gateway.listRuns(query);
103
103
  }
104
+ /** `(status, origin)` counts over the same predicates {@link listRuns} pages — what keeps the
105
+ * console's chips exact while its list is bounded to a page. */
106
+ runFacets(query) {
107
+ return this.gateway.runFacets(query);
108
+ }
104
109
  /** This deployment's durable role (control plane vs tenant) + tenant name — for the dashboard header
105
110
  * badge. Routes through the gateway, which knows its own topology (store-backed = operator, proxy =
106
111
  * tenant). Works on both topologies (no store/engine needed). */
@@ -129,8 +134,12 @@ var DashboardService = class {
129
134
  return this.gateway.cancel(runId, opts);
130
135
  }
131
136
  /**
132
- * Apply `retry` or `cancel` to every run matching a filter (status / tag / workflow) — e.g. "retry
133
- * every `dead` run tagged `type:mel`". Capped at 500 runs per call; runs that can't be acted on
137
+ * Apply `retry` or `cancel` to every run matching a filter (status / tag / workflow / namespace /
138
+ * origin) — e.g. "retry every `dead` run tagged `type:mel`". The filter is the same one the run
139
+ * list uses, so a bulk action can never reach wider than the list it was launched from — but note
140
+ * an ABSENT predicate still means "every value on that axis" (an omitted `namespace` is every
141
+ * tenant), which is what makes passing the operator's full filter through mandatory, not optional.
142
+ * Capped at 500 runs per call; runs that can't be acted on
134
143
  * (already terminal) are skipped. Returns how many matched and how many the action applied to.
135
144
  */
136
145
  async bulk(action, filter, opts) {
@@ -242,6 +251,13 @@ function parseAttrFilters(attr) {
242
251
  }
243
252
  __name(parseAttrFilters, "parseAttrFilters");
244
253
 
254
+ // src/server/run-list-row.ts
255
+ function toRunListRow(run) {
256
+ const { input: _input, output: _output, error: _error, ...row } = run;
257
+ return row;
258
+ }
259
+ __name(toRunListRow, "toRunListRow");
260
+
245
261
  // src/server/durable-api.controller.ts
246
262
  function _ts_decorate2(decorators, target, key, desc) {
247
263
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -260,6 +276,41 @@ function _ts_param2(paramIndex, decorator) {
260
276
  };
261
277
  }
262
278
  __name(_ts_param2, "_ts_param");
279
+ function pageBounds(limit, offset) {
280
+ const bound = /* @__PURE__ */ __name((raw) => {
281
+ const n = Number(raw);
282
+ return raw !== void 0 && raw !== "" && Number.isInteger(n) && n >= 0 ? n : void 0;
283
+ }, "bound");
284
+ const l = bound(limit);
285
+ const o = bound(offset);
286
+ return {
287
+ ...l !== void 0 ? {
288
+ limit: l
289
+ } : {},
290
+ ...o !== void 0 ? {
291
+ offset: o
292
+ } : {}
293
+ };
294
+ }
295
+ __name(pageBounds, "pageBounds");
296
+ function statusPredicate(status) {
297
+ if (Array.isArray(status)) return status.length ? {
298
+ statuses: status
299
+ } : {};
300
+ return status ? {
301
+ status
302
+ } : {};
303
+ }
304
+ __name(statusPredicate, "statusPredicate");
305
+ function originPredicate(origin, unattributed) {
306
+ if (unattributed === "true" || unattributed === "1") return {
307
+ origin: null
308
+ };
309
+ return origin ? {
310
+ origin
311
+ } : {};
312
+ }
313
+ __name(originPredicate, "originPredicate");
263
314
  var DurableApiController = class {
264
315
  static {
265
316
  __name(this, "DurableApiController");
@@ -268,12 +319,40 @@ var DurableApiController = class {
268
319
  constructor(dashboard) {
269
320
  this.dashboard = dashboard;
270
321
  }
271
- runs(status, workflow, tag, attr) {
272
- return this.dashboard.listRuns({
273
- status,
322
+ /**
323
+ * The run list, filtered by whatever the operator asked for. Every predicate is OPTIONAL and an
324
+ * omitted one means "don't narrow on this axis" — in particular `namespace`, whose absence keeps
325
+ * the historical, deliberate behaviour that read paths are NOT namespace-scoped (core,
326
+ * `WorkflowRun.namespace`): the console shows every tenant's runs until an operator chooses one.
327
+ * A blank param (`?namespace=`, from a cleared filter box) is the same as an absent one; passing
328
+ * `''` through would be an exact match on a tenant nobody has, i.e. a silently empty console.
329
+ */
330
+ async runs(status, workflow, tag, attr, namespace, origin, unattributed, limit, offset) {
331
+ const runs = await this.dashboard.listRuns({
332
+ ...statusPredicate(status),
274
333
  workflow,
275
334
  tag,
276
- attributes: parseAttrFilters(attr)
335
+ attributes: parseAttrFilters(attr),
336
+ namespace: namespace || void 0,
337
+ ...originPredicate(origin, unattributed),
338
+ ...pageBounds(limit, offset)
339
+ });
340
+ return runs.map(toRunListRow);
341
+ }
342
+ /**
343
+ * `(status, origin)` counts for the runs matching the same tag/tenant/attribute predicates as
344
+ * `GET runs`. This is what lets that listing be PAGED at all: the page bounds what the console
345
+ * renders, this bounds nothing and keeps its status and origin chips exact.
346
+ *
347
+ * `status`, `origin` and the page bounds are deliberately NOT accepted — they are the axes being
348
+ * counted, so narrowing by them would report the answer back to itself.
349
+ */
350
+ facets(workflow, tag, attr, namespace) {
351
+ return this.dashboard.runFacets({
352
+ workflow,
353
+ tag,
354
+ attributes: parseAttrFilters(attr),
355
+ namespace: namespace || void 0
277
356
  });
278
357
  }
279
358
  /** Prometheus-text metrics (runs/steps by outcome, per-workflow counts) for a scrape. */
@@ -305,13 +384,22 @@ var DurableApiController = class {
305
384
  retryWithInput(id, body) {
306
385
  return this.dashboard.retryWithInput(id, body?.input);
307
386
  }
308
- /** Bulk retry/cancel every run matching a filter (status / tag / workflow). */
309
- bulk(action, status, tag, workflow, attr, compensate) {
387
+ /**
388
+ * Bulk retry/cancel every run matching a filter (status / tag / workflow / namespace / origin).
389
+ * The filter takes the SAME params as `GET runs` and is parsed identically, so "retry all" acts on
390
+ * exactly the set the operator was looking at rather than a wider one.
391
+ */
392
+ bulk(action, status, tag, workflow, attr, compensate, namespace, origin, unattributed) {
310
393
  return this.dashboard.bulk(action === "cancel" ? "cancel" : "retry", {
311
394
  status,
312
395
  tag,
313
396
  workflow,
314
- attributes: parseAttrFilters(attr)
397
+ attributes: parseAttrFilters(attr),
398
+ namespace: namespace || void 0,
399
+ // The unattributed bucket is a scope a console can actually SELECT, so a bulk action launched
400
+ // under it must be narrowed to it. Dropping the param would widen a retry/cancel to every
401
+ // origin — acting on runs the operator was not looking at.
402
+ ...originPredicate(origin, unattributed)
315
403
  }, {
316
404
  compensate: compensate === "true"
317
405
  });
@@ -360,15 +448,40 @@ _ts_decorate2([
360
448
  _ts_param2(1, Query("workflow")),
361
449
  _ts_param2(2, Query("tag")),
362
450
  _ts_param2(3, Query("attr")),
451
+ _ts_param2(4, Query("namespace")),
452
+ _ts_param2(5, Query("origin")),
453
+ _ts_param2(6, Query("unattributed")),
454
+ _ts_param2(7, Query("limit")),
455
+ _ts_param2(8, Query("offset")),
363
456
  _ts_metadata2("design:type", Function),
364
457
  _ts_metadata2("design:paramtypes", [
365
- typeof RunStatus === "undefined" ? Object : RunStatus,
458
+ Object,
366
459
  String,
367
460
  String,
368
- Object
461
+ Object,
462
+ String,
463
+ String,
464
+ String,
465
+ String,
466
+ String
369
467
  ]),
370
- _ts_metadata2("design:returntype", void 0)
468
+ _ts_metadata2("design:returntype", Promise)
371
469
  ], DurableApiController.prototype, "runs", null);
470
+ _ts_decorate2([
471
+ Get("runs/facets"),
472
+ _ts_param2(0, Query("workflow")),
473
+ _ts_param2(1, Query("tag")),
474
+ _ts_param2(2, Query("attr")),
475
+ _ts_param2(3, Query("namespace")),
476
+ _ts_metadata2("design:type", Function),
477
+ _ts_metadata2("design:paramtypes", [
478
+ String,
479
+ String,
480
+ Object,
481
+ String
482
+ ]),
483
+ _ts_metadata2("design:returntype", void 0)
484
+ ], DurableApiController.prototype, "facets", null);
372
485
  _ts_decorate2([
373
486
  Get("metrics"),
374
487
  Header("Content-Type", "text/plain; version=0.0.4"),
@@ -434,6 +547,9 @@ _ts_decorate2([
434
547
  _ts_param2(3, Query("workflow")),
435
548
  _ts_param2(4, Query("attr")),
436
549
  _ts_param2(5, Query("compensate")),
550
+ _ts_param2(6, Query("namespace")),
551
+ _ts_param2(7, Query("origin")),
552
+ _ts_param2(8, Query("unattributed")),
437
553
  _ts_metadata2("design:type", Function),
438
554
  _ts_metadata2("design:paramtypes", [
439
555
  String,
@@ -441,6 +557,9 @@ _ts_decorate2([
441
557
  String,
442
558
  String,
443
559
  Object,
560
+ String,
561
+ String,
562
+ String,
444
563
  String
445
564
  ]),
446
565
  _ts_metadata2("design:returntype", void 0)