station-kit 1.0.7 → 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 (177) 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 +10 -3
  5. package/.next/standalone/packages/station-kit/.next/build-manifest.json +3 -3
  6. package/.next/standalone/packages/station-kit/.next/prerender-manifest.json +112 -16
  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 +10 -3
  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 +10 -2
  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/index.d.ts +2 -0
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +2 -0
  110. package/dist/index.js.map +1 -1
  111. package/dist/server/auth/keys.d.ts +56 -8
  112. package/dist/server/auth/keys.d.ts.map +1 -1
  113. package/dist/server/auth/keys.js +155 -53
  114. package/dist/server/auth/keys.js.map +1 -1
  115. package/dist/server/index.d.ts +7 -0
  116. package/dist/server/index.d.ts.map +1 -1
  117. package/dist/server/index.js +55 -5
  118. package/dist/server/index.js.map +1 -1
  119. package/dist/server/middleware/auth.js +1 -1
  120. package/dist/server/middleware/auth.js.map +1 -1
  121. package/dist/server/routes/v1/definitions.d.ts +21 -0
  122. package/dist/server/routes/v1/definitions.d.ts.map +1 -0
  123. package/dist/server/routes/v1/definitions.js +139 -0
  124. package/dist/server/routes/v1/definitions.js.map +1 -0
  125. package/dist/server/routes/v1/expressions.d.ts +3 -0
  126. package/dist/server/routes/v1/expressions.d.ts.map +1 -0
  127. package/dist/server/routes/v1/expressions.js +56 -0
  128. package/dist/server/routes/v1/expressions.js.map +1 -0
  129. package/dist/server/routes/v1/keys.js +3 -3
  130. package/dist/server/routes/v1/keys.js.map +1 -1
  131. package/dist/server/routes/v1/schedules.d.ts +10 -0
  132. package/dist/server/routes/v1/schedules.d.ts.map +1 -0
  133. package/dist/server/routes/v1/schedules.js +169 -0
  134. package/dist/server/routes/v1/schedules.js.map +1 -0
  135. package/dist/server/routes/v1/trigger.d.ts.map +1 -1
  136. package/dist/server/routes/v1/trigger.js +21 -0
  137. package/dist/server/routes/v1/trigger.js.map +1 -1
  138. package/package.json +15 -7
  139. package/src/app/broadcasts/components/broadcast-builder.tsx +535 -0
  140. package/src/app/broadcasts/components/dag-editor.tsx +510 -0
  141. package/src/app/broadcasts/dyn/[name]/dynamic-detail.tsx +243 -0
  142. package/src/app/broadcasts/dyn/[name]/page.tsx +10 -0
  143. package/src/app/broadcasts/dyn/[name]/v/[n]/page.tsx +10 -0
  144. package/src/app/broadcasts/dyn/[name]/v/[n]/version-view.tsx +285 -0
  145. package/src/app/broadcasts/new/page.tsx +102 -0
  146. package/src/app/broadcasts/page.tsx +176 -91
  147. package/src/app/components/api-panel.tsx +151 -0
  148. package/src/app/components/shell.tsx +23 -0
  149. package/src/app/hooks/use-api.ts +117 -0
  150. package/src/app/playground/expression/page.tsx +245 -0
  151. package/src/app/schedules/[id]/page.tsx +10 -0
  152. package/src/app/schedules/[id]/schedule-editor.tsx +195 -0
  153. package/src/app/schedules/components/schedule-form.tsx +140 -0
  154. package/src/app/schedules/new/page.tsx +166 -0
  155. package/src/app/schedules/page.tsx +126 -0
  156. package/src/config/schema.ts +14 -0
  157. package/src/index.ts +2 -0
  158. package/src/server/auth/keys.ts +191 -56
  159. package/src/server/index.ts +78 -5
  160. package/src/server/middleware/auth.ts +1 -1
  161. package/src/server/routes/v1/definitions.ts +164 -0
  162. package/src/server/routes/v1/expressions.ts +76 -0
  163. package/src/server/routes/v1/keys.ts +3 -3
  164. package/src/server/routes/v1/schedules.ts +176 -0
  165. package/src/server/routes/v1/trigger.ts +27 -0
  166. package/.next/standalone/packages/station-kit/.next/static/chunks/580-f007f4d4c050db4e.js +0 -1
  167. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/[id]/page-a0a20cccda13a0e9.js +0 -1
  168. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-937eb876f9087bc9.js +0 -1
  169. package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-68cd71116ba65cd8.js +0 -1
  170. package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-70b0c0958c03459a.js +0 -1
  171. package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-01f8040619fe56c5.js +0 -1
  172. package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-beac11049f90da31.js +0 -1
  173. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-931e6a38a4a53d25.js +0 -1
  174. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-6a123a355d93fec5.js +0 -1
  175. package/.next/standalone/packages/station-kit/.next/static/chunks/pages/_app-0a7b2e66ecbe3f0a.js +0 -1
  176. package/.next/standalone/packages/station-kit/.next/static/pHHaxeGaet0VW1dhcIcuY/_buildManifest.js +0 -1
  177. /package/.next/standalone/packages/station-kit/.next/static/{pHHaxeGaet0VW1dhcIcuY → 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,10 +37,29 @@ 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";
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";
35
55
 
36
56
  export interface StationInstance {
37
57
  start(): Promise<void>;
38
58
  stop(): Promise<void>;
59
+ /** The KeyStore instance (available when auth is configured). */
60
+ keyStore?: KeyStore;
61
+ /** The resolved data directory path. */
62
+ dataDir: string;
39
63
  }
40
64
 
41
65
  export async function createStation(config: StationConfig, cwd: string, nextPort?: number): Promise<StationInstance> {
@@ -55,7 +79,9 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
55
79
  let sessionConfig: SessionConfig | undefined;
56
80
 
57
81
  if (config.auth) {
58
- 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);
59
85
  sessionConfig = {
60
86
  username: config.auth.username,
61
87
  password: config.auth.password,
@@ -87,8 +113,23 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
87
113
  // Create runners if enabled
88
114
  let signalRunner: SignalRunner | undefined;
89
115
  let broadcastRunner: BroadcastRunner | undefined;
116
+ const scheduleAdapter: ScheduleAdapter | undefined = config.scheduleAdapter;
90
117
 
91
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
+
92
133
  signalRunner = new SignalRunner({
93
134
  signalsDir,
94
135
  adapter: signalAdapter,
@@ -97,15 +138,29 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
97
138
  maxAttempts: config.runner.maxAttempts,
98
139
  retryBackoffMs: config.runner.retryBackoffMs,
99
140
  subscribers: [stationSignalSub],
141
+ scheduleReconciler: signalScheduleReconciler,
100
142
  });
101
143
 
102
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
+
103
157
  broadcastRunner = new BroadcastRunner({
104
158
  signalRunner,
105
159
  broadcastsDir,
106
160
  adapter: broadcastAdapter ?? new BroadcastMemoryAdapter(),
107
161
  pollIntervalMs: config.broadcastRunner.pollIntervalMs,
108
162
  subscribers: [stationBroadcastSub],
163
+ scheduleReconciler: broadcastScheduleReconciler,
109
164
  });
110
165
  }
111
166
  }
@@ -197,6 +252,15 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
197
252
  readRoutes.route("/", v1RunRoutes({ signalRunner, signalAdapter, logBuffer, logStore }));
198
253
  readRoutes.route("/", v1BroadcastRoutes({ broadcastRunner, broadcastAdapter, broadcastSubscriber: stationBroadcastSub }));
199
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
+ }));
200
264
  v1.route("/", readRoutes);
