@axlsdk/studio 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  handleWsMessage
4
- } from "./chunk-HAKTKSZS.js";
4
+ } from "./chunk-MKL6RKQ4.js";
5
5
 
6
6
  // src/middleware.ts
7
7
  import { resolve as resolve2, dirname } from "path";
@@ -65,6 +65,12 @@ function redactValue(value, redact) {
65
65
  }
66
66
  function redactExecutionInfo(info, redact) {
67
67
  if (!redact) return info;
68
+ return {
69
+ ...redactExecutionFields(info),
70
+ events: info.events.map((e) => redactStreamEvent(e, true))
71
+ };
72
+ }
73
+ function redactExecutionFields(info) {
68
74
  return {
69
75
  ...info,
70
76
  ...info.result !== void 0 ? { result: REDACTED } : {},
@@ -76,13 +82,13 @@ function redactExecutionInfo(info, redact) {
76
82
  // scrubbed. Use a `{ redacted: true }` marker to keep the field
77
83
  // queryable/serializable rather than dropping it (mirrors
78
84
  // `redactPendingDecision` on `decision.metadata`).
79
- ...info.metadata !== void 0 ? { metadata: { redacted: true } } : {},
80
- events: info.events.map((e) => redactStreamEvent(e, true))
85
+ ...info.metadata !== void 0 ? { metadata: { redacted: true } } : {}
81
86
  };
82
87
  }
83
- function redactExecutionList(infos, redact) {
84
- if (!redact) return infos;
85
- return infos.map((info) => redactExecutionInfo(info, redact));
88
+ function summarizeExecution(info, redact) {
89
+ const { events, ...rest } = info;
90
+ const summary = { ...rest, eventCount: events.length };
91
+ return redact ? redactExecutionFields(summary) : summary;
86
92
  }
87
93
  function redactMemoryValue(value, redact) {
88
94
  if (!redact) return value;
@@ -693,6 +699,29 @@ function createWsHandlers(connMgr) {
693
699
  };
694
700
  }
695
701
 
702
+ // src/server/aggregates/coalesced-runner.ts
703
+ var CoalescedRunner = class {
704
+ constructor(task) {
705
+ this.task = task;
706
+ }
707
+ current;
708
+ trailing;
709
+ run() {
710
+ if (!this.current) {
711
+ const started = this.task().finally(() => {
712
+ this.current = void 0;
713
+ });
714
+ this.current = started;
715
+ return started;
716
+ }
717
+ this.trailing ??= this.current.catch(() => void 0).then(() => {
718
+ this.trailing = void 0;
719
+ return this.run();
720
+ });
721
+ return this.trailing;
722
+ }
723
+ };
724
+
696
725
  // src/server/aggregates/aggregate-snapshots.ts
697
726
  var WINDOW_MS = {
698
727
  "24h": 24 * 60 * 60 * 1e3,
@@ -787,10 +816,19 @@ var TraceAggregator = class {
787
816
  REBUILD_INTERVAL_MS
788
817
  );
789
818
  }
790
- async rebuild() {
791
- const executions = await this.options.runtime.getExecutions();
819
+ /**
820
+ * Rebuild from the store. Requests that arrive during a rebuild (a burst of
821
+ * deletes, the timer) coalesce into one trailing rebuild.
822
+ */
823
+ rebuild() {
824
+ return this.rebuilds.run();
825
+ }
826
+ rebuilds = new CoalescedRunner(() => this.rebuildFromStore());
827
+ async rebuildFromStore() {
792
828
  const cap = this.options.executionCap ?? 2e3;
793
- const capped = executions.slice(0, cap);
829
+ const capped = await this.options.runtime.getExecutions({
830
+ limit: cap
831
+ });
794
832
  const now = Date.now();
795
833
  const fresh = new Map(
796
834
  this.options.windows.map((w) => [w, this.options.emptyState()])
@@ -862,11 +900,20 @@ var ExecutionAggregator = class {
862
900
  REBUILD_INTERVAL_MS
863
901
  );
864
902
  }
865
- async rebuild() {
903
+ /**
904
+ * Rebuild from the store. Requests that arrive during a rebuild (a burst of
905
+ * deletes, the timer) coalesce into one trailing rebuild.
906
+ */
907
+ rebuild() {
908
+ return this.rebuilds.run();
909
+ }
910
+ rebuilds = new CoalescedRunner(() => this.rebuildFromStore());
911
+ async rebuildFromStore() {
866
912
  this.generation++;
867
- const executions = await this.options.runtime.getExecutions();
868
913
  const cap = this.options.executionCap ?? 2e3;
869
- const capped = executions.slice(0, cap);
914
+ const capped = await this.options.runtime.getExecutions({
915
+ limit: cap
916
+ });
870
917
  const now = Date.now();
871
918
  const fresh = new Map(
872
919
  this.options.windows.map((w) => [w, this.options.emptyState()])
@@ -1483,16 +1530,66 @@ function createWorkflowRoutes(connMgr) {
1483
1530
 
1484
1531
  // src/server/routes/executions.ts
1485
1532
  var import_hono3 = require("hono");
1533
+
1534
+ // src/server/execution-cursor.ts
1535
+ function encodeExecutionCursor(cursor) {
1536
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
1537
+ }
1538
+ function decodeExecutionCursor(value) {
1539
+ if (!/^[A-Za-z0-9_-]+$/.test(value)) return void 0;
1540
+ let parsed;
1541
+ try {
1542
+ parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
1543
+ } catch {
1544
+ return void 0;
1545
+ }
1546
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1547
+ const { startedAt, executionId } = parsed;
1548
+ if (typeof startedAt !== "number" || !Number.isFinite(startedAt)) return void 0;
1549
+ if (typeof executionId !== "string" || executionId === "") return void 0;
1550
+ return { startedAt, executionId };
1551
+ }
1552
+
1553
+ // src/server/routes/executions.ts
1554
+ var DEFAULT_PAGE_LIMIT = 100;
1555
+ var MAX_PAGE_LIMIT = 500;
1486
1556
  function createExecutionRoutes(connMgr) {
1487
1557
  const app5 = new import_hono3.Hono();
1488
1558
  app5.get("/executions", async (c) => {
1489
1559
  const runtime = c.get("runtime");
1490
- const executions = await runtime.getExecutions();
1560
+ const limitParam = c.req.query("limit");
1561
+ let limit = DEFAULT_PAGE_LIMIT;
1562
+ if (limitParam !== void 0) {
1563
+ limit = Number(limitParam);
1564
+ if (limitParam.trim() === "" || !Number.isInteger(limit) || limit < 1) {
1565
+ return invalidParam("limit", `\`limit\` must be a positive integer (got "${limitParam}")`);
1566
+ }
1567
+ if (limit > MAX_PAGE_LIMIT) {
1568
+ return invalidParam("limit", `\`limit\` must be at most ${MAX_PAGE_LIMIT} (got ${limit})`);
1569
+ }
1570
+ }
1571
+ const cursorParam = c.req.query("cursor");
1572
+ let cursor;
1573
+ if (cursorParam !== void 0) {
1574
+ cursor = decodeExecutionCursor(cursorParam);
1575
+ if (!cursor) return invalidParam("cursor", "`cursor` is not a valid cursor");
1576
+ }
1577
+ const rows = await runtime.getExecutions({ limit: limit + 1, cursor });
1578
+ const page = rows.slice(0, limit);
1579
+ const last = page.at(-1);
1580
+ const redact = runtime.isRedactEnabled();
1491
1581
  return c.json({
1492
1582
  ok: true,
1493
- data: redactExecutionList(executions, runtime.isRedactEnabled())
1583
+ data: page.map((info) => summarizeExecution(info, redact)),
1584
+ nextCursor: rows.length > limit && last ? encodeExecutionCursor({ startedAt: last.startedAt, executionId: last.executionId }) : null
1494
1585
  });
1495
1586
  });
1587
+ function invalidParam(param, message) {
1588
+ return Response.json(
1589
+ { ok: false, error: { code: "INVALID_PARAM", message, param } },
1590
+ { status: 400 }
1591
+ );
1592
+ }
1496
1593
  app5.get("/executions/:id", async (c) => {
1497
1594
  const runtime = c.get("runtime");
1498
1595
  const id = c.req.param("id");