station-kit 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) 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 +75 -16
  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 +108 -12
  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/THKSkCipW_pj0F6DRXYEG/_buildManifest.js +1 -0
  81. package/.next/standalone/packages/station-kit/.next/static/chunks/145-9e370afd2e5aba39.js +1 -0
  82. package/.next/standalone/packages/station-kit/.next/static/chunks/285-ff198f0a909c4fdd.js +1 -0
  83. package/.next/standalone/packages/station-kit/.next/static/chunks/561-33d912169940283e.js +1 -0
  84. package/.next/standalone/packages/station-kit/.next/static/chunks/935-dff12960528de017.js +1 -0
  85. package/.next/standalone/packages/station-kit/.next/static/chunks/app/_not-found/{page-ce21b4ba9038a5a7.js → page-67ef312aee40cfeb.js} +1 -1
  86. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/[id]/page-fe2f5467a0c68fef.js +1 -0
  87. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/dyn/[name]/page-0d2505242014f51e.js +1 -0
  88. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/dyn/[name]/v/[n]/page-5eac0507f49a00ec.js +1 -0
  89. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/new/page-3d02707043d24dc7.js +1 -0
  90. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-dee500ccc01f0821.js +1 -0
  91. package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-e14e14f3e5b0b8a9.js +1 -0
  92. package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-aac41ef7a470daab.js +1 -0
  93. package/.next/standalone/packages/station-kit/.next/static/chunks/app/playground/expression/page-dc9d91f3f50f4716.js +1 -0
  94. package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-9e4c4f751a4bea72.js +1 -0
  95. package/.next/standalone/packages/station-kit/.next/static/chunks/app/schedules/[id]/page-435f67be180b8e4f.js +1 -0
  96. package/.next/standalone/packages/station-kit/.next/static/chunks/app/schedules/new/page-f697c289c813496a.js +1 -0
  97. package/.next/standalone/packages/station-kit/.next/static/chunks/app/schedules/page-738d98dc0b63166e.js +1 -0
  98. package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-fc5654b31f57ac21.js +1 -0
  99. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-4b1c09a539a1ebcd.js +1 -0
  100. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-d2f2403dfede87cc.js +1 -0
  101. package/.next/standalone/packages/station-kit/.next/static/chunks/pages/_app-a3774a320f58a018.js +1 -0
  102. package/.next/standalone/packages/station-kit/package.json +7 -4
  103. package/dist/config/schema.d.ts +23 -0
  104. package/dist/config/schema.d.ts.map +1 -1
  105. package/dist/config/schema.js +2 -0
  106. package/dist/config/schema.js.map +1 -1
  107. package/dist/server/auth/keys.d.ts +91 -8
  108. package/dist/server/auth/keys.d.ts.map +1 -1
  109. package/dist/server/auth/keys.js +289 -54
  110. package/dist/server/auth/keys.js.map +1 -1
  111. package/dist/server/index.d.ts +5 -2
  112. package/dist/server/index.d.ts.map +1 -1
  113. package/dist/server/index.js +84 -9
  114. package/dist/server/index.js.map +1 -1
  115. package/dist/server/log-store.d.ts +102 -6
  116. package/dist/server/log-store.d.ts.map +1 -1
  117. package/dist/server/log-store.js +140 -32
  118. package/dist/server/log-store.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/broadcasts.d.ts.map +1 -1
  122. package/dist/server/routes/broadcasts.js +3 -1
  123. package/dist/server/routes/broadcasts.js.map +1 -1
  124. package/dist/server/routes/runs.js +1 -1
  125. package/dist/server/routes/runs.js.map +1 -1
  126. package/dist/server/routes/v1/definitions.d.ts +21 -0
  127. package/dist/server/routes/v1/definitions.d.ts.map +1 -0
  128. package/dist/server/routes/v1/definitions.js +139 -0
  129. package/dist/server/routes/v1/definitions.js.map +1 -0
  130. package/dist/server/routes/v1/expressions.d.ts +3 -0
  131. package/dist/server/routes/v1/expressions.d.ts.map +1 -0
  132. package/dist/server/routes/v1/expressions.js +56 -0
  133. package/dist/server/routes/v1/expressions.js.map +1 -0
  134. package/dist/server/routes/v1/keys.js +3 -3
  135. package/dist/server/routes/v1/keys.js.map +1 -1
  136. package/dist/server/routes/v1/runs.js +1 -1
  137. package/dist/server/routes/v1/runs.js.map +1 -1
  138. package/dist/server/routes/v1/schedules.d.ts +10 -0
  139. package/dist/server/routes/v1/schedules.d.ts.map +1 -0
  140. package/dist/server/routes/v1/schedules.js +169 -0
  141. package/dist/server/routes/v1/schedules.js.map +1 -0
  142. package/dist/server/routes/v1/trigger.d.ts.map +1 -1
  143. package/dist/server/routes/v1/trigger.js +21 -0
  144. package/dist/server/routes/v1/trigger.js.map +1 -1
  145. package/package.json +12 -9
  146. package/src/app/broadcasts/components/broadcast-builder.tsx +535 -0
  147. package/src/app/broadcasts/components/dag-editor.tsx +510 -0
  148. package/src/app/broadcasts/dyn/[name]/dynamic-detail.tsx +243 -0
  149. package/src/app/broadcasts/dyn/[name]/page.tsx +10 -0
  150. package/src/app/broadcasts/dyn/[name]/v/[n]/page.tsx +10 -0
  151. package/src/app/broadcasts/dyn/[name]/v/[n]/version-view.tsx +285 -0
  152. package/src/app/broadcasts/new/page.tsx +102 -0
  153. package/src/app/broadcasts/page.tsx +176 -91
  154. package/src/app/components/api-panel.tsx +151 -0
  155. package/src/app/components/shell.tsx +23 -0
  156. package/src/app/hooks/use-api.ts +117 -0
  157. package/src/app/playground/expression/page.tsx +245 -0
  158. package/src/app/schedules/[id]/page.tsx +10 -0
  159. package/src/app/schedules/[id]/schedule-editor.tsx +195 -0
  160. package/src/app/schedules/components/schedule-form.tsx +140 -0
  161. package/src/app/schedules/new/page.tsx +166 -0
  162. package/src/app/schedules/page.tsx +126 -0
  163. package/src/config/schema.ts +25 -0
  164. package/src/server/auth/keys.ts +348 -58
  165. package/src/server/index.ts +118 -11
  166. package/src/server/log-store.ts +196 -45
  167. package/src/server/middleware/auth.ts +1 -1
  168. package/src/server/routes/broadcasts.ts +3 -1
  169. package/src/server/routes/runs.ts +1 -1
  170. package/src/server/routes/v1/definitions.ts +164 -0
  171. package/src/server/routes/v1/expressions.ts +76 -0
  172. package/src/server/routes/v1/keys.ts +3 -3
  173. package/src/server/routes/v1/runs.ts +1 -1
  174. package/src/server/routes/v1/schedules.ts +176 -0
  175. package/src/server/routes/v1/trigger.ts +27 -0
  176. package/.next/standalone/packages/station-kit/.next/static/chunks/580-f007f4d4c050db4e.js +0 -1
  177. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/[id]/page-a0a20cccda13a0e9.js +0 -1
  178. package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-937eb876f9087bc9.js +0 -1
  179. package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-68cd71116ba65cd8.js +0 -1
  180. package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-70b0c0958c03459a.js +0 -1
  181. package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-01f8040619fe56c5.js +0 -1
  182. package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-beac11049f90da31.js +0 -1
  183. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-931e6a38a4a53d25.js +0 -1
  184. package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-6a123a355d93fec5.js +0 -1
  185. package/.next/standalone/packages/station-kit/.next/static/chunks/pages/_app-0a7b2e66ecbe3f0a.js +0 -1
  186. package/.next/standalone/packages/station-kit/.next/static/xYd6dn0Ox68DaamIrH_pB/_buildManifest.js +0 -1
  187. /package/.next/standalone/packages/station-kit/.next/static/{xYd6dn0Ox68DaamIrH_pB → THKSkCipW_pj0F6DRXYEG}/_ssgManifest.js +0 -0
@@ -1,12 +1,152 @@
1
1
  import crypto from "node:crypto";
2
- import Database from "better-sqlite3";
3
- export class KeyStore {
2
+ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeSync, } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { dirname } from "node:path";
5
+ /**
6
+ * Default ApiKeyStorageAdapter backed by a JSON file. Used by the Station
7
+ * server when no `keyStorage` is configured. Has no native dependencies —
8
+ * works on any Node 18+ install without compiling bindings.
9
+ *
10
+ * Crash-safety: writes go through a fsync'd tmp-file + rename, with a
11
+ * second fsync on the parent directory so the rename itself survives
12
+ * power loss. The keys file is created with `0o600` and the parent dir
13
+ * with `0o700` so a default umask doesn't expose key metadata.
14
+ *
15
+ * Single-process only: do not point two `createStation` instances at
16
+ * the same file or last-rename-wins will silently clobber writes. For
17
+ * multi-process or high-volume deployments, implement
18
+ * `ApiKeyStorageAdapter` against Postgres / MySQL / Redis.
19
+ */
20
+ export class FileKeyStorage {
21
+ filePath;
22
+ records = new Map();
23
+ constructor(options) {
24
+ this.filePath = options.filePath;
25
+ mkdirSync(dirname(this.filePath), { recursive: true, mode: 0o700 });
26
+ this.load();
27
+ }
28
+ load() {
29
+ if (!existsSync(this.filePath))
30
+ return;
31
+ try {
32
+ const raw = readFileSync(this.filePath, "utf8");
33
+ const data = JSON.parse(raw);
34
+ if (Array.isArray(data)) {
35
+ for (const r of data)
36
+ this.records.set(r.id, r);
37
+ }
38
+ }
39
+ catch {
40
+ // Corrupt or unreadable file — start fresh rather than throwing.
41
+ }
42
+ }
43
+ flush() {
44
+ const tmp = `${this.filePath}.tmp`;
45
+ const body = JSON.stringify(Array.from(this.records.values()), null, 2);
46
+ // Write tmp file with fsync so its bytes are durable before rename.
47
+ const fd = openSync(tmp, "w", 0o600);
48
+ try {
49
+ writeSync(fd, body);
50
+ fsyncSync(fd);
51
+ }
52
+ finally {
53
+ closeSync(fd);
54
+ }
55
+ renameSync(tmp, this.filePath);
56
+ // fsync the parent directory so the rename's directory entry survives
57
+ // a crash. Best-effort: opening a directory for fsync isn't supported
58
+ // on every platform (notably Windows), so swallow errors.
59
+ try {
60
+ const dirFd = openSync(dirname(this.filePath), "r");
61
+ try {
62
+ fsyncSync(dirFd);
63
+ }
64
+ finally {
65
+ closeSync(dirFd);
66
+ }
67
+ }
68
+ catch {
69
+ // Platform doesn't support directory fsync; rename + tmp fsync
70
+ // already give us most of the durability we can offer.
71
+ }
72
+ }
73
+ insert(record) {
74
+ this.records.set(record.id, { ...record });
75
+ this.flush();
76
+ }
77
+ findByHash(keyHash) {
78
+ for (const r of this.records.values()) {
79
+ if (r.keyHash === keyHash)
80
+ return { ...r };
81
+ }
82
+ return null;
83
+ }
84
+ list() {
85
+ return Array.from(this.records.values())
86
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
87
+ .map((r) => {
88
+ const { keyHash: _h, ...rest } = r;
89
+ return rest;
90
+ });
91
+ }
92
+ touch(id, lastUsedIso) {
93
+ const r = this.records.get(id);
94
+ if (!r)
95
+ return;
96
+ r.lastUsed = lastUsedIso;
97
+ this.flush();
98
+ }
99
+ revoke(id) {
100
+ const r = this.records.get(id);
101
+ if (!r)
102
+ return false;
103
+ r.revoked = true;
104
+ this.flush();
105
+ return true;
106
+ }
107
+ }
108
+ let cachedBetterSqlite3 = null;
109
+ function loadBetterSqlite3() {
110
+ if (cachedBetterSqlite3)
111
+ return cachedBetterSqlite3;
112
+ try {
113
+ const requireFn = createRequire(import.meta.url);
114
+ cachedBetterSqlite3 = requireFn("better-sqlite3");
115
+ return cachedBetterSqlite3;
116
+ }
117
+ catch (err) {
118
+ const reason = err instanceof Error ? err.message : String(err);
119
+ throw new Error(`SqliteKeyStorage requires the optional 'better-sqlite3' package, ` +
120
+ `which isn't installed. Install it with:\n` +
121
+ ` npm install better-sqlite3\n` +
122
+ `Or use FileKeyStorage (default) / MemoryKeyStorage instead.\n` +
123
+ `Underlying error: ${reason}`);
124
+ }
125
+ }
126
+ /**
127
+ * Optional ApiKeyStorageAdapter backed by better-sqlite3. Requires the
128
+ * `better-sqlite3` package to be installed separately — Station Kit no
129
+ * longer ships it as a hard dependency.
130
+ *
131
+ * Prefer `FileKeyStorage` (the default) unless you specifically need
132
+ * sqlite features (concurrent reads from multiple processes, large
133
+ * key catalogs, etc.).
134
+ */
135
+ export class SqliteKeyStorage {
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
137
  db;
5
- constructor(dbPath) {
6
- this.db = new Database(dbPath);
138
+ table;
139
+ constructor(options) {
140
+ const tableName = options.tableName ?? "api_keys";
141
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
142
+ throw new Error(`Invalid table name "${tableName}"`);
143
+ }
144
+ this.table = tableName;
145
+ const Database = loadBetterSqlite3();
146
+ this.db = new Database(options.dbPath);
7
147
  this.db.pragma("journal_mode = WAL");
8
148
  this.db.exec(`
9
- CREATE TABLE IF NOT EXISTS api_keys (
149
+ CREATE TABLE IF NOT EXISTS ${this.table} (
10
150
  id TEXT PRIMARY KEY,
11
151
  name TEXT NOT NULL,
12
152
  key_hash TEXT NOT NULL UNIQUE,
@@ -19,73 +159,168 @@ export class KeyStore {
19
159
  )
20
160
  `);
21
161
  }
22
- /** Generate a new API key. Returns the full key (only shown once) and the stored record. */
23
- create(name, scopes = ["trigger", "read"]) {
24
- const id = crypto.randomUUID();
25
- const rawKey = `sk_live_${crypto.randomBytes(16).toString("hex")}`;
26
- const keyHash = crypto.createHash("sha256").update(rawKey).digest("hex");
27
- const keyPrefix = rawKey.slice(0, 12);
28
- const createdAt = new Date().toISOString();
162
+ insert(record) {
29
163
  this.db.prepare(`
30
- INSERT INTO api_keys (id, name, key_hash, key_prefix, scopes, created_at)
31
- VALUES (?, ?, ?, ?, ?, ?)
32
- `).run(id, name, keyHash, keyPrefix, JSON.stringify(scopes), createdAt);
33
- return {
34
- key: rawKey,
35
- record: { id, name, keyHash, keyPrefix, scopes, createdAt, lastUsed: null, expiresAt: null, revoked: false },
36
- };
164
+ INSERT INTO ${this.table}
165
+ (id, name, key_hash, key_prefix, scopes, created_at, last_used, expires_at, revoked)
166
+ VALUES
167
+ (@id, @name, @key_hash, @key_prefix, @scopes, @created_at, @last_used, @expires_at, @revoked)
168
+ `).run({
169
+ id: record.id,
170
+ name: record.name,
171
+ key_hash: record.keyHash,
172
+ key_prefix: record.keyPrefix,
173
+ scopes: JSON.stringify(record.scopes),
174
+ created_at: record.createdAt,
175
+ last_used: record.lastUsed,
176
+ expires_at: record.expiresAt,
177
+ revoked: record.revoked ? 1 : 0,
178
+ });
37
179
  }
38
- /** Verify an API key. Returns the key record if valid, null otherwise. */
39
- verify(rawKey) {
40
- const keyHash = crypto.createHash("sha256").update(rawKey).digest("hex");
41
- const row = this.db.prepare(`
42
- SELECT id, name, key_hash, key_prefix, scopes, created_at, last_used, expires_at, revoked
43
- FROM api_keys WHERE key_hash = ?
44
- `).get(keyHash);
45
- if (!row)
46
- return null;
47
- if (row.revoked)
48
- return null;
49
- if (row.expires_at && new Date(row.expires_at) < new Date())
50
- return null;
51
- // Update last_used
52
- this.db.prepare("UPDATE api_keys SET last_used = ? WHERE id = ?").run(new Date().toISOString(), row.id);
53
- return {
54
- id: row.id,
55
- name: row.name,
56
- keyHash: row.key_hash,
57
- keyPrefix: row.key_prefix,
58
- scopes: JSON.parse(row.scopes),
59
- createdAt: row.created_at,
60
- lastUsed: row.last_used,
61
- expiresAt: row.expires_at,
62
- revoked: Boolean(row.revoked),
63
- };
180
+ findByHash(keyHash) {
181
+ const row = this.db
182
+ .prepare(`SELECT id, name, key_hash, key_prefix, scopes, created_at, last_used, expires_at, revoked
183
+ FROM ${this.table} WHERE key_hash = ?`)
184
+ .get(keyHash);
185
+ return row ? rowToApiKey(row) : null;
64
186
  }
65
- /** List all keys (without hashes). */
66
187
  list() {
67
- const rows = this.db.prepare(`
68
- SELECT id, name, key_prefix, scopes, created_at, last_used, expires_at, revoked
69
- FROM api_keys ORDER BY created_at DESC
70
- `).all();
188
+ const rows = this.db
189
+ .prepare(`SELECT id, name, key_prefix, scopes, created_at, last_used, expires_at, revoked
190
+ FROM ${this.table} ORDER BY created_at DESC`)
191
+ .all();
71
192
  return rows.map((row) => ({
72
193
  id: row.id,
73
194
  name: row.name,
74
195
  keyPrefix: row.key_prefix,
75
196
  scopes: JSON.parse(row.scopes),
76
197
  createdAt: row.created_at,
77
- lastUsed: row.last_used,
78
- expiresAt: row.expires_at,
198
+ lastUsed: row.last_used ?? null,
199
+ expiresAt: row.expires_at ?? null,
79
200
  revoked: Boolean(row.revoked),
80
201
  }));
81
202
  }
82
- /** Revoke a key by ID. */
203
+ touch(id, lastUsedIso) {
204
+ this.db.prepare(`UPDATE ${this.table} SET last_used = ? WHERE id = ?`).run(lastUsedIso, id);
205
+ }
83
206
  revoke(id) {
84
- const result = this.db.prepare("UPDATE api_keys SET revoked = 1 WHERE id = ?").run(id);
207
+ const result = this.db.prepare(`UPDATE ${this.table} SET revoked = 1 WHERE id = ?`).run(id);
85
208
  return result.changes > 0;
86
209
  }
87
210
  close() {
88
211
  this.db.close();
89
212
  }
90
213
  }
214
+ function rowToApiKey(row) {
215
+ return {
216
+ id: row.id,
217
+ name: row.name,
218
+ keyHash: row.key_hash,
219
+ keyPrefix: row.key_prefix,
220
+ scopes: JSON.parse(row.scopes),
221
+ createdAt: row.created_at,
222
+ lastUsed: row.last_used ?? null,
223
+ expiresAt: row.expires_at ?? null,
224
+ revoked: Boolean(row.revoked),
225
+ };
226
+ }
227
+ // ─── In-memory storage for tests / ephemeral deployments ────────────
228
+ export class MemoryKeyStorage {
229
+ records = new Map();
230
+ insert(record) {
231
+ this.records.set(record.id, { ...record });
232
+ }
233
+ findByHash(keyHash) {
234
+ for (const r of this.records.values()) {
235
+ if (r.keyHash === keyHash)
236
+ return { ...r };
237
+ }
238
+ return null;
239
+ }
240
+ list() {
241
+ return Array.from(this.records.values())
242
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
243
+ .map((r) => {
244
+ const { keyHash: _h, ...rest } = r;
245
+ return rest;
246
+ });
247
+ }
248
+ touch(id, lastUsedIso) {
249
+ const r = this.records.get(id);
250
+ if (r)
251
+ r.lastUsed = lastUsedIso;
252
+ }
253
+ revoke(id) {
254
+ const r = this.records.get(id);
255
+ if (!r)
256
+ return false;
257
+ r.revoked = true;
258
+ return true;
259
+ }
260
+ }
261
+ // ─── KeyStore — owns crypto, delegates persistence ──────────────────
262
+ export class KeyStore {
263
+ storage;
264
+ /**
265
+ * Pass an `ApiKeyStorageAdapter` for any backend. The string overload is
266
+ * retained for backwards compatibility — it constructs a FileKeyStorage
267
+ * at the given path. (Previously this returned a SqliteKeyStorage; SQLite
268
+ * is now opt-in to avoid native build dependencies.)
269
+ */
270
+ constructor(storageOrPath) {
271
+ if (typeof storageOrPath === "string") {
272
+ const filePath = storageOrPath.endsWith(".db")
273
+ ? storageOrPath.replace(/\.db$/, ".json")
274
+ : storageOrPath;
275
+ this.storage = new FileKeyStorage({ filePath });
276
+ }
277
+ else {
278
+ this.storage = storageOrPath;
279
+ }
280
+ }
281
+ /** Generate a new API key. Returns the full key (only shown once) and the stored record. */
282
+ async create(name, scopes = ["trigger", "read"]) {
283
+ const id = crypto.randomUUID();
284
+ const rawKey = `sk_live_${crypto.randomBytes(16).toString("hex")}`;
285
+ const keyHash = crypto.createHash("sha256").update(rawKey).digest("hex");
286
+ const keyPrefix = rawKey.slice(0, 12);
287
+ const createdAt = new Date().toISOString();
288
+ const record = {
289
+ id, name, keyHash, keyPrefix, scopes, createdAt,
290
+ lastUsed: null, expiresAt: null, revoked: false,
291
+ };
292
+ await this.storage.insert(record);
293
+ return { key: rawKey, record };
294
+ }
295
+ /** Verify an API key. Returns the key record if valid, null otherwise. */
296
+ async verify(rawKey) {
297
+ const keyHash = crypto.createHash("sha256").update(rawKey).digest("hex");
298
+ const record = await this.storage.findByHash(keyHash);
299
+ if (!record)
300
+ return null;
301
+ if (record.revoked)
302
+ return null;
303
+ if (record.expiresAt && new Date(record.expiresAt) < new Date())
304
+ return null;
305
+ // Touch is best-effort — don't block verification on the write. Wrap in
306
+ // an explicit deferred so a synchronous throw from a sync `touch()` is
307
+ // also swallowed, matching the async case.
308
+ Promise.resolve()
309
+ .then(() => this.storage.touch(record.id, new Date().toISOString()))
310
+ .catch(() => { });
311
+ return record;
312
+ }
313
+ /** List all keys (without hashes). */
314
+ async list() {
315
+ return this.storage.list();
316
+ }
317
+ /** Revoke a key by ID. */
318
+ async revoke(id) {
319
+ return this.storage.revoke(id);
320
+ }
321
+ async close() {
322
+ if (this.storage.close)
323
+ await this.storage.close();
324
+ }
325
+ }
91
326
  //# sourceMappingURL=keys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/server/auth/keys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/server/auth/keys.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,10 @@
1
1
  import type { StationConfig } from "../config/schema.js";
2
2
  import { KeyStore } from "./auth/keys.js";
3
- export { KeyStore } from "./auth/keys.js";
4
- export type { ApiKey } from "./auth/keys.js";
3
+ export { KeyStore, FileKeyStorage, SqliteKeyStorage, MemoryKeyStorage, } from "./auth/keys.js";
4
+ export type { ApiKey, ApiKeyPublic, ApiKeyStorageAdapter, FileKeyStorageOptions, SqliteKeyStorageOptions, } from "./auth/keys.js";
5
+ export { LogStore, FileLogStorage, MemoryLogStorage, } from "./log-store.js";
6
+ export type { LogStorageAdapter, FileLogStorageOptions, } from "./log-store.js";
7
+ export type { LogEntry } from "./log-buffer.js";
5
8
  export interface StationInstance {
6
9
  start(): Promise<void>;
7
10
  stop(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAc1C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA2SnH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,OAAO,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAiB1D,OAAO,EACL,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,MAAM,EACN,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAiWnH"}
@@ -3,19 +3,20 @@ import { createMiddleware } from "hono/factory";
3
3
  import { serve } from "@hono/node-server";
4
4
  import { resolve } from "node:path";
5
5
  import { existsSync } from "node:fs";
6
- import { SignalRunner, MemoryAdapter } from "station-signal";
6
+ import { SignalRunner, MemoryAdapter, parseInterval } from "station-signal";
7
7
  import { BroadcastRunner, BroadcastMemoryAdapter } from "station-broadcast";
8
+ import { ScheduleReconciler, } from "station-schedules";
8
9
  import { ensureStationDir } from "../station-dir.js";
9
10
  import { WebSocketHub } from "./ws.js";
10
11
  import { SSEHub } from "./sse.js";
11
12
  import { LogBuffer } from "./log-buffer.js";
12
- import { LogStore } from "./log-store.js";
13
+ import { LogStore, FileLogStorage } from "./log-store.js";
13
14
  import { StationSignalSubscriber, StationBroadcastSubscriber } from "./subscriber.js";
14
15
  import { healthRoutes } from "./routes/health.js";
15
16
  import { signalRoutes } from "./routes/signals.js";
16
17
  import { runRoutes } from "./routes/runs.js";
17
18
  import { broadcastRoutes } from "./routes/broadcasts.js";
18
- import { KeyStore } from "./auth/keys.js";
19
+ import { KeyStore, FileKeyStorage } from "./auth/keys.js";
19
20
  import { verifySessionToken, verifyCredentials, createSessionToken } from "./auth/session.js";
20
21
  import { authResolver } from "./middleware/auth.js";
21
22
  import { requireScope } from "./middleware/scope-guard.js";
@@ -28,20 +29,30 @@ import { v1TriggerRoutes } from "./routes/v1/trigger.js";
28
29
  import { v1KeyRoutes } from "./routes/v1/keys.js";
29
30
  import { v1AuthRoutes } from "./routes/v1/auth.js";
30
31
  import { v1EventRoutes } from "./routes/v1/events.js";
31
- export { KeyStore } from "./auth/keys.js";
32
+ import { v1DefinitionRoutes, v1DefinitionReadRoutes } from "./routes/v1/definitions.js";
33
+ import { v1ScheduleRoutes, v1ScheduleReadRoutes } from "./routes/v1/schedules.js";
34
+ import { v1ExpressionRoutes } from "./routes/v1/expressions.js";
35
+ export { KeyStore, FileKeyStorage, SqliteKeyStorage, MemoryKeyStorage, } from "./auth/keys.js";
36
+ export { LogStore, FileLogStorage, MemoryLogStorage, } from "./log-store.js";
32
37
  export async function createStation(config, cwd, nextPort) {
33
38
  const signalAdapter = config.adapter ?? new MemoryAdapter();
34
39
  const broadcastAdapter = config.broadcastAdapter ?? (config.broadcastsDir ? new BroadcastMemoryAdapter() : undefined);
35
40
  const { dataDir } = ensureStationDir(cwd, config.stationDir);
41
+ warnIfLegacySqliteFiles(dataDir);
36
42
  const wsHub = new WebSocketHub();
37
43
  const sseHub = new SSEHub();
38
44
  const logBuffer = new LogBuffer();
39
- const logStore = new LogStore(resolve(dataDir, "station-logs.db"));
45
+ const logStore = new LogStore(config.logStorage ?? new FileLogStorage({
46
+ filePath: resolve(dataDir, "station-logs.jsonl"),
47
+ onError: (err) => console.error("[station] log write failed:", err),
48
+ }));
40
49
  // Auth: create KeyStore and SessionConfig if auth is configured
41
50
  let keyStore;
42
51
  let sessionConfig;
43
52
  if (config.auth) {
44
- keyStore = new KeyStore(resolve(dataDir, "station-keys.db"));
53
+ const storage = config.auth.keyStorage
54
+ ?? new FileKeyStorage({ filePath: resolve(dataDir, "station-keys.json") });
55
+ keyStore = new KeyStore(storage);
45
56
  sessionConfig = {
46
57
  username: config.auth.username,
47
58
  password: config.auth.password,
@@ -68,7 +79,20 @@ export async function createStation(config, cwd, nextPort) {
68
79
  // Create runners if enabled
69
80
  let signalRunner;
70
81
  let broadcastRunner;
82
+ const scheduleAdapter = config.scheduleAdapter;
71
83
  if (config.runRunners) {
84
+ // Build schedule reconcilers up front. Each reconciler handles only the
85
+ // kinds it's responsible for; the runner ticks it once per loop.
86
+ const signalScheduleReconciler = scheduleAdapter
87
+ ? new ScheduleReconciler({
88
+ adapter: scheduleAdapter,
89
+ kinds: ["signal"],
90
+ parseInterval,
91
+ triggerFn: (s) => signalRunner.triggerSignal(s.target, s.input ?? {}),
92
+ hasPendingOrRunning: (s) => signalRunner.hasPendingOrRunningForSignal(s.target),
93
+ onError: (err) => console.error("[station] Signal schedule reconciler:", err),
94
+ })
95
+ : undefined;
72
96
  signalRunner = new SignalRunner({
73
97
  signalsDir,
74
98
  adapter: signalAdapter,
@@ -77,14 +101,26 @@ export async function createStation(config, cwd, nextPort) {
77
101
  maxAttempts: config.runner.maxAttempts,
78
102
  retryBackoffMs: config.runner.retryBackoffMs,
79
103
  subscribers: [stationSignalSub],
104
+ scheduleReconciler: signalScheduleReconciler,
80
105
  });
81
106
  if (broadcastsDir || broadcastAdapter) {
107
+ const broadcastScheduleReconciler = scheduleAdapter
108
+ ? new ScheduleReconciler({
109
+ adapter: scheduleAdapter,
110
+ kinds: ["broadcast-static", "broadcast-dynamic"],
111
+ parseInterval,
112
+ triggerFn: (s) => broadcastRunner.trigger(s.target, s.input ?? {}),
113
+ hasPendingOrRunning: (s) => broadcastRunner.hasPendingOrRunningForBroadcast(s.target),
114
+ onError: (err) => console.error("[station] Broadcast schedule reconciler:", err),
115
+ })
116
+ : undefined;
82
117
  broadcastRunner = new BroadcastRunner({
83
118
  signalRunner,
84
119
  broadcastsDir,
85
120
  adapter: broadcastAdapter ?? new BroadcastMemoryAdapter(),
86
121
  pollIntervalMs: config.broadcastRunner.pollIntervalMs,
87
122
  subscribers: [stationBroadcastSub],
123
+ scheduleReconciler: broadcastScheduleReconciler,
88
124
  });
89
125
  }
90
126
  }
@@ -166,6 +202,15 @@ export async function createStation(config, cwd, nextPort) {
166
202
  readRoutes.route("/", v1RunRoutes({ signalRunner, signalAdapter, logBuffer, logStore }));
167
203
  readRoutes.route("/", v1BroadcastRoutes({ broadcastRunner, broadcastAdapter, broadcastSubscriber: stationBroadcastSub }));
168
204
  readRoutes.route("/", v1EventRoutes({ sseHub }));
205
+ readRoutes.route("/", v1ExpressionRoutes());
206
+ // Schedule GET + preview are read-scoped; mutating routes are mounted under admin below.
207
+ readRoutes.route("/", v1ScheduleReadRoutes({ scheduleAdapter }));
208
+ readRoutes.route("/", v1DefinitionReadRoutes({
209
+ broadcastRunner,
210
+ broadcastAdapter,
211
+ signalRunner,
212
+ signalSubscriber: stationSignalSub,
213
+ }));
169
214
  v1.route("/", readRoutes);
170
215
  // Trigger-scope routes
171
216
  const triggerRoutes = new Hono();
@@ -198,10 +243,17 @@ export async function createStation(config, cwd, nextPort) {
198
243
  return c.json({ data: { cancelled: true } });
199
244
  });
200
245
  v1.route("/", cancelRoutes);
201
- // Admin-scope routes
246
+ // Admin-scope routes — destructive / mutating endpoints
202
247
  const adminRoutes = new Hono();
203
248
  adminRoutes.use("/*", requireScope("admin"));
204
249
  adminRoutes.route("/", v1KeyRoutes({ keyStore }));
250
+ adminRoutes.route("/", v1DefinitionRoutes({
251
+ broadcastRunner,
252
+ broadcastAdapter,
253
+ signalRunner,
254
+ signalSubscriber: stationSignalSub,
255
+ }));
256
+ adminRoutes.route("/", v1ScheduleRoutes({ scheduleAdapter }));
205
257
  v1.route("/", adminRoutes);
206
258
  app.route("/api/v1", v1);
207
259
  // ── Proxy to Next.js standalone server ──────────────────────────
@@ -283,12 +335,35 @@ export async function createStation(config, cwd, nextPort) {
283
335
  }
284
336
  wsHub.close();
285
337
  sseHub.close();
286
- logStore.close();
287
- keyStore?.close();
338
+ await logStore.close();
339
+ await keyStore?.close();
288
340
  if (httpServer) {
289
341
  httpServer.close();
290
342
  }
291
343
  },
292
344
  };
293
345
  }
346
+ // Existing deployments that ran older Station versions persisted keys
347
+ // to `station-keys.db` (SQLite) and run logs to `station-logs.db`. The
348
+ // new defaults are `station-keys.json` and `station-logs.jsonl`; the
349
+ // legacy files are NOT auto-migrated. Emit a one-time warning so an
350
+ // upgrade doesn't silently appear to wipe a user's API keys.
351
+ function warnIfLegacySqliteFiles(dataDir) {
352
+ const legacy = [
353
+ { file: "station-keys.db", replacement: "station-keys.json" },
354
+ { file: "station-logs.db", replacement: "station-logs.jsonl" },
355
+ ];
356
+ for (const { file, replacement } of legacy) {
357
+ const legacyPath = resolve(dataDir, file);
358
+ if (!existsSync(legacyPath))
359
+ continue;
360
+ const replacementPath = resolve(dataDir, replacement);
361
+ if (existsSync(replacementPath))
362
+ continue;
363
+ console.warn(`[station] Legacy ${file} detected at ${legacyPath} but no ${replacement} found. ` +
364
+ `Station no longer reads SQLite-backed defaults; data in ${file} will not be loaded. ` +
365
+ `If you need the contents, export them with the better-sqlite3 CLI before upgrading. ` +
366
+ `To suppress this warning, delete or rename ${file}.`);
367
+ }
368
+ }
294
369
  //# sourceMappingURL=index.js.map