@dudousxd/nestjs-durable-dashboard 0.53.0 → 0.55.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.
Files changed (41) hide show
  1. package/dist/client/durable-client.d.ts +67 -3
  2. package/dist/client/durable-client.d.ts.map +1 -1
  3. package/dist/client/durable-client.js +26 -46
  4. package/dist/client/durable-client.js.map +1 -1
  5. package/dist/client/event-tail.d.ts +25 -0
  6. package/dist/client/event-tail.d.ts.map +1 -0
  7. package/dist/client/event-tail.js +30 -0
  8. package/dist/client/event-tail.js.map +1 -0
  9. package/dist/server/index.cjs +322 -1337
  10. package/dist/server/index.cjs.map +1 -1
  11. package/dist/server/index.d.cts +149 -389
  12. package/dist/server/index.d.ts +149 -389
  13. package/dist/server/index.js +308 -1317
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/spa/assets/{bench-Db-gqOEF.js → bench-DCclNvcZ.js} +1 -1
  16. package/dist/spa/assets/index-B-wtX4GV.js +66 -0
  17. package/dist/spa/assets/index-CJdk28vt.css +1 -0
  18. package/dist/spa/assets/{index-4tEXGRlA.js → index-ChjKRQZx.js} +1 -1
  19. package/dist/spa/assets/{preview-BvCp1zR5.js → preview-CdsjvOrK.js} +1 -1
  20. package/dist/spa/bench.html +3 -3
  21. package/dist/spa/index.html +3 -3
  22. package/dist/spa/preview.html +3 -3
  23. package/package.json +5 -18
  24. package/dist/client/console-session.d.ts +0 -63
  25. package/dist/client/console-session.d.ts.map +0 -1
  26. package/dist/client/console-session.js +0 -107
  27. package/dist/client/console-session.js.map +0 -1
  28. package/dist/react/index.d.ts +0 -4
  29. package/dist/react/index.d.ts.map +0 -1
  30. package/dist/react/index.js +0 -14
  31. package/dist/react/index.js.map +0 -1
  32. package/dist/react/open-console-button.d.ts +0 -26
  33. package/dist/react/open-console-button.d.ts.map +0 -1
  34. package/dist/react/open-console-button.js +0 -26
  35. package/dist/react/open-console-button.js.map +0 -1
  36. package/dist/react/use-open-console.d.ts +0 -40
  37. package/dist/react/use-open-console.d.ts.map +0 -1
  38. package/dist/react/use-open-console.js +0 -77
  39. package/dist/react/use-open-console.js.map +0 -1
  40. package/dist/spa/assets/index-BbR4CZ1K.css +0 -1
  41. package/dist/spa/assets/index-CEV7yPmk.js +0 -66
@@ -174,6 +174,51 @@ var DashboardService = class {
174
174
  return this.gateway.cancel(runId, opts);
175
175
  }
176
176
  /**
177
+ * Publish a settled run's lifecycle event again, to its subscribers that have not started (or to
178
+ * `subscriber` alone). Undefined when the gateway cannot republish (a tenant proxy).
179
+ */
180
+ async republishLifecycle(runId, subscriber) {
181
+ return this.gateway.republishLifecycle?.({
182
+ runId,
183
+ ...subscriber !== void 0 ? {
184
+ subscriber
185
+ } : {}
186
+ });
187
+ }
188
+ /**
189
+ * Who starts whom through lifecycle events, for `workflow`, plus `triggerCounts` when this process
190
+ * counts the engine's events (the control plane) and the workflow has subscribers. Undefined when
191
+ * the gateway cannot answer.
192
+ */
193
+ async workflowLifecycle(workflow) {
194
+ const links = await this.gateway.workflowLifecycle?.(workflow);
195
+ if (!links) return links;
196
+ const counts = this.triggerCountsOf(links);
197
+ return counts ? {
198
+ ...links,
199
+ triggerCounts: counts
200
+ } : links;
201
+ }
202
+ /**
203
+ * How many runs each subscriber of `links.workflow` was started for and how many times starting it
204
+ * failed, counted by this process's metrics since it started. Undefined without metrics (a tenant)
205
+ * or without subscribers.
206
+ */
207
+ triggerCountsOf(links) {
208
+ const snapshot = this.metricsCollector?.snapshot();
209
+ if (!snapshot || links.startsOnSettle.length === 0) return void 0;
210
+ const triggered = snapshot.triggeredBySubscriber[links.workflow] ?? {};
211
+ const failed = snapshot.triggerFailedBySubscriber[links.workflow] ?? {};
212
+ const counts = {};
213
+ for (const { workflow } of links.startsOnSettle) {
214
+ counts[workflow] = {
215
+ triggered: triggered[workflow] ?? 0,
216
+ failed: failed[workflow] ?? 0
217
+ };
218
+ }
219
+ return counts;
220
+ }
221
+ /**
177
222
  * Apply `retry` or `cancel` to every run matching a filter (status / tag / workflow / namespace /
178
223
  * origin) — e.g. "retry every `dead` run tagged `type:mel`". The filter is the same one the run
179
224
  * list uses, so a bulk action can never reach wider than the list it was launched from — but note
@@ -274,7 +319,7 @@ DashboardService = _ts_decorate([
274
319
 
275
320
  // src/server/durable-api.controller.ts
276
321
  import { ApplyFilter, ApplyFilterInterceptor, FilterRunner } from "@dudousxd/nestjs-filter";
277
- import { Body, Controller, Get, Header, Headers, NotFoundException, Param, Post, Query, Res, Sse, UseFilters, UseInterceptors } from "@nestjs/common";
322
+ import { BadRequestException as BadRequestException2, Body, Controller, Get, Header, Headers, NotFoundException, NotImplementedException, Param, Post, Query, Res, Sse, UseFilters, UseInterceptors } from "@nestjs/common";
278
323
 
279
324
  // src/server/durable-run.ts
280
325
  var DurableRun = class {
@@ -351,87 +396,6 @@ __name(runValueAxisFor, "runValueAxisFor");
351
396
 
352
397
  // src/server/not-modified.ts
353
398
  import { Catch, HttpException } from "@nestjs/common";
354
-
355
- // src/server/auth/response.ts
356
- function isRecord(value) {
357
- return typeof value === "object" && value !== null;
358
- }
359
- __name(isRecord, "isRecord");
360
- function isRawCookieResponse(value) {
361
- return isRecord(value) && typeof value.getHeader === "function" && typeof value.setHeader === "function";
362
- }
363
- __name(isRawCookieResponse, "isRawCookieResponse");
364
- function resolveCookieResponse(response) {
365
- if (isRawCookieResponse(response)) return response;
366
- if (isRecord(response) && isRawCookieResponse(response.raw)) return response.raw;
367
- return null;
368
- }
369
- __name(resolveCookieResponse, "resolveCookieResponse");
370
- function existingSetCookies(response) {
371
- const current = response.getHeader("set-cookie");
372
- if (Array.isArray(current)) return current;
373
- if (typeof current === "string") return [
374
- current
375
- ];
376
- return [];
377
- }
378
- __name(existingSetCookies, "existingSetCookies");
379
- function appendSetCookie(response, cookie) {
380
- const raw = resolveCookieResponse(response);
381
- if (!raw) return;
382
- raw.setHeader("set-cookie", [
383
- ...existingSetCookies(raw),
384
- cookie
385
- ]);
386
- }
387
- __name(appendSetCookie, "appendSetCookie");
388
- function isRawRedirectResponse(value) {
389
- return isRecord(value) && typeof value.getHeader === "function" && typeof value.setHeader === "function" && typeof value.end === "function";
390
- }
391
- __name(isRawRedirectResponse, "isRawRedirectResponse");
392
- function resolveRedirectResponse(response) {
393
- if (isRawRedirectResponse(response)) return response;
394
- if (isRecord(response) && isRawRedirectResponse(response.raw)) return response.raw;
395
- return null;
396
- }
397
- __name(resolveRedirectResponse, "resolveRedirectResponse");
398
- function redirectRaw(response, location, status = 302) {
399
- const raw = resolveRedirectResponse(response);
400
- if (!raw) return;
401
- raw.statusCode = status;
402
- raw.setHeader("location", location);
403
- raw.end();
404
- }
405
- __name(redirectRaw, "redirectRaw");
406
- function responseAlreadyWritten(response) {
407
- const raw = resolveRedirectResponse(response);
408
- return raw?.headersSent === true;
409
- }
410
- __name(responseAlreadyWritten, "responseAlreadyWritten");
411
- function writeHtmlRaw(response, html, status = 200) {
412
- const raw = resolveRedirectResponse(response);
413
- if (!raw) return;
414
- raw.statusCode = status;
415
- raw.setHeader("content-type", "text/html; charset=utf-8");
416
- raw.setHeader("cache-control", "no-store, must-revalidate");
417
- raw.end(html);
418
- }
419
- __name(writeHtmlRaw, "writeHtmlRaw");
420
- function setRawHeader(response, name, value) {
421
- resolveCookieResponse(response)?.setHeader(name, value);
422
- }
423
- __name(setRawHeader, "setRawHeader");
424
- function writeNotModifiedRaw(response, etag) {
425
- const raw = resolveRedirectResponse(response);
426
- if (!raw) return;
427
- raw.statusCode = 304;
428
- raw.setHeader("etag", etag);
429
- raw.setHeader("cache-control", "private, no-cache");
430
- raw.end();
431
- }
432
- __name(writeNotModifiedRaw, "writeNotModifiedRaw");
433
-
434
- // src/server/not-modified.ts
435
399
  function _ts_decorate2(decorators, target, key, desc) {
436
400
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
437
401
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -446,6 +410,31 @@ function _ts_decorate2(decorators, target, key, desc) {
446
410
  return c > 3 && r && Object.defineProperty(target, key, r), r;
447
411
  }
448
412
  __name(_ts_decorate2, "_ts_decorate");
413
+ function isRawResponse(value) {
414
+ if (typeof value !== "object" || value === null) return false;
415
+ const candidate = value;
416
+ return typeof candidate.setHeader === "function" && typeof candidate.end === "function";
417
+ }
418
+ __name(isRawResponse, "isRawResponse");
419
+ function rawResponse(response) {
420
+ if (isRawResponse(response)) return response;
421
+ const raw = response?.raw;
422
+ return isRawResponse(raw) ? raw : null;
423
+ }
424
+ __name(rawResponse, "rawResponse");
425
+ function setRawHeader(response, name, value) {
426
+ rawResponse(response)?.setHeader(name, value);
427
+ }
428
+ __name(setRawHeader, "setRawHeader");
429
+ function writeNotModifiedRaw(response, etag) {
430
+ const raw = rawResponse(response);
431
+ if (!raw) return;
432
+ raw.statusCode = 304;
433
+ raw.setHeader("etag", etag);
434
+ raw.setHeader("cache-control", "private, no-cache");
435
+ raw.end();
436
+ }
437
+ __name(writeNotModifiedRaw, "writeNotModifiedRaw");
449
438
  var NotModifiedException = class extends HttpException {
450
439
  static {
451
440
  __name(this, "NotModifiedException");
@@ -761,10 +750,10 @@ var RunQueryAdapter = class {
761
750
  }
762
751
  unsupported(clause, "equals, notEquals, gt, gte, lt, lte, in");
763
752
  }
764
- applyOffsetPagination(qb, page2, size) {
753
+ applyOffsetPagination(qb, page, size) {
765
754
  this.draft(qb).narrow({
766
755
  limit: size,
767
- offset: (page2 - 1) * size
756
+ offset: (page - 1) * size
768
757
  });
769
758
  }
770
759
  /**
@@ -1108,9 +1097,9 @@ function _ts_param2(paramIndex, decorator) {
1108
1097
  }
1109
1098
  __name(_ts_param2, "_ts_param");
1110
1099
  var FAN_INLINE_MAX = 50;
1111
- var DurableApiController = class {
1100
+ var DurableApiControllerBase = class {
1112
1101
  static {
1113
- __name(this, "DurableApiController");
1102
+ __name(this, "DurableApiControllerBase");
1114
1103
  }
1115
1104
  dashboard;
1116
1105
  runner;
@@ -1202,6 +1191,15 @@ var DurableApiController = class {
1202
1191
  triggerSchedule(id) {
1203
1192
  return this.dashboard.triggerSchedule(id).catch(notFound);
1204
1193
  }
1194
+ /**
1195
+ * Who starts whom through lifecycle events, for one workflow: the workflows that start when it
1196
+ * settles, and the ones whose settling starts it. What the console's workflow page lists.
1197
+ */
1198
+ async workflowLifecycle(name) {
1199
+ const links = await this.dashboard.workflowLifecycle(name).catch(notSupportedByServer);
1200
+ if (!links) throw new NotImplementedException("workflow lifecycle links need the control plane");
1201
+ return links;
1202
+ }
1205
1203
  /** This deployment's durable role (control plane vs tenant) + tenant name — for the header badge. */
