@dudousxd/nestjs-durable-dashboard 0.43.2 → 0.44.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.
@@ -106,6 +106,12 @@ var DashboardService = class {
106
106
  runFacets(query) {
107
107
  return this.gateway.runFacets(query);
108
108
  }
109
+ /** The distinct values of one filter axis over the same predicates — what fills the console's
110
+ * tenant/tag/attribute pickers, so an operator picks a value that exists instead of typing one
111
+ * blind. `[]` when the store cannot enumerate them; the pickers then fall back to free text. */
112
+ runValueFacets(axis, query, opts) {
113
+ return this.gateway.runValueFacets(axis, query, opts);
114
+ }
109
115
  /** This deployment's durable role (control plane vs tenant) + tenant name — for the dashboard header
110
116
  * badge. Routes through the gateway, which knows its own topology (store-backed = operator, proxy =
111
117
  * tenant). Works on both topologies (no store/engine needed). */
@@ -214,10 +220,95 @@ DashboardService = _ts_decorate([
214
220
  ], DashboardService);
215
221
 
216
222
  // src/server/durable-api.controller.ts
223
+ import { ApplyFilter, FilterRunner } from "@dudousxd/nestjs-filter";
217
224
  import { Body, Controller, Get, Header, NotFoundException, Param, Post, Query, Sse } from "@nestjs/common";
218
225
 
226
+ // src/server/durable-run.ts
227
+ var DurableRun = class {
228
+ static {
229
+ __name(this, "DurableRun");
230
+ }
231
+ workflow;
232
+ status;
233
+ /** `null` for a run no package claims — see `WorkflowRun.origin`. */
234
+ origin;
235
+ /** Tenant / worker-pool partition. */
236
+ namespace;
237
+ /**
238
+ * ONE of the run's tags. Singular deliberately: a run carries a SET of tags, so a predicate here
239
+ * asks about membership (`tag in [etl, nightly]` = runs carrying either), never equality with the
240
+ * whole set.
241
+ */
242
+ tag;
243
+ /** Typed search attributes, addressed per key as `attr.<key>` — a JSON sub-path, not a column. */
244
+ attr;
245
+ };
246
+ var ATTRIBUTE_FIELD = "attr";
247
+ var DURABLE_RUN_FIELDS = [
248
+ {
249
+ name: "workflow",
250
+ columnName: "workflow",
251
+ type: "string"
252
+ },
253
+ {
254
+ name: "status",
255
+ columnName: "status",
256
+ type: "string"
257
+ },
258
+ {
259
+ name: "origin",
260
+ columnName: "origin",
261
+ type: "string"
262
+ },
263
+ {
264
+ name: "namespace",
265
+ columnName: "namespace",
266
+ type: "string"
267
+ },
268
+ {
269
+ name: "tag",
270
+ columnName: "tags",
271
+ type: "string"
272
+ },
273
+ {
274
+ name: ATTRIBUTE_FIELD,
275
+ columnName: "search_attributes",
276
+ type: "json"
277
+ }
278
+ ];
279
+ function runValueAxisFor(field) {
280
+ if (field === ATTRIBUTE_FIELD) return {
281
+ field: "attributeKey"
282
+ };
283
+ if (field.startsWith(`${ATTRIBUTE_FIELD}.`)) {
284
+ const key = field.slice(ATTRIBUTE_FIELD.length + 1);
285
+ return key ? {
286
+ field: "attributeValue",
287
+ key
288
+ } : null;
289
+ }
290
+ if (field === "workflow" || field === "status" || field === "origin" || field === "namespace" || field === "tag") {
291
+ return {
292
+ field
293
+ };
294
+ }
295
+ return null;
296
+ }
297
+ __name(runValueAxisFor, "runValueAxisFor");
298
+
299
+ // src/server/run-list-row.ts
300
+ function toRunListRow(run) {
301
+ const { input: _input, output: _output, error: _error, ...row } = run;
302
+ return row;
303
+ }
304
+ __name(toRunListRow, "toRunListRow");
305
+
306
+ // src/server/run.filter.ts
307
+ import { BaseFilter, FilterFor, Filterable } from "@dudousxd/nestjs-filter";
308
+ import { Injectable as Injectable3 } from "@nestjs/common";
309
+
219
310
  // src/server/attr-filter.ts
220
- var ATTR_OPS = /* @__PURE__ */ new Set([
311
+ var SCALAR_OPS = /* @__PURE__ */ new Set([
221
312
  "eq",
222
313
  "ne",
223
314
  "gt",
@@ -225,6 +316,7 @@ var ATTR_OPS = /* @__PURE__ */ new Set([
225
316
  "lt",
226
317
  "lte"
227
318
  ]);
319
+ var IN_SEPARATOR = "|";
228
320
  function coerce(v) {
229
321
  if (v === "true") return true;
230
322
  if (v === "false") return false;
@@ -240,25 +332,60 @@ function parseAttrFilters(attr) {
240
332
  const filters = [];
241
333
  for (const entry of raw) {
242
334
  const [key, op, ...rest] = entry.split(":");
243
- if (!key || !ATTR_OPS.has(op) || rest.length === 0) continue;
335
+ if (!key || rest.length === 0) continue;
336
+ const operand = rest.join(":");
337
+ if (op === "in") {
338
+ const values = operand.split(IN_SEPARATOR).filter(Boolean).map(coerce);
339
+ if (values.length) filters.push({
340
+ key,
341
+ op: "in",
342
+ values
343
+ });
344
+ continue;
345
+ }
346
+ if (!SCALAR_OPS.has(op)) continue;
244
347
  filters.push({
245
348
  key,
246
349
  op,
247
- value: coerce(rest.join(":"))
350
+ value: coerce(operand)
248
351
  });
249
352
  }
250
353
  return filters.length ? filters : void 0;
251
354
  }
252
355
  __name(parseAttrFilters, "parseAttrFilters");
253
356
 
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");
357
+ // src/server/run-query.adapter.ts
358
+ import { RUN_VALUE_FACET_LIMIT, RunGateway as RunGateway2 } from "@dudousxd/nestjs-durable-core";
359
+ import { BadRequestException, Injectable as Injectable2 } from "@nestjs/common";
260
360
 
261
- // src/server/durable-api.controller.ts
361
+ // src/server/run-query-draft.ts
362
+ var RunQueryDraft2 = class {
363
+ static {
364
+ __name(this, "RunQueryDraft");
365
+ }
366
+ query = {};
367
+ /** Narrow by one or more predicates. Later calls overwrite the same field — the runner applies one
368
+ * clause per field, so the last one wins, exactly as a repeated `where` on a builder would. */
369
+ narrow(patch) {
370
+ Object.assign(this.query, patch);
371
+ }
372
+ /** Add one search-attribute predicate. These ACCUMULATE (unlike {@link narrow}) because
373
+ * `RunQuery.attributes` is an ANDed list — two predicates on different keys are both meant. */
374
+ attribute(filter) {
375
+ this.query.attributes = [
376
+ ...this.query.attributes ?? [],
377
+ filter
378
+ ];
379
+ }
380
+ /** The predicates minus the page window and the axes a facet count reports on — what
381
+ * `runFacets`/`runValueFacets` are taken over. */
382
+ facetQuery() {
383
+ const { status, statuses, origin, origins, limit, offset, ...facetQuery } = this.query;
384
+ return facetQuery;
385
+ }
386
+ };
387
+
388
+ // src/server/run-query.adapter.ts
262
389
  function _ts_decorate2(decorators, target, key, desc) {
263
390
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
264
391
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -270,73 +397,527 @@ function _ts_metadata2(k, v) {
270
397
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
271
398
  }
272
399
  __name(_ts_metadata2, "_ts_metadata");
400
+ var RUN_QUERY_ADAPTER = Symbol("DURABLE_RUN_QUERY_ADAPTER");
401
+ var FIXED_ORDER = {
402
+ field: "createdAt",
403
+ direction: "desc"
404
+ };
405
+ function coerce2(value) {
406
+ if (typeof value === "number" || typeof value === "boolean") return value;
407
+ const raw = String(value);
408
+ if (raw === "true") return true;
409
+ if (raw === "false") return false;
410
+ if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
411
+ return raw;
412
+ }
413
+ __name(coerce2, "coerce");
414
+ function operands(clause) {
415
+ return Array.isArray(clause.value) ? clause.value : [
416
+ clause.value
417
+ ];
418
+ }
419
+ __name(operands, "operands");
420
+ function unsupported(clause, supported) {
421
+ throw new BadRequestException(`The durable run list cannot filter \`${clause.field}\` with \`${clause.operator}\`. Supported here: ${supported}.`);
422
+ }
423
+ __name(unsupported, "unsupported");
424
+ var RunQueryAdapter = class {
425
+ static {
426
+ __name(this, "RunQueryAdapter");
427
+ }
428
+ gateway;
429
+ constructor(gateway) {
430
+ this.gateway = gateway;
431
+ }
432
+ createQueryBuilder(entity) {
433
+ this.assertDurableRun(entity);
434
+ return new RunQueryDraft2();
435
+ }
436
+ getEntityFields(entity) {
437
+ return entity === DurableRun ? DURABLE_RUN_FIELDS : null;
438
+ }
439
+ /** No relations: a run is a flat row here, and the one nested-looking address (`attr.<key>`) is a
440
+ * JSON path. Returning `[]` (not null) tells the runner the answer is "none", not "can't say". */
441
+ getEntityRelations() {
442
+ return [];
443
+ }
444
+ /**
445
+ * Which addresses resolve, and as what. `attr` and `attr.<key>` are JSON paths — that classification
446
+ * is what routes a flat `?attr.tier=pro` through the same translation as a structured
447
+ * `where[attr.tier]`, instead of it being dropped as an unknown key.
448
+ */
449
+ resolveFieldPath(entity, path) {
450
+ if (entity !== DurableRun) return null;
451
+ if (path === ATTRIBUTE_FIELD || path.startsWith(`${ATTRIBUTE_FIELD}.`)) return "json";
452
+ return DURABLE_RUN_FIELDS.some((f) => f.name === path) ? "field" : null;
453
+ }
454
+ applyColumnFilters(qb, filters, entity) {
455
+ if (entity) this.assertDurableRun(entity);
456
+ const draft = this.draft(qb);
457
+ for (const clause of filters) {
458
+ if (clause.AND || clause.OR) {
459
+ throw new BadRequestException("The durable run list cannot filter with nested AND/OR groups: its predicates are a flat conjunction.");
460
+ }
461
+ this.applyClause(draft, clause);
462
+ }
463
+ }
464
+ applyClause(draft, clause) {
465
+ const { field, operator } = clause;
466
+ if (field === ATTRIBUTE_FIELD || field.startsWith(`${ATTRIBUTE_FIELD}.`)) {
467
+ this.applyAttributeClause(draft, clause);
468
+ return;
469
+ }
470
+ const anyOf = operator === "in" || operator === "isAnyOf";
471
+ switch (field) {
472
+ case "status":
473
+ if (operator === "equals") {
474
+ draft.narrow({
475
+ status: String(clause.value)
476
+ });
477
+ } else if (anyOf) {
478
+ draft.narrow({
479
+ statuses: operands(clause).map((v) => String(v))
480
+ });
481
+ } else {
482
+ unsupported(clause, "equals, in");
483
+ }
484
+ return;
485
+ case "workflow":
486
+ if (operator === "equals") {
487
+ draft.narrow({
488
+ workflow: String(clause.value)
489
+ });
490
+ } else if (anyOf) {
491
+ draft.narrow({
492
+ workflows: operands(clause).map(String)
493
+ });
494
+ } else {
495
+ unsupported(clause, "equals, in");
496
+ }
497
+ return;
498
+ case "namespace":
499
+ if (operator === "equals") {
500
+ draft.narrow({
501
+ namespace: String(clause.value)
502
+ });
503
+ } else if (anyOf) {
504
+ draft.narrow({
505
+ namespaces: operands(clause).map(String)
506
+ });
507
+ } else {
508
+ unsupported(clause, "equals, in");
509
+ }
510
+ return;
511
+ case "tag":
512
+ if (operator === "equals") {
513
+ draft.narrow({
514
+ tag: String(clause.value)
515
+ });
516
+ } else if (anyOf) {
517
+ draft.narrow({
518
+ tags: operands(clause).map(String)
519
+ });
520
+ } else {
521
+ unsupported(clause, "equals, in");
522
+ }
523
+ return;
524
+ case "origin":
525
+ if (operator === "isNull") {
526
+ draft.narrow({
527
+ origin: null
528
+ });
529
+ } else if (operator === "equals") {
530
+ draft.narrow({
531
+ origin: clause.value === null ? null : String(clause.value)
532
+ });
533
+ } else if (anyOf) {
534
+ draft.narrow({
535
+ origins: operands(clause).map((v) => v === null ? null : String(v))
536
+ });
537
+ } else {
538
+ unsupported(clause, "equals, in, isNull");
539
+ }
540
+ return;
541
+ default:
542
+ unsupported(clause, `one of ${DURABLE_RUN_FIELDS.map((f) => f.name).join(", ")}`);
543
+ }
544
+ }
545
+ /** `attr.<key>` → one {@link AttributeFilter}. The operator set is the store's, so the mapping is
546
+ * 1:1 and anything outside it is rejected rather than approximated. */
547
+ applyAttributeClause(draft, clause) {
548
+ const key = clause.field.slice(ATTRIBUTE_FIELD.length + 1);
549
+ if (!key) {
550
+ throw new BadRequestException("Filtering search attributes needs a key: `attr.<key>`, e.g. `attr.tier`.");
551
+ }
552
+ const scalarOps = {
553
+ equals: "eq",
554
+ notEquals: "ne",
555
+ gt: "gt",
556
+ gte: "gte",
557
+ lt: "lt",
558
+ lte: "lte"
559
+ };
560
+ const scalar = scalarOps[clause.operator];
561
+ if (scalar) {
562
+ draft.attribute({
563
+ key,
564
+ op: scalar,
565
+ value: coerce2(clause.value)
566
+ });
567
+ return;
568
+ }
569
+ if (clause.operator === "in" || clause.operator === "isAnyOf") {
570
+ draft.attribute({
571
+ key,
572
+ op: "in",
573
+ values: operands(clause).map(coerce2)
574
+ });
575
+ return;
576
+ }
577
+ unsupported(clause, "equals, notEquals, gt, gte, lt, lte, in");
578
+ }
579
+ applyOffsetPagination(qb, page2, size) {
580
+ this.draft(qb).narrow({
581
+ limit: size,
582
+ offset: (page2 - 1) * size
583
+ });
584
+ }
585
+ /**
586
+ * A run listing has ONE order — newest first — because that is what every store adapter's
587
+ * `listRuns` emits and what the console's paging depends on. A request for any other sort is
588
+ * refused rather than accepted and ignored: silently returning a differently-ordered page is how a
589
+ * paginated list starts duplicating and skipping rows.
590
+ */
591
+ applySort(_qb, sorts) {
592
+ for (const sort of sorts) {
593
+ if (sort.field !== FIXED_ORDER.field || sort.direction !== FIXED_ORDER.direction) {
594
+ throw new BadRequestException(`The durable run list is always ordered \`${FIXED_ORDER.field} ${FIXED_ORDER.direction}\` and cannot be sorted by \`${sort.field} ${sort.direction}\`.`);
595
+ }
596
+ }
597
+ }
598
+ getResult(qb) {
599
+ return this.gateway.listRuns(this.draft(qb).query);
600
+ }
601
+ /**
602
+ * A page plus the size of the whole matching set. The total comes from the facet aggregate rather
603
+ * than from a second listing — that is the entire reason durable exposes one, and it keeps the
604
+ * count exact while the page stays bounded.
605
+ */
606
+ async getResultAndCount(qb) {
607
+ const draft = this.draft(qb);
608
+ const [rows, facets] = await Promise.all([
609
+ this.gateway.listRuns(draft.query),
610
+ this.gateway.runFacets(draft.facetQuery())
611
+ ]);
612
+ const { status, statuses, origin, origins } = draft.query;
613
+ const total = facets.filter((cell) => {
614
+ if (status && cell.status !== status) return false;
615
+ if (statuses && !statuses.includes(cell.status)) return false;
616
+ if (origin !== void 0 && cell.origin !== origin) return false;
617
+ if (origins && !origins.includes(cell.origin)) return false;
618
+ return true;
619
+ }).reduce((sum, cell) => sum + cell.count, 0);
620
+ return {
621
+ rows,
622
+ total
623
+ };
624
+ }
625
+ /**
626
+ * The distinct values of one field over the runs the active predicates select — the console's
627
+ * pickers. `limit` is honoured because it has to be: tag and search-attribute cardinality grows
628
+ * with the data, so the unbounded answer is a listing rather than an aggregate.
629
+ */
630
+ async groupByCount(qb, field, entity, opts) {
631
+ this.assertDurableRun(entity);
632
+ if (typeof field !== "string") {
633
+ throw new BadRequestException("The durable run list has no computed fields to group by \u2014 group by one of its own fields.");
634
+ }
635
+ if (opts?.bucket !== void 0) {
636
+ throw new BadRequestException("The durable run list cannot bucket a group-by-count: none of its filterable fields is numeric.");
637
+ }
638
+ const axis = runValueAxisFor(field);
639
+ if (!axis) {
640
+ throw new BadRequestException(`The durable run list cannot enumerate values of \`${field}\`.`);
641
+ }
642
+ const rows = await this.gateway.runValueFacets(axis, this.draft(qb).facetQuery(), {
643
+ limit: opts?.limit ?? RUN_VALUE_FACET_LIMIT
644
+ });
645
+ return rows.map((row) => ({
646
+ value: row.value,
647
+ count: row.count
648
+ }));
649
+ }
650
+ getPrimaryKey() {
651
+ return "id";
652
+ }
653
+ draft(qb) {
654
+ if (!(qb instanceof RunQueryDraft2)) {
655
+ throw new Error("The durable run adapter was handed a query builder it did not create. A filter using it must declare `@Filterable({ entity: DurableRun, adapter: RUN_QUERY_ADAPTER })`.");
656
+ }
657
+ return qb;
658
+ }
659
+ assertDurableRun(entity) {
660
+ if (entity !== DurableRun) {
661
+ throw new Error(`The durable run adapter only answers for DurableRun, not ${entity?.name ?? String(entity)}.`);
662
+ }
663
+ }
664
+ };
665
+ RunQueryAdapter = _ts_decorate2([
666
+ Injectable2(),
667
+ _ts_metadata2("design:type", Function),
668
+ _ts_metadata2("design:paramtypes", [
669
+ typeof RunGateway2 === "undefined" ? Object : RunGateway2
670
+ ])
671
+ ], RunQueryAdapter);
672
+
673
+ // src/server/run.filter.ts
674
+ function _ts_decorate3(decorators, target, key, desc) {
675
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
676
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
677
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
678
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
679
+ }
680
+ __name(_ts_decorate3, "_ts_decorate");
681
+ function _ts_metadata3(k, v) {
682
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
683
+ }
684
+ __name(_ts_metadata3, "_ts_metadata");
685
+ function list(value) {
686
+ return (Array.isArray(value) ? value : [
687
+ value
688
+ ]).filter((v) => v !== void 0 && v !== null && v !== "").map(String);
689
+ }
690
+ __name(list, "list");
691
+ function bound(value) {
692
+ const n = Number(value);
693
+ return value !== void 0 && value !== "" && Number.isInteger(n) && n >= 0 ? n : void 0;
694
+ }
695
+ __name(bound, "bound");
696
+ var RunFilter = class extends BaseFilter {
697
+ static {
698
+ __name(this, "RunFilter");
699
+ }
700
+ /** One status narrows; several match ANY of them — how a caller asks for a SET (the console's
701
+ * in-flight sibling query, for one). */
702
+ applyStatus(value) {
703
+ const values = list(value);
704
+ if (values.length === 0) return;
705
+ this.$query.narrow(values.length === 1 ? {
706
+ status: values[0]
707
+ } : {
708
+ statuses: values
709
+ });
710
+ }
711
+ applyWorkflow(value) {
712
+ const values = list(value);
713
+ if (values.length === 0) return;
714
+ this.$query.narrow(values.length === 1 ? {
715
+ workflow: values[0]
716
+ } : {
717
+ workflows: values
718
+ });
719
+ }
720
+ /** A blank param (`?namespace=`, from a cleared filter box) is the same as an absent one; passing
721
+ * `''` through would be an exact match on a tenant nobody has, i.e. a silently empty console. */
722
+ applyNamespace(value) {
723
+ const values = list(value);
724
+ if (values.length === 0) return;
725
+ this.$query.narrow(values.length === 1 ? {
726
+ namespace: values[0]
727
+ } : {
728
+ namespaces: values
729
+ });
730
+ }
731
+ applyTag(value) {
732
+ const values = list(value);
733
+ if (values.length === 0) return;
734
+ this.$query.narrow(values.length === 1 ? {
735
+ tag: values[0]
736
+ } : {
737
+ tags: values
738
+ });
739
+ }
740
+ /**
741
+ * Origin has THREE states rather than two — a named package, the unattributed bucket, or no
742
+ * restriction — and the bucket rides on its own `unattributed` param rather than on a reserved
743
+ * `origin` value, because any reserved string is a package name someone can legitimately have and a
744
+ * console that quietly reinterpreted it would show the wrong runs.
745
+ *
746
+ * Both keys resolve through one function, called from either, reading the RAW input each time:
747
+ * the answer depends on both params, and `@FilterFor` dispatch follows the order the client's keys
748
+ * happen to arrive in. Recomputing from the input makes the result the same either way, where
749
+ * accumulating onto the draft would make `?origin=x&unattributed=true` mean different things
750
+ * depending on key order.
751
+ *
752
+ * Sent together they are a UNION, not a contradiction: `origins` carries the absent bucket
753
+ * alongside named packages, which is the "this package plus the runs nothing claims" view that a
754
+ * single `origin` value cannot express at all.
755
+ */
756
+ applyOrigin() {
757
+ this.resolveOrigin();
758
+ }
759
+ applyUnattributed() {
760
+ this.resolveOrigin();
761
+ }
762
+ resolveOrigin() {
763
+ const named = list(this.$input.origin);
764
+ const raw = this.$input.unattributed;
765
+ const wantsUnattributed = raw === "true" || raw === "1" || raw === true;
766
+ const values = [
767
+ ...named,
768
+ ...wantsUnattributed ? [
769
+ null
770
+ ] : []
771
+ ];
772
+ if (values.length === 0) return;
773
+ if (values.length === 1) {
774
+ const only = values[0];
775
+ this.$query.narrow({
776
+ origin: only
777
+ });
778
+ return;
779
+ }
780
+ this.$query.narrow({
781
+ origins: values
782
+ });
783
+ }
784
+ /** Comma/repeat-separated `key:op:value` predicates (e.g. `amount:gte:200`), ANDed. The structured
785
+ * spelling of the same thing is `filter[where][attr.amount][gte]=200`. */
786
+ applyAttributes(value) {
787
+ for (const filter of parseAttrFilters(value) ?? []) {
788
+ this.$query.attribute(filter);
789
+ }
790
+ }
791
+ applyLimit(value) {
792
+ const limit = bound(value);
793
+ if (limit !== void 0) this.$query.narrow({
794
+ limit
795
+ });
796
+ }
797
+ applyOffset(value) {
798
+ const offset = bound(value);
799
+ if (offset !== void 0) this.$query.narrow({
800
+ offset
801
+ });
802
+ }
803
+ };
804
+ _ts_decorate3([
805
+ FilterFor("status"),
806
+ _ts_metadata3("design:type", Function),
807
+ _ts_metadata3("design:paramtypes", [
808
+ Object
809
+ ]),
810
+ _ts_metadata3("design:returntype", void 0)
811
+ ], RunFilter.prototype, "applyStatus", null);
812
+ _ts_decorate3([
813
+ FilterFor("workflow"),
814
+ _ts_metadata3("design:type", Function),
815
+ _ts_metadata3("design:paramtypes", [
816
+ Object
817
+ ]),
818
+ _ts_metadata3("design:returntype", void 0)
819
+ ], RunFilter.prototype, "applyWorkflow", null);
820
+ _ts_decorate3([
821
+ FilterFor("namespace"),
822
+ _ts_metadata3("design:type", Function),
823
+ _ts_metadata3("design:paramtypes", [
824
+ Object
825
+ ]),
826
+ _ts_metadata3("design:returntype", void 0)
827
+ ], RunFilter.prototype, "applyNamespace", null);
828
+ _ts_decorate3([
829
+ FilterFor("tag"),
830
+ _ts_metadata3("design:type", Function),
831
+ _ts_metadata3("design:paramtypes", [
832
+ Object
833
+ ]),
834
+ _ts_metadata3("design:returntype", void 0)
835
+ ], RunFilter.prototype, "applyTag", null);
836
+ _ts_decorate3([
837
+ FilterFor("origin"),
838
+ _ts_metadata3("design:type", Function),
839
+ _ts_metadata3("design:paramtypes", []),
840
+ _ts_metadata3("design:returntype", void 0)
841
+ ], RunFilter.prototype, "applyOrigin", null);
842
+ _ts_decorate3([
843
+ FilterFor("unattributed"),
844
+ _ts_metadata3("design:type", Function),
845
+ _ts_metadata3("design:paramtypes", []),
846
+ _ts_metadata3("design:returntype", void 0)
847
+ ], RunFilter.prototype, "applyUnattributed", null);
848
+ _ts_decorate3([
849
+ FilterFor("attr"),
850
+ _ts_metadata3("design:type", Function),
851
+ _ts_metadata3("design:paramtypes", [
852
+ Object
853
+ ]),
854
+ _ts_metadata3("design:returntype", void 0)
855
+ ], RunFilter.prototype, "applyAttributes", null);
856
+ _ts_decorate3([
857
+ FilterFor("limit"),
858
+ _ts_metadata3("design:type", Function),
859
+ _ts_metadata3("design:paramtypes", [
860
+ Object
861
+ ]),
862
+ _ts_metadata3("design:returntype", void 0)
863
+ ], RunFilter.prototype, "applyLimit", null);
864
+ _ts_decorate3([
865
+ FilterFor("offset"),
866
+ _ts_metadata3("design:type", Function),
867
+ _ts_metadata3("design:paramtypes", [
868
+ Object
869
+ ]),
870
+ _ts_metadata3("design:returntype", void 0)
871
+ ], RunFilter.prototype, "applyOffset", null);
872
+ RunFilter = _ts_decorate3([
873
+ Injectable3(),
874
+ Filterable({
875
+ entity: DurableRun,
876
+ adapter: RUN_QUERY_ADAPTER,
877
+ autoFields: false
878
+ })
879
+ ], RunFilter);
880
+
881
+ // src/server/durable-api.controller.ts
882
+ function _ts_decorate4(decorators, target, key, desc) {
883
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
884
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
885
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
886
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
887
+ }
888
+ __name(_ts_decorate4, "_ts_decorate");
889
+ function _ts_metadata4(k, v) {
890
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
891
+ }
892
+ __name(_ts_metadata4, "_ts_metadata");
273
893
  function _ts_param2(paramIndex, decorator) {
274
894
  return function(target, key) {
275
895
  decorator(target, key, paramIndex);
276
896
  };
277
897
  }
278
898
  __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");
314
899
  var DurableApiController = class {
315
900
  static {
316
901
  __name(this, "DurableApiController");
317
902
  }
318
903
  dashboard;
319
- constructor(dashboard) {
904
+ runner;
905
+ constructor(dashboard, runner) {
320
906
  this.dashboard = dashboard;
907
+ this.runner = runner;
321
908
  }
322
909
  /**
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.
910
+ * The run list, filtered by whatever the operator asked for.
911
+ *
912
+ * The predicates are parsed, validated and translated by `@dudousxd/nestjs-filter` (see
913
+ * `RunFilter` for the two spellings it accepts, and `RunQueryAdapter` for how they become a
914
+ * `RunQuery`). Every predicate is OPTIONAL and an omitted one means "don't narrow on this axis" —
915
+ * in particular `namespace`, whose absence keeps the historical, deliberate behaviour that read
916
+ * paths are NOT namespace-scoped (core, `WorkflowRun.namespace`): the console shows every tenant's
917
+ * runs until an operator chooses one.
329
918
  */
330
- async runs(status, workflow, tag, attr, namespace, origin, unattributed, limit, offset) {
331
- const runs = await this.dashboard.listRuns({
332
- ...statusPredicate(status),
333
- workflow,
334
- tag,
335
- attributes: parseAttrFilters(attr),
336
- namespace: namespace || void 0,
337
- ...originPredicate(origin, unattributed),
338
- ...pageBounds(limit, offset)
339
- });
919
+ async runs(draft) {
920
+ const runs = await this.dashboard.listRuns(draft.query);
340
921
  return runs.map(toRunListRow);
341
922
  }
342
923
  /**
@@ -344,15 +925,28 @@ var DurableApiController = class {
344
925
  * `GET runs`. This is what lets that listing be PAGED at all: the page bounds what the console
345
926
  * renders, this bounds nothing and keeps its status and origin chips exact.
346
927
  *
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.
928
+ * `status`, `origin` and the page bounds are accepted (the route takes the same filter) but
929
+ * deliberately DROPPED before counting — they are the axes being counted, so narrowing by them
930
+ * would report the answer back to itself.
931
+ */
932
+ facets(draft) {
933
+ return this.dashboard.runFacets(draft.facetQuery());
934
+ }
935
+ /**
936
+ * The distinct values one filter field takes across the runs matching every OTHER active
937
+ * predicate, with counts — what the console's tenant/tag/attribute pickers list.
938
+ *
939
+ * `?groupByCount[field]=tag&groupByCount[limit]=20`, with the filters in the structured
940
+ * `filter[where][...]` form (what `filterQuery()` builds). Scoping the values to the active
941
+ * filters is the point: picking a tenant leaves the tag picker offering only that tenant's tags,
942
+ * so a picker never offers a value whose result set is empty.
943
+ *
944
+ * `limit` matters rather than being a nicety — tag and attribute cardinality grows with the data
945
+ * (a `singleton:<key>` tag is minted per key), so the unbounded answer is a listing.
349
946
  */
350
- facets(workflow, tag, attr, namespace) {
351
- return this.dashboard.runFacets({
352
- workflow,
353
- tag,
354
- attributes: parseAttrFilters(attr),
355
- namespace: namespace || void 0
947
+ values(query) {
948
+ return this.runner.groupByCount(DurableRun, query, {
949
+ filterClass: RunFilter
356
950
  });
357
951
  }
358
952
  /** Prometheus-text metrics (runs/steps by outcome, per-workflow counts) for a scrape. */
@@ -389,18 +983,9 @@ var DurableApiController = class {
389
983
  * The filter takes the SAME params as `GET runs` and is parsed identically, so "retry all" acts on
390
984
  * exactly the set the operator was looking at rather than a wider one.
391
985
  */
392
- bulk(action, status, tag, workflow, attr, compensate, namespace, origin, unattributed) {
393
- return this.dashboard.bulk(action === "cancel" ? "cancel" : "retry", {
394
- status,
395
- tag,
396
- workflow,
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)
403
- }, {
986
+ bulk(action, draft, compensate) {
987
+ const { limit, offset, ...filter } = draft.query;
988
+ return this.dashboard.bulk(action === "cancel" ? "cancel" : "retry", filter, {
404
989
  compensate: compensate === "true"
405
990
  });
406
991
  }
@@ -442,202 +1027,178 @@ var DurableApiController = class {
442
1027
  return this.dashboard.update(id, name, body);
443
1028
  }
444
1029
  };
445
- _ts_decorate2([
1030
+ _ts_decorate4([
446
1031
  Get("runs"),
447
- _ts_param2(0, Query("status")),
448
- _ts_param2(1, Query("workflow")),
449
- _ts_param2(2, Query("tag")),
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")),
456
- _ts_metadata2("design:type", Function),
457
- _ts_metadata2("design:paramtypes", [
458
- Object,
459
- String,
460
- String,
461
- Object,
462
- String,
463
- String,
464
- String,
465
- String,
466
- String
1032
+ _ts_param2(0, ApplyFilter(RunFilter)),
1033
+ _ts_metadata4("design:type", Function),
1034
+ _ts_metadata4("design:paramtypes", [
1035
+ typeof RunQueryDraft === "undefined" ? Object : RunQueryDraft
467
1036
  ]),
468
- _ts_metadata2("design:returntype", Promise)
1037
+ _ts_metadata4("design:returntype", Promise)
469
1038
  ], DurableApiController.prototype, "runs", null);
470
- _ts_decorate2([
1039
+ _ts_decorate4([
471
1040
  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
1041
+ _ts_param2(0, ApplyFilter(RunFilter)),
1042
+ _ts_metadata4("design:type", Function),
1043
+ _ts_metadata4("design:paramtypes", [
1044
+ typeof RunQueryDraft === "undefined" ? Object : RunQueryDraft
482
1045
  ]),
483
- _ts_metadata2("design:returntype", void 0)
1046
+ _ts_metadata4("design:returntype", void 0)
484
1047
  ], DurableApiController.prototype, "facets", null);
485
- _ts_decorate2([
1048
+ _ts_decorate4([
1049
+ Get("runs/values"),
1050
+ _ts_param2(0, Query()),
1051
+ _ts_metadata4("design:type", Function),
1052
+ _ts_metadata4("design:paramtypes", [
1053
+ typeof Record === "undefined" ? Object : Record
1054
+ ]),
1055
+ _ts_metadata4("design:returntype", void 0)
1056
+ ], DurableApiController.prototype, "values", null);
1057
+ _ts_decorate4([
486
1058
  Get("metrics"),
487
1059
  Header("Content-Type", "text/plain; version=0.0.4"),
488
- _ts_metadata2("design:type", Function),
489
- _ts_metadata2("design:paramtypes", []),
490
- _ts_metadata2("design:returntype", void 0)
1060
+ _ts_metadata4("design:type", Function),
1061
+ _ts_metadata4("design:paramtypes", []),
1062
+ _ts_metadata4("design:returntype", void 0)
491
1063
  ], DurableApiController.prototype, "metrics", null);
492
- _ts_decorate2([
1064
+ _ts_decorate4([
493
1065
  Get("workers"),
494
- _ts_metadata2("design:type", Function),
495
- _ts_metadata2("design:paramtypes", []),
496
- _ts_metadata2("design:returntype", void 0)
1066
+ _ts_metadata4("design:type", Function),
1067
+ _ts_metadata4("design:paramtypes", []),
1068
+ _ts_metadata4("design:returntype", void 0)
497
1069
  ], DurableApiController.prototype, "workers", null);
498
- _ts_decorate2([
1070
+ _ts_decorate4([
499
1071
  Get("topology"),
500
- _ts_metadata2("design:type", Function),
501
- _ts_metadata2("design:paramtypes", []),
502
- _ts_metadata2("design:returntype", void 0)
1072
+ _ts_metadata4("design:type", Function),
1073
+ _ts_metadata4("design:paramtypes", []),
1074
+ _ts_metadata4("design:returntype", void 0)
503
1075
  ], DurableApiController.prototype, "topology", null);
504
- _ts_decorate2([
1076
+ _ts_decorate4([
505
1077
  Get("runs/:id"),
506
1078
  _ts_param2(0, Param("id")),
507
- _ts_metadata2("design:type", Function),
508
- _ts_metadata2("design:paramtypes", [
1079
+ _ts_metadata4("design:type", Function),
1080
+ _ts_metadata4("design:paramtypes", [
509
1081
  String
510
1082
  ]),
511
- _ts_metadata2("design:returntype", Promise)
1083
+ _ts_metadata4("design:returntype", Promise)
512
1084
  ], DurableApiController.prototype, "run", null);
513
- _ts_decorate2([
1085
+ _ts_decorate4([
514
1086
  Sse("runs/:id/stream"),
515
1087
  _ts_param2(0, Param("id")),
516
- _ts_metadata2("design:type", Function),
517
- _ts_metadata2("design:paramtypes", [
1088
+ _ts_metadata4("design:type", Function),
1089
+ _ts_metadata4("design:paramtypes", [
518
1090
  String
519
1091
  ]),
520
- _ts_metadata2("design:returntype", void 0)
1092
+ _ts_metadata4("design:returntype", void 0)
521
1093
  ], DurableApiController.prototype, "stream", null);
522
- _ts_decorate2([
1094
+ _ts_decorate4([
523
1095
  Post("runs/:id/retry"),
524
1096
  _ts_param2(0, Param("id")),
525
- _ts_metadata2("design:type", Function),
526
- _ts_metadata2("design:paramtypes", [
1097
+ _ts_metadata4("design:type", Function),
1098
+ _ts_metadata4("design:paramtypes", [
527
1099
  String
528
1100
  ]),
529
- _ts_metadata2("design:returntype", void 0)
1101
+ _ts_metadata4("design:returntype", void 0)
530
1102
  ], DurableApiController.prototype, "retry", null);
531
- _ts_decorate2([
1103
+ _ts_decorate4([
532
1104
  Post("runs/:id/retry-with-input"),
533
1105
  _ts_param2(0, Param("id")),
534
1106
  _ts_param2(1, Body()),
535
- _ts_metadata2("design:type", Function),
536
- _ts_metadata2("design:paramtypes", [
1107
+ _ts_metadata4("design:type", Function),
1108
+ _ts_metadata4("design:paramtypes", [
537
1109
  String,
538
1110
  Object
539
1111
  ]),
540
- _ts_metadata2("design:returntype", void 0)
1112
+ _ts_metadata4("design:returntype", void 0)
541
1113
  ], DurableApiController.prototype, "retryWithInput", null);
542
- _ts_decorate2([
1114
+ _ts_decorate4([
543
1115
  Post("bulk/:action"),
544
1116
  _ts_param2(0, Param("action")),
545
- _ts_param2(1, Query("status")),
546
- _ts_param2(2, Query("tag")),
547
- _ts_param2(3, Query("workflow")),
548
- _ts_param2(4, Query("attr")),
549
- _ts_param2(5, Query("compensate")),
550
- _ts_param2(6, Query("namespace")),
551
- _ts_param2(7, Query("origin")),
552
- _ts_param2(8, Query("unattributed")),
553
- _ts_metadata2("design:type", Function),
554
- _ts_metadata2("design:paramtypes", [
555
- String,
556
- typeof RunStatus === "undefined" ? Object : RunStatus,
557
- String,
558
- String,
559
- Object,
560
- String,
561
- String,
1117
+ _ts_param2(1, ApplyFilter(RunFilter)),
1118
+ _ts_param2(2, Query("compensate")),
1119
+ _ts_metadata4("design:type", Function),
1120
+ _ts_metadata4("design:paramtypes", [
562
1121
  String,
1122
+ typeof RunQueryDraft === "undefined" ? Object : RunQueryDraft,
563
1123
  String
564
1124
  ]),
565
- _ts_metadata2("design:returntype", void 0)
1125
+ _ts_metadata4("design:returntype", void 0)
566
1126
  ], DurableApiController.prototype, "bulk", null);
567
- _ts_decorate2([
1127
+ _ts_decorate4([
568
1128
  Post("runs/:id/cancel"),
569
1129
  _ts_param2(0, Param("id")),
570
1130
  _ts_param2(1, Query("compensate")),
571
- _ts_metadata2("design:type", Function),
572
- _ts_metadata2("design:paramtypes", [
1131
+ _ts_metadata4("design:type", Function),
1132
+ _ts_metadata4("design:paramtypes", [
573
1133
  String,
574
1134
  String
575
1135
  ]),
576
- _ts_metadata2("design:returntype", Promise)
1136
+ _ts_metadata4("design:returntype", Promise)
577
1137
  ], DurableApiController.prototype, "cancel", null);
578
- _ts_decorate2([
1138
+ _ts_decorate4([
579
1139
  Post("runs/:id/continue"),
580
1140
  _ts_param2(0, Param("id")),
581
- _ts_metadata2("design:type", Function),
582
- _ts_metadata2("design:paramtypes", [
1141
+ _ts_metadata4("design:type", Function),
1142
+ _ts_metadata4("design:paramtypes", [
583
1143
  String
584
1144
  ]),
585
- _ts_metadata2("design:returntype", Promise)
1145
+ _ts_metadata4("design:returntype", Promise)
586
1146
  ], DurableApiController.prototype, "continue", null);
587
- _ts_decorate2([
1147
+ _ts_decorate4([
588
1148
  Post("runs/:id/redispatch"),
589
1149
  _ts_param2(0, Param("id")),
590
- _ts_metadata2("design:type", Function),
591
- _ts_metadata2("design:paramtypes", [
1150
+ _ts_metadata4("design:type", Function),
1151
+ _ts_metadata4("design:paramtypes", [
592
1152
  String
593
1153
  ]),
594
- _ts_metadata2("design:returntype", Promise)
1154
+ _ts_metadata4("design:returntype", Promise)
595
1155
  ], DurableApiController.prototype, "redispatch", null);
596
- _ts_decorate2([
1156
+ _ts_decorate4([
597
1157
  Post("webhooks/:token"),
598
1158
  _ts_param2(0, Param("token")),
599
1159
  _ts_param2(1, Body()),
600
- _ts_metadata2("design:type", Function),
601
- _ts_metadata2("design:paramtypes", [
1160
+ _ts_metadata4("design:type", Function),
1161
+ _ts_metadata4("design:paramtypes", [
602
1162
  String,
603
1163
  Object
604
1164
  ]),
605
- _ts_metadata2("design:returntype", Promise)
1165
+ _ts_metadata4("design:returntype", Promise)
606
1166
  ], DurableApiController.prototype, "webhook", null);
607
- _ts_decorate2([
1167
+ _ts_decorate4([
608
1168
  Get("runs/:id/events/:key"),
609
1169
  _ts_param2(0, Param("id")),
610
1170
  _ts_param2(1, Param("key")),
611
- _ts_metadata2("design:type", Function),
612
- _ts_metadata2("design:paramtypes", [
1171
+ _ts_metadata4("design:type", Function),
1172
+ _ts_metadata4("design:paramtypes", [
613
1173
  String,
614
1174
  String
615
1175
  ]),
616
- _ts_metadata2("design:returntype", void 0)
1176
+ _ts_metadata4("design:returntype", void 0)
617
1177
  ], DurableApiController.prototype, "event", null);
618
- _ts_decorate2([
1178
+ _ts_decorate4([
619
1179
  Post("runs/:id/updates/:name"),
620
1180
  _ts_param2(0, Param("id")),
621
1181
  _ts_param2(1, Param("name")),
622
1182
  _ts_param2(2, Body()),
623
- _ts_metadata2("design:type", Function),
624
- _ts_metadata2("design:paramtypes", [
1183
+ _ts_metadata4("design:type", Function),
1184
+ _ts_metadata4("design:paramtypes", [
625
1185
  String,
626
1186
  String,
627
1187
  Object
628
1188
  ]),
629
- _ts_metadata2("design:returntype", void 0)
1189
+ _ts_metadata4("design:returntype", void 0)
630
1190
  ], DurableApiController.prototype, "update", null);
631
- DurableApiController = _ts_decorate2([
1191
+ DurableApiController = _ts_decorate4([
632
1192
  Controller(),
633
- _ts_metadata2("design:type", Function),
634
- _ts_metadata2("design:paramtypes", [
635
- typeof DashboardService === "undefined" ? Object : DashboardService
1193
+ _ts_metadata4("design:type", Function),
1194
+ _ts_metadata4("design:paramtypes", [
1195
+ typeof DashboardService === "undefined" ? Object : DashboardService,
1196
+ typeof FilterRunner === "undefined" ? Object : FilterRunner
636
1197
  ])
637
1198
  ], DurableApiController);
638
1199
 
639
1200
  // src/server/durable-auth.controller.ts
640
- import { BadRequestException, Body as Body2, Controller as Controller3, Get as Get3, Header as Header3, HttpCode, Inject as Inject4, Logger as Logger2, NotFoundException as NotFoundException3, Optional as Optional3, Post as Post2, Req, Res, UnauthorizedException } from "@nestjs/common";
1201
+ import { BadRequestException as BadRequestException2, Body as Body2, Controller as Controller3, Get as Get3, Header as Header3, HttpCode, Inject as Inject4, Logger as Logger2, NotFoundException as NotFoundException3, Optional as Optional3, Post as Post2, Req, Res, UnauthorizedException } from "@nestjs/common";
641
1202
 
642
1203
  // src/server/auth/dashboard-auth-config.ts
643
1204
  var DASHBOARD_AUTH = Symbol("DASHBOARD_AUTH");
@@ -1193,13 +1754,13 @@ import { Controller as Controller2, Get as Get2, Header as Header2, Inject as In
1193
1754
 
1194
1755
  // src/server/auth/login-redirect.exception.ts
1195
1756
  import { Catch, HttpException, HttpStatus } from "@nestjs/common";
1196
- function _ts_decorate3(decorators, target, key, desc) {
1757
+ function _ts_decorate5(decorators, target, key, desc) {
1197
1758
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1198
1759
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1199
1760
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1200
1761
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1201
1762
  }
1202
- __name(_ts_decorate3, "_ts_decorate");
1763
+ __name(_ts_decorate5, "_ts_decorate");
1203
1764
  var DashboardLoginRedirectException = class extends HttpException {
1204
1765
  static {
1205
1766
  __name(this, "DashboardLoginRedirectException");
@@ -1220,23 +1781,23 @@ var DashboardLoginRedirectFilter = class {
1220
1781
  redirectRaw(response, exception.location);
1221
1782
  }
1222
1783
  };
1223
- DashboardLoginRedirectFilter = _ts_decorate3([
1784
+ DashboardLoginRedirectFilter = _ts_decorate5([
1224
1785
  Catch(DashboardLoginRedirectException)
1225
1786
  ], DashboardLoginRedirectFilter);
1226
1787
 
1227
1788
  // src/server/auth/session-required.exception.ts
1228
1789
  import { Catch as Catch2, HttpException as HttpException2, HttpStatus as HttpStatus2, Inject as Inject2, Logger, Optional as Optional2 } from "@nestjs/common";
1229
- function _ts_decorate4(decorators, target, key, desc) {
1790
+ function _ts_decorate6(decorators, target, key, desc) {
1230
1791
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1231
1792
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1232
1793
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1233
1794
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1234
1795
  }
1235
- __name(_ts_decorate4, "_ts_decorate");
1236
- function _ts_metadata3(k, v) {
1796
+ __name(_ts_decorate6, "_ts_decorate");
1797
+ function _ts_metadata5(k, v) {
1237
1798
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1238
1799
  }
1239
- __name(_ts_metadata3, "_ts_metadata");
1800
+ __name(_ts_metadata5, "_ts_metadata");
1240
1801
  function _ts_param3(paramIndex, decorator) {
1241
1802
  return function(target, key) {
1242
1803
  decorator(target, key, paramIndex);
@@ -1291,28 +1852,28 @@ var DashboardSessionRequiredFilter = class _DashboardSessionRequiredFilter {
1291
1852
  this.logger.warn(message);
1292
1853
  }
1293
1854
  };
1294
- DashboardSessionRequiredFilter = _ts_decorate4([
1855
+ DashboardSessionRequiredFilter = _ts_decorate6([
1295
1856
  Catch2(DashboardSessionRequiredException),
1296
1857
  _ts_param3(0, Optional2()),
1297
1858
  _ts_param3(0, Inject2(DASHBOARD_AUTH)),
1298
- _ts_metadata3("design:type", Function),
1299
- _ts_metadata3("design:paramtypes", [
1859
+ _ts_metadata5("design:type", Function),
1860
+ _ts_metadata5("design:paramtypes", [
1300
1861
  Object
1301
1862
  ])
1302
1863
  ], DashboardSessionRequiredFilter);
1303
1864
 
1304
1865
  // src/server/durable-ui.controller.ts
1305
- function _ts_decorate5(decorators, target, key, desc) {
1866
+ function _ts_decorate7(decorators, target, key, desc) {
1306
1867
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1307
1868
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1308
1869
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1309
1870
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1310
1871
  }
1311
- __name(_ts_decorate5, "_ts_decorate");
1312
- function _ts_metadata4(k, v) {
1872
+ __name(_ts_decorate7, "_ts_decorate");
1873
+ function _ts_metadata6(k, v) {
1313
1874
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1314
1875
  }
1315
- __name(_ts_metadata4, "_ts_metadata");
1876
+ __name(_ts_metadata6, "_ts_metadata");
1316
1877
  function _ts_param4(paramIndex, decorator) {
1317
1878
  return function(target, key) {
1318
1879
  decorator(target, key, paramIndex);
@@ -1371,48 +1932,48 @@ var DurableUiController = class {
1371
1932
  });
1372
1933
  }
1373
1934
  };
1374
- _ts_decorate5([
1935
+ _ts_decorate7([
1375
1936
  Get2(),
1376
1937
  Header2("Content-Type", "text/html; charset=utf-8"),
1377
1938
  Header2("Cache-Control", "no-store, must-revalidate"),
1378
- _ts_metadata4("design:type", Function),
1379
- _ts_metadata4("design:paramtypes", []),
1380
- _ts_metadata4("design:returntype", String)
1939
+ _ts_metadata6("design:type", Function),
1940
+ _ts_metadata6("design:paramtypes", []),
1941
+ _ts_metadata6("design:returntype", String)
1381
1942
  ], DurableUiController.prototype, "index", null);
1382
- _ts_decorate5([
1943
+ _ts_decorate7([
1383
1944
  Get2("assets/:file"),
1384
1945
  Header2("Cache-Control", "public, max-age=31536000, immutable"),
1385
1946
  _ts_param4(0, Param2("file")),
1386
- _ts_metadata4("design:type", Function),
1387
- _ts_metadata4("design:paramtypes", [
1947
+ _ts_metadata6("design:type", Function),
1948
+ _ts_metadata6("design:paramtypes", [
1388
1949
  String
1389
1950
  ]),
1390
- _ts_metadata4("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
1951
+ _ts_metadata6("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
1391
1952
  ], DurableUiController.prototype, "asset", null);
1392
- DurableUiController = _ts_decorate5([
1953
+ DurableUiController = _ts_decorate7([
1393
1954
  UseFilters(DashboardLoginRedirectFilter, DashboardSessionRequiredFilter),
1394
1955
  Controller2(),
1395
1956
  _ts_param4(0, Inject3(DASHBOARD_BASE_PATH)),
1396
1957
  _ts_param4(1, Inject3(DASHBOARD_API_PATH)),
1397
- _ts_metadata4("design:type", Function),
1398
- _ts_metadata4("design:paramtypes", [
1958
+ _ts_metadata6("design:type", Function),
1959
+ _ts_metadata6("design:paramtypes", [
1399
1960
  String,
1400
1961
  String
1401
1962
  ])
1402
1963
  ], DurableUiController);
1403
1964
 
1404
1965
  // src/server/durable-auth.controller.ts
1405
- function _ts_decorate6(decorators, target, key, desc) {
1966
+ function _ts_decorate8(decorators, target, key, desc) {
1406
1967
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1407
1968
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1408
1969
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1409
1970
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1410
1971
  }
1411
- __name(_ts_decorate6, "_ts_decorate");
1412
- function _ts_metadata5(k, v) {
1972
+ __name(_ts_decorate8, "_ts_decorate");
1973
+ function _ts_metadata7(k, v) {
1413
1974
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1414
1975
  }
1415
- __name(_ts_metadata5, "_ts_metadata");
1976
+ __name(_ts_metadata7, "_ts_metadata");
1416
1977
  function _ts_param5(paramIndex, decorator) {
1417
1978
  return function(target, key) {
1418
1979
  decorator(target, key, paramIndex);
@@ -1452,7 +2013,7 @@ var DurableAuthController = class _DurableAuthController {
1452
2013
  const auth = this.requireAuth();
1453
2014
  if (!auth.modes.includes("login")) throw new NotFoundException3();
1454
2015
  if (!isString(body?.username) || !isString(body?.password)) {
1455
- throw new BadRequestException("Body must include string `username` and `password`.");
2016
+ throw new BadRequestException2("Body must include string `username` and `password`.");
1456
2017
  }
1457
2018
  const { username, password } = body;
1458
2019
  const user = await this.runHook("login", () => auth.login?.(username, password) ?? null);
@@ -1514,15 +2075,15 @@ var DurableAuthController = class _DurableAuthController {
1514
2075
  }
1515
2076
  }
1516
2077
  };
1517
- _ts_decorate6([
2078
+ _ts_decorate8([
1518
2079
  Get3("login"),
1519
2080
  Header3("Content-Type", "text/html; charset=utf-8"),
1520
2081
  Header3("Cache-Control", "no-store, must-revalidate"),
1521
- _ts_metadata5("design:type", Function),
1522
- _ts_metadata5("design:paramtypes", []),
1523
- _ts_metadata5("design:returntype", String)
2082
+ _ts_metadata7("design:type", Function),
2083
+ _ts_metadata7("design:paramtypes", []),
2084
+ _ts_metadata7("design:returntype", String)
1524
2085
  ], DurableAuthController.prototype, "loginPage", null);
1525
- _ts_decorate6([
2086
+ _ts_decorate8([
1526
2087
  Post2("login"),
1527
2088
  HttpCode(200),
1528
2089
  _ts_param5(0, Body2()),
@@ -1530,46 +2091,46 @@ _ts_decorate6([
1530
2091
  _ts_param5(2, Res({
1531
2092
  passthrough: true
1532
2093
  })),
1533
- _ts_metadata5("design:type", Function),
1534
- _ts_metadata5("design:paramtypes", [
2094
+ _ts_metadata7("design:type", Function),
2095
+ _ts_metadata7("design:paramtypes", [
1535
2096
  typeof LoginBody === "undefined" ? Object : LoginBody,
1536
2097
  Object,
1537
2098
  Object
1538
2099
  ]),
1539
- _ts_metadata5("design:returntype", Promise)
2100
+ _ts_metadata7("design:returntype", Promise)
1540
2101
  ], DurableAuthController.prototype, "login", null);
1541
- _ts_decorate6([
2102
+ _ts_decorate8([
1542
2103
  Post2("session"),
1543
2104
  HttpCode(204),
1544
2105
  _ts_param5(0, Req()),
1545
2106
  _ts_param5(1, Res({
1546
2107
  passthrough: true
1547
2108
  })),
1548
- _ts_metadata5("design:type", Function),
1549
- _ts_metadata5("design:paramtypes", [
2109
+ _ts_metadata7("design:type", Function),
2110
+ _ts_metadata7("design:paramtypes", [
1550
2111
  Object,
1551
2112
  Object
1552
2113
  ]),
1553
- _ts_metadata5("design:returntype", Promise)
2114
+ _ts_metadata7("design:returntype", Promise)
1554
2115
  ], DurableAuthController.prototype, "session", null);
1555
- _ts_decorate6([
2116
+ _ts_decorate8([
1556
2117
  Get3("logout"),
1557
2118
  _ts_param5(0, Req()),
1558
2119
  _ts_param5(1, Res()),
1559
- _ts_metadata5("design:type", Function),
1560
- _ts_metadata5("design:paramtypes", [
2120
+ _ts_metadata7("design:type", Function),
2121
+ _ts_metadata7("design:paramtypes", [
1561
2122
  Object,
1562
2123
  Object
1563
2124
  ]),
1564
- _ts_metadata5("design:returntype", void 0)
2125
+ _ts_metadata7("design:returntype", void 0)
1565
2126
  ], DurableAuthController.prototype, "logout", null);
1566
- DurableAuthController = _ts_decorate6([
2127
+ DurableAuthController = _ts_decorate8([
1567
2128
  Controller3(),
1568
2129
  _ts_param5(0, Optional3()),
1569
2130
  _ts_param5(0, Inject4(DASHBOARD_AUTH)),
1570
2131
  _ts_param5(1, Inject4(DASHBOARD_BASE_PATH)),
1571
- _ts_metadata5("design:type", Function),
1572
- _ts_metadata5("design:paramtypes", [
2132
+ _ts_metadata7("design:type", Function),
2133
+ _ts_metadata7("design:paramtypes", [
1573
2134
  Object,
1574
2135
  String
1575
2136
  ])
@@ -1577,22 +2138,23 @@ DurableAuthController = _ts_decorate6([
1577
2138
 
1578
2139
  // src/server/durable-dashboard.module.ts
1579
2140
  import "reflect-metadata";
2141
+ import { FilterModule } from "@dudousxd/nestjs-filter";
1580
2142
  import { Module } from "@nestjs/common";
1581
2143
  import { RouterModule } from "@nestjs/core";
1582
2144
 
1583
2145
  // src/server/durable-session.guard.ts
1584
- import { Inject as Inject5, Injectable as Injectable2, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
1585
- function _ts_decorate7(decorators, target, key, desc) {
2146
+ import { Inject as Inject5, Injectable as Injectable4, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
2147
+ function _ts_decorate9(decorators, target, key, desc) {
1586
2148
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1587
2149
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1588
2150
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1589
2151
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1590
2152
  }
1591
- __name(_ts_decorate7, "_ts_decorate");
1592
- function _ts_metadata6(k, v) {
2153
+ __name(_ts_decorate9, "_ts_decorate");
2154
+ function _ts_metadata8(k, v) {
1593
2155
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1594
2156
  }
1595
- __name(_ts_metadata6, "_ts_metadata");
2157
+ __name(_ts_metadata8, "_ts_metadata");
1596
2158
  function _ts_param6(paramIndex, decorator) {
1597
2159
  return function(target, key) {
1598
2160
  decorator(target, key, paramIndex);
@@ -1639,12 +2201,12 @@ var DurableUiSessionGuard = class {
1639
2201
  throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
1640
2202
  }
1641
2203
  };
1642
- DurableUiSessionGuard = _ts_decorate7([
1643
- Injectable2(),
2204
+ DurableUiSessionGuard = _ts_decorate9([
2205
+ Injectable4(),
1644
2206
  _ts_param6(0, Inject5(DASHBOARD_AUTH)),
1645
2207
  _ts_param6(1, Inject5(DASHBOARD_BASE_PATH)),
1646
- _ts_metadata6("design:type", Function),
1647
- _ts_metadata6("design:paramtypes", [
2208
+ _ts_metadata8("design:type", Function),
2209
+ _ts_metadata8("design:paramtypes", [
1648
2210
  Object,
1649
2211
  String
1650
2212
  ])
@@ -1683,23 +2245,23 @@ var DurableApiSessionGuard = class {
1683
2245
  });
1684
2246
  }
1685
2247
  };
1686
- DurableApiSessionGuard = _ts_decorate7([
1687
- Injectable2(),
2248
+ DurableApiSessionGuard = _ts_decorate9([
2249
+ Injectable4(),
1688
2250
  _ts_param6(0, Inject5(DASHBOARD_AUTH)),
1689
- _ts_metadata6("design:type", Function),
1690
- _ts_metadata6("design:paramtypes", [
2251
+ _ts_metadata8("design:type", Function),
2252
+ _ts_metadata8("design:paramtypes", [
1691
2253
  Object
1692
2254
  ])
1693
2255
  ], DurableApiSessionGuard);
1694
2256
 
1695
2257
  // src/server/durable-dashboard.module.ts
1696
- function _ts_decorate8(decorators, target, key, desc) {
2258
+ function _ts_decorate10(decorators, target, key, desc) {
1697
2259
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1698
2260
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1699
2261
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1700
2262
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1701
2263
  }
1702
- __name(_ts_decorate8, "_ts_decorate");
2264
+ __name(_ts_decorate10, "_ts_decorate");
1703
2265
  var GUARDS_METADATA = "__guards__";
1704
2266
  function normalize(path) {
1705
2267
  return `/${path.replace(/^\/+|\/+$/g, "")}`;
@@ -1724,13 +2286,31 @@ var DurableApiModule = class _DurableApiModule {
1724
2286
  return {
1725
2287
  module: _DurableApiModule,
1726
2288
  imports: [
1727
- ...options.imports ?? []
2289
+ ...options.imports ?? [],
2290
+ // `validation: 'off'`: the run filter's inputs are validated by its own translation — every
2291
+ // field/operator pair either maps onto a `RunQuery` predicate or is rejected by name — so
2292
+ // there is nothing left for class-validator to check, and requiring it would put a peer
2293
+ // dependency on every consumer of this dashboard for no gain.
2294
+ ...options.filterModule === "host" ? [] : [
2295
+ FilterModule.forRoot({
2296
+ validation: "off"
2297
+ })
2298
+ ]
1728
2299
  ],
1729
2300
  controllers: [
1730
2301
  DurableApiController
1731
2302
  ],
1732
2303
  providers: [
1733
2304
  DashboardService,
2305
+ // The adapter is bound to its own token (never to the global `FILTER_ADAPTER`), which is
2306
+ // what lets the console's filter and the host app's own filters run on different backends
2307
+ // in one process — see `RunFilter`'s `@Filterable({ adapter })`.
2308
+ RunQueryAdapter,
2309
+ {
2310
+ provide: RUN_QUERY_ADAPTER,
2311
+ useExisting: RunQueryAdapter
2312
+ },
2313
+ RunFilter,
1734
2314
  options.authProvider,
1735
2315
  DurableApiSessionGuard,
1736
2316
  ...options.guards ?? []
@@ -1741,7 +2321,7 @@ var DurableApiModule = class _DurableApiModule {
1741
2321
  };
1742
2322
  }
1743
2323
  };
1744
- DurableApiModule = _ts_decorate8([
2324
+ DurableApiModule = _ts_decorate10([
1745
2325
  Module({})
1746
2326
  ], DurableApiModule);
1747
2327
  var DurableDashboardModule = class _DurableDashboardModule {
@@ -1782,6 +2362,9 @@ var DurableDashboardModule = class _DurableDashboardModule {
1782
2362
  ...options.guards ? {
1783
2363
  guards: options.guards
1784
2364
  } : {},
2365
+ ...options.filterModule ? {
2366
+ filterModule: options.filterModule
2367
+ } : {},
1785
2368
  authProvider,
1786
2369
  authMayBeConfigured
1787
2370
  }),
@@ -1827,7 +2410,7 @@ var DurableDashboardModule = class _DurableDashboardModule {
1827
2410
  };
1828
2411
  }
1829
2412
  };
1830
- DurableDashboardModule = _ts_decorate8([
2413
+ DurableDashboardModule = _ts_decorate10([
1831
2414
  Module({})
1832
2415
  ], DurableDashboardModule);
1833
2416
  export {