@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/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.