201
265
 
202
266
  // Trigger-scope routes
@@ -232,10 +296,17 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
232
296
  });
233
297
  v1.route("/", cancelRoutes);
234
298
 
235
- // Admin-scope routes
299
+ // Admin-scope routes — destructive / mutating endpoints
236
300
  const adminRoutes = new Hono();
237
301
  adminRoutes.use("/*", requireScope("admin"));
238
302
  adminRoutes.route("/", v1KeyRoutes({ keyStore }));
303
+ adminRoutes.route("/", v1DefinitionRoutes({
304
+ broadcastRunner,
305
+ broadcastAdapter,
306
+ signalRunner,
307
+ signalSubscriber: stationSignalSub,
308
+ }));
309
+ adminRoutes.route("/", v1ScheduleRoutes({ scheduleAdapter }));
239
310
  v1.route("/", adminRoutes);
240
311
 
241
312
  app.route("/api/v1", v1);
@@ -291,6 +362,8 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
291
362
  let httpServer: Server | null = null;
292
363
 
293
364
  return {
365
+ keyStore,
366
+ dataDir,
294
367
  async start() {
295
368
  // Start runners (non-blocking — they have internal poll loops)
296
369
  if (config.runRunners) {
@@ -329,7 +402,7 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
329
402
  wsHub.close();
330
403
  sseHub.close();
331
404
  logStore.close();
332
- keyStore?.close();
405
+ await keyStore?.close();
333
406
  if (httpServer) {
334
407
  httpServer.close();
335
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)}}}]);