station-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cli-main.d.ts +2 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +58 -0
- package/dist/cli-main.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/loader.d.ts +3 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +29 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +36 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +40 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/server/auth/keys.d.ts +28 -0
- package/dist/server/auth/keys.d.ts.map +1 -0
- package/dist/server/auth/keys.js +91 -0
- package/dist/server/auth/keys.js.map +1 -0
- package/dist/server/auth/session.d.ts +9 -0
- package/dist/server/auth/session.d.ts.map +1 -0
- package/dist/server/auth/session.js +42 -0
- package/dist/server/auth/session.js.map +1 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +253 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/log-buffer.d.ts +20 -0
- package/dist/server/log-buffer.d.ts.map +1 -0
- package/dist/server/log-buffer.js +33 -0
- package/dist/server/log-buffer.js.map +1 -0
- package/dist/server/log-store.d.ts +11 -0
- package/dist/server/log-store.d.ts.map +1 -0
- package/dist/server/log-store.js +40 -0
- package/dist/server/log-store.js.map +1 -0
- package/dist/server/metadata.d.ts +38 -0
- package/dist/server/metadata.d.ts.map +1 -0
- package/dist/server/metadata.js +130 -0
- package/dist/server/metadata.js.map +1 -0
- package/dist/server/middleware/auth.d.ts +12 -0
- package/dist/server/middleware/auth.d.ts.map +1 -0
- package/dist/server/middleware/auth.js +42 -0
- package/dist/server/middleware/auth.js.map +1 -0
- package/dist/server/middleware/rate-limit.d.ts +15 -0
- package/dist/server/middleware/rate-limit.d.ts.map +1 -0
- package/dist/server/middleware/rate-limit.js +36 -0
- package/dist/server/middleware/rate-limit.js.map +1 -0
- package/dist/server/middleware/scope-guard.d.ts +9 -0
- package/dist/server/middleware/scope-guard.d.ts.map +1 -0
- package/dist/server/middleware/scope-guard.js +17 -0
- package/dist/server/middleware/scope-guard.js.map +1 -0
- package/dist/server/routes/broadcasts.d.ts +12 -0
- package/dist/server/routes/broadcasts.d.ts.map +1 -0
- package/dist/server/routes/broadcasts.js +135 -0
- package/dist/server/routes/broadcasts.js.map +1 -0
- package/dist/server/routes/health.d.ts +9 -0
- package/dist/server/routes/health.d.ts.map +1 -0
- package/dist/server/routes/health.js +27 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/runs.d.ts +12 -0
- package/dist/server/routes/runs.d.ts.map +1 -0
- package/dist/server/routes/runs.js +122 -0
- package/dist/server/routes/runs.js.map +1 -0
- package/dist/server/routes/signals.d.ts +10 -0
- package/dist/server/routes/signals.d.ts.map +1 -0
- package/dist/server/routes/signals.js +120 -0
- package/dist/server/routes/signals.js.map +1 -0
- package/dist/server/routes/v1/auth.d.ts +7 -0
- package/dist/server/routes/v1/auth.d.ts.map +1 -0
- package/dist/server/routes/v1/auth.js +28 -0
- package/dist/server/routes/v1/auth.js.map +1 -0
- package/dist/server/routes/v1/broadcasts.d.ts +10 -0
- package/dist/server/routes/v1/broadcasts.d.ts.map +1 -0
- package/dist/server/routes/v1/broadcasts.js +68 -0
- package/dist/server/routes/v1/broadcasts.js.map +1 -0
- package/dist/server/routes/v1/events.d.ts +7 -0
- package/dist/server/routes/v1/events.d.ts.map +1 -0
- package/dist/server/routes/v1/events.js +57 -0
- package/dist/server/routes/v1/events.js.map +1 -0
- package/dist/server/routes/v1/health.d.ts +9 -0
- package/dist/server/routes/v1/health.d.ts.map +1 -0
- package/dist/server/routes/v1/health.js +31 -0
- package/dist/server/routes/v1/health.js.map +1 -0
- package/dist/server/routes/v1/keys.d.ts +7 -0
- package/dist/server/routes/v1/keys.d.ts.map +1 -0
- package/dist/server/routes/v1/keys.js +43 -0
- package/dist/server/routes/v1/keys.js.map +1 -0
- package/dist/server/routes/v1/runs.d.ts +12 -0
- package/dist/server/routes/v1/runs.d.ts.map +1 -0
- package/dist/server/routes/v1/runs.js +76 -0
- package/dist/server/routes/v1/runs.js.map +1 -0
- package/dist/server/routes/v1/signals.d.ts +9 -0
- package/dist/server/routes/v1/signals.d.ts.map +1 -0
- package/dist/server/routes/v1/signals.js +33 -0
- package/dist/server/routes/v1/signals.js.map +1 -0
- package/dist/server/routes/v1/trigger.d.ts +12 -0
- package/dist/server/routes/v1/trigger.d.ts.map +1 -0
- package/dist/server/routes/v1/trigger.js +73 -0
- package/dist/server/routes/v1/trigger.js.map +1 -0
- package/dist/server/sse.d.ts +19 -0
- package/dist/server/sse.d.ts.map +1 -0
- package/dist/server/sse.js +51 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/server/subscriber.d.ts +128 -0
- package/dist/server/subscriber.d.ts.map +1 -0
- package/dist/server/subscriber.js +246 -0
- package/dist/server/subscriber.js.map +1 -0
- package/dist/server/ws.d.ts +15 -0
- package/dist/server/ws.d.ts.map +1 -0
- package/dist/server/ws.js +32 -0
- package/dist/server/ws.js.map +1 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +10 -0
- package/package.json +49 -0
- package/src/app/broadcasts/[id]/page.tsx +511 -0
- package/src/app/broadcasts/page.tsx +158 -0
- package/src/app/components/auth-provider.tsx +75 -0
- package/src/app/components/breadcrumb-provider.tsx +18 -0
- package/src/app/components/dag-view.tsx +380 -0
- package/src/app/components/empty-state.tsx +7 -0
- package/src/app/components/json-viewer.tsx +153 -0
- package/src/app/components/login-page.tsx +78 -0
- package/src/app/components/node-detail.tsx +158 -0
- package/src/app/components/pulse-dot.tsx +8 -0
- package/src/app/components/relative-time.tsx +34 -0
- package/src/app/components/run-table.tsx +96 -0
- package/src/app/components/schema-form.tsx +121 -0
- package/src/app/components/shell.tsx +203 -0
- package/src/app/components/status-badge.tsx +10 -0
- package/src/app/components/step-timeline.tsx +134 -0
- package/src/app/components/theme-provider.tsx +45 -0
- package/src/app/components/workflow-node-sidebar.tsx +68 -0
- package/src/app/globals.css +1523 -0
- package/src/app/hooks/use-api.ts +129 -0
- package/src/app/hooks/use-breadcrumb.ts +37 -0
- package/src/app/hooks/use-realtime.ts +68 -0
- package/src/app/hooks/use-station.tsx +34 -0
- package/src/app/layout.tsx +42 -0
- package/src/app/page.tsx +275 -0
- package/src/app/runs/[id]/page.tsx +277 -0
- package/src/app/signals/[name]/page.tsx +250 -0
- package/src/app/signals/page.tsx +99 -0
- package/src/cli-main.ts +70 -0
- package/src/cli.ts +27 -0
- package/src/config/loader.ts +33 -0
- package/src/config/schema.ts +80 -0
- package/src/index.ts +7 -0
- package/src/server/auth/keys.ts +112 -0
- package/src/server/auth/session.ts +48 -0
- package/src/server/index.ts +296 -0
- package/src/server/log-buffer.ts +43 -0
- package/src/server/log-store.ts +56 -0
- package/src/server/metadata.ts +180 -0
- package/src/server/middleware/auth.ts +50 -0
- package/src/server/middleware/rate-limit.ts +61 -0
- package/src/server/middleware/scope-guard.ts +20 -0
- package/src/server/routes/broadcasts.ts +160 -0
- package/src/server/routes/health.ts +37 -0
- package/src/server/routes/runs.ts +149 -0
- package/src/server/routes/signals.ts +153 -0
- package/src/server/routes/v1/auth.ts +47 -0
- package/src/server/routes/v1/broadcasts.ts +84 -0
- package/src/server/routes/v1/events.ts +71 -0
- package/src/server/routes/v1/health.ts +41 -0
- package/src/server/routes/v1/keys.ts +57 -0
- package/src/server/routes/v1/runs.ts +97 -0
- package/src/server/routes/v1/signals.ts +44 -0
- package/src/server/routes/v1/trigger.ts +111 -0
- package/src/server/sse.ts +70 -0
- package/src/server/subscriber.ts +288 -0
- package/src/server/ws.ts +44 -0
- package/station.config.example.ts +16 -0
- package/tsconfig.json +12 -0
- package/tsconfig.next.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 porkytheblack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-main.d.ts","sourceRoot":"","sources":["../src/cli-main.ts"],"names":[],"mappings":""}
|
package/dist/cli-main.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { loadConfig } from "./config/loader.js";
|
|
4
|
+
import { createStation } from "./server/index.js";
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
const config = await loadConfig(cwd);
|
|
7
|
+
const station = await createStation(config, cwd);
|
|
8
|
+
await station.start();
|
|
9
|
+
// Start Next.js dev server as child process
|
|
10
|
+
const nextPort = config.port + 1;
|
|
11
|
+
const stationRoot = resolve(import.meta.dirname, "..");
|
|
12
|
+
const nextProcess = spawn("npx", ["next", "dev", "--port", String(nextPort), "--hostname", config.host], {
|
|
13
|
+
cwd: stationRoot,
|
|
14
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
NEXT_PUBLIC_STATION_API: `http://${config.host}:${config.port}`,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
nextProcess.stdout?.on("data", (chunk) => {
|
|
21
|
+
const msg = chunk.toString().trim();
|
|
22
|
+
if (msg)
|
|
23
|
+
console.log(`[station:ui] ${msg}`);
|
|
24
|
+
});
|
|
25
|
+
nextProcess.stderr?.on("data", (chunk) => {
|
|
26
|
+
const msg = chunk.toString().trim();
|
|
27
|
+
// Filter out noisy Next.js dev warnings
|
|
28
|
+
if (msg && !msg.includes("ExperimentalWarning")) {
|
|
29
|
+
console.error(`[station:ui] ${msg}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
console.log(`[station] Dashboard on http://${config.host}:${nextPort}`);
|
|
33
|
+
// Open browser
|
|
34
|
+
if (config.open) {
|
|
35
|
+
const url = `http://${config.host}:${nextPort}`;
|
|
36
|
+
const { execFile } = await import("node:child_process");
|
|
37
|
+
const platform = process.platform;
|
|
38
|
+
await new Promise((res) => setTimeout(() => res(true), 5000));
|
|
39
|
+
if (platform === "darwin") {
|
|
40
|
+
execFile("open", [url]);
|
|
41
|
+
}
|
|
42
|
+
else if (platform === "linux") {
|
|
43
|
+
execFile("xdg-open", [url]);
|
|
44
|
+
}
|
|
45
|
+
else if (platform === "win32") {
|
|
46
|
+
execFile("cmd", ["/c", "start", url]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Graceful shutdown
|
|
50
|
+
const shutdown = async () => {
|
|
51
|
+
console.log("\n[station] Shutting down...");
|
|
52
|
+
nextProcess.kill("SIGTERM");
|
|
53
|
+
await station.stop();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
};
|
|
56
|
+
process.on("SIGINT", shutdown);
|
|
57
|
+
process.on("SIGTERM", shutdown);
|
|
58
|
+
//# sourceMappingURL=cli-main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-main.js","sourceRoot":"","sources":["../src/cli-main.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Station CLI launcher — re-executes itself with tsx loader so user .ts files
|
|
3
|
+
// (signals, broadcasts, configs) can be imported with full resolution.
|
|
4
|
+
import { execPath } from "node:process";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
const MARKER = "__STATION_TSX_LOADED";
|
|
8
|
+
if (!process.env[MARKER]) {
|
|
9
|
+
// Re-exec with --import tsx
|
|
10
|
+
const main = fileURLToPath(new URL("./cli-main.js", import.meta.url));
|
|
11
|
+
const child = spawn(execPath, ["--import", "tsx", main], {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
env: { ...process.env, [MARKER]: "1" },
|
|
14
|
+
});
|
|
15
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
16
|
+
child.on("error", (err) => {
|
|
17
|
+
console.error("[station] Failed to start:", err.message);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// Already loaded with tsx — import main
|
|
23
|
+
await import("./cli-main.js");
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,8EAA8E;AAC9E,uEAAuE;AAEvE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,MAAM,GAAG,sBAAsB,CAAC;AAEtC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;IACzB,4BAA4B;IAC5B,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE;QACvD,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE;KACvC,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACxB,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,wCAAwC;IACxC,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAQhE,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAapE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { resolveConfig } from "./schema.js";
|
|
5
|
+
const CONFIG_NAMES = [
|
|
6
|
+
"station.config.ts",
|
|
7
|
+
"station.config.js",
|
|
8
|
+
"station.config.mjs",
|
|
9
|
+
];
|
|
10
|
+
export async function loadConfig(cwd) {
|
|
11
|
+
const configPath = findConfigFile(cwd);
|
|
12
|
+
if (!configPath) {
|
|
13
|
+
console.log("[station] No config file found. Using defaults.");
|
|
14
|
+
return resolveConfig({});
|
|
15
|
+
}
|
|
16
|
+
console.log(`[station] Loading ${configPath}`);
|
|
17
|
+
const mod = await import(pathToFileURL(resolve(configPath)).href);
|
|
18
|
+
const raw = mod.default ?? mod;
|
|
19
|
+
return resolveConfig(raw);
|
|
20
|
+
}
|
|
21
|
+
function findConfigFile(cwd) {
|
|
22
|
+
for (const name of CONFIG_NAMES) {
|
|
23
|
+
const candidate = join(cwd, name);
|
|
24
|
+
if (existsSync(candidate))
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AAEhE,MAAM,YAAY,GAAG;IACnB,mBAAmB;IACnB,mBAAmB;IACnB,oBAAoB;CACrB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IAEvC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;QAC/D,OAAO,aAAa,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,qBAAqB,UAAU,EAAE,CAAC,CAAC;IAE/C,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;IAC/B,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,cAAc,CAAC,GAAW;IACjC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAClC,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SignalQueueAdapter } from "station-signal";
|
|
2
|
+
import type { BroadcastQueueAdapter } from "station-broadcast";
|
|
3
|
+
export interface AuthConfig {
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
sessionTtlMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface RunnerConfig {
|
|
9
|
+
pollIntervalMs: number;
|
|
10
|
+
maxConcurrent: number;
|
|
11
|
+
maxAttempts: number;
|
|
12
|
+
retryBackoffMs: number;
|
|
13
|
+
}
|
|
14
|
+
export interface BroadcastRunnerConfig {
|
|
15
|
+
pollIntervalMs: number;
|
|
16
|
+
}
|
|
17
|
+
export interface StationConfig {
|
|
18
|
+
port: number;
|
|
19
|
+
host: string;
|
|
20
|
+
adapter?: SignalQueueAdapter;
|
|
21
|
+
broadcastAdapter?: BroadcastQueueAdapter;
|
|
22
|
+
signalsDir?: string;
|
|
23
|
+
broadcastsDir?: string;
|
|
24
|
+
runner: RunnerConfig;
|
|
25
|
+
broadcastRunner: BroadcastRunnerConfig;
|
|
26
|
+
runRunners: boolean;
|
|
27
|
+
open: boolean;
|
|
28
|
+
logLevel: "debug" | "info" | "warn" | "error";
|
|
29
|
+
auth?: AuthConfig;
|
|
30
|
+
}
|
|
31
|
+
export type StationUserConfig = Partial<Omit<StationConfig, "runner" | "broadcastRunner">> & {
|
|
32
|
+
runner?: Partial<RunnerConfig>;
|
|
33
|
+
broadcastRunner?: Partial<BroadcastRunnerConfig>;
|
|
34
|
+
};
|
|
35
|
+
export declare function resolveConfig(input: StationUserConfig): StationConfig;
|
|
36
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/D,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,eAAe,EAAE,qBAAqB,CAAC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC9C,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,iBAAiB,CAAC,CAAC,GAAG;IAC3F,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAClD,CAAC;AAmBF,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,aAAa,CAsBrE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const DEFAULTS = {
|
|
2
|
+
port: 4400,
|
|
3
|
+
host: "localhost",
|
|
4
|
+
runner: {
|
|
5
|
+
pollIntervalMs: 1000,
|
|
6
|
+
maxConcurrent: 5,
|
|
7
|
+
maxAttempts: 1,
|
|
8
|
+
retryBackoffMs: 1000,
|
|
9
|
+
},
|
|
10
|
+
broadcastRunner: {
|
|
11
|
+
pollIntervalMs: 1000,
|
|
12
|
+
},
|
|
13
|
+
runRunners: true,
|
|
14
|
+
open: true,
|
|
15
|
+
logLevel: "info",
|
|
16
|
+
};
|
|
17
|
+
export function resolveConfig(input) {
|
|
18
|
+
return {
|
|
19
|
+
port: input.port ?? DEFAULTS.port,
|
|
20
|
+
host: input.host ?? DEFAULTS.host,
|
|
21
|
+
adapter: input.adapter,
|
|
22
|
+
broadcastAdapter: input.broadcastAdapter,
|
|
23
|
+
signalsDir: input.signalsDir,
|
|
24
|
+
broadcastsDir: input.broadcastsDir,
|
|
25
|
+
runner: {
|
|
26
|
+
pollIntervalMs: input.runner?.pollIntervalMs ?? DEFAULTS.runner.pollIntervalMs,
|
|
27
|
+
maxConcurrent: input.runner?.maxConcurrent ?? DEFAULTS.runner.maxConcurrent,
|
|
28
|
+
maxAttempts: input.runner?.maxAttempts ?? DEFAULTS.runner.maxAttempts,
|
|
29
|
+
retryBackoffMs: input.runner?.retryBackoffMs ?? DEFAULTS.runner.retryBackoffMs,
|
|
30
|
+
},
|
|
31
|
+
broadcastRunner: {
|
|
32
|
+
pollIntervalMs: input.broadcastRunner?.pollIntervalMs ?? DEFAULTS.broadcastRunner.pollIntervalMs,
|
|
33
|
+
},
|
|
34
|
+
runRunners: input.runRunners ?? DEFAULTS.runRunners,
|
|
35
|
+
open: input.open ?? DEFAULTS.open,
|
|
36
|
+
logLevel: input.logLevel ?? DEFAULTS.logLevel,
|
|
37
|
+
auth: input.auth,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAwCA,MAAM,QAAQ,GAAkB;IAC9B,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE;QACN,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,CAAC;QAChB,WAAW,EAAE,CAAC;QACd,cAAc,EAAE,IAAI;KACrB;IACD,eAAe,EAAE;QACf,cAAc,EAAE,IAAI;KACrB;IACD,UAAU,EAAE,IAAI;IAChB,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,MAAM;CACjB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,KAAwB;IACpD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI;QACjC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI;QACjC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,MAAM,EAAE;YACN,cAAc,EAAE,KAAK,CAAC,MAAM,EAAE,cAAc,IAAI,QAAQ,CAAC,MAAM,CAAC,cAAc;YAC9E,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,IAAI,QAAQ,CAAC,MAAM,CAAC,aAAa;YAC3E,WAAW,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,QAAQ,CAAC,MAAM,CAAC,WAAW;YACrE,cAAc,EAAE,KAAK,CAAC,MAAM,EAAE,cAAc,IAAI,QAAQ,CAAC,MAAM,CAAC,cAAc;SAC/E;QACD,eAAe,EAAE;YACf,cAAc,EAAE,KAAK,CAAC,eAAe,EAAE,cAAc,IAAI,QAAQ,CAAC,eAAe,CAAC,cAAc;SACjG;QACD,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,QAAQ,CAAC,UAAU;QACnD,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI;QACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ;QAC7C,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { StationUserConfig } from "./config/schema.js";
|
|
2
|
+
export declare function defineConfig(config: StationUserConfig): StationUserConfig;
|
|
3
|
+
export type { StationConfig, StationUserConfig, AuthConfig } from "./config/schema.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,CAEzE;AAED,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,MAAyB;IACpD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ApiKey {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
keyHash: string;
|
|
5
|
+
keyPrefix: string;
|
|
6
|
+
scopes: string[];
|
|
7
|
+
createdAt: string;
|
|
8
|
+
lastUsed: string | null;
|
|
9
|
+
expiresAt: string | null;
|
|
10
|
+
revoked: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class KeyStore {
|
|
13
|
+
private db;
|
|
14
|
+
constructor(dbPath: string);
|
|
15
|
+
/** Generate a new API key. Returns the full key (only shown once) and the stored record. */
|
|
16
|
+
create(name: string, scopes?: string[]): {
|
|
17
|
+
key: string;
|
|
18
|
+
record: ApiKey;
|
|
19
|
+
};
|
|
20
|
+
/** Verify an API key. Returns the key record if valid, null otherwise. */
|
|
21
|
+
verify(rawKey: string): ApiKey | null;
|
|
22
|
+
/** List all keys (without hashes). */
|
|
23
|
+
list(): Omit<ApiKey, "keyHash">[];
|
|
24
|
+
/** Revoke a key by ID. */
|
|
25
|
+
revoke(id: string): boolean;
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/server/auth/keys.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,EAAE,MAAM;IAkB1B,4FAA4F;IAC5F,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,EAAwB,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAkB7F,0EAA0E;IAC1E,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA2BrC,sCAAsC;IACtC,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE;IAkBjC,0BAA0B;IAC1B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAK3B,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
|
+
export class KeyStore {
|
|
4
|
+
db;
|
|
5
|
+
constructor(dbPath) {
|
|
6
|
+
this.db = new Database(dbPath);
|
|
7
|
+
this.db.pragma("journal_mode = WAL");
|
|
8
|
+
this.db.exec(`
|
|
9
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
10
|
+
id TEXT PRIMARY KEY,
|
|
11
|
+
name TEXT NOT NULL,
|
|
12
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
13
|
+
key_prefix TEXT NOT NULL,
|
|
14
|
+
scopes TEXT NOT NULL DEFAULT '[]',
|
|
15
|
+
created_at TEXT NOT NULL,
|
|
16
|
+
last_used TEXT,
|
|
17
|
+
expires_at TEXT,
|
|
18
|
+
revoked INTEGER NOT NULL DEFAULT 0
|
|
19
|
+
)
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
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();
|
|
29
|
+
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
|
+
};
|
|
37
|
+
}
|
|
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
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** List all keys (without hashes). */
|
|
66
|
+
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();
|
|
71
|
+
return rows.map((row) => ({
|
|
72
|
+
id: row.id,
|
|
73
|
+
name: row.name,
|
|
74
|
+
keyPrefix: row.key_prefix,
|
|
75
|
+
scopes: JSON.parse(row.scopes),
|
|
76
|
+
createdAt: row.created_at,
|
|
77
|
+
lastUsed: row.last_used,
|
|
78
|
+
expiresAt: row.expires_at,
|
|
79
|
+
revoked: Boolean(row.revoked),
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
/** Revoke a key by ID. */
|
|
83
|
+
revoke(id) {
|
|
84
|
+
const result = this.db.prepare("UPDATE api_keys SET revoked = 1 WHERE id = ?").run(id);
|
|
85
|
+
return result.changes > 0;
|
|
86
|
+
}
|
|
87
|
+
close() {
|
|
88
|
+
this.db.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/server/auth/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SessionConfig {
|
|
2
|
+
username: string;
|
|
3
|
+
password: string;
|
|
4
|
+
sessionTtlMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function createSessionToken(config: SessionConfig): string;
|
|
7
|
+
export declare function verifySessionToken(token: string, config: SessionConfig): boolean;
|
|
8
|
+
export declare function verifyCredentials(username: string, password: string, config: SessionConfig): boolean;
|
|
9
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/server/auth/session.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AASD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAKhE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAchF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAOpG"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
const SESSION_TTL_MS = 86_400_000; // 24 hours
|
|
3
|
+
/** HMAC-sign a token. The secret is derived from the password. */
|
|
4
|
+
function sign(payload, secret) {
|
|
5
|
+
const hmac = crypto.createHmac("sha256", secret);
|
|
6
|
+
hmac.update(payload);
|
|
7
|
+
return hmac.digest("hex");
|
|
8
|
+
}
|
|
9
|
+
export function createSessionToken(config) {
|
|
10
|
+
const exp = Date.now() + (config.sessionTtlMs ?? SESSION_TTL_MS);
|
|
11
|
+
const payload = `${config.username}:${exp}`;
|
|
12
|
+
const signature = sign(payload, config.password);
|
|
13
|
+
return Buffer.from(`${payload}:${signature}`).toString("base64url");
|
|
14
|
+
}
|
|
15
|
+
export function verifySessionToken(token, config) {
|
|
16
|
+
try {
|
|
17
|
+
const decoded = Buffer.from(token, "base64url").toString();
|
|
18
|
+
const parts = decoded.split(":");
|
|
19
|
+
if (parts.length !== 3)
|
|
20
|
+
return false;
|
|
21
|
+
const [username, expStr, sig] = parts;
|
|
22
|
+
const exp = parseInt(expStr, 10);
|
|
23
|
+
if (isNaN(exp) || Date.now() > exp)
|
|
24
|
+
return false;
|
|
25
|
+
if (username !== config.username)
|
|
26
|
+
return false;
|
|
27
|
+
const expected = sign(`${username}:${expStr}`, config.password);
|
|
28
|
+
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function verifyCredentials(username, password, config) {
|
|
35
|
+
// Use timing-safe comparison to prevent timing attacks
|
|
36
|
+
const userMatch = username.length === config.username.length &&
|
|
37
|
+
crypto.timingSafeEqual(Buffer.from(username), Buffer.from(config.username));
|
|
38
|
+
const passMatch = password.length === config.password.length &&
|
|
39
|
+
crypto.timingSafeEqual(Buffer.from(password), Buffer.from(config.password));
|
|
40
|
+
return userMatch && passMatch;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/server/auth/session.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,WAAW;AAQ9C,kEAAkE;AAClE,SAAS,IAAI,CAAC,OAAe,EAAE,MAAc;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrB,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAqB;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,QAAQ,IAAI,GAAG,EAAE,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,MAAqB;IACrE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;QACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG;YAAE,OAAO,KAAK,CAAC;QACjD,IAAI,QAAQ,KAAK,MAAM,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,QAAQ,IAAI,MAAM,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChE,OAAO,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,QAAgB,EAAE,MAAqB;IACzF,uDAAuD;IACvD,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM;QAC1D,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM;QAC1D,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9E,OAAO,SAAS,IAAI,SAAS,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StationConfig } from "../config/schema.js";
|
|
2
|
+
export interface StationInstance {
|
|
3
|
+
start(): Promise<void>;
|
|
4
|
+
stop(): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export declare function createStation(config: StationConfig, cwd: string): Promise<StationInstance>;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAwBzD,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA+PhG"}
|