1206
1204
  topology() {
1207
1205
  return this.dashboard.topology();
@@ -1231,7 +1229,7 @@ var DurableApiController = class {
1231
1229
  }
1232
1230
  /** One page of a summarized fan's members, in seq order. Versioned like {@link run}. */
1233
1231
  async fanMembers(id, group, offset, limit, failed, ifNoneMatch, response) {
1234
- const page2 = await this.dashboard.getFanMembers(id, group, {
1232
+ const page = await this.dashboard.getFanMembers(id, group, {
1235
1233
  offset: offset === void 0 ? void 0 : Number(offset),
1236
1234
  limit: limit === void 0 ? void 0 : Number(limit),
1237
1235
  failedOnly: failed === "true",
@@ -1239,8 +1237,8 @@ var DurableApiController = class {
1239
1237
  ifNoneMatch
1240
1238
  }
1241
1239
  });
1242
- if (!page2) throw new NotFoundException(`fan ${group} of run ${id} not found`);
1243
- const { etag, notModified, ...body } = page2;
1240
+ if (!page) throw new NotFoundException(`fan ${group} of run ${id} not found`);
1241
+ const { etag, notModified, ...body } = page;
1244
1242
  if (etag) {
1245
1243
  if (notModified) throw new NotModifiedException(etag);
1246
1244
  setEtag(response, etag);
@@ -1277,6 +1275,28 @@ var DurableApiController = class {
1277
1275
  if (!result) throw new NotFoundException(`run ${id} not found`);
1278
1276
  return result;
1279
1277
  }
1278
+ /**
1279
+ * Publish a settled run's lifecycle event again, to the subscribers that have not started from it
1280
+ * (or only `subscriber`). Same gate as retry/cancel.
1281
+ */
1282
+ async republishLifecycle(id, body) {
1283
+ let result;
1284
+ try {
1285
+ result = await this.dashboard.republishLifecycle(id, body?.subscriber || void 0);
1286
+ } catch (error) {
1287
+ const message = error instanceof Error ? error.message : String(error);
1288
+ if (/^run .* not found$/.test(message)) throw new NotFoundException(message);
1289
+ if (/^run .* has not settled/.test(message)) throw new BadRequestException2(message);
1290
+ if (error?.code === "bad-request") {
1291
+ throw new BadRequestException2(message);
1292
+ }
1293
+ return notSupportedByServer(error);
1294
+ }
1295
+ if (!result) {
1296
+ throw new NotImplementedException("republishing lifecycle events needs the control plane");
1297
+ }
1298
+ return result;
1299
+ }
1280
1300
  async continue(id) {
1281
1301
  const result = await this.dashboard.continue(id);
1282
1302
  if (!result) throw new NotFoundException(`run ${id} is not paused at a breakpoint`);
@@ -1316,7 +1336,7 @@ _ts_decorate5([
1316
1336
  typeof RunQueryDraft === "undefined" ? Object : RunQueryDraft
1317
1337
  ]),
1318
1338
  _ts_metadata4("design:returntype", Promise)
1319
- ], DurableApiController.prototype, "runs", null);
1339
+ ], DurableApiControllerBase.prototype, "runs", null);
1320
1340
  _ts_decorate5([
1321
1341
  Get("runs/facets"),
1322
1342
  _ts_param2(0, ApplyFilter(RunFilter)),
@@ -1325,7 +1345,7 @@ _ts_decorate5([
1325
1345
  typeof RunQueryDraft === "undefined" ? Object : RunQueryDraft
1326
1346
  ]),
1327
1347
  _ts_metadata4("design:returntype", void 0)
1328
- ], DurableApiController.prototype, "facets", null);
1348
+ ], DurableApiControllerBase.prototype, "facets", null);
1329
1349
  _ts_decorate5([
1330
1350
  Get("runs/values"),
1331
1351
  _ts_param2(0, Query()),
@@ -1334,20 +1354,20 @@ _ts_decorate5([
1334
1354
  typeof Record === "undefined" ? Object : Record
1335
1355
  ]),
1336
1356
  _ts_metadata4("design:returntype", void 0)
1337
- ], DurableApiController.prototype, "values", null);
1357
+ ], DurableApiControllerBase.prototype, "values", null);
1338
1358
  _ts_decorate5([
1339
1359
  Get("metrics"),
1340
1360
  Header("Content-Type", "text/plain; version=0.0.4"),
1341
1361
  _ts_metadata4("design:type", Function),
1342
1362
  _ts_metadata4("design:paramtypes", []),
1343
1363
  _ts_metadata4("design:returntype", void 0)
1344
- ], DurableApiController.prototype, "metrics", null);
1364
+ ], DurableApiControllerBase.prototype, "metrics", null);
1345
1365
  _ts_decorate5([
1346
1366
  Get("workers"),
1347
1367
  _ts_metadata4("design:type", Function),
1348
1368
  _ts_metadata4("design:paramtypes", []),
1349
1369
  _ts_metadata4("design:returntype", void 0)
1350
- ], DurableApiController.prototype, "workers", null);
1370
+ ], DurableApiControllerBase.prototype, "workers", null);
1351
1371
  _ts_decorate5([
1352
1372
  Get("schedules"),
1353
1373
  _ts_param2(0, Query("namespace")),
@@ -1364,7 +1384,7 @@ _ts_decorate5([
1364
1384
  String
1365
1385
  ]),
1366
1386
  _ts_metadata4("design:returntype", void 0)
1367
- ], DurableApiController.prototype, "schedules", null);
1387
+ ], DurableApiControllerBase.prototype, "schedules", null);
1368
1388
  _ts_decorate5([
1369
1389
  Post("schedules/:id/pause"),
1370
1390
  _ts_param2(0, Param("id")),
@@ -1373,7 +1393,7 @@ _ts_decorate5([
1373
1393
  String
1374
1394
  ]),
1375
1395
  _ts_metadata4("design:returntype", void 0)
1376
- ], DurableApiController.prototype, "pauseSchedule", null);
1396
+ ], DurableApiControllerBase.prototype, "pauseSchedule", null);
1377
1397
  _ts_decorate5([
1378
1398
  Post("schedules/:id/resume"),
1379
1399
  _ts_param2(0, Param("id")),
@@ -1382,7 +1402,7 @@ _ts_decorate5([
1382
1402
  String
1383
1403
  ]),
1384
1404
  _ts_metadata4("design:returntype", void 0)
1385
- ], DurableApiController.prototype, "resumeSchedule", null);
1405
+ ], DurableApiControllerBase.prototype, "resumeSchedule", null);
1386
1406
  _ts_decorate5([
1387
1407
  Post("schedules/:id/trigger"),
1388
1408
  _ts_param2(0, Param("id")),
@@ -1391,13 +1411,22 @@ _ts_decorate5([
1391
1411
  String
1392
1412
  ]),
1393
1413
  _ts_metadata4("design:returntype", void 0)
1394
- ], DurableApiController.prototype, "triggerSchedule", null);
1414
+ ], DurableApiControllerBase.prototype, "triggerSchedule", null);
1415
+ _ts_decorate5([
1416
+ Get("workflows/:name/lifecycle"),
1417
+ _ts_param2(0, Param("name")),
1418
+ _ts_metadata4("design:type", Function),
1419
+ _ts_metadata4("design:paramtypes", [
1420
+ String
1421
+ ]),
1422
+ _ts_metadata4("design:returntype", Promise)
1423
+ ], DurableApiControllerBase.prototype, "workflowLifecycle", null);
1395
1424
  _ts_decorate5([
1396
1425
  Get("topology"),
1397
1426
  _ts_metadata4("design:type", Function),
1398
1427
  _ts_metadata4("design:paramtypes", []),
1399
1428
  _ts_metadata4("design:returntype", void 0)
1400
- ], DurableApiController.prototype, "topology", null);
1429
+ ], DurableApiControllerBase.prototype, "topology", null);
1401
1430
  _ts_decorate5([
1402
1431
  Get("runs/:id"),
1403
1432
  _ts_param2(0, Param("id")),
@@ -1414,7 +1443,7 @@ _ts_decorate5([
1414
1443
  Object
1415
1444
  ]),
1416
1445
  _ts_metadata4("design:returntype", Promise)
1417
- ], DurableApiController.prototype, "run", null);
1446
+ ], DurableApiControllerBase.prototype, "run", null);
1418
1447
  _ts_decorate5([
1419
1448
  Get("runs/:id/fans/:group"),
1420
1449
  _ts_param2(0, Param("id")),
@@ -1437,7 +1466,7 @@ _ts_decorate5([
1437
1466
  Object
1438
1467
  ]),
1439
1468
  _ts_metadata4("design:returntype", Promise)
1440
- ], DurableApiController.prototype, "fanMembers", null);
1469
+ ], DurableApiControllerBase.prototype, "fanMembers", null);
1441
1470
  _ts_decorate5([
1442
1471
  Sse("runs/:id/stream"),
1443
1472
  _ts_param2(0, Param("id")),
@@ -1446,7 +1475,7 @@ _ts_decorate5([
1446
1475
  String
1447
1476
  ]),
1448
1477
  _ts_metadata4("design:returntype", void 0)
1449
- ], DurableApiController.prototype, "stream", null);
1478
+ ], DurableApiControllerBase.prototype, "stream", null);
1450
1479
  _ts_decorate5([
1451
1480
  Post("runs/:id/retry"),
1452
1481
  _ts_param2(0, Param("id")),
@@ -1455,7 +1484,7 @@ _ts_decorate5([
1455
1484
  String
1456
1485
  ]),
1457
1486
  _ts_metadata4("design:returntype", void 0)
1458
- ], DurableApiController.prototype, "retry", null);
1487
+ ], DurableApiControllerBase.prototype, "retry", null);
1459
1488
  _ts_decorate5([
1460
1489
  Post("runs/:id/retry-with-input"),
1461
1490
  _ts_param2(0, Param("id")),
@@ -1466,7 +1495,7 @@ _ts_decorate5([
1466
1495
  Object
1467
1496
  ]),
1468
1497
  _ts_metadata4("design:returntype", void 0)
1469
- ], DurableApiController.prototype, "retryWithInput", null);
1498
+ ], DurableApiControllerBase.prototype, "retryWithInput", null);
1470
1499
  _ts_decorate5([
1471
1500
  Post("bulk/:action"),
1472
1501
  _ts_param2(0, Param("action")),
@@ -1479,7 +1508,7 @@ _ts_decorate5([
1479
1508
  String
1480
1509
  ]),
1481
1510
  _ts_metadata4("design:returntype", void 0)
1482
- ], DurableApiController.prototype, "bulk", null);
1511
+ ], DurableApiControllerBase.prototype, "bulk", null);
1483
1512
  _ts_decorate5([
1484
1513
  Post("runs/:id/cancel"),
1485
1514
  _ts_param2(0, Param("id")),
@@ -1490,7 +1519,18 @@ _ts_decorate5([
1490
1519
  String
1491
1520
  ]),
1492
1521
  _ts_metadata4("design:returntype", Promise)
1493
- ], DurableApiController.prototype, "cancel", null);
1522
+ ], DurableApiControllerBase.prototype, "cancel", null);
1523
+ _ts_decorate5([
1524
+ Post("runs/:id/republish-lifecycle"),
1525
+ _ts_param2(0, Param("id")),
1526
+ _ts_param2(1, Body()),
1527
+ _ts_metadata4("design:type", Function),
1528
+ _ts_metadata4("design:paramtypes", [
1529
+ String,
1530
+ Object
1531
+ ]),
1532
+ _ts_metadata4("design:returntype", Promise)
1533
+ ], DurableApiControllerBase.prototype, "republishLifecycle", null);
1494
1534
  _ts_decorate5([
1495
1535
  Post("runs/:id/continue"),
1496
1536
  _ts_param2(0, Param("id")),
@@ -1499,7 +1539,7 @@ _ts_decorate5([
1499
1539
  String
1500
1540
  ]),
1501
1541
  _ts_metadata4("design:returntype", Promise)
1502
- ], DurableApiController.prototype, "continue", null);
1542
+ ], DurableApiControllerBase.prototype, "continue", null);
1503
1543
  _ts_decorate5([
1504
1544
  Post("runs/:id/redispatch"),
1505
1545
  _ts_param2(0, Param("id")),
@@ -1508,7 +1548,7 @@ _ts_decorate5([
1508
1548
  String
1509
1549
  ]),
1510
1550
  _ts_metadata4("design:returntype", Promise)
1511
- ], DurableApiController.prototype, "redispatch", null);
1551
+ ], DurableApiControllerBase.prototype, "redispatch", null);
1512
1552
  _ts_decorate5([
1513
1553
  Post("webhooks/:token"),
1514
1554
  _ts_param2(0, Param("token")),
@@ -1519,7 +1559,7 @@ _ts_decorate5([
1519
1559
  Object
1520
1560
  ]),
1521
1561
  _ts_metadata4("design:returntype", Promise)
1522
- ], DurableApiController.prototype, "webhook", null);
1562
+ ], DurableApiControllerBase.prototype, "webhook", null);
1523
1563
  _ts_decorate5([
1524
1564
  Get("runs/:id/events/:key"),
1525
1565
  _ts_param2(0, Param("id")),
@@ -1530,7 +1570,7 @@ _ts_decorate5([
1530
1570
  String
1531
1571
  ]),
1532
1572
  _ts_metadata4("design:returntype", void 0)
1533
- ], DurableApiController.prototype, "event", null);
1573
+ ], DurableApiControllerBase.prototype, "event", null);
1534
1574
  _ts_decorate5([
1535
1575
  Post("runs/:id/updates/:name"),
1536
1576
  _ts_param2(0, Param("id")),
@@ -1543,8 +1583,8 @@ _ts_decorate5([
1543
1583
  Object
1544
1584
  ]),
1545
1585
  _ts_metadata4("design:returntype", void 0)
1546
- ], DurableApiController.prototype, "update", null);
1547
- DurableApiController = _ts_decorate5([
1586
+ ], DurableApiControllerBase.prototype, "update", null);
1587
+ DurableApiControllerBase = _ts_decorate5([
1548
1588
  Controller(),
1549
1589
  UseInterceptors(ApplyFilterInterceptor),
1550
1590
  UseFilters(NotModifiedFilter),
@@ -1553,7 +1593,7 @@ DurableApiController = _ts_decorate5([
1553
1593
  typeof DashboardService === "undefined" ? Object : DashboardService,
1554
1594
  typeof FilterRunner === "undefined" ? Object : FilterRunner
1555
1595
  ])
1556
- ], DurableApiController);
1596
+ ], DurableApiControllerBase);
1557
1597
  function notFound(error) {
1558
1598
  if (error instanceof Error && error.name === "ScheduleNotFoundError") {
1559
1599
  throw new NotFoundException(error.message);
@@ -1561,498 +1601,59 @@ function notFound(error) {
1561
1601
  throw error;
1562
1602
  }
1563
1603
  __name(notFound, "notFound");
1564
-
1565
- // src/server/durable-auth.controller.ts
1566
- 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 as Res2, UnauthorizedException } from "@nestjs/common";
1567
-
1568
- // src/server/auth/dashboard-auth-config.ts
1569
- var DASHBOARD_AUTH = Symbol("DASHBOARD_AUTH");
1570
- var DEFAULT_TTL = "8h";
1571
- var DEFAULT_TTL_MS = 8 * 60 * 60 * 1e3;
1572
- var DURATION_UNITS = {
1573
- s: 1e3,
1574
- m: 60 * 1e3,
1575
- h: 60 * 60 * 1e3,
1576
- d: 24 * 60 * 60 * 1e3
1577
- };
1578
- function durationToMs(ttl) {
1579
- const match = /^(\d+)([smhd])$/.exec(ttl.trim());
1580
- if (!match) return DEFAULT_TTL_MS;
1581
- const unit = DURATION_UNITS[match[2] ?? ""];
1582
- if (unit === void 0) return DEFAULT_TTL_MS;
1583
- return Number(match[1]) * unit;
1584
- }
1585
- __name(durationToMs, "durationToMs");
1586
- function resolveDashboardAuth(options) {
1587
- if (options === void 0) return null;
1588
- if (typeof options.secret !== "string" || options.secret === "") {
1589
- throw new Error("DurableDashboardModule: dashboardAuth.secret is required and must be a non-empty string (HMAC-SHA256 signing key, 32+ bytes recommended). Failing closed.");
1590
- }
1591
- const modes = [];
1592
- if (options.session !== void 0) {
1593
- if (typeof options.session !== "function") {
1594
- throw new Error("DurableDashboardModule: dashboardAuth.session must be a function.");
1595
- }
1596
- modes.push("session");
1597
- }
1598
- if (options.login !== void 0) {
1599
- if (typeof options.login !== "function") {
1600
- throw new Error("DurableDashboardModule: dashboardAuth.login must be a function.");
1601
- }
1602
- modes.push("login");
1603
- }
1604
- if (options.revalidate !== void 0 && typeof options.revalidate !== "function") {
1605
- throw new Error("DurableDashboardModule: dashboardAuth.revalidate must be a function.");
1604
+ function notSupportedByServer(error) {
1605
+ if (error?.code === "unsupported-request") {
1606
+ throw new NotImplementedException(error instanceof Error ? error.message : String(error));
1606
1607
  }
1607
- if (options.unauthenticatedPage !== void 0 && typeof options.unauthenticatedPage !== "function") {
1608
- throw new Error("DurableDashboardModule: dashboardAuth.unauthenticatedPage must be a function.");
1609
- }
1610
- if (modes.length === 0) {
1611
- throw new Error("DurableDashboardModule: dashboardAuth needs at least one of `session` or `login` (otherwise the cookie can never be minted). Failing closed.");
1612
- }
1613
- return {
1614
- secret: options.secret,
1615
- ttlMs: durationToMs(options.ttl ?? DEFAULT_TTL),
1616
- modes,
1617
- ...options.session !== void 0 ? {
1618
- session: options.session
1619
- } : {},
1620
- ...options.login !== void 0 ? {
1621
- login: options.login
1622
- } : {},
1623
- ...options.revalidate !== void 0 ? {
1624
- revalidate: options.revalidate
1625
- } : {},
1626
- ...options.unauthenticatedPage !== void 0 ? {
1627
- unauthenticatedPage: options.unauthenticatedPage
1628
- } : {}
1629
- };
1630
- }
1631
- __name(resolveDashboardAuth, "resolveDashboardAuth");
1632
-
1633
- // src/server/auth/login-page.ts
1634
- function page(content, title = "Durable") {
1635
- return `<!doctype html>
1636
- <html lang="en">
1637
- <head>
1638
- <meta charset="utf-8" />
1639
- <meta name="viewport" content="width=device-width, initial-scale=1" />
1640
- <meta name="robots" content="noindex, nofollow" />
1641
- <title>${title}</title>
1642
- <style>
1643
- :root { color-scheme: dark; }
1644
- * { box-sizing: border-box; }
1645
- body {
1646
- margin: 0;
1647
- min-height: 100vh;
1648
- display: flex;
1649
- align-items: center;
1650
- justify-content: center;
1651
- background: #09090b;
1652
- color: #e4e4e7;
1653
- font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1654
- padding: 16px;
1655
- }
1656
- .card {
1657
- width: 100%;
1658
- max-width: 384px;
1659
- border: 1px solid #27272a;
1660
- background: #18181b;
1661
- border-radius: 8px;
1662
- padding: 32px;
1663
- box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);
1664
- }
1665
- .brand {
1666
- margin: 0 0 24px;
1667
- text-align: center;
1668
- font-size: 18px;
1669
- font-weight: 600;
1670
- color: #34d399;
1671
- }
1672
- h1 {
1673
- margin: 0 0 12px;
1674
- font-size: 16px;
1675
- font-weight: 600;
1676
- color: #f4f4f5;
1677
- }
1678
- p { margin: 0 0 16px; color: #a1a1aa; }
1679
- form { display: flex; flex-direction: column; gap: 16px; }
1680
- label { display: flex; flex-direction: column; gap: 6px; }
1681
- .field-label {
1682
- font-size: 11px;
1683
- text-transform: uppercase;
1684
- letter-spacing: 0.05em;
1685
- color: #71717a;
1686
- }
1687
- input {
1688
- border-radius: 4px;
1689
- border: 1px solid #3f3f46;
1690
- background: #09090b;
1691
- color: #f4f4f5;
1692
- padding: 8px 12px;
1693
- font: inherit;
1694
- outline: none;
1695
- }
1696
- input:focus { border-color: rgb(52 211 153 / 0.6); }
1697
- #error {
1698
- display: none;
1699
- margin: 0;
1700
- font-size: 12px;
1701
- color: #fb7185;
1702
- }
1703
- button, .button {
1704
- margin-top: 8px;
1705
- border-radius: 4px;
1706
- border: 1px solid rgb(52 211 153 / 0.4);
1707
- background: rgb(52 211 153 / 0.1);
1708
- color: #6ee7b7;
1709
- padding: 8px 12px;
1710
- font: inherit;
1711
- font-size: 12px;
1712
- font-weight: 500;
1713
- text-transform: uppercase;
1714
- letter-spacing: 0.05em;
1715
- cursor: pointer;
1716
- display: inline-block;
1717
- text-align: center;
1718
- text-decoration: none;
1719
- box-sizing: border-box;
1720
- }
1721
- button:hover:not(:disabled), .button:hover { background: rgb(52 211 153 / 0.2); }
1722
- button:disabled { opacity: 0.6; cursor: default; }
1723
- </style>
1724
- </head>
1725
- <body>
1726
- <div class="card">
1727
- ${content}
1728
- </div>
1729
- </body>
1730
- </html>`;
1608
+ throw error;
1731
1609
  }
1732
- __name(page, "page");
1733
- function renderLoginPage(basePath) {
1734
- const loginAction = `${basePath}/login`;
1735
- const defaultReturnTo = basePath;
1736
- return page(`<p class="brand">Durable</p>
1737
- <form id="login-form" autocomplete="on">
1738
- <label>
1739
- <span class="field-label">Username</span>
1740
- <input id="username" name="username" type="text" autocomplete="username" required autofocus />
1741
- </label>
1742
- <label>
1743
- <span class="field-label">Password</span>
1744
- <input id="password" name="password" type="password" autocomplete="current-password" />
1745
- </label>
1746
- <p id="error" role="alert">Invalid username or password.</p>
1747
- <button id="submit" type="submit">Sign in</button>
1748
- </form>
1749
- <script>
1750
- (function () {
1751
- var LOGIN_ACTION = ${JSON.stringify(loginAction)};
1752
- var DEFAULT_RETURN_TO = ${JSON.stringify(defaultReturnTo)};
1753
- var params = new URLSearchParams(window.location.search);
1754
- var errorBox = document.getElementById('error');
1755
- if (params.get('error')) errorBox.style.display = 'block';
1756
-
1757
- function sameOriginReturnTo(value) {
1758
- if (typeof value !== 'string' || value === '') return DEFAULT_RETURN_TO;
1759
- if (value.charAt(0) !== '/' || value.charAt(1) === '/' || value.indexOf('://') !== -1) {
1760
- return DEFAULT_RETURN_TO;
1761
- }
1762
- return value;
1610
+ __name(notSupportedByServer, "notSupportedByServer");
1611
+ var DurableApiController = class extends DurableApiControllerBase {
1612
+ static {
1613
+ __name(this, "DurableApiController");
1763
1614
  }
1615
+ };
1616
+ DurableApiController = _ts_decorate5([
1617
+ Controller()
1618
+ ], DurableApiController);
1764
1619
 
1765
- var form = document.getElementById('login-form');
1766
- var submitButton = document.getElementById('submit');
1767
- form.addEventListener('submit', function (event) {
1768
- event.preventDefault();
1769
- submitButton.disabled = true;
1770
- errorBox.style.display = 'none';
1771
- var returnTo = sameOriginReturnTo(params.get('returnTo'));
1772
- fetch(LOGIN_ACTION, {
1773
- method: 'POST',
1774
- headers: { 'content-type': 'application/json' },
1775
- credentials: 'same-origin',
1776
- body: JSON.stringify({
1777
- username: document.getElementById('username').value,
1778
- password: document.getElementById('password').value,
1779
- returnTo: returnTo,
1780
- }),
1781
- })
1782
- .then(function (response) {
1783
- if (!response.ok) throw new Error('unauthorized');
1784
- return response.json();
1785
- })
1786
- .then(function (data) {
1787
- window.location.href = data.redirectTo || DEFAULT_RETURN_TO;
1788
- })
1789
- .catch(function () {
1790
- errorBox.style.display = 'block';
1791
- submitButton.disabled = false;
1792
- });
1793
- });
1794
- })();
1795
- </script>`, "Sign in \u2014 Durable");
1796
- }
1797
- __name(renderLoginPage, "renderLoginPage");
1798
- function renderSessionRequiredPage(basePath) {
1799
- return page(`<h1>Open this console from your application</h1>
1800
- <p>Your session is minted by the host app. Use its console launcher to sign in, then reload.</p>
1801
- <a class="button" href="${basePath}">Retry</a>`);
1802
- }
1803
- __name(renderSessionRequiredPage, "renderSessionRequiredPage");
1804
-
1805
- // src/server/auth/request.ts
1806
- function isRecord2(value) {
1807
- return typeof value === "object" && value !== null;
1808
- }
1809
- __name(isRecord2, "isRecord");
1810
- function readHeaders(request) {
1811
- if (!isRecord2(request)) return {};
1812
- const headers = request.headers;
1813
- return isRecord2(headers) ? headers : {};
1814
- }
1815
- __name(readHeaders, "readHeaders");
1816
- function headerToString(value) {
1817
- if (typeof value === "string") return value;
1818
- if (Array.isArray(value) && value.every((part) => typeof part === "string")) {
1819
- return value.join(",");
1820
- }
1821
- return void 0;
1822
- }
1823
- __name(headerToString, "headerToString");
1824
- function readCookieHeader(request) {
1825
- return headerToString(readHeaders(request).cookie);
1826
- }
1827
- __name(readCookieHeader, "readCookieHeader");
1828
- function isHttpsRequest(request) {
1829
- if (isRecord2(request) && request.protocol === "https") return true;
1830
- const forwarded = headerToString(readHeaders(request)["x-forwarded-proto"]);
1831
- if (forwarded === void 0) return false;
1832
- return forwarded.split(",").map((proto) => proto.trim().toLowerCase()).includes("https");
1833
- }
1834
- __name(isHttpsRequest, "isHttpsRequest");
1835
- function originalRequestUrl(request) {
1836
- if (!isRecord2(request)) return "/";
1837
- if (typeof request.originalUrl === "string" && request.originalUrl !== "") {
1838
- return request.originalUrl;
1839
- }
1840
- if (typeof request.url === "string" && request.url !== "") return request.url;
1841
- return "/";
1842
- }
1843
- __name(originalRequestUrl, "originalRequestUrl");
1844
- function sanitizeReturnTo(candidate, fallback) {
1845
- if (typeof candidate !== "string") return fallback;
1846
- if (!candidate.startsWith("/")) return fallback;
1847
- if (candidate.startsWith("//")) return fallback;
1848
- if (candidate.includes("://")) return fallback;
1849
- return candidate;
1850
- }
1851
- __name(sanitizeReturnTo, "sanitizeReturnTo");
1620
+ // src/server/durable-dashboard.module.ts
1621
+ import "reflect-metadata";
1622
+ import { ApplyFilterInterceptor as ApplyFilterInterceptor2, FilterRunner as FilterRunner2 } from "@dudousxd/nestjs-filter";
1623
+ import { Logger, Module } from "@nestjs/common";
1624
+ import { ModuleRef, RouterModule } from "@nestjs/core";
1852
1625
 
1853
- // src/server/auth/cookie-header.ts
1854
- function parseCookieHeader(header) {
1855
- const cookies = {};
1856
- if (typeof header !== "string" || header === "") return cookies;
1857
- for (const segment of header.split(";")) {
1858
- const eq = segment.indexOf("=");
1859
- if (eq <= 0) continue;
1860
- const name = segment.slice(0, eq).trim();
1861
- if (name === "") continue;
1862
- let rawValue = segment.slice(eq + 1).trim();
1863
- if (rawValue.length >= 2 && rawValue.startsWith('"') && rawValue.endsWith('"')) {
1864
- rawValue = rawValue.slice(1, -1);
1865
- }
1866
- if (name in cookies) continue;
1867
- try {
1868
- cookies[name] = decodeURIComponent(rawValue);
1869
- } catch {
1870
- cookies[name] = rawValue;
1626
+ // src/server/controller-decorators.ts
1627
+ import "reflect-metadata";
1628
+ var CAN_ANYONE_METADATA = "authorization:can-anyone";
1629
+ function markOpenToAuthorization(controller) {
1630
+ Reflect.defineMetadata(CAN_ANYONE_METADATA, true, controller);
1631
+ }
1632
+ __name(markOpenToAuthorization, "markOpenToAuthorization");
1633
+ function controllerFor(base, decorators) {
1634
+ if (decorators === void 0 || decorators.length === 0) return base;
1635
+ const derived = class extends base {
1636
+ static {
1637
+ __name(this, "derived");
1871
1638
  }
1872
- }
1873
- return cookies;
1874
- }
1875
- __name(parseCookieHeader, "parseCookieHeader");
1876
- function serializeSetCookie(name, value, options) {
1877
- const parts = [
1878
- `${name}=${options.clear ? "" : encodeURIComponent(value)}`
1879
- ];
1880
- parts.push(`Path=${options.path}`);
1881
- parts.push("HttpOnly");
1882
- parts.push("SameSite=Lax");
1883
- if (options.secure) parts.push("Secure");
1884
- if (options.clear) {
1885
- parts.push("Max-Age=0");
1886
- parts.push("Expires=Thu, 01 Jan 1970 00:00:00 GMT");
1887
- } else {
1888
- parts.push(`Max-Age=${options.maxAgeSeconds}`);
1889
- }
1890
- return parts.join("; ");
1891
- }
1892
- __name(serializeSetCookie, "serializeSetCookie");
1893
-
1894
- // src/server/auth/session-cookie.ts
1895
- import { createHmac, timingSafeEqual } from "node:crypto";
1896
- var EXP_GRACE_MS = 3e4;
1897
- function base64urlEncode(value) {
1898
- return Buffer.from(value, "utf8").toString("base64url");
1899
- }
1900
- __name(base64urlEncode, "base64urlEncode");
1901
- function base64urlDecode(value) {
1902
- return Buffer.from(value, "base64url").toString("utf8");
1903
- }
1904
- __name(base64urlDecode, "base64urlDecode");
1905
- function hmac(secret, payload) {
1906
- return createHmac("sha256", secret).update(payload).digest("base64url");
1907
- }
1908
- __name(hmac, "hmac");
1909
- function signSessionCookie(user, options) {
1910
- const issuedAt = options.now ?? Date.now();
1911
- const session = {
1912
- sub: user.id,
1913
- ...user.name !== void 0 ? {
1914
- name: user.name
1915
- } : {},
1916
- roles: user.roles ?? [],
1917
- iat: issuedAt,
1918
- exp: issuedAt + options.ttlMs
1919
- };
1920
- const encodedPayload = base64urlEncode(JSON.stringify(session));
1921
- const signature = hmac(options.secret, encodedPayload);
1922
- return `${encodedPayload}.${signature}`;
1923
- }
1924
- __name(signSessionCookie, "signSessionCookie");
1925
- function isSessionPayload(value) {
1926
- if (typeof value !== "object" || value === null) return false;
1927
- const record = {
1928
- ...value
1929
1639
  };
1930
- if (typeof record.sub !== "string") return false;
1931
- if (record.name !== void 0 && typeof record.name !== "string") return false;
1932
- if (!Array.isArray(record.roles)) return false;
1933
- if (!record.roles.every((role) => typeof role === "string")) return false;
1934
- if (typeof record.iat !== "number" || !Number.isFinite(record.iat)) return false;
1935
- if (typeof record.exp !== "number" || !Number.isFinite(record.exp)) return false;
1936
- return true;
1937
- }
1938
- __name(isSessionPayload, "isSessionPayload");
1939
- function verifySessionCookie(value, options) {
1940
- try {
1941
- const dot = value.indexOf(".");
1942
- if (dot <= 0 || dot === value.length - 1) return null;
1943
- const encodedPayload = value.slice(0, dot);
1944
- const providedSignature = value.slice(dot + 1);
1945
- const expectedSignature = hmac(options.secret, encodedPayload);
1946
- const provided = Buffer.from(providedSignature, "base64url");
1947
- const expected = Buffer.from(expectedSignature, "base64url");
1948
- if (provided.length !== expected.length) return null;
1949
- if (!timingSafeEqual(provided, expected)) return null;
1950
- const decoded = JSON.parse(base64urlDecode(encodedPayload));
1951
- if (!isSessionPayload(decoded)) return null;
1952
- const now = options.now ?? Date.now();
1953
- if (now > decoded.exp + EXP_GRACE_MS) return null;
1954
- const session = {
1955
- sub: decoded.sub,
1956
- ...decoded.name !== void 0 ? {
1957
- name: decoded.name
1958
- } : {},
1959
- roles: decoded.roles,
1960
- iat: decoded.iat,
1961
- exp: decoded.exp
1962
- };
1963
- return session;
1964
- } catch {
1965
- return null;
1966
- }
1967
- }
1968
- __name(verifySessionCookie, "verifySessionCookie");
1969
-
1970
- // src/server/auth/session-cookie-io.ts
1971
- var SESSION_COOKIE_NAME = "durable_dashboard_session";
1972
- var COOKIE_PATH = "/";
1973
- function issueSessionCookie(user, context) {
1974
- const value = signSessionCookie(user, {
1975
- secret: context.auth.secret,
1976
- ttlMs: context.auth.ttlMs,
1977
- ...context.now !== void 0 ? {
1978
- now: context.now
1979
- } : {}
1980
- });
1981
- const cookie = serializeSetCookie(SESSION_COOKIE_NAME, value, {
1982
- path: COOKIE_PATH,
1983
- maxAgeSeconds: Math.floor(context.auth.ttlMs / 1e3),
1984
- secure: isHttpsRequest(context.request)
1985
- });
1986
- appendSetCookie(context.response, cookie);
1987
- }
1988
- __name(issueSessionCookie, "issueSessionCookie");
1989
- function clearSessionCookie(context) {
1990
- const cookie = serializeSetCookie(SESSION_COOKIE_NAME, "", {
1991
- path: COOKIE_PATH,
1992
- maxAgeSeconds: 0,
1993
- secure: isHttpsRequest(context.request),
1994
- clear: true
1995
- });
1996
- appendSetCookie(context.response, cookie);
1997
- }
1998
- __name(clearSessionCookie, "clearSessionCookie");
1999
- function readSessionFromRequest(auth, request) {
2000
- const cookieValue = parseCookieHeader(readCookieHeader(request))[SESSION_COOKIE_NAME];
2001
- if (cookieValue === void 0) return null;
2002
- return verifySessionCookie(cookieValue, {
2003
- secret: auth.secret
1640
+ Object.defineProperty(derived, "name", {
1641
+ value: base.name
2004
1642
  });
2005
- }
2006
- __name(readSessionFromRequest, "readSessionFromRequest");
2007
- var inFlightRevalidations = /* @__PURE__ */ new Map();
2008
- function revalidateOnce(auth, user) {
2009
- const revalidate = auth.revalidate;
2010
- if (!revalidate) return Promise.resolve(true);
2011
- const key = `${auth.secret}:${user.id}`;
2012
- const inFlight = inFlightRevalidations.get(key);
2013
- if (inFlight) return inFlight;
2014
- const call = Promise.resolve().then(() => revalidate(user)).catch(() => false).finally(() => {
2015
- inFlightRevalidations.delete(key);
2016
- });
2017
- inFlightRevalidations.set(key, call);
2018
- return call;
2019
- }
2020
- __name(revalidateOnce, "revalidateOnce");
2021
- async function maybeRenewSession(auth, session, request, response, now = Date.now()) {
2022
- if (now - session.iat <= auth.ttlMs / 2) return true;
2023
- const user = {
2024
- id: session.sub,
2025
- ...session.name !== void 0 ? {
2026
- name: session.name
2027
- } : {},
2028
- roles: session.roles
2029
- };
2030
- const allowed = await revalidateOnce(auth, user);
2031
- if (!allowed) {
2032
- clearSessionCookie({
2033
- request,
2034
- response
2035
- });
2036
- return false;
1643
+ markOpenToAuthorization(derived);
1644
+ let decorated = derived;
1645
+ for (const decorator of decorators) {
1646
+ decorated = decorator(decorated) ?? decorated;
2037
1647
  }
2038
- issueSessionCookie(user, {
2039
- auth,
2040
- request,
2041
- response,
2042
- now
2043
- });
2044
- return true;
1648
+ return decorated;
2045
1649
  }
2046
- __name(maybeRenewSession, "maybeRenewSession");
1650
+ __name(controllerFor, "controllerFor");
2047
1651
 
2048
1652
  // src/server/durable-ui.controller.ts
2049
1653
  import { existsSync, readFileSync } from "node:fs";
2050
1654
  import { basename, extname, join, resolve, sep } from "node:path";
2051
1655
  import { fileURLToPath } from "node:url";
2052
- import { Controller as Controller2, Get as Get2, Header as Header2, Inject as Inject3, NotFoundException as NotFoundException2, Param as Param2, StreamableFile, UseFilters as UseFilters2 } from "@nestjs/common";
2053
-
2054
- // src/server/auth/login-redirect.exception.ts
2055
- import { Catch as Catch2, HttpException as HttpException2, HttpStatus } from "@nestjs/common";
1656
+ import { Controller as Controller2, Get as Get2, Header as Header2, Inject as Inject2, NotFoundException as NotFoundException2, Param as Param2, StreamableFile } from "@nestjs/common";
2056
1657
  function _ts_decorate6(decorators, target, key, desc) {
2057
1658
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2058
1659
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -2067,46 +1668,6 @@ function _ts_decorate6(decorators, target, key, desc) {
2067
1668
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2068
1669
  }
2069
1670
  __name(_ts_decorate6, "_ts_decorate");
2070
- var DashboardLoginRedirectException = class extends HttpException2 {
2071
- static {
2072
- __name(this, "DashboardLoginRedirectException");
2073
- }
2074
- location;
2075
- constructor(location) {
2076
- super({
2077
- location
2078
- }, HttpStatus.FOUND), this.location = location;
2079
- }
2080
- };
2081
- var DashboardLoginRedirectFilter = class {
2082
- static {
2083
- __name(this, "DashboardLoginRedirectFilter");
2084
- }
2085
- catch(exception, host) {
2086
- const response = host.switchToHttp().getResponse();
2087
- redirectRaw(response, exception.location);
2088
- }
2089
- };
2090
- DashboardLoginRedirectFilter = _ts_decorate6([
2091
- Catch2(DashboardLoginRedirectException)
2092
- ], DashboardLoginRedirectFilter);
2093
-
2094
- // src/server/auth/session-required.exception.ts
2095
- import { Catch as Catch3, HttpException as HttpException3, HttpStatus as HttpStatus2, Inject as Inject2, Logger, Optional as Optional2 } from "@nestjs/common";
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
1671
  function _ts_metadata5(metadataKey, metadataValue) {
2111
1672
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2112
1673
  return Reflect.metadata(metadataKey, metadataValue);
@@ -2119,91 +1680,6 @@ function _ts_param3(paramIndex, decorator) {
2119
1680
  };
2120
1681
  }
2121
1682
  __name(_ts_param3, "_ts_param");
2122
- var DashboardSessionRequiredException = class extends HttpException3 {
2123
- static {
2124
- __name(this, "DashboardSessionRequiredException");
2125
- }
2126
- basePath;
2127
- constructor(basePath) {
2128
- super({
2129
- basePath
2130
- }, HttpStatus2.UNAUTHORIZED), this.basePath = basePath;
2131
- }
2132
- };
2133
- var DashboardSessionRequiredFilter = class _DashboardSessionRequiredFilter {
2134
- static {
2135
- __name(this, "DashboardSessionRequiredFilter");
2136
- }
2137
- auth;
2138
- logger = new Logger(_DashboardSessionRequiredFilter.name);
2139
- warned = false;
2140
- constructor(auth = null) {
2141
- this.auth = auth;
2142
- }
2143
- async catch(exception, host) {
2144
- const http = host.switchToHttp();
2145
- const response = http.getResponse();
2146
- const hook = this.auth?.unauthenticatedPage;
2147
- if (hook) {
2148
- try {
2149
- await hook({
2150
- request: http.getRequest(),
2151
- response,
2152
- basePath: exception.basePath
2153
- });
2154
- if (responseAlreadyWritten(response)) return;
2155
- this.warnOnce("dashboardAuth.unauthenticatedPage returned without writing a response; falling back to the built-in page. The hook must write AND end the response it is given.");
2156
- } catch (error) {
2157
- this.warnOnce(`dashboardAuth.unauthenticatedPage threw; falling back to the built-in page. ${String(error)}`);
2158
- if (responseAlreadyWritten(response)) return;
2159
- }
2160
- }
2161
- writeHtmlRaw(response, renderSessionRequiredPage(exception.basePath), HttpStatus2.UNAUTHORIZED);
2162
- }
2163
- /** Once per process, not once per request: a broken hook fires on every denied navigation. */
2164
- warnOnce(message) {
2165
- if (this.warned) return;
2166
- this.warned = true;
2167
- this.logger.warn(message);
2168
- }
2169
- };
2170
- DashboardSessionRequiredFilter = _ts_decorate7([
2171
- Catch3(DashboardSessionRequiredException),
2172
- _ts_param3(0, Optional2()),
2173
- _ts_param3(0, Inject2(DASHBOARD_AUTH)),
2174
- _ts_metadata5("design:type", Function),
2175
- _ts_metadata5("design:paramtypes", [
2176
- Object
2177
- ])
2178
- ], DashboardSessionRequiredFilter);
2179
-
2180
- // src/server/durable-ui.controller.ts
2181
- function _ts_decorate8(decorators, target, key, desc) {
2182
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2183
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2184
- r = Reflect.decorate(decorators, target, key, desc);
2185
- } else {
2186
- for (var i = decorators.length - 1; i >= 0; i--) {
2187
- if (d = decorators[i]) {
2188
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2189
- }
2190
- }
2191
- }
2192
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2193
- }
2194
- __name(_ts_decorate8, "_ts_decorate");
2195
- function _ts_metadata6(metadataKey, metadataValue) {
2196
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2197
- return Reflect.metadata(metadataKey, metadataValue);
2198
- }
2199
- }
2200
- __name(_ts_metadata6, "_ts_metadata");
2201
- function _ts_param4(paramIndex, decorator) {
2202
- return function(target, key) {
2203
- decorator(target, key, paramIndex);
2204
- };
2205
- }
2206
- __name(_ts_param4, "_ts_param");
2207
1683
  var DASHBOARD_BASE_PATH = Symbol("DASHBOARD_BASE_PATH");
2208
1684
  var DASHBOARD_API_PATH = Symbol("DASHBOARD_API_PATH");
2209
1685
  var BUILD_BASE = "/durable";
@@ -2220,9 +1696,9 @@ var CONTENT_TYPES = {
2220
1696
  ".woff2": "font/woff2",
2221
1697
  ".ico": "image/x-icon"
2222
1698
  };
2223
- var DurableUiController = class {
1699
+ var DurableUiControllerBase = class {
2224
1700
  static {
2225
- __name(this, "DurableUiController");
1701
+ __name(this, "DurableUiControllerBase");
2226
1702
  }
2227
1703
  basePath;
2228
1704
  apiBasePath;
@@ -2256,223 +1732,45 @@ var DurableUiController = class {
2256
1732
  });
2257
1733
  }
2258
1734
  };
2259
- _ts_decorate8([
1735
+ _ts_decorate6([
2260
1736
  Get2(),
2261
1737
  Header2("Content-Type", "text/html; charset=utf-8"),
2262
1738
  Header2("Cache-Control", "no-store, must-revalidate"),
2263
- _ts_metadata6("design:type", Function),
2264
- _ts_metadata6("design:paramtypes", []),
2265
- _ts_metadata6("design:returntype", String)
2266
- ], DurableUiController.prototype, "index", null);
2267
- _ts_decorate8([
1739
+ _ts_metadata5("design:type", Function),
1740
+ _ts_metadata5("design:paramtypes", []),
1741
+ _ts_metadata5("design:returntype", String)
1742
+ ], DurableUiControllerBase.prototype, "index", null);
1743
+ _ts_decorate6([
2268
1744
  Get2("assets/:file"),
2269
1745
  Header2("Cache-Control", "public, max-age=31536000, immutable"),
2270
- _ts_param4(0, Param2("file")),
2271
- _ts_metadata6("design:type", Function),
2272
- _ts_metadata6("design:paramtypes", [
1746
+ _ts_param3(0, Param2("file")),
1747
+ _ts_metadata5("design:type", Function),
1748
+ _ts_metadata5("design:paramtypes", [
2273
1749
  String
2274
1750
  ]),
2275
- _ts_metadata6("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
2276
- ], DurableUiController.prototype, "asset", null);
2277
- DurableUiController = _ts_decorate8([
2278
- UseFilters2(DashboardLoginRedirectFilter, DashboardSessionRequiredFilter),
1751
+ _ts_metadata5("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
1752
+ ], DurableUiControllerBase.prototype, "asset", null);
1753
+ DurableUiControllerBase = _ts_decorate6([
2279
1754
  Controller2(),
2280
- _ts_param4(0, Inject3(DASHBOARD_BASE_PATH)),
2281
- _ts_param4(1, Inject3(DASHBOARD_API_PATH)),
2282
- _ts_metadata6("design:type", Function),
2283
- _ts_metadata6("design:paramtypes", [
1755
+ _ts_param3(0, Inject2(DASHBOARD_BASE_PATH)),
1756
+ _ts_param3(1, Inject2(DASHBOARD_API_PATH)),
1757
+ _ts_metadata5("design:type", Function),
1758
+ _ts_metadata5("design:paramtypes", [
2284
1759
  String,
2285
1760
  String
2286
1761
  ])
2287
- ], DurableUiController);
2288
-
2289
- // src/server/durable-auth.controller.ts
2290
- function _ts_decorate9(decorators, target, key, desc) {
2291
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2292
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2293
- r = Reflect.decorate(decorators, target, key, desc);
2294
- } else {
2295
- for (var i = decorators.length - 1; i >= 0; i--) {
2296
- if (d = decorators[i]) {
2297
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2298
- }
2299
- }
2300
- }
2301
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2302
- }
2303
- __name(_ts_decorate9, "_ts_decorate");
2304
- function _ts_metadata7(metadataKey, metadataValue) {
2305
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2306
- return Reflect.metadata(metadataKey, metadataValue);
2307
- }
2308
- }
2309
- __name(_ts_metadata7, "_ts_metadata");
2310
- function _ts_param5(paramIndex, decorator) {
2311
- return function(target, key) {
2312
- decorator(target, key, paramIndex);
2313
- };
2314
- }
2315
- __name(_ts_param5, "_ts_param");
2316
- function isString(value) {
2317
- return typeof value === "string";
2318
- }
2319
- __name(isString, "isString");
2320
- var DurableAuthController = class _DurableAuthController {
1762
+ ], DurableUiControllerBase);
1763
+ var DurableUiController = class extends DurableUiControllerBase {
2321
1764
  static {
2322
- __name(this, "DurableAuthController");
2323
- }
2324
- auth;
2325
- basePath;
2326
- logger = new Logger2(_DurableAuthController.name);
2327
- /** Warn once per hook kind so a throwing hook doesn't spam logs on every failed attempt. */
2328
- warnedHooks = /* @__PURE__ */ new Set();
2329
- constructor(auth, basePath) {
2330
- this.auth = auth;
2331
- this.basePath = basePath;
2332
- }
2333
- // The login page reads `returnTo`/`error` from `location.search` client-side (see
2334
- // `login-page.ts`), so this response never varies per request and needs no templating beyond
2335
- // the developer-controlled `basePath`.
2336
- loginPage() {
2337
- const auth = this.requireAuth();
2338
- if (!auth.modes.includes("login")) throw new NotFoundException3();
2339
- return renderLoginPage(this.basePath);
2340
- }
2341
- // Called by the login page's own fetch (JSON, not a classic form POST — see `login-page.ts` for
2342
- // why: it keeps the login page working with no host-configured body parser beyond the
2343
- // JSON one Nest wires in by default on both Express and Fastify). Uniform `401` on any failure
2344
- // (unknown user, wrong password, throwing hook) — no user-enumeration.
2345
- async login(body, request, response) {
2346
- const auth = this.requireAuth();
2347
- if (!auth.modes.includes("login")) throw new NotFoundException3();
2348
- if (!isString(body?.username) || !isString(body?.password)) {
2349
- throw new BadRequestException2("Body must include string `username` and `password`.");
2350
- }
2351
- const { username, password } = body;
2352
- const user = await this.runHook("login", () => auth.login?.(username, password) ?? null);
2353
- if (!user) throw new UnauthorizedException({
2354
- message: "Invalid username or password."
2355
- });
2356
- issueSessionCookie(user, {
2357
- auth,
2358
- request,
2359
- response
2360
- });
2361
- return {
2362
- redirectTo: sanitizeReturnTo(body.returnTo, this.basePath)
2363
- };
2364
- }
2365
- // Mode A: the host frontend (carrying its own auth) POSTs here; the host hook validates the raw
2366
- // request and returns the session user, or `null` to deny. This is the SSO path — the host's own
2367
- // guard decides, and no credential ever reaches this library.
2368
- async session(request, response) {
2369
- const auth = this.requireAuth();
2370
- if (!auth.modes.includes("session")) throw new NotFoundException3();
2371
- const user = await this.runHook("session", () => auth.session?.(request) ?? null);
2372
- if (!user) throw new UnauthorizedException();
2373
- issueSessionCookie(user, {
2374
- auth,
2375
- request,
2376
- response
2377
- });
2378
- }
2379
- // Plain `GET` (not `POST`): logging out only ever destroys the CALLER's own session, so it's
2380
- // safe/idempotent from the caller's perspective — a simple `<a href>` (no JS, no CSRF token)
2381
- // works. Non-passthrough `@Res()`: this handler owns the response outright (clears the cookie,
2382
- // then redirects), so Nest must not also try to send a return value afterwards.
2383
- logout(request, response) {
2384
- clearSessionCookie({
2385
- request,
2386
- response
2387
- });
2388
- redirectRaw(response, this.auth?.modes.includes("login") ? `${this.basePath}/login` : this.basePath);
2389
- }
2390
- requireAuth() {
2391
- if (!this.auth) throw new NotFoundException3();
2392
- return this.auth;
2393
- }
2394
- /**
2395
- * Run a host hook (`login` or `session`) defensively: a throw is treated as a denial (uniform
2396
- * failure) and warn-logged once PER KIND, so a buggy hook never 500s the endpoint into a
2397
- * stack-trace leak nor floods the logs on repeated bad attempts.
2398
- */
2399
- async runHook(kind, run) {
2400
- try {
2401
- return await run() ?? null;
2402
- } catch (error) {
2403
- if (!this.warnedHooks.has(kind)) {
2404
- this.warnedHooks.add(kind);
2405
- this.logger.warn(`dashboardAuth ${kind} hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
2406
- }
2407
- return null;
2408
- }
1765
+ __name(this, "DurableUiController");
2409
1766
  }
2410
1767
  };
2411
- _ts_decorate9([
2412
- Get3("login"),
2413
- Header3("Content-Type", "text/html; charset=utf-8"),
2414
- Header3("Cache-Control", "no-store, must-revalidate"),
2415
- _ts_metadata7("design:type", Function),
2416
- _ts_metadata7("design:paramtypes", []),
2417
- _ts_metadata7("design:returntype", String)
2418
- ], DurableAuthController.prototype, "loginPage", null);
2419
- _ts_decorate9([
2420
- Post2("login"),
2421
- HttpCode(200),
2422
- _ts_param5(0, Body2()),
2423
- _ts_param5(1, Req()),
2424
- _ts_param5(2, Res2({
2425
- passthrough: true
2426
- })),
2427
- _ts_metadata7("design:type", Function),
2428
- _ts_metadata7("design:paramtypes", [
2429
- typeof LoginBody === "undefined" ? Object : LoginBody,
2430
- Object,
2431
- Object
2432
- ]),
2433
- _ts_metadata7("design:returntype", Promise)
2434
- ], DurableAuthController.prototype, "login", null);
2435
- _ts_decorate9([
2436
- Post2("session"),
2437
- HttpCode(204),
2438
- _ts_param5(0, Req()),
2439
- _ts_param5(1, Res2({
2440
- passthrough: true
2441
- })),
2442
- _ts_metadata7("design:type", Function),
2443
- _ts_metadata7("design:paramtypes", [
2444
- Object,
2445
- Object
2446
- ]),
2447
- _ts_metadata7("design:returntype", Promise)
2448
- ], DurableAuthController.prototype, "session", null);
2449
- _ts_decorate9([
2450
- Get3("logout"),
2451
- _ts_param5(0, Req()),
2452
- _ts_param5(1, Res2()),
2453
- _ts_metadata7("design:type", Function),
2454
- _ts_metadata7("design:paramtypes", [
2455
- Object,
2456
- Object
2457
- ]),
2458
- _ts_metadata7("design:returntype", void 0)
2459
- ], DurableAuthController.prototype, "logout", null);
2460
- DurableAuthController = _ts_decorate9([
2461
- Controller3(),
2462
- _ts_param5(0, Optional3()),
2463
- _ts_param5(0, Inject4(DASHBOARD_AUTH)),
2464
- _ts_param5(1, Inject4(DASHBOARD_BASE_PATH)),
2465
- _ts_metadata7("design:type", Function),
2466
- _ts_metadata7("design:paramtypes", [
2467
- Object,
2468
- String
2469
- ])
2470
- ], DurableAuthController);
1768
+ DurableUiController = _ts_decorate6([
1769
+ Controller2()
1770
+ ], DurableUiController);
2471
1771
 
2472
- // src/server/durable-dashboard-gate.guard.ts
2473
- import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
2474
- import { ForbiddenException, Inject as Inject5, Injectable as Injectable4, Logger as Logger3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
2475
- function _ts_decorate10(decorators, target, key, desc) {
1772
+ // src/server/durable-dashboard.module.ts
1773
+ function _ts_decorate7(decorators, target, key, desc) {
2476
1774
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2477
1775
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2478
1776
  r = Reflect.decorate(decorators, target, key, desc);
@@ -2485,342 +1783,59 @@ function _ts_decorate10(decorators, target, key, desc) {
2485
1783
  }
2486
1784
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2487
1785
  }
2488
- __name(_ts_decorate10, "_ts_decorate");
2489
- function _ts_metadata8(metadataKey, metadataValue) {
2490
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2491
- return Reflect.metadata(metadataKey, metadataValue);
2492
- }
2493
- }
2494
- __name(_ts_metadata8, "_ts_metadata");
2495
- function _ts_param6(paramIndex, decorator) {
2496
- return function(target, key) {
2497
- decorator(target, key, paramIndex);
2498
- };
2499
- }
2500
- __name(_ts_param6, "_ts_param");
2501
- var DURABLE_DASHBOARD_TOKEN_ENV = "DURABLE_DASHBOARD_TOKEN";
2502
- var DASHBOARD_GATE = Symbol("DASHBOARD_GATE");
2503
- var LOG_CONTEXT = "DurableDashboard";
2504
- var HOW_TO_CONFIGURE = [
2505
- "Configure one of: `guards: [YourAuthGuard]`, `dashboardAuth: { secret, session | login }`",
2506
- "(or `useDashboardAuth` in forRootAsync), a bearer token via the `token` option or the",
2507
- `${DURABLE_DASHBOARD_TOKEN_ENV} env var, or \`allowUnauthenticated: true\` when the console is`,
2508
- "already protected outside this module (a global guard, a reverse proxy, a private network)."
1786
+ __name(_ts_decorate7, "_ts_decorate");
1787
+ markOpenToAuthorization(DurableApiController);
1788
+ markOpenToAuthorization(DurableUiController);
1789
+ var DASHBOARD_PROTECTION_CHECK = Symbol("DASHBOARD_PROTECTION_CHECK");
1790
+ var UNPROTECTED_CONSOLE_IN_PRODUCTION = [
1791
+ "DurableDashboardModule: NODE_ENV=production and the durable console is mounted with no",
1792
+ "protection, so the app refuses to boot. Protect it one of two ways:",
1793
+ "(1) decorators on the mounted console: DurableDashboardModule.forRoot({ decorators: [UseGuards(AuthenticationGuard), Can(ConsolePolicy, 'view')] });",
1794
+ "(2) mount it yourself: DurableDashboardModule.forRoot({ routes: false }) plus @Controller('durable') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view') class ConsoleUi extends DurableUiControllerBase {} (and the same over DurableApiControllerBase at 'durable/api')."
2509
1795
  ].join(" ");
2510
1796
  function isProduction() {
2511
1797
  return (process.env.NODE_ENV ?? "").trim().toLowerCase() === "production";
2512
1798
  }
2513
1799
  __name(isProduction, "isProduction");
2514
- function resolveDashboardGate(options, auth) {
2515
- if (options.hasGuards || auth !== null) return {
2516
- mode: "delegated"
2517
- };
2518
- const token = options.token ?? process.env[DURABLE_DASHBOARD_TOKEN_ENV];
2519
- if (typeof token === "string" && token !== "") return {
2520
- mode: "token",
2521
- token
2522
- };
2523
- if (options.allowUnauthenticated === true) return {
2524
- mode: "open"
2525
- };
1800
+ function protectionCheckProvider() {
2526
1801
  return {
2527
- mode: isProduction() ? "deny" : "unconfigured"
2528
- };
2529
- }
2530
- __name(resolveDashboardGate, "resolveDashboardGate");
2531
- function reportDashboardGate(gate, options, logger = new Logger3(LOG_CONTEXT)) {
2532
- switch (gate.mode) {
2533
- case "open":
2534
- logger.warn("allowUnauthenticated: true. The durable console (including retry/cancel and other mutating endpoints) is served with NO authentication from this module. Only do this when a global guard or reverse proxy already protects it.");
2535
- return;
2536
- case "token":
2537
- if (options.allowUnauthenticated === true) {
2538
- logger.warn("allowUnauthenticated: true is ignored because a dashboard token is configured; requests must carry the token.");
2539
- }
2540
- return;
2541
- case "unconfigured":
2542
- logger.warn([
2543
- "The durable console is UNAUTHENTICATED: no guards, dashboardAuth, token or",
2544
- "allowUnauthenticated were configured. It is served open because NODE_ENV is not",
2545
- '"production", but it WILL deny every request when NODE_ENV=production.',
2546
- HOW_TO_CONFIGURE
2547
- ].join(" "));
2548
- return;
2549
- case "deny":
2550
- logger.error([
2551
- "The durable console has no authentication configured and NODE_ENV=production, so it",
2552
- "is failing closed: every console request (UI and API) is denied.",
2553
- HOW_TO_CONFIGURE
2554
- ].join(" "));
2555
- return;
2556
- case "delegated":
2557
- return;
2558
- }
2559
- }
2560
- __name(reportDashboardGate, "reportDashboardGate");
2561
- function dashboardGateProvider(options) {
2562
- return {
2563
- provide: DASHBOARD_GATE,
2564
- inject: [
2565
- DASHBOARD_AUTH
2566
- ],
2567
- useFactory: /* @__PURE__ */ __name((auth) => {
2568
- const gate = resolveDashboardGate(options, auth);
2569
- reportDashboardGate(gate, options);
2570
- return gate;
1802
+ provide: DASHBOARD_PROTECTION_CHECK,
1803
+ useFactory: /* @__PURE__ */ __name(() => {
1804
+ if (isProduction()) throw new Error(UNPROTECTED_CONSOLE_IN_PRODUCTION);
1805
+ new Logger("DurableDashboard").warn("The durable console is mounted with no `decorators`, so it is served OPEN (retry/cancel included). That is refused when NODE_ENV=production: pass `decorators` (e.g. [UseGuards(AuthenticationGuard), Can(ConsolePolicy, 'view')]) or mount it yourself with `routes: false`.");
1806
+ return true;
2571
1807
  }, "useFactory")
2572
1808
  };
2573
1809
  }
2574
- __name(dashboardGateProvider, "dashboardGateProvider");
2575
- function isRecord3(value) {
2576
- return typeof value === "object" && value !== null;
2577
- }
2578
- __name(isRecord3, "isRecord");
2579
- function headerValue(request, name) {
2580
- const headers = request.headers;
2581
- if (!isRecord3(headers)) return void 0;
2582
- const value = headers[name];
2583
- if (typeof value === "string") return value;
2584
- if (Array.isArray(value) && typeof value[0] === "string") return value[0];
2585
- return void 0;
2586
- }
2587
- __name(headerValue, "headerValue");
2588
- function readDashboardToken(request) {
2589
- if (!isRecord3(request)) return void 0;
2590
- const authorization = headerValue(request, "authorization");
2591
- if (authorization) {
2592
- const match = /^Bearer\s+(.+)$/i.exec(authorization);
2593
- if (match?.[1]) return match[1].trim();
2594
- }
2595
- const header = headerValue(request, "x-durable-token");
2596
- if (header) return header.trim();
2597
- const method = typeof request.method === "string" ? request.method.toUpperCase() : "";
2598
- if (method === "GET" || method === "HEAD") {
2599
- const query = request.query;
2600
- if (isRecord3(query)) {
2601
- const value = query.token;
2602
- if (typeof value === "string" && value !== "") return value;
2603
- }
2604
- }
2605
- return void 0;
2606
- }
2607
- __name(readDashboardToken, "readDashboardToken");
2608
- function secretsMatch(provided, expected) {
2609
- const a = Buffer.from(provided);
2610
- const b = Buffer.from(expected);
2611
- if (a.length !== b.length) return false;
2612
- return timingSafeEqual2(a, b);
2613
- }
2614
- __name(secretsMatch, "secretsMatch");
2615
- var DurableDashboardGateGuard = class {
2616
- static {
2617
- __name(this, "DurableDashboardGateGuard");
2618
- }
2619
- gate;
2620
- constructor(gate) {
2621
- this.gate = gate;
2622
- }
2623
- canActivate(context) {
2624
- switch (this.gate.mode) {
2625
- case "delegated":
2626
- case "open":
2627
- case "unconfigured":
2628
- return true;
2629
- case "deny":
2630
- throw new ForbiddenException([
2631
- "Durable dashboard authentication is not configured, and NODE_ENV=production denies",
2632
- "an unauthenticated console.",
2633
- HOW_TO_CONFIGURE
2634
- ].join(" "));
2635
- case "token": {
2636
- const provided = readDashboardToken(context.switchToHttp().getRequest());
2637
- if (provided !== void 0 && secretsMatch(provided, this.gate.token ?? "")) return true;
2638
- throw new UnauthorizedException2("A valid durable dashboard token is required (Authorization: Bearer <token>, x-durable-token, or ?token= on GET).");
2639
- }
2640
- }
2641
- }
2642
- };
2643
- DurableDashboardGateGuard = _ts_decorate10([
2644
- Injectable4(),
2645
- _ts_param6(0, Inject5(DASHBOARD_GATE)),
2646
- _ts_metadata8("design:type", Function),
2647
- _ts_metadata8("design:paramtypes", [
2648
- typeof DashboardGate === "undefined" ? Object : DashboardGate
2649
- ])
2650
- ], DurableDashboardGateGuard);
2651
-
2652
- // src/server/durable-dashboard.module.ts
2653
- import "reflect-metadata";
2654
- import { ApplyFilterInterceptor as ApplyFilterInterceptor2, FilterRunner as FilterRunner2 } from "@dudousxd/nestjs-filter";
2655
- import { Module } from "@nestjs/common";
2656
- import { ModuleRef, RouterModule } from "@nestjs/core";
2657
-
2658
- // src/server/durable-session.guard.ts
2659
- import { Inject as Inject6, Injectable as Injectable5, UnauthorizedException as UnauthorizedException3 } from "@nestjs/common";
2660
- function _ts_decorate11(decorators, target, key, desc) {
2661
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2662
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2663
- r = Reflect.decorate(decorators, target, key, desc);
2664
- } else {
2665
- for (var i = decorators.length - 1; i >= 0; i--) {
2666
- if (d = decorators[i]) {
2667
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2668
- }
2669
- }
2670
- }
2671
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2672
- }
2673
- __name(_ts_decorate11, "_ts_decorate");
2674
- function _ts_metadata9(metadataKey, metadataValue) {
2675
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2676
- return Reflect.metadata(metadataKey, metadataValue);
2677
- }
2678
- }
2679
- __name(_ts_metadata9, "_ts_metadata");
2680
- function _ts_param7(paramIndex, decorator) {
2681
- return function(target, key) {
2682
- decorator(target, key, paramIndex);
2683
- };
2684
- }
2685
- __name(_ts_param7, "_ts_param");
2686
- var DurableUiSessionGuard = class {
2687
- static {
2688
- __name(this, "DurableUiSessionGuard");
2689
- }
2690
- auth;
2691
- basePath;
2692
- constructor(auth, basePath) {
2693
- this.auth = auth;
2694
- this.basePath = basePath;
2695
- }
2696
- async canActivate(context) {
2697
- if (!this.auth) return true;
2698
- const auth = this.auth;
2699
- const http = context.switchToHttp();
2700
- const request = http.getRequest();
2701
- const session = readSessionFromRequest(auth, request);
2702
- if (!session) {
2703
- this.denyUnauthenticated(auth, request);
2704
- }
2705
- if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
2706
- this.denyUnauthenticated(auth, request);
2707
- }
2708
- return true;
2709
- }
2710
- /**
2711
- * A revoked session (the host's `revalidate` hook says no) is denied exactly like an absent
2712
- * one — by design, there is nothing left to distinguish "never had a session" from "had one,
2713
- * then lost it" once the cookie is cleared, so both get the same Mode-aware treatment.
2714
- *
2715
- * Takes `auth` explicitly rather than reading `this.auth`: `canActivate`'s `!this.auth` early
2716
- * return narrows the field within that method only — TS doesn't carry it across a method call
2717
- * — so callers pass the already-narrowed value instead of this method re-deriving (or
2718
- * asserting away) the nullability.
2719
- */
2720
- denyUnauthenticated(auth, request) {
2721
- if (!auth.modes.includes("login")) throw new DashboardSessionRequiredException(this.basePath);
2722
- const returnTo = encodeURIComponent(originalRequestUrl(request));
2723
- throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
2724
- }
2725
- };
2726
- DurableUiSessionGuard = _ts_decorate11([
2727
- Injectable5(),
2728
- _ts_param7(0, Inject6(DASHBOARD_AUTH)),
2729
- _ts_param7(1, Inject6(DASHBOARD_BASE_PATH)),
2730
- _ts_metadata9("design:type", Function),
2731
- _ts_metadata9("design:paramtypes", [
2732
- Object,
2733
- String
2734
- ])
2735
- ], DurableUiSessionGuard);
2736
- var DurableApiSessionGuard = class {
2737
- static {
2738
- __name(this, "DurableApiSessionGuard");
2739
- }
2740
- auth;
2741
- constructor(auth) {
2742
- this.auth = auth;
2743
- }
2744
- async canActivate(context) {
2745
- if (!this.auth) return true;
2746
- const auth = this.auth;
2747
- const http = context.switchToHttp();
2748
- const request = http.getRequest();
2749
- const session = readSessionFromRequest(auth, request);
2750
- if (!session) throw this.unauthorized(auth);
2751
- if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
2752
- throw this.unauthorized(auth);
2753
- }
2754
- return true;
2755
- }
2756
- /**
2757
- * A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
2758
- * dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
2759
- * SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
2760
- * 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
2761
- */
2762
- unauthorized(auth) {
2763
- return new UnauthorizedException3({
2764
- auth: {
2765
- modes: auth.modes
2766
- }
2767
- });
2768
- }
2769
- };
2770
- DurableApiSessionGuard = _ts_decorate11([
2771
- Injectable5(),
2772
- _ts_param7(0, Inject6(DASHBOARD_AUTH)),
2773
- _ts_metadata9("design:type", Function),
2774
- _ts_metadata9("design:paramtypes", [
2775
- Object
2776
- ])
2777
- ], DurableApiSessionGuard);
2778
-
2779
- // src/server/durable-dashboard.module.ts
2780
- function _ts_decorate12(decorators, target, key, desc) {
2781
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2782
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2783
- r = Reflect.decorate(decorators, target, key, desc);
2784
- } else {
2785
- for (var i = decorators.length - 1; i >= 0; i--) {
2786
- if (d = decorators[i]) {
2787
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2788
- }
2789
- }
2790
- }
2791
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2792
- }
2793
- __name(_ts_decorate12, "_ts_decorate");
2794
- var GUARDS_METADATA = "__guards__";
1810
+ __name(protectionCheckProvider, "protectionCheckProvider");
1811
+ function assertNothingToDecorate(options) {
1812
+ if (options.decorators === void 0) return;
1813
+ throw new Error([
1814
+ "DurableDashboardModule.forRoot({ routes: false }) mounts no controllers, so `decorators`",
1815
+ "would protect nothing. Put your protection on the controllers you mount (extend",
1816
+ "DurableUiControllerBase / DurableApiControllerBase and decorate them with @UseGuards /",
1817
+ "@Can), and drop `decorators`."
1818
+ ].join(" "));
1819
+ }
1820
+ __name(assertNothingToDecorate, "assertNothingToDecorate");
2795
1821
  function normalize(path) {
2796
1822
  return `/${path.replace(/^\/+|\/+$/g, "")}`;
2797
1823
  }
2798
1824
  __name(normalize, "normalize");
2799
- function stampGuards(hostGuards, builtIn, controller) {
2800
- const merged = [
2801
- ...builtIn.filter((guard) => guard !== void 0),
2802
- ...hostGuards ?? []
2803
- ];
2804
- Reflect.defineMetadata(GUARDS_METADATA, merged, controller);
2805
- }
2806
- __name(stampGuards, "stampGuards");
2807
1825
  var DurableApiModule = class _DurableApiModule {
2808
1826
  static {
2809
1827
  __name(this, "DurableApiModule");
2810
1828
  }
2811
1829
  static register(options) {
2812
- stampGuards(options.guards, [
2813
- options.authMayBeConfigured ? DurableApiSessionGuard : void 0,
2814
- options.stampGate ? DurableDashboardGateGuard : void 0
2815
- ], DurableApiController);
1830
+ const routes = options.routes !== false;
2816
1831
  return {
2817
1832
  module: _DurableApiModule,
2818
1833
  imports: [
2819
1834
  ...options.imports ?? []
2820
1835
  ],
2821
- controllers: [
2822
- DurableApiController
2823
- ],
1836
+ controllers: routes ? [
1837
+ controllerFor(DurableApiController, options.decorators)
1838
+ ] : [],
2824
1839
  providers: [
2825
1840
  DashboardService,
2826
1841
  // `@dudousxd/nestjs-filter` is wired PRIVATELY here rather than through
@@ -2853,23 +1868,22 @@ var DurableApiModule = class _DurableApiModule {
2853
1868
  provide: RUN_QUERY_ADAPTER,
2854
1869
  useExisting: RunQueryAdapter
2855
1870
  },
2856
- RunFilter,
2857
- options.authProvider,
2858
- DurableApiSessionGuard,
2859
- // Resolved ONCE here (this module is imported by the UI module, which reuses it via the
2860
- // export below), so the boot report is logged once per mount.
2861
- dashboardGateProvider(options.gate),
2862
- DurableDashboardGateGuard,
2863
- ...options.guards ?? []
1871
+ RunFilter
2864
1872
  ],
2865
- exports: [
1873
+ // Self-mounted (`routes: false`): the host's own subclass of `DurableApiControllerBase` is
1874
+ // hosted in the HOST's module, so what its constructor injects must be exported. `FilterRunner`
1875
+ // is only exported then: exporting it from a mounted console would shadow a host's own
1876
+ // `FilterModule` runner in whatever module imports the dashboard.
1877
+ exports: routes ? [
1878
+ DashboardService
1879
+ ] : [
2866
1880
  DashboardService,
2867
- DASHBOARD_GATE
1881
+ FilterRunner2
2868
1882
  ]
2869
1883
  };
2870
1884
  }
2871
1885
  };
2872
- DurableApiModule = _ts_decorate12([
1886
+ DurableApiModule = _ts_decorate7([
2873
1887
  Module({})
2874
1888
  ], DurableApiModule);
2875
1889
  var DurableDashboardModule = class _DurableDashboardModule {
@@ -2877,58 +1891,58 @@ var DurableDashboardModule = class _DurableDashboardModule {
2877
1891
  __name(this, "DurableDashboardModule");
2878
1892
  }
2879
1893
  static forRoot(options = {}) {
2880
- const authProvider = {
2881
- provide: DASHBOARD_AUTH,
2882
- useValue: resolveDashboardAuth(options.dashboardAuth)
2883
- };
2884
- const authConfigured = options.dashboardAuth !== void 0;
2885
- return _DurableDashboardModule.build(options, authProvider, authConfigured, authConfigured);
2886
- }
2887
- static forRootAsync(options) {
2888
- const authProvider = {
2889
- provide: DASHBOARD_AUTH,
2890
- inject: options.inject ?? [],
2891
- useFactory: /* @__PURE__ */ __name(async (...deps) => resolveDashboardAuth(await options.useDashboardAuth(...deps)), "useFactory")
2892
- };
2893
- return _DurableDashboardModule.build(options, authProvider, true, false);
2894
- }
2895
- /** Shared wiring: static routing + the API module, with `dashboardAuth` supplied by `authProvider`. */
2896
- static build(options, authProvider, authMayBeConfigured, authStaticallyConfigured) {
2897
1894
  const basePath = normalize(options.basePath ?? "/durable");
2898
1895
  const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);
2899
- const hasGuards = (options.guards?.length ?? 0) > 0;
2900
- const gate = {
2901
- hasGuards,
2902
- ...options.allowUnauthenticated !== void 0 ? {
2903
- allowUnauthenticated: options.allowUnauthenticated
2904
- } : {},
2905
- ...options.token !== void 0 ? {
2906
- token: options.token
2907
- } : {}
2908
- };
2909
- const stampGate = !hasGuards && !authStaticallyConfigured;
2910
- stampGuards(options.guards, [
2911
- authMayBeConfigured ? DurableUiSessionGuard : void 0,
2912
- stampGate ? DurableDashboardGateGuard : void 0
2913
- ], DurableUiController);
1896
+ const pathProviders = [
1897
+ {
1898
+ provide: DASHBOARD_BASE_PATH,
1899
+ useValue: basePath
1900
+ },
1901
+ {
1902
+ provide: DASHBOARD_API_PATH,
1903
+ useValue: apiBasePath
1904
+ }
1905
+ ];
1906
+ if (options.routes === false) {
1907
+ assertNothingToDecorate(options);
1908
+ const api = DurableApiModule.register({
1909
+ ...options.imports ? {
1910
+ imports: options.imports
1911
+ } : {},
1912
+ routes: false
1913
+ });
1914
+ return {
1915
+ module: _DurableDashboardModule,
1916
+ imports: [
1917
+ ...options.imports ?? [],
1918
+ api
1919
+ ],
1920
+ controllers: [],
1921
+ providers: pathProviders,
1922
+ // What a host's subclasses of `DurableUiControllerBase` / `DurableApiControllerBase` inject:
1923
+ // the SPA's paths, and the read/action service and the filter runner via the API module.
1924
+ exports: [
1925
+ DurableApiModule,
1926
+ DASHBOARD_BASE_PATH,
1927
+ DASHBOARD_API_PATH
1928
+ ]
1929
+ };
1930
+ }
1931
+ const decorated = (options.decorators?.length ?? 0) > 0;
2914
1932
  return {
2915
1933
  module: _DurableDashboardModule,
2916
1934
  imports: [
2917
1935
  ...options.imports ?? [],
2918
- // Guards + host imports must reach the API controller's HOST module — enhancers resolve
2919
- // from their controller's own module, never from a parent (see DurableApiModule.register).
1936
+ // Host imports must reach the API controller's HOST module: enhancers resolve from their
1937
+ // controller's own module, never from a parent (see DurableApiModule.register).
2920
1938
  // Spread-only-when-set: exactOptionalPropertyTypes rejects an explicit `undefined`.
2921
1939
  DurableApiModule.register({
2922
1940
  ...options.imports ? {
2923
1941
  imports: options.imports
2924
1942
  } : {},
2925
- ...options.guards ? {
2926
- guards: options.guards
2927
- } : {},
2928
- authProvider,
2929
- authMayBeConfigured,
2930
- gate,
2931
- stampGate
1943
+ ...options.decorators ? {
1944
+ decorators: options.decorators
1945
+ } : {}
2932
1946
  }),
2933
1947
  RouterModule.register([
2934
1948
  {
@@ -2942,30 +1956,13 @@ var DurableDashboardModule = class _DurableDashboardModule {
2942
1956
  ])
2943
1957
  ],
2944
1958
  controllers: [
2945
- DurableUiController,
2946
- // The auth controller (login page + logout) only exists when dashboardAuth might be
2947
- // configured — mounting it unconditionally would add a new `/login` route even for hosts
2948
- // that never set `dashboardAuth`, breaking "absent -> today's behavior byte-for-byte".
2949
- ...authMayBeConfigured ? [
2950
- DurableAuthController
2951
- ] : []
1959
+ controllerFor(DurableUiController, options.decorators)
2952
1960
  ],
2953
1961
  providers: [
2954
- {
2955
- provide: DASHBOARD_BASE_PATH,
2956
- useValue: basePath
2957
- },
2958
- {
2959
- provide: DASHBOARD_API_PATH,
2960
- useValue: apiBasePath
2961
- },
2962
- authProvider,
2963
- DurableUiSessionGuard,
2964
- // `DASHBOARD_GATE` itself comes from the imported DurableApiModule (resolved once there).
2965
- DurableDashboardGateGuard,
2966
- // DurableUiController (and DurableAuthController, when present) are hosted HERE, so their
2967
- // guards DI-instantiate from this module.
2968
- ...options.guards ?? []
1962
+ ...pathProviders,
1963
+ ...decorated ? [] : [
1964
+ protectionCheckProvider()
1965
+ ]
2969
1966
  ],
2970
1967
  // Re-export the API module so its DashboardService reaches importers (e.g. flip's own controllers).
2971
1968
  exports: [
@@ -2974,26 +1971,20 @@ var DurableDashboardModule = class _DurableDashboardModule {
2974
1971
  };
2975
1972
  }
2976
1973
  };
2977
- DurableDashboardModule = _ts_decorate12([
1974
+ DurableDashboardModule = _ts_decorate7([
2978
1975
  Module({})
2979
1976
  ], DurableDashboardModule);
2980
1977
  export {
2981
1978
  DASHBOARD_API_PATH,
2982
1979
  DASHBOARD_BASE_PATH,
2983
- DASHBOARD_GATE,
2984
- DURABLE_DASHBOARD_TOKEN_ENV,
1980
+ DASHBOARD_PROTECTION_CHECK,
2985
1981
  DashboardService,
2986
1982
  DurableApiController,
1983
+ DurableApiControllerBase,
2987
1984
  DurableApiModule,
2988
- DurableApiSessionGuard,
2989
- DurableAuthController,
2990
- DurableDashboardGateGuard,
2991
1985
  DurableDashboardModule,
2992
1986
  DurableUiController,
2993
- DurableUiSessionGuard,
2994
- dashboardGateProvider,
2995
- readDashboardToken,
2996
- reportDashboardGate,
2997
- resolveDashboardGate
1987
+ DurableUiControllerBase,
1988
+ UNPROTECTED_CONSOLE_IN_PRODUCTION
2998
1989
  };
2999
1990
  //# sourceMappingURL=index.js.map