@axlsdk/studio 0.26.0 → 0.27.1

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.
@@ -22,7 +22,7 @@
22
22
  }
23
23
  })();
24
24
  </script>
25
- <script type="module" crossorigin src="./assets/index-Cmvsh3H7.js"></script>
25
+ <script type="module" crossorigin src="./assets/index-DxRc450b.js"></script>
26
26
  <link rel="stylesheet" crossorigin href="./assets/index-DinaGVe2.css">
27
27
  </head>
28
28
  <body>
@@ -69,6 +69,12 @@ function redactValue(value, redact) {
69
69
  }
70
70
  function redactExecutionInfo(info, redact) {
71
71
  if (!redact) return info;
72
+ return {
73
+ ...redactExecutionFields(info),
74
+ events: info.events.map((e) => redactStreamEvent(e, true))
75
+ };
76
+ }
77
+ function redactExecutionFields(info) {
72
78
  return {
73
79
  ...info,
74
80
  ...info.result !== void 0 ? { result: REDACTED } : {},
@@ -80,13 +86,13 @@ function redactExecutionInfo(info, redact) {
80
86
  // scrubbed. Use a `{ redacted: true }` marker to keep the field
81
87
  // queryable/serializable rather than dropping it (mirrors
82
88
  // `redactPendingDecision` on `decision.metadata`).
83
- ...info.metadata !== void 0 ? { metadata: { redacted: true } } : {},
84
- events: info.events.map((e) => redactStreamEvent(e, true))
89
+ ...info.metadata !== void 0 ? { metadata: { redacted: true } } : {}
85
90
  };
86
91
  }
87
- function redactExecutionList(infos, redact) {
88
- if (!redact) return infos;
89
- return infos.map((info) => redactExecutionInfo(info, redact));
92
+ function summarizeExecution(info, redact) {
93
+ const { events, ...rest } = info;
94
+ const summary = { ...rest, eventCount: events.length };
95
+ return redact ? redactExecutionFields(summary) : summary;
90
96
  }
91
97
  function redactMemoryValue(value, redact) {
92
98
  if (!redact) return value;
@@ -697,6 +703,29 @@ function createWsHandlers(connMgr) {
697
703
  };
698
704
  }
699
705
 
706
+ // src/server/aggregates/coalesced-runner.ts
707
+ var CoalescedRunner = class {
708
+ constructor(task) {
709
+ this.task = task;
710
+ }
711
+ current;
712
+ trailing;
713
+ run() {
714
+ if (!this.current) {
715
+ const started = this.task().finally(() => {
716
+ this.current = void 0;
717
+ });
718
+ this.current = started;
719
+ return started;
720
+ }
721
+ this.trailing ??= this.current.catch(() => void 0).then(() => {
722
+ this.trailing = void 0;
723
+ return this.run();
724
+ });
725
+ return this.trailing;
726
+ }
727
+ };
728
+
700
729
  // src/server/aggregates/aggregate-snapshots.ts
701
730
  var WINDOW_MS = {
702
731
  "24h": 24 * 60 * 60 * 1e3,
@@ -791,10 +820,19 @@ var TraceAggregator = class {
791
820
  REBUILD_INTERVAL_MS
792
821
  );
793
822
  }
794
- async rebuild() {
795
- const executions = await this.options.runtime.getExecutions();
823
+ /**
824
+ * Rebuild from the store. Requests that arrive during a rebuild (a burst of
825
+ * deletes, the timer) coalesce into one trailing rebuild.
826
+ */
827
+ rebuild() {
828
+ return this.rebuilds.run();
829
+ }
830
+ rebuilds = new CoalescedRunner(() => this.rebuildFromStore());
831
+ async rebuildFromStore() {
796
832
  const cap = this.options.executionCap ?? 2e3;
797
- const capped = executions.slice(0, cap);
833
+ const capped = await this.options.runtime.getExecutions({
834
+ limit: cap
835
+ });
798
836
  const now = Date.now();
799
837
  const fresh = new Map(
800
838
  this.options.windows.map((w) => [w, this.options.emptyState()])
@@ -866,11 +904,20 @@ var ExecutionAggregator = class {
866
904
  REBUILD_INTERVAL_MS
867
905
  );
868
906
  }
869
- async rebuild() {
907
+ /**
908
+ * Rebuild from the store. Requests that arrive during a rebuild (a burst of
909
+ * deletes, the timer) coalesce into one trailing rebuild.
910
+ */
911
+ rebuild() {
912
+ return this.rebuilds.run();
913
+ }
914
+ rebuilds = new CoalescedRunner(() => this.rebuildFromStore());
915
+ async rebuildFromStore() {
870
916
  this.generation++;
871
- const executions = await this.options.runtime.getExecutions();
872
917
  const cap = this.options.executionCap ?? 2e3;
873
- const capped = executions.slice(0, cap);
918
+ const capped = await this.options.runtime.getExecutions({
919
+ limit: cap
920
+ });
874
921
  const now = Date.now();
875
922
  const fresh = new Map(
876
923
  this.options.windows.map((w) => [w, this.options.emptyState()])
@@ -1487,16 +1534,66 @@ function createWorkflowRoutes(connMgr) {
1487
1534
 
1488
1535
  // src/server/routes/executions.ts
1489
1536
  var import_hono3 = require("hono");
1537
+
1538
+ // src/server/execution-cursor.ts
1539
+ function encodeExecutionCursor(cursor) {
1540
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
1541
+ }
1542
+ function decodeExecutionCursor(value) {
1543
+ if (!/^[A-Za-z0-9_-]+$/.test(value)) return void 0;
1544
+ let parsed;
1545
+ try {
1546
+ parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
1547
+ } catch {
1548
+ return void 0;
1549
+ }
1550
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1551
+ const { startedAt, executionId } = parsed;
1552
+ if (typeof startedAt !== "number" || !Number.isFinite(startedAt)) return void 0;
1553
+ if (typeof executionId !== "string" || executionId === "") return void 0;
1554
+ return { startedAt, executionId };
1555
+ }
1556
+
1557
+ // src/server/routes/executions.ts
1558
+ var DEFAULT_PAGE_LIMIT = 100;
1559
+ var MAX_PAGE_LIMIT = 500;
1490
1560
  function createExecutionRoutes(connMgr) {
1491
1561
  const app5 = new import_hono3.Hono();
1492
1562
  app5.get("/executions", async (c) => {
1493
1563
  const runtime = c.get("runtime");
1494
- const executions = await runtime.getExecutions();
1564
+ const limitParam = c.req.query("limit");
1565
+ let limit = DEFAULT_PAGE_LIMIT;
1566
+ if (limitParam !== void 0) {
1567
+ limit = Number(limitParam);
1568
+ if (limitParam.trim() === "" || !Number.isInteger(limit) || limit < 1) {
1569
+ return invalidParam("limit", `\`limit\` must be a positive integer (got "${limitParam}")`);
1570
+ }
1571
+ if (limit > MAX_PAGE_LIMIT) {
1572
+ return invalidParam("limit", `\`limit\` must be at most ${MAX_PAGE_LIMIT} (got ${limit})`);
1573
+ }
1574
+ }
1575
+ const cursorParam = c.req.query("cursor");
1576
+ let cursor;
1577
+ if (cursorParam !== void 0) {
1578
+ cursor = decodeExecutionCursor(cursorParam);
1579
+ if (!cursor) return invalidParam("cursor", "`cursor` is not a valid cursor");
1580
+ }
1581
+ const rows = await runtime.getExecutions({ limit: limit + 1, cursor });
1582
+ const page = rows.slice(0, limit);
1583
+ const last = page.at(-1);
1584
+ const redact = runtime.isRedactEnabled();
1495
1585
  return c.json({
1496
1586
  ok: true,
1497
- data: redactExecutionList(executions, runtime.isRedactEnabled())
1587
+ data: page.map((info) => summarizeExecution(info, redact)),
1588
+ nextCursor: rows.length > limit && last ? encodeExecutionCursor({ startedAt: last.startedAt, executionId: last.executionId }) : null
1498
1589
  });
1499
1590
  });
1591
+ function invalidParam(param, message) {
1592
+ return Response.json(
1593
+ { ok: false, error: { code: "INVALID_PARAM", message, param } },
1594
+ { status: 400 }
1595
+ );
1596
+ }
1500
1597
  app5.get("/executions/:id", async (c) => {
1501
1598
  const runtime = c.get("runtime");
1502
1599
  const id = c.req.param("id");