@rawdash/server 0.13.0 → 0.14.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 +87 -71
- package/dist/index.d.ts +93 -14
- package/dist/index.js +215 -235
- package/dist/index.js.map +1 -1
- package/package.json +3 -7
package/README.md
CHANGED
|
@@ -3,11 +3,28 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@rawdash/server)
|
|
4
4
|
[](https://github.com/rawdash/rawdash/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Framework-agnostic rawdash request handlers, sync engine, and wire-contract types. **No HTTP framework dependency** — wrap with [`@rawdash/hono`](https://www.npmjs.com/package/@rawdash/hono) (or another adapter) to serve over HTTP.
|
|
7
7
|
|
|
8
8
|
## What it is
|
|
9
9
|
|
|
10
|
-
`@rawdash/server`
|
|
10
|
+
`@rawdash/server` is the engine half of rawdash:
|
|
11
|
+
|
|
12
|
+
- The **sync engine** (`runSync`, `createEngine`) that drives connectors and writes to storage.
|
|
13
|
+
- **Pure HTTP handlers** (`listWidgets`, `getWidget`, `triggerSync`, `getSyncStateHandler`, `getHealth`, `runRetentionOnce`) — async functions you can call from any framework.
|
|
14
|
+
- **`EngineContext`** — the per-request interface adapters use to inject `DashboardConfig` and `ServerStorage`. The handler doesn't care whether those come from a static config or are looked up fresh per request — that decision belongs to the adapter.
|
|
15
|
+
- **`ROUTES`** — canonical URL paths, the single source of truth for the wire contract.
|
|
16
|
+
- **`RawdashError`** — structured errors with `status` and `code` for adapters to translate.
|
|
17
|
+
- The **`SyncState` types** and `InMemoryStorage` (re-exported from `@rawdash/core`).
|
|
18
|
+
|
|
19
|
+
This package does **not** know about Hono, Express, Node's `http`, or any HTTP framework. Pick an adapter.
|
|
20
|
+
|
|
21
|
+
## When to use what
|
|
22
|
+
|
|
23
|
+
| You want to… | Use |
|
|
24
|
+
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
|
|
25
|
+
| Serve rawdash over HTTP in a Hono / Workers / Bun / Deno app | [`@rawdash/hono`](https://www.npmjs.com/package/@rawdash/hono) (depends on `@rawdash/server`) |
|
|
26
|
+
| Build a different framework adapter (Express, NestJS, etc.) | This package directly — wrap the pure handlers |
|
|
27
|
+
| Use the engine without HTTP (background job, CLI, MCP) | This package — `createEngine` / `runSync` |
|
|
11
28
|
|
|
12
29
|
## Install
|
|
13
30
|
|
|
@@ -15,99 +32,98 @@ Standalone Hono HTTP server hosting the rawdash data API.
|
|
|
15
32
|
npm install @rawdash/server
|
|
16
33
|
```
|
|
17
34
|
|
|
18
|
-
##
|
|
35
|
+
## The contract for adapter authors
|
|
19
36
|
|
|
20
|
-
|
|
21
|
-
import { GitHubConnector } from '@rawdash/connector-github';
|
|
22
|
-
import { defineConfig, defineDashboard, secret } from '@rawdash/core';
|
|
23
|
-
import { serve } from '@rawdash/server';
|
|
24
|
-
|
|
25
|
-
const github = new GitHubConnector(
|
|
26
|
-
{
|
|
27
|
-
owner: 'my-org',
|
|
28
|
-
repo: 'my-repo',
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
token: secret('GITHUB_TOKEN'),
|
|
32
|
-
},
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
serve(
|
|
36
|
-
defineConfig({
|
|
37
|
-
connectors: [{ connector: github }],
|
|
38
|
-
dashboards: {
|
|
39
|
-
engineering: defineDashboard({ widgets: {} }),
|
|
40
|
-
},
|
|
41
|
-
}),
|
|
42
|
-
{ port: 8080 },
|
|
43
|
-
);
|
|
44
|
-
```
|
|
37
|
+
Each pure handler takes an `EngineContext` (and any path parameters) and returns the response body or throws a `RawdashError`. Your adapter:
|
|
45
38
|
|
|
46
|
-
|
|
39
|
+
1. Routes the HTTP request to the matching handler.
|
|
40
|
+
2. Constructs an `EngineContext` from the request — `getConfig` and `getStorage` can return constants or values derived from the request (e.g. read from a database keyed by a path param or auth header).
|
|
41
|
+
3. Awaits the handler and serializes the result as JSON.
|
|
42
|
+
4. Catches `RawdashError` and maps `status` + `code` to a structured HTTP response.
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
```ts
|
|
45
|
+
import { RawdashError, isRawdashError, listWidgets } from '@rawdash/server';
|
|
46
|
+
|
|
47
|
+
// example: a hypothetical Express adapter
|
|
48
|
+
app.get('/dashboards/:id/widgets', async (req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const body = await listWidgets(
|
|
51
|
+
{
|
|
52
|
+
getConfig: () => loadConfig(),
|
|
53
|
+
getStorage: () => loadStorage(),
|
|
54
|
+
},
|
|
55
|
+
req.params.id,
|
|
56
|
+
);
|
|
57
|
+
res.json(body);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (isRawdashError(err)) {
|
|
60
|
+
res.status(err.status).json({ error: err.message, code: err.code });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
## The wire contract
|
|
51
69
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
70
|
+
| Route | Method | Handler | Response |
|
|
71
|
+
| -------------------------------------------- | ------ | --------------------- | --------------------------------------------- |
|
|
72
|
+
| `/health` | GET | `getHealth` | `{status:'ok'}` (liveness, no storage access) |
|
|
73
|
+
| `/sync/state` | GET | `getSyncStateHandler` | `SyncState` |
|
|
74
|
+
| `/sync` | POST | `triggerSync` | `{queued: boolean}` — returns immediately |
|
|
75
|
+
| `/dashboards/:dashboardId/widgets` | GET | `listWidgets` | `WidgetsListResponse` |
|
|
76
|
+
| `/dashboards/:dashboardId/widgets/:widgetId` | GET | `getWidget` | `CachedWidget` |
|
|
77
|
+
| `/retention/retain` | POST | `runRetentionOnce` | `{triggered: true}` (synchronous) |
|
|
56
78
|
|
|
57
|
-
|
|
79
|
+
Paths are exported as constants from `ROUTES`. Use them in adapters (and in clients) instead of hard-coding.
|
|
58
80
|
|
|
59
|
-
|
|
81
|
+
### `SyncState`
|
|
60
82
|
|
|
61
83
|
```ts
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
type SyncStatus = 'idle' | 'queued' | 'running' | 'succeeded' | 'failed';
|
|
85
|
+
interface SyncState {
|
|
86
|
+
status: SyncStatus;
|
|
87
|
+
queuedAt: string | null;
|
|
88
|
+
startedAt: string | null;
|
|
89
|
+
lastSyncAt: string | null;
|
|
90
|
+
lastError: string | null;
|
|
91
|
+
}
|
|
66
92
|
```
|
|
67
93
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
### `GET /dashboards/:dashboardId/widgets`
|
|
71
|
-
|
|
72
|
-
Returns all cached widget entries for a dashboard.
|
|
94
|
+
Transitions:
|
|
73
95
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"id": "github:pull_requests",
|
|
78
|
-
"connectorId": "github",
|
|
79
|
-
"widgetId": "pull_requests",
|
|
80
|
-
"data": [],
|
|
81
|
-
"cachedAt": "2026-04-11T10:00:00.000Z"
|
|
82
|
-
}
|
|
83
|
-
]
|
|
84
|
-
```
|
|
96
|
+
- `idle → queued → running → succeeded` (happy path; cloud may use `queued`, OSS skips it)
|
|
97
|
+
- `running → failed` (sets `lastError`)
|
|
98
|
+
- Any terminal state can transition back to `queued` / `running` on the next trigger.
|
|
85
99
|
|
|
86
|
-
|
|
100
|
+
Clients (`@rawdash/client`) poll `/sync/state` and wait for `!isSyncActive(status)` to settle.
|
|
87
101
|
|
|
88
|
-
|
|
102
|
+
## Engine without HTTP
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
```ts
|
|
105
|
+
import { createEngine } from '@rawdash/server';
|
|
91
106
|
|
|
92
|
-
|
|
107
|
+
const engine = createEngine(config, { storage });
|
|
108
|
+
const widgets = await engine.getWidgets('engineering');
|
|
109
|
+
const state = await engine.getSyncState();
|
|
110
|
+
```
|
|
93
111
|
|
|
94
|
-
|
|
112
|
+
`createEngine` exposes the same shape as the handlers but bypasses HTTP entirely — useful for jobs, CLI tools, or the MCP server.
|
|
95
113
|
|
|
96
|
-
|
|
114
|
+
## Storage
|
|
97
115
|
|
|
98
|
-
|
|
99
|
-
{
|
|
100
|
-
"status": "idle",
|
|
101
|
-
"lastSyncAt": "2026-04-11T10:00:00.000Z",
|
|
102
|
-
"lastError": null
|
|
103
|
-
}
|
|
104
|
-
```
|
|
116
|
+
Provide any `ServerStorage` implementation:
|
|
105
117
|
|
|
106
|
-
`
|
|
118
|
+
- `InMemoryStorage` (re-exported here) — dev/test.
|
|
119
|
+
- [`@rawdash/adapter-libsql`](https://www.npmjs.com/package/@rawdash/adapter-libsql) — durable libSQL/Turso/SQLite backend.
|
|
120
|
+
- Roll your own by implementing the [`ServerStorage`](https://github.com/rawdash/rawdash/blob/main/packages/core/src/server-storage.ts) interface.
|
|
107
121
|
|
|
108
122
|
## Links
|
|
109
123
|
|
|
110
124
|
- [rawdash docs](https://rawdash.dev)
|
|
125
|
+
- [`@rawdash/hono`](https://www.npmjs.com/package/@rawdash/hono) — Hono adapter
|
|
126
|
+
- [`@rawdash/client`](https://www.npmjs.com/package/@rawdash/client) — typed HTTP client
|
|
111
127
|
- [GitHub](https://github.com/rawdash/rawdash)
|
|
112
128
|
- [Issues](https://github.com/rawdash/rawdash/issues)
|
|
113
129
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,99 @@
|
|
|
1
|
-
import { ServerStorage,
|
|
2
|
-
export { DashboardConfig, InMemoryStorage, ServerStorage, computeMetric } from '@rawdash/core';
|
|
3
|
-
import { Hono } from 'hono';
|
|
1
|
+
import { DashboardConfig, ServerStorage, HealthResponse, SyncState, CachedWidget, WidgetsListResponse, TriggerSyncResponse, RetentionConfig } from '@rawdash/core';
|
|
2
|
+
export { ACTIVE_SYNC_STATUSES, CachedWidget, ConfiguredConnector, DashboardConfig, HealthResponse, InMemoryStorage, ServerStorage, SyncState, SyncStatus, TriggerSyncResponse, WidgetsListResponse, computeMetric, isSyncActive } from '@rawdash/core';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Per-request lookup functions an HTTP adapter passes to engine handlers.
|
|
6
|
+
*
|
|
7
|
+
* Adapters can close over per-request data (e.g. an id extracted from a
|
|
8
|
+
* path parameter or auth header) when constructing `getConfig` /
|
|
9
|
+
* `getStorage`. The handlers themselves only know how to operate on a
|
|
10
|
+
* given `DashboardConfig` and `ServerStorage` — they don't know or care
|
|
11
|
+
* how those are obtained.
|
|
12
|
+
*/
|
|
13
|
+
interface EngineContext {
|
|
14
|
+
getConfig: () => DashboardConfig | Promise<DashboardConfig>;
|
|
15
|
+
getStorage: () => ServerStorage | Promise<ServerStorage>;
|
|
8
16
|
}
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Thrown by `@rawdash/server` handlers when a request fails in a way the
|
|
20
|
+
* HTTP adapter should translate to a structured response (e.g. 404, 400).
|
|
21
|
+
* Framework adapters (`@rawdash/hono`, etc.) should catch this and map
|
|
22
|
+
* `status` to the appropriate HTTP status code.
|
|
23
|
+
*/
|
|
24
|
+
declare class RawdashError extends Error {
|
|
25
|
+
readonly status: number;
|
|
26
|
+
readonly code: string;
|
|
27
|
+
constructor(status: number, code: string, message: string);
|
|
12
28
|
}
|
|
29
|
+
declare function isRawdashError(err: unknown): err is RawdashError;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Canonical URL path conventions for the rawdash HTTP wire contract.
|
|
33
|
+
*
|
|
34
|
+
* Framework adapters (`@rawdash/hono`, etc.) and clients
|
|
35
|
+
* (`@rawdash/client`) should use these constants instead of hard-coding
|
|
36
|
+
* paths, so the contract stays in one place.
|
|
37
|
+
*/
|
|
38
|
+
declare const ROUTES: {
|
|
39
|
+
readonly health: "/health";
|
|
40
|
+
readonly syncState: "/sync/state";
|
|
41
|
+
readonly sync: "/sync";
|
|
42
|
+
readonly retention: "/retention/retain";
|
|
43
|
+
readonly widgets: {
|
|
44
|
+
readonly list: (dashboardId: string) => string;
|
|
45
|
+
readonly single: (dashboardId: string, widgetId: string) => string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
13
48
|
|
|
14
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Framework-agnostic request handlers for the rawdash wire contract.
|
|
51
|
+
*
|
|
52
|
+
* Each function takes an `EngineContext` (providing per-request access to
|
|
53
|
+
* the config + storage) and returns the response body, or throws a
|
|
54
|
+
* `RawdashError` on a client-visible failure. HTTP adapters
|
|
55
|
+
* (`@rawdash/hono`, etc.) wrap these in their framework's request/response
|
|
56
|
+
* cycle and translate `RawdashError` into a structured error response.
|
|
57
|
+
*/
|
|
58
|
+
declare function getHealth(): HealthResponse;
|
|
59
|
+
declare function getSyncStateHandler(ctx: EngineContext): Promise<SyncState>;
|
|
60
|
+
declare function triggerSync(ctx: EngineContext): Promise<TriggerSyncResponse>;
|
|
61
|
+
declare function listWidgets(ctx: EngineContext, dashboardId: string): Promise<WidgetsListResponse>;
|
|
62
|
+
declare function getWidget(ctx: EngineContext, dashboardId: string, widgetId: string): Promise<CachedWidget>;
|
|
63
|
+
declare function runRetentionOnce(ctx: EngineContext): Promise<void>;
|
|
15
64
|
|
|
16
|
-
declare
|
|
65
|
+
declare const FULL_SYNC_TIMEOUT_MS = 300000;
|
|
66
|
+
/**
|
|
67
|
+
* Run a full sync across all connectors in the config in parallel via
|
|
68
|
+
* `Promise.allSettled`, so one failure doesn't abort the others. Each
|
|
69
|
+
* connector run is wrapped in a hard timeout (`FULL_SYNC_TIMEOUT_MS`)
|
|
70
|
+
* raced against the connector's own `Promise`, so a connector that
|
|
71
|
+
* ignores `AbortSignal` (or a storage call that hangs) can still not
|
|
72
|
+
* pin sync state in `running` indefinitely.
|
|
73
|
+
*
|
|
74
|
+
* The per-run storage handle is bound to the same `AbortController`, so
|
|
75
|
+
* once the timeout fires every subsequent write call on that handle
|
|
76
|
+
* becomes a no-op. That makes tail writes from a timed-out connector
|
|
77
|
+
* invisible to the next sync even if the connector itself keeps running
|
|
78
|
+
* — see `withAbortSignal` in `@rawdash/core` and the safety-net note in
|
|
79
|
+
* `docs/authoring-a-connector.md`.
|
|
80
|
+
*
|
|
81
|
+
* Transitions storage through `queued` → `running` → `succeeded`/`failed`.
|
|
82
|
+
* The `queued` step is a no-op if the caller (typically `triggerSync`)
|
|
83
|
+
* already marked the run as queued.
|
|
84
|
+
*
|
|
85
|
+
* Returns silently if another sync acquired the `running` lock first.
|
|
86
|
+
*/
|
|
87
|
+
declare function runSync(config: DashboardConfig, storage: ServerStorage): Promise<void>;
|
|
88
|
+
|
|
89
|
+
declare const DEFAULT_RETENTION_INTERVAL_MS: number;
|
|
90
|
+
declare function hasPruningPolicy(config: RetentionConfig): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Apply the retention policy in `config` to every connector's stored data.
|
|
93
|
+
* No-op if the config has no pruning policy. Throws an aggregated error if
|
|
94
|
+
* any connector fails.
|
|
95
|
+
*/
|
|
96
|
+
declare function runRetention(config: DashboardConfig, storage: ServerStorage): Promise<void>;
|
|
17
97
|
|
|
18
98
|
interface EngineOptions {
|
|
19
99
|
storage?: ServerStorage;
|
|
@@ -21,11 +101,10 @@ interface EngineOptions {
|
|
|
21
101
|
interface Engine {
|
|
22
102
|
getWidget(dashboardId: string, widgetId: string): Promise<CachedWidget | undefined>;
|
|
23
103
|
getWidgets(dashboardId: string): Promise<CachedWidget[]>;
|
|
24
|
-
getHealth(): Promise<
|
|
104
|
+
getHealth(): Promise<HealthResponse>;
|
|
105
|
+
getSyncState(): Promise<SyncState>;
|
|
25
106
|
triggerSync(): Promise<TriggerSyncResponse>;
|
|
26
107
|
}
|
|
27
108
|
declare function createEngine(config: DashboardConfig, options?: EngineOptions): Engine;
|
|
28
109
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export { type Engine, type EngineOptions, type RouterMount, type ServeOptions, createEngine, createEngineRouters, createServer, serve };
|
|
110
|
+
export { DEFAULT_RETENTION_INTERVAL_MS, type Engine, type EngineContext, type EngineOptions, FULL_SYNC_TIMEOUT_MS, ROUTES, RawdashError, createEngine, getHealth, getSyncStateHandler, getWidget, hasPruningPolicy, isRawdashError, listWidgets, runRetention, runRetentionOnce, runSync, triggerSync };
|
package/dist/index.js
CHANGED
|
@@ -1,116 +1,85 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
this.
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var RawdashError = class extends Error {
|
|
3
|
+
constructor(status, code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = "RawdashError";
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
};
|
|
12
|
+
function isRawdashError(err) {
|
|
13
|
+
return err instanceof RawdashError;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/routes.ts
|
|
17
|
+
var ROUTES = {
|
|
18
|
+
health: "/health",
|
|
19
|
+
syncState: "/sync/state",
|
|
20
|
+
sync: "/sync",
|
|
21
|
+
retention: "/retention/retain",
|
|
22
|
+
widgets: {
|
|
23
|
+
list: (dashboardId) => `/dashboards/${encodeURIComponent(dashboardId)}/widgets`,
|
|
24
|
+
single: (dashboardId, widgetId) => `/dashboards/${encodeURIComponent(dashboardId)}/widgets/${encodeURIComponent(widgetId)}`
|
|
14
25
|
}
|
|
15
26
|
};
|
|
16
27
|
|
|
17
|
-
// src/
|
|
28
|
+
// src/handlers.ts
|
|
29
|
+
import { isSyncActive, resolveWidget } from "@rawdash/core";
|
|
30
|
+
|
|
31
|
+
// src/retention.ts
|
|
18
32
|
import { selectForDeletion } from "@rawdash/core";
|
|
19
|
-
var
|
|
33
|
+
var DEFAULT_RETENTION_INTERVAL_MS = 60 * 60 * 1e3;
|
|
20
34
|
function hasPruningPolicy(config) {
|
|
21
35
|
return config.maxAge !== void 0 || config.maxSize !== void 0;
|
|
22
36
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
config;
|
|
29
|
-
storage;
|
|
30
|
-
interval = null;
|
|
31
|
-
inFlight = null;
|
|
32
|
-
async runRetention() {
|
|
33
|
-
if (this.inFlight) {
|
|
34
|
-
return this.inFlight;
|
|
35
|
-
}
|
|
36
|
-
this.inFlight = this.runRetentionOnce().finally(() => {
|
|
37
|
-
this.inFlight = null;
|
|
38
|
-
});
|
|
39
|
-
return this.inFlight;
|
|
37
|
+
async function runRetention(config, storage) {
|
|
38
|
+
const retentionConfig = config.retention;
|
|
39
|
+
if (!retentionConfig || !hasPruningPolicy(retentionConfig)) {
|
|
40
|
+
return;
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
events,
|
|
57
|
-
(e) => e.start_ts,
|
|
58
|
-
retentionConfig,
|
|
59
|
-
nowMs,
|
|
60
|
-
(survivors, names) => handle.events(survivors, { names })
|
|
61
|
-
);
|
|
62
|
-
await applyRetentionToShape(
|
|
63
|
-
metrics,
|
|
64
|
-
(m) => m.ts,
|
|
65
|
-
retentionConfig,
|
|
66
|
-
nowMs,
|
|
67
|
-
(survivors, names) => handle.metrics(survivors, { names })
|
|
68
|
-
);
|
|
69
|
-
await applyRetentionToShape(
|
|
70
|
-
distributions,
|
|
71
|
-
(d) => d.ts,
|
|
72
|
-
retentionConfig,
|
|
73
|
-
nowMs,
|
|
74
|
-
(survivors, names) => handle.distributions(survivors, { names })
|
|
75
|
-
);
|
|
76
|
-
})
|
|
77
|
-
);
|
|
78
|
-
const failures = results.filter(
|
|
79
|
-
(r) => r.status === "rejected"
|
|
80
|
-
);
|
|
81
|
-
if (failures.length > 0) {
|
|
82
|
-
throw new Error(
|
|
83
|
-
`Retention failed for ${failures.length} connector(s): ${failures.map((f) => String(f.reason)).join("; ")}`
|
|
42
|
+
const nowMs = Date.now();
|
|
43
|
+
const results = await Promise.allSettled(
|
|
44
|
+
config.connectors.map(async ({ connector }) => {
|
|
45
|
+
const handle = storage.getStorageHandle(connector.id);
|
|
46
|
+
const [events, metrics, distributions] = await Promise.all([
|
|
47
|
+
handle.queryEvents({}),
|
|
48
|
+
handle.queryMetrics({}),
|
|
49
|
+
handle.queryDistributions({})
|
|
50
|
+
]);
|
|
51
|
+
await applyRetentionToShape(
|
|
52
|
+
events,
|
|
53
|
+
(e) => e.start_ts,
|
|
54
|
+
retentionConfig,
|
|
55
|
+
nowMs,
|
|
56
|
+
(survivors, names) => handle.events(survivors, { names })
|
|
84
57
|
);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (this.interval !== null) {
|
|
109
|
-
clearInterval(this.interval);
|
|
110
|
-
this.interval = null;
|
|
111
|
-
}
|
|
58
|
+
await applyRetentionToShape(
|
|
59
|
+
metrics,
|
|
60
|
+
(m) => m.ts,
|
|
61
|
+
retentionConfig,
|
|
62
|
+
nowMs,
|
|
63
|
+
(survivors, names) => handle.metrics(survivors, { names })
|
|
64
|
+
);
|
|
65
|
+
await applyRetentionToShape(
|
|
66
|
+
distributions,
|
|
67
|
+
(d) => d.ts,
|
|
68
|
+
retentionConfig,
|
|
69
|
+
nowMs,
|
|
70
|
+
(survivors, names) => handle.distributions(survivors, { names })
|
|
71
|
+
);
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
const failures = results.filter(
|
|
75
|
+
(r) => r.status === "rejected"
|
|
76
|
+
);
|
|
77
|
+
if (failures.length > 0) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Retention failed for ${failures.length} connector(s): ${failures.map((f) => String(f.reason)).join("; ")}`
|
|
80
|
+
);
|
|
112
81
|
}
|
|
113
|
-
}
|
|
82
|
+
}
|
|
114
83
|
async function applyRetentionToShape(rows, getTs, config, nowMs, writeSurvivors) {
|
|
115
84
|
if (rows.length === 0) {
|
|
116
85
|
return;
|
|
@@ -125,146 +94,138 @@ async function applyRetentionToShape(rows, getTs, config, nowMs, writeSurvivors)
|
|
|
125
94
|
await writeSurvivors(survivors, allNames);
|
|
126
95
|
}
|
|
127
96
|
|
|
128
|
-
// src/
|
|
97
|
+
// src/sync.ts
|
|
129
98
|
var FULL_SYNC_TIMEOUT_MS = 3e5;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
99
|
+
async function runSync(config, storage) {
|
|
100
|
+
await storage.markSyncQueued();
|
|
101
|
+
const acquired = await storage.markSyncRunning();
|
|
102
|
+
if (!acquired) {
|
|
103
|
+
return;
|
|
134
104
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
() => controller.abort(),
|
|
149
|
-
FULL_SYNC_TIMEOUT_MS
|
|
105
|
+
const errors = [];
|
|
106
|
+
await Promise.allSettled(
|
|
107
|
+
config.connectors.map(async ({ connector }) => {
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
const handle = storage.getStorageHandle(connector.id, {
|
|
110
|
+
signal: controller.signal
|
|
111
|
+
});
|
|
112
|
+
let timer;
|
|
113
|
+
try {
|
|
114
|
+
const syncPromise = connector.sync(
|
|
115
|
+
{ mode: "full" },
|
|
116
|
+
handle,
|
|
117
|
+
controller.signal
|
|
150
118
|
);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
controller.signal
|
|
156
|
-
);
|
|
157
|
-
if (!result.done) {
|
|
158
|
-
errors.push(
|
|
159
|
-
`${connector.id} did not complete in one chunk (chunked syncs are only supported in cloud)`
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
} catch (err) {
|
|
163
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
164
|
-
errors.push(
|
|
119
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
120
|
+
timer = setTimeout(() => {
|
|
121
|
+
controller.abort();
|
|
122
|
+
const err = new Error(
|
|
165
123
|
`${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`
|
|
166
124
|
);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
}
|
|
125
|
+
err.name = "AbortError";
|
|
126
|
+
reject(err);
|
|
127
|
+
}, FULL_SYNC_TIMEOUT_MS);
|
|
128
|
+
});
|
|
129
|
+
const result = await Promise.race([syncPromise, timeoutPromise]);
|
|
130
|
+
if (!result.done) {
|
|
131
|
+
errors.push(
|
|
132
|
+
`${connector.id} did not complete in one chunk (chunked syncs are only supported in cloud)`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
137
|
+
errors.push(
|
|
138
|
+
`${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
142
|
+
}
|
|
143
|
+
} finally {
|
|
144
|
+
if (timer !== void 0) {
|
|
171
145
|
clearTimeout(timer);
|
|
172
146
|
}
|
|
173
|
-
})
|
|
174
|
-
);
|
|
175
|
-
if (errors.length > 0) {
|
|
176
|
-
await this.storage.setSyncError(errors.join("; "));
|
|
177
|
-
} else {
|
|
178
|
-
await this.storage.setSyncSuccess();
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
mount(app) {
|
|
182
|
-
app.post("/sync", async (c) => {
|
|
183
|
-
const state = await this.storage.getSyncState();
|
|
184
|
-
if (state.status === "syncing") {
|
|
185
|
-
return c.json({ triggered: false });
|
|
186
147
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
if (errors.length > 0) {
|
|
151
|
+
await storage.markSyncFailed(errors.join("; "));
|
|
152
|
+
} else {
|
|
153
|
+
await storage.markSyncSucceeded();
|
|
190
154
|
}
|
|
191
|
-
}
|
|
155
|
+
}
|
|
192
156
|
|
|
193
|
-
// src/
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
157
|
+
// src/handlers.ts
|
|
158
|
+
function getHealth() {
|
|
159
|
+
return { status: "ok" };
|
|
160
|
+
}
|
|
161
|
+
async function getSyncStateHandler(ctx) {
|
|
162
|
+
const storage = await ctx.getStorage();
|
|
163
|
+
return storage.getSyncState();
|
|
164
|
+
}
|
|
165
|
+
async function triggerSync(ctx) {
|
|
166
|
+
const storage = await ctx.getStorage();
|
|
167
|
+
const state = await storage.getSyncState();
|
|
168
|
+
if (isSyncActive(state.status)) {
|
|
169
|
+
return { queued: false };
|
|
201
170
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
resolve(id, widget) {
|
|
207
|
-
return resolveWidget(id, widget, this.connectors, this.storage);
|
|
171
|
+
const config = await ctx.getConfig();
|
|
172
|
+
const queued = await storage.markSyncQueued();
|
|
173
|
+
if (!queued) {
|
|
174
|
+
return { queued: false };
|
|
208
175
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const response = { widgets };
|
|
220
|
-
return c.json(response);
|
|
221
|
-
});
|
|
222
|
-
app.get(`${base}/:widgetId`, async (c) => {
|
|
223
|
-
const widgetId = c.req.param("widgetId");
|
|
224
|
-
const widget = this.dashboard.widgets[widgetId];
|
|
225
|
-
if (!widget) {
|
|
226
|
-
return c.json({ error: "Widget not found" }, 404);
|
|
227
|
-
}
|
|
228
|
-
const result = await this.resolve(widgetId, widget);
|
|
229
|
-
if (!result) {
|
|
230
|
-
return c.json({ error: "Widget not found" }, 404);
|
|
231
|
-
}
|
|
232
|
-
return c.json(result);
|
|
233
|
-
});
|
|
176
|
+
void runSync(config, storage).catch((err) => {
|
|
177
|
+
console.error("Rawdash sync failed", err);
|
|
178
|
+
});
|
|
179
|
+
return { queued: true };
|
|
180
|
+
}
|
|
181
|
+
async function listWidgets(ctx, dashboardId) {
|
|
182
|
+
const config = await ctx.getConfig();
|
|
183
|
+
const dashboard = config.dashboards[dashboardId];
|
|
184
|
+
if (!dashboard) {
|
|
185
|
+
throw new RawdashError(404, "DASHBOARD_NOT_FOUND", "Dashboard not found");
|
|
234
186
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
function createEngineRouters(config, storage = new InMemoryStorage()) {
|
|
242
|
-
const widgetRouters = Object.entries(config.dashboards).map(
|
|
243
|
-
([dashboardId, dashboard]) => new WidgetsRouter(dashboardId, dashboard, config.connectors, storage)
|
|
187
|
+
const storage = await ctx.getStorage();
|
|
188
|
+
const entries = Object.entries(dashboard.widgets);
|
|
189
|
+
const resolved = await Promise.all(
|
|
190
|
+
entries.map(
|
|
191
|
+
([key, widget]) => resolveWidget(key, widget, config.connectors, storage)
|
|
192
|
+
)
|
|
244
193
|
);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
new SyncRouter(config, storage),
|
|
248
|
-
new RetentionRouter(config, storage),
|
|
249
|
-
new HealthRouter(storage)
|
|
250
|
-
];
|
|
194
|
+
const widgets = resolved.filter((w) => w !== void 0);
|
|
195
|
+
return { widgets };
|
|
251
196
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
for (const router of routers) {
|
|
258
|
-
router.mount(app);
|
|
197
|
+
async function getWidget(ctx, dashboardId, widgetId) {
|
|
198
|
+
const config = await ctx.getConfig();
|
|
199
|
+
const dashboard = config.dashboards[dashboardId];
|
|
200
|
+
if (!dashboard) {
|
|
201
|
+
throw new RawdashError(404, "DASHBOARD_NOT_FOUND", "Dashboard not found");
|
|
259
202
|
}
|
|
260
|
-
|
|
203
|
+
const widget = dashboard.widgets[widgetId];
|
|
204
|
+
if (!widget) {
|
|
205
|
+
throw new RawdashError(404, "WIDGET_NOT_FOUND", "Widget not found");
|
|
206
|
+
}
|
|
207
|
+
const storage = await ctx.getStorage();
|
|
208
|
+
const result = await resolveWidget(
|
|
209
|
+
widgetId,
|
|
210
|
+
widget,
|
|
211
|
+
config.connectors,
|
|
212
|
+
storage
|
|
213
|
+
);
|
|
214
|
+
if (!result) {
|
|
215
|
+
throw new RawdashError(404, "WIDGET_NOT_FOUND", "Widget not found");
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
async function runRetentionOnce(ctx) {
|
|
220
|
+
const config = await ctx.getConfig();
|
|
221
|
+
const storage = await ctx.getStorage();
|
|
222
|
+
await runRetention(config, storage);
|
|
261
223
|
}
|
|
262
224
|
|
|
263
225
|
// src/engine.ts
|
|
264
|
-
import { InMemoryStorage as
|
|
226
|
+
import { InMemoryStorage, isSyncActive as isSyncActive2, resolveWidget as resolveWidget2 } from "@rawdash/core";
|
|
265
227
|
function createEngine(config, options = {}) {
|
|
266
|
-
const storage = options.storage ?? new
|
|
267
|
-
const syncRouter = new SyncRouter(config, storage);
|
|
228
|
+
const storage = options.storage ?? new InMemoryStorage();
|
|
268
229
|
return {
|
|
269
230
|
async getWidget(dashboardId, widgetId) {
|
|
270
231
|
const dashboard = config.dashboards[dashboardId];
|
|
@@ -291,17 +252,24 @@ function createEngine(config, options = {}) {
|
|
|
291
252
|
return resolved.filter((w) => w !== void 0);
|
|
292
253
|
},
|
|
293
254
|
async getHealth() {
|
|
255
|
+
return { status: "ok" };
|
|
256
|
+
},
|
|
257
|
+
async getSyncState() {
|
|
294
258
|
return storage.getSyncState();
|
|
295
259
|
},
|
|
296
260
|
async triggerSync() {
|
|
297
261
|
const state = await storage.getSyncState();
|
|
298
|
-
if (state.status
|
|
299
|
-
return {
|
|
262
|
+
if (isSyncActive2(state.status)) {
|
|
263
|
+
return { queued: false };
|
|
300
264
|
}
|
|
301
|
-
|
|
265
|
+
const queued = await storage.markSyncQueued();
|
|
266
|
+
if (!queued) {
|
|
267
|
+
return { queued: false };
|
|
268
|
+
}
|
|
269
|
+
void runSync(config, storage).catch((error) => {
|
|
302
270
|
console.error("Rawdash sync failed", error);
|
|
303
271
|
});
|
|
304
|
-
return {
|
|
272
|
+
return { queued: true };
|
|
305
273
|
}
|
|
306
274
|
};
|
|
307
275
|
}
|
|
@@ -309,18 +277,30 @@ function createEngine(config, options = {}) {
|
|
|
309
277
|
// src/compute.ts
|
|
310
278
|
import { computeMetric } from "@rawdash/core";
|
|
311
279
|
|
|
280
|
+
// src/storage.ts
|
|
281
|
+
import { InMemoryStorage as InMemoryStorage2 } from "@rawdash/core";
|
|
282
|
+
|
|
312
283
|
// src/index.ts
|
|
313
|
-
|
|
314
|
-
const { port = 8080, storage } = options;
|
|
315
|
-
const app = createServer(createEngineRouters(config, storage));
|
|
316
|
-
honoServe({ fetch: app.fetch, port });
|
|
317
|
-
}
|
|
284
|
+
import { isSyncActive as isSyncActive3, ACTIVE_SYNC_STATUSES } from "@rawdash/core";
|
|
318
285
|
export {
|
|
319
|
-
|
|
286
|
+
ACTIVE_SYNC_STATUSES,
|
|
287
|
+
DEFAULT_RETENTION_INTERVAL_MS,
|
|
288
|
+
FULL_SYNC_TIMEOUT_MS,
|
|
289
|
+
InMemoryStorage2 as InMemoryStorage,
|
|
290
|
+
ROUTES,
|
|
291
|
+
RawdashError,
|
|
320
292
|
computeMetric,
|
|
321
293
|
createEngine,
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
294
|
+
getHealth,
|
|
295
|
+
getSyncStateHandler,
|
|
296
|
+
getWidget,
|
|
297
|
+
hasPruningPolicy,
|
|
298
|
+
isRawdashError,
|
|
299
|
+
isSyncActive3 as isSyncActive,
|
|
300
|
+
listWidgets,
|
|
301
|
+
runRetention,
|
|
302
|
+
runRetentionOnce,
|
|
303
|
+
runSync,
|
|
304
|
+
triggerSync
|
|
325
305
|
};
|
|
326
306
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/routers/health.ts","../src/routers/retention.ts","../src/routers/sync.ts","../src/routers/widgets.ts","../src/storage.ts","../src/engine-router.ts","../src/server.ts","../src/engine.ts","../src/compute.ts"],"sourcesContent":["import { serve as honoServe } from '@hono/node-server';\n\nimport { createEngineRouters } from './engine-router';\nimport { createServer } from './server';\nimport type { DashboardConfig, ServeOptions } from './types';\n\nexport { createServer } from './server';\nexport { createEngineRouters } from './engine-router';\nexport { createEngine } from './engine';\nexport { computeMetric } from './compute';\nexport { InMemoryStorage } from './storage';\nexport type { RouterMount } from './router';\nexport type { DashboardConfig, ServeOptions, ServerStorage } from './types';\nexport type { Engine, EngineOptions } from './engine';\n\nexport function serve(\n config: DashboardConfig,\n options: ServeOptions = {},\n): void {\n const { port = 8080, storage } = options;\n const app = createServer(createEngineRouters(config, storage));\n honoServe({ fetch: app.fetch, port });\n}\n","import type { Hono } from 'hono';\n\nimport type { RouterMount } from '../router';\nimport type { ServerStorage } from '../types';\n\nexport class HealthRouter implements RouterMount {\n constructor(private storage: ServerStorage) {}\n\n mount(app: Hono): void {\n app.get('/health', async (c) => {\n return c.json(await this.storage.getSyncState());\n });\n }\n}\n","import type { DashboardConfig, RetentionConfig } from '@rawdash/core';\nimport { selectForDeletion } from '@rawdash/core';\nimport type { Hono } from 'hono';\n\nimport type { RouterMount } from '../router';\nimport type { ServerStorage } from '../types';\n\nconst DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\n\nfunction hasPruningPolicy(config: RetentionConfig): boolean {\n return config.maxAge !== undefined || config.maxSize !== undefined;\n}\n\nexport class RetentionRouter implements RouterMount {\n private interval: ReturnType<typeof setInterval> | null = null;\n private inFlight: Promise<void> | null = null;\n\n constructor(\n private config: DashboardConfig,\n private storage: ServerStorage,\n ) {}\n\n async runRetention(): Promise<void> {\n if (this.inFlight) {\n return this.inFlight;\n }\n\n this.inFlight = this.runRetentionOnce().finally(() => {\n this.inFlight = null;\n });\n\n return this.inFlight;\n }\n\n private async runRetentionOnce(): Promise<void> {\n const retentionConfig = this.config.retention;\n if (!retentionConfig || !hasPruningPolicy(retentionConfig)) {\n return;\n }\n\n const nowMs = Date.now();\n\n const results = await Promise.allSettled(\n this.config.connectors.map(async ({ connector }) => {\n const handle = this.storage.getStorageHandle(connector.id);\n\n const [events, metrics, distributions] = await Promise.all([\n handle.queryEvents({}),\n handle.queryMetrics({}),\n handle.queryDistributions({}),\n ]);\n\n await applyRetentionToShape(\n events,\n (e) => e.start_ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.events(survivors, { names }),\n );\n\n await applyRetentionToShape(\n metrics,\n (m) => m.ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.metrics(survivors, { names }),\n );\n\n await applyRetentionToShape(\n distributions,\n (d) => d.ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.distributions(survivors, { names }),\n );\n }),\n );\n\n const failures = results.filter(\n (r): r is PromiseRejectedResult => r.status === 'rejected',\n );\n if (failures.length > 0) {\n throw new Error(\n `Retention failed for ${failures.length} connector(s): ${failures.map((f) => String(f.reason)).join('; ')}`,\n );\n }\n }\n\n mount(app: Hono): void {\n app.post('/retain', async (c) => {\n try {\n await this.runRetention();\n return c.json({ triggered: true });\n } catch (err) {\n console.error('retention run failed', err);\n return c.json({ triggered: false }, 500);\n }\n });\n\n const retentionConfig = this.config.retention;\n if (retentionConfig && hasPruningPolicy(retentionConfig)) {\n const intervalMs = retentionConfig.intervalMs ?? DEFAULT_INTERVAL_MS;\n this.interval = setInterval(() => {\n void this.runRetention().catch((err) => {\n console.error('retention run failed', err);\n });\n }, intervalMs);\n }\n }\n\n stop(): void {\n if (this.interval !== null) {\n clearInterval(this.interval);\n this.interval = null;\n }\n }\n}\n\nasync function applyRetentionToShape<T extends { name: string }>(\n rows: T[],\n getTs: (row: T) => number,\n config: RetentionConfig,\n nowMs: number,\n writeSurvivors: (survivors: T[], names: string[]) => Promise<void>,\n): Promise<void> {\n if (rows.length === 0) {\n return;\n }\n\n const sorted = [...rows].sort((a, b) => getTs(b) - getTs(a));\n const toDeleteSet = new Set(selectForDeletion(sorted, getTs, config, nowMs));\n\n if (toDeleteSet.size === 0) {\n return;\n }\n\n const survivors = sorted.filter((r) => !toDeleteSet.has(r));\n const allNames = [...new Set(rows.map((r) => r.name))];\n\n await writeSurvivors(survivors, allNames);\n}\n","import type { DashboardConfig } from '@rawdash/core';\nimport type { Hono } from 'hono';\n\nimport type { RouterMount } from '../router';\nimport type { ServerStorage } from '../types';\n\nconst FULL_SYNC_TIMEOUT_MS = 300_000;\n\nexport class SyncRouter implements RouterMount {\n constructor(\n private config: DashboardConfig,\n private storage: ServerStorage,\n ) {}\n\n async runSync(): Promise<void> {\n const acquired = await this.storage.setSyncing();\n if (!acquired) {\n return;\n }\n const errors: string[] = [];\n await Promise.allSettled(\n this.config.connectors.map(async ({ connector }) => {\n const handle = this.storage.getStorageHandle(connector.id);\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n FULL_SYNC_TIMEOUT_MS,\n );\n try {\n const result = await connector.sync(\n { mode: 'full' },\n handle,\n controller.signal,\n );\n if (!result.done) {\n errors.push(\n `${connector.id} did not complete in one chunk (chunked syncs are only supported in cloud)`,\n );\n }\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n errors.push(\n `${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`,\n );\n } else {\n errors.push(err instanceof Error ? err.message : String(err));\n }\n } finally {\n clearTimeout(timer);\n }\n }),\n );\n if (errors.length > 0) {\n await this.storage.setSyncError(errors.join('; '));\n } else {\n await this.storage.setSyncSuccess();\n }\n }\n\n mount(app: Hono): void {\n app.post('/sync', async (c) => {\n const state = await this.storage.getSyncState();\n if (state.status === 'syncing') {\n return c.json({ triggered: false });\n }\n void this.runSync();\n return c.json({ triggered: true });\n });\n }\n}\n","import type {\n CachedWidget,\n ConfiguredConnector,\n Dashboard,\n Widget,\n WidgetsListResponse,\n} from '@rawdash/core';\nimport { resolveWidget } from '@rawdash/core';\nimport type { Hono } from 'hono';\n\nimport type { RouterMount } from '../router';\nimport type { ServerStorage } from '../types';\n\nexport class WidgetsRouter implements RouterMount {\n constructor(\n private dashboardId: string,\n private dashboard: Dashboard,\n private connectors: ConfiguredConnector[],\n private storage: ServerStorage,\n ) {}\n\n private resolve(\n id: string,\n widget: Widget,\n ): Promise<CachedWidget | undefined> {\n return resolveWidget(id, widget, this.connectors, this.storage);\n }\n\n mount(app: Hono): void {\n const base = `/dashboards/${this.dashboardId}/widgets`;\n\n app.get(base, async (c) => {\n const entries = Object.entries(this.dashboard.widgets);\n const resolved = await Promise.all(\n entries.map(([key, widget]) => this.resolve(key, widget)),\n );\n const widgets = resolved.filter(\n (w): w is CachedWidget => w !== undefined,\n );\n const response: WidgetsListResponse = { widgets };\n return c.json(response);\n });\n\n app.get(`${base}/:widgetId`, async (c) => {\n const widgetId = c.req.param('widgetId');\n const widget = this.dashboard.widgets[widgetId];\n if (!widget) {\n return c.json({ error: 'Widget not found' }, 404);\n }\n const result = await this.resolve(widgetId, widget);\n if (!result) {\n return c.json({ error: 'Widget not found' }, 404);\n }\n return c.json(result);\n });\n }\n}\n","export { InMemoryStorage } from '@rawdash/core';\n","import type { DashboardConfig } from '@rawdash/core';\n\nimport type { RouterMount } from './router';\nimport { HealthRouter } from './routers/health';\nimport { RetentionRouter } from './routers/retention';\nimport { SyncRouter } from './routers/sync';\nimport { WidgetsRouter } from './routers/widgets';\nimport { InMemoryStorage } from './storage';\nimport type { ServerStorage } from './types';\n\nexport function createEngineRouters(\n config: DashboardConfig,\n storage: ServerStorage = new InMemoryStorage(),\n): RouterMount[] {\n const widgetRouters = Object.entries(config.dashboards).map(\n ([dashboardId, dashboard]) =>\n new WidgetsRouter(dashboardId, dashboard, config.connectors, storage),\n );\n\n return [\n ...widgetRouters,\n new SyncRouter(config, storage),\n new RetentionRouter(config, storage),\n new HealthRouter(storage),\n ];\n}\n","import { Hono } from 'hono';\n\nimport type { RouterMount } from './router';\n\nexport function createServer(routers: RouterMount[]): Hono {\n const app = new Hono();\n for (const router of routers) {\n router.mount(app);\n }\n return app;\n}\n","import type {\n CachedWidget,\n DashboardConfig,\n SyncState,\n TriggerSyncResponse,\n} from '@rawdash/core';\nimport { InMemoryStorage, resolveWidget } from '@rawdash/core';\n\nimport { SyncRouter } from './routers/sync';\nimport type { ServerStorage } from './types';\n\nexport interface EngineOptions {\n storage?: ServerStorage;\n}\n\nexport interface Engine {\n getWidget(\n dashboardId: string,\n widgetId: string,\n ): Promise<CachedWidget | undefined>;\n getWidgets(dashboardId: string): Promise<CachedWidget[]>;\n getHealth(): Promise<SyncState>;\n triggerSync(): Promise<TriggerSyncResponse>;\n}\n\nexport function createEngine(\n config: DashboardConfig,\n options: EngineOptions = {},\n): Engine {\n const storage: ServerStorage = options.storage ?? new InMemoryStorage();\n const syncRouter = new SyncRouter(config, storage);\n\n return {\n async getWidget(dashboardId, widgetId) {\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n return undefined;\n }\n const widget = dashboard.widgets[widgetId];\n if (!widget) {\n return undefined;\n }\n return resolveWidget(widgetId, widget, config.connectors, storage);\n },\n\n async getWidgets(dashboardId) {\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n return [];\n }\n const entries = Object.entries(dashboard.widgets);\n const resolved = await Promise.all(\n entries.map(([key, widget]) =>\n resolveWidget(key, widget, config.connectors, storage),\n ),\n );\n return resolved.filter((w): w is CachedWidget => w !== undefined);\n },\n\n async getHealth() {\n return storage.getSyncState();\n },\n\n async triggerSync() {\n const state = await storage.getSyncState();\n if (state.status === 'syncing') {\n return { triggered: false };\n }\n void syncRouter.runSync().catch((error) => {\n console.error('Rawdash sync failed', error);\n });\n return { triggered: true };\n },\n };\n}\n","export { computeMetric } from '@rawdash/core';\n"],"mappings":"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","names":["InMemoryStorage","resolveWidget","InMemoryStorage","resolveWidget"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/routes.ts","../src/handlers.ts","../src/retention.ts","../src/sync.ts","../src/engine.ts","../src/compute.ts","../src/storage.ts","../src/index.ts"],"sourcesContent":["/**\n * Thrown by `@rawdash/server` handlers when a request fails in a way the\n * HTTP adapter should translate to a structured response (e.g. 404, 400).\n * Framework adapters (`@rawdash/hono`, etc.) should catch this and map\n * `status` to the appropriate HTTP status code.\n */\nexport class RawdashError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = 'RawdashError';\n }\n}\n\nexport function isRawdashError(err: unknown): err is RawdashError {\n return err instanceof RawdashError;\n}\n","/**\n * Canonical URL path conventions for the rawdash HTTP wire contract.\n *\n * Framework adapters (`@rawdash/hono`, etc.) and clients\n * (`@rawdash/client`) should use these constants instead of hard-coding\n * paths, so the contract stays in one place.\n */\nexport const ROUTES = {\n health: '/health',\n syncState: '/sync/state',\n sync: '/sync',\n retention: '/retention/retain',\n widgets: {\n list: (dashboardId: string): string =>\n `/dashboards/${encodeURIComponent(dashboardId)}/widgets`,\n single: (dashboardId: string, widgetId: string): string =>\n `/dashboards/${encodeURIComponent(dashboardId)}/widgets/${encodeURIComponent(widgetId)}`,\n },\n} as const;\n","import type {\n CachedWidget,\n HealthResponse,\n SyncState,\n TriggerSyncResponse,\n WidgetsListResponse,\n} from '@rawdash/core';\nimport { isSyncActive, resolveWidget } from '@rawdash/core';\n\nimport type { EngineContext } from './context';\nimport { RawdashError } from './errors';\nimport { runRetention } from './retention';\nimport { runSync } from './sync';\n\n/**\n * Framework-agnostic request handlers for the rawdash wire contract.\n *\n * Each function takes an `EngineContext` (providing per-request access to\n * the config + storage) and returns the response body, or throws a\n * `RawdashError` on a client-visible failure. HTTP adapters\n * (`@rawdash/hono`, etc.) wrap these in their framework's request/response\n * cycle and translate `RawdashError` into a structured error response.\n */\n\nexport function getHealth(): HealthResponse {\n return { status: 'ok' };\n}\n\nexport async function getSyncStateHandler(\n ctx: EngineContext,\n): Promise<SyncState> {\n const storage = await ctx.getStorage();\n return storage.getSyncState();\n}\n\nexport async function triggerSync(\n ctx: EngineContext,\n): Promise<TriggerSyncResponse> {\n const storage = await ctx.getStorage();\n const state = await storage.getSyncState();\n if (isSyncActive(state.status)) {\n return { queued: false };\n }\n // Load the config *before* marking queued — if config loading rejects\n // after we've persisted the queued transition, the sync state would be\n // stuck in `queued` with no background run to drain it.\n const config = await ctx.getConfig();\n // Persist the queued transition synchronously so clients polling\n // /sync/state right after the trigger see `queued` (with queuedAt),\n // not a stale terminal state.\n const queued = await storage.markSyncQueued();\n if (!queued) {\n return { queued: false };\n }\n void runSync(config, storage).catch((err) => {\n console.error('Rawdash sync failed', err);\n });\n return { queued: true };\n}\n\nexport async function listWidgets(\n ctx: EngineContext,\n dashboardId: string,\n): Promise<WidgetsListResponse> {\n const config = await ctx.getConfig();\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n throw new RawdashError(404, 'DASHBOARD_NOT_FOUND', 'Dashboard not found');\n }\n const storage = await ctx.getStorage();\n const entries = Object.entries(dashboard.widgets);\n const resolved = await Promise.all(\n entries.map(([key, widget]) =>\n resolveWidget(key, widget, config.connectors, storage),\n ),\n );\n const widgets = resolved.filter((w): w is CachedWidget => w !== undefined);\n return { widgets };\n}\n\nexport async function getWidget(\n ctx: EngineContext,\n dashboardId: string,\n widgetId: string,\n): Promise<CachedWidget> {\n const config = await ctx.getConfig();\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n throw new RawdashError(404, 'DASHBOARD_NOT_FOUND', 'Dashboard not found');\n }\n const widget = dashboard.widgets[widgetId];\n if (!widget) {\n throw new RawdashError(404, 'WIDGET_NOT_FOUND', 'Widget not found');\n }\n const storage = await ctx.getStorage();\n const result = await resolveWidget(\n widgetId,\n widget,\n config.connectors,\n storage,\n );\n if (!result) {\n throw new RawdashError(404, 'WIDGET_NOT_FOUND', 'Widget not found');\n }\n return result;\n}\n\nexport async function runRetentionOnce(ctx: EngineContext): Promise<void> {\n const config = await ctx.getConfig();\n const storage = await ctx.getStorage();\n await runRetention(config, storage);\n}\n","import type {\n DashboardConfig,\n RetentionConfig,\n ServerStorage,\n} from '@rawdash/core';\nimport { selectForDeletion } from '@rawdash/core';\n\nexport const DEFAULT_RETENTION_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\n\nexport function hasPruningPolicy(config: RetentionConfig): boolean {\n return config.maxAge !== undefined || config.maxSize !== undefined;\n}\n\n/**\n * Apply the retention policy in `config` to every connector's stored data.\n * No-op if the config has no pruning policy. Throws an aggregated error if\n * any connector fails.\n */\nexport async function runRetention(\n config: DashboardConfig,\n storage: ServerStorage,\n): Promise<void> {\n const retentionConfig = config.retention;\n if (!retentionConfig || !hasPruningPolicy(retentionConfig)) {\n return;\n }\n\n const nowMs = Date.now();\n\n const results = await Promise.allSettled(\n config.connectors.map(async ({ connector }) => {\n const handle = storage.getStorageHandle(connector.id);\n\n const [events, metrics, distributions] = await Promise.all([\n handle.queryEvents({}),\n handle.queryMetrics({}),\n handle.queryDistributions({}),\n ]);\n\n await applyRetentionToShape(\n events,\n (e) => e.start_ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.events(survivors, { names }),\n );\n\n await applyRetentionToShape(\n metrics,\n (m) => m.ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.metrics(survivors, { names }),\n );\n\n await applyRetentionToShape(\n distributions,\n (d) => d.ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.distributions(survivors, { names }),\n );\n }),\n );\n\n const failures = results.filter(\n (r): r is PromiseRejectedResult => r.status === 'rejected',\n );\n if (failures.length > 0) {\n throw new Error(\n `Retention failed for ${failures.length} connector(s): ${failures.map((f) => String(f.reason)).join('; ')}`,\n );\n }\n}\n\nasync function applyRetentionToShape<T extends { name: string }>(\n rows: T[],\n getTs: (row: T) => number,\n config: RetentionConfig,\n nowMs: number,\n writeSurvivors: (survivors: T[], names: string[]) => Promise<void>,\n): Promise<void> {\n if (rows.length === 0) {\n return;\n }\n\n const sorted = [...rows].sort((a, b) => getTs(b) - getTs(a));\n const toDeleteSet = new Set(selectForDeletion(sorted, getTs, config, nowMs));\n\n if (toDeleteSet.size === 0) {\n return;\n }\n\n const survivors = sorted.filter((r) => !toDeleteSet.has(r));\n const allNames = [...new Set(rows.map((r) => r.name))];\n\n await writeSurvivors(survivors, allNames);\n}\n","import type { DashboardConfig, ServerStorage } from '@rawdash/core';\n\nexport const FULL_SYNC_TIMEOUT_MS = 300_000;\n\n/**\n * Run a full sync across all connectors in the config in parallel via\n * `Promise.allSettled`, so one failure doesn't abort the others. Each\n * connector run is wrapped in a hard timeout (`FULL_SYNC_TIMEOUT_MS`)\n * raced against the connector's own `Promise`, so a connector that\n * ignores `AbortSignal` (or a storage call that hangs) can still not\n * pin sync state in `running` indefinitely.\n *\n * The per-run storage handle is bound to the same `AbortController`, so\n * once the timeout fires every subsequent write call on that handle\n * becomes a no-op. That makes tail writes from a timed-out connector\n * invisible to the next sync even if the connector itself keeps running\n * — see `withAbortSignal` in `@rawdash/core` and the safety-net note in\n * `docs/authoring-a-connector.md`.\n *\n * Transitions storage through `queued` → `running` → `succeeded`/`failed`.\n * The `queued` step is a no-op if the caller (typically `triggerSync`)\n * already marked the run as queued.\n *\n * Returns silently if another sync acquired the `running` lock first.\n */\nexport async function runSync(\n config: DashboardConfig,\n storage: ServerStorage,\n): Promise<void> {\n // Idempotent: if the caller already queued, this returns false and we\n // proceed to markSyncRunning anyway. If nothing queued us, we queue\n // ourselves now so the state machine still goes through `queued`.\n await storage.markSyncQueued();\n const acquired = await storage.markSyncRunning();\n if (!acquired) {\n return;\n }\n const errors: string[] = [];\n await Promise.allSettled(\n config.connectors.map(async ({ connector }) => {\n const controller = new AbortController();\n const handle = storage.getStorageHandle(connector.id, {\n signal: controller.signal,\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const syncPromise = connector.sync(\n { mode: 'full' },\n handle,\n controller.signal,\n );\n const timeoutPromise = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n const err = new Error(\n `${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`,\n );\n err.name = 'AbortError';\n reject(err);\n }, FULL_SYNC_TIMEOUT_MS);\n });\n const result = await Promise.race([syncPromise, timeoutPromise]);\n if (!result.done) {\n errors.push(\n `${connector.id} did not complete in one chunk (chunked syncs are only supported in cloud)`,\n );\n }\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n errors.push(\n `${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`,\n );\n } else {\n errors.push(err instanceof Error ? err.message : String(err));\n }\n } finally {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n }\n }),\n );\n if (errors.length > 0) {\n await storage.markSyncFailed(errors.join('; '));\n } else {\n await storage.markSyncSucceeded();\n }\n}\n","import type {\n CachedWidget,\n DashboardConfig,\n HealthResponse,\n ServerStorage,\n SyncState,\n TriggerSyncResponse,\n} from '@rawdash/core';\nimport { InMemoryStorage, isSyncActive, resolveWidget } from '@rawdash/core';\n\nimport { runSync } from './sync';\n\nexport interface EngineOptions {\n storage?: ServerStorage;\n}\n\nexport interface Engine {\n getWidget(\n dashboardId: string,\n widgetId: string,\n ): Promise<CachedWidget | undefined>;\n getWidgets(dashboardId: string): Promise<CachedWidget[]>;\n getHealth(): Promise<HealthResponse>;\n getSyncState(): Promise<SyncState>;\n triggerSync(): Promise<TriggerSyncResponse>;\n}\n\nexport function createEngine(\n config: DashboardConfig,\n options: EngineOptions = {},\n): Engine {\n const storage: ServerStorage = options.storage ?? new InMemoryStorage();\n\n return {\n async getWidget(dashboardId, widgetId) {\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n return undefined;\n }\n const widget = dashboard.widgets[widgetId];\n if (!widget) {\n return undefined;\n }\n return resolveWidget(widgetId, widget, config.connectors, storage);\n },\n\n async getWidgets(dashboardId) {\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n return [];\n }\n const entries = Object.entries(dashboard.widgets);\n const resolved = await Promise.all(\n entries.map(([key, widget]) =>\n resolveWidget(key, widget, config.connectors, storage),\n ),\n );\n return resolved.filter((w): w is CachedWidget => w !== undefined);\n },\n\n async getHealth() {\n return { status: 'ok' };\n },\n\n async getSyncState() {\n return storage.getSyncState();\n },\n\n async triggerSync() {\n const state = await storage.getSyncState();\n if (isSyncActive(state.status)) {\n return { queued: false };\n }\n const queued = await storage.markSyncQueued();\n if (!queued) {\n return { queued: false };\n }\n void runSync(config, storage).catch((error) => {\n console.error('Rawdash sync failed', error);\n });\n return { queued: true };\n },\n };\n}\n","export { computeMetric } from '@rawdash/core';\n","export { InMemoryStorage } from '@rawdash/core';\n","export type { EngineContext } from './context';\nexport { RawdashError, isRawdashError } from './errors';\nexport { ROUTES } from './routes';\nexport {\n getHealth,\n getSyncStateHandler,\n getWidget,\n listWidgets,\n runRetentionOnce,\n triggerSync,\n} from './handlers';\nexport { runSync, FULL_SYNC_TIMEOUT_MS } from './sync';\nexport {\n runRetention,\n hasPruningPolicy,\n DEFAULT_RETENTION_INTERVAL_MS,\n} from './retention';\nexport { createEngine } from './engine';\nexport type { Engine, EngineOptions } from './engine';\nexport { computeMetric } from './compute';\nexport { InMemoryStorage } from './storage';\nexport type {\n CachedWidget,\n ConfiguredConnector,\n DashboardConfig,\n HealthResponse,\n ServerStorage,\n SyncState,\n SyncStatus,\n TriggerSyncResponse,\n WidgetsListResponse,\n} from './types';\nexport { isSyncActive, ACTIVE_SYNC_STATUSES } from '@rawdash/core';\n"],"mappings":"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","names":["isSyncActive","resolveWidget","resolveWidget","isSyncActive","InMemoryStorage","isSyncActive"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawdash/server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "Framework-agnostic rawdash request handlers, engine, and wire contract. Wrap with @rawdash/hono (or another adapter) to serve over HTTP.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -22,18 +22,14 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@
|
|
26
|
-
"hono": "^4.7.7",
|
|
27
|
-
"@rawdash/core": "0.13.0"
|
|
25
|
+
"@rawdash/core": "0.14.0"
|
|
28
26
|
},
|
|
29
27
|
"devDependencies": {
|
|
30
28
|
"tsup": "^8.0.0",
|
|
31
|
-
"tsx": "^4.19.2",
|
|
32
29
|
"typescript": "^5.7.2"
|
|
33
30
|
},
|
|
34
31
|
"scripts": {
|
|
35
32
|
"build": "tsup",
|
|
36
|
-
"dev": "tsx src/dev.ts",
|
|
37
33
|
"typecheck": "tsc --noEmit",
|
|
38
34
|
"lint": "eslint src",
|
|
39
35
|
"test": "vitest run"
|