station-kit 1.1.0 → 2.0.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 (177) hide show
  1. package/.next/standalone/packages/station-kit/.next/BUILD_ID +1 -1
  2. package/.next/standalone/packages/station-kit/.next/app-build-manifest.json +55 -34
  3. package/.next/standalone/packages/station-kit/.next/app-path-routes-manifest.json +11 -8
  4. package/.next/standalone/packages/station-kit/.next/build-manifest.json +2 -2
  5. package/.next/standalone/packages/station-kit/.next/prerender-manifest.json +73 -25
  6. package/.next/standalone/packages/station-kit/.next/routes-manifest.json +20 -0
  7. package/.next/standalone/packages/station-kit/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/packages/station-kit/.next/server/app/_not-found.html +1 -1
  9. package/.next/standalone/packages/station-kit/.next/server/app/_not-found.rsc +8 -8
  10. package/.next/standalone/packages/station-kit/.next/server/app/beacons/[name]/page.js +2 -0
  11. package/.next/standalone/packages/station-kit/.next/server/app/beacons/[name]/page.js.nft.json +1 -0
  12. package/.next/standalone/packages/station-kit/.next/server/app/beacons/[name]/page_client-reference-manifest.js +1 -0
  13. package/.next/standalone/packages/station-kit/.next/server/app/beacons/page.js +2 -0
  14. package/.next/standalone/packages/station-kit/.next/server/app/beacons/page.js.nft.json +1 -0
  15. package/.next/standalone/packages/station-kit/.next/server/app/beacons/page_client-reference-manifest.js +1 -0
  16. package/.next/standalone/packages/station-kit/.next/server/app/beacons.html +1 -0
  17. package/.next/standalone/packages/station-kit/.next/server/app/beacons.meta +7 -0
  18. package/.next/standalone/packages/station-kit/.next/server/app/beacons.rsc +25 -0
  19. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/[id]/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/dyn/[name]/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/dyn/[name]/v/[n]/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new.html +1 -1
  24. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new.rsc +9 -9
  25. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/page_client-reference-manifest.js +1 -1
  26. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts.html +1 -1
  27. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts.rsc +9 -9
  28. package/.next/standalone/packages/station-kit/.next/server/app/environment/page.js +2 -0
  29. package/.next/standalone/packages/station-kit/.next/server/app/environment/page.js.nft.json +1 -0
  30. package/.next/standalone/packages/station-kit/.next/server/app/environment/page_client-reference-manifest.js +1 -0
  31. package/.next/standalone/packages/station-kit/.next/server/app/environment.html +1 -0
  32. package/.next/standalone/packages/station-kit/.next/server/app/environment.meta +7 -0
  33. package/.next/standalone/packages/station-kit/.next/server/app/environment.rsc +25 -0
  34. package/.next/standalone/packages/station-kit/.next/server/app/index.html +1 -1
  35. package/.next/standalone/packages/station-kit/.next/server/app/index.rsc +9 -9
  36. package/.next/standalone/packages/station-kit/.next/server/app/page_client-reference-manifest.js +1 -1
  37. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression.html +1 -1
  39. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression.rsc +9 -9
  40. package/.next/standalone/packages/station-kit/.next/server/app/runs/[id]/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/packages/station-kit/.next/server/app/schedules/[id]/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new.html +1 -1
  44. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new.rsc +9 -9
  45. package/.next/standalone/packages/station-kit/.next/server/app/schedules/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/packages/station-kit/.next/server/app/schedules.html +1 -1
  47. package/.next/standalone/packages/station-kit/.next/server/app/schedules.rsc +9 -9
  48. package/.next/standalone/packages/station-kit/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/packages/station-kit/.next/server/app/settings.html +1 -1
  50. package/.next/standalone/packages/station-kit/.next/server/app/settings.rsc +9 -9
  51. package/.next/standalone/packages/station-kit/.next/server/app/signals/[name]/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/packages/station-kit/.next/server/app/signals/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/packages/station-kit/.next/server/app/signals.html +1 -1
  54. package/.next/standalone/packages/station-kit/.next/server/app/signals.rsc +9 -9
  55. package/.next/standalone/packages/station-kit/.next/server/app-paths-manifest.json +11 -8
  56. package/.next/standalone/packages/station-kit/.next/server/chunks/102.js +1 -1
  57. package/.next/standalone/packages/station-kit/.next/server/pages/404.html +1 -1
  58. package/.next/standalone/packages/station-kit/.next/server/pages/500.html +1 -1
  59. package/.next/standalone/packages/station-kit/.next/server/pages-manifest.json +1 -1
  60. package/.next/standalone/packages/station-kit/.next/server/server-reference-manifest.json +1 -1
  61. package/.next/standalone/packages/station-kit/.next/static/chunks/285-583f23ee7827983f.js +1 -0
  62. package/.next/standalone/packages/station-kit/.next/static/chunks/561-05a7ef9669e3921a.js +1 -0
  63. package/.next/standalone/packages/station-kit/.next/static/chunks/app/beacons/[name]/page-eb1f82847734e145.js +1 -0
  64. package/.next/standalone/packages/station-kit/.next/static/chunks/app/beacons/page-64e296346197e44b.js +1 -0
  65. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/dyn/[name]/v/[n]/page-15edf07ab26241c9.js +1 -0
  66. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-b23a03097c8f75c4.js +1 -0
  67. package/.next/standalone/packages/station-kit/.next/static/chunks/app/environment/page-111242bec067fdee.js +1 -0
  68. package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-e9ac56fcaf7a5ea7.js +1 -0
  69. package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-ef78692958c2fc01.js +1 -0
  70. package/.next/standalone/packages/station-kit/.next/static/chunks/app/playground/expression/page-29294a705dd8f471.js +1 -0
  71. package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-287bbb8f6dec2e23.js +1 -0
  72. package/.next/standalone/packages/station-kit/.next/static/chunks/app/schedules/page-c869218da203e3b4.js +1 -0
  73. package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-05cf43b6ae6b3187.js +1 -0
  74. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-4027803be00c695a.js +1 -0
  75. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-8e076fc5d0188d72.js +1 -0
  76. package/.next/standalone/packages/station-kit/.next/static/css/ddb1646dc25ed100.css +1 -0
  77. package/.next/standalone/packages/station-kit/.next/static/vTGEzSqxhR_E57YAFn9DC/_buildManifest.js +1 -0
  78. package/.next/standalone/packages/station-kit/package.json +4 -1
  79. package/dist/config/schema.d.ts +18 -0
  80. package/dist/config/schema.d.ts.map +1 -1
  81. package/dist/config/schema.js +3 -0
  82. package/dist/config/schema.js.map +1 -1
  83. package/dist/index.d.ts +1 -0
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +3 -0
  86. package/dist/index.js.map +1 -1
  87. package/dist/server/auth/keys.d.ts +18 -1
  88. package/dist/server/auth/keys.d.ts.map +1 -1
  89. package/dist/server/auth/keys.js +38 -6
  90. package/dist/server/auth/keys.js.map +1 -1
  91. package/dist/server/auth/session.d.ts.map +1 -1
  92. package/dist/server/auth/session.js +34 -11
  93. package/dist/server/auth/session.js.map +1 -1
  94. package/dist/server/index.d.ts.map +1 -1
  95. package/dist/server/index.js +135 -18
  96. package/dist/server/index.js.map +1 -1
  97. package/dist/server/metadata.d.ts +2 -0
  98. package/dist/server/metadata.d.ts.map +1 -1
  99. package/dist/server/metadata.js.map +1 -1
  100. package/dist/server/middleware/rate-limit.d.ts.map +1 -1
  101. package/dist/server/middleware/rate-limit.js +22 -1
  102. package/dist/server/middleware/rate-limit.js.map +1 -1
  103. package/dist/server/routes/beacons.d.ts +10 -0
  104. package/dist/server/routes/beacons.d.ts.map +1 -0
  105. package/dist/server/routes/beacons.js +98 -0
  106. package/dist/server/routes/beacons.js.map +1 -0
  107. package/dist/server/routes/broadcasts.d.ts.map +1 -1
  108. package/dist/server/routes/broadcasts.js +6 -2
  109. package/dist/server/routes/broadcasts.js.map +1 -1
  110. package/dist/server/routes/runs.d.ts.map +1 -1
  111. package/dist/server/routes/runs.js +19 -59
  112. package/dist/server/routes/runs.js.map +1 -1
  113. package/dist/server/routes/signals.d.ts.map +1 -1
  114. package/dist/server/routes/signals.js +17 -13
  115. package/dist/server/routes/signals.js.map +1 -1
  116. package/dist/server/routes/v1/env.d.ts +10 -0
  117. package/dist/server/routes/v1/env.d.ts.map +1 -0
  118. package/dist/server/routes/v1/env.js +132 -0
  119. package/dist/server/routes/v1/env.js.map +1 -0
  120. package/dist/server/routes/v1/events.js +2 -2
  121. package/dist/server/routes/v1/events.js.map +1 -1
  122. package/dist/server/routes/v1/runs.d.ts.map +1 -1
  123. package/dist/server/routes/v1/runs.js +10 -31
  124. package/dist/server/routes/v1/runs.js.map +1 -1
  125. package/dist/server/sse.d.ts +2 -1
  126. package/dist/server/sse.d.ts.map +1 -1
  127. package/dist/server/sse.js +5 -1
  128. package/dist/server/sse.js.map +1 -1
  129. package/dist/server/subscriber.d.ts +56 -0
  130. package/dist/server/subscriber.d.ts.map +1 -1
  131. package/dist/server/subscriber.js +94 -0
  132. package/dist/server/subscriber.js.map +1 -1
  133. package/dist/server/ws.d.ts +8 -2
  134. package/dist/server/ws.d.ts.map +1 -1
  135. package/dist/server/ws.js +28 -3
  136. package/dist/server/ws.js.map +1 -1
  137. package/package.json +11 -8
  138. package/src/app/beacons/[name]/beacon-detail.tsx +253 -0
  139. package/src/app/beacons/[name]/page.tsx +6 -0
  140. package/src/app/beacons/page.tsx +98 -0
  141. package/src/app/components/shell.tsx +24 -0
  142. package/src/app/components/status-badge.tsx +13 -1
  143. package/src/app/environment/page.tsx +415 -0
  144. package/src/app/globals.css +48 -0
  145. package/src/app/hooks/use-api.ts +80 -0
  146. package/src/config/schema.ts +21 -0
  147. package/src/index.ts +12 -0
  148. package/src/server/auth/keys.ts +50 -5
  149. package/src/server/auth/session.ts +38 -11
  150. package/src/server/index.ts +144 -18
  151. package/src/server/metadata.ts +2 -0
  152. package/src/server/middleware/rate-limit.ts +20 -1
  153. package/src/server/routes/beacons.ts +112 -0
  154. package/src/server/routes/broadcasts.ts +6 -2
  155. package/src/server/routes/runs.ts +20 -67
  156. package/src/server/routes/signals.ts +17 -13
  157. package/src/server/routes/v1/env.ts +144 -0
  158. package/src/server/routes/v1/events.ts +2 -2
  159. package/src/server/routes/v1/runs.ts +11 -35
  160. package/src/server/sse.ts +7 -2
  161. package/src/server/subscriber.ts +111 -0
  162. package/src/server/ws.ts +38 -4
  163. package/.next/standalone/packages/station-kit/.next/static/THKSkCipW_pj0F6DRXYEG/_buildManifest.js +0 -1
  164. package/.next/standalone/packages/station-kit/.next/static/chunks/285-ff198f0a909c4fdd.js +0 -1
  165. package/.next/standalone/packages/station-kit/.next/static/chunks/561-33d912169940283e.js +0 -1
  166. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/dyn/[name]/v/[n]/page-5eac0507f49a00ec.js +0 -1
  167. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-dee500ccc01f0821.js +0 -1
  168. package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-e14e14f3e5b0b8a9.js +0 -1
  169. package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-aac41ef7a470daab.js +0 -1
  170. package/.next/standalone/packages/station-kit/.next/static/chunks/app/playground/expression/page-dc9d91f3f50f4716.js +0 -1
  171. package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-9e4c4f751a4bea72.js +0 -1
  172. package/.next/standalone/packages/station-kit/.next/static/chunks/app/schedules/page-738d98dc0b63166e.js +0 -1
  173. package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-fc5654b31f57ac21.js +0 -1
  174. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-4b1c09a539a1ebcd.js +0 -1
  175. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-d2f2403dfede87cc.js +0 -1
  176. package/.next/standalone/packages/station-kit/.next/static/css/0be0e65a5f561f37.css +0 -1
  177. /package/.next/standalone/packages/station-kit/.next/static/{THKSkCipW_pj0F6DRXYEG → vTGEzSqxhR_E57YAFn9DC}/_ssgManifest.js +0 -0
@@ -43,17 +43,17 @@ export function signalRoutes(deps: SignalDeps) {
43
43
  }> = [];
44
44
 
45
45
  for (const sig of recurring) {
46
- const runs = await deps.signalAdapter.listRuns(sig.name);
47
- const pendingRun = runs.find((r) => r.status === "pending" && r.kind === "recurring");
48
- const lastRun = runs
49
- .filter((r) => r.status !== "pending")
50
- .sort((a, b) => {
51
- const aT = a.completedAt ?? a.startedAt ?? a.createdAt;
52
- const bT = b.completedAt ?? b.startedAt ?? b.createdAt;
53
- const aMs = aT instanceof Date ? aT.getTime() : new Date(aT).getTime();
54
- const bMs = bT instanceof Date ? bT.getTime() : new Date(bT).getTime();
55
- return bMs - aMs;
56
- })[0];
46
+ // Two bounded queries instead of loading the signal's full history:
47
+ // the current pending run and the most recent non-pending run.
48
+ const [pendingRuns, lastRuns] = await Promise.all([
49
+ deps.signalAdapter.listRuns(sig.name, { limit: 1, statuses: ["pending"] }),
50
+ deps.signalAdapter.listRuns(sig.name, {
51
+ limit: 1,
52
+ statuses: ["running", "completed", "failed", "cancelled"],
53
+ }),
54
+ ]);
55
+ const pendingRun = pendingRuns.find((r) => r.kind === "recurring");
56
+ const lastRun = lastRuns[0];
57
57
 
58
58
  result.push({
59
59
  name: sig.name,
@@ -131,10 +131,14 @@ export function signalRoutes(deps: SignalDeps) {
131
131
  if (!deps.signalRunner) {
132
132
  return c.json({ data: [], meta: { total: 0 } });
133
133
  }
134
- const runs = await deps.signalRunner.listRuns(name);
134
+ const limitRaw = parseInt(c.req.query("limit") ?? "100", 10);
135
+ const limit = Math.min(Number.isNaN(limitRaw) ? 100 : Math.max(limitRaw, 1), 500);
136
+ const offsetRaw = parseInt(c.req.query("offset") ?? "0", 10);
137
+ const offset = Number.isNaN(offsetRaw) ? 0 : Math.max(offsetRaw, 0);
138
+ const runs = await deps.signalRunner.listRuns(name, { limit, offset });
135
139
  return c.json({
136
140
  data: runs.map(serializeRun),
137
- meta: { total: runs.length },
141
+ meta: { count: runs.length, limit, offset },
138
142
  });
139
143
  });
140
144
 
@@ -0,0 +1,144 @@
1
+ import { Hono } from "hono";
2
+ import type { Context } from "hono";
3
+ import {
4
+ EnvStore,
5
+ EnvValidationError,
6
+ toPublic,
7
+ type EnvTarget,
8
+ type EnvVarPublic,
9
+ } from "station-env";
10
+
11
+ export interface V1EnvDeps {
12
+ envStore?: EnvStore;
13
+ }
14
+
15
+ /**
16
+ * Parse the request body and return it only if it's a JSON object. Returns
17
+ * null for a parse error or a non-object body (e.g. `null`, a string, a
18
+ * number) so callers can respond 400 instead of throwing a 500 on
19
+ * `"key" in body`.
20
+ */
21
+ async function readJsonObject(c: Context): Promise<Record<string, unknown> | null> {
22
+ const raw = await c.req.json().catch(() => undefined);
23
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
24
+ return raw as Record<string, unknown>;
25
+ }
26
+
27
+ /** Read-scope routes: list / get. Secret values are redacted. */
28
+ export function v1EnvReadRoutes(deps: V1EnvDeps) {
29
+ const app = new Hono();
30
+
31
+ app.get("/env", async (c) => {
32
+ if (!deps.envStore) return c.json({ data: [] });
33
+ const list = await deps.envStore.listPublic();
34
+ return c.json({ data: list.map(serialize) });
35
+ });
36
+
37
+ app.get("/env/:id", async (c) => {
38
+ if (!deps.envStore) return c.json({ error: "unavailable" }, 503);
39
+ const v = await deps.envStore.getPublic(c.req.param("id"));
40
+ if (!v) return c.json({ error: "not_found" }, 404);
41
+ return c.json({ data: serialize(v) });
42
+ });
43
+
44
+ return app;
45
+ }
46
+
47
+ /** Admin-scope routes: create / update / delete. */
48
+ export function v1EnvRoutes(deps: V1EnvDeps) {
49
+ const app = new Hono();
50
+
51
+ app.post("/env", async (c) => {
52
+ if (!deps.envStore) return c.json({ error: "unavailable" }, 503);
53
+ const body = await readJsonObject(c);
54
+ if (!body) return c.json({ error: "bad_request", message: "body must be a JSON object" }, 400);
55
+ const { key, value, secret, targets } = body as {
56
+ key?: unknown;
57
+ value?: unknown;
58
+ secret?: unknown;
59
+ targets?: unknown;
60
+ };
61
+ if (typeof key !== "string") {
62
+ return c.json({ error: "bad_request", message: "key is required" }, 400);
63
+ }
64
+ if (typeof value !== "string") {
65
+ return c.json({ error: "bad_request", message: "value is required" }, 400);
66
+ }
67
+ const apiKeyId = c.get("apiKeyId" as never) as string | undefined;
68
+ try {
69
+ const created = await deps.envStore.create({
70
+ key,
71
+ value,
72
+ secret: secret === true,
73
+ targets: normalizeTargets(targets),
74
+ createdBy: apiKeyId,
75
+ });
76
+ // Echo back the redacted view — a secret value is never returned, even
77
+ // to its creator, to keep the "write-only" contract uniform.
78
+ return c.json({ data: serialize(toPublic(created)) }, 201);
79
+ } catch (err) {
80
+ if (err instanceof EnvValidationError) {
81
+ return c.json({ error: "bad_request", message: err.message }, 400);
82
+ }
83
+ throw err;
84
+ }
85
+ });
86
+
87
+ app.patch("/env/:id", async (c) => {
88
+ if (!deps.envStore) return c.json({ error: "unavailable" }, 503);
89
+ const id = c.req.param("id");
90
+ const existing = await deps.envStore.get(id);
91
+ if (!existing) return c.json({ error: "not_found" }, 404);
92
+
93
+ const body = await readJsonObject(c);
94
+ if (!body) return c.json({ error: "bad_request", message: "body must be a JSON object" }, 400);
95
+ const patch: { value?: string; secret?: boolean; targets?: EnvTarget[] } = {};
96
+ if ("value" in body) {
97
+ if (typeof body.value !== "string") {
98
+ return c.json({ error: "bad_request", message: "value must be a string" }, 400);
99
+ }
100
+ patch.value = body.value;
101
+ }
102
+ if ("secret" in body) patch.secret = body.secret === true;
103
+ if ("targets" in body) patch.targets = normalizeTargets(body.targets);
104
+
105
+ try {
106
+ const updated = await deps.envStore.update(id, patch);
107
+ if (!updated) return c.json({ error: "not_found" }, 404);
108
+ return c.json({ data: serialize(toPublic(updated)) });
109
+ } catch (err) {
110
+ if (err instanceof EnvValidationError) {
111
+ return c.json({ error: "bad_request", message: err.message }, 400);
112
+ }
113
+ throw err;
114
+ }
115
+ });
116
+
117
+ app.delete("/env/:id", async (c) => {
118
+ if (!deps.envStore) return c.json({ error: "unavailable" }, 503);
119
+ const ok = await deps.envStore.delete(c.req.param("id"));
120
+ if (!ok) return c.json({ error: "not_found" }, 404);
121
+ return c.json({ data: { deleted: true } });
122
+ });
123
+
124
+ return app;
125
+ }
126
+
127
+ function normalizeTargets(raw: unknown): EnvTarget[] {
128
+ if (!Array.isArray(raw)) return [];
129
+ const out: EnvTarget[] = [];
130
+ for (const t of raw) {
131
+ if (t && typeof t === "object" && "kind" in t && "name" in t) {
132
+ out.push({ kind: (t as EnvTarget).kind, name: (t as EnvTarget).name });
133
+ }
134
+ }
135
+ return out;
136
+ }
137
+
138
+ function serialize(v: EnvVarPublic): Record<string, unknown> {
139
+ return {
140
+ ...v,
141
+ createdAt: v.createdAt.toISOString(),
142
+ updatedAt: v.updatedAt.toISOString(),
143
+ };
144
+ }
@@ -31,11 +31,11 @@ export function v1EventRoutes(deps: V1EventDeps) {
31
31
  signalFilter,
32
32
  broadcastFilter,
33
33
  eventFilter,
34
- send(event: StationEvent) {
34
+ send(event: StationEvent, serializedData: string) {
35
35
  eventCounter++;
36
36
  stream.writeSSE({
37
37
  event: event.type,
38
- data: JSON.stringify(event.data),
38
+ data: serializedData,
39
39
  id: `evt_${eventCounter}`,
40
40
  });
41
41
  },
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import type { SignalRunner, SignalQueueAdapter, Run } from "station-signal";
2
+ import type { SignalRunner, SignalQueueAdapter, Run, RunStatus } from "station-signal";
3
3
  import type { LogBuffer } from "../../log-buffer.js";
4
4
  import type { LogStore } from "../../log-store.js";
5
5
 
@@ -19,42 +19,18 @@ export function v1RunRoutes(deps: V1RunDeps) {
19
19
  app.get("/runs", async (c) => {
20
20
  const status = c.req.query("status");
21
21
  const signalName = c.req.query("signalName");
22
- const limit = Math.min(parseInt(c.req.query("limit") ?? "50", 10) || 50, 200);
22
+ const limitRaw = parseInt(c.req.query("limit") ?? "50", 10);
23
+ const limit = Math.min(Number.isNaN(limitRaw) ? 50 : Math.max(limitRaw, 1), 200);
24
+ const offsetRaw = parseInt(c.req.query("offset") ?? "0", 10);
25
+ const offset = Number.isNaN(offsetRaw) ? 0 : Math.max(offsetRaw, 0);
26
+ const statuses = status ? ([status] as RunStatus[]) : undefined;
23
27
 
24
- let runs: Run[] = [];
28
+ // Filtering/ordering/limiting happen in the adapter, not in memory.
29
+ const runs: Run[] = signalName
30
+ ? await deps.signalAdapter.listRuns(signalName, { limit, offset, statuses })
31
+ : await deps.signalAdapter.listAllRuns({ limit, offset, statuses });
25
32
 
26
- if (signalName) {
27
- runs = await deps.signalAdapter.listRuns(signalName);
28
- } else if (deps.signalRunner) {
29
- const seen = new Set<string>();
30
- for (const { name } of deps.signalRunner.listRegistered()) {
31
- const signalRuns = await deps.signalAdapter.listRuns(name);
32
- for (const r of signalRuns) {
33
- if (!seen.has(r.id)) {
34
- seen.add(r.id);
35
- runs.push(r);
36
- }
37
- }
38
- }
39
- }
40
-
41
- if (status) {
42
- runs = runs.filter((r) => r.status === status);
43
- }
44
-
45
- runs.sort((a, b) => {
46
- const aTime = a.createdAt instanceof Date
47
- ? a.createdAt.getTime()
48
- : new Date(a.createdAt as unknown as string).getTime();
49
- const bTime = b.createdAt instanceof Date
50
- ? b.createdAt.getTime()
51
- : new Date(b.createdAt as unknown as string).getTime();
52
- return bTime - aTime;
53
- });
54
-
55
- runs = runs.slice(0, limit);
56
-
57
- return c.json({ data: runs.map(serializeRun), meta: { total: runs.length } });
33
+ return c.json({ data: runs.map(serializeRun), meta: { count: runs.length, limit, offset } });
58
34
  });
59
35
 
60
36
  app.get("/runs/:id", async (c) => {
package/src/server/sse.ts CHANGED
@@ -2,7 +2,8 @@ import type { StationEvent } from "./ws.js";
2
2
 
3
3
  export interface SSEClient {
4
4
  id: string;
5
- send(event: StationEvent): void;
5
+ /** `serializedData` is `JSON.stringify(event.data)`, computed once per broadcast. */
6
+ send(event: StationEvent, serializedData: string): void;
6
7
  close(): void;
7
8
  readonly signalFilter: Set<string> | null;
8
9
  readonly broadcastFilter: Set<string> | null;
@@ -25,9 +26,13 @@ export class SSEHub {
25
26
  }
26
27
 
27
28
  broadcast(event: StationEvent): void {
29
+ // Serialize once per event, not once per client — log-chunk events fan
30
+ // out to every connected dashboard.
31
+ let serialized: string | null = null;
28
32
  for (const client of this.clients.values()) {
29
33
  if (this.matchesFilter(client, event)) {
30
- client.send(event);
34
+ serialized ??= JSON.stringify(event.data);
35
+ client.send(event, serialized);
31
36
  }
32
37
  }
33
38
  }
@@ -2,12 +2,31 @@ import { isSignal } from "station-signal";
2
2
  import { isBroadcast } from "station-broadcast";
3
3
  import type { SignalSubscriber, Run, Step } from "station-signal";
4
4
  import type { BroadcastSubscriber, BroadcastRun, BroadcastNodeRun } from "station-broadcast";
5
+ import type { BeaconSubscriber, BeaconInstance, ExitReason } from "station-beacon";
5
6
  import type { WebSocketHub } from "./ws.js";
6
7
  import type { SSEHub } from "./sse.js";
7
8
  import type { LogBuffer } from "./log-buffer.js";
8
9
  import type { LogStore } from "./log-store.js";
9
10
  import { serializeZodSchema, type SignalMeta, type BroadcastMeta } from "./metadata.js";
10
11
 
12
+ /** LogStore/LogBuffer key under which a beacon's log lines are stored. */
13
+ export function beaconLogKey(beaconName: string): string {
14
+ return `beacon:${beaconName}`;
15
+ }
16
+
17
+ function serializeInstance(inst: BeaconInstance): Record<string, unknown> {
18
+ return {
19
+ ...inst,
20
+ startedAt: inst.startedAt?.toISOString(),
21
+ readyAt: inst.readyAt?.toISOString(),
22
+ lastHeartbeatAt: inst.lastHeartbeatAt?.toISOString(),
23
+ lastExitAt: inst.lastExitAt?.toISOString(),
24
+ nextRestartAt: inst.nextRestartAt?.toISOString(),
25
+ createdAt: inst.createdAt?.toISOString(),
26
+ updatedAt: inst.updatedAt?.toISOString(),
27
+ };
28
+ }
29
+
11
30
  function serializeRun(run: Run): Record<string, unknown> {
12
31
  return {
13
32
  ...run,
@@ -87,6 +106,7 @@ export class StationSignalSubscriber implements SignalSubscriber {
87
106
  maxConcurrency: sig.maxConcurrency ?? null,
88
107
  hasSteps: Array.isArray(sig.steps) && sig.steps.length > 0,
89
108
  stepNames: sig.steps?.map((s: { name: string }) => s.name) ?? [],
109
+ requiredEnv: Array.isArray(sig.requiredEnv) ? sig.requiredEnv : [],
90
110
  });
91
111
  break;
92
112
  }
@@ -286,3 +306,94 @@ export class StationBroadcastSubscriber implements BroadcastSubscriber {
286
306
  });
287
307
  }
288
308
  }
309
+
310
+ export class StationBeaconSubscriber implements BeaconSubscriber {
311
+ private logBuffer?: LogBuffer;
312
+ private logStore?: LogStore;
313
+ private sseHub?: SSEHub;
314
+ private discovered = new Map<string, { beaconName: string; filePath: string }>();
315
+
316
+ constructor(private hub: WebSocketHub, logBuffer?: LogBuffer, logStore?: LogStore) {
317
+ this.logBuffer = logBuffer;
318
+ this.logStore = logStore;
319
+ }
320
+
321
+ /** Attach an SSE hub so events are also pushed to SSE clients. */
322
+ setSSEHub(sseHub: SSEHub): void {
323
+ this.sseHub = sseHub;
324
+ }
325
+
326
+ private emit(type: string, data: Record<string, unknown>): void {
327
+ const event = { type, timestamp: new Date().toISOString(), data };
328
+ this.hub.broadcast(event);
329
+ this.sseHub?.broadcast(event);
330
+ }
331
+
332
+ private emitInstance(type: string, inst: BeaconInstance, extra?: Record<string, unknown>): void {
333
+ this.emit(type, { instance: serializeInstance(inst), ...extra });
334
+ }
335
+
336
+ onBeaconDiscovered(event: { beaconName: string; filePath: string }): void {
337
+ this.discovered.set(event.beaconName, event);
338
+ this.emit("beacon:discovered", event);
339
+ }
340
+
341
+ onBeaconStarting(event: { instance: BeaconInstance }): void {
342
+ this.emitInstance("beacon:starting", event.instance);
343
+ }
344
+
345
+ onBeaconStarted(event: { instance: BeaconInstance }): void {
346
+ this.emitInstance("beacon:started", event.instance);
347
+ }
348
+
349
+ onBeaconReady(event: { instance: BeaconInstance }): void {
350
+ this.emitInstance("beacon:ready", event.instance);
351
+ }
352
+
353
+ onBeaconHeartbeat(event: { instance: BeaconInstance }): void {
354
+ this.emitInstance("beacon:heartbeat", event.instance);
355
+ }
356
+
357
+ onBeaconExited(event: { instance: BeaconInstance; reason: ExitReason; code: number | null }): void {
358
+ this.emitInstance("beacon:exited", event.instance, { reason: event.reason, code: event.code });
359
+ }
360
+
361
+ onBeaconRestartScheduled(event: { instance: BeaconInstance; delayMs: number; nextRestartAt: Date }): void {
362
+ this.emitInstance("beacon:restart-scheduled", event.instance, {
363
+ delayMs: event.delayMs,
364
+ nextRestartAt: event.nextRestartAt.toISOString(),
365
+ });
366
+ }
367
+
368
+ onBeaconStopped(event: { instance: BeaconInstance }): void {
369
+ this.emitInstance("beacon:stopped", event.instance);
370
+ }
371
+
372
+ onBeaconErrored(event: { instance: BeaconInstance; error?: string }): void {
373
+ this.emitInstance("beacon:errored", event.instance, { error: event.error ?? "" });
374
+ }
375
+
376
+ onBeaconStalled(event: { instance: BeaconInstance }): void {
377
+ this.emitInstance("beacon:stalled", event.instance);
378
+ }
379
+
380
+ onBeaconLog(event: { instance: BeaconInstance; level: "log" | "stdout" | "stderr"; message: string }): void {
381
+ const timestamp = new Date().toISOString();
382
+ const entry = {
383
+ runId: beaconLogKey(event.instance.beaconName),
384
+ signalName: event.instance.beaconName,
385
+ level: event.level === "stderr" ? ("stderr" as const) : ("stdout" as const),
386
+ message: event.message,
387
+ timestamp,
388
+ };
389
+ this.logBuffer?.add(entry);
390
+ this.logStore?.add(entry);
391
+ this.emit("beacon:log", {
392
+ beaconName: event.instance.beaconName,
393
+ incarnation: event.instance.incarnation,
394
+ level: event.level,
395
+ message: event.message,
396
+ timestamp,
397
+ });
398
+ }
399
+ }
package/src/server/ws.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { WebSocketServer, WebSocket } from "ws";
2
- import type { Server } from "node:http";
2
+ import type { IncomingMessage, Server } from "node:http";
3
3
 
4
4
  export interface StationEvent {
5
5
  type: string;
@@ -7,14 +7,25 @@ export interface StationEvent {
7
7
  data: Record<string, unknown>;
8
8
  }
9
9
 
10
+ /**
11
+ * Async guard run before completing a WebSocket upgrade. Return false to
12
+ * reject with 401. Event payloads include run inputs/outputs and full job
13
+ * logs, so the stream must enforce the same auth as the HTTP API.
14
+ */
15
+ export type WsVerifyFn = (req: IncomingMessage) => Promise<boolean> | boolean;
16
+
10
17
  export class WebSocketHub {
11
18
  private wss: WebSocketServer | null = null;
12
19
  private clients = new Set<WebSocket>();
13
20
 
14
- attach(server: Server): void {
15
- this.wss = new WebSocketServer({ server, path: "/api/events" });
21
+ attach(server: Server, verify?: WsVerifyFn): void {
22
+ // noServer + manual upgrade handling: the `ws` path-matching shortcut
23
+ // would complete the handshake before any auth check could run (Hono
24
+ // middleware never sees upgrade requests).
25
+ const wss = new WebSocketServer({ noServer: true });
26
+ this.wss = wss;
16
27
 
17
- this.wss.on("connection", (ws) => {
28
+ wss.on("connection", (ws: WebSocket) => {
18
29
  this.clients.add(ws);
19
30
  ws.on("close", () => {
20
31
  this.clients.delete(ws);
@@ -23,6 +34,29 @@ export class WebSocketHub {
23
34
  this.clients.delete(ws);
24
35
  });
25
36
  });
37
+
38
+ server.on("upgrade", async (req, socket, head) => {
39
+ const pathname = new URL(req.url ?? "", "http://localhost").pathname;
40
+ if (pathname !== "/api/events") {
41
+ socket.destroy();
42
+ return;
43
+ }
44
+
45
+ try {
46
+ if (verify && !(await verify(req))) {
47
+ socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
48
+ socket.destroy();
49
+ return;
50
+ }
51
+ } catch {
52
+ socket.destroy();
53
+ return;
54
+ }
55
+
56
+ wss.handleUpgrade(req, socket, head, (ws) => {
57
+ wss.emit("connection", ws, req);
58
+ });
59
+ });
26
60
  }
27
61
 
28
62
  get clientCount(): number {
@@ -1 +0,0 @@
1
- self.__BUILD_MANIFEST=function(e,r,t,_){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:9,errorRate:1e-4,numBits:173,numHashes:14,bitArray:[0,0,r,1,1,e,r,e,r,e,e,r,e,e,e,e,e,e,e,e,e,e,e,e,e,r,r,e,e,e,r,e,e,r,r,r,e,e,r,e,e,e,e,r,e,e,r,e,e,r,r,e,r,r,e,r,e,r,r,r,r,e,e,e,r,r,r,e,r,e,e,e,e,e,e,r,r,e,e,r,e,r,r,e,r,r,e,r,r,e,e,r,r,r,r,r,e,e,e,e,r,r,r,r,e,e,r,e,e,e,e,r,e,r,e,e,e,r,e,r,r,e,e,e,e,e,r,e,r,r,r,e,e,r,r,r,r,r,e,r,r,e,e,e,r,r,r,e,r,r,e,r,r,e,r,e,e,e,r,e,e,r,r,r,e,e,e,e,e,r,r,r,e]},__routerFilterDynamic:{numItems:5,errorRate:1e-4,numBits:96,numHashes:14,bitArray:[e,e,e,r,e,r,e,e,r,r,e,r,r,e,r,r,r,e,e,e,e,r,r,e,e,r,e,e,e,r,r,r,e,r,r,e,r,e,r,r,r,r,e,e,r,r,e,e,r,e,e,e,e,r,r,e,r,e,e,e,e,e,e,e,e,e,e,e,r,e,e,r,e,e,r,e,r,e,e,r,r,r,r,e,r,r,r,r,e,r,r,e,e,r,e,r]},"/_error":["static/chunks/pages/_error-273a093c18b5ed0f.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4,14),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[285],{593:(e,t,n)=>{async function a(e,t){let n=await fetch("".concat("","/api").concat(e),{...t,credentials:"include",headers:{"Content-Type":"application/json",...null==t?void 0:t.headers}});if(!n.ok)throw Error((await n.json().catch(()=>({error:"unknown",message:"Request failed."}))).message);return n.json()}async function o(){let e=await fetch("".concat("","/api/auth/check"),{credentials:"include"});return(await e.json()).data}async function r(e,t){return(await fetch("".concat("","/api/auth/login"),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:t})})).ok}async function s(){await fetch("".concat("","/api/auth/logout"),{method:"POST",credentials:"include"})}function i(){return{getHealth:()=>a("/health"),getSignals:()=>a("/signals"),getScheduledSignals:()=>a("/signals/scheduled"),getSignal:e=>a("/signals/".concat(encodeURIComponent(e))),getSignalRuns:e=>a("/signals/".concat(encodeURIComponent(e),"/runs")),triggerSignal:(e,t)=>a("/signals/".concat(encodeURIComponent(e),"/trigger"),{method:"POST",body:JSON.stringify({input:null!=t?t:{}})}),getRuns:e=>{let t=new URLSearchParams;(null==e?void 0:e.status)&&t.set("status",e.status),(null==e?void 0:e.signalName)&&t.set("signalName",e.signalName);let n=t.toString();return a("/runs".concat(n?"?".concat(n):""))},getRunStats:()=>a("/runs/stats"),getRun:e=>a("/runs/".concat(e)),getRunSteps:e=>a("/runs/".concat(e,"/steps")),getRunLogs:e=>a("/runs/".concat(e,"/logs")),cancelRun:e=>a("/runs/".concat(e,"/cancel"),{method:"POST"}),rerunRun:e=>a("/runs/".concat(e,"/rerun"),{method:"POST"}),retryRun:e=>a("/runs/".concat(e,"/retry"),{method:"POST"}),getBroadcasts:()=>a("/broadcasts"),getBroadcast:e=>a("/broadcasts/".concat(encodeURIComponent(e))),triggerBroadcast:(e,t)=>a("/broadcasts/".concat(encodeURIComponent(e),"/trigger"),{method:"POST",body:JSON.stringify({input:null!=t?t:{}})}),getBroadcastRuns:e=>a("/broadcasts/".concat(encodeURIComponent(e),"/runs")),getBroadcastRun:e=>a("/broadcast-runs/".concat(e)),getBroadcastRunNodes:e=>a("/broadcast-runs/".concat(e,"/nodes")),getBroadcastRunLogs:e=>a("/broadcast-runs/".concat(e,"/logs")),cancelBroadcastRun:e=>a("/broadcast-runs/".concat(e,"/cancel"),{method:"POST"}),rerunBroadcastRun:e=>a("/broadcast-runs/".concat(e,"/rerun"),{method:"POST"}),getBroadcastDefinitions:()=>a("/v1/broadcast-definitions"),getBroadcastDefinition:e=>a("/v1/broadcast-definitions/".concat(encodeURIComponent(e))),getBroadcastDefinitionVersions:e=>a("/v1/broadcast-definitions/".concat(encodeURIComponent(e),"/versions")),saveBroadcastDefinition:e=>a("/v1/broadcast-definitions",{method:"POST",body:JSON.stringify(e)}),validateBroadcastDefinition:e=>a("/v1/broadcast-definitions/validate",{method:"POST",body:JSON.stringify(e)}),deleteBroadcastDefinition:e=>a("/v1/broadcast-definitions/".concat(encodeURIComponent(e)),{method:"DELETE"}),triggerDynamicBroadcast:(e,t)=>a("/v1/trigger-dynamic-broadcast",{method:"POST",body:JSON.stringify({broadcastName:e,input:null!=t?t:{}})}),getSchedules:e=>{let t=new URLSearchParams;(null==e?void 0:e.kind)&&t.set("kind",e.kind),(null==e?void 0:e.enabled)!==void 0&&t.set("enabled",String(e.enabled));let n=t.toString();return a("/v1/schedules".concat(n?"?".concat(n):""))},getSchedule:e=>a("/v1/schedules/".concat(e)),createSchedule:e=>a("/v1/schedules",{method:"POST",body:JSON.stringify(e)}),updateSchedule:(e,t)=>a("/v1/schedules/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),deleteSchedule:e=>a("/v1/schedules/".concat(e),{method:"DELETE"}),previewSchedule:function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:5;return a("/v1/schedules/".concat(e,"/preview"),{method:"POST",body:JSON.stringify({count:t})})},evaluateExpression:(e,t)=>a("/v1/expressions/evaluate",{method:"POST",body:JSON.stringify({node:e,context:t})}),validateExpression:(e,t)=>a("/v1/expressions/validate",{method:"POST",body:JSON.stringify({node:e,schemaContext:t})}),parseExpression:e=>a("/v1/expressions/parse",{method:"POST",body:JSON.stringify({source:e})}),getApiKeys:()=>a("/v1/keys"),createApiKey:(e,t)=>a("/v1/keys",{method:"POST",body:JSON.stringify({name:e,scopes:t})}),revokeApiKey:e=>a("/v1/keys/".concat(e),{method:"DELETE"})}}n.d(t,{T5:()=>o,gf:()=>i,iD:()=>r,ri:()=>s})},4736:(e,t,n)=>{var a=n(1504);n.o(a,"useParams")&&n.d(t,{useParams:function(){return a.useParams}}),n.o(a,"usePathname")&&n.d(t,{usePathname:function(){return a.usePathname}}),n.o(a,"useRouter")&&n.d(t,{useRouter:function(){return a.useRouter}})},7646:(e,t,n)=>{n.d(t,{Hc:()=>o,iQ:()=>s,rT:()=>r});var a=n(5271);let o=(0,a.createContext)({segments:[],activeSection:null,setSegments:()=>{},setActiveSection:()=>{}});function r(e,t){let n=(0,a.useContext)(o);(0,a.useEffect)(()=>{n.setSegments(e),n.setActiveSection(t)},[JSON.stringify(e),t])}function s(){return(0,a.useContext)(o)}},9129:(e,t,n)=>{n.d(t,{D:()=>s,ThemeProvider:()=>c});var a=n(8111),o=n(5271);let r=(0,o.createContext)({theme:"light",toggle:()=>{}});function s(){return(0,o.useContext)(r)}function i(){let e=localStorage.getItem("station-theme");return"light"===e||"dark"===e?e:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function c(e){let{children:t}=e,[n,s]=(0,o.useState)(i);(0,o.useEffect)(()=>{document.documentElement.setAttribute("data-theme",n),localStorage.setItem("station-theme",n)},[n]);let c=(0,o.useCallback)(()=>{s(e=>"light"===e?"dark":"light")},[]);return(0,a.jsx)(r.Provider,{value:{theme:n,toggle:c},children:t})}},9711:(e,t,n)=>{n.d(t,{C0:()=>d,QK:()=>u,WZ:()=>l});var a=n(8111),o=n(9129);let r={completed:{bar:"#4A6741",fill:"rgba(74, 103, 65, 0.06)",stroke:"#4A6741",text:"#4A6741"},running:{bar:"#6B9962",fill:"rgba(107, 153, 98, 0.08)",stroke:"#6B9962",text:"#4A6741"},failed:{bar:"#8B5A2B",fill:"rgba(139, 90, 43, 0.06)",stroke:"#8B5A2B",text:"#8B5A2B"},pending:{bar:"#D4CEBF",fill:"#F9F7F3",stroke:"#D4CEBF",text:"#8A8A8E"},cancelled:{bar:"#AEAEB2",fill:"rgba(139, 90, 43, 0.03)",stroke:"#AEAEB2",text:"#8A8A8E"},skipped:{bar:"#AEAEB2",fill:"transparent",stroke:"#AEAEB2",text:"#8A8A8E"}},s={completed:{bar:"#6B9962",fill:"rgba(107, 153, 98, 0.12)",stroke:"#6B9962",text:"#8BB882"},running:{bar:"#8BB882",fill:"rgba(139, 185, 130, 0.1)",stroke:"#8BB882",text:"#8BB882"},failed:{bar:"#C4834A",fill:"rgba(196, 131, 74, 0.1)",stroke:"#C4834A",text:"#D4975C"},pending:{bar:"#4A4A4C",fill:"#252527",stroke:"#4A4A4C",text:"#8A8A8E"},cancelled:{bar:"#6A6A6E",fill:"rgba(60, 60, 62, 0.3)",stroke:"#6A6A6E",text:"#8A8A8E"},skipped:{bar:"#6A6A6E",fill:"transparent",stroke:"#6A6A6E",text:"#8A8A8E"}},i={bar:"#4A6741",fill:"#FFFFFF",stroke:"#D4CEBF",text:"#1C1C1E"},c={bar:"#6B9962",fill:"#222224",stroke:"#4A4A4C",text:"#E8E4DC"};function l(){let{theme:e}=(0,o.D)();return"dark"===e?s:r}function d(e){var t,n;if(0===e.length)return[];let a=new Map;for(let t of e)a.set(t.name,t);let o=new Map;for(let t of e)!function e(t,n){if(o.has(t))return o.get(t);if(n.has(t))return 0;n.add(t);let r=a.get(t);if(!r||0===r.dependsOn.length)return o.set(t,0),0;let s=0;for(let t of r.dependsOn)a.has(t)&&(s=Math.max(s,e(t,n)+1));return o.set(t,s),s}(t.name,new Set);let r=new Map;for(let n of e){let e=null!=(t=o.get(n.name))?t:0;r.has(e)||r.set(e,[]),r.get(e).push(n)}let s=Math.max(...Array.from(r.keys()),0),i=[];for(let e=0;e<=s;e++)i.push(null!=(n=r.get(e))?n:[]);return i.filter(e=>e.length>0)}function u(e){let{nodes:t,onNodeClick:n,selectedNode:l,compact:u}=e,{theme:g}=(0,o.D)(),f="dark"===g,h=f?s:r,m=f?c:i;if(0===t.length)return(0,a.jsx)("div",{className:"empty-state",children:(0,a.jsx)("p",{className:"empty-state-text",children:"No nodes."})});let p=u?140:184,x=u?36:60,{positions:y,svgWidth:v,svgHeight:S}=function(e,t){let n=new Map;if(0===e.length)return{positions:n,svgWidth:0,svgHeight:0};let{nodeWidth:a,nodeHeight:o,gapX:r,gapY:s,paddingX:i,paddingY:c}=t,l=Math.max(1,...e.map(e=>e.length))*(a+r)-r+2*i,d=e.length*(o+s)-s+2*c;for(let t=0;t<e.length;t++){let i=e[t],d=(l-(i.length*(a+r)-r))/2,u=c+t*(o+s);for(let e=0;e<i.length;e++){let t=d+e*(a+r);n.set(i[e].name,{x:t,y:u,node:i[e]})}}return{positions:n,svgWidth:l,svgHeight:d}}(d(t),{nodeWidth:p,nodeHeight:x,gapX:u?32:48,gapY:u?28:40,paddingX:u?20:32,paddingY:u?16:24}),b=t.some(e=>void 0!==e.status),A=new Map;for(let e of t)e.status&&A.set(e.name,e.status);let k=[];for(let e of t)for(let t of e.dependsOn)y.has(t)&&k.push({from:t,to:e.name});return(0,a.jsx)("div",{className:"dag-container",children:(0,a.jsxs)("svg",{width:v,height:S,viewBox:"0 0 ".concat(v," ").concat(S),style:{display:"block",margin:"0 auto"},children:[(0,a.jsxs)("defs",{children:[(0,a.jsx)("marker",{id:"dag-arrow",viewBox:"0 0 6 6",refX:"6",refY:"3",markerWidth:"6",markerHeight:"6",orient:"auto-start-reverse",children:(0,a.jsx)("path",{d:"M 0 0 L 6 3 L 0 6 z",fill:f?"#4A4A4C":"#D4CEBF"})}),(0,a.jsx)("marker",{id:"dag-arrow-active",viewBox:"0 0 6 6",refX:"6",refY:"3",markerWidth:"6",markerHeight:"6",orient:"auto-start-reverse",children:(0,a.jsx)("path",{d:"M 0 0 L 6 3 L 0 6 z",fill:f?"#6B9962":"#4A6741"})})]}),k.map(e=>{let t=y.get(e.from),n=y.get(e.to);if(!t||!n)return null;let o="completed"===A.get(e.from),r="skipped"===A.get(e.to),s=t.x+p/2,i=t.y+x,c=n.x+p/2,l=n.y,d=(i+l)/2,u="M ".concat(s," ").concat(i," C ").concat(s," ").concat(d,", ").concat(c," ").concat(d,", ").concat(c," ").concat(l);return(0,a.jsx)("path",{d:u,className:"dag-edge".concat(o?" dag-edge--active":""),strokeDasharray:r?"4 3":void 0,markerEnd:"url(#".concat(o?"dag-arrow-active":"dag-arrow",")")},"".concat(e.from,"->").concat(e.to))}),Array.from(y.values()).map(e=>{var t;let{x:o,y:r,node:s}=e,i=void 0!==s.status&&null!==s.status,c=i&&null!=(t=h[s.status])?t:m,d="skipped"===s.status,g=l===s.name,y="running"===s.status,v=function(e,t){if(!e)return null;let n=new Date(e).getTime(),a=(t?new Date(t).getTime():Date.now())-n;return a<1e3?"".concat(a,"ms"):a<6e4?"".concat((a/1e3).toFixed(1),"s"):"".concat((a/6e4).toFixed(1),"m")}(s.startedAt,s.completedAt),S=u?14:18,A=s.name.length>S?s.name.slice(0,S-1)+"…":s.name,k=s.signalName.length>22?s.signalName.slice(0,21)+"…":s.signalName;return(0,a.jsxs)("g",{className:"dag-node".concat(g?" dag-node--selected":""),onClick:()=>null==n?void 0:n(s.name),style:{cursor:n?"pointer":"default"},role:n?"button":void 0,tabIndex:n?0:void 0,"aria-label":"".concat(s.name).concat(i?", "+s.status:""),onKeyDown:e=>{n&&("Enter"===e.key||" "===e.key)&&(e.preventDefault(),n(s.name))},children:[y&&(0,a.jsx)("rect",{x:o-3,y:r-3,width:p+6,height:x+6,rx:7,fill:"none",stroke:f?"#8BB882":"#6B9962",strokeWidth:2,children:(0,a.jsx)("animate",{attributeName:"opacity",values:"0;0.5;0",dur:"2.4s",repeatCount:"indefinite"})}),(0,a.jsx)("rect",{x:o,y:r,width:p,height:x,rx:4,fill:c.fill,stroke:c.stroke,strokeWidth:g?2.5:1.5,strokeDasharray:d?"4 3":void 0}),(0,a.jsx)("rect",{x:o,y:r,width:p,height:4,rx:4,fill:c.bar}),(0,a.jsx)("rect",{x:o,y:r+2,width:p,height:2,fill:c.bar}),(0,a.jsx)("text",{x:o+8,y:u?r+x/2+2:r+4+16,fill:c.text,dominantBaseline:"middle",style:{fontSize:u?"10px":"11px",fontFamily:"var(--font-mono)",fontWeight:500},children:A}),u&&b&&i&&(0,a.jsx)("text",{x:o+p-8,y:r+x/2+2,fill:c.text,opacity:.5,dominantBaseline:"middle",textAnchor:"end",style:{fontSize:"8px",fontFamily:"var(--font-mono)",textTransform:"uppercase",letterSpacing:"0.05em"},children:null!=v?v:s.status}),!u&&(0,a.jsxs)(a.Fragment,{children:[b&&v&&(0,a.jsx)("text",{x:o+p-10,y:r+4+16,fill:c.text,opacity:.6,dominantBaseline:"middle",textAnchor:"end",style:{fontSize:"9px",fontFamily:"var(--font-mono)"},children:v}),(0,a.jsx)("text",{x:o+10,y:r+x-10,fill:c.text,opacity:.45,dominantBaseline:"middle",style:{fontSize:"9px",fontFamily:"var(--font-mono)"},children:k}),b&&i&&(0,a.jsx)("text",{x:o+p-10,y:r+x-10,fill:c.text,opacity:.5,dominantBaseline:"middle",textAnchor:"end",style:{fontSize:"8px",fontFamily:"var(--font-mono)",textTransform:"uppercase",letterSpacing:"0.05em"},children:s.status})]})]},s.name)})]})})}}}]);
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[561],{593:(e,t,n)=>{async function a(e,t){let n=await fetch("".concat("","/api").concat(e),{...t,credentials:"include",headers:{"Content-Type":"application/json",...null==t?void 0:t.headers}});if(!n.ok)throw Error((await n.json().catch(()=>({error:"unknown",message:"Request failed."}))).message);return n.json()}async function o(){let e=await fetch("".concat("","/api/auth/check"),{credentials:"include"});return(await e.json()).data}async function s(e,t){return(await fetch("".concat("","/api/auth/login"),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:t})})).ok}async function r(){await fetch("".concat("","/api/auth/logout"),{method:"POST",credentials:"include"})}function i(){return{getHealth:()=>a("/health"),getSignals:()=>a("/signals"),getScheduledSignals:()=>a("/signals/scheduled"),getSignal:e=>a("/signals/".concat(encodeURIComponent(e))),getSignalRuns:e=>a("/signals/".concat(encodeURIComponent(e),"/runs")),triggerSignal:(e,t)=>a("/signals/".concat(encodeURIComponent(e),"/trigger"),{method:"POST",body:JSON.stringify({input:null!=t?t:{}})}),getRuns:e=>{let t=new URLSearchParams;(null==e?void 0:e.status)&&t.set("status",e.status),(null==e?void 0:e.signalName)&&t.set("signalName",e.signalName);let n=t.toString();return a("/runs".concat(n?"?".concat(n):""))},getRunStats:()=>a("/runs/stats"),getRun:e=>a("/runs/".concat(e)),getRunSteps:e=>a("/runs/".concat(e,"/steps")),getRunLogs:e=>a("/runs/".concat(e,"/logs")),cancelRun:e=>a("/runs/".concat(e,"/cancel"),{method:"POST"}),rerunRun:e=>a("/runs/".concat(e,"/rerun"),{method:"POST"}),retryRun:e=>a("/runs/".concat(e,"/retry"),{method:"POST"}),getBroadcasts:()=>a("/broadcasts"),getBroadcast:e=>a("/broadcasts/".concat(encodeURIComponent(e))),triggerBroadcast:(e,t)=>a("/broadcasts/".concat(encodeURIComponent(e),"/trigger"),{method:"POST",body:JSON.stringify({input:null!=t?t:{}})}),getBroadcastRuns:e=>a("/broadcasts/".concat(encodeURIComponent(e),"/runs")),getBroadcastRun:e=>a("/broadcast-runs/".concat(e)),getBroadcastRunNodes:e=>a("/broadcast-runs/".concat(e,"/nodes")),getBroadcastRunLogs:e=>a("/broadcast-runs/".concat(e,"/logs")),cancelBroadcastRun:e=>a("/broadcast-runs/".concat(e,"/cancel"),{method:"POST"}),rerunBroadcastRun:e=>a("/broadcast-runs/".concat(e,"/rerun"),{method:"POST"}),getBroadcastDefinitions:()=>a("/v1/broadcast-definitions"),getBroadcastDefinition:e=>a("/v1/broadcast-definitions/".concat(encodeURIComponent(e))),getBroadcastDefinitionVersions:e=>a("/v1/broadcast-definitions/".concat(encodeURIComponent(e),"/versions")),saveBroadcastDefinition:e=>a("/v1/broadcast-definitions",{method:"POST",body:JSON.stringify(e)}),validateBroadcastDefinition:e=>a("/v1/broadcast-definitions/validate",{method:"POST",body:JSON.stringify(e)}),deleteBroadcastDefinition:e=>a("/v1/broadcast-definitions/".concat(encodeURIComponent(e)),{method:"DELETE"}),triggerDynamicBroadcast:(e,t)=>a("/v1/trigger-dynamic-broadcast",{method:"POST",body:JSON.stringify({broadcastName:e,input:null!=t?t:{}})}),getSchedules:e=>{let t=new URLSearchParams;(null==e?void 0:e.kind)&&t.set("kind",e.kind),(null==e?void 0:e.enabled)!==void 0&&t.set("enabled",String(e.enabled));let n=t.toString();return a("/v1/schedules".concat(n?"?".concat(n):""))},getSchedule:e=>a("/v1/schedules/".concat(e)),createSchedule:e=>a("/v1/schedules",{method:"POST",body:JSON.stringify(e)}),updateSchedule:(e,t)=>a("/v1/schedules/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),deleteSchedule:e=>a("/v1/schedules/".concat(e),{method:"DELETE"}),previewSchedule:function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:5;return a("/v1/schedules/".concat(e,"/preview"),{method:"POST",body:JSON.stringify({count:t})})},evaluateExpression:(e,t)=>a("/v1/expressions/evaluate",{method:"POST",body:JSON.stringify({node:e,context:t})}),validateExpression:(e,t)=>a("/v1/expressions/validate",{method:"POST",body:JSON.stringify({node:e,schemaContext:t})}),parseExpression:e=>a("/v1/expressions/parse",{method:"POST",body:JSON.stringify({source:e})}),getApiKeys:()=>a("/v1/keys"),createApiKey:(e,t)=>a("/v1/keys",{method:"POST",body:JSON.stringify({name:e,scopes:t})}),revokeApiKey:e=>a("/v1/keys/".concat(e),{method:"DELETE"})}}n.d(t,{T5:()=>o,gf:()=>i,iD:()=>s,ri:()=>r})},4736:(e,t,n)=>{var a=n(1504);n.o(a,"useParams")&&n.d(t,{useParams:function(){return a.useParams}}),n.o(a,"usePathname")&&n.d(t,{usePathname:function(){return a.usePathname}}),n.o(a,"useRouter")&&n.d(t,{useRouter:function(){return a.useRouter}})},6990:(e,t,n)=>{n.d(t,{F:()=>s});var a=n(8111),o=n(5271);function s(e){let{title:t="API equivalent",snippets:n}=e,[s,r]=(0,o.useState)(!1),[i,c]=(0,o.useState)(null),[l,d]=(0,o.useState)(null);if(0===n.length)return null;async function u(e,t){try{var n;if(null==(n=navigator.clipboard)?void 0:n.writeText){await navigator.clipboard.writeText(e),c(t),setTimeout(()=>c(e=>e===t?null:e),1500);return}throw Error("clipboard API unavailable")}catch(n){try{let n=document.createElement("textarea");n.value=e,n.style.position="fixed",n.style.left="-9999px",document.body.appendChild(n),n.select();let a=document.execCommand("copy");if(document.body.removeChild(n),a)c(t),setTimeout(()=>c(e=>e===t?null:e),1500);else throw Error("execCommand copy returned false")}catch(e){d(t),setTimeout(()=>d(e=>e===t?null:e),2e3)}}}return(0,a.jsxs)("details",{open:s,onToggle:e=>r(e.target.open),style:{marginTop:"1.5rem",border:"1px solid var(--border)",borderRadius:"4px",background:"var(--surface)"},children:[(0,a.jsxs)("summary",{style:{padding:"0.5rem 0.75rem",fontSize:"0.75rem",fontFamily:"var(--mono-font, monospace)",color:"var(--muted)",textTransform:"uppercase",letterSpacing:"0.05em",cursor:"pointer",listStyle:"none"},children:["▸ ",t]}),(0,a.jsx)("div",{style:{padding:"0.5rem 0.75rem 0.75rem"},children:n.map((e,t)=>{let o=function(e){var t;let n=[];if(e.query)for(let[t,a]of Object.entries(e.query))void 0!==a&&n.push("".concat(encodeURIComponent(t),"=").concat(encodeURIComponent(String(a))));let a="".concat(e.path).concat(n.length?"?".concat(n.join("&")):""),o=(null==(t=window.location)?void 0:t.origin)?window.location.origin:"https://your-station.example.com",s=[];if(s.push("curl -X ".concat(e.method," \\")),s.push(" ".concat(o).concat(a," \\")),s.push(" -H 'Authorization: Bearer sk_live_…' \\"),void 0!==e.body){s.push(" -H 'Content-Type: application/json' \\");let t="string"==typeof e.body?e.body:JSON.stringify(e.body,null,2);s.push(" -d ".concat(JSON.stringify(t)))}else s[s.length-1]=s[s.length-1].replace(/ \\$/,"");return s.join("\n")}(e);return(0,a.jsxs)("div",{style:{marginBottom:t===n.length-1?0:"0.75rem"},children:[(0,a.jsxs)("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"0.25rem"},children:[(0,a.jsx)("div",{className:"mono",style:{fontSize:"0.75rem",color:"var(--muted)"},children:e.label}),(0,a.jsx)("button",{type:"button",className:"btn btn--sm",onClick:()=>u(o,t),title:l===t?"Copy failed — select the text manually":"",children:i===t?"Copied":l===t?"Copy failed":"Copy"})]}),(0,a.jsx)("pre",{className:"mono",style:{margin:0,padding:"0.5rem 0.75rem",background:"var(--bg, #fff)",border:"1px solid var(--border)",borderRadius:"4px",fontSize:"0.75rem",overflowX:"auto",whiteSpace:"pre"},children:o})]},t)})})]})}},7388:(e,t,n)=>{n.d(t,{Q:()=>r});var a=n(8111),o=n(5271),s=n(593);function r(e){let{value:t,onChange:n,locked:r}=e,d=(0,s.gf)(),[u,m]=(0,o.useState)([]),[g,h]=(0,o.useState)([]),[p,y]=(0,o.useState)([]);(0,o.useEffect)(()=>{Promise.all([d.getSignals().catch(()=>({data:[]})),d.getBroadcasts().catch(()=>({data:[]})),d.getBroadcastDefinitions().catch(()=>({data:[]}))]).then(e=>{let[t,n,a]=e;m(t.data.map(e=>e.name)),h(n.data.map(e=>e.name)),y(a.data.map(e=>e.name))})},[]);let f="signal"===t.kind?u:"broadcast-static"===t.kind?g:p;return(0,a.jsxs)("div",{style:{display:"grid",gap:"1rem",maxWidth:"640px"},children:[(0,a.jsx)(i,{label:"Kind",children:(0,a.jsxs)("select",{value:t.kind,disabled:r,onChange:e=>n({...t,kind:e.target.value,target:""}),className:"mono",style:l,children:[(0,a.jsx)("option",{value:"signal",children:"signal"}),(0,a.jsx)("option",{value:"broadcast-static",children:"broadcast-static"}),(0,a.jsx)("option",{value:"broadcast-dynamic",children:"broadcast-dynamic"})]})}),(0,a.jsxs)(i,{label:"Target",hint:"".concat(f.length," available"),children:[r?(0,a.jsx)("input",{className:"mono",value:t.target,readOnly:!0,style:c}):(0,a.jsx)("input",{className:"mono",value:t.target,list:"schedule-targets-".concat(t.kind),onChange:e=>n({...t,target:e.target.value}),placeholder:"signal or broadcast name",style:c}),(0,a.jsx)("datalist",{id:"schedule-targets-".concat(t.kind),children:f.map(e=>(0,a.jsx)("option",{value:e},e))})]}),(0,a.jsx)(i,{label:"Interval",hint:'e.g. "30s", "5m", "1h", "1d"',children:(0,a.jsx)("input",{className:"mono",value:t.interval,onChange:e=>n({...t,interval:e.target.value}),placeholder:"5m",style:c})}),(0,a.jsx)(i,{label:"Input (JSON)",children:(0,a.jsx)("textarea",{className:"mono",value:t.input,onChange:e=>n({...t,input:e.target.value}),rows:6,spellCheck:!1,style:{...c,fontFamily:"var(--mono-font, monospace)",fontSize:"0.8125rem"}})}),(0,a.jsxs)("label",{style:{display:"flex",alignItems:"center",gap:"0.5rem"},children:[(0,a.jsx)("input",{type:"checkbox",checked:t.enabled,onChange:e=>n({...t,enabled:e.target.checked})}),(0,a.jsx)("span",{className:"mono",style:{fontSize:"0.875rem"},children:"Enabled"})]})]})}function i(e){let{label:t,hint:n,children:o}=e;return(0,a.jsxs)("div",{children:[(0,a.jsxs)("div",{style:{display:"flex",alignItems:"baseline",justifyContent:"space-between",marginBottom:"0.25rem"},children:[(0,a.jsx)("label",{className:"mono",style:{fontSize:"0.75rem",color:"var(--muted)"},children:t}),n&&(0,a.jsx)("span",{className:"mono",style:{fontSize:"0.6875rem",color:"var(--muted)"},children:n})]}),o]})}let c={width:"100%",padding:"0.5rem 0.625rem",border:"1px solid var(--border)",borderRadius:"4px",background:"var(--surface)",color:"var(--text)",fontSize:"0.875rem"},l={...c,appearance:"auto"}},7646:(e,t,n)=>{n.d(t,{Hc:()=>o,iQ:()=>r,rT:()=>s});var a=n(5271);let o=(0,a.createContext)({segments:[],activeSection:null,setSegments:()=>{},setActiveSection:()=>{}});function s(e,t){let n=(0,a.useContext)(o);(0,a.useEffect)(()=>{n.setSegments(e),n.setActiveSection(t)},[JSON.stringify(e),t])}function r(){return(0,a.useContext)(o)}}}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[719],{593:(e,t,n)=>{"use strict";async function o(e,t){let n=await fetch("".concat("","/api").concat(e),{...t,credentials:"include",headers:{"Content-Type":"application/json",...null==t?void 0:t.headers}});if(!n.ok)throw Error((await n.json().catch(()=>({error:"unknown",message:"Request failed."}))).message);return n.json()}async function a(){let e=await fetch("".concat("","/api/auth/check"),{credentials:"include"});return(await e.json()).data}async function s(e,t){return(await fetch("".concat("","/api/auth/login"),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:t})})).ok}async function r(){await fetch("".concat("","/api/auth/logout"),{method:"POST",credentials:"include"})}function i(){return{getHealth:()=>o("/health"),getSignals:()=>o("/signals"),getScheduledSignals:()=>o("/signals/scheduled"),getSignal:e=>o("/signals/".concat(encodeURIComponent(e))),getSignalRuns:e=>o("/signals/".concat(encodeURIComponent(e),"/runs")),triggerSignal:(e,t)=>o("/signals/".concat(encodeURIComponent(e),"/trigger"),{method:"POST",body:JSON.stringify({input:null!=t?t:{}})}),getRuns:e=>{let t=new URLSearchParams;(null==e?void 0:e.status)&&t.set("status",e.status),(null==e?void 0:e.signalName)&&t.set("signalName",e.signalName);let n=t.toString();return o("/runs".concat(n?"?".concat(n):""))},getRunStats:()=>o("/runs/stats"),getRun:e=>o("/runs/".concat(e)),getRunSteps:e=>o("/runs/".concat(e,"/steps")),getRunLogs:e=>o("/runs/".concat(e,"/logs")),cancelRun:e=>o("/runs/".concat(e,"/cancel"),{method:"POST"}),rerunRun:e=>o("/runs/".concat(e,"/rerun"),{method:"POST"}),retryRun:e=>o("/runs/".concat(e,"/retry"),{method:"POST"}),getBroadcasts:()=>o("/broadcasts"),getBroadcast:e=>o("/broadcasts/".concat(encodeURIComponent(e))),triggerBroadcast:(e,t)=>o("/broadcasts/".concat(encodeURIComponent(e),"/trigger"),{method:"POST",body:JSON.stringify({input:null!=t?t:{}})}),getBroadcastRuns:e=>o("/broadcasts/".concat(encodeURIComponent(e),"/runs")),getBroadcastRun:e=>o("/broadcast-runs/".concat(e)),getBroadcastRunNodes:e=>o("/broadcast-runs/".concat(e,"/nodes")),getBroadcastRunLogs:e=>o("/broadcast-runs/".concat(e,"/logs")),cancelBroadcastRun:e=>o("/broadcast-runs/".concat(e,"/cancel"),{method:"POST"}),rerunBroadcastRun:e=>o("/broadcast-runs/".concat(e,"/rerun"),{method:"POST"}),getBroadcastDefinitions:()=>o("/v1/broadcast-definitions"),getBroadcastDefinition:e=>o("/v1/broadcast-definitions/".concat(encodeURIComponent(e))),getBroadcastDefinitionVersions:e=>o("/v1/broadcast-definitions/".concat(encodeURIComponent(e),"/versions")),saveBroadcastDefinition:e=>o("/v1/broadcast-definitions",{method:"POST",body:JSON.stringify(e)}),validateBroadcastDefinition:e=>o("/v1/broadcast-definitions/validate",{method:"POST",body:JSON.stringify(e)}),deleteBroadcastDefinition:e=>o("/v1/broadcast-definitions/".concat(encodeURIComponent(e)),{method:"DELETE"}),triggerDynamicBroadcast:(e,t)=>o("/v1/trigger-dynamic-broadcast",{method:"POST",body:JSON.stringify({broadcastName:e,input:null!=t?t:{}})}),getSchedules:e=>{let t=new URLSearchParams;(null==e?void 0:e.kind)&&t.set("kind",e.kind),(null==e?void 0:e.enabled)!==void 0&&t.set("enabled",String(e.enabled));let n=t.toString();return o("/v1/schedules".concat(n?"?".concat(n):""))},getSchedule:e=>o("/v1/schedules/".concat(e)),createSchedule:e=>o("/v1/schedules",{method:"POST",body:JSON.stringify(e)}),updateSchedule:(e,t)=>o("/v1/schedules/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),deleteSchedule:e=>o("/v1/schedules/".concat(e),{method:"DELETE"}),previewSchedule:function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:5;return o("/v1/schedules/".concat(e,"/preview"),{method:"POST",body:JSON.stringify({count:t})})},evaluateExpression:(e,t)=>o("/v1/expressions/evaluate",{method:"POST",body:JSON.stringify({node:e,context:t})}),validateExpression:(e,t)=>o("/v1/expressions/validate",{method:"POST",body:JSON.stringify({node:e,schemaContext:t})}),parseExpression:e=>o("/v1/expressions/parse",{method:"POST",body:JSON.stringify({source:e})}),getApiKeys:()=>o("/v1/keys"),createApiKey:(e,t)=>o("/v1/keys",{method:"POST",body:JSON.stringify({name:e,scopes:t})}),revokeApiKey:e=>o("/v1/keys/".concat(e),{method:"DELETE"})}}n.d(t,{T5:()=>a,gf:()=>i,iD:()=>s,ri:()=>r})},2311:(e,t,n)=>{Promise.resolve().then(n.bind(n,5617))},5617:(e,t,n)=>{"use strict";n.d(t,{VersionView:()=>d});var o=n(8111),a=n(5271),s=n(5935),r=n.n(s),i=n(593),l=n(7646),c=n(6990);function d(e){var t,n;let{name:s,version:d}=e,p=(0,i.gf)(),[h,f]=(0,a.useState)([]),[v,y]=(0,a.useState)(null),[b,S]=(0,a.useState)(null),[x,j]=(0,a.useState)(!0),[N,O]=(0,a.useState)(null);(0,l.rT)([{label:"Broadcasts",href:"/broadcasts"},{label:s,href:"/broadcasts/dyn/".concat(encodeURIComponent(s))},{label:"v".concat(d)}],"broadcasts"),(0,a.useEffect)(()=>{Promise.all([p.getBroadcastDefinitionVersions(s),p.getBroadcastDefinition(s).then(e=>e.data,()=>null)]).then(e=>{var t;let[n]=e;f(n.data);let o=n.data.find(e=>e.version===d);y(null!=o?o:null);let a=n.data.find(e=>e.version===d-1);S(null!=(t=null==a?void 0:a.version)?t:null)}).catch(e=>O(e instanceof Error?e.message:String(e))).finally(()=>j(!1))},[s,d]);let T=(0,a.useMemo)(()=>{var e;return null!=(e=h.find(e=>e.version===b))?e:null},[h,b]),w=(0,a.useMemo)(()=>v&&T?function(e,t){let n=JSON.stringify(e,null,2).split("\n"),o=JSON.stringify(t,null,2).split("\n"),a=new Map;for(let e=0;e<n.length;e++){let t=a.get(n[e]);t?t.push(e):a.set(n[e],[e])}let s=new Map;for(let e=0;e<o.length;e++){let t=s.get(o[e]);t?t.push(e):s.set(o[e],[e])}let r=new Set,i=new Set;for(let e=0;e<o.length;e++){let t=a.get(o[e]);t&&0!==t.length?t.shift():r.add(e)}for(let e=0;e<n.length;e++){let t=s.get(n[e]);t&&0!==t.length?t.shift():i.add(e)}return{addedLines:r,removedLines:i}}(m(T),m(v)):null,[v,T]);return x?(0,o.jsxs)("div",{children:[(0,o.jsxs)("h1",{className:"page-title",children:[s," v",d]}),(0,o.jsx)("div",{className:"loading-bar",children:(0,o.jsx)("div",{className:"loading-bar-fill"})})]}):v?(0,o.jsxs)("div",{children:[(0,o.jsxs)("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"1rem"},children:[(0,o.jsxs)("h1",{className:"page-title",style:{margin:0},children:[(0,o.jsx)("span",{className:"mono",children:s}),(0,o.jsxs)("span",{style:{marginLeft:"0.5rem",color:"var(--muted)",fontSize:"0.875rem"},children:["v",d]}),v.deletedAt&&(0,o.jsx)("span",{style:{marginLeft:"0.75rem",fontSize:"0.6875rem",padding:"0.125rem 0.375rem",borderRadius:"3px",background:"var(--error-bg, #fee)",color:"var(--error, #b00)"},children:"deleted"})]}),(0,o.jsx)(r(),{href:"/broadcasts/dyn/".concat(encodeURIComponent(s)),className:"btn",children:"Back to current"})]}),(0,o.jsxs)("div",{style:{display:"grid",gridTemplateColumns:"minmax(0, 1fr) minmax(0, 1fr)",gap:"1rem",marginBottom:"1rem"},children:[(0,o.jsxs)("div",{children:[(0,o.jsx)("div",{className:"mono",style:g,children:"Compare to"}),(0,o.jsxs)("select",{className:"mono",value:null!=b?b:"",onChange:e=>S(e.target.value?Number(e.target.value):null),style:{width:"100%",padding:"0.5rem 0.625rem",border:"1px solid var(--border)",borderRadius:"4px",background:"var(--surface)",color:"var(--text)",fontSize:"0.875rem"},children:[(0,o.jsx)("option",{value:"",children:"— No comparison —"}),h.filter(e=>e.version!==d).map(e=>(0,o.jsxs)("option",{value:e.version,children:["v",e.version,e.deletedAt?" (deleted)":""," — ",new Date(e.updatedAt).toLocaleString()]},e.version))]})]}),(0,o.jsx)("div",{className:"mono",style:{fontSize:"0.75rem",color:"var(--muted)",alignSelf:"end"},children:T?"Diffing v".concat(T.version," → v").concat(v.version):"Showing the spec as-saved (no diff)."})]}),(0,o.jsxs)("div",{style:{display:"grid",gridTemplateColumns:T?"minmax(0, 1fr) minmax(0, 1fr)":"1fr",gap:"1rem"},children:[T&&(0,o.jsx)(u,{title:"v".concat(T.version),spec:T,highlight:null!=(t=null==w?void 0:w.removedLines)?t:new Set,side:"left"}),(0,o.jsx)(u,{title:"v".concat(v.version),spec:v,highlight:null!=(n=null==w?void 0:w.addedLines)?n:new Set,side:"right"})]}),(0,o.jsx)(c.F,{title:"Inspect this version",snippets:[{label:"Get this version",method:"GET",path:"/api/v1/broadcast-definitions/".concat(encodeURIComponent(s),"/versions/").concat(d)},{label:"List all versions",method:"GET",path:"/api/v1/broadcast-definitions/".concat(encodeURIComponent(s),"/versions")}]})]}):(0,o.jsxs)("div",{children:[(0,o.jsxs)("h1",{className:"page-title",children:[s," v",d]}),(0,o.jsx)("div",{className:"empty-state",children:(0,o.jsx)("p",{className:"empty-state-text",children:null!=N?N:"Version not found."})})]})}function u(e){let{title:t,spec:n,highlight:a,side:s}=e,r=JSON.stringify(m(n),null,2).split("\n");return(0,o.jsxs)("div",{children:[(0,o.jsx)("div",{className:"mono",style:g,children:t}),(0,o.jsx)("pre",{className:"mono",style:{margin:0,padding:"0.5rem",background:"var(--surface)",border:"1px solid var(--border)",borderRadius:"4px",fontSize:"0.75rem",lineHeight:1.5,overflowX:"auto",maxHeight:"640px",overflowY:"auto"},children:r.map((e,t)=>(0,o.jsx)("div",{style:{padding:"0 0.25rem",background:a.has(t)?"left"===s?"rgba(196, 131, 74, 0.18)":"rgba(107, 153, 98, 0.18)":"transparent",whiteSpace:"pre"},children:e||" "},t))})]})}function m(e){let{createdAt:t,updatedAt:n,deletedAt:o,createdBy:a,...s}=e;return function e(t){if(Array.isArray(t))return t.map(t=>e(t));if(t&&"object"==typeof t){let n={};for(let o of Object.keys(t).sort())n[o]=e(t[o]);return n}return t}(s)}let g={display:"block",fontSize:"0.75rem",color:"var(--muted)",textTransform:"uppercase",letterSpacing:"0.05em",marginBottom:"0.375rem"}},6990:(e,t,n)=>{"use strict";n.d(t,{F:()=>s});var o=n(8111),a=n(5271);function s(e){let{title:t="API equivalent",snippets:n}=e,[s,r]=(0,a.useState)(!1),[i,l]=(0,a.useState)(null),[c,d]=(0,a.useState)(null);if(0===n.length)return null;async function u(e,t){try{var n;if(null==(n=navigator.clipboard)?void 0:n.writeText){await navigator.clipboard.writeText(e),l(t),setTimeout(()=>l(e=>e===t?null:e),1500);return}throw Error("clipboard API unavailable")}catch(n){try{let n=document.createElement("textarea");n.value=e,n.style.position="fixed",n.style.left="-9999px",document.body.appendChild(n),n.select();let o=document.execCommand("copy");if(document.body.removeChild(n),o)l(t),setTimeout(()=>l(e=>e===t?null:e),1500);else throw Error("execCommand copy returned false")}catch(e){d(t),setTimeout(()=>d(e=>e===t?null:e),2e3)}}}return(0,o.jsxs)("details",{open:s,onToggle:e=>r(e.target.open),style:{marginTop:"1.5rem",border:"1px solid var(--border)",borderRadius:"4px",background:"var(--surface)"},children:[(0,o.jsxs)("summary",{style:{padding:"0.5rem 0.75rem",fontSize:"0.75rem",fontFamily:"var(--mono-font, monospace)",color:"var(--muted)",textTransform:"uppercase",letterSpacing:"0.05em",cursor:"pointer",listStyle:"none"},children:["▸ ",t]}),(0,o.jsx)("div",{style:{padding:"0.5rem 0.75rem 0.75rem"},children:n.map((e,t)=>{let a=function(e){var t;let n=[];if(e.query)for(let[t,o]of Object.entries(e.query))void 0!==o&&n.push("".concat(encodeURIComponent(t),"=").concat(encodeURIComponent(String(o))));let o="".concat(e.path).concat(n.length?"?".concat(n.join("&")):""),a=(null==(t=window.location)?void 0:t.origin)?window.location.origin:"https://your-station.example.com",s=[];if(s.push("curl -X ".concat(e.method," \\")),s.push(" ".concat(a).concat(o," \\")),s.push(" -H 'Authorization: Bearer sk_live_…' \\"),void 0!==e.body){s.push(" -H 'Content-Type: application/json' \\");let t="string"==typeof e.body?e.body:JSON.stringify(e.body,null,2);s.push(" -d ".concat(JSON.stringify(t)))}else s[s.length-1]=s[s.length-1].replace(/ \\$/,"");return s.join("\n")}(e);return(0,o.jsxs)("div",{style:{marginBottom:t===n.length-1?0:"0.75rem"},children:[(0,o.jsxs)("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"0.25rem"},children:[(0,o.jsx)("div",{className:"mono",style:{fontSize:"0.75rem",color:"var(--muted)"},children:e.label}),(0,o.jsx)("button",{type:"button",className:"btn btn--sm",onClick:()=>u(a,t),title:c===t?"Copy failed — select the text manually":"",children:i===t?"Copied":c===t?"Copy failed":"Copy"})]}),(0,o.jsx)("pre",{className:"mono",style:{margin:0,padding:"0.5rem 0.75rem",background:"var(--bg, #fff)",border:"1px solid var(--border)",borderRadius:"4px",fontSize:"0.75rem",overflowX:"auto",whiteSpace:"pre"},children:a})]},t)})})]})}},7646:(e,t,n)=>{"use strict";n.d(t,{Hc:()=>a,iQ:()=>r,rT:()=>s});var o=n(5271);let a=(0,o.createContext)({segments:[],activeSection:null,setSegments:()=>{},setActiveSection:()=>{}});function s(e,t){let n=(0,o.useContext)(a);(0,o.useEffect)(()=>{n.setSegments(e),n.setActiveSection(t)},[JSON.stringify(e),t])}function r(){return(0,o.useContext)(a)}}},e=>{e.O(0,[935,814,743,358],()=>e(e.s=2311)),_N_E=e.O()}]);