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