@dashwire/server 0.2.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -1,2 +1 @@
1
- import Fastify from "fastify";
2
- export declare function startDashwire(userConfig?: any): Promise<Fastify.FastifyInstance<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, Fastify.FastifyBaseLogger, Fastify.FastifyTypeProviderDefault>>;
1
+ export declare function startDashwire(userConfig?: any): void;
package/dist/index.js CHANGED
@@ -1,35 +1,114 @@
1
1
  import Fastify from "fastify";
2
+ import fastifyStatic from "@fastify/static";
2
3
  import { Server as SocketServer } from "socket.io";
3
- import { projectRoutes } from "./routes/projects.js";
4
- import { overviewRoutes } from "./routes/overview.js";
5
- import { authRoutes } from "./routes/auth.js";
6
- import { attachSdkNamespace } from "./realtime/sdkNamespace.js";
7
- import { attachDashboardNamespace } from "./realtime/dashboardNamespace.js";
8
- export async function startDashwire(userConfig = {}) {
9
- const PORT = Number(userConfig?.server?.port ?? process.env.PORT ?? 4000);
10
- const HOST = userConfig?.server?.host ?? "0.0.0.0";
11
- const app = Fastify({ logger: true });
12
- app.addHook("onRequest", async (req, reply) => {
13
- reply.header("Access-Control-Allow-Origin", "*");
14
- reply.header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS");
15
- reply.header("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, x-admin-token");
16
- if (req.method === "OPTIONS") {
17
- return reply.code(200).send();
4
+ import Database from "better-sqlite3";
5
+ import { fileURLToPath } from "node:url";
6
+ import { dirname, join } from "node:path";
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const PORT = Number(process.env.PORT ?? 4000);
9
+ const dbPath = process.env.DASHWIRE_DB_PATH ?? "./dashwire.db";
10
+ const app = Fastify({ logger: true });
11
+ const sqlite = new Database(dbPath);
12
+ sqlite.pragma("journal_mode = WAL");
13
+ // Database Schema Initialisatie
14
+ sqlite.exec(`
15
+ CREATE TABLE IF NOT EXISTS projects (
16
+ id TEXT PRIMARY KEY,
17
+ name TEXT NOT NULL,
18
+ api_key_hash TEXT NOT NULL,
19
+ structure_version INTEGER NOT NULL DEFAULT 1,
20
+ structure_json TEXT NOT NULL,
21
+ status TEXT NOT NULL DEFAULT 'offline',
22
+ last_seen_at TEXT,
23
+ created_at TEXT NOT NULL
24
+ );
25
+ CREATE TABLE IF NOT EXISTS capability_state (
26
+ project_id TEXT NOT NULL,
27
+ capability_path TEXT NOT NULL,
28
+ value_json TEXT NOT NULL,
29
+ updated_at TEXT NOT NULL,
30
+ PRIMARY KEY (project_id, capability_path)
31
+ );
32
+ `);
33
+ app.addHook("onRequest", async (req, reply) => {
34
+ reply.header("Access-Control-Allow-Origin", "*");
35
+ reply.header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS");
36
+ reply.header("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, x-admin-token");
37
+ if (req.method === "OPTIONS")
38
+ return reply.code(200).send();
39
+ });
40
+ app.get("/health", async () => ({ ok: true }));
41
+ app.get("/api/projects", async () => {
42
+ return sqlite.prepare("SELECT id, name, status, last_seen_at as lastSeenAt, structure_version as structureVersion FROM projects").all();
43
+ });
44
+ app.get("/api/projects/:id", async (req, reply) => {
45
+ const project = sqlite.prepare("SELECT * FROM projects WHERE id = ?").get(req.params.id);
46
+ if (!project)
47
+ return reply.status(404).send({ error: "not found" });
48
+ const states = sqlite.prepare("SELECT capability_path, value_json FROM capability_state WHERE project_id = ?").all(req.params.id);
49
+ const stateMap = {};
50
+ for (const row of states)
51
+ stateMap[row.capability_path] = JSON.parse(row.value_json);
52
+ return {
53
+ id: project.id,
54
+ name: project.name,
55
+ status: project.status,
56
+ structure: JSON.parse(project.structure_json),
57
+ state: stateMap,
58
+ };
59
+ });
60
+ // Serveer de ingebouwde Dashboard UI
61
+ app.register(fastifyStatic, {
62
+ root: join(__dirname, "../public"),
63
+ prefix: "/",
64
+ });
65
+ // SPA catch-all fallback voor Next.js client-side routing
66
+ app.setNotFoundHandler((req, reply) => {
67
+ if (req.url?.startsWith("/api") || req.url?.startsWith("/sdk")) {
68
+ return reply.code(404).send({ error: "Not found" });
69
+ }
70
+ return reply.sendFile("index.html");
71
+ });
72
+ const io = new SocketServer(app.server, { cors: { origin: "*" } });
73
+ const sdkNsp = io.of("/sdk");
74
+ sdkNsp.on("connection", (socket) => {
75
+ const { projectId } = socket.handshake.auth || {};
76
+ if (!projectId)
77
+ return socket.disconnect();
78
+ socket.join(`project:${projectId}`);
79
+ sqlite.prepare("UPDATE projects SET status = 'online', last_seen_at = ? WHERE id = ?").run(new Date().toISOString(), projectId);
80
+ io.of("/dashboard").to(`project:${projectId}`).emit("project:online", { projectId });
81
+ socket.on("state:sync", ({ state }) => {
82
+ const stmt = sqlite.prepare(`INSERT INTO capability_state (project_id, capability_path, value_json, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(project_id, capability_path) DO UPDATE SET value_json = excluded.value_json, updated_at = excluded.updated_at`);
83
+ const now = new Date().toISOString();
84
+ for (const [path, val] of Object.entries(state || {})) {
85
+ stmt.run(projectId, path, JSON.stringify(val), now);
86
+ io.of("/dashboard").to(`project:${projectId}`).emit("state:update", { projectId, path, value: val });
18
87
  }
19
88
  });
20
- app.get("/health", async () => ({ ok: true }));
21
- const io = new SocketServer(app.server, {
22
- cors: { origin: "*" },
89
+ socket.on("state:update", ({ path, value }) => {
90
+ const now = new Date().toISOString();
91
+ sqlite.prepare(`INSERT INTO capability_state (project_id, capability_path, value_json, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(project_id, capability_path) DO UPDATE SET value_json = excluded.value_json, updated_at = excluded.updated_at`).run(projectId, path, JSON.stringify(value), now);
92
+ io.of("/dashboard").to(`project:${projectId}`).emit("state:update", { projectId, path, value });
93
+ });
94
+ socket.on("disconnect", () => {
95
+ sqlite.prepare("UPDATE projects SET status = 'offline', last_seen_at = ? WHERE id = ?").run(new Date().toISOString(), projectId);
96
+ io.of("/dashboard").to(`project:${projectId}`).emit("project:offline", { projectId });
97
+ });
98
+ });
99
+ const dashNsp = io.of("/dashboard");
100
+ dashNsp.on("connection", (socket) => {
101
+ socket.on("subscribe:project", ({ projectId }) => socket.join(`project:${projectId}`));
102
+ socket.on("unsubscribe:project", ({ projectId }) => socket.leave(`project:${projectId}`));
103
+ socket.on("capability:command", ({ projectId, path, value }) => {
104
+ io.of("/sdk").to(`project:${projectId}`).emit("capability:command", { path, value });
105
+ });
106
+ socket.on("action:execute", ({ projectId, path }) => {
107
+ io.of("/sdk").to(`project:${projectId}`).emit("action:execute", { path });
108
+ });
109
+ });
110
+ export function startDashwire(userConfig) {
111
+ app.listen({ port: PORT, host: "0.0.0.0" }).then(() => {
112
+ console.log(`⚡ Dashwire server & dashboard running on http://0.0.0.0:${PORT}`);
23
113
  });
24
- projectRoutes(app, io);
25
- overviewRoutes(app);
26
- authRoutes(app);
27
- attachSdkNamespace(io);
28
- attachDashboardNamespace(io);
29
- await app.listen({ port: PORT, host: HOST });
30
- app.log.info(`⚡ Dashwire server running on http://${HOST}:${PORT}`);
31
- return app;
32
- }
33
- if (process.argv[1] && process.argv[1].endsWith("index.ts")) {
34
- startDashwire();
35
114
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dashwire/server",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
- "files": ["dist"],
7
+ "files": ["dist", "public"],
8
8
  "private": false,
9
9
  "scripts": {
10
10
  "build": "tsc -p tsconfig.json",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@dashwire/core": "^0.1.0",
16
+ "@fastify/static": "^7.0.4",
16
17
  "better-sqlite3": "^11.3.0",
17
18
  "drizzle-orm": "^0.33.0",
18
19
  "fastify": "^4.28.1",
@@ -24,7 +25,6 @@
24
25
  "@types/better-sqlite3": "^7.6.11",
25
26
  "@types/jsonwebtoken": "^9.0.10",
26
27
  "@types/node": "^22.7.0",
27
- "drizzle-kit": "^0.24.2",
28
28
  "tsx": "^4.19.1",
29
29
  "typescript": "^5.6.0"
30
30
  }