@dudousxd/nestjs-durable-dashboard 0.57.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.
- package/dist/server/index.cjs +1189 -94
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +217 -5
- package/dist/server/index.d.ts +217 -5
- package/dist/server/index.js +1176 -94
- package/dist/server/index.js.map +1 -1
- package/dist/spa/assets/{bench-BXhQP2W7.js → bench--sTow8cG.js} +1 -1
- package/dist/spa/assets/index-BA5RJf7A.css +1 -0
- package/dist/spa/assets/{index-C6Ih-K3x.js → index-DG9crD2T.js} +1 -1
- package/dist/spa/assets/index-L85j6H4X.js +66 -0
- package/dist/spa/assets/{preview-ekPMzj7t.js → preview-b6LbKDyY.js} +1 -1
- package/dist/spa/bench.html +3 -3
- package/dist/spa/index.html +3 -3
- package/dist/spa/preview.html +3 -3
- package/package.json +2 -2
- package/dist/spa/assets/index-D_rIGnea.css +0 -1
- package/dist/spa/assets/index-DwGUovEa.js +0 -66
package/dist/server/index.cjs
CHANGED
|
@@ -32,10 +32,12 @@ 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,
|
|
39
41
|
CONSOLE_PUBLIC_METADATA: () => CONSOLE_PUBLIC_METADATA,
|
|
40
42
|
ConsoleAccessDenied: () => ConsoleAccessDenied,
|
|
41
43
|
ConsoleAuthorizeFilter: () => ConsoleAuthorizeFilter,
|
|
@@ -44,16 +46,27 @@ __export(index_exports, {
|
|
|
44
46
|
ConsolePublic: () => ConsolePublic,
|
|
45
47
|
DASHBOARD_API_PATH: () => DASHBOARD_API_PATH,
|
|
46
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,
|
|
47
52
|
DashboardService: () => DashboardService,
|
|
48
53
|
DurableApiController: () => DurableApiController,
|
|
49
54
|
DurableApiControllerBase: () => DurableApiControllerBase,
|
|
50
55
|
DurableApiModule: () => DurableApiModule,
|
|
56
|
+
DurableConsoleRegistration: () => DurableConsoleRegistration,
|
|
57
|
+
DurableConsolesController: () => DurableConsolesController,
|
|
51
58
|
DurableDashboardModule: () => DurableDashboardModule,
|
|
59
|
+
DurableDevToolsRegistration: () => DurableDevToolsRegistration,
|
|
52
60
|
DurableUiController: () => DurableUiController,
|
|
53
61
|
DurableUiControllerBase: () => DurableUiControllerBase,
|
|
62
|
+
MAX_RUN_IDS: () => MAX_RUN_IDS,
|
|
54
63
|
UseConsoleAuthorize: () => UseConsoleAuthorize,
|
|
64
|
+
anyAuthorize: () => anyAuthorize,
|
|
55
65
|
authorizeByToken: () => authorizeByToken,
|
|
66
|
+
durableDevTools: () => durableDevTools,
|
|
67
|
+
listVisibleConsoles: () => listVisibleConsoles,
|
|
56
68
|
normalizeConsoleResult: () => normalizeConsoleResult,
|
|
69
|
+
registerAviaryConsole: () => registerAviaryConsole,
|
|
57
70
|
resolveConsoleAuthorize: () => resolveConsoleAuthorize
|
|
58
71
|
});
|
|
59
72
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -93,9 +106,12 @@ var CONSOLE_GATE = Symbol.for("@dudousxd/aviary:console-gate");
|
|
|
93
106
|
var PACKAGE = "@dudousxd/nestjs-durable-dashboard";
|
|
94
107
|
var MODULE = "DurableDashboardModule";
|
|
95
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().`;
|
|
96
|
-
var allow = /* @__PURE__ */ __name(() =>
|
|
109
|
+
var allow = /* @__PURE__ */ __name((caller) => caller !== void 0 && caller !== "" ? {
|
|
110
|
+
allowed: true,
|
|
111
|
+
caller
|
|
112
|
+
} : {
|
|
97
113
|
allowed: true
|
|
98
|
-
}
|
|
114
|
+
}, "allow");
|
|
99
115
|
var forbidden = /* @__PURE__ */ __name(() => ({
|
|
100
116
|
allowed: false,
|
|
101
117
|
reason: "forbidden"
|
|
@@ -135,7 +151,9 @@ function normalizeConsoleResult(result) {
|
|
|
135
151
|
redirectTo: shape.redirectTo
|
|
136
152
|
};
|
|
137
153
|
}
|
|
138
|
-
if (shape.allowed === true)
|
|
154
|
+
if (shape.allowed === true) {
|
|
155
|
+
return allow(typeof shape.caller === "string" ? shape.caller : void 0);
|
|
156
|
+
}
|
|
139
157
|
return shape.reason === "unauthenticated" ? unauthenticated() : forbidden();
|
|
140
158
|
}
|
|
141
159
|
__name(normalizeConsoleResult, "normalizeConsoleResult");
|
|
@@ -218,11 +236,11 @@ function classDecider(moduleRef) {
|
|
|
218
236
|
}
|
|
219
237
|
__name(classDecider, "classDecider");
|
|
220
238
|
async function resolveConsoleAuthorize(authorize, moduleRef, options = {}) {
|
|
221
|
-
const
|
|
239
|
+
const logger3 = options.logger ?? new import_common.Logger(MODULE);
|
|
222
240
|
if (authorize === void 0) {
|
|
223
|
-
if (options.warnIfMissing !== false)
|
|
241
|
+
if (options.warnIfMissing !== false) logger3.warn(CONSOLE_AUTHORIZE_MISSING_WARNING);
|
|
224
242
|
const production = isProduction();
|
|
225
|
-
return async () => production ? forbidden() : allow();
|
|
243
|
+
return async () => production ? forbidden() : allow("dev-open");
|
|
226
244
|
}
|
|
227
245
|
let decide;
|
|
228
246
|
if (typeof authorize === "string") {
|
|
@@ -249,7 +267,7 @@ async function resolveConsoleAuthorize(authorize, moduleRef, options = {}) {
|
|
|
249
267
|
try {
|
|
250
268
|
return await decide(context);
|
|
251
269
|
} catch (error) {
|
|
252
|
-
|
|
270
|
+
logger3.warn(`console \`authorize\` threw, denying the request: ${error instanceof Error ? error.message : String(error)}`);
|
|
253
271
|
return forbidden();
|
|
254
272
|
}
|
|
255
273
|
};
|
|
@@ -262,10 +280,16 @@ function authorizeByToken(options) {
|
|
|
262
280
|
throw new Error("authorizeByToken: `token` is empty; set it (e.g. from an env var).");
|
|
263
281
|
}
|
|
264
282
|
const expected = sha256(token);
|
|
283
|
+
const caller = `token:${expected.toString("hex").slice(0, 12)}`;
|
|
265
284
|
return (context) => {
|
|
266
285
|
const header = context.switchToHttp().getRequest()?.headers?.authorization;
|
|
267
286
|
const match = typeof header === "string" ? /^Bearer\s+(.+)$/i.exec(header.trim()) : null;
|
|
268
|
-
if (match?.[1] && (0, import_node_crypto.timingSafeEqual)(sha256(match[1]), expected))
|
|
287
|
+
if (match?.[1] && (0, import_node_crypto.timingSafeEqual)(sha256(match[1]), expected)) {
|
|
288
|
+
return {
|
|
289
|
+
allowed: true,
|
|
290
|
+
caller
|
|
291
|
+
};
|
|
292
|
+
}
|
|
269
293
|
return {
|
|
270
294
|
allowed: false,
|
|
271
295
|
reason: "unauthenticated"
|
|
@@ -273,6 +297,48 @@ function authorizeByToken(options) {
|
|
|
273
297
|
};
|
|
274
298
|
}
|
|
275
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");
|
|
276
342
|
var CONSOLE_AUTHORIZE_DECIDE = Symbol(`${PACKAGE}:console-authorize`);
|
|
277
343
|
var CONSOLE_PAGE_METADATA = `${PACKAGE}:console-page`;
|
|
278
344
|
var ConsolePage = /* @__PURE__ */ __name(() => (target) => {
|
|
@@ -284,7 +350,7 @@ var ConsoleAccessDenied = class extends import_common.HttpException {
|
|
|
284
350
|
}
|
|
285
351
|
decision;
|
|
286
352
|
page;
|
|
287
|
-
constructor(decision,
|
|
353
|
+
constructor(decision, page2) {
|
|
288
354
|
const unauthenticated2 = decision.reason === "unauthenticated";
|
|
289
355
|
super(unauthenticated2 ? {
|
|
290
356
|
error: "unauthenticated",
|
|
@@ -293,7 +359,7 @@ var ConsoleAccessDenied = class extends import_common.HttpException {
|
|
|
293
359
|
} : {}
|
|
294
360
|
} : {
|
|
295
361
|
error: "forbidden"
|
|
296
|
-
}, unauthenticated2 ? 401 : 403), this.decision = decision, this.page =
|
|
362
|
+
}, unauthenticated2 ? 401 : 403), this.decision = decision, this.page = page2;
|
|
297
363
|
}
|
|
298
364
|
};
|
|
299
365
|
var CONSOLE_PUBLIC_METADATA = `${PACKAGE}:console-public`;
|
|
@@ -345,12 +411,12 @@ var ConsoleAuthorizeFilter = class {
|
|
|
345
411
|
catch(exception, host) {
|
|
346
412
|
const adapter = this.adapterHost.httpAdapter;
|
|
347
413
|
const response = host.switchToHttp().getResponse();
|
|
348
|
-
const { decision, page } = exception;
|
|
349
|
-
if (
|
|
414
|
+
const { decision, page: page2 } = exception;
|
|
415
|
+
if (page2 && decision.redirectTo) {
|
|
350
416
|
adapter.redirect(response, 302, decision.redirectTo);
|
|
351
417
|
return;
|
|
352
418
|
}
|
|
353
|
-
if (
|
|
419
|
+
if (page2) {
|
|
354
420
|
const forbiddenPage = decision.reason === "forbidden";
|
|
355
421
|
adapter.setHeader(response, "Content-Type", "text/html; charset=utf-8");
|
|
356
422
|
adapter.reply(response, forbiddenPage ? DENIED_PAGE : SIGN_IN_PAGE, forbiddenPage ? 403 : 401);
|
|
@@ -522,31 +588,31 @@ var DashboardService = class {
|
|
|
522
588
|
async workerHealth() {
|
|
523
589
|
return this.gateway.workerHealth();
|
|
524
590
|
}
|
|
525
|
-
getRunDetail(
|
|
526
|
-
return this.gateway.getRunDetail(
|
|
591
|
+
getRunDetail(runId2, options) {
|
|
592
|
+
return this.gateway.getRunDetail(runId2, options);
|
|
527
593
|
}
|
|
528
594
|
/** A page of one fan's members, or null when the run is unknown or the gateway can't page fans. */
|
|
529
|
-
async getFanMembers(
|
|
530
|
-
return await this.gateway.getFanMembers?.(
|
|
595
|
+
async getFanMembers(runId2, group, query) {
|
|
596
|
+
return await this.gateway.getFanMembers?.(runId2, group, query) ?? null;
|
|
531
597
|
}
|
|
532
598
|
/** Fix-and-replay a dead/failed run with a corrected input — a fresh linked run. Returns its id. */
|
|
533
|
-
retryWithInput(
|
|
534
|
-
return this.gateway.retryWithInput(
|
|
599
|
+
retryWithInput(runId2, input) {
|
|
600
|
+
return this.gateway.retryWithInput(runId2, input);
|
|
535
601
|
}
|
|
536
602
|
/** Re-run a failed/incomplete run; completed steps replay from their checkpoints. */
|
|
537
|
-
retry(
|
|
538
|
-
return this.gateway.retry(
|
|
603
|
+
retry(runId2) {
|
|
604
|
+
return this.gateway.retry(runId2);
|
|
539
605
|
}
|
|
540
|
-
cancel(
|
|
541
|
-
return this.gateway.cancel(
|
|
606
|
+
cancel(runId2, opts) {
|
|
607
|
+
return this.gateway.cancel(runId2, opts);
|
|
542
608
|
}
|
|
543
609
|
/**
|
|
544
610
|
* Publish a settled run's lifecycle event again, to its subscribers that have not started (or to
|
|
545
611
|
* `subscriber` alone). Undefined when the gateway cannot republish (a tenant proxy).
|
|
546
612
|
*/
|
|
547
|
-
async republishLifecycle(
|
|
613
|
+
async republishLifecycle(runId2, subscriber) {
|
|
548
614
|
return this.gateway.republishLifecycle?.({
|
|
549
|
-
runId,
|
|
615
|
+
runId: runId2,
|
|
550
616
|
...subscriber !== void 0 ? {
|
|
551
617
|
subscriber
|
|
552
618
|
} : {}
|
|
@@ -614,12 +680,12 @@ var DashboardService = class {
|
|
|
614
680
|
};
|
|
615
681
|
}
|
|
616
682
|
/** Resume a run paused at a `ctx.breakpoint` (the "continue" button). */
|
|
617
|
-
continue(
|
|
618
|
-
return this.gateway.continue(
|
|
683
|
+
continue(runId2) {
|
|
684
|
+
return this.gateway.continue(runId2);
|
|
619
685
|
}
|
|
620
686
|
/** Re-dispatch a run's stuck `pending` remote steps — recovery for a lost step dispatch. */
|
|
621
|
-
redispatch(
|
|
622
|
-
return this.gateway.redispatchPending(
|
|
687
|
+
redispatch(runId2) {
|
|
688
|
+
return this.gateway.redispatchPending(runId2);
|
|
623
689
|
}
|
|
624
690
|
/**
|
|
625
691
|
* Deliver a `ctx.webhook()` callback: turn an inbound POST (token + body) into the signal the
|
|
@@ -648,13 +714,39 @@ var DashboardService = class {
|
|
|
648
714
|
status: fresh?.status ?? run.status
|
|
649
715
|
};
|
|
650
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
|
+
}
|
|
651
743
|
/** Side-effect-free read of a value a run published via `ctx.setEvent` (a live query). Control-plane-only. */
|
|
652
|
-
async getEvent(
|
|
653
|
-
return this.controlPlane().engine.getEvent(
|
|
744
|
+
async getEvent(runId2, key) {
|
|
745
|
+
return this.controlPlane().engine.getEvent(runId2, key);
|
|
654
746
|
}
|
|
655
747
|
/** Deliver a validated `ctx.onUpdate` to a run; the validator may reject it (see UpdateResult). Control-plane-only. */
|
|
656
|
-
async update(
|
|
657
|
-
return this.controlPlane().engine.update(
|
|
748
|
+
async update(runId2, name, arg) {
|
|
749
|
+
return this.controlPlane().engine.update(runId2, name, arg);
|
|
658
750
|
}
|
|
659
751
|
/**
|
|
660
752
|
* Live stream of a run's lifecycle events for SSE. Backed by `gateway.subscribe`, which — on the
|
|
@@ -662,9 +754,9 @@ var DashboardService = class {
|
|
|
662
754
|
* executing on a worker pod), and on a tenant round-trips over the transport. Either way the
|
|
663
755
|
* gateway already filters to `runId`; the dashboard just wires the events through.
|
|
664
756
|
*/
|
|
665
|
-
streamRun(
|
|
757
|
+
streamRun(runId2) {
|
|
666
758
|
return new import_rxjs.Observable((subscriber) => {
|
|
667
|
-
const off = this.gateway.subscribe(
|
|
759
|
+
const off = this.gateway.subscribe(runId2, (event) => subscriber.next({
|
|
668
760
|
data: event
|
|
669
761
|
}));
|
|
670
762
|
return () => off();
|
|
@@ -912,7 +1004,7 @@ var RunQueryDraft2 = class {
|
|
|
912
1004
|
/** The predicates minus the page window and the axes a facet count reports on — what
|
|
913
1005
|
* `runFacets`/`runValueFacets` are taken over. */
|
|
914
1006
|
facetQuery() {
|
|
915
|
-
const { status, statuses, origin, origins, limit, offset, ...facetQuery } = this.query;
|
|
1007
|
+
const { status, statuses, origin, origins, limit: limit2, offset, ...facetQuery } = this.query;
|
|
916
1008
|
return facetQuery;
|
|
917
1009
|
}
|
|
918
1010
|
};
|
|
@@ -1117,10 +1209,10 @@ var RunQueryAdapter = class {
|
|
|
1117
1209
|
}
|
|
1118
1210
|
unsupported(clause, "equals, notEquals, gt, gte, lt, lte, in");
|
|
1119
1211
|
}
|
|
1120
|
-
applyOffsetPagination(qb,
|
|
1212
|
+
applyOffsetPagination(qb, page2, size) {
|
|
1121
1213
|
this.draft(qb).narrow({
|
|
1122
1214
|
limit: size,
|
|
1123
|
-
offset: (
|
|
1215
|
+
offset: (page2 - 1) * size
|
|
1124
1216
|
});
|
|
1125
1217
|
}
|
|
1126
1218
|
/**
|
|
@@ -1347,9 +1439,9 @@ var RunFilter = class extends import_nestjs_filter.BaseFilter {
|
|
|
1347
1439
|
}
|
|
1348
1440
|
}
|
|
1349
1441
|
applyLimit(value) {
|
|
1350
|
-
const
|
|
1351
|
-
if (
|
|
1352
|
-
limit
|
|
1442
|
+
const limit2 = bound(value);
|
|
1443
|
+
if (limit2 !== void 0) this.$query.narrow({
|
|
1444
|
+
limit: limit2
|
|
1353
1445
|
});
|
|
1354
1446
|
}
|
|
1355
1447
|
applyOffset(value) {
|
|
@@ -1531,7 +1623,7 @@ var DurableApiControllerBase = class {
|
|
|
1531
1623
|
* The persisted schedules (`engine.schedules`), soonest-due first, optionally narrowed by
|
|
1532
1624
|
* `namespace`/`workflow`/`tag`/`paused`. Empty on a tenant or a store without schedules.
|
|
1533
1625
|
*/
|
|
1534
|
-
schedules(namespace, workflow, tag, paused,
|
|
1626
|
+
schedules(namespace, workflow, tag, paused, limit2) {
|
|
1535
1627
|
return this.dashboard.listSchedules({
|
|
1536
1628
|
...namespace ? {
|
|
1537
1629
|
namespace
|
|
@@ -1545,7 +1637,7 @@ var DurableApiControllerBase = class {
|
|
|
1545
1637
|
...paused === "true" || paused === "false" ? {
|
|
1546
1638
|
paused: paused === "true"
|
|
1547
1639
|
} : {},
|
|
1548
|
-
limit: Math.min(Number(
|
|
1640
|
+
limit: Math.min(Number(limit2) || 200, 1e3)
|
|
1549
1641
|
});
|
|
1550
1642
|
}
|
|
1551
1643
|
pauseSchedule(id) {
|
|
@@ -1595,17 +1687,17 @@ var DurableApiControllerBase = class {
|
|
|
1595
1687
|
return body;
|
|
1596
1688
|
}
|
|
1597
1689
|
/** One page of a summarized fan's members, in seq order. Versioned like {@link run}. */
|
|
1598
|
-
async fanMembers(id, group, offset,
|
|
1599
|
-
const
|
|
1690
|
+
async fanMembers(id, group, offset, limit2, failed, ifNoneMatch, response) {
|
|
1691
|
+
const page2 = await this.dashboard.getFanMembers(id, group, {
|
|
1600
1692
|
offset: offset === void 0 ? void 0 : Number(offset),
|
|
1601
|
-
limit:
|
|
1693
|
+
limit: limit2 === void 0 ? void 0 : Number(limit2),
|
|
1602
1694
|
failedOnly: failed === "true",
|
|
1603
1695
|
...ifNoneMatch && {
|
|
1604
1696
|
ifNoneMatch
|
|
1605
1697
|
}
|
|
1606
1698
|
});
|
|
1607
|
-
if (!
|
|
1608
|
-
const { etag, notModified, ...body } =
|
|
1699
|
+
if (!page2) throw new import_common6.NotFoundException(`fan ${group} of run ${id} not found`);
|
|
1700
|
+
const { etag, notModified, ...body } = page2;
|
|
1609
1701
|
if (etag) {
|
|
1610
1702
|
if (notModified) throw new NotModifiedException(etag);
|
|
1611
1703
|
setEtag(response, etag);
|
|
@@ -1630,7 +1722,7 @@ var DurableApiControllerBase = class {
|
|
|
1630
1722
|
* exactly the set the operator was looking at rather than a wider one.
|
|
1631
1723
|
*/
|
|
1632
1724
|
bulk(action, draft, compensate) {
|
|
1633
|
-
const { limit, offset, ...filter } = draft.query;
|
|
1725
|
+
const { limit: limit2, offset, ...filter } = draft.query;
|
|
1634
1726
|
return this.dashboard.bulk(action === "cancel" ? "cancel" : "retry", filter, {
|
|
1635
1727
|
compensate: compensate === "true"
|
|
1636
1728
|
});
|
|
@@ -1988,9 +2080,196 @@ DurableApiController = _ts_decorate6([
|
|
|
1988
2080
|
// src/server/durable-dashboard.module.ts
|
|
1989
2081
|
var import_reflect_metadata3 = require("reflect-metadata");
|
|
1990
2082
|
var import_nestjs_filter3 = require("@dudousxd/nestjs-filter");
|
|
1991
|
-
var
|
|
2083
|
+
var import_common10 = require("@nestjs/common");
|
|
1992
2084
|
var import_core2 = require("@nestjs/core");
|
|
1993
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
|
+
|
|
1994
2273
|
// src/server/controller-decorators.ts
|
|
1995
2274
|
var import_reflect_metadata2 = require("reflect-metadata");
|
|
1996
2275
|
var CAN_ANYONE_METADATA = "authorization:can-anyone";
|
|
@@ -2017,12 +2296,785 @@ function controllerFor(base, decorators) {
|
|
|
2017
2296
|
}
|
|
2018
2297
|
__name(controllerFor, "controllerFor");
|
|
2019
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
|
+
|
|
2020
3072
|
// src/server/durable-ui.controller.ts
|
|
2021
3073
|
var import_node_fs = require("fs");
|
|
2022
3074
|
var import_node_path = require("path");
|
|
2023
3075
|
var import_node_url = require("url");
|
|
2024
|
-
var
|
|
2025
|
-
function
|
|
3076
|
+
var import_common9 = require("@nestjs/common");
|
|
3077
|
+
function _ts_decorate9(decorators, target, key, desc) {
|
|
2026
3078
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2027
3079
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
2028
3080
|
r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -2035,21 +3087,20 @@ function _ts_decorate7(decorators, target, key, desc) {
|
|
|
2035
3087
|
}
|
|
2036
3088
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2037
3089
|
}
|
|
2038
|
-
__name(
|
|
2039
|
-
function
|
|
3090
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
3091
|
+
function _ts_metadata8(metadataKey, metadataValue) {
|
|
2040
3092
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
2041
3093
|
return Reflect.metadata(metadataKey, metadataValue);
|
|
2042
3094
|
}
|
|
2043
3095
|
}
|
|
2044
|
-
__name(
|
|
2045
|
-
function
|
|
3096
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
3097
|
+
function _ts_param6(paramIndex, decorator) {
|
|
2046
3098
|
return function(target, key) {
|
|
2047
3099
|
decorator(target, key, paramIndex);
|
|
2048
3100
|
};
|
|
2049
3101
|
}
|
|
2050
|
-
__name(
|
|
2051
|
-
var
|
|
2052
|
-
var DASHBOARD_API_PATH = Symbol("DASHBOARD_API_PATH");
|
|
3102
|
+
__name(_ts_param6, "_ts_param");
|
|
3103
|
+
var DURABLE_DASHBOARD_CONFIG_ID = "durable-dashboard-config";
|
|
2053
3104
|
var BUILD_BASE = "/durable";
|
|
2054
3105
|
function spaDir() {
|
|
2055
3106
|
return (0, import_node_url.fileURLToPath)(new URL("../spa", __importMetaUrl));
|
|
@@ -2080,51 +3131,65 @@ var DurableUiControllerBase = class {
|
|
|
2080
3131
|
index() {
|
|
2081
3132
|
const indexPath = (0, import_node_path.join)(this.dir, "index.html");
|
|
2082
3133
|
if (!(0, import_node_fs.existsSync)(indexPath)) {
|
|
2083
|
-
throw new
|
|
3134
|
+
throw new import_common9.NotFoundException("Dashboard is not built. Run the package build.");
|
|
2084
3135
|
}
|
|
2085
3136
|
const html = (0, import_node_fs.readFileSync)(indexPath, "utf8").replaceAll(`="${BUILD_BASE}/`, `="${this.basePath}/`);
|
|
2086
|
-
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()}`;
|
|
2087
3138
|
return html.includes("</head>") ? html.replace("</head>", `${inject}</head>`) : inject + html;
|
|
2088
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
|
+
}
|
|
2089
3154
|
asset(file) {
|
|
2090
3155
|
const safe = (0, import_node_path.basename)(file);
|
|
2091
|
-
if (safe !== file) throw new
|
|
3156
|
+
if (safe !== file) throw new import_common9.NotFoundException();
|
|
2092
3157
|
const root = (0, import_node_path.resolve)(this.dir, "assets");
|
|
2093
3158
|
const assetPath = (0, import_node_path.resolve)(root, safe);
|
|
2094
3159
|
if (!assetPath.startsWith(root + import_node_path.sep) || !(0, import_node_fs.existsSync)(assetPath)) {
|
|
2095
|
-
throw new
|
|
3160
|
+
throw new import_common9.NotFoundException();
|
|
2096
3161
|
}
|
|
2097
3162
|
const type = CONTENT_TYPES[(0, import_node_path.extname)(safe)] ?? "application/octet-stream";
|
|
2098
|
-
return new
|
|
3163
|
+
return new import_common9.StreamableFile((0, import_node_fs.readFileSync)(assetPath), {
|
|
2099
3164
|
type
|
|
2100
3165
|
});
|
|
2101
3166
|
}
|
|
2102
3167
|
};
|
|
2103
|
-
|
|
2104
|
-
(0,
|
|
2105
|
-
(0,
|
|
2106
|
-
(0,
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
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)
|
|
2110
3175
|
], DurableUiControllerBase.prototype, "index", null);
|
|
2111
|
-
|
|
2112
|
-
(0,
|
|
2113
|
-
(0,
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
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", [
|
|
2117
3182
|
String
|
|
2118
3183
|
]),
|
|
2119
|
-
|
|
3184
|
+
_ts_metadata8("design:returntype", typeof import_common9.StreamableFile === "undefined" ? Object : import_common9.StreamableFile)
|
|
2120
3185
|
], DurableUiControllerBase.prototype, "asset", null);
|
|
2121
|
-
DurableUiControllerBase =
|
|
2122
|
-
(0,
|
|
3186
|
+
DurableUiControllerBase = _ts_decorate9([
|
|
3187
|
+
(0, import_common9.Controller)(),
|
|
2123
3188
|
ConsolePage(),
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
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", [
|
|
2128
3193
|
String,
|
|
2129
3194
|
String
|
|
2130
3195
|
])
|
|
@@ -2134,12 +3199,12 @@ var DurableUiController = class extends DurableUiControllerBase {
|
|
|
2134
3199
|
__name(this, "DurableUiController");
|
|
2135
3200
|
}
|
|
2136
3201
|
};
|
|
2137
|
-
DurableUiController =
|
|
2138
|
-
(0,
|
|
3202
|
+
DurableUiController = _ts_decorate9([
|
|
3203
|
+
(0, import_common9.Controller)()
|
|
2139
3204
|
], DurableUiController);
|
|
2140
3205
|
|
|
2141
3206
|
// src/server/durable-dashboard.module.ts
|
|
2142
|
-
function
|
|
3207
|
+
function _ts_decorate10(decorators, target, key, desc) {
|
|
2143
3208
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2144
3209
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
2145
3210
|
r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -2152,9 +3217,10 @@ function _ts_decorate8(decorators, target, key, desc) {
|
|
|
2152
3217
|
}
|
|
2153
3218
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2154
3219
|
}
|
|
2155
|
-
__name(
|
|
3220
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
2156
3221
|
markOpenToAuthorization(DurableApiController);
|
|
2157
3222
|
markOpenToAuthorization(DurableUiController);
|
|
3223
|
+
markOpenToAuthorization(DurableConsolesController);
|
|
2158
3224
|
function assertNothingToDecorate(options) {
|
|
2159
3225
|
if (options.decorators === void 0) return;
|
|
2160
3226
|
throw new Error([
|
|
@@ -2188,10 +3254,16 @@ var DurableApiModule = class _DurableApiModule {
|
|
|
2188
3254
|
...options.imports ?? []
|
|
2189
3255
|
],
|
|
2190
3256
|
controllers: routes ? [
|
|
2191
|
-
consoleController(DurableApiController, options.decorators)
|
|
3257
|
+
consoleController(DurableApiController, options.decorators),
|
|
3258
|
+
consoleController(DurableConsolesController, options.decorators)
|
|
2192
3259
|
] : [],
|
|
2193
3260
|
providers: [
|
|
2194
3261
|
DashboardService,
|
|
3262
|
+
{
|
|
3263
|
+
provide: DASHBOARD_DEV_TOOLS,
|
|
3264
|
+
useValue: options.devTools ?? routes
|
|
3265
|
+
},
|
|
3266
|
+
DurableDevToolsRegistration,
|
|
2195
3267
|
// Resolved at boot: a gate name without nestjs-authz, or a policy without
|
|
2196
3268
|
// @nestjs/authorization, fails here rather than on the first request.
|
|
2197
3269
|
{
|
|
@@ -2249,8 +3321,8 @@ var DurableApiModule = class _DurableApiModule {
|
|
|
2249
3321
|
};
|
|
2250
3322
|
}
|
|
2251
3323
|
};
|
|
2252
|
-
DurableApiModule =
|
|
2253
|
-
(0,
|
|
3324
|
+
DurableApiModule = _ts_decorate10([
|
|
3325
|
+
(0, import_common10.Module)({})
|
|
2254
3326
|
], DurableApiModule);
|
|
2255
3327
|
var DurableDashboardModule = class _DurableDashboardModule {
|
|
2256
3328
|
static {
|
|
@@ -2278,6 +3350,9 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
2278
3350
|
...options.authorize !== void 0 ? {
|
|
2279
3351
|
authorize: options.authorize
|
|
2280
3352
|
} : {},
|
|
3353
|
+
...options.devTools !== void 0 ? {
|
|
3354
|
+
devTools: options.devTools
|
|
3355
|
+
} : {},
|
|
2281
3356
|
routes: false
|
|
2282
3357
|
});
|
|
2283
3358
|
return {
|
|
@@ -2314,6 +3389,9 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
2314
3389
|
} : {},
|
|
2315
3390
|
...options.authorize !== void 0 ? {
|
|
2316
3391
|
authorize: options.authorize
|
|
3392
|
+
} : {},
|
|
3393
|
+
...options.devTools !== void 0 ? {
|
|
3394
|
+
devTools: options.devTools
|
|
2317
3395
|
} : {}
|
|
2318
3396
|
}),
|
|
2319
3397
|
import_core2.RouterModule.register([
|
|
@@ -2330,7 +3408,11 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
2330
3408
|
controllers: [
|
|
2331
3409
|
consoleController(DurableUiController, options.decorators)
|
|
2332
3410
|
],
|
|
2333
|
-
|
|
3411
|
+
// The console switcher lists this console only where it serves its own page.
|
|
3412
|
+
providers: [
|
|
3413
|
+
...pathProviders,
|
|
3414
|
+
DurableConsoleRegistration
|
|
3415
|
+
],
|
|
2334
3416
|
// Re-export the API module so its DashboardService reaches importers (e.g. flip's own controllers).
|
|
2335
3417
|
exports: [
|
|
2336
3418
|
DurableApiModule
|
|
@@ -2338,15 +3420,17 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
2338
3420
|
};
|
|
2339
3421
|
}
|
|
2340
3422
|
};
|
|
2341
|
-
DurableDashboardModule =
|
|
2342
|
-
(0,
|
|
3423
|
+
DurableDashboardModule = _ts_decorate10([
|
|
3424
|
+
(0, import_common10.Module)({})
|
|
2343
3425
|
], DurableDashboardModule);
|
|
2344
3426
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2345
3427
|
0 && (module.exports = {
|
|
3428
|
+
AVIARY_CONSOLES_SLOT,
|
|
2346
3429
|
CONSOLE_AUTHORIZE_DECIDE,
|
|
2347
3430
|
CONSOLE_AUTHORIZE_MISSING_WARNING,
|
|
2348
3431
|
CONSOLE_GATE,
|
|
2349
3432
|
CONSOLE_PAGE_METADATA,
|
|
3433
|
+
CONSOLE_PROBE_TIMEOUT_MS,
|
|
2350
3434
|
CONSOLE_PUBLIC_METADATA,
|
|
2351
3435
|
ConsoleAccessDenied,
|
|
2352
3436
|
ConsoleAuthorizeFilter,
|
|
@@ -2355,16 +3439,27 @@ DurableDashboardModule = _ts_decorate8([
|
|
|
2355
3439
|
ConsolePublic,
|
|
2356
3440
|
DASHBOARD_API_PATH,
|
|
2357
3441
|
DASHBOARD_BASE_PATH,
|
|
3442
|
+
DEV_TOOLS_SLOT,
|
|
3443
|
+
DURABLE_CONSOLE_ID,
|
|
3444
|
+
DURABLE_DASHBOARD_CONFIG_ID,
|
|
2358
3445
|
DashboardService,
|
|
2359
3446
|
DurableApiController,
|
|
2360
3447
|
DurableApiControllerBase,
|
|
2361
3448
|
DurableApiModule,
|
|
3449
|
+
DurableConsoleRegistration,
|
|
3450
|
+
DurableConsolesController,
|
|
2362
3451
|
DurableDashboardModule,
|
|
3452
|
+
DurableDevToolsRegistration,
|
|
2363
3453
|
DurableUiController,
|
|
2364
3454
|
DurableUiControllerBase,
|
|
3455
|
+
MAX_RUN_IDS,
|
|
2365
3456
|
UseConsoleAuthorize,
|
|
3457
|
+
anyAuthorize,
|
|
2366
3458
|
authorizeByToken,
|
|
3459
|
+
durableDevTools,
|
|
3460
|
+
listVisibleConsoles,
|
|
2367
3461
|
normalizeConsoleResult,
|
|
3462
|
+
registerAviaryConsole,
|
|
2368
3463
|
resolveConsoleAuthorize
|
|
2369
3464
|
});
|
|
2370
3465
|
//# sourceMappingURL=index.cjs.map
|