@dudousxd/nestjs-durable-dashboard 0.22.2 → 0.22.4

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/dist/client/durable-client.d.ts +3 -3
  3. package/dist/client/durable-client.d.ts.map +1 -1
  4. package/dist/client/durable-client.js.map +1 -1
  5. package/dist/client/group-subprocesses.d.ts +5 -5
  6. package/dist/client/group-subprocesses.d.ts.map +1 -1
  7. package/dist/server/index.cjs +695 -0
  8. package/dist/server/index.cjs.map +1 -0
  9. package/dist/server/index.d.cts +160 -0
  10. package/dist/server/index.d.ts +160 -5
  11. package/dist/server/index.js +662 -4
  12. package/dist/server/index.js.map +1 -1
  13. package/dist/spa/assets/{index-B0Nn98HA.js → index-D8irnyfw.js} +2 -2
  14. package/dist/spa/index.html +1 -1
  15. package/package.json +14 -7
  16. package/dist/server/attr-filter.d.ts +0 -8
  17. package/dist/server/attr-filter.d.ts.map +0 -1
  18. package/dist/server/attr-filter.js +0 -30
  19. package/dist/server/attr-filter.js.map +0 -1
  20. package/dist/server/dashboard.service.d.ts +0 -70
  21. package/dist/server/dashboard.service.d.ts.map +0 -1
  22. package/dist/server/dashboard.service.js +0 -157
  23. package/dist/server/dashboard.service.js.map +0 -1
  24. package/dist/server/durable-api.controller.d.ts +0 -44
  25. package/dist/server/durable-api.controller.d.ts.map +0 -1
  26. package/dist/server/durable-api.controller.js +0 -197
  27. package/dist/server/durable-api.controller.js.map +0 -1
  28. package/dist/server/durable-dashboard.module.d.ts +0 -26
  29. package/dist/server/durable-dashboard.module.d.ts.map +0 -1
  30. package/dist/server/durable-dashboard.module.js +0 -60
  31. package/dist/server/durable-dashboard.module.js.map +0 -1
  32. package/dist/server/durable-ui.controller.d.ts +0 -19
  33. package/dist/server/durable-ui.controller.d.ts.map +0 -1
  34. package/dist/server/durable-ui.controller.js +0 -99
  35. package/dist/server/durable-ui.controller.js.map +0 -1
  36. package/dist/server/index.d.ts.map +0 -1
