@getdashfy/server 0.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.
@@ -0,0 +1,422 @@
1
+ import { PollMode, Subscription, DashfyConfig, APIRegistration } from '@getdashfy/types';
2
+ import { FastifyInstance } from 'fastify';
3
+ import { Logger } from 'pino';
4
+ import { Socket } from 'socket.io';
5
+
6
+ interface API {
7
+ mode: PollMode;
8
+ methods: Record<string, (...args: any[]) => Promise<unknown>>;
9
+ }
10
+ interface BusOptions {
11
+ logger: Logger;
12
+ pollInterval?: number;
13
+ }
14
+ interface ClientInfo {
15
+ socket: Socket;
16
+ subscriptions: Set<string>;
17
+ connectedAt: Date;
18
+ }
19
+ interface DashfyOptions {
20
+ logger?: Logger;
21
+ app?: FastifyInstance;
22
+ }
23
+ interface SubscriptionData extends Subscription {
24
+ clients: Set<string>;
25
+ timer?: NodeJS.Timeout;
26
+ pushDispose?: () => void;
27
+ cached?: {
28
+ id: string;
29
+ data: unknown;
30
+ };
31
+ }
32
+ interface SubscriptionInfo {
33
+ id: string;
34
+ clientCount: number;
35
+ hasCachedData: boolean;
36
+ hasTimer: boolean;
37
+ }
38
+
39
+ /**
40
+ * Main Dashfy server class that manages the dashboard application.
41
+ *
42
+ * Orchestrates HTTP server (Fastify), WebSocket communication (Socket.IO),
43
+ * API registration (Bus), and configuration handling with hot-reload support.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { Dashfy } from '@getdashfy/server'
48
+ *
49
+ * const dashfy = new Dashfy()
50
+ *
51
+ * // Load configuration from JSON (or YAML)
52
+ * await dashfy.configureFromFile('./dashfy.config.json')
53
+ *
54
+ * // Register APIs
55
+ * dashfy.registerApi('github', () => ({
56
+ * async stars(params: { owner: string; repo: string }) {
57
+ * const res = await fetch(`https://api.github.com/repos/${params.owner}/${params.repo}`)
58
+ * const data = await res.json()
59
+ * return { stars: data.stargazers_count }
60
+ * }
61
+ * }))
62
+ *
63
+ * // Start server
64
+ * await dashfy.start()
65
+ * // Server running at http://0.0.0.0:5001
66
+ * ```
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * // Push mode for real-time data
71
+ * dashfy.registerApi('metrics', () => ({
72
+ * cpuUsage(callback: (data: unknown) => void) {
73
+ * const interval = setInterval(() => {
74
+ * callback({ usage: process.cpuUsage(), timestamp: Date.now() })
75
+ * }, 1000)
76
+ * return () => clearInterval(interval) // cleanup
77
+ * }
78
+ * }), 'push')
79
+ * ```
80
+ */
81
+ declare class Dashfy {
82
+ private app;
83
+ private io?;
84
+ private bus;
85
+ private config?;
86
+ private logger;
87
+ private configPath?;
88
+ /**
89
+ * Creates a new Dashfy instance.
90
+ *
91
+ * @param options - Dashfy options
92
+ * @param options.logger - Logger instance
93
+ * @param options.app - Existing Fastify instance. When provided,
94
+ * Dashfy will use this instance instead of creating a new one,
95
+ * allowing integration with existing Fastify middleware and routes.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * // Integrate Dashfy with existing Fastify app
100
+ * import Fastify from 'fastify'
101
+ * import { Dashfy } from '@getdashfy/server'
102
+ *
103
+ * const app = Fastify()
104
+ *
105
+ * // Add your custom routes
106
+ * app.get('/custom-api', async (request, reply) => {
107
+ * return { message: 'Custom endpoint' }
108
+ * })
109
+ *
110
+ * // Initialize Dashfy with existing app
111
+ * const dashfy = new Dashfy({ app })
112
+ * await dashfy.configureFromFile('./dashfy.config.json')
113
+ * await dashfy.start()
114
+ * ```
115
+ */
116
+ constructor(options?: DashfyOptions);
117
+ /**
118
+ * Configures the Dashfy server with the provided configuration object.
119
+ *
120
+ * This method accepts a configuration object and applies it to the server instance.
121
+ * If a global poll interval is specified in the configuration, it will be applied
122
+ * to the message bus for all poll-mode APIs.
123
+ *
124
+ * @param config - The Dashfy configuration object containing server settings,
125
+ * dashboards, and optional API configuration
126
+ *
127
+ * @remarks
128
+ * This method should be called before {@link start}. For loading configuration
129
+ * from a JSON or YAML file, use {@link configureFromFile} instead.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * const dashfy = new Dashfy();
134
+ *
135
+ * // Configure with a configuration object
136
+ * dashfy.configure({
137
+ * port: 5001,
138
+ * host: 'localhost',
139
+ * dashboards: [
140
+ * {
141
+ * id: 'main',
142
+ * title: 'Main Dashboard',
143
+ * widgets: []
144
+ * }
145
+ * ],
146
+ * apis: {
147
+ * pollInterval: 30000 // 30 seconds
148
+ * }
149
+ * });
150
+ *
151
+ * await dashfy.start();
152
+ * ```
153
+ */
154
+ configure(config: DashfyConfig): void;
155
+ /**
156
+ * Loads and applies configuration from a file (JSON or YAML).
157
+ *
158
+ * This method reads a configuration file (supports .json, .yml, .yaml formats),
159
+ * validates it against the schema, and applies it to the server instance. It also
160
+ * optionally watches the file for changes and automatically reloads the configuration
161
+ * when the file is modified. When changes are detected, all connected clients are
162
+ * notified via Socket.IO.
163
+ *
164
+ * @param configPath - Path to the configuration file (absolute or relative).
165
+ * Supports .json, .yml, .yaml extensions
166
+ * @param watchConfig - Whether to watch the configuration file for changes.
167
+ * When `true`, the configuration will be automatically reloaded
168
+ * on file changes. Defaults to `true`
169
+ *
170
+ * @throws {Error} If the configuration file cannot be read or fails validation
171
+ *
172
+ * @remarks
173
+ * This method should be called before {@link start}. If you already have a
174
+ * configuration object, use {@link configure} instead.
175
+ *
176
+ * When the configuration is reloaded due to file changes:
177
+ * - The new configuration is validated before applying
178
+ * - Connected clients receive the updated configuration (excluding sensitive API data)
179
+ * - If validation fails, the old configuration remains active
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * const dashfy = new Dashfy();
184
+ *
185
+ * // Load configuration from JSON file with auto-reload
186
+ * await dashfy.configureFromFile('./dashfy.config.json');
187
+ *
188
+ * // Load configuration from YAML file with auto-reload
189
+ * await dashfy.configureFromFile('./dashfy.config.yaml');
190
+ *
191
+ * // Load without watching for changes (production)
192
+ * await dashfy.configureFromFile('./dashfy.config.yaml', false);
193
+ *
194
+ * await dashfy.start();
195
+ * ```
196
+ */
197
+ configureFromFile(configPath: string, watchConfig?: boolean): Promise<void>;
198
+ /**
199
+ * Registers an API with the message bus for data retrieval.
200
+ *
201
+ * This method allows you to register custom APIs that widgets can subscribe to
202
+ * for fetching data. APIs can operate in either poll mode (periodic fetching)
203
+ * or push mode (real-time updates via callbacks).
204
+ *
205
+ * @param id - Unique identifier for the API (e.g., 'github', 'weather', 'analytics')
206
+ * @param api - Factory function that returns an object containing API methods.
207
+ * Each method should be async and return data that widgets can consume
208
+ * @param mode - The API mode: `poll` (default) for periodic polling,
209
+ * or `push` for real-time push-based updates
210
+ *
211
+ * @throws {Error} If an API with the same ID is already registered
212
+ * @throws {Error} If the API mode is invalid
213
+ *
214
+ * @remarks
215
+ * - In **poll mode**, the bus automatically calls API methods at regular intervals
216
+ * - In **push mode**, API methods receive a callback to push data when available
217
+ * - APIs should be registered before calling {@link start}
218
+ * - The core `dashfy` API is automatically registered and provides system information
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * const dashfy = new Dashfy();
223
+ *
224
+ * // Register a poll-mode API (default)
225
+ * dashfy.registerApi('github', () => ({
226
+ * async stars(params: { owner: string; repo: string }) {
227
+ * const response = await fetch(
228
+ * `https://api.github.com/repos/${params.owner}/${params.repo}`
229
+ * );
230
+ * const data = await response.json();
231
+ * return { stars: data.stargazers_count };
232
+ * }
233
+ * }));
234
+ *
235
+ * // Register a push-mode API for real-time updates
236
+ * dashfy.registerApi('metrics', () => ({
237
+ * async cpuUsage(callback: (data: unknown) => void) {
238
+ * const interval = setInterval(() => {
239
+ * const usage = process.cpuUsage();
240
+ * callback({ cpu: usage });
241
+ * }, 1000);
242
+ *
243
+ * return () => clearInterval(interval);
244
+ * }
245
+ * }), 'push');
246
+ * ```
247
+ */
248
+ registerApi(id: string, api: APIRegistration, mode?: PollMode): void;
249
+ /**
250
+ * Starts the Dashfy server and begins listening for connections.
251
+ *
252
+ * This method initializes and starts all server components including the HTTP server,
253
+ * WebSocket connections, static file serving, and API endpoints. It must be called
254
+ * after configuration is loaded via {@link configure} or {@link configureFromFile}.
255
+ *
256
+ * @throws {Error} If configuration has not been set before calling this method
257
+ *
258
+ * @remarks
259
+ * **What this method does:**
260
+ * 1. Validates that configuration is loaded
261
+ * 2. Sets up CORS for cross-origin requests
262
+ * 3. Serves static files from the `build` directory (if available)
263
+ * 4. Registers HTTP endpoints:
264
+ * - `GET /config` - Returns public configuration (excludes sensitive API data)
265
+ * - `GET /health` - Health check endpoint with uptime and status
266
+ * - `GET /api/info` - Returns registered APIs and connection info
267
+ * 5. Initializes Socket.IO for real-time WebSocket communication
268
+ * 6. Sets up Socket.IO event handlers:
269
+ * - `connection` - Client connects, receives configuration
270
+ * - `api.subscription` - Widget subscribes to data
271
+ * - `api.unsubscription` - Widget unsubscribes from data
272
+ * - `disconnect` - Client disconnects, cleanup performed
273
+ * - `error` - Socket error handling
274
+ * 7. Starts listening on configured host and port
275
+ *
276
+ * **Port and Host Resolution:**
277
+ * - Port: `process.env.PORT` → `config.port` → `5001` (default)
278
+ * - Host: `process.env.HOST` → `config.host` → `0.0.0.0` (default)
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * const dashfy = new Dashfy();
283
+ *
284
+ * // Configure the server
285
+ * await dashfy.configureFromFile('./dashfy.config.json');
286
+ *
287
+ * // Register custom APIs
288
+ * dashfy.registerApi('github', () => ({
289
+ * async stars(params: { owner: string; repo: string }) {
290
+ * // ... fetch GitHub stars
291
+ * }
292
+ * }));
293
+ *
294
+ * // Start the server
295
+ * await dashfy.start();
296
+ * // Server is now running and accepting connections
297
+ * ```
298
+ *
299
+ * @example
300
+ * ```ts
301
+ * // With environment variables
302
+ * process.env.PORT = '3000';
303
+ * process.env.HOST = 'localhost';
304
+ *
305
+ * const dashfy = new Dashfy();
306
+ * await dashfy.configureFromFile('./dashfy.config.json');
307
+ * await dashfy.start();
308
+ * // Server running on http://localhost:3000
309
+ * ```
310
+ */
311
+ start(): Promise<void>;
312
+ /**
313
+ * Gracefully stops the Dashfy server and cleans up all resources.
314
+ *
315
+ * This method performs a graceful shutdown of the server, closing all active
316
+ * connections and releasing resources. It closes the Socket.IO server (if initialized)
317
+ * and the underlying Fastify HTTP server.
318
+ *
319
+ * @remarks
320
+ * **Shutdown process:**
321
+ * 1. Logs shutdown initiation
322
+ * 2. Closes Socket.IO server (disconnects all WebSocket clients)
323
+ * 3. Closes Fastify HTTP server (stops accepting new connections)
324
+ * 4. Waits for all pending requests to complete
325
+ * 5. Logs shutdown completion
326
+ *
327
+ * This method is safe to call even if the server was never started or is already stopped.
328
+ * All connected clients will be disconnected, and their subscriptions will be cleaned up
329
+ * automatically by the bus.
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * const dashfy = new Dashfy();
334
+ * await dashfy.configureFromFile('./dashfy.config.json');
335
+ * await dashfy.start();
336
+ *
337
+ * // Later, gracefully shut down the server
338
+ * await dashfy.stop();
339
+ * ```
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * // Graceful shutdown on process signals
344
+ * const dashfy = new Dashfy();
345
+ * await dashfy.configureFromFile('./dashfy.config.json');
346
+ * await dashfy.start();
347
+ *
348
+ * process.on('SIGTERM', async () => {
349
+ * console.log('SIGTERM received, shutting down gracefully...');
350
+ * await dashfy.stop();
351
+ * process.exit(0);
352
+ * });
353
+ *
354
+ * process.on('SIGINT', async () => {
355
+ * console.log('SIGINT received, shutting down gracefully...');
356
+ * await dashfy.stop();
357
+ * process.exit(0);
358
+ * });
359
+ * ```
360
+ */
361
+ stop(): Promise<void>;
362
+ /**
363
+ * Returns a sanitized configuration object without sensitive API data.
364
+ *
365
+ * Removes the `apis` property from the configuration before sending it to clients,
366
+ * as it may contain sensitive server-side settings like polling intervals or API keys.
367
+ *
368
+ * @param config - The full Dashfy configuration object
369
+ * @returns Configuration object without the `apis` property
370
+ *
371
+ * @internal
372
+ */
373
+ private getPublicConfig;
374
+ /**
375
+ * Registers the core `dashfy` API that provides system introspection and monitoring.
376
+ *
377
+ * This internal method is automatically called during construction to register
378
+ * a built-in API that exposes server health, statistics, and debugging information.
379
+ * The `dashfy` API is available to all widgets for monitoring server state.
380
+ *
381
+ * @remarks
382
+ * **Available methods:**
383
+ * - `inspector()` - Returns comprehensive system information including:
384
+ * - `apis`: List of all registered API identifiers
385
+ * - `clientCount`: Number of currently connected WebSocket clients
386
+ * - `subscriptions`: Detailed information about all active subscriptions
387
+ * - `uptime`: Server process uptime in seconds
388
+ * - `version`: Dashfy server version (from package.json)
389
+ * - `nodeVersion`: Node.js runtime version
390
+ *
391
+ * This API is useful for:
392
+ * - Building admin/monitoring dashboards
393
+ * - Debugging subscription and connection issues
394
+ * - Health checks and system monitoring
395
+ * - Displaying server statistics to users
396
+ *
397
+ * @example
398
+ * ```ts
399
+ * // Widgets can subscribe to the dashfy API
400
+ * // In a widget configuration:
401
+ * {
402
+ * api: 'dashfy',
403
+ * method: 'inspector'
404
+ * }
405
+ *
406
+ * // This returns:
407
+ * {
408
+ * apis: ['dashfy', 'github', 'weather'],
409
+ * clientCount: 5,
410
+ * subscriptions: [...],
411
+ * uptime: 3600.5,
412
+ * version: '0.1.0',
413
+ * nodeVersion: 'v20.10.0'
414
+ * }
415
+ * ```
416
+ *
417
+ * @internal
418
+ */
419
+ private registerCoreApi;
420
+ }
421
+
422
+ export { type API, type BusOptions, type ClientInfo, Dashfy, type DashfyOptions, type SubscriptionData, type SubscriptionInfo };