@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.
- package/CHANGELOG.md +10 -0
- package/LICENSE +661 -0
- package/README.md +768 -0
- package/dist/index.cjs +1235 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +422 -0
- package/dist/index.d.ts +422 -0
- package/dist/index.js +1225 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../types/src/index.ts","../src/constants.ts","../src/pushInterval.ts","../src/request.ts","../src/bus.ts","../src/config.ts","../src/logger.ts","../src/dashfy.ts"],"names":["WebSocketEvent","undiciRequest","sub","getErrorMessage","parseYaml","path","SocketIOServer"],"mappings":"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","file":"index.js","sourcesContent":["import type * as React from 'react'\n\nexport enum NotificationType {\n SUCCESS = 'success',\n ERROR = 'error',\n WARNING = 'warning',\n INFO = 'info',\n}\n\nexport enum WebSocketStatus {\n CONNECTING = 'connecting',\n CONNECTED = 'connected',\n DISCONNECTED = 'disconnected',\n RECONNECTING = 'reconnecting',\n ERROR = 'error',\n}\n\nexport enum WebSocketEvent {\n CONNECT = 'connect',\n DISCONNECT = 'disconnect',\n ERROR = 'error',\n CONFIGURATION = 'configuration',\n RECONNECT = 'reconnect',\n RECONNECT_ATTEMPT = 'reconnect_attempt',\n RECONNECT_ERROR = 'reconnect_error',\n RECONNECT_FAILED = 'reconnect_failed',\n API_SUBSCRIPTION = 'api.subscription',\n API_UNSUBSCRIPTION = 'api.unsubscription',\n API_DATA = 'api.data',\n API_ERROR = 'api.error',\n}\n\nexport type APIClient = Record<string, (...args: any[]) => Promise<unknown>>\n\nexport type CreatePushInterval = (options?: {\n interval?: number\n}) => (\n key: string,\n callback: (data: unknown) => void,\n fetchFn: () => Promise<unknown>,\n) => () => void\n\nexport type APIRegistration = (dashfy: {\n logger: Logger\n request?: (options: RequestOptions) => Promise<unknown>\n createPushInterval?: CreatePushInterval\n}) => APIClient\n\nexport type ApiConfig = Record<string, unknown>\n\nexport type Dashboard = DashboardConfig\n\nexport type Extension = Record<string, WidgetComponent | React.ComponentType<any>>\n\nexport type PollMode = 'poll' | 'push'\n\nexport type ThemeId = string\n\nexport type ThemeMode = 'light' | 'dark'\n\nexport interface ApiData<T = unknown> {\n data: T | null\n error: string | null\n loading: boolean\n lastUpdate: number\n}\n\nexport interface ApiError {\n id: string\n error: {\n message: string\n code?: string\n statusCode?: number\n }\n}\n\nexport interface DashboardConfig {\n name?: string\n title?: string\n columns: number\n rows: number\n widgets: WidgetConfig[]\n}\n\nexport interface DashfyConfig {\n port?: number\n host?: string\n baseDir?: string\n theme?: ThemeId\n rotationDuration?: number\n dashboards: DashboardConfig[]\n apis?: {\n pollInterval?: number\n [key: string]: ApiConfig | number | undefined\n }\n}\n\nexport interface Logger {\n info: (message: string, ...args: any[]) => void\n warn: (message: string, ...args: any[]) => void\n error: (message: string, ...args: any[]) => void\n debug: (message: string, ...args: any[]) => void\n}\n\nexport interface Notification {\n id: string\n type: NotificationType\n message: string\n /**\n * Time To Live in milliseconds - how long before auto-dismissing\n * - positive number: auto-dismiss after this many ms\n * - -1 or 0: never auto-dismiss (user must manually close)\n * - undefined: uses default TTL based on notification type\n */\n ttl?: number\n action?: {\n label: string\n onClick: () => void\n }\n createdAt: number\n}\n\nexport interface RequestOptions {\n url: string\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n headers?: Record<string, string>\n body?: unknown\n timeout?: number\n}\n\nexport interface Subscription {\n id: string\n api: string\n endpoint: string\n params?: Record<string, unknown>\n hasSubscribed?: boolean\n}\n\nexport interface WidgetComponent<P = any> {\n (props: P): React.ReactNode\n displayName?: string\n getApiRequest?: (props: P) => Subscription\n}\n\nexport interface WidgetConfig {\n extension: string\n widget: string\n title?: string\n columns: number\n rows: number\n x: number\n y: number\n [key: string]: unknown\n}\n\nexport interface WidgetMetadata {\n displayName: string\n description?: string\n defaultConfig?: Record<string, unknown>\n previewImage?: string\n requiredApis?: string[]\n}\n\nexport interface WidgetProps<T = unknown> {\n apiData?: T\n apiError?: Error\n isLoading?: boolean\n}\n\nexport interface WidgetRegistryEntry {\n component: WidgetComponent\n metadata?: WidgetMetadata\n}\n","/** Default server port when not specified in configuration or environment variables. */\nexport const DEFAULT_PORT = 5001\n\n/**\n * Default server host when not specified in configuration or environment variables.\n * Using '0.0.0.0' allows connections from any network interface.\n */\nexport const DEFAULT_HOST = '0.0.0.0'\n\n/**\n * Default polling interval for API calls in milliseconds (15 seconds).\n * Used when no specific poll interval is configured.\n */\nexport const DEFAULT_POLL_INTERVAL = 15_000\n\n/**\n * Default push interval for API calls in milliseconds (2 seconds).\n * Used when no specific push interval is configured.\n */\nexport const DEFAULT_PUSH_INTERVAL = 2_000\n\n/**\n * Socket.IO ping timeout in milliseconds (60 seconds).\n * The server will close the connection if no pong is received within this time.\n */\nexport const SOCKET_PING_TIMEOUT = 60_000\n\n/**\n * Socket.IO ping interval in milliseconds (25 seconds).\n * How often the server sends a ping packet to the client.\n */\nexport const SOCKET_PING_INTERVAL = 25_000\n\n/** Default request timeout in milliseconds (10 seconds). */\nexport const DEFAULT_REQUEST_TIMEOUT = 10_000\n","import type { CreatePushInterval, Logger } from '@getdashfy/types'\nimport { getErrorMessage } from '@getdashfy/utils'\n\nimport { DEFAULT_PUSH_INTERVAL } from './constants'\n\n/**\n * Creates a push interval factory bound to a logger and prefix.\n * Used internally by the Bus when registering APIs.\n *\n * @param logger - Logger instance (from Bus, scoped per API)\n * @param prefix - Prefix for log messages (e.g. API id produces '[apiId.key]')\n * @returns CreatePushInterval function for extensions to use with only interval config\n *\n * @example\n * ```ts\n * const createPushInterval = createPushIntervalFactory(logger, 'api')\n *\n * // With explicit interval\n * const startPushInterval = createPushInterval({ interval: 1_000 })\n *\n * // With default interval (2 seconds)\n * const startPushInterval = createPushInterval()\n *\n * const stopPushInterval = startPushInterval('key', (data) => console.log(data), () => fetchData())\n *\n * // Later, stop pushing for this key\n * stopPushInterval()\n * ```\n */\nexport function createPushIntervalFactory(logger: Logger, prefix: string): CreatePushInterval {\n return (options = {}) => {\n const { interval = DEFAULT_PUSH_INTERVAL } = options\n const intervals = new Map<string, NodeJS.Timeout>()\n const logKey = (key: string) => (prefix ? `${prefix}.${key}` : key)\n\n return (\n key: string,\n callback: (data: unknown) => void,\n fetchFn: () => Promise<unknown>,\n ): (() => void) => {\n const stop = (): void => {\n const existing = intervals.get(key)\n\n if (!existing) {\n return\n }\n\n clearInterval(existing)\n intervals.delete(key)\n logger.info(`[${logKey(key)}] Stopped push interval`)\n }\n\n // If a push is already running for this key, return a disposer that stops it.\n // This keeps the call idempotent without starting a duplicate interval.\n if (intervals.has(key)) {\n return stop\n }\n\n const pushData = async () => {\n try {\n const data = await fetchFn()\n callback(data)\n } catch (error) {\n logger.error(`[${logKey(key)}] Error: ${getErrorMessage(error)}`)\n }\n }\n\n void pushData()\n\n const timer = setInterval(() => {\n void pushData()\n }, interval)\n\n intervals.set(key, timer)\n logger.info(`[${logKey(key)}] Started push interval (${interval}ms)`)\n\n return stop\n }\n }\n}\n","import type { RequestOptions } from '@getdashfy/types'\nimport { request as undiciRequest } from 'undici'\n\nimport { DEFAULT_REQUEST_TIMEOUT } from './constants'\n\n/**\n * Makes an HTTP request and returns the parsed JSON response.\n *\n * @param options - Request configuration\n * @returns Parsed JSON response\n * @throws Error if request fails or response is not valid JSON\n *\n * @example\n * ```ts\n * // Simple GET request\n * const data = await request({ url: 'https://api.example.com/data' })\n *\n * // POST with body\n * const result = await request({\n * url: 'https://api.example.com/items',\n * method: 'POST',\n * body: { name: 'New Item' },\n * headers: { 'Authorization': 'Bearer token' }\n * })\n * ```\n */\nexport async function request(options: RequestOptions): Promise<unknown> {\n const { url, method = 'GET', headers = {}, body, timeout = DEFAULT_REQUEST_TIMEOUT } = options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...headers,\n }\n\n const response = await undiciRequest(url, {\n method,\n headers: requestHeaders,\n body: body ? JSON.stringify(body) : undefined,\n headersTimeout: timeout,\n bodyTimeout: timeout,\n })\n\n if (response.statusCode >= 400) {\n const errorBody = await response.body.text().catch(() => '')\n throw new Error(`HTTP ${response.statusCode}: ${errorBody || 'Request failed'}`)\n }\n\n return response.body.json()\n}\n","import { EventEmitter } from 'node:events'\n\nimport type { APIRegistration, PollMode, Subscription } from '@getdashfy/types'\nimport { WebSocketEvent } from '@getdashfy/types'\nimport { getErrorMessage } from '@getdashfy/utils'\nimport type { Logger } from 'pino'\nimport type { Socket } from 'socket.io'\n\nimport { DEFAULT_POLL_INTERVAL } from './constants'\nimport { createPushIntervalFactory } from './pushInterval'\nimport { request } from './request'\nimport type { API, BusOptions, ClientInfo, SubscriptionData, SubscriptionInfo } from './types'\n\n/**\n * Central message bus for managing real-time API communication between server and clients.\n *\n * Handles API registration, client lifecycle, subscription management, and data delivery\n * through polling (periodic fetch) or push (callback-based) mechanisms.\n */\nexport class Bus extends EventEmitter {\n public readonly logger: Logger\n public pollInterval: number\n private apis = new Map<string, API>()\n private clients = new Map<string, ClientInfo>()\n private subscriptions = new Map<string, SubscriptionData>()\n\n /**\n * Creates a new Bus instance.\n *\n * @param options - Bus options\n * @param options.logger - Logger instance\n * @param options.pollInterval - Poll interval in milliseconds (default: 15_000)\n *\n * @remarks\n * Changing `pollInterval` after construction only affects subscriptions created afterwards;\n * timers for already-active poll subscriptions keep their original interval until recreated.\n */\n constructor(options: BusOptions) {\n super()\n this.logger = options.logger\n this.pollInterval = options.pollInterval ?? DEFAULT_POLL_INTERVAL\n }\n\n /**\n * Register a new API with the bus.\n *\n * An API is an object composed of various methods that can be called by clients.\n * APIs can operate in two modes:\n * - `poll`: Methods are called periodically at a fixed interval\n * - `push`: Methods push data to clients when available\n *\n * @param id - Unique identifier for the API\n * @param apiRegistration - Function that returns an object with API methods\n * @param mode - API mode: 'poll' (default) or 'push'\n * @throws {Error} If the API mode is not 'poll' or 'push'\n * @throws {Error} If an API with the same ID is already registered\n *\n * @example\n * ```ts\n * bus.registerApi('weather', ({ logger, request }) => ({\n * getCurrentWeather: async () => {\n * return request({ url: 'https://api.weather.com/current' })\n * }\n * }), 'poll')\n * ```\n */\n public registerApi(id: string, apiRegistration: APIRegistration, mode: PollMode = 'poll'): void {\n if (mode !== 'poll' && mode !== 'push') {\n const errorMessage = `Invalid API mode: ${String(mode)}. Must be 'poll' or 'push'`\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n if (this.apis.has(id)) {\n const errorMessage = `API '${id}' is already registered`\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n const logger = this.logger.child({ api: id })\n\n const methods = apiRegistration({\n logger,\n request,\n createPushInterval: createPushIntervalFactory(logger, id),\n })\n\n this.apis.set(id, { mode, methods })\n this.logger.info(`Registered API '${id}' (mode: ${mode})`)\n }\n\n /**\n * Get all registered API identifiers.\n *\n * Returns an array of all API IDs that have been registered with the bus,\n * sorted alphabetically. Useful for debugging, introspection, or displaying\n * available APIs.\n *\n * @returns Array of registered API IDs in alphabetical order\n *\n * @example\n * ```ts\n * const apis = bus.listApis()\n * console.log(apis) // ['github', 'news', 'weather']\n *\n * // Check if a specific API is registered\n * if (bus.listApis().includes('github')) {\n * console.log('GitHub API is available')\n * }\n * ```\n */\n public listApis(): string[] {\n return Array.from(this.apis.keys()).sort((a, b) => a.localeCompare(b))\n }\n\n /**\n * Register a new client connection with the bus.\n *\n * This should be called when a client establishes a WebSocket connection. The client\n * is tracked with its socket, an empty subscriptions set, and a connection timestamp.\n * Once registered, the client can subscribe to API methods.\n *\n * @param socket - Socket.IO socket instance representing the connected client\n * @throws {Error} If a client with the same socket ID is already registered\n *\n * @example\n * ```ts\n * // In your Socket.IO connection handler\n * io.on('connection', (socket) => {\n * bus.addClient(socket)\n *\n * socket.on('disconnect', () => {\n * bus.removeClient(socket.id)\n * })\n * })\n * ```\n */\n public addClient(socket: Socket): void {\n if (this.clients.has(socket.id)) {\n const errorMessage = `Client with id ${socket.id} already registered`\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n this.clients.set(socket.id, {\n socket,\n subscriptions: new Set(),\n connectedAt: new Date(),\n })\n\n this.logger.info(`Client with id ${socket.id} connected`)\n }\n\n /**\n * Remove a client and clean up all associated resources.\n *\n * This method removes the client from all subscriptions it was part of. If a subscription\n * has no remaining clients after removal, the subscription is cleaned up and any polling\n * timers are cleared. This should be called when a client disconnects to prevent memory leaks.\n *\n * @param clientId - The unique identifier of the client to remove (socket.id)\n * @throws {Error} If the client is not found\n *\n * @example\n * ```ts\n * // In your Socket.IO disconnect handler\n * socket.on('disconnect', () => {\n * bus.removeClient(socket.id)\n * console.log(`Client ${socket.id} removed`)\n * })\n * ```\n */\n public removeClient(clientId: string): void {\n const clientInfo = this.clients.get(clientId)\n\n if (!clientInfo) {\n const errorMessage = `Client with id ${clientId} not found`\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n // Remove client from all subscriptions\n for (const subscriptionId of clientInfo.subscriptions) {\n const subscription = this.subscriptions.get(subscriptionId)\n\n if (!subscription) {\n continue\n }\n\n subscription.clients.delete(clientId)\n\n if (subscription.clients.size === 0) {\n this.teardownSubscription(subscriptionId)\n }\n }\n\n this.clients.delete(clientId)\n this.logger.info(`Client with id ${clientId} disconnected`)\n }\n\n /**\n * Get the current number of connected clients.\n *\n * Returns the real-time count of active client connections. Useful for monitoring,\n * health checks, or displaying connection statistics.\n *\n * @returns Number of currently connected clients\n *\n * @example\n * ```ts\n * // Display connection stats\n * const count = bus.clientCount()\n * console.log(`${count} client(s) connected`)\n *\n * // Health check endpoint\n * app.get('/health', (req, res) => {\n * res.json({\n * status: 'ok',\n * clients: bus.clientCount()\n * })\n * })\n * ```\n */\n public clientCount(): number {\n return this.clients.size\n }\n\n /**\n * Subscribe a client to an API method call.\n *\n * This creates or reuses a subscription for the specified API method and adds the client\n * to receive updates. For poll mode APIs, this starts periodic polling. For push mode APIs,\n * this sets up the push producer. If cached data exists, it's immediately sent to the client.\n *\n * Routing is resolved from `subscription.api` (the registered API) and `subscription.endpoint`\n * (the method on that API). The `subscription.id` is used as the key for deduping subscriptions,\n * caching, and client messages; by convention it is `${api}.${endpoint}` (e.g. 'github.repos'),\n * but this format is not enforced.\n *\n * @param clientId - The unique identifier of the connected client\n * @param subscription - Subscription details including `id`, `api`, `endpoint`, and optional `params`\n * @throws {Error} If the API ID (`subscription.api`) is empty or undefined\n * @throws {Error} If the endpoint (`subscription.endpoint`) is empty or undefined\n * @throws {Error} If the API is not registered\n * @throws {Error} If the API method does not exist or is not a function\n *\n * @remarks\n * If the client is not registered, the call returns silently (logging a warning) instead of\n * throwing. If a subscription with the same `id` already exists, the incoming `params` are\n * ignored: the subscription keeps the `params` supplied by its first subscriber.\n *\n * @example\n * ```ts\n * // Subscribe to a GitHub API method\n * bus.subscribe('client-123', {\n * id: 'github.repos',\n * api: 'github',\n * endpoint: 'repos',\n * params: { user: 'octocat' }\n * })\n *\n * // Subscribe without parameters\n * bus.subscribe('client-456', {\n * id: 'weather.current',\n * api: 'weather',\n * endpoint: 'current'\n * })\n * ```\n */\n public subscribe(clientId: string, subscription: Subscription): void {\n const clientInfo = this.clients.get(clientId)\n\n if (!clientInfo) {\n this.logger.warn(`Cannot subscribe: client with id ${clientId} not found`)\n return\n }\n\n const subscriptionId = subscription.id\n\n // Use subscription.api and subscription.endpoint directly\n const apiId = subscription.api\n const methodName = subscription.endpoint\n\n if (!apiId) {\n const errorMessage = `Invalid API ID in subscription: ${subscriptionId}`\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n if (!methodName) {\n const errorMessage = `Invalid endpoint in subscription: ${subscriptionId}`\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n const api = this.apis.get(apiId)\n\n if (!api) {\n const errorMessage = `API not found: ${apiId}`\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n const method = api.methods[methodName]\n\n if (!method || typeof method !== 'function') {\n const errorMessage = `API method not found: ${apiId}.${methodName}`\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n // Create subscription if it doesn't exist\n if (!this.subscriptions.has(subscriptionId)) {\n this.subscriptions.set(subscriptionId, {\n id: subscriptionId,\n api: apiId,\n endpoint: methodName,\n params: subscription.params,\n clients: new Set(),\n })\n\n this.logger.info(`Created subscription ${subscriptionId}`)\n\n if (api.mode === 'poll') {\n // Make an immediate call to avoid waiting for the first interval\n void this.processApiCall(subscriptionId, method, subscription.params)\n\n // Set up polling to call the method periodically\n const timer = setInterval(() => {\n // Call the method periodically\n void this.processApiCall(subscriptionId, method, subscription.params)\n }, this.pollInterval)\n\n const sub = this.subscriptions.get(subscriptionId)\n\n if (sub) {\n sub.timer = timer\n }\n\n this.logger.info(\n `Started polling for subscription ${subscriptionId} every ${this.pollInterval}ms`,\n )\n } else if (api.mode === 'push') {\n // Set up push producer to call the method when data is available.\n // The producer may return a disposer that stops its underlying interval; capture it\n // so we can stop pushing when the last client unsubscribes or disconnects.\n const producerResult = method((data: unknown) => {\n const message = { id: subscriptionId, data }\n\n // Cache the response\n const sub = this.subscriptions.get(subscriptionId)\n\n if (sub) {\n sub.cached = message\n }\n\n // Send the data to all subscribed clients\n this.send(subscriptionId, message, WebSocketEvent.API_DATA)\n }, subscription.params)\n\n Promise.resolve(producerResult)\n .then((result) => {\n if (typeof result !== 'function') {\n return\n }\n\n const dispose = result as () => void\n const sub = this.subscriptions.get(subscriptionId)\n\n // Subscription may have been removed before the producer resolved.\n // In that case dispose immediately to avoid a leaked interval.\n if (sub) {\n sub.pushDispose = dispose\n } else {\n dispose()\n }\n })\n .catch((error) => {\n this.logger.error(\n `Push producer for ${subscriptionId} failed: ${getErrorMessage(error)}`,\n )\n })\n\n this.logger.info(`Created push producer for subscription ${subscriptionId}`)\n }\n }\n\n // Add client to subscription\n const sub = this.subscriptions.get(subscriptionId)\n\n if (!sub) {\n this.logger.warn(`Cannot subscribe: subscription ${subscriptionId} not found`)\n return\n }\n\n sub.clients.add(clientId)\n clientInfo.subscriptions.add(subscriptionId)\n\n // Send cached data immediately if available\n if (sub.cached) {\n this.logger.info(\n `Sending cached data to client with id ${clientId} for subscription ${subscriptionId}`,\n )\n clientInfo.socket.emit(WebSocketEvent.API_DATA, sub.cached)\n }\n }\n\n /**\n * Unsubscribe a client from an API method subscription.\n *\n * This removes the client from the specified subscription. If this was the last client\n * subscribed to this API method, the subscription is cleaned up entirely and any polling\n * timers are cleared to prevent unnecessary API calls and memory leaks.\n *\n * If the client or subscription doesn't exist, the method returns silently without error.\n * This is safe to call multiple times or for non-existent subscriptions.\n *\n * @param clientId - The unique identifier of the client to unsubscribe\n * @param subscriptionId - The subscription ID to unsubscribe from (matches `subscription.id`, conventionally 'api.method')\n *\n * @example\n * ```ts\n * // Unsubscribe from a specific API\n * socket.on('unsubscribe', (subscriptionId) => {\n * bus.unsubscribe(socket.id, subscriptionId)\n * })\n *\n * // Unsubscribe when widget is removed\n * bus.unsubscribe('client-123', 'github.repos')\n * ```\n */\n public unsubscribe(clientId: string, subscriptionId: string): void {\n const clientInfo = this.clients.get(clientId)\n\n if (!clientInfo) {\n this.logger.warn(`Cannot unsubscribe: client with id ${clientId} not found`)\n return\n }\n\n const subscription = this.subscriptions.get(subscriptionId)\n\n if (!subscription) {\n this.logger.warn(`Cannot unsubscribe: subscription ${subscriptionId} not found`)\n return\n }\n\n subscription.clients.delete(clientId)\n clientInfo.subscriptions.delete(subscriptionId)\n\n if (subscription.clients.size === 0) {\n this.teardownSubscription(subscriptionId)\n }\n }\n\n /**\n * Get detailed information about all active subscriptions.\n *\n * Returns an array of subscription metadata including subscription IDs, client counts,\n * cache status, and timer status. Useful for monitoring, debugging, and displaying\n * real-time statistics about API subscriptions.\n *\n * @returns Array of subscription info objects, each containing:\n * - `id`: Subscription identifier (conventionally 'api.method')\n * - `clientCount`: Number of clients subscribed to this API method\n * - `hasCachedData`: Whether cached data is available for immediate delivery\n * - `hasTimer`: Whether a polling timer is active (poll mode only)\n *\n * @remarks\n * Subscriptions are torn down automatically when their last client leaves, so a\n * `clientCount` of `0` should not appear during normal operation.\n *\n * @example\n * ```ts\n * // Get all subscription stats\n * const subs = bus.getSubscriptionsInfo()\n * console.log(subs)\n * // [\n * // {\n * // id: 'github.repos',\n * // clientCount: 3,\n * // hasCachedData: true,\n * // hasTimer: true\n * // },\n * // {\n * // id: 'weather.current',\n * // clientCount: 1,\n * // hasCachedData: false,\n * // hasTimer: false\n * // }\n * // ]\n *\n * // Debug endpoint for monitoring\n * app.get('/debug/subscriptions', (req, res) => {\n * res.json({\n * total: bus.getSubscriptionsInfo().length,\n * subscriptions: bus.getSubscriptionsInfo()\n * })\n * })\n * ```\n */\n public getSubscriptionsInfo(): SubscriptionInfo[] {\n return Array.from(this.subscriptions.entries()).map(([id, sub]) => ({\n id,\n clientCount: sub.clients.size,\n hasCachedData: !!sub.cached,\n hasTimer: !!sub.timer,\n }))\n }\n\n /**\n * Execute an API method call and distribute results to subscribed clients.\n *\n * This internal method is called either periodically (for poll mode) or on-demand (for push mode).\n * It executes the API method with the provided parameters, caches the result, and sends the data\n * to all clients subscribed to this API method. If the API call fails, an error message is sent\n * instead and logged.\n *\n * @param subscriptionId - The subscription identifier (conventionally 'api.method')\n * @param method - The API method function to execute\n * @param params - Optional parameters to pass to the API method\n *\n * @remarks\n * This method handles:\n * - Executing the API method with error handling\n * - Caching successful responses for immediate delivery to new subscribers\n * - Broadcasting data to all subscribed clients via 'api.data' event\n * - Broadcasting errors to all subscribed clients via 'api.error' event\n * - Logging execution details for debugging\n *\n * @internal\n */\n private async processApiCall(\n subscriptionId: string,\n method: (...args: any[]) => Promise<unknown>,\n params?: unknown,\n ): Promise<void> {\n try {\n this.logger.debug(`Calling subscription ${subscriptionId}`)\n\n const data = await method(params)\n const message = { id: subscriptionId, data }\n\n // Cache the response\n const subscription = this.subscriptions.get(subscriptionId)\n\n if (subscription) {\n this.logger.info(`Caching response for subscription ${subscriptionId}`)\n subscription.cached = message\n }\n\n // Send to all subscribed clients\n this.send(subscriptionId, message, WebSocketEvent.API_DATA)\n } catch (error) {\n const errorMessage = {\n id: subscriptionId,\n error: {\n message: getErrorMessage(error),\n },\n }\n\n this.logger.error({ err: error, subscriptionId }, 'Subscription execution error')\n this.send(subscriptionId, errorMessage, WebSocketEvent.API_ERROR)\n }\n }\n\n /**\n * Broadcast data to all clients subscribed to a specific API method.\n *\n * This internal method iterates through all clients subscribed to the given subscription\n * and emits the data via Socket.IO. It safely handles cases where clients may have\n * disconnected but haven't been cleaned up yet.\n *\n * @param subscriptionId - The subscription identifier (conventionally 'api.method')\n * @param data - The data payload to send (API response or error message)\n * @param type - The message type: 'api.data' for successful responses, 'api.error' for errors\n *\n * @remarks\n * This method:\n * - Returns silently if the subscription doesn't exist (already cleaned up)\n * - Skips clients that no longer exist (race condition handling)\n * - Uses Socket.IO's emit to send data to each client\n *\n * @internal\n */\n private send(subscriptionId: string, data: unknown, type: WebSocketEvent): void {\n const subscription = this.subscriptions.get(subscriptionId)\n\n if (!subscription) {\n this.logger.warn(`Cannot send data to subscription ${subscriptionId}: not found`)\n return\n }\n\n for (const clientId of subscription.clients) {\n const clientInfo = this.clients.get(clientId)\n\n if (clientInfo) {\n clientInfo.socket.emit(type, data)\n }\n }\n }\n\n /**\n * Tear down a subscription that has no clients remaining.\n *\n * Clears any poll-mode timer and invokes the push-mode disposer (if either is set),\n * then removes the subscription entry. Safe to call when the subscription is missing.\n *\n * @param subscriptionId - The subscription identifier (conventionally 'api.method')\n *\n * @internal\n */\n private teardownSubscription(subscriptionId: string): void {\n const subscription = this.subscriptions.get(subscriptionId)\n\n if (!subscription) {\n return\n }\n\n if (subscription.timer) {\n clearInterval(subscription.timer)\n }\n\n if (subscription.pushDispose) {\n try {\n subscription.pushDispose()\n } catch (error) {\n this.logger.error(`Push dispose for ${subscriptionId} failed: ${getErrorMessage(error)}`)\n }\n }\n\n this.subscriptions.delete(subscriptionId)\n this.logger.info(`Removed subscription ${subscriptionId} (no clients)`)\n }\n}\n","import { readFile } from 'node:fs/promises'\nimport { extname } from 'node:path'\n\nimport type { DashfyConfig } from '@getdashfy/types'\nimport { getErrorMessage } from '@getdashfy/utils'\nimport { parse as parseYaml } from 'yaml'\nimport { z } from 'zod'\n\nimport { DEFAULT_HOST, DEFAULT_PORT } from './constants'\n\nconst WidgetConfigSchema = z\n .object({\n extension: z.string(),\n widget: z.string(),\n title: z.string().optional(),\n columns: z.number().positive(),\n rows: z.number().positive(),\n x: z.number().nonnegative(),\n y: z.number().nonnegative(),\n })\n .passthrough() // Allow additional widget-specific properties\n\nconst DashboardConfigSchema = z.object({\n name: z.string().optional(),\n title: z.string().optional(),\n columns: z.number().positive(),\n rows: z.number().positive(),\n widgets: z.array(WidgetConfigSchema),\n})\n\nconst DashfyConfigSchema = z.object({\n port: z.number().positive().default(DEFAULT_PORT),\n host: z.string().default(DEFAULT_HOST),\n baseDir: z.string().optional(),\n theme: z.string().optional(),\n rotationDuration: z.number().positive().optional(),\n dashboards: z.array(DashboardConfigSchema).min(1),\n apis: z\n .object({\n pollInterval: z.number().positive().optional(),\n })\n .passthrough()\n .optional(),\n})\n\n/**\n * Error thrown for configuration problems that already carry a user-facing message\n * (unsupported format, parse failure, validation failure). `loadConfig` re-throws these\n * as-is instead of wrapping them again.\n */\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigError'\n }\n}\n\n/**\n * Supported configuration file formats.\n */\nenum ConfigFormat {\n JSON = 'json',\n YAML = 'yaml',\n}\n\n/**\n * Determines the configuration file format based on file extension.\n *\n * @param filePath - Path to the configuration file\n * @returns The detected format (JSON or YAML)\n * @throws {Error} If the file extension is not supported\n */\nfunction detectConfigFormat(filePath: string): ConfigFormat {\n const extension = extname(filePath).toLowerCase()\n\n switch (extension) {\n case '.json':\n return ConfigFormat.JSON\n case '.yml':\n case '.yaml':\n return ConfigFormat.YAML\n default:\n throw new ConfigError(\n `Unsupported configuration file format: ${extension}. Supported formats: .json, .yml, .yaml`,\n )\n }\n}\n\n/**\n * Parses the configuration content based on the specified format.\n *\n * @param content - Raw file content as string\n * @param format - The format to parse (JSON or YAML)\n * @returns Parsed configuration object\n * @throws {Error} If parsing fails\n */\nfunction parseConfigContent(content: string, format: ConfigFormat): unknown {\n try {\n switch (format) {\n case ConfigFormat.JSON:\n return JSON.parse(content)\n case ConfigFormat.YAML:\n return parseYaml(content)\n /* v8 ignore next 3 */\n default:\n // This should never happen due to exhaustive enum check\n throw new Error(`Unsupported format: ${format as string}`)\n }\n } catch (error) {\n throw new ConfigError(\n `Failed to parse ${format.toUpperCase()} content: ${getErrorMessage(error)}`,\n )\n }\n}\n\n/**\n * Loads and validates a Dashfy configuration file (JSON or YAML).\n *\n * Automatically detects the file format based on the extension (.json, .yml, .yaml),\n * parses the content, and validates the structure against the Zod schema.\n * Throws descriptive errors if the file cannot be read, parsed, or validation fails.\n *\n * @param path - Path to the configuration file (supports .json, .yml, .yaml)\n * @returns Validated and type-safe Dashfy configuration\n * @throws {Error} If file cannot be read, format is unsupported, or validation fails\n *\n * @example\n * ```ts\n * // Load JSON config\n * const config = await loadConfig('./config.json')\n *\n * // Load YAML config\n * const config = await loadConfig('./config.yml')\n *\n * console.log(`Server will run on ${config.host}:${config.port}`)\n * ```\n */\nexport async function loadConfig(path: string): Promise<DashfyConfig> {\n try {\n // Detect format from file extension\n const format = detectConfigFormat(path)\n\n // Read file content\n const content = await readFile(path, 'utf-8')\n\n // Parse content based on format\n const rawConfig = parseConfigContent(content, format)\n\n // Validate and transform with Zod schema\n const config = DashfyConfigSchema.parse(rawConfig)\n\n return config as DashfyConfig\n } catch (error) {\n if (error instanceof z.ZodError) {\n const issues = error.issues\n .map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)\n .join('\\n')\n throw new ConfigError(`Configuration validation failed:\\n${issues}`)\n }\n\n // Re-throw errors that already carry a user-facing message\n if (error instanceof ConfigError) {\n throw error\n }\n\n throw new Error(`Failed to load configuration from \"${path}\": ${getErrorMessage(error)}`)\n }\n}\n","import type { Logger } from 'pino'\nimport pino from 'pino'\n\n/**\n * Creates a Pino logger instance with environment-aware configuration.\n *\n * Development: pretty-printed output via `pino-pretty` (default level: `info`)\n * Production: structured JSON logs (default level: `warn`)\n *\n * @param options - Optional Pino configuration to override defaults\n * @returns Configured Pino Logger instance\n *\n * @example\n * ```ts\n * const logger = createLogger()\n * logger.info('Server started')\n * logger.error({ err }, 'Request failed')\n *\n * // Custom level\n * const debugLogger = createLogger({ level: 'debug' })\n *\n * // Child logger with context\n * const busLogger = logger.child({ component: 'bus' })\n * ```\n */\nexport function createLogger(options?: pino.LoggerOptions): Logger {\n const isDevelopment = process.env.NODE_ENV !== 'production'\n\n return pino({\n level: process.env.LOG_LEVEL ?? (isDevelopment ? 'info' : 'warn'),\n transport: isDevelopment\n ? {\n target: 'pino-pretty',\n options: {\n colorize: true,\n translateTime: 'HH:MM:ss',\n ignore: 'pid,hostname',\n singleLine: false,\n },\n }\n : undefined,\n ...options,\n })\n}\n","import path from 'node:path'\n\nimport cors from '@fastify/cors'\nimport fastifyStatic from '@fastify/static'\nimport type { APIRegistration, DashfyConfig, PollMode, Subscription } from '@getdashfy/types'\nimport { WebSocketEvent } from '@getdashfy/types'\nimport { getErrorMessage } from '@getdashfy/utils'\nimport { watch } from 'chokidar'\nimport type { FastifyInstance } from 'fastify'\nimport Fastify from 'fastify'\nimport type { Logger } from 'pino'\nimport { Server as SocketIOServer } from 'socket.io'\n\nimport { Bus } from './bus'\nimport { loadConfig } from './config'\nimport { DEFAULT_HOST, DEFAULT_PORT, SOCKET_PING_INTERVAL, SOCKET_PING_TIMEOUT } from './constants'\nimport { createLogger } from './logger'\nimport type { DashfyOptions } from './types'\n\n/**\n * Main Dashfy server class that manages the dashboard application.\n *\n * Orchestrates HTTP server (Fastify), WebSocket communication (Socket.IO),\n * API registration (Bus), and configuration handling with hot-reload support.\n *\n * @example\n * ```ts\n * import { Dashfy } from '@getdashfy/server'\n *\n * const dashfy = new Dashfy()\n *\n * // Load configuration from JSON (or YAML)\n * await dashfy.configureFromFile('./dashfy.config.json')\n *\n * // Register APIs\n * dashfy.registerApi('github', () => ({\n * async stars(params: { owner: string; repo: string }) {\n * const res = await fetch(`https://api.github.com/repos/${params.owner}/${params.repo}`)\n * const data = await res.json()\n * return { stars: data.stargazers_count }\n * }\n * }))\n *\n * // Start server\n * await dashfy.start()\n * // Server running at http://0.0.0.0:5001\n * ```\n *\n * @example\n * ```ts\n * // Push mode for real-time data\n * dashfy.registerApi('metrics', () => ({\n * cpuUsage(callback: (data: unknown) => void) {\n * const interval = setInterval(() => {\n * callback({ usage: process.cpuUsage(), timestamp: Date.now() })\n * }, 1000)\n * return () => clearInterval(interval) // cleanup\n * }\n * }), 'push')\n * ```\n */\nexport class Dashfy {\n private app: FastifyInstance\n private io?: SocketIOServer\n private bus: Bus\n private config?: DashfyConfig\n private logger: Logger\n private configPath?: string\n\n /**\n * Creates a new Dashfy instance.\n *\n * @param options - Dashfy options\n * @param options.logger - Logger instance\n * @param options.app - Existing Fastify instance. When provided,\n * Dashfy will use this instance instead of creating a new one,\n * allowing integration with existing Fastify middleware and routes.\n *\n * @example\n * ```ts\n * // Integrate Dashfy with existing Fastify app\n * import Fastify from 'fastify'\n * import { Dashfy } from '@getdashfy/server'\n *\n * const app = Fastify()\n *\n * // Add your custom routes\n * app.get('/custom-api', async (request, reply) => {\n * return { message: 'Custom endpoint' }\n * })\n *\n * // Initialize Dashfy with existing app\n * const dashfy = new Dashfy({ app })\n * await dashfy.configureFromFile('./dashfy.config.json')\n * await dashfy.start()\n * ```\n */\n constructor(options: DashfyOptions = {}) {\n this.logger = options.logger ?? createLogger()\n\n // Use existing Fastify instance if provided, otherwise create a new one\n // Disable Fastify's built-in logger when a custom logger is provided\n if (options.app) {\n this.app = options.app\n } else {\n this.app = Fastify({\n logger: options.logger ? false : true,\n disableRequestLogging: true,\n })\n }\n\n this.bus = new Bus({\n logger: this.logger.child({ component: 'bus' }),\n })\n\n // Register core API\n this.registerCoreApi()\n }\n\n /**\n * Configures the Dashfy server with the provided configuration object.\n *\n * This method accepts a configuration object and applies it to the server instance.\n * If a global poll interval is specified in the configuration, it will be applied\n * to the message bus for all poll-mode APIs.\n *\n * @param config - The Dashfy configuration object containing server settings,\n * dashboards, and optional API configuration\n *\n * @remarks\n * This method should be called before {@link start}. For loading configuration\n * from a JSON or YAML file, use {@link configureFromFile} instead.\n *\n * @example\n * ```ts\n * const dashfy = new Dashfy();\n *\n * // Configure with a configuration object\n * dashfy.configure({\n * port: 5001,\n * host: 'localhost',\n * dashboards: [\n * {\n * id: 'main',\n * title: 'Main Dashboard',\n * widgets: []\n * }\n * ],\n * apis: {\n * pollInterval: 30000 // 30 seconds\n * }\n * });\n *\n * await dashfy.start();\n * ```\n */\n public configure(config: DashfyConfig): void {\n this.config = config\n\n if (config.apis?.pollInterval) {\n this.bus.pollInterval = config.apis.pollInterval\n this.logger.info(`Set global poll interval to ${this.bus.pollInterval}ms`)\n }\n }\n\n /**\n * Loads and applies configuration from a file (JSON or YAML).\n *\n * This method reads a configuration file (supports .json, .yml, .yaml formats),\n * validates it against the schema, and applies it to the server instance. It also\n * optionally watches the file for changes and automatically reloads the configuration\n * when the file is modified. When changes are detected, all connected clients are\n * notified via Socket.IO.\n *\n * @param configPath - Path to the configuration file (absolute or relative).\n * Supports .json, .yml, .yaml extensions\n * @param watchConfig - Whether to watch the configuration file for changes.\n * When `true`, the configuration will be automatically reloaded\n * on file changes. Defaults to `true`\n *\n * @throws {Error} If the configuration file cannot be read or fails validation\n *\n * @remarks\n * This method should be called before {@link start}. If you already have a\n * configuration object, use {@link configure} instead.\n *\n * When the configuration is reloaded due to file changes:\n * - The new configuration is validated before applying\n * - Connected clients receive the updated configuration (excluding sensitive API data)\n * - If validation fails, the old configuration remains active\n *\n * @example\n * ```ts\n * const dashfy = new Dashfy();\n *\n * // Load configuration from JSON file with auto-reload\n * await dashfy.configureFromFile('./dashfy.config.json');\n *\n * // Load configuration from YAML file with auto-reload\n * await dashfy.configureFromFile('./dashfy.config.yaml');\n *\n * // Load without watching for changes (production)\n * await dashfy.configureFromFile('./dashfy.config.yaml', false);\n *\n * await dashfy.start();\n * ```\n */\n public async configureFromFile(configPath: string, watchConfig = true): Promise<void> {\n this.configPath = configPath\n const config = await loadConfig(configPath)\n this.configure(config)\n\n // Watch for configuration changes\n if (watchConfig) {\n const watcher = watch(configPath)\n\n watcher.on('change', async () => {\n this.logger.info('Configuration file changed, reloading...')\n\n try {\n const newConfig = await loadConfig(configPath)\n this.configure(newConfig)\n\n // Broadcast configuration update to connected clients\n if (this.io) {\n // Don't send sensitive api configuration\n this.io.emit('configuration', this.getPublicConfig(newConfig))\n }\n\n this.logger.info('Configuration reloaded successfully')\n } catch (error) {\n this.logger.error({ err: error }, 'Failed to reload configuration')\n }\n })\n\n this.logger.info(`Watching configuration file: ${configPath}`)\n }\n }\n\n /**\n * Registers an API with the message bus for data retrieval.\n *\n * This method allows you to register custom APIs that widgets can subscribe to\n * for fetching data. APIs can operate in either poll mode (periodic fetching)\n * or push mode (real-time updates via callbacks).\n *\n * @param id - Unique identifier for the API (e.g., 'github', 'weather', 'analytics')\n * @param api - Factory function that returns an object containing API methods.\n * Each method should be async and return data that widgets can consume\n * @param mode - The API mode: `poll` (default) for periodic polling,\n * or `push` for real-time push-based updates\n *\n * @throws {Error} If an API with the same ID is already registered\n * @throws {Error} If the API mode is invalid\n *\n * @remarks\n * - In **poll mode**, the bus automatically calls API methods at regular intervals\n * - In **push mode**, API methods receive a callback to push data when available\n * - APIs should be registered before calling {@link start}\n * - The core `dashfy` API is automatically registered and provides system information\n *\n * @example\n * ```ts\n * const dashfy = new Dashfy();\n *\n * // Register a poll-mode API (default)\n * dashfy.registerApi('github', () => ({\n * async stars(params: { owner: string; repo: string }) {\n * const response = await fetch(\n * `https://api.github.com/repos/${params.owner}/${params.repo}`\n * );\n * const data = await response.json();\n * return { stars: data.stargazers_count };\n * }\n * }));\n *\n * // Register a push-mode API for real-time updates\n * dashfy.registerApi('metrics', () => ({\n * async cpuUsage(callback: (data: unknown) => void) {\n * const interval = setInterval(() => {\n * const usage = process.cpuUsage();\n * callback({ cpu: usage });\n * }, 1000);\n *\n * return () => clearInterval(interval);\n * }\n * }), 'push');\n * ```\n */\n public registerApi(id: string, api: APIRegistration, mode: PollMode = 'poll'): void {\n this.bus.registerApi(id, api, mode)\n }\n\n /**\n * Starts the Dashfy server and begins listening for connections.\n *\n * This method initializes and starts all server components including the HTTP server,\n * WebSocket connections, static file serving, and API endpoints. It must be called\n * after configuration is loaded via {@link configure} or {@link configureFromFile}.\n *\n * @throws {Error} If configuration has not been set before calling this method\n *\n * @remarks\n * **What this method does:**\n * 1. Validates that configuration is loaded\n * 2. Sets up CORS for cross-origin requests\n * 3. Serves static files from the `build` directory (if available)\n * 4. Registers HTTP endpoints:\n * - `GET /config` - Returns public configuration (excludes sensitive API data)\n * - `GET /health` - Health check endpoint with uptime and status\n * - `GET /api/info` - Returns registered APIs and connection info\n * 5. Initializes Socket.IO for real-time WebSocket communication\n * 6. Sets up Socket.IO event handlers:\n * - `connection` - Client connects, receives configuration\n * - `api.subscription` - Widget subscribes to data\n * - `api.unsubscription` - Widget unsubscribes from data\n * - `disconnect` - Client disconnects, cleanup performed\n * - `error` - Socket error handling\n * 7. Starts listening on configured host and port\n *\n * **Port and Host Resolution:**\n * - Port: `process.env.PORT` → `config.port` → `5001` (default)\n * - Host: `process.env.HOST` → `config.host` → `0.0.0.0` (default)\n *\n * @example\n * ```ts\n * const dashfy = new Dashfy();\n *\n * // Configure the server\n * await dashfy.configureFromFile('./dashfy.config.json');\n *\n * // Register custom APIs\n * dashfy.registerApi('github', () => ({\n * async stars(params: { owner: string; repo: string }) {\n * // ... fetch GitHub stars\n * }\n * }));\n *\n * // Start the server\n * await dashfy.start();\n * // Server is now running and accepting connections\n * ```\n *\n * @example\n * ```ts\n * // With environment variables\n * process.env.PORT = '3000';\n * process.env.HOST = 'localhost';\n *\n * const dashfy = new Dashfy();\n * await dashfy.configureFromFile('./dashfy.config.json');\n * await dashfy.start();\n * // Server running on http://localhost:3000\n * ```\n */\n public async start(): Promise<void> {\n if (!this.config) {\n const errorMessage =\n 'Configuration required. Call configure() or configureFromFile() before starting.'\n this.logger.error(errorMessage)\n throw new Error(errorMessage)\n }\n\n // Setup CORS\n await this.app.register(cors, {\n origin: true,\n credentials: true,\n })\n\n // Serve static files (for production builds)\n const baseDir = this.config.baseDir ?? process.cwd()\n const staticPath = path.join(baseDir, 'build')\n\n try {\n await this.app.register(fastifyStatic, {\n root: staticPath,\n prefix: '/',\n })\n this.logger.info(`Serving static files from ${staticPath}`)\n } catch (_error) {\n this.logger.warn('Static files directory not found, skipping static serve')\n }\n\n // Configuration endpoint (don't expose sensitive api config)\n this.app.get('/config', () => {\n return this.getPublicConfig(this.config!)\n })\n\n // Health check endpoint\n this.app.get('/health', () => ({\n status: 'ok',\n uptime: process.uptime(),\n timestamp: new Date().toISOString(),\n }))\n\n // API info endpoint\n this.app.get('/api/info', () => ({\n apis: this.bus.listApis(),\n clientCount: this.bus.clientCount(),\n subscriptions: this.bus.getSubscriptionsInfo(),\n }))\n\n // Setup Socket.IO - attach to Fastify's server\n this.io = new SocketIOServer(this.app.server, {\n cors: {\n origin: '*',\n credentials: true,\n },\n transports: ['websocket', 'polling'],\n pingTimeout: SOCKET_PING_TIMEOUT,\n pingInterval: SOCKET_PING_INTERVAL,\n })\n\n // Socket.IO server-level error handler\n this.io.on('error', (error: Error) => {\n this.logger.error({ err: error }, 'Socket.IO server error')\n })\n\n // Socket.IO connection handler\n this.io.on('connection', (socket) => {\n this.logger.info(`Socket connected: ${socket.id}`)\n\n this.bus.addClient(socket)\n\n // Send configuration to newly connected client\n if (this.config) {\n socket.emit('configuration', this.getPublicConfig(this.config))\n }\n\n socket.on(WebSocketEvent.API_SUBSCRIPTION, (subscription: Subscription) => {\n this.logger.debug(`Subscription request: ${subscription.id} from ${socket.id}`)\n try {\n this.bus.subscribe(socket.id, subscription)\n } catch (error) {\n this.logger.error({ err: error }, 'Subscription error')\n socket.emit(WebSocketEvent.API_ERROR, {\n id: subscription.id,\n error: {\n message: getErrorMessage(error),\n },\n })\n }\n })\n\n socket.on(WebSocketEvent.API_UNSUBSCRIPTION, (subscription: Subscription) => {\n this.logger.debug(`Unsubscription request: ${subscription.id} from ${socket.id}`)\n this.bus.unsubscribe(socket.id, subscription.id)\n })\n\n socket.on(WebSocketEvent.DISCONNECT, (reason) => {\n this.logger.info(`Socket disconnected: ${socket.id} (${reason})`)\n this.bus.removeClient(socket.id)\n })\n\n socket.on(WebSocketEvent.ERROR, (error) => {\n this.logger.error({ err: error, socketId: socket.id }, 'Socket error')\n })\n })\n\n const port = process.env.PORT ? Number(process.env.PORT) : (this.config.port ?? DEFAULT_PORT)\n const host = process.env.HOST ?? this.config.host ?? DEFAULT_HOST\n\n // Start both Fastify and Socket.IO\n await this.app.listen({ port, host })\n\n this.logger.info(`🚀 Dashfy server started`)\n this.logger.info(` - HTTP: http://${host}:${port}`)\n this.logger.info(` - WebSocket: ws://${host}:${port}`)\n this.logger.info(` - Registered APIs: ${this.bus.listApis().join(', ')}`)\n }\n\n /**\n * Gracefully stops the Dashfy server and cleans up all resources.\n *\n * This method performs a graceful shutdown of the server, closing all active\n * connections and releasing resources. It closes the Socket.IO server (if initialized)\n * and the underlying Fastify HTTP server.\n *\n * @remarks\n * **Shutdown process:**\n * 1. Logs shutdown initiation\n * 2. Closes Socket.IO server (disconnects all WebSocket clients)\n * 3. Closes Fastify HTTP server (stops accepting new connections)\n * 4. Waits for all pending requests to complete\n * 5. Logs shutdown completion\n *\n * This method is safe to call even if the server was never started or is already stopped.\n * All connected clients will be disconnected, and their subscriptions will be cleaned up\n * automatically by the bus.\n *\n * @example\n * ```ts\n * const dashfy = new Dashfy();\n * await dashfy.configureFromFile('./dashfy.config.json');\n * await dashfy.start();\n *\n * // Later, gracefully shut down the server\n * await dashfy.stop();\n * ```\n *\n * @example\n * ```ts\n * // Graceful shutdown on process signals\n * const dashfy = new Dashfy();\n * await dashfy.configureFromFile('./dashfy.config.json');\n * await dashfy.start();\n *\n * process.on('SIGTERM', async () => {\n * console.log('SIGTERM received, shutting down gracefully...');\n * await dashfy.stop();\n * process.exit(0);\n * });\n *\n * process.on('SIGINT', async () => {\n * console.log('SIGINT received, shutting down gracefully...');\n * await dashfy.stop();\n * process.exit(0);\n * });\n * ```\n */\n public async stop(): Promise<void> {\n this.logger.info('Stopping Dashfy server...')\n\n if (this.io) {\n await new Promise<void>((resolve, reject) => {\n void this.io!.close((error) => {\n if (error) {\n reject(error)\n } else {\n resolve()\n }\n })\n })\n }\n\n await this.app.close()\n this.logger.info('Server stopped')\n }\n\n /**\n * Returns a sanitized configuration object without sensitive API data.\n *\n * Removes the `apis` property from the configuration before sending it to clients,\n * as it may contain sensitive server-side settings like polling intervals or API keys.\n *\n * @param config - The full Dashfy configuration object\n * @returns Configuration object without the `apis` property\n *\n * @internal\n */\n private getPublicConfig(config: DashfyConfig): Omit<DashfyConfig, 'apis'> {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { apis, ...publicConfig } = config\n return publicConfig\n }\n\n /**\n * Registers the core `dashfy` API that provides system introspection and monitoring.\n *\n * This internal method is automatically called during construction to register\n * a built-in API that exposes server health, statistics, and debugging information.\n * The `dashfy` API is available to all widgets for monitoring server state.\n *\n * @remarks\n * **Available methods:**\n * - `inspector()` - Returns comprehensive system information including:\n * - `apis`: List of all registered API identifiers\n * - `clientCount`: Number of currently connected WebSocket clients\n * - `subscriptions`: Detailed information about all active subscriptions\n * - `uptime`: Server process uptime in seconds\n * - `version`: Dashfy server version (from package.json)\n * - `nodeVersion`: Node.js runtime version\n *\n * This API is useful for:\n * - Building admin/monitoring dashboards\n * - Debugging subscription and connection issues\n * - Health checks and system monitoring\n * - Displaying server statistics to users\n *\n * @example\n * ```ts\n * // Widgets can subscribe to the dashfy API\n * // In a widget configuration:\n * {\n * api: 'dashfy',\n * method: 'inspector'\n * }\n *\n * // This returns:\n * {\n * apis: ['dashfy', 'github', 'weather'],\n * clientCount: 5,\n * subscriptions: [...],\n * uptime: 3600.5,\n * version: '0.1.0',\n * nodeVersion: 'v20.10.0'\n * }\n * ```\n *\n * @internal\n */\n private registerCoreApi(): void {\n this.registerApi('dashfy', () => ({\n inspector: async () =>\n Promise.resolve({\n apis: this.bus.listApis(),\n clientCount: this.bus.clientCount(),\n subscriptions: this.bus.getSubscriptionsInfo(),\n uptime: process.uptime(),\n version: process.env.npm_package_version ?? 'unknown',\n nodeVersion: process.version,\n }),\n }))\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getdashfy/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Dashfy server with real-time data streaming and multi-dashboard support",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dashfy",
|
|
7
|
+
"server",
|
|
8
|
+
"fastify",
|
|
9
|
+
"socket.io",
|
|
10
|
+
"dashboard"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/dashfy/dashfy#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/dashfy/dashfy/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git@github.com:dashfy/dashfy.git",
|
|
19
|
+
"directory": "packages/server"
|
|
20
|
+
},
|
|
21
|
+
"license": "AGPL-3.0-or-later",
|
|
22
|
+
"author": {
|
|
23
|
+
"name": "Breno Polanski",
|
|
24
|
+
"email": "breno@dashfy.dev",
|
|
25
|
+
"url": "https://github.com/brenopolanski"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"require": "./dist/index.cjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"main": "./dist/index.cjs",
|
|
36
|
+
"module": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"CHANGELOG.md"
|
|
43
|
+
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20.0.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"check:circular": "madge --circular --extensions ts src/",
|
|
53
|
+
"clean": "rm -rf .turbo dist node_modules",
|
|
54
|
+
"dev": "tsup --watch",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:coverage": "vitest run --coverage",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"typecheck": "tsc --noEmit"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@getdashfy/utils": "workspace:*",
|
|
62
|
+
"@fastify/cors": "^10.0.1",
|
|
63
|
+
"@fastify/static": "^8.0.2",
|
|
64
|
+
"chokidar": "^4.0.3",
|
|
65
|
+
"fastify": "^5.1.0",
|
|
66
|
+
"pino": "^9.5.0",
|
|
67
|
+
"pino-pretty": "^13.0.0",
|
|
68
|
+
"socket.io": "^4.8.1",
|
|
69
|
+
"undici": "^6.22.0",
|
|
70
|
+
"yaml": "^2.8.1",
|
|
71
|
+
"zod": "^3.24.1"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@getdashfy/tsconfig": "workspace:*",
|
|
75
|
+
"@getdashfy/types": "workspace:*",
|
|
76
|
+
"@types/node": "^20.17.10",
|
|
77
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
78
|
+
"tsup": "^8.3.5",
|
|
79
|
+
"typescript": "^5.7.2",
|
|
80
|
+
"vitest": "^2.1.8"
|
|
81
|
+
}
|
|
82
|
+
}
|