@@ -1,5 +1,663 @@
1
- export * from './dashboard.service.js';
2
- export * from './durable-api.controller.js';
3
- export * from './durable-dashboard.module.js';
4
- export * from './durable-ui.controller.js';
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/server/dashboard.service.ts
5
+ import { STATE_STORE_CANONICAL, WorkflowEngine, collectMetrics } from "@dudousxd/nestjs-durable-core";
6
+ import { Inject, Injectable } from "@nestjs/common";
7
+ import { Observable } from "rxjs";
8
+ function _ts_decorate(decorators, target, key, desc) {
9
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
10
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
11
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
12
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
13
+ }
14
+ __name(_ts_decorate, "_ts_decorate");
15
+ function _ts_metadata(k, v) {
16
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
17
+ }
18
+ __name(_ts_metadata, "_ts_metadata");
19
+ function _ts_param(paramIndex, decorator) {
20
+ return function(target, key) {
21
+ decorator(target, key, paramIndex);
22
+ };
23
+ }
24
+ __name(_ts_param, "_ts_param");
25
+ var DashboardService = class {
26
+ static {
27
+ __name(this, "DashboardService");
28
+ }
29
+ store;
30
+ engine;
31
+ /** Prometheus counters accumulated from engine events since boot (per process). */
32
+ metricsCollector;
33
+ constructor(store, engine) {
34
+ this.store = store;
35
+ this.engine = engine;
36
+ this.metricsCollector = collectMetrics(this.engine);
37
+ }
38
+ /**
39
+ * Prometheus-text metrics for a `/metrics` scrape: the event-counters (runs/steps by outcome,
40
+ * per-workflow counts) plus live **backlog gauges** queried at scrape time — `durable_pending_runs`
41
+ * (the dispatch backlog: the key health signal of the dispatch model) and `durable_dead_runs` (DLQ
42
+ * size). Capped per status so the scrape can't load an unbounded result set.
43
+ */
44
+ async metrics() {
45
+ const cap = 1e4;
46
+ const [pending, running, dead] = await Promise.all([
47
+ this.store.listRuns({
48
+ status: "pending",
49
+ limit: cap
50
+ }),
51
+ this.store.listRuns({
52
+ status: "running",
53
+ limit: cap
54
+ }),
55
+ this.store.listRuns({
56
+ status: "dead",
57
+ limit: cap
58
+ })
59
+ ]);
60
+ const gauges = [
61
+ "# TYPE durable_pending_runs gauge",
62
+ `durable_pending_runs ${pending.length}`,
63
+ "# TYPE durable_running_runs gauge",
64
+ `durable_running_runs ${running.length}`,
65
+ "# TYPE durable_dead_runs gauge",
66
+ `durable_dead_runs ${dead.length}`
67
+ ];
68
+ const health = await this.engine.workerHealth();
69
+ if (health.length > 0) {
70
+ gauges.push("# TYPE durable_group_queue_depth gauge");
71
+ for (const h of health) {
72
+ gauges.push(`durable_group_queue_depth{group="${h.group}"} ${h.depth}`);
73
+ }
74
+ gauges.push("# TYPE durable_group_live_workers gauge");
75
+ for (const h of health) {
76
+ gauges.push(`durable_group_live_workers{group="${h.group}"} ${h.liveWorkers.length}`);
77
+ }
78
+ }
79
+ return `${this.metricsCollector.prometheus()}${gauges.join("\n")}
80
+ `;
81
+ }
82
+ listRuns(query) {
83
+ return this.store.listRuns(query);
84
+ }
85
+ /** Per-group worker health (queue backlog + live worker heartbeats) for the Workers panel. The
86
+ * alert state a row turns red on is `depth > 0 && liveWorkers.length === 0`. */
87
+ workerHealth() {
88
+ return this.engine.workerHealth();
89
+ }
90
+ async getRunDetail(runId) {
91
+ const run = await this.store.getRun(runId);
92
+ if (!run) return null;
93
+ const [timeline, children] = await Promise.all([
94
+ this.store.listCheckpoints(runId),
95
+ this.engine.getRunChildren(runId)
96
+ ]);
97
+ return {
98
+ run,
99
+ timeline,
100
+ children
101
+ };
102
+ }
103
+ /** Fix-and-replay a dead/failed run with a corrected input — a fresh linked run. Returns its id. */
104
+ retryWithInput(runId, input) {
105
+ return this.engine.retryWithInput(runId, input);
106
+ }
107
+ /** Re-run a failed/incomplete run; completed steps replay from their checkpoints. */
108
+ retry(runId) {
109
+ return this.engine.requeue(runId);
110
+ }
111
+ cancel(runId, opts) {
112
+ return this.engine.cancel(runId, opts);
113
+ }
114
+ /**
115
+ * Apply `retry` or `cancel` to every run matching a filter (status / tag / workflow) — e.g. "retry
116
+ * every `dead` run tagged `type:mel`". Capped at 500 runs per call; runs that can't be acted on
117
+ * (already terminal) are skipped. Returns how many matched and how many the action applied to.
118
+ */
119
+ async bulk(action, filter, opts) {
120
+ const runs = await this.store.listRuns({
121
+ ...filter,
122
+ limit: 500
123
+ });
124
+ let applied = 0;
125
+ for (const r of runs) {
126
+ try {
127
+ if (action === "retry") await this.engine.requeue(r.id);
128
+ else await this.engine.cancel(r.id, opts);
129
+ applied += 1;
130
+ } catch {
131
+ }
132
+ }
133
+ return {
134
+ matched: runs.length,
135
+ applied
136
+ };
137
+ }
138
+ /** Resume a run paused at a `ctx.breakpoint` (the "continue" button). */
139
+ continue(runId) {
140
+ return this.engine.continue(runId);
141
+ }
142
+ /**
143
+ * Deliver a `ctx.webhook()` callback: turn an inbound POST (token + body) into the signal the
144
+ * waiting run is parked on. Returns the run result, or `null` if no run waits on that token (a
145
+ * stale/duplicate callback) — a safe no-op the controller maps to 404.
146
+ */
147
+ deliverWebhook(token, body) {
148
+ return this.engine.signal(token, body);
149
+ }
150
+ /** Side-effect-free read of a value a run published via `ctx.setEvent` (a live query). */
151
+ getEvent(runId, key) {
152
+ return this.engine.getEvent(runId, key);
153
+ }
154
+ /** Deliver a validated `ctx.onUpdate` to a run; the validator may reject it (see UpdateResult). */
155
+ update(runId, name, arg) {
156
+ return this.engine.update(runId, name, arg);
157
+ }
158
+ /**
159
+ * Live stream of a run's lifecycle events for SSE. Backed by `engine.subscribe`, which — when the
160
+ * transport has a control plane — receives events from EVERY instance, so a dashboard-only pod
161
+ * tails a run executing on a worker pod. Without a control plane it only sees same-process events.
162
+ */
163
+ streamRun(runId) {
164
+ return new Observable((subscriber) => {
165
+ const off = this.engine.subscribe((event) => {
166
+ if (event.runId === runId) subscriber.next({
167
+ data: event
168
+ });
169
+ });
170
+ return () => off();
171
+ });
172
+ }
173
+ };
174
+ DashboardService = _ts_decorate([
175
+ Injectable(),
176
+ _ts_param(0, Inject(STATE_STORE_CANONICAL)),
177
+ _ts_metadata("design:type", Function),
178
+ _ts_metadata("design:paramtypes", [
179
+ typeof StateStore === "undefined" ? Object : StateStore,
180
+ typeof WorkflowEngine === "undefined" ? Object : WorkflowEngine
181
+ ])
182
+ ], DashboardService);
183
+
184
+ // src/server/durable-api.controller.ts
185
+ import { Body, Controller, Get, Header, NotFoundException, Param, Post, Query, Sse } from "@nestjs/common";
186
+
187
+ // src/server/attr-filter.ts
188
+ var ATTR_OPS = /* @__PURE__ */ new Set([
189
+ "eq",
190
+ "ne",
191
+ "gt",
192
+ "gte",
193
+ "lt",
194
+ "lte"
195
+ ]);
196
+ function coerce(v) {
197
+ if (v === "true") return true;
198
+ if (v === "false") return false;
199
+ if (v !== "" && !Number.isNaN(Number(v))) return Number(v);
200
+ return v;
201
+ }
202
+ __name(coerce, "coerce");
203
+ function parseAttrFilters(attr) {
204
+ if (!attr) return void 0;
205
+ const raw = Array.isArray(attr) ? attr : [
206
+ attr
207
+ ];
208
+ const filters = [];
209
+ for (const entry of raw) {
210
+ const [key, op, ...rest] = entry.split(":");
211
+ if (!key || !ATTR_OPS.has(op) || rest.length === 0) continue;
212
+ filters.push({
213
+ key,
214
+ op,
215
+ value: coerce(rest.join(":"))
216
+ });
217
+ }
218
+ return filters.length ? filters : void 0;
219
+ }
220
+ __name(parseAttrFilters, "parseAttrFilters");
221
+
222
+ // src/server/durable-api.controller.ts
223
+ function _ts_decorate2(decorators, target, key, desc) {
224
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
225
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
226
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
227
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
228
+ }
229
+ __name(_ts_decorate2, "_ts_decorate");
230
+ function _ts_metadata2(k, v) {
231
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
232
+ }
233
+ __name(_ts_metadata2, "_ts_metadata");
234
+ function _ts_param2(paramIndex, decorator) {
235
+ return function(target, key) {
236
+ decorator(target, key, paramIndex);
237
+ };
238
+ }
239
+ __name(_ts_param2, "_ts_param");
240
+ var DurableApiController = class {
241
+ static {
242
+ __name(this, "DurableApiController");
243
+ }
244
+ dashboard;
245
+ constructor(dashboard) {
246
+ this.dashboard = dashboard;
247
+ }
248
+ runs(status, workflow, tag, attr) {
249
+ return this.dashboard.listRuns({
250
+ status,
251
+ workflow,
252
+ tag,
253
+ attributes: parseAttrFilters(attr)
254
+ });
255
+ }
256
+ /** Prometheus-text metrics (runs/steps by outcome, per-workflow counts) for a scrape. */
257
+ metrics() {
258
+ return this.dashboard.metrics();
259
+ }
260
+ /** Per-group worker health (queue backlog + live worker heartbeats) for the Workers panel. */
261
+ workers() {
262
+ return this.dashboard.workerHealth();
263
+ }
264
+ async run(id) {
265
+ const detail = await this.dashboard.getRunDetail(id);
266
+ if (!detail) throw new NotFoundException(`run ${id} not found`);
267
+ return detail;
268
+ }
269
+ /** Server-Sent Events stream of a run's live lifecycle events — the dashboard tails it instead
270
+ * of polling. Cross-pod when the transport has a control plane (see DashboardService.streamRun). */
271
+ stream(id) {
272
+ return this.dashboard.streamRun(id);
273
+ }
274
+ retry(id) {
275
+ return this.dashboard.retry(id);
276
+ }
277
+ /** Fix-and-replay: re-run a dead/failed run with a corrected input (a fresh linked run). */
278
+ retryWithInput(id, body) {
279
+ return this.dashboard.retryWithInput(id, body?.input);
280
+ }
281
+ /** Bulk retry/cancel every run matching a filter (status / tag / workflow). */
282
+ bulk(action, status, tag, workflow, attr, compensate) {
283
+ return this.dashboard.bulk(action === "cancel" ? "cancel" : "retry", {
284
+ status,
285
+ tag,
286
+ workflow,
287
+ attributes: parseAttrFilters(attr)
288
+ }, {
289
+ compensate: compensate === "true"
290
+ });
291
+ }
292
+ async cancel(id, compensate) {
293
+ const result = await this.dashboard.cancel(id, {
294
+ compensate: compensate === "true"
295
+ });
296
+ if (!result) throw new NotFoundException(`run ${id} not found`);
297
+ return result;
298
+ }
299
+ async continue(id) {
300
+ const result = await this.dashboard.continue(id);
301
+ if (!result) throw new NotFoundException(`run ${id} is not paused at a breakpoint`);
302
+ return result;
303
+ }
304
+ /**
305
+ * Public callback endpoint for `ctx.webhook()`: a third party POSTs here (the url handed to it),
306
+ * and the body resumes the waiting run. NOTE: this is reachable by external systems — protect it
307
+ * by treating the token as a secret (it embeds runId:seq) and/or fronting it with signature
308
+ * verification in your own middleware.
309
+ */
310
+ async webhook(token, body) {
311
+ const result = await this.dashboard.deliverWebhook(token, body);
312
+ if (!result) throw new NotFoundException(`no run is waiting on webhook ${token}`);
313
+ return result;
314
+ }
315
+ /** Live query: the latest value a run published for `key` via `ctx.setEvent` (no side effect). */
316
+ event(id, key) {
317
+ return this.dashboard.getEvent(id, key);
318
+ }
319
+ /** Deliver a validated `ctx.onUpdate` to a run; the body is the update argument. */
320
+ update(id, name, body) {
321
+ return this.dashboard.update(id, name, body);
322
+ }
323
+ };
324
+ _ts_decorate2([
325
+ Get("runs"),
326
+ _ts_param2(0, Query("status")),
327
+ _ts_param2(1, Query("workflow")),
328
+ _ts_param2(2, Query("tag")),
329
+ _ts_param2(3, Query("attr")),
330
+ _ts_metadata2("design:type", Function),
331
+ _ts_metadata2("design:paramtypes", [
332
+ typeof RunStatus === "undefined" ? Object : RunStatus,
333
+ String,
334
+ String,
335
+ Object
336
+ ]),
337
+ _ts_metadata2("design:returntype", void 0)
338
+ ], DurableApiController.prototype, "runs", null);
339
+ _ts_decorate2([
340
+ Get("metrics"),
341
+ Header("Content-Type", "text/plain; version=0.0.4"),
342
+ _ts_metadata2("design:type", Function),
343
+ _ts_metadata2("design:paramtypes", []),
344
+ _ts_metadata2("design:returntype", void 0)
345
+ ], DurableApiController.prototype, "metrics", null);
346
+ _ts_decorate2([
347
+ Get("workers"),
348
+ _ts_metadata2("design:type", Function),
349
+ _ts_metadata2("design:paramtypes", []),
350
+ _ts_metadata2("design:returntype", void 0)
351
+ ], DurableApiController.prototype, "workers", null);
352
+ _ts_decorate2([
353
+ Get("runs/:id"),
354
+ _ts_param2(0, Param("id")),
355
+ _ts_metadata2("design:type", Function),
356
+ _ts_metadata2("design:paramtypes", [
357
+ String
358
+ ]),
359
+ _ts_metadata2("design:returntype", Promise)
360
+ ], DurableApiController.prototype, "run", null);
361
+ _ts_decorate2([
362
+ Sse("runs/:id/stream"),
363
+ _ts_param2(0, Param("id")),
364
+ _ts_metadata2("design:type", Function),
365
+ _ts_metadata2("design:paramtypes", [
366
+ String
367
+ ]),
368
+ _ts_metadata2("design:returntype", void 0)
369
+ ], DurableApiController.prototype, "stream", null);
370
+ _ts_decorate2([
371
+ Post("runs/:id/retry"),
372
+ _ts_param2(0, Param("id")),
373
+ _ts_metadata2("design:type", Function),
374
+ _ts_metadata2("design:paramtypes", [
375
+ String
376
+ ]),
377
+ _ts_metadata2("design:returntype", void 0)
378
+ ], DurableApiController.prototype, "retry", null);
379
+ _ts_decorate2([
380
+ Post("runs/:id/retry-with-input"),
381
+ _ts_param2(0, Param("id")),
382
+ _ts_param2(1, Body()),
383
+ _ts_metadata2("design:type", Function),
384
+ _ts_metadata2("design:paramtypes", [
385
+ String,
386
+ Object
387
+ ]),
388
+ _ts_metadata2("design:returntype", void 0)
389
+ ], DurableApiController.prototype, "retryWithInput", null);
390
+ _ts_decorate2([
391
+ Post("bulk/:action"),
392
+ _ts_param2(0, Param("action")),
393
+ _ts_param2(1, Query("status")),
394
+ _ts_param2(2, Query("tag")),
395
+ _ts_param2(3, Query("workflow")),
396
+ _ts_param2(4, Query("attr")),
397
+ _ts_param2(5, Query("compensate")),
398
+ _ts_metadata2("design:type", Function),
399
+ _ts_metadata2("design:paramtypes", [
400
+ String,
401
+ typeof RunStatus === "undefined" ? Object : RunStatus,
402
+ String,
403
+ String,
404
+ Object,
405
+ String
406
+ ]),
407
+ _ts_metadata2("design:returntype", void 0)
408
+ ], DurableApiController.prototype, "bulk", null);
409
+ _ts_decorate2([
410
+ Post("runs/:id/cancel"),
411
+ _ts_param2(0, Param("id")),
412
+ _ts_param2(1, Query("compensate")),
413
+ _ts_metadata2("design:type", Function),
414
+ _ts_metadata2("design:paramtypes", [
415
+ String,
416
+ String
417
+ ]),
418
+ _ts_metadata2("design:returntype", Promise)
419
+ ], DurableApiController.prototype, "cancel", null);
420
+ _ts_decorate2([
421
+ Post("runs/:id/continue"),
422
+ _ts_param2(0, Param("id")),
423
+ _ts_metadata2("design:type", Function),
424
+ _ts_metadata2("design:paramtypes", [
425
+ String
426
+ ]),
427
+ _ts_metadata2("design:returntype", Promise)
428
+ ], DurableApiController.prototype, "continue", null);
429
+ _ts_decorate2([
430
+ Post("webhooks/:token"),
431
+ _ts_param2(0, Param("token")),
432
+ _ts_param2(1, Body()),
433
+ _ts_metadata2("design:type", Function),
434
+ _ts_metadata2("design:paramtypes", [
435
+ String,
436
+ Object
437
+ ]),
438
+ _ts_metadata2("design:returntype", Promise)
439
+ ], DurableApiController.prototype, "webhook", null);
440
+ _ts_decorate2([
441
+ Get("runs/:id/events/:key"),
442
+ _ts_param2(0, Param("id")),
443
+ _ts_param2(1, Param("key")),
444
+ _ts_metadata2("design:type", Function),
445
+ _ts_metadata2("design:paramtypes", [
446
+ String,
447
+ String
448
+ ]),
449
+ _ts_metadata2("design:returntype", void 0)
450
+ ], DurableApiController.prototype, "event", null);
451
+ _ts_decorate2([
452
+ Post("runs/:id/updates/:name"),
453
+ _ts_param2(0, Param("id")),
454
+ _ts_param2(1, Param("name")),
455
+ _ts_param2(2, Body()),
456
+ _ts_metadata2("design:type", Function),
457
+ _ts_metadata2("design:paramtypes", [
458
+ String,
459
+ String,
460
+ Object
461
+ ]),
462
+ _ts_metadata2("design:returntype", void 0)
463
+ ], DurableApiController.prototype, "update", null);
464
+ DurableApiController = _ts_decorate2([
465
+ Controller(),
466
+ _ts_metadata2("design:type", Function),
467
+ _ts_metadata2("design:paramtypes", [
468
+ typeof DashboardService === "undefined" ? Object : DashboardService
469
+ ])
470
+ ], DurableApiController);
471
+
472
+ // src/server/durable-dashboard.module.ts
473
+ import { Module } from "@nestjs/common";
474
+ import { RouterModule } from "@nestjs/core";
475
+
476
+ // src/server/durable-ui.controller.ts
477
+ import { existsSync, readFileSync } from "node:fs";
478
+ import { basename, extname, join, resolve, sep } from "node:path";
479
+ import { fileURLToPath } from "node:url";
480
+ import { Controller as Controller2, Get as Get2, Header as Header2, Inject as Inject2, NotFoundException as NotFoundException2, Param as Param2, StreamableFile } from "@nestjs/common";
481
+ function _ts_decorate3(decorators, target, key, desc) {
482
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
483
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
484
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
485
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
486
+ }
487
+ __name(_ts_decorate3, "_ts_decorate");
488
+ function _ts_metadata3(k, v) {
489
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
490
+ }
491
+ __name(_ts_metadata3, "_ts_metadata");
492
+ function _ts_param3(paramIndex, decorator) {
493
+ return function(target, key) {
494
+ decorator(target, key, paramIndex);
495
+ };
496
+ }
497
+ __name(_ts_param3, "_ts_param");
498
+ var DASHBOARD_BASE_PATH = Symbol("DASHBOARD_BASE_PATH");
499
+ var DASHBOARD_API_PATH = Symbol("DASHBOARD_API_PATH");
500
+ var BUILD_BASE = "/durable";
501
+ function spaDir() {
502
+ return fileURLToPath(new URL("../spa", import.meta.url));
503
+ }
504
+ __name(spaDir, "spaDir");
505
+ var CONTENT_TYPES = {
506
+ ".js": "text/javascript; charset=utf-8",
507
+ ".css": "text/css; charset=utf-8",
508
+ ".map": "application/json; charset=utf-8",
509
+ ".svg": "image/svg+xml",
510
+ ".json": "application/json; charset=utf-8",
511
+ ".woff2": "font/woff2",
512
+ ".ico": "image/x-icon"
513
+ };
514
+ var DurableUiController = class {
515
+ static {
516
+ __name(this, "DurableUiController");
517
+ }
518
+ basePath;
519
+ apiBasePath;
520
+ dir = spaDir();
521
+ constructor(basePath, apiBasePath) {
522
+ this.basePath = basePath;
523
+ this.apiBasePath = apiBasePath;
524
+ }
525
+ // index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = the
526
+ // classic "stuck loading after a deploy"). The hashed assets below are immutable.
527
+ index() {
528
+ const indexPath = join(this.dir, "index.html");
529
+ if (!existsSync(indexPath)) {
530
+ throw new NotFoundException2("Dashboard is not built. Run the package build.");
531
+ }
532
+ const html = readFileSync(indexPath, "utf8").replaceAll(`="${BUILD_BASE}/`, `="${this.basePath}/`);
533
+ const inject = `<script>window.__DURABLE_BASE__='${this.basePath}';window.__DURABLE_API__='${this.apiBasePath}';</script>`;
534
+ return html.includes("</head>") ? html.replace("</head>", `${inject}</head>`) : inject + html;
535
+ }
536
+ asset(file) {
537
+ const safe = basename(file);
538
+ if (safe !== file) throw new NotFoundException2();
539
+ const root = resolve(this.dir, "assets");
540
+ const assetPath = resolve(root, safe);
541
+ if (!assetPath.startsWith(root + sep) || !existsSync(assetPath)) {
542
+ throw new NotFoundException2();
543
+ }
544
+ const type = CONTENT_TYPES[extname(safe)] ?? "application/octet-stream";
545
+ return new StreamableFile(readFileSync(assetPath), {
546
+ type
547
+ });
548
+ }
549
+ };
550
+ _ts_decorate3([
551
+ Get2(),
552
+ Header2("Content-Type", "text/html; charset=utf-8"),
553
+ Header2("Cache-Control", "no-store, must-revalidate"),
554
+ _ts_metadata3("design:type", Function),
555
+ _ts_metadata3("design:paramtypes", []),
556
+ _ts_metadata3("design:returntype", String)
557
+ ], DurableUiController.prototype, "index", null);
558
+ _ts_decorate3([
559
+ Get2("assets/:file"),
560
+ Header2("Cache-Control", "public, max-age=31536000, immutable"),
561
+ _ts_param3(0, Param2("file")),
562
+ _ts_metadata3("design:type", Function),
563
+ _ts_metadata3("design:paramtypes", [
564
+ String
565
+ ]),
566
+ _ts_metadata3("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
567
+ ], DurableUiController.prototype, "asset", null);
568
+ DurableUiController = _ts_decorate3([
569
+ Controller2(),
570
+ _ts_param3(0, Inject2(DASHBOARD_BASE_PATH)),
571
+ _ts_param3(1, Inject2(DASHBOARD_API_PATH)),
572
+ _ts_metadata3("design:type", Function),
573
+ _ts_metadata3("design:paramtypes", [
574
+ String,
575
+ String
576
+ ])
577
+ ], DurableUiController);
578
+
579
+ // src/server/durable-dashboard.module.ts
580
+ function _ts_decorate4(decorators, target, key, desc) {
581
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
582
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
583
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
584
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
585
+ }
586
+ __name(_ts_decorate4, "_ts_decorate");
587
+ function normalize(path) {
588
+ return `/${path.replace(/^\/+|\/+$/g, "")}`;
589
+ }
590
+ __name(normalize, "normalize");
591
+ var DurableApiModule = class {
592
+ static {
593
+ __name(this, "DurableApiModule");
594
+ }
595
+ };
596
+ DurableApiModule = _ts_decorate4([
597
+ Module({
598
+ controllers: [
599
+ DurableApiController
600
+ ],
601
+ providers: [
602
+ DashboardService
603
+ ],
604
+ exports: [
605
+ DashboardService
606
+ ]
607
+ })
608
+ ], DurableApiModule);
609
+ var DurableDashboardModule = class _DurableDashboardModule {
610
+ static {
611
+ __name(this, "DurableDashboardModule");
612
+ }
613
+ static forRoot(options = {}) {
614
+ const basePath = normalize(options.basePath ?? "/durable");
615
+ const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);
616
+ return {
617
+ module: _DurableDashboardModule,
618
+ imports: [
619
+ DurableApiModule,
620
+ RouterModule.register([
621
+ {
622
+ path: basePath,
623
+ module: _DurableDashboardModule
624
+ },
625
+ {
626
+ path: apiBasePath,
627
+ module: DurableApiModule
628
+ }
629
+ ])
630
+ ],
631
+ controllers: [
632
+ DurableUiController
633
+ ],
634
+ providers: [
635
+ {
636
+ provide: DASHBOARD_BASE_PATH,
637
+ useValue: basePath
638
+ },
639
+ {
640
+ provide: DASHBOARD_API_PATH,
641
+ useValue: apiBasePath
642
+ }
643
+ ],
644
+ // Re-export the API module so its DashboardService reaches importers (e.g. flip's own controllers).
645
+ exports: [
646
+ DurableApiModule
647
+ ]
648
+ };
649
+ }
650
+ };
651
+ DurableDashboardModule = _ts_decorate4([
652
+ Module({})
653
+ ], DurableDashboardModule);
654
+ export {
655
+ DASHBOARD_API_PATH,
656
+ DASHBOARD_BASE_PATH,
657
+ DashboardService,
658
+ DurableApiController,
659
+ DurableApiModule,
660
+ DurableDashboardModule,
661
+ DurableUiController
662
+ };
5
663
  //# sourceMappingURL=index.js.map