station-kit 1.0.8 → 1.0.9

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 (172) hide show
  1. package/.next/standalone/package.json +3 -1
  2. package/.next/standalone/packages/station-kit/.next/BUILD_ID +1 -1
  3. package/.next/standalone/packages/station-kit/.next/app-build-manifest.json +76 -17
  4. package/.next/standalone/packages/station-kit/.next/app-path-routes-manifest.json +11 -4
  5. package/.next/standalone/packages/station-kit/.next/build-manifest.json +3 -3
  6. package/.next/standalone/packages/station-kit/.next/prerender-manifest.json +105 -9
  7. package/.next/standalone/packages/station-kit/.next/routes-manifest.json +49 -0
  8. package/.next/standalone/packages/station-kit/.next/server/app/_not-found/page.js +2 -2
  9. package/.next/standalone/packages/station-kit/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/packages/station-kit/.next/server/app/_not-found.html +1 -1
  11. package/.next/standalone/packages/station-kit/.next/server/app/_not-found.rsc +7 -7
  12. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/[id]/page.js +2 -2
  13. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/[id]/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/dyn/[name]/page.js +2 -0
  15. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/dyn/[name]/page.js.nft.json +1 -0
  16. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/dyn/[name]/page_client-reference-manifest.js +1 -0
  17. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/dyn/[name]/v/[n]/page.js +2 -0
  18. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/dyn/[name]/v/[n]/page.js.nft.json +1 -0
  19. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/dyn/[name]/v/[n]/page_client-reference-manifest.js +1 -0
  20. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new/page.js +2 -0
  21. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new/page.js.nft.json +1 -0
  22. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new/page_client-reference-manifest.js +1 -0
  23. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new.html +1 -0
  24. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new.meta +7 -0
  25. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/new.rsc +25 -0
  26. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/page.js +2 -2
  27. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/page_client-reference-manifest.js +1 -1
  28. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts.html +1 -1
  29. package/.next/standalone/packages/station-kit/.next/server/app/broadcasts.rsc +8 -8
  30. package/.next/standalone/packages/station-kit/.next/server/app/index.html +1 -1
  31. package/.next/standalone/packages/station-kit/.next/server/app/index.rsc +8 -8
  32. package/.next/standalone/packages/station-kit/.next/server/app/page.js +2 -2
  33. package/.next/standalone/packages/station-kit/.next/server/app/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression/page.js +2 -0
  35. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression/page.js.nft.json +1 -0
  36. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression/page_client-reference-manifest.js +1 -0
  37. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression.html +1 -0
  38. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression.meta +7 -0
  39. package/.next/standalone/packages/station-kit/.next/server/app/playground/expression.rsc +25 -0
  40. package/.next/standalone/packages/station-kit/.next/server/app/runs/[id]/page.js +2 -2
  41. package/.next/standalone/packages/station-kit/.next/server/app/runs/[id]/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/packages/station-kit/.next/server/app/schedules/[id]/page.js +2 -0
  43. package/.next/standalone/packages/station-kit/.next/server/app/schedules/[id]/page.js.nft.json +1 -0
  44. package/.next/standalone/packages/station-kit/.next/server/app/schedules/[id]/page_client-reference-manifest.js +1 -0
  45. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new/page.js +2 -0
  46. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new/page.js.nft.json +1 -0
  47. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new/page_client-reference-manifest.js +1 -0
  48. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new.html +1 -0
  49. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new.meta +7 -0
  50. package/.next/standalone/packages/station-kit/.next/server/app/schedules/new.rsc +25 -0
  51. package/.next/standalone/packages/station-kit/.next/server/app/schedules/page.js +2 -0
  52. package/.next/standalone/packages/station-kit/.next/server/app/schedules/page.js.nft.json +1 -0
  53. package/.next/standalone/packages/station-kit/.next/server/app/schedules/page_client-reference-manifest.js +1 -0
  54. package/.next/standalone/packages/station-kit/.next/server/app/schedules.html +1 -0
  55. package/.next/standalone/packages/station-kit/.next/server/app/schedules.meta +7 -0
  56. package/.next/standalone/packages/station-kit/.next/server/app/schedules.rsc +25 -0
  57. package/.next/standalone/packages/station-kit/.next/server/app/settings/page.js +2 -2
  58. package/.next/standalone/packages/station-kit/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/packages/station-kit/.next/server/app/settings.html +1 -1
  60. package/.next/standalone/packages/station-kit/.next/server/app/settings.rsc +8 -8
  61. package/.next/standalone/packages/station-kit/.next/server/app/signals/[name]/page.js +2 -2
  62. package/.next/standalone/packages/station-kit/.next/server/app/signals/[name]/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/packages/station-kit/.next/server/app/signals/page.js +2 -2
  64. package/.next/standalone/packages/station-kit/.next/server/app/signals/page_client-reference-manifest.js +1 -1
  65. package/.next/standalone/packages/station-kit/.next/server/app/signals.html +1 -1
  66. package/.next/standalone/packages/station-kit/.next/server/app/signals.rsc +8 -8
  67. package/.next/standalone/packages/station-kit/.next/server/app-paths-manifest.json +11 -4
  68. package/.next/standalone/packages/station-kit/.next/server/chunks/102.js +1 -1
  69. package/.next/standalone/packages/station-kit/.next/server/chunks/535.js +2 -0
  70. package/.next/standalone/packages/station-kit/.next/server/chunks/606.js +14 -14
  71. package/.next/standalone/packages/station-kit/.next/server/chunks/783.js +3 -3
  72. package/.next/standalone/packages/station-kit/.next/server/middleware-build-manifest.js +1 -1
  73. package/.next/standalone/packages/station-kit/.next/server/pages/404.html +1 -1
  74. package/.next/standalone/packages/station-kit/.next/server/pages/500.html +1 -1
  75. package/.next/standalone/packages/station-kit/.next/server/pages/_app.js +1 -1
  76. package/.next/standalone/packages/station-kit/.next/server/pages/_document.js +1 -1
  77. package/.next/standalone/packages/station-kit/.next/server/pages/_error.js +9 -9
  78. package/.next/standalone/packages/station-kit/.next/server/pages-manifest.json +1 -1
  79. package/.next/standalone/packages/station-kit/.next/server/server-reference-manifest.json +1 -1
  80. package/.next/standalone/packages/station-kit/.next/static/chunks/145-9e370afd2e5aba39.js +1 -0
  81. package/.next/standalone/packages/station-kit/.next/static/chunks/285-ff198f0a909c4fdd.js +1 -0
  82. package/.next/standalone/packages/station-kit/.next/static/chunks/561-33d912169940283e.js +1 -0
  83. package/.next/standalone/packages/station-kit/.next/static/chunks/935-dff12960528de017.js +1 -0
  84. package/.next/standalone/packages/station-kit/.next/static/chunks/app/_not-found/{page-ce21b4ba9038a5a7.js → page-67ef312aee40cfeb.js} +1 -1
  85. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/[id]/page-fe2f5467a0c68fef.js +1 -0
  86. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/dyn/[name]/page-0d2505242014f51e.js +1 -0
  87. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/dyn/[name]/v/[n]/page-5eac0507f49a00ec.js +1 -0
  88. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/new/page-3d02707043d24dc7.js +1 -0
  89. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-dee500ccc01f0821.js +1 -0
  90. package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-e14e14f3e5b0b8a9.js +1 -0
  91. package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-aac41ef7a470daab.js +1 -0
  92. package/.next/standalone/packages/station-kit/.next/static/chunks/app/playground/expression/page-dc9d91f3f50f4716.js +1 -0
  93. package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-9e4c4f751a4bea72.js +1 -0
  94. package/.next/standalone/packages/station-kit/.next/static/chunks/app/schedules/[id]/page-435f67be180b8e4f.js +1 -0
  95. package/.next/standalone/packages/station-kit/.next/static/chunks/app/schedules/new/page-f697c289c813496a.js +1 -0
  96. package/.next/standalone/packages/station-kit/.next/static/chunks/app/schedules/page-738d98dc0b63166e.js +1 -0
  97. package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-fc5654b31f57ac21.js +1 -0
  98. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-4b1c09a539a1ebcd.js +1 -0
  99. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-d2f2403dfede87cc.js +1 -0
  100. package/.next/standalone/packages/station-kit/.next/static/chunks/pages/_app-a3774a320f58a018.js +1 -0
  101. package/.next/standalone/packages/station-kit/.next/static/demLiQWDy62JuUkBw-ILG/_buildManifest.js +1 -0
  102. package/.next/standalone/packages/station-kit/package.json +5 -1
  103. package/dist/config/schema.d.ts +13 -0
  104. package/dist/config/schema.d.ts.map +1 -1
  105. package/dist/config/schema.js +1 -0
  106. package/dist/config/schema.js.map +1 -1
  107. package/dist/server/auth/keys.d.ts +56 -8
  108. package/dist/server/auth/keys.d.ts.map +1 -1
  109. package/dist/server/auth/keys.js +155 -53
  110. package/dist/server/auth/keys.js.map +1 -1
  111. package/dist/server/index.d.ts +2 -2
  112. package/dist/server/index.d.ts.map +1 -1
  113. package/dist/server/index.js +53 -6
  114. package/dist/server/index.js.map +1 -1
  115. package/dist/server/middleware/auth.js +1 -1
  116. package/dist/server/middleware/auth.js.map +1 -1
  117. package/dist/server/routes/v1/definitions.d.ts +21 -0
  118. package/dist/server/routes/v1/definitions.d.ts.map +1 -0
  119. package/dist/server/routes/v1/definitions.js +139 -0
  120. package/dist/server/routes/v1/definitions.js.map +1 -0
  121. package/dist/server/routes/v1/expressions.d.ts +3 -0
  122. package/dist/server/routes/v1/expressions.d.ts.map +1 -0
  123. package/dist/server/routes/v1/expressions.js +56 -0
  124. package/dist/server/routes/v1/expressions.js.map +1 -0
  125. package/dist/server/routes/v1/keys.js +3 -3
  126. package/dist/server/routes/v1/keys.js.map +1 -1
  127. package/dist/server/routes/v1/schedules.d.ts +10 -0
  128. package/dist/server/routes/v1/schedules.d.ts.map +1 -0
  129. package/dist/server/routes/v1/schedules.js +169 -0
  130. package/dist/server/routes/v1/schedules.js.map +1 -0
  131. package/dist/server/routes/v1/trigger.d.ts.map +1 -1
  132. package/dist/server/routes/v1/trigger.js +21 -0
  133. package/dist/server/routes/v1/trigger.js.map +1 -1
  134. package/package.json +11 -7
  135. package/src/app/broadcasts/components/broadcast-builder.tsx +535 -0
  136. package/src/app/broadcasts/components/dag-editor.tsx +510 -0
  137. package/src/app/broadcasts/dyn/[name]/dynamic-detail.tsx +243 -0
  138. package/src/app/broadcasts/dyn/[name]/page.tsx +10 -0
  139. package/src/app/broadcasts/dyn/[name]/v/[n]/page.tsx +10 -0
  140. package/src/app/broadcasts/dyn/[name]/v/[n]/version-view.tsx +285 -0
  141. package/src/app/broadcasts/new/page.tsx +102 -0
  142. package/src/app/broadcasts/page.tsx +176 -91
  143. package/src/app/components/api-panel.tsx +151 -0
  144. package/src/app/components/shell.tsx +23 -0
  145. package/src/app/hooks/use-api.ts +117 -0
  146. package/src/app/playground/expression/page.tsx +245 -0
  147. package/src/app/schedules/[id]/page.tsx +10 -0
  148. package/src/app/schedules/[id]/schedule-editor.tsx +195 -0
  149. package/src/app/schedules/components/schedule-form.tsx +140 -0
  150. package/src/app/schedules/new/page.tsx +166 -0
  151. package/src/app/schedules/page.tsx +126 -0
  152. package/src/config/schema.ts +14 -0
  153. package/src/server/auth/keys.ts +191 -56
  154. package/src/server/index.ts +72 -8
  155. package/src/server/middleware/auth.ts +1 -1
  156. package/src/server/routes/v1/definitions.ts +164 -0
  157. package/src/server/routes/v1/expressions.ts +76 -0
  158. package/src/server/routes/v1/keys.ts +3 -3
  159. package/src/server/routes/v1/schedules.ts +176 -0
  160. package/src/server/routes/v1/trigger.ts +27 -0
  161. package/.next/standalone/packages/station-kit/.next/static/chunks/580-f007f4d4c050db4e.js +0 -1
  162. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/[id]/page-a0a20cccda13a0e9.js +0 -1
  163. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-937eb876f9087bc9.js +0 -1
  164. package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-68cd71116ba65cd8.js +0 -1
  165. package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-70b0c0958c03459a.js +0 -1
  166. package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-01f8040619fe56c5.js +0 -1
  167. package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-beac11049f90da31.js +0 -1
  168. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-931e6a38a4a53d25.js +0 -1
  169. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-6a123a355d93fec5.js +0 -1
  170. package/.next/standalone/packages/station-kit/.next/static/chunks/pages/_app-0a7b2e66ecbe3f0a.js +0 -1
  171. package/.next/standalone/packages/station-kit/.next/static/xYd6dn0Ox68DaamIrH_pB/_buildManifest.js +0 -1
  172. /package/.next/standalone/packages/station-kit/.next/static/{xYd6dn0Ox68DaamIrH_pB → demLiQWDy62JuUkBw-ILG}/_ssgManifest.js +0 -0
@@ -4,10 +4,15 @@ import { serve } from "@hono/node-server";
4
4
  import { resolve } from "node:path";
5
5
  import { existsSync } from "node:fs";
6
6
  import type { Server } from "node:http";
7
- import { SignalRunner, MemoryAdapter } from "station-signal";
7
+ import { SignalRunner, MemoryAdapter, parseInterval } from "station-signal";
8
8
  import { BroadcastRunner, BroadcastMemoryAdapter } from "station-broadcast";
9
9
  import type { SignalQueueAdapter } from "station-signal";
10
10
  import type { BroadcastQueueAdapter } from "station-broadcast";
11
+ import {
12
+ ScheduleReconciler,
13
+ type Schedule,
14
+ type ScheduleAdapter,
15
+ } from "station-schedules";
11
16
  import type { StationConfig } from "../config/schema.js";
12
17
  import { ensureStationDir } from "../station-dir.js";
13
18
  import { WebSocketHub } from "./ws.js";
@@ -19,7 +24,7 @@ import { healthRoutes } from "./routes/health.js";
19
24
  import { signalRoutes } from "./routes/signals.js";
20
25
  import { runRoutes } from "./routes/runs.js";
21
26
  import { broadcastRoutes } from "./routes/broadcasts.js";
22
- import { KeyStore } from "./auth/keys.js";
27
+ import { KeyStore, SqliteKeyStorage } from "./auth/keys.js";
23
28
  import { verifySessionToken, verifyCredentials, createSessionToken, type SessionConfig } from "./auth/session.js";
24
29
  import { authResolver } from "./middleware/auth.js";
25
30
  import { requireScope } from "./middleware/scope-guard.js";
@@ -32,9 +37,21 @@ import { v1TriggerRoutes } from "./routes/v1/trigger.js";
32
37
  import { v1KeyRoutes } from "./routes/v1/keys.js";
33
38
  import { v1AuthRoutes } from "./routes/v1/auth.js";
34
39
  import { v1EventRoutes } from "./routes/v1/events.js";
35
-
36
- export { KeyStore } from "./auth/keys.js";
37
- export type { ApiKey } from "./auth/keys.js";
40
+ import { v1DefinitionRoutes, v1DefinitionReadRoutes } from "./routes/v1/definitions.js";
41
+ import { v1ScheduleRoutes, v1ScheduleReadRoutes } from "./routes/v1/schedules.js";
42
+ import { v1ExpressionRoutes } from "./routes/v1/expressions.js";
43
+
44
+ export {
45
+ KeyStore,
46
+ SqliteKeyStorage,
47
+ MemoryKeyStorage,
48
+ } from "./auth/keys.js";
49
+ export type {
50
+ ApiKey,
51
+ ApiKeyPublic,
52
+ ApiKeyStorageAdapter,
53
+ SqliteKeyStorageOptions,
54
+ } from "./auth/keys.js";
38
55
 
39
56
  export interface StationInstance {
40
57
  start(): Promise<void>;
@@ -62,7 +79,9 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
62
79
  let sessionConfig: SessionConfig | undefined;
63
80
 
64
81
  if (config.auth) {
65
- keyStore = new KeyStore(resolve(dataDir, "station-keys.db"));
82
+ const storage = config.auth.keyStorage
83
+ ?? new SqliteKeyStorage({ dbPath: resolve(dataDir, "station-keys.db") });
84
+ keyStore = new KeyStore(storage);
66
85
  sessionConfig = {
67
86
  username: config.auth.username,
68
87
  password: config.auth.password,
@@ -94,8 +113,23 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
94
113
  // Create runners if enabled
95
114
  let signalRunner: SignalRunner | undefined;
96
115
  let broadcastRunner: BroadcastRunner | undefined;
116
+ const scheduleAdapter: ScheduleAdapter | undefined = config.scheduleAdapter;
97
117
 
98
118
  if (config.runRunners) {
119
+ // Build schedule reconcilers up front. Each reconciler handles only the
120
+ // kinds it's responsible for; the runner ticks it once per loop.
121
+ const signalScheduleReconciler = scheduleAdapter
122
+ ? new ScheduleReconciler({
123
+ adapter: scheduleAdapter,
124
+ kinds: ["signal"],
125
+ parseInterval,
126
+ triggerFn: (s: Schedule) => signalRunner!.triggerSignal(s.target, s.input ?? {}),
127
+ hasPendingOrRunning: (s: Schedule) =>
128
+ signalRunner!.hasPendingOrRunningForSignal(s.target),
129
+ onError: (err) => console.error("[station] Signal schedule reconciler:", err),
130
+ })
131
+ : undefined;
132
+
99
133
  signalRunner = new SignalRunner({
100
134
  signalsDir,
101
135
  adapter: signalAdapter,
@@ -104,15 +138,29 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
104
138
  maxAttempts: config.runner.maxAttempts,
105
139
  retryBackoffMs: config.runner.retryBackoffMs,
106
140
  subscribers: [stationSignalSub],
141
+ scheduleReconciler: signalScheduleReconciler,
107
142
  });
108
143
 
109
144
  if (broadcastsDir || broadcastAdapter) {
145
+ const broadcastScheduleReconciler = scheduleAdapter
146
+ ? new ScheduleReconciler({
147
+ adapter: scheduleAdapter,
148
+ kinds: ["broadcast-static", "broadcast-dynamic"],
149
+ parseInterval,
150
+ triggerFn: (s: Schedule) => broadcastRunner!.trigger(s.target, s.input ?? {}),
151
+ hasPendingOrRunning: (s: Schedule) =>
152
+ broadcastRunner!.hasPendingOrRunningForBroadcast(s.target),
153
+ onError: (err) => console.error("[station] Broadcast schedule reconciler:", err),
154
+ })
155
+ : undefined;
156
+
110
157
  broadcastRunner = new BroadcastRunner({
111
158
  signalRunner,
112
159
  broadcastsDir,
113
160
  adapter: broadcastAdapter ?? new BroadcastMemoryAdapter(),
114
161
  pollIntervalMs: config.broadcastRunner.pollIntervalMs,
115
162
  subscribers: [stationBroadcastSub],
163
+ scheduleReconciler: broadcastScheduleReconciler,
116
164
  });
117
165
  }
118
166
  }
@@ -204,6 +252,15 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
204
252
  readRoutes.route("/", v1RunRoutes({ signalRunner, signalAdapter, logBuffer, logStore }));
205
253
  readRoutes.route("/", v1BroadcastRoutes({ broadcastRunner, broadcastAdapter, broadcastSubscriber: stationBroadcastSub }));
206
254
  readRoutes.route("/", v1EventRoutes({ sseHub }));
255
+ readRoutes.route("/", v1ExpressionRoutes());
256
+ // Schedule GET + preview are read-scoped; mutating routes are mounted under admin below.
257
+ readRoutes.route("/", v1ScheduleReadRoutes({ scheduleAdapter }));
258
+ readRoutes.route("/", v1DefinitionReadRoutes({
259
+ broadcastRunner,
260
+ broadcastAdapter,
261
+ signalRunner,
262
+ signalSubscriber: stationSignalSub,
263
+ }));
207
264
  v1.route("/", readRoutes);
208
265
 
209
266
  // Trigger-scope routes
@@ -239,10 +296,17 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
239
296
  });
240
297
  v1.route("/", cancelRoutes);
241
298
 
242
- // Admin-scope routes
299
+ // Admin-scope routes — destructive / mutating endpoints
243
300
  const adminRoutes = new Hono();
244
301
  adminRoutes.use("/*", requireScope("admin"));
245
302
  adminRoutes.route("/", v1KeyRoutes({ keyStore }));
303
+ adminRoutes.route("/", v1DefinitionRoutes({
304
+ broadcastRunner,
305
+ broadcastAdapter,
306
+ signalRunner,
307
+ signalSubscriber: stationSignalSub,
308
+ }));
309
+ adminRoutes.route("/", v1ScheduleRoutes({ scheduleAdapter }));
246
310
  v1.route("/", adminRoutes);
247
311
 
248
312
  app.route("/api/v1", v1);
@@ -338,7 +402,7 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
338
402
  wsHub.close();
339
403
  sseHub.close();
340
404
  logStore.close();
341
- keyStore?.close();
405
+ await keyStore?.close();
342
406
  if (httpServer) {
343
407
  httpServer.close();
344
408
  }
@@ -15,7 +15,7 @@ export function authResolver(deps: AuthDeps) {
15
15
  if (authHeader?.startsWith("Bearer ") && deps.keyStore) {
16
16
  const token = authHeader.slice(7);
17
17
  if (token.startsWith("sk_")) {
18
- const key = deps.keyStore.verify(token);
18
+ const key = await deps.keyStore.verify(token);
19
19
  if (key) {
20
20
  c.set("authType", "api-key");
21
21
  c.set("apiKeyId", key.id);
@@ -0,0 +1,164 @@
1
+ import { Hono } from "hono";
2
+ import type {
3
+ BroadcastRunner,
4
+ BroadcastQueueAdapter,
5
+ DynamicBroadcastSpec,
6
+ } from "station-broadcast";
7
+ import { validateDynamicSpec, type DynamicValidationContext } from "station-broadcast";
8
+ import type { SignalRunner } from "station-signal";
9
+ import type { SchemaField } from "station-expressions";
10
+ import type { StationSignalSubscriber } from "../../subscriber.js";
11
+
12
+ export interface V1DefinitionDeps {
13
+ broadcastRunner?: BroadcastRunner;
14
+ broadcastAdapter?: BroadcastQueueAdapter;
15
+ signalRunner?: SignalRunner;
16
+ signalSubscriber?: StationSignalSubscriber;
17
+ }
18
+
19
+ /**
20
+ * Read-scope routes for dynamic broadcast definitions: list, get, version
21
+ * history, get-by-version, plus the read-only `validate` endpoint.
22
+ */
23
+ export function v1DefinitionReadRoutes(deps: V1DefinitionDeps) {
24
+ const app = new Hono();
25
+
26
+ app.get("/broadcast-definitions", async (c) => {
27
+ if (!deps.broadcastAdapter?.listDefinitions) return c.json({ data: [] });
28
+ const list = await deps.broadcastAdapter.listDefinitions();
29
+ return c.json({ data: list.map(serializeSpec) });
30
+ });
31
+
32
+ app.get("/broadcast-definitions/:name", async (c) => {
33
+ if (!deps.broadcastAdapter?.getDefinition) return c.json({ error: "unavailable" }, 503);
34
+ const spec = await deps.broadcastAdapter.getDefinition(c.req.param("name"));
35
+ if (!spec) return c.json({ error: "not_found" }, 404);
36
+ return c.json({ data: serializeSpec(spec) });
37
+ });
38
+
39
+ app.get("/broadcast-definitions/:name/versions", async (c) => {
40
+ if (!deps.broadcastAdapter?.listDefinitionVersions) return c.json({ data: [] });
41
+ const versions = await deps.broadcastAdapter.listDefinitionVersions(c.req.param("name"));
42
+ return c.json({ data: versions.map(serializeSpec) });
43
+ });
44
+
45
+ app.get("/broadcast-definitions/:name/versions/:n", async (c) => {
46
+ if (!deps.broadcastAdapter?.getDefinition) return c.json({ error: "unavailable" }, 503);
47
+ const version = parseInt(c.req.param("n"), 10);
48
+ if (Number.isNaN(version)) {
49
+ return c.json({ error: "bad_request", message: "Version must be a number." }, 400);
50
+ }
51
+ const spec = await deps.broadcastAdapter.getDefinition(c.req.param("name"), version);
52
+ if (!spec) return c.json({ error: "not_found" }, 404);
53
+ return c.json({ data: serializeSpec(spec) });
54
+ });
55
+
56
+ app.post("/broadcast-definitions/validate", async (c) => {
57
+ const body = await c.req.json().catch(() => ({}));
58
+ const spec = body as DynamicBroadcastSpec;
59
+ if (!spec || typeof spec !== "object" || typeof spec.name !== "string") {
60
+ return c.json({ error: "bad_request", message: "Spec is missing required fields." }, 400);
61
+ }
62
+ const ctx = buildValidationContext(deps);
63
+ const result = validateDynamicSpec(spec, ctx);
64
+ return c.json({ data: result });
65
+ });
66
+
67
+ return app;
68
+ }
69
+
70
+ /**
71
+ * Admin-scope routes for mutating dynamic broadcast definitions. GETs are
72
+ * mounted under read scope in server/index.ts.
73
+ */
74
+ export function v1DefinitionRoutes(deps: V1DefinitionDeps) {
75
+ const app = new Hono();
76
+
77
+ app.post("/broadcast-definitions", async (c) => {
78
+ if (!deps.broadcastAdapter?.saveDefinition) {
79
+ return c.json({ error: "unavailable", message: "Broadcast adapter does not support dynamic definitions." }, 503);
80
+ }
81
+ const body = await c.req.json().catch(() => ({}));
82
+ const incoming = body as DynamicBroadcastSpec;
83
+ if (!incoming?.name || !Array.isArray(incoming.nodes)) {
84
+ return c.json({ error: "bad_request", message: "Spec is missing required fields." }, 400);
85
+ }
86
+
87
+ const ctx = buildValidationContext(deps);
88
+ const validation = validateDynamicSpec(incoming, ctx);
89
+ if (!validation.ok) {
90
+ return c.json({ error: "validation_failed", data: validation }, 422);
91
+ }
92
+
93
+ const apiKeyId = c.get("apiKeyId" as never) as string | undefined;
94
+ const now = new Date();
95
+ const toSave: DynamicBroadcastSpec = {
96
+ ...incoming,
97
+ version: 0,
98
+ createdAt: incoming.createdAt ?? now,
99
+ updatedAt: now,
100
+ createdBy: apiKeyId,
101
+ failurePolicy: incoming.failurePolicy ?? "fail-fast",
102
+ };
103
+
104
+ const saved = await deps.broadcastAdapter.saveDefinition(toSave);
105
+
106
+ // Trigger an eager reconciliation so the new version is live immediately.
107
+ if (deps.broadcastRunner) {
108
+ void deps.broadcastRunner.reconcileDynamicDefinitions().catch(() => {});
109
+ }
110
+
111
+ return c.json({ data: serializeSpec(saved) }, 201);
112
+ });
113
+
114
+ // GET routes for definitions live in the read-scope mount in server/index.ts;
115
+ // this router only exposes mutating endpoints under admin scope.
116
+
117
+ app.delete("/broadcast-definitions/:name", async (c) => {
118
+ if (!deps.broadcastAdapter?.deleteDefinition) {
119
+ return c.json({ error: "unavailable" }, 503);
120
+ }
121
+ const success = await deps.broadcastAdapter.deleteDefinition(c.req.param("name"));
122
+ if (!success) return c.json({ error: "not_found" }, 404);
123
+ if (deps.broadcastRunner) {
124
+ void deps.broadcastRunner.reconcileDynamicDefinitions().catch(() => {});
125
+ }
126
+ return c.json({ data: { deleted: true } });
127
+ });
128
+
129
+ return app;
130
+ }
131
+
132
+ function serializeSpec(spec: DynamicBroadcastSpec): Record<string, unknown> {
133
+ return {
134
+ ...spec,
135
+ createdAt: spec.createdAt?.toISOString?.() ?? spec.createdAt,
136
+ updatedAt: spec.updatedAt?.toISOString?.() ?? spec.updatedAt,
137
+ deletedAt: spec.deletedAt?.toISOString?.() ?? spec.deletedAt,
138
+ };
139
+ }
140
+
141
+ function buildValidationContext(deps: V1DefinitionDeps): DynamicValidationContext {
142
+ const signalSchemas = new Map<string, { inputSchema: SchemaField; outputSchema: SchemaField }>();
143
+ // Best-effort schema reflection: we don't know the precise SchemaField for
144
+ // each signal here without traversing Zod schemas. Use `any` for now — the
145
+ // structural checks (signal exists, deps exist, no cycles, expression
146
+ // wellformedness) still run. Zod input validation runs at trigger time.
147
+ const sigs = deps.signalRunner?.getAllSignals();
148
+ if (sigs) {
149
+ for (const name of sigs.keys()) {
150
+ signalSchemas.set(name, {
151
+ inputSchema: { type: "any" },
152
+ outputSchema: { type: "any" },
153
+ });
154
+ }
155
+ } else if (deps.signalSubscriber) {
156
+ for (const meta of deps.signalSubscriber.getAllSignalMeta()) {
157
+ signalSchemas.set(meta.name, {
158
+ inputSchema: { type: "any" },
159
+ outputSchema: { type: "any" },
160
+ });
161
+ }
162
+ }
163
+ return { signalSchemas };
164
+ }
@@ -0,0 +1,76 @@
1
+ import { Hono } from "hono";
2
+ import {
3
+ evaluate,
4
+ validate,
5
+ parse,
6
+ ExpressionEvalError,
7
+ ExpressionParseError,
8
+ type ExprNode,
9
+ type SchemaField,
10
+ } from "station-expressions";
11
+
12
+ export function v1ExpressionRoutes() {
13
+ const app = new Hono();
14
+
15
+ app.post("/expressions/evaluate", async (c) => {
16
+ const body = await c.req.json().catch(() => ({}));
17
+ const { node, context } = body as {
18
+ node?: ExprNode;
19
+ context?: { input?: unknown; upstream?: Record<string, unknown> };
20
+ };
21
+ if (!node) {
22
+ return c.json({ error: "bad_request", message: "Missing `node`." }, 400);
23
+ }
24
+ try {
25
+ const result = evaluate(node, {
26
+ input: context?.input,
27
+ upstream: context?.upstream ?? {},
28
+ });
29
+ return c.json({ data: { value: result } });
30
+ } catch (err) {
31
+ const message = err instanceof Error ? err.message : String(err);
32
+ const status = err instanceof ExpressionEvalError ? 400 : 500;
33
+ return c.json({ error: "evaluation_failed", message }, status);
34
+ }
35
+ });
36
+
37
+ app.post("/expressions/validate", async (c) => {
38
+ const body = await c.req.json().catch(() => ({}));
39
+ const { node, schemaContext } = body as {
40
+ node?: ExprNode;
41
+ schemaContext?: {
42
+ inputSchema?: SchemaField;
43
+ upstreamSchemas?: Record<string, SchemaField>;
44
+ expectedSchema?: SchemaField;
45
+ };
46
+ };
47
+ if (!node) {
48
+ return c.json({ error: "bad_request", message: "Missing `node`." }, 400);
49
+ }
50
+ const result = validate(node, {
51
+ inputSchema: schemaContext?.inputSchema ?? { type: "any" },
52
+ upstreamSchemas: schemaContext?.upstreamSchemas ?? {},
53
+ expectedSchema: schemaContext?.expectedSchema,
54
+ });
55
+ return c.json({ data: result });
56
+ });
57
+
58
+ app.post("/expressions/parse", async (c) => {
59
+ const body = await c.req.json().catch(() => ({}));
60
+ const { source } = body as { source?: string };
61
+ if (typeof source !== "string") {
62
+ return c.json({ error: "bad_request", message: "Missing `source` string." }, 400);
63
+ }
64
+ try {
65
+ const node = parse(source);
66
+ return c.json({ data: { node } });
67
+ } catch (err) {
68
+ if (err instanceof ExpressionParseError) {
69
+ return c.json({ error: "parse_error", message: err.message, position: err.position }, 400);
70
+ }
71
+ return c.json({ error: "parse_error", message: err instanceof Error ? err.message : String(err) }, 400);
72
+ }
73
+ });
74
+
75
+ return app;
76
+ }
@@ -17,7 +17,7 @@ export function v1KeyRoutes(deps: V1KeyDeps) {
17
17
  const name = body.name || "Unnamed key";
18
18
  const scopes = Array.isArray(body.scopes) ? body.scopes : ["trigger", "read"];
19
19
 
20
- const { key, record } = deps.keyStore.create(name, scopes);
20
+ const { key, record } = await deps.keyStore.create(name, scopes);
21
21
  return c.json(
22
22
  {
23
23
  data: {
@@ -37,7 +37,7 @@ export function v1KeyRoutes(deps: V1KeyDeps) {
37
37
  if (!deps.keyStore) {
38
38
  return c.json({ error: "unavailable", message: "Auth not configured." }, 503);
39
39
  }
40
- const keys = deps.keyStore.list();
40
+ const keys = await deps.keyStore.list();
41
41
  return c.json({ data: keys });
42
42
  });
43
43
 
@@ -46,7 +46,7 @@ export function v1KeyRoutes(deps: V1KeyDeps) {
46
46
  return c.json({ error: "unavailable", message: "Auth not configured." }, 503);
47
47
  }
48
48
  const id = c.req.param("id");
49
- const success = deps.keyStore.revoke(id);
49
+ const success = await deps.keyStore.revoke(id);
50
50
  if (!success) {
51
51
  return c.json({ error: "not_found", message: "Key not found." }, 404);
52
52
  }
@@ -0,0 +1,176 @@
1
+ import { Hono } from "hono";
2
+ import { parseInterval } from "station-signal";
3
+ import type { Schedule, ScheduleAdapter, ScheduleKind } from "station-schedules";
4
+
5
+ export interface V1ScheduleDeps {
6
+ scheduleAdapter?: ScheduleAdapter;
7
+ }
8
+
9
+ const KIND_VALUES: ScheduleKind[] = ["signal", "broadcast-static", "broadcast-dynamic"];
10
+
11
+ /** Read-scope routes: list / get / preview. */
12
+ export function v1ScheduleReadRoutes(deps: V1ScheduleDeps) {
13
+ const app = new Hono();
14
+
15
+ app.get("/schedules", async (c) => {
16
+ if (!deps.scheduleAdapter) return c.json({ data: [] });
17
+ const kindParam = c.req.query("kind");
18
+ const enabledParam = c.req.query("enabled");
19
+
20
+ const kind = KIND_VALUES.includes(kindParam as ScheduleKind)
21
+ ? (kindParam as ScheduleKind)
22
+ : undefined;
23
+ const enabled = enabledParam === "true" ? true : enabledParam === "false" ? false : undefined;
24
+
25
+ const list = await deps.scheduleAdapter.list({ kind, enabled });
26
+ return c.json({ data: list.map(serialize) });
27
+ });
28
+
29
+ app.get("/schedules/:id", async (c) => {
30
+ if (!deps.scheduleAdapter) return c.json({ error: "unavailable" }, 503);
31
+ const s = await deps.scheduleAdapter.get(c.req.param("id"));
32
+ if (!s) return c.json({ error: "not_found" }, 404);
33
+ return c.json({ data: serialize(s) });
34
+ });
35
+
36
+ app.post("/schedules/:id/preview", async (c) => {
37
+ if (!deps.scheduleAdapter) return c.json({ error: "unavailable" }, 503);
38
+ const id = c.req.param("id");
39
+ const s = await deps.scheduleAdapter.get(id);
40
+ if (!s) return c.json({ error: "not_found" }, 404);
41
+ const body = await c.req.json().catch(() => ({}));
42
+ const count = Math.min(20, Math.max(1, Number(body.count) || 5));
43
+ const ms = parseInterval(s.interval);
44
+ const fires: string[] = [];
45
+ let next = s.nextRunAt.getTime();
46
+ for (let i = 0; i < count; i++) {
47
+ fires.push(new Date(next).toISOString());
48
+ next += ms;
49
+ }
50
+ return c.json({ data: { fires } });
51
+ });
52
+
53
+ return app;
54
+ }
55
+
56
+ /** Admin-scope routes: create / update / delete. */
57
+ export function v1ScheduleRoutes(deps: V1ScheduleDeps) {
58
+ const app = new Hono();
59
+
60
+ app.post("/schedules", async (c) => {
61
+ if (!deps.scheduleAdapter) return c.json({ error: "unavailable" }, 503);
62
+ const body = await c.req.json().catch(() => ({}));
63
+ const { kind, target, interval, input, enabled } = body as Partial<Schedule>;
64
+
65
+ if (!KIND_VALUES.includes(kind as ScheduleKind)) {
66
+ return c.json({ error: "bad_request", message: `kind must be one of ${KIND_VALUES.join(", ")}` }, 400);
67
+ }
68
+ if (typeof target !== "string" || target.length === 0) {
69
+ return c.json({ error: "bad_request", message: "target is required" }, 400);
70
+ }
71
+ if (typeof interval !== "string") {
72
+ return c.json({ error: "bad_request", message: "interval is required" }, 400);
73
+ }
74
+ let intervalMs: number;
75
+ try {
76
+ intervalMs = parseInterval(interval);
77
+ } catch (err) {
78
+ return c.json({
79
+ error: "bad_request",
80
+ message: `interval invalid: ${err instanceof Error ? err.message : String(err)}`,
81
+ }, 400);
82
+ }
83
+ // Reject circular / non-serializable inputs before they reach the adapter,
84
+ // which would otherwise surface as a 500 with a stacktrace.
85
+ if (input !== undefined) {
86
+ try {
87
+ JSON.stringify(input);
88
+ } catch (err) {
89
+ return c.json({
90
+ error: "bad_request",
91
+ message: `input is not JSON-serializable: ${err instanceof Error ? err.message : String(err)}`,
92
+ }, 400);
93
+ }
94
+ }
95
+
96
+ const apiKeyId = c.get("apiKeyId" as never) as string | undefined;
97
+ const now = new Date();
98
+ const schedule: Schedule = {
99
+ id: deps.scheduleAdapter.generateId(),
100
+ kind: kind as ScheduleKind,
101
+ target,
102
+ interval,
103
+ input,
104
+ enabled: enabled ?? true,
105
+ nextRunAt: new Date(now.getTime() + intervalMs),
106
+ createdAt: now,
107
+ updatedAt: now,
108
+ createdBy: apiKeyId,
109
+ };
110
+ await deps.scheduleAdapter.add(schedule);
111
+ return c.json({ data: serialize(schedule) }, 201);
112
+ });
113
+
114
+ app.patch("/schedules/:id", async (c) => {
115
+ if (!deps.scheduleAdapter) return c.json({ error: "unavailable" }, 503);
116
+ const id = c.req.param("id");
117
+ const existing = await deps.scheduleAdapter.get(id);
118
+ if (!existing) return c.json({ error: "not_found" }, 404);
119
+
120
+ const body = await c.req.json().catch(() => ({}));
121
+ const patch: Record<string, unknown> = {};
122
+ if ("interval" in body) {
123
+ try {
124
+ parseInterval(body.interval);
125
+ } catch (err) {
126
+ return c.json({
127
+ error: "bad_request",
128
+ message: `interval invalid: ${err instanceof Error ? err.message : String(err)}`,
129
+ }, 400);
130
+ }
131
+ patch.interval = body.interval;
132
+ }
133
+ if ("input" in body) {
134
+ try {
135
+ JSON.stringify(body.input);
136
+ } catch (err) {
137
+ return c.json({
138
+ error: "bad_request",
139
+ message: `input is not JSON-serializable: ${err instanceof Error ? err.message : String(err)}`,
140
+ }, 400);
141
+ }
142
+ patch.input = body.input;
143
+ }
144
+ if ("enabled" in body) patch.enabled = Boolean(body.enabled);
145
+ if ("nextRunAt" in body) {
146
+ const parsed = new Date(body.nextRunAt);
147
+ if (Number.isNaN(parsed.getTime())) {
148
+ return c.json({ error: "bad_request", message: "nextRunAt must be a valid date" }, 400);
149
+ }
150
+ patch.nextRunAt = parsed;
151
+ }
152
+
153
+ await deps.scheduleAdapter.update(id, patch);
154
+ const updated = await deps.scheduleAdapter.get(id);
155
+ return c.json({ data: updated ? serialize(updated) : null });
156
+ });
157
+
158
+ app.delete("/schedules/:id", async (c) => {
159
+ if (!deps.scheduleAdapter) return c.json({ error: "unavailable" }, 503);
160
+ const ok = await deps.scheduleAdapter.delete(c.req.param("id"));
161
+ if (!ok) return c.json({ error: "not_found" }, 404);
162
+ return c.json({ data: { deleted: true } });
163
+ });
164
+
165
+ return app;
166
+ }
167
+
168
+ function serialize(s: Schedule): Record<string, unknown> {
169
+ return {
170
+ ...s,
171
+ nextRunAt: s.nextRunAt.toISOString(),
172
+ lastRunAt: s.lastRunAt?.toISOString(),
173
+ createdAt: s.createdAt.toISOString(),
174
+ updatedAt: s.updatedAt.toISOString(),
175
+ };
176
+ }
@@ -185,6 +185,33 @@ export function v1TriggerRoutes(deps: V1TriggerDeps) {
185
185
  }
186
186
  });
187
187
 
188
+ app.post("/trigger-dynamic-broadcast", async (c) => {
189
+ if (!deps.broadcastRunner) {
190
+ return c.json({ error: "unavailable", message: "Broadcast runner not configured." }, 503);
191
+ }
192
+ const body = await c.req.json().catch(() => null);
193
+ if (!body?.broadcastName) {
194
+ return c.json({ error: "bad_request", message: "Missing broadcastName." }, 400);
195
+ }
196
+ const { broadcastName, input } = body;
197
+ if (!deps.broadcastRunner.hasDynamicBroadcast(broadcastName)) {
198
+ return c.json(
199
+ { error: "not_found", message: `Dynamic broadcast "${broadcastName}" not registered.` },
200
+ 404,
201
+ );
202
+ }
203
+ try {
204
+ const id = await deps.broadcastRunner.triggerDynamic(broadcastName, input ?? {});
205
+ return c.json(
206
+ { data: { id, broadcastName, status: "pending", createdAt: new Date().toISOString() } },
207
+ 201,
208
+ );
209
+ } catch (err: unknown) {
210
+ const message = err instanceof Error ? err.message : String(err);
211
+ return c.json({ error: "trigger_failed", message }, 400);
212
+ }
213
+ });
214
+
188
215
  app.post("/broadcast-runs/:id/rerun", async (c) => {
189
216
  if (!deps.broadcastRunner || !deps.broadcastAdapter) {
190
217
  return c.json({ error: "unavailable", message: "Broadcast runner not configured." }, 503);
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[580],{706:(e,t)=>{function r(e){let t={};for(let[r,n]of e.entries()){let e=t[r];void 0===e?t[r]=n:Array.isArray(e)?e.push(n):t[r]=[e,n]}return t}function n(e){return"string"==typeof e?e:("number"!=typeof e||isNaN(e))&&"boolean"!=typeof e?"":String(e)}function o(e){let t=new URLSearchParams;for(let[r,o]of Object.entries(e))if(Array.isArray(o))for(let e of o)t.append(r,n(e));else t.set(r,n(o));return t}function u(e){for(var t=arguments.length,r=Array(t>1?t-1:0),n=1;n<t;n++)r[n-1]=arguments[n];for(let t of r){for(let r of t.keys())e.delete(r);for(let[r,n]of t.entries())e.append(r,n)}return e}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{assign:function(){return u},searchParamsToUrlQuery:function(){return r},urlQueryToSearchParams:function(){return o}})},2823:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"useMergedRef",{enumerable:!0,get:function(){return o}});let n=r(5271);function o(e,t){let r=(0,n.useRef)(null),o=(0,n.useRef)(null);return(0,n.useCallback)(n=>{if(null===n){let e=r.current;e&&(r.current=null,e());let t=o.current;t&&(o.current=null,t())}else e&&(r.current=u(e,n)),t&&(o.current=u(t,n))},[e,t])}function u(e,t){if("function"!=typeof e)return e.current=t,()=>{e.current=null};{let r=e(t);return"function"==typeof r?r:()=>e(null)}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4736:(e,t,r)=>{var n=r(1504);r.o(n,"useParams")&&r.d(t,{useParams:function(){return n.useParams}}),r.o(n,"usePathname")&&r.d(t,{usePathname:function(){return n.usePathname}}),r.o(n,"useRouter")&&r.d(t,{useRouter:function(){return n.useRouter}})},5073:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"errorOnce",{enumerable:!0,get:function(){return r}});let r=e=>{}},5935:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return g},useLinkStatus:function(){return b}});let n=r(4284),o=r(8111),u=n._(r(5271)),a=r(9642),i=r(5911),l=r(2823),f=r(7076),c=r(3790);r(4209);let s=r(7335),p=r(7843),d=r(587);r(5073);let h=r(5564);function y(e){return"string"==typeof e?e:(0,a.formatUrl)(e)}function g(e){var t;let r,n,a,[g,b]=(0,u.useOptimistic)(s.IDLE_LINK_STATUS),P=(0,u.useRef)(null),{href:_,as:v,children:E,prefetch:O=null,passHref:j,replace:S,shallow:C,scroll:N,onClick:T,onMouseEnter:R,onTouchStart:x,legacyBehavior:A=!1,onNavigate:M,ref:L,unstable_dynamicOnHover:k,...I}=e;r=E,A&&("string"==typeof r||"number"==typeof r)&&(r=(0,o.jsx)("a",{children:r}));let U=u.default.useContext(i.AppRouterContext),w=!1!==O,F=!1!==O?null===(t=O)||"auto"===t?h.FetchStrategy.PPR:h.FetchStrategy.Full:h.FetchStrategy.PPR,{href:D,as:K}=u.default.useMemo(()=>{let e=y(_);return{href:e,as:v?y(v):e}},[_,v]);A&&(n=u.default.Children.only(r));let B=A?n&&"object"==typeof n&&n.ref:L,z=u.default.useCallback(e=>(null!==U&&(P.current=(0,s.mountLinkInstance)(e,D,U,F,w,b)),()=>{P.current&&((0,s.unmountLinkForCurrentNavigation)(P.current),P.current=null),(0,s.unmountPrefetchableInstance)(e)}),[w,D,U,F,b]),Q={ref:(0,l.useMergedRef)(z,B),onClick(e){A||"function"!=typeof T||T(e),A&&n.props&&"function"==typeof n.props.onClick&&n.props.onClick(e),U&&(e.defaultPrevented||function(e,t,r,n,o,a,i){let{nodeName:l}=e.currentTarget;if(!("A"===l.toUpperCase()&&function(e){let t=e.currentTarget.getAttribute("target");return t&&"_self"!==t||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.nativeEvent&&2===e.nativeEvent.which}(e)||e.currentTarget.hasAttribute("download"))){if(!(0,p.isLocalURL)(t)){o&&(e.preventDefault(),location.replace(t));return}if(e.preventDefault(),i){let e=!1;if(i({preventDefault:()=>{e=!0}}),e)return}u.default.startTransition(()=>{(0,d.dispatchNavigateAction)(r||t,o?"replace":"push",null==a||a,n.current)})}}(e,D,K,P,S,N,M))},onMouseEnter(e){A||"function"!=typeof R||R(e),A&&n.props&&"function"==typeof n.props.onMouseEnter&&n.props.onMouseEnter(e),U&&w&&(0,s.onNavigationIntent)(e.currentTarget,!0===k)},onTouchStart:function(e){A||"function"!=typeof x||x(e),A&&n.props&&"function"==typeof n.props.onTouchStart&&n.props.onTouchStart(e),U&&w&&(0,s.onNavigationIntent)(e.currentTarget,!0===k)}};return(0,f.isAbsoluteUrl)(K)?Q.href=K:A&&!j&&("a"!==n.type||"href"in n.props)||(Q.href=(0,c.addBasePath)(K)),a=A?u.default.cloneElement(n,Q):(0,o.jsx)("a",{...I,...Q,children:r}),(0,o.jsx)(m.Provider,{value:g,children:a})}let m=(0,u.createContext)(s.IDLE_LINK_STATUS),b=()=>(0,u.useContext)(m);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7076:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DecodeError:function(){return h},MiddlewareNotFoundError:function(){return b},MissingStaticPage:function(){return m},NormalizeError:function(){return y},PageNotFoundError:function(){return g},SP:function(){return p},ST:function(){return d},WEB_VITALS:function(){return r},execOnce:function(){return n},getDisplayName:function(){return l},getLocationOrigin:function(){return a},getURL:function(){return i},isAbsoluteUrl:function(){return u},isResSent:function(){return f},loadGetInitialProps:function(){return s},normalizeRepeatedSlashes:function(){return c},stringifyError:function(){return P}});let r=["CLS","FCP","FID","INP","LCP","TTFB"];function n(e){let t,r=!1;return function(){for(var n=arguments.length,o=Array(n),u=0;u<n;u++)o[u]=arguments[u];return r||(r=!0,t=e(...o)),t}}let o=/^[a-zA-Z][a-zA-Z\d+\-.]*?:/,u=e=>o.test(e);function a(){let{protocol:e,hostname:t,port:r}=window.location;return e+"//"+t+(r?":"+r:"")}function i(){let{href:e}=window.location,t=a();return e.substring(t.length)}function l(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function f(e){return e.finished||e.headersSent}function c(e){let t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?"?"+t.slice(1).join("?"):"")}async function s(e,t){let r=t.res||t.ctx&&t.ctx.res;if(!e.getInitialProps)return t.ctx&&t.Component?{pageProps:await s(t.Component,t.ctx)}:{};let n=await e.getInitialProps(t);if(r&&f(r))return n;if(!n)throw Object.defineProperty(Error('"'+l(e)+'.getInitialProps()" should resolve to an object. But found "'+n+'" instead.'),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});return n}let p="undefined"!=typeof performance,d=p&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);class h extends Error{}class y extends Error{}class g extends Error{constructor(e){super(),this.code="ENOENT",this.name="PageNotFoundError",this.message="Cannot find module for page: "+e}}class m extends Error{constructor(e,t){super(),this.message="Failed to load static file for page: "+e+" "+t}}class b extends Error{constructor(){super(),this.code="ENOENT",this.message="Cannot find the middleware module"}}function P(e){return JSON.stringify({message:e.message,stack:e.stack})}},7843:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isLocalURL",{enumerable:!0,get:function(){return u}});let n=r(7076),o=r(3506);function u(e){if(!(0,n.isAbsoluteUrl)(e))return!0;try{let t=(0,n.getLocationOrigin)(),r=new URL(e,t);return r.origin===t&&(0,o.hasBasePath)(r.pathname)}catch(e){return!1}}},9642:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{formatUrl:function(){return u},formatWithValidation:function(){return i},urlObjectKeys:function(){return a}});let n=r(4284)._(r(706)),o=/https?|ftp|gopher|file/;function u(e){let{auth:t,hostname:r}=e,u=e.protocol||"",a=e.pathname||"",i=e.hash||"",l=e.query||"",f=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?f=t+e.host:r&&(f=t+(~r.indexOf(":")?"["+r+"]":r),e.port&&(f+=":"+e.port)),l&&"object"==typeof l&&(l=String(n.urlQueryToSearchParams(l)));let c=e.search||l&&"?"+l||"";return u&&!u.endsWith(":")&&(u+=":"),e.slashes||(!u||o.test(u))&&!1!==f?(f="//"+(f||""),a&&"/"!==a[0]&&(a="/"+a)):f||(f=""),i&&"#"!==i[0]&&(i="#"+i),c&&"?"!==c[0]&&(c="?"+c),""+u+f+(a=a.replace(/[?#]/g,encodeURIComponent))+(c=c.replace("#","%23"))+i}let a=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"];function i(e){return u(e)}}}]);