@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
package/README.md
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
# `@getdashfy/server`
|
|
2
|
+
|
|
3
|
+
> Dashfy server with real-time data streaming and multi-dashboard support.
|
|
4
|
+
|
|
5
|
+
## Introduction
|
|
6
|
+
|
|
7
|
+
`@getdashfy/server` is the backend runtime for Dashfy dashboards. It handles configuration loading, API registration, real-time data streaming, and WebSocket communication with clients.
|
|
8
|
+
|
|
9
|
+
The server acts as the central orchestrator that:
|
|
10
|
+
|
|
11
|
+
- Loads dashboard configuration from `TypeScript` object / `JSON` / `YAML` files
|
|
12
|
+
- Connects to APIs and data sources through extensions
|
|
13
|
+
- Manages subscriptions and real-time updates
|
|
14
|
+
- Streams data to connected clients via WebSockets
|
|
15
|
+
- Provides HTTP endpoints for health checks and configuration
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Install with your favorite package manager:
|
|
20
|
+
|
|
21
|
+
#### `npm`
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @getdashfy/server
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### `pnpm`
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add @getdashfy/server
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### `yarn`
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
yarn add @getdashfy/server
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### `bun`
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bun add @getdashfy/server
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
Create a Dashfy server and load a dashboard configuration:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createJsonClient } from '@getdashfy/ext-json/client'
|
|
51
|
+
import { createGitHubClient } from '@getdashfy/ext-github/client'
|
|
52
|
+
import { Dashfy } from '@getdashfy/server'
|
|
53
|
+
|
|
54
|
+
// Create server instance
|
|
55
|
+
const dashfy = new Dashfy()
|
|
56
|
+
|
|
57
|
+
// Load dashboard configuration from a file (JSON or YAML):
|
|
58
|
+
await dashfy.configureFromFile('./dashfy.config.yml')
|
|
59
|
+
|
|
60
|
+
// or from a TypeScript object:
|
|
61
|
+
// import type { DashfyConfig } from '@getdashfy/types'
|
|
62
|
+
// const dashfyConfig: DashfyConfig = {...}
|
|
63
|
+
// dashfy.configure(dashfyConfig)
|
|
64
|
+
|
|
65
|
+
// Register JSON API
|
|
66
|
+
dashfy.registerApi('json', createJsonClient())
|
|
67
|
+
|
|
68
|
+
// Register GitHub API
|
|
69
|
+
dashfy.registerApi(
|
|
70
|
+
'github',
|
|
71
|
+
createGitHubClient({
|
|
72
|
+
token: process.env.GITHUB_TOKEN!,
|
|
73
|
+
}),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// Start server
|
|
77
|
+
await dashfy.start()
|
|
78
|
+
// Server running at http://0.0.0.0:5001
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Core Features
|
|
82
|
+
|
|
83
|
+
#### ยป Configuration Management
|
|
84
|
+
|
|
85
|
+
Load dashboard configuration from `TypeScript` object, `JSON` or `YAML` files with automatic format detection:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// Load from file
|
|
89
|
+
await dashfy.configureFromFile('./dashfy.config.json')
|
|
90
|
+
|
|
91
|
+
// Or provide config directly
|
|
92
|
+
dashfy.configure({
|
|
93
|
+
dashboards: [
|
|
94
|
+
{
|
|
95
|
+
title: 'My Dashboard',
|
|
96
|
+
columns: 3,
|
|
97
|
+
rows: 2,
|
|
98
|
+
widgets: [
|
|
99
|
+
/* ... */
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Hot-reload support**: Configuration changes are automatically detected and broadcast to connected clients.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// Enable hot-reload (default)
|
|
110
|
+
await dashfy.configureFromFile('./dashfy.config.yml', true)
|
|
111
|
+
|
|
112
|
+
// Disable hot-reload (production)
|
|
113
|
+
await dashfy.configureFromFile('./dashfy.config.yml', false)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### ยป API Registration
|
|
117
|
+
|
|
118
|
+
Register custom APIs that widgets can subscribe to:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
dashfy.registerApi('github', ({ logger, request }) => ({
|
|
122
|
+
async repos(params: { user: string }) {
|
|
123
|
+
logger.info({ user: params.user }, 'Fetching repositories')
|
|
124
|
+
return request({
|
|
125
|
+
url: `https://api.github.com/users/${params.user}/repos`,
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
async stars(params: { owner: string; repo: string }) {
|
|
130
|
+
const data = await request({
|
|
131
|
+
url: `https://api.github.com/repos/${params.owner}/${params.repo}`,
|
|
132
|
+
})
|
|
133
|
+
return { stars: data.stargazers_count }
|
|
134
|
+
},
|
|
135
|
+
}))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**API modes**:
|
|
139
|
+
|
|
140
|
+
- **Poll mode** (default): Periodically fetches data at configured intervals
|
|
141
|
+
- **Push mode**: Real-time streaming with callback-based producers
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// Poll mode - fetches every 15 seconds (configurable)
|
|
145
|
+
dashfy.registerApi('weather', weatherApi, 'poll')
|
|
146
|
+
|
|
147
|
+
// Push mode - streams data as it arrives
|
|
148
|
+
dashfy.registerApi(
|
|
149
|
+
'metrics',
|
|
150
|
+
({ logger }) => ({
|
|
151
|
+
cpuUsage(callback: (data: unknown) => void) {
|
|
152
|
+
const interval = setInterval(() => {
|
|
153
|
+
callback({
|
|
154
|
+
usage: process.cpuUsage(),
|
|
155
|
+
timestamp: Date.now(),
|
|
156
|
+
})
|
|
157
|
+
}, 1_000)
|
|
158
|
+
|
|
159
|
+
return () => clearInterval(interval)
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
'push',
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### ยป Real-time Updates
|
|
167
|
+
|
|
168
|
+
Built on [Socket.IO](https://github.com/socketio/socket.io) for bidirectional [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) communication:
|
|
169
|
+
|
|
170
|
+
**Server โ Client Events**:
|
|
171
|
+
|
|
172
|
+
- `configuration` - Dashboard config updates
|
|
173
|
+
- `api.data` - API response data
|
|
174
|
+
- `api.error` - API error messages
|
|
175
|
+
|
|
176
|
+
**Client โ Server Events**:
|
|
177
|
+
|
|
178
|
+
- `api.subscription` - Subscribe to API method
|
|
179
|
+
- `api.unsubscription` - Unsubscribe from API method
|
|
180
|
+
|
|
181
|
+
**Subscription lifecycle**:
|
|
182
|
+
|
|
183
|
+
1. Client subscribes with `api`, `endpoint`, and an `id` (conventionally `api.method`, e.g. `github.stars`); routing uses `api` + `endpoint`, while `id` is the dedup/cache key
|
|
184
|
+
2. Server creates subscription if it doesn't exist (reusing it for later subscribers)
|
|
185
|
+
3. Immediate data fetch + periodic polling (poll mode) or callback setup (push mode)
|
|
186
|
+
4. Data is cached and broadcast to all subscribed clients
|
|
187
|
+
5. When last client unsubscribes, subscription is cleaned up
|
|
188
|
+
|
|
189
|
+
#### ยป Built-in Inspector API
|
|
190
|
+
|
|
191
|
+
The server automatically provides a `dashfy` API for system introspection:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// Widgets can subscribe to dashfy.inspector for monitoring
|
|
195
|
+
{
|
|
196
|
+
apis: ['github', 'json', 'dashfy'],
|
|
197
|
+
clientCount: 3,
|
|
198
|
+
subscriptions: [
|
|
199
|
+
{
|
|
200
|
+
id: 'github.repos',
|
|
201
|
+
clientCount: 2,
|
|
202
|
+
hasCachedData: true,
|
|
203
|
+
hasTimer: true
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
uptime: 3600,
|
|
207
|
+
version: '0.1.0',
|
|
208
|
+
nodeVersion: 'v20.11.0'
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### ยป HTTP Endpoints
|
|
213
|
+
|
|
214
|
+
When started, the server exposes:
|
|
215
|
+
|
|
216
|
+
| Endpoint | Method | Description |
|
|
217
|
+
| ----------- | ------ | -------------------------------------------------------------- |
|
|
218
|
+
| `/config` | GET | Returns public configuration (excludes sensitive `apis` field) |
|
|
219
|
+
| `/health` | GET | Health check with uptime and timestamp |
|
|
220
|
+
| `/api/info` | GET | Server info: registered APIs, client count, subscriptions |
|
|
221
|
+
|
|
222
|
+
#### ยป Logging
|
|
223
|
+
|
|
224
|
+
Structured logging with [Pino](https://github.com/pinojs/pino):
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { Dashfy } from '@getdashfy/server'
|
|
228
|
+
import pino from 'pino'
|
|
229
|
+
|
|
230
|
+
// Custom logger
|
|
231
|
+
const logger = pino({ level: 'debug' })
|
|
232
|
+
const dashfy = new Dashfy({ logger })
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Environment-aware defaults**:
|
|
236
|
+
|
|
237
|
+
- **Development**: Pretty-printed output (level: `info`)
|
|
238
|
+
- **Production**: JSON logs (level: `warn`)
|
|
239
|
+
- Respects `LOG_LEVEL` environment variable
|
|
240
|
+
|
|
241
|
+
#### ยป Integration with Existing Apps
|
|
242
|
+
|
|
243
|
+
Use an existing [Fastify](https://github.com/fastify/fastify) instance:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import Fastify from 'fastify'
|
|
247
|
+
import { Dashfy } from '@getdashfy/server'
|
|
248
|
+
|
|
249
|
+
const app = Fastify()
|
|
250
|
+
|
|
251
|
+
// Add custom routes
|
|
252
|
+
app.get('/custom', async () => ({ message: 'Hello' }))
|
|
253
|
+
|
|
254
|
+
// Initialize Dashfy with existing app
|
|
255
|
+
const dashfy = new Dashfy({ app })
|
|
256
|
+
await dashfy.configureFromFile('./dashfy.config.json')
|
|
257
|
+
await dashfy.start()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## API Reference
|
|
261
|
+
|
|
262
|
+
### `Dashfy` Class
|
|
263
|
+
|
|
264
|
+
#### ยป Constructor
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
new Dashfy(options?: DashfyOptions)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Options**:
|
|
271
|
+
|
|
272
|
+
- `logger?: Logger` - Custom Pino logger instance
|
|
273
|
+
- `app?: FastifyInstance` - Existing Fastify app to integrate with
|
|
274
|
+
|
|
275
|
+
#### ยป Methods
|
|
276
|
+
|
|
277
|
+
##### `configure(config: DashfyConfig): void`
|
|
278
|
+
|
|
279
|
+
Apply configuration object directly.
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
dashfy.configure({
|
|
283
|
+
port: 3000,
|
|
284
|
+
dashboards: [
|
|
285
|
+
/* ... */
|
|
286
|
+
],
|
|
287
|
+
})
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
##### `configureFromFile(configPath: string, watchConfig = true): Promise<void>`
|
|
291
|
+
|
|
292
|
+
Load configuration from `JSON` or `YAML` file.
|
|
293
|
+
|
|
294
|
+
**Parameters**:
|
|
295
|
+
|
|
296
|
+
- `configPath` - Path to configuration file
|
|
297
|
+
- `watchConfig` - Enable hot-reload (default: `true`)
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
await dashfy.configureFromFile('./dashfy.config.yml', true)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
##### `registerApi(id: string, api: APIRegistration, mode: PollMode = 'poll'): void`
|
|
304
|
+
|
|
305
|
+
Register a custom API for widgets to consume.
|
|
306
|
+
|
|
307
|
+
**Parameters**:
|
|
308
|
+
|
|
309
|
+
- `id` - Unique API identifier (e.g., `'github'`, `'weather'`)
|
|
310
|
+
- `api` - Factory function that returns API methods
|
|
311
|
+
- `mode` - `'poll'` (periodic) or `'push'` (real-time)
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
dashfy.registerApi('github', ({ logger, request }) => ({
|
|
315
|
+
async repos(params: { user: string }) {
|
|
316
|
+
return request({ url: `https://api.github.com/users/${params.user}/repos` })
|
|
317
|
+
},
|
|
318
|
+
}))
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
##### `start(): Promise<void>`
|
|
322
|
+
|
|
323
|
+
Start HTTP and WebSocket servers.
|
|
324
|
+
|
|
325
|
+
**Port resolution** (in order):
|
|
326
|
+
|
|
327
|
+
1. `process.env.PORT`
|
|
328
|
+
2. `config.port`
|
|
329
|
+
3. Default: `5001`
|
|
330
|
+
|
|
331
|
+
**Host resolution** (in order):
|
|
332
|
+
|
|
333
|
+
1. `process.env.HOST`
|
|
334
|
+
2. `config.host`
|
|
335
|
+
3. Default: `0.0.0.0`
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
await dashfy.start()
|
|
339
|
+
// Server running at http://0.0.0.0:5001
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
##### `stop(): Promise<void>`
|
|
343
|
+
|
|
344
|
+
Graceful shutdown of all connections.
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
process.on('SIGTERM', async () => {
|
|
348
|
+
await dashfy.stop()
|
|
349
|
+
process.exit(0)
|
|
350
|
+
})
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Configuration Schema
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
interface DashfyConfig {
|
|
357
|
+
port?: number // Server port (default: 5001)
|
|
358
|
+
host?: string // Server host (default: '0.0.0.0')
|
|
359
|
+
baseDir?: string // Base directory for static files
|
|
360
|
+
rotationDuration?: number // Dashboard rotation interval (ms)
|
|
361
|
+
theme?: string // Default theme ID
|
|
362
|
+
dashboards: Array<{
|
|
363
|
+
title?: string
|
|
364
|
+
name?: string
|
|
365
|
+
columns: number // Grid columns
|
|
366
|
+
rows: number // Grid rows
|
|
367
|
+
widgets: Array<{
|
|
368
|
+
extension: string // Extension ID (e.g., 'github')
|
|
369
|
+
widget: string // Widget name (e.g., 'RepoBadge')
|
|
370
|
+
x: number // Grid X position
|
|
371
|
+
y: number // Grid Y position
|
|
372
|
+
columns: number // Widget width in columns
|
|
373
|
+
rows: number // Widget height in rows
|
|
374
|
+
title?: string // Widget title
|
|
375
|
+
// ... widget-specific properties
|
|
376
|
+
}>
|
|
377
|
+
}>
|
|
378
|
+
apis?: {
|
|
379
|
+
pollInterval?: number // Global poll interval in ms (default: 15_000)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## API Registration
|
|
385
|
+
|
|
386
|
+
#### ยป API Factory Function
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
type APIClient = Record<string, (...args: any[]) => Promise<unknown>>
|
|
390
|
+
|
|
391
|
+
type CreatePushInterval = (options?: {
|
|
392
|
+
interval?: number
|
|
393
|
+
}) => (
|
|
394
|
+
key: string,
|
|
395
|
+
callback: (data: unknown) => void,
|
|
396
|
+
fetchFn: () => Promise<unknown>,
|
|
397
|
+
) => () => void
|
|
398
|
+
|
|
399
|
+
type APIRegistration = (dashfy: {
|
|
400
|
+
logger: Logger
|
|
401
|
+
request?: (options: RequestOptions) => Promise<unknown>
|
|
402
|
+
createPushInterval?: CreatePushInterval
|
|
403
|
+
}) => APIClient
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The factory receives three helpers:
|
|
407
|
+
|
|
408
|
+
- `logger` - Child [Pino](https://github.com/pinojs/pino) logger scoped to the API id
|
|
409
|
+
- `request` - HTTP client for fetching data (see [HTTP Request Utility](#http-request-utility))
|
|
410
|
+
- `createPushInterval` - Helper for building push-mode producers that poll a `fetchFn` on an interval (see [Push Mode](#-push-mode))
|
|
411
|
+
|
|
412
|
+
#### ยป Poll Mode (Default)
|
|
413
|
+
|
|
414
|
+
Periodic data fetching at configured intervals:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
dashfy.registerApi(
|
|
418
|
+
'weather',
|
|
419
|
+
({ request }) => ({
|
|
420
|
+
async current(params: { city: string }) {
|
|
421
|
+
return request({
|
|
422
|
+
url: `https://api.weather.com/current?city=${params.city}`,
|
|
423
|
+
})
|
|
424
|
+
},
|
|
425
|
+
}),
|
|
426
|
+
'poll',
|
|
427
|
+
)
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Behavior**:
|
|
431
|
+
|
|
432
|
+
- Fetches data immediately on first subscription
|
|
433
|
+
- Polls at `pollInterval` (default: 15 seconds, configurable in config)
|
|
434
|
+
- Caches responses for instant delivery to new subscribers
|
|
435
|
+
- Stops polling when last client unsubscribes
|
|
436
|
+
|
|
437
|
+
#### ยป Push Mode
|
|
438
|
+
|
|
439
|
+
Real-time streaming with callback-based producers:
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
dashfy.registerApi(
|
|
443
|
+
'metrics',
|
|
444
|
+
({ logger }) => ({
|
|
445
|
+
cpuUsage(callback: (data: unknown) => void) {
|
|
446
|
+
logger.info('Starting CPU monitoring')
|
|
447
|
+
|
|
448
|
+
const interval = setInterval(() => {
|
|
449
|
+
callback({
|
|
450
|
+
usage: process.cpuUsage(),
|
|
451
|
+
timestamp: Date.now(),
|
|
452
|
+
})
|
|
453
|
+
}, 1_000)
|
|
454
|
+
|
|
455
|
+
// Return cleanup function
|
|
456
|
+
return () => {
|
|
457
|
+
clearInterval(interval)
|
|
458
|
+
logger.info('Stopped CPU monitoring')
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
}),
|
|
462
|
+
'push',
|
|
463
|
+
)
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Behavior**:
|
|
467
|
+
|
|
468
|
+
- Calls producer function on first subscription
|
|
469
|
+
- Producer receives callback to push data
|
|
470
|
+
- Data is broadcast to all subscribed clients
|
|
471
|
+
- Cleanup function called when last client unsubscribes
|
|
472
|
+
|
|
473
|
+
**Using `createPushInterval`**: Instead of managing timers by hand, use the injected `createPushInterval` helper to poll a `fetchFn` on an interval and push each result. It returns a disposer used for cleanup:
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
dashfy.registerApi(
|
|
477
|
+
'metrics',
|
|
478
|
+
({ request, createPushInterval }) => {
|
|
479
|
+
// Push every 2 seconds (default interval)
|
|
480
|
+
const startPushInterval = createPushInterval({ interval: 2_000 })
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
async prices(callback: (data: unknown) => void, params: { symbol: string }) {
|
|
484
|
+
return startPushInterval(`prices:${params.symbol}`, callback, () =>
|
|
485
|
+
request({ url: `https://api.example.com/price/${params.symbol}` }),
|
|
486
|
+
)
|
|
487
|
+
},
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
'push',
|
|
491
|
+
)
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## HTTP Request Utility
|
|
495
|
+
|
|
496
|
+
The `request` utility provided to APIs is built on [Undici](https://github.com/nodejs/undici):
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
interface RequestOptions {
|
|
500
|
+
url: string
|
|
501
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
502
|
+
headers?: Record<string, string>
|
|
503
|
+
body?: unknown
|
|
504
|
+
timeout?: number // Default: 10_000ms
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**Features**:
|
|
509
|
+
|
|
510
|
+
- Automatic JSON parsing
|
|
511
|
+
- Configurable timeout
|
|
512
|
+
- Error handling with status codes
|
|
513
|
+
- TypeScript-friendly
|
|
514
|
+
|
|
515
|
+
## Examples
|
|
516
|
+
|
|
517
|
+
#### ยป Basic Server
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
import { Dashfy } from '@getdashfy/server'
|
|
521
|
+
|
|
522
|
+
const dashfy = new Dashfy()
|
|
523
|
+
await dashfy.configureFromFile('./dashfy.config.json')
|
|
524
|
+
await dashfy.start()
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### ยป With Custom Logger
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
import { Dashfy } from '@getdashfy/server'
|
|
531
|
+
import pino from 'pino'
|
|
532
|
+
|
|
533
|
+
const logger = pino({ level: 'debug' })
|
|
534
|
+
const dashfy = new Dashfy({ logger })
|
|
535
|
+
|
|
536
|
+
await dashfy.configureFromFile('./dashfy.config.yml')
|
|
537
|
+
await dashfy.start()
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
#### ยป Multiple APIs
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
import { createJsonClient } from '@getdashfy/ext-json/client'
|
|
544
|
+
import { createGitHubClient } from '@getdashfy/ext-github/client'
|
|
545
|
+
import { createNbaClient } from '@getdashfy/ext-nba/client'
|
|
546
|
+
import { Dashfy } from '@getdashfy/server'
|
|
547
|
+
|
|
548
|
+
const dashfy = new Dashfy()
|
|
549
|
+
await dashfy.configureFromFile('./dashfy.config.yml')
|
|
550
|
+
|
|
551
|
+
// Register multiple APIs
|
|
552
|
+
dashfy.registerApi('json', createJsonClient())
|
|
553
|
+
dashfy.registerApi('github', createGitHubClient({ token: process.env.GITHUB_TOKEN! }))
|
|
554
|
+
dashfy.registerApi('nba', createNbaClient())
|
|
555
|
+
|
|
556
|
+
await dashfy.start()
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
#### ยป Custom API with Authentication
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
dashfy.registerApi('myapi', ({ request }) => ({
|
|
563
|
+
async getData(params: { id: string }) {
|
|
564
|
+
return request({
|
|
565
|
+
url: `https://api.example.com/data/${params.id}`,
|
|
566
|
+
headers: {
|
|
567
|
+
Authorization: `Bearer ${process.env.API_TOKEN}`,
|
|
568
|
+
'Content-Type': 'application/json',
|
|
569
|
+
},
|
|
570
|
+
timeout: 5_000,
|
|
571
|
+
})
|
|
572
|
+
},
|
|
573
|
+
}))
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### ยป Graceful Shutdown
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
const dashfy = new Dashfy()
|
|
580
|
+
await dashfy.configureFromFile('./dashfy.config.yml')
|
|
581
|
+
await dashfy.start()
|
|
582
|
+
|
|
583
|
+
// Handle shutdown signals
|
|
584
|
+
process.on('SIGTERM', async () => {
|
|
585
|
+
console.log('Shutting down gracefully...')
|
|
586
|
+
await dashfy.stop()
|
|
587
|
+
process.exit(0)
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
process.on('SIGINT', async () => {
|
|
591
|
+
console.log('Shutting down gracefully...')
|
|
592
|
+
await dashfy.stop()
|
|
593
|
+
process.exit(0)
|
|
594
|
+
})
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
## Environment Variables
|
|
598
|
+
|
|
599
|
+
| Variable | Description | Default |
|
|
600
|
+
| ----------- | ------------------------------------------------------------------ | --------------------------- |
|
|
601
|
+
| `PORT` | Server port | `5001` |
|
|
602
|
+
| `HOST` | Server host | `0.0.0.0` |
|
|
603
|
+
| `LOG_LEVEL` | Logging level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) | `info` (dev), `warn` (prod) |
|
|
604
|
+
| `NODE_ENV` | Environment (`development`, `production`) | `development` |
|
|
605
|
+
|
|
606
|
+
## Architecture
|
|
607
|
+
|
|
608
|
+
The server consists of several key components working together to power real-time dashboards:
|
|
609
|
+
|
|
610
|
+
```mermaid
|
|
611
|
+
flowchart TB
|
|
612
|
+
ConfigFile[๐ Config File<br/>TypeScript / JSON / YAML]
|
|
613
|
+
|
|
614
|
+
subgraph DashfyServer["๐ฅ๏ธ Dashfy Server"]
|
|
615
|
+
direction TB
|
|
616
|
+
|
|
617
|
+
subgraph Core["Core Orchestrator"]
|
|
618
|
+
DashfyClass[Dashfy Class<br/><i>Main Controller</i>]
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
subgraph Components["Internal Components"]
|
|
622
|
+
direction TB
|
|
623
|
+
ConfigLoader[Configuration Loader<br/><i>Zod Validation</i>]
|
|
624
|
+
MessageBus[Message Bus<br/><i>Pub/Sub System</i>]
|
|
625
|
+
HTTPServer[HTTP Server<br/><i>Fastify</i>]
|
|
626
|
+
WSServer[WebSocket Server<br/><i>Socket.IO</i>]
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
DashfyClass --> ConfigLoader
|
|
630
|
+
DashfyClass --> MessageBus
|
|
631
|
+
DashfyClass --> HTTPServer
|
|
632
|
+
DashfyClass --> WSServer
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
subgraph APIs["๐ Registered APIs"]
|
|
636
|
+
direction LR
|
|
637
|
+
API1[GitHub API]
|
|
638
|
+
API2[JSON API]
|
|
639
|
+
API3[Custom APIs]
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
subgraph Clients["๐ Connected Clients"]
|
|
643
|
+
direction LR
|
|
644
|
+
Client1[Client 1<br/><i>Browser/App</i>]
|
|
645
|
+
Client2[Client 2<br/><i>Browser/App</i>]
|
|
646
|
+
Client3[Client N<br/><i>Browser/App</i>]
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
subgraph DataFlow["Data Flow"]
|
|
650
|
+
direction TB
|
|
651
|
+
Sub[๐ฅ Subscription Request<br/><i>api.subscription</i>]
|
|
652
|
+
Poll[โฑ๏ธ Poll/Push Data<br/><i>15s interval or real-time</i>]
|
|
653
|
+
Cache[๐พ Cache Response]
|
|
654
|
+
Broadcast[๐ค Broadcast to Clients<br/><i>api.data event</i>]
|
|
655
|
+
Sub --> Poll --> Cache --> Broadcast
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
ConfigFile -->|Load & Watch| ConfigLoader
|
|
659
|
+
ConfigLoader -->|Validate & Apply| MessageBus
|
|
660
|
+
|
|
661
|
+
APIs -->|Register| MessageBus
|
|
662
|
+
MessageBus <-->|Manage Subscriptions| WSServer
|
|
663
|
+
|
|
664
|
+
HTTPServer -->|REST Endpoints| Clients
|
|
665
|
+
WSServer <-->|WebSocket Events| Clients
|
|
666
|
+
|
|
667
|
+
MessageBus -.->|Poll/Push| APIs
|
|
668
|
+
APIs -.->|Data| MessageBus
|
|
669
|
+
|
|
670
|
+
style DashfyServer fill:#9b59b6,stroke:#7d3c98,color:#fff
|
|
671
|
+
style APIs fill:#f39c12,stroke:#d68910,color:#fff
|
|
672
|
+
style Clients fill:#27ae60,stroke:#1e8449,color:#fff
|
|
673
|
+
style DataFlow fill:#3498db,stroke:#2874a6,color:#fff
|
|
674
|
+
style ConfigFile fill:#e74c3c,stroke:#c0392b,color:#fff
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Components Overview
|
|
678
|
+
|
|
679
|
+
#### 1. **Dashfy Class**
|
|
680
|
+
|
|
681
|
+
Main orchestrator that manages HTTP server, WebSocket server, and message bus.
|
|
682
|
+
|
|
683
|
+
#### 2. **Message Bus**
|
|
684
|
+
|
|
685
|
+
Central pub/sub system that:
|
|
686
|
+
|
|
687
|
+
- Manages API registrations
|
|
688
|
+
- Tracks client connections
|
|
689
|
+
- Handles subscriptions
|
|
690
|
+
- Coordinates data streaming
|
|
691
|
+
- Caches responses
|
|
692
|
+
|
|
693
|
+
#### 3. **HTTP Server (Fastify)**
|
|
694
|
+
|
|
695
|
+
Provides REST endpoints and static file serving with CORS support.
|
|
696
|
+
|
|
697
|
+
#### 4. **WebSocket Server (Socket.IO)**
|
|
698
|
+
|
|
699
|
+
Real-time bidirectional communication with clients.
|
|
700
|
+
|
|
701
|
+
#### 5. **Configuration Loader**
|
|
702
|
+
|
|
703
|
+
Parses and validates `TypeScript` object / `JSON` / `YAML` configuration with Zod schema validation.
|
|
704
|
+
|
|
705
|
+
## Performance
|
|
706
|
+
|
|
707
|
+
The server is optimized for efficiency:
|
|
708
|
+
|
|
709
|
+
- **Shared subscriptions**: Multiple clients subscribing to the same API method share a single data stream
|
|
710
|
+
- **Response caching**: New subscribers receive cached data immediately
|
|
711
|
+
- **Automatic cleanup**: Unused subscriptions are removed to free resources
|
|
712
|
+
- **Configurable polling**: Adjust poll intervals per dashboard or globally
|
|
713
|
+
- **Connection pooling**: Efficient HTTP client (Undici) for API requests
|
|
714
|
+
|
|
715
|
+
## Error Handling
|
|
716
|
+
|
|
717
|
+
The server provides robust error handling:
|
|
718
|
+
|
|
719
|
+
- API failures don't crash the server
|
|
720
|
+
- Errors are logged with context
|
|
721
|
+
- Clients receive error messages via `api.error` events
|
|
722
|
+
- Type-safe error extraction with `getErrorMessage()`
|
|
723
|
+
|
|
724
|
+
## TypeScript Support
|
|
725
|
+
|
|
726
|
+
Fully typed with TypeScript:
|
|
727
|
+
|
|
728
|
+
```ts
|
|
729
|
+
import type { DashfyConfig, APIRegistration, PollMode } from '@getdashfy/types'
|
|
730
|
+
|
|
731
|
+
const myApi: APIRegistration = ({ request }) => ({
|
|
732
|
+
async fetchData(params: { id: string }) {
|
|
733
|
+
return request({ url: `https://api.example.com/${params.id}` })
|
|
734
|
+
},
|
|
735
|
+
})
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
## Development
|
|
739
|
+
|
|
740
|
+
```bash
|
|
741
|
+
# Install dependencies
|
|
742
|
+
pnpm install
|
|
743
|
+
|
|
744
|
+
# Build
|
|
745
|
+
pnpm build
|
|
746
|
+
|
|
747
|
+
# Watch mode
|
|
748
|
+
pnpm dev
|
|
749
|
+
|
|
750
|
+
# Run tests
|
|
751
|
+
pnpm test
|
|
752
|
+
|
|
753
|
+
# Run tests with coverage
|
|
754
|
+
pnpm test:coverage
|
|
755
|
+
|
|
756
|
+
# Type check
|
|
757
|
+
pnpm typecheck
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
## Community
|
|
761
|
+
|
|
762
|
+
Join the community on [Dashfy's Discord server](https://dashfy.dev/discord) to discuss the project, ask questions, or get help.
|
|
763
|
+
|
|
764
|
+
Join the conversation on X (Twitter) and follow [@dashfydev](https://x.com/dashfydev) for updates and announcements.
|
|
765
|
+
|
|
766
|
+
## License
|
|
767
|
+
|
|
768
|
+
This project is licensed under the AGPL-3.0 License - see the [LICENSE](./LICENSE) file for details.
|