@rawdash/hono 0.13.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 +147 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# @rawdash/hono
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@rawdash/hono)
|
|
4
|
+
[](https://github.com/rawdash/rawdash/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Hono adapter for rawdash. Mounts [`@rawdash/server`](https://www.npmjs.com/package/@rawdash/server) handlers as Hono routers — runtime-agnostic, so it works in Cloudflare Workers, Node, Bun, Deno, or anywhere Hono runs.
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
A thin wrapper around `@rawdash/server`'s pure handlers. Each factory returns a `Hono` app you mount with `app.route('/path', router)`. You provide:
|
|
11
|
+
|
|
12
|
+
- **`getConfig(c)`** and **`getStorage(c)`** — per-request functions that the adapter calls to obtain the `DashboardConfig` and `ServerStorage` for the request. Return constants for the simple case, or derive values from `c` (path params, auth headers, env bindings) when each request needs its own config or storage.
|
|
13
|
+
- **`before`** middleware — typically auth and authorization checks.
|
|
14
|
+
|
|
15
|
+
No business logic lives here — every router delegates to a `@rawdash/server` handler and translates `RawdashError` into structured HTTP responses.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install @rawdash/hono hono
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Hono is a peer dependency.
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
The `mountEngine` helper builds a fully-wired Hono app for the simple case (one config, one storage):
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createClient } from '@libsql/client';
|
|
31
|
+
import { LibsqlStorage } from '@rawdash/adapter-libsql';
|
|
32
|
+
import { mountEngine } from '@rawdash/hono';
|
|
33
|
+
|
|
34
|
+
const storage = new LibsqlStorage({
|
|
35
|
+
client: createClient({ url: 'file:rawdash.db' }),
|
|
36
|
+
});
|
|
37
|
+
const { app } = mountEngine(config, { storage });
|
|
38
|
+
|
|
39
|
+
// Cloudflare Worker / Bun / Deno: export the app
|
|
40
|
+
export default app;
|
|
41
|
+
|
|
42
|
+
// Node: use @hono/node-server
|
|
43
|
+
// import { serve } from '@hono/node-server';
|
|
44
|
+
// serve({ fetch: app.fetch, port: 8080 });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This mounts:
|
|
48
|
+
|
|
49
|
+
| Path | Method | Source |
|
|
50
|
+
| ---------------------------------------- | ------ | ----------------------- |
|
|
51
|
+
| `/health` | GET | `createHealthRouter` |
|
|
52
|
+
| `/sync/state` | GET | `createSyncStateRouter` |
|
|
53
|
+
| `/sync` | POST | `createSyncRouter` |
|
|
54
|
+
| `/dashboards/:dashboardId/widgets[/...]` | GET | `createWidgetsRouter` |
|
|
55
|
+
| `/retention/retain` | POST | `createRetentionRouter` |
|
|
56
|
+
|
|
57
|
+
`mountEngine` also starts a background retention loop on long-lived runtimes; pass `{ startRetention: false }` on serverless and trigger retention via your platform's scheduler instead.
|
|
58
|
+
|
|
59
|
+
## Per-request config and storage — compose factories directly
|
|
60
|
+
|
|
61
|
+
For deployments that need auth or that look up config / storage per request, skip `mountEngine` and compose the factories. Each factory accepts `before` middleware that runs before the handler, plus the `getConfig` / `getStorage` callbacks:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import {
|
|
65
|
+
createHealthRouter,
|
|
66
|
+
createSyncRouter,
|
|
67
|
+
createSyncStateRouter,
|
|
68
|
+
createWidgetsRouter,
|
|
69
|
+
} from '@rawdash/hono';
|
|
70
|
+
import { Hono } from 'hono';
|
|
71
|
+
|
|
72
|
+
import { assertScope, requireAuth } from './my-auth';
|
|
73
|
+
import { loadConfig, loadStorage } from './my-loaders';
|
|
74
|
+
|
|
75
|
+
const app = new Hono();
|
|
76
|
+
|
|
77
|
+
app.route('/health', createHealthRouter()); // public liveness probe
|
|
78
|
+
|
|
79
|
+
const authedApp = new Hono();
|
|
80
|
+
authedApp.use('*', requireAuth);
|
|
81
|
+
|
|
82
|
+
authedApp.route(
|
|
83
|
+
'/dashboards',
|
|
84
|
+
createWidgetsRouter({
|
|
85
|
+
before: [assertScope('widgets:read')],
|
|
86
|
+
getConfig: (c) => loadConfig(c),
|
|
87
|
+
getStorage: (c) => loadStorage(c),
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
authedApp.route(
|
|
92
|
+
'/sync',
|
|
93
|
+
createSyncRouter({
|
|
94
|
+
before: [assertScope('widgets:write')],
|
|
95
|
+
getConfig: (c) => loadConfig(c),
|
|
96
|
+
getStorage: (c) => loadStorage(c),
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
authedApp.route(
|
|
101
|
+
'/sync/state',
|
|
102
|
+
createSyncStateRouter({
|
|
103
|
+
before: [assertScope('widgets:read')],
|
|
104
|
+
getStorage: (c) => loadStorage(c),
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
app.route('/', authedApp);
|
|
109
|
+
export default app;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The pure handlers in `@rawdash/server` do all the work; this package only translates HTTP. Adapters in other frameworks (Express, NestJS, etc.) would be a parallel thin layer over the same handlers.
|
|
113
|
+
|
|
114
|
+
## Running on Node
|
|
115
|
+
|
|
116
|
+
`@rawdash/hono` does **not** depend on `@hono/node-server` — the package stays runtime-agnostic so a Workers bundle doesn't pull Node-specific code. To run on Node, add `@hono/node-server` yourself:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
npm install @hono/node-server
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { serve } from '@hono/node-server';
|
|
124
|
+
import { mountEngine } from '@rawdash/hono';
|
|
125
|
+
|
|
126
|
+
const { app } = mountEngine(config);
|
|
127
|
+
serve({ fetch: app.fetch, port: 8080 });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Error mapping
|
|
131
|
+
|
|
132
|
+
Handler errors translate to JSON:
|
|
133
|
+
|
|
134
|
+
- `RawdashError` → `{ error: message, code }` at `err.status` (e.g. 404 `{error:'Dashboard not found', code:'DASHBOARD_NOT_FOUND'}`).
|
|
135
|
+
- Other errors propagate to Hono's `onError` for you to handle.
|
|
136
|
+
|
|
137
|
+
## Links
|
|
138
|
+
|
|
139
|
+
- [rawdash docs](https://rawdash.dev)
|
|
140
|
+
- [`@rawdash/server`](../server) — pure handlers + engine (the package this wraps)
|
|
141
|
+
- [`@rawdash/client`](../client) — typed HTTP client (speaks the same wire contract)
|
|
142
|
+
- [GitHub](https://github.com/rawdash/rawdash)
|
|
143
|
+
- [Issues](https://github.com/rawdash/rawdash/issues)
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { DashboardConfig, ServerStorage } from '@rawdash/server';
|
|
2
|
+
import { Context, MiddlewareHandler, Hono } from 'hono';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Common options accepted by every `@rawdash/hono` router factory.
|
|
6
|
+
*
|
|
7
|
+
* `getConfig` / `getStorage` are invoked per-request with the Hono
|
|
8
|
+
* `Context`, so adapters can derive the config or storage from request
|
|
9
|
+
* state (e.g. a path parameter, an id attached by an auth middleware, or
|
|
10
|
+
* environment bindings).
|
|
11
|
+
*
|
|
12
|
+
* `before` middleware runs before any handler — typically auth/scope
|
|
13
|
+
* checks.
|
|
14
|
+
*/
|
|
15
|
+
interface HonoRouterOptions {
|
|
16
|
+
getConfig: (c: Context) => DashboardConfig | Promise<DashboardConfig>;
|
|
17
|
+
getStorage: (c: Context) => ServerStorage | Promise<ServerStorage>;
|
|
18
|
+
before?: MiddlewareHandler[];
|
|
19
|
+
}
|
|
20
|
+
interface HonoStorageRouterOptions {
|
|
21
|
+
getStorage: (c: Context) => ServerStorage | Promise<ServerStorage>;
|
|
22
|
+
before?: MiddlewareHandler[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Liveness probe — returns `{status:'ok'}` synchronously, no storage
|
|
27
|
+
* access. Mount at `/health` (or wherever your platform's probe expects).
|
|
28
|
+
*/
|
|
29
|
+
declare function createHealthRouter(): Hono;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `POST /` — triggers a sync, returning immediately with
|
|
33
|
+
* `{queued: true|false}`. The sync runs in the background.
|
|
34
|
+
*
|
|
35
|
+
* Mount at `/sync`.
|
|
36
|
+
*/
|
|
37
|
+
declare function createSyncRouter(opts: HonoRouterOptions): Hono;
|
|
38
|
+
/**
|
|
39
|
+
* `GET /` — returns the current `SyncState`. Mount at `/sync/state`.
|
|
40
|
+
*/
|
|
41
|
+
declare function createSyncStateRouter(opts: HonoStorageRouterOptions): Hono;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* - `GET /:dashboardId/widgets` → `WidgetsListResponse`
|
|
45
|
+
* - `GET /:dashboardId/widgets/:widgetId` → `CachedWidget`
|
|
46
|
+
*
|
|
47
|
+
* Mount at `/dashboards`.
|
|
48
|
+
*/
|
|
49
|
+
declare function createWidgetsRouter(opts: HonoRouterOptions): Hono;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `POST /retain` — runs the retention policy once. Coalesces concurrent
|
|
53
|
+
* calls so multiple requests during an in-flight run share the same
|
|
54
|
+
* promise.
|
|
55
|
+
*
|
|
56
|
+
* Mount at `/retention`.
|
|
57
|
+
*/
|
|
58
|
+
declare function createRetentionRouter(opts: HonoRouterOptions): Hono;
|
|
59
|
+
interface RetentionLoopOptions {
|
|
60
|
+
getConfig: () => DashboardConfig | Promise<DashboardConfig>;
|
|
61
|
+
getStorage: () => ServerStorage | Promise<ServerStorage>;
|
|
62
|
+
intervalMs?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Start a background loop that periodically runs the retention policy.
|
|
66
|
+
* Returns a `stop()` function that clears the interval.
|
|
67
|
+
*
|
|
68
|
+
* Only useful on long-lived runtimes (Node, Bun, Deno). In serverless
|
|
69
|
+
* runtimes (Workers, Lambda) use the platform's native scheduler
|
|
70
|
+
* (Cron Triggers, CloudWatch Events) to call `POST /retention/retain`.
|
|
71
|
+
*/
|
|
72
|
+
declare function startRetentionLoop(opts: RetentionLoopOptions): () => void;
|
|
73
|
+
|
|
74
|
+
interface MountEngineOptions {
|
|
75
|
+
storage?: ServerStorage;
|
|
76
|
+
/** Set false to skip the background retention timer (e.g. on serverless). */
|
|
77
|
+
startRetention?: boolean;
|
|
78
|
+
}
|
|
79
|
+
interface MountEngineResult {
|
|
80
|
+
app: Hono;
|
|
81
|
+
stop(): void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Convenience wrapper for the common case: builds a Hono app with all
|
|
85
|
+
* standard rawdash routes mounted at their canonical paths, backed by
|
|
86
|
+
* one `DashboardConfig` and one `ServerStorage` (defaults to
|
|
87
|
+
* `InMemoryStorage`).
|
|
88
|
+
*
|
|
89
|
+
* For deployments that need auth or that look up config / storage per
|
|
90
|
+
* request, skip this and compose the router factories directly with
|
|
91
|
+
* per-request `getConfig` / `getStorage` and `before` middleware.
|
|
92
|
+
*/
|
|
93
|
+
declare function mountEngine(config: DashboardConfig, options?: MountEngineOptions): MountEngineResult;
|
|
94
|
+
|
|
95
|
+
export { type HonoRouterOptions, type HonoStorageRouterOptions, type MountEngineOptions, type MountEngineResult, type RetentionLoopOptions, createHealthRouter, createRetentionRouter, createSyncRouter, createSyncStateRouter, createWidgetsRouter, mountEngine, startRetentionLoop };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// src/health.ts
|
|
2
|
+
import { getHealth } from "@rawdash/server";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
function createHealthRouter() {
|
|
5
|
+
const app = new Hono();
|
|
6
|
+
app.get("/", (c) => c.json(getHealth()));
|
|
7
|
+
return app;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/sync.ts
|
|
11
|
+
import { getSyncStateHandler, triggerSync } from "@rawdash/server";
|
|
12
|
+
import { Hono as Hono2 } from "hono";
|
|
13
|
+
|
|
14
|
+
// src/shared.ts
|
|
15
|
+
import { isRawdashError } from "@rawdash/server";
|
|
16
|
+
function makeEngineContext(c, opts) {
|
|
17
|
+
return {
|
|
18
|
+
getConfig: () => opts.getConfig(c),
|
|
19
|
+
getStorage: () => opts.getStorage(c)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function applyBefore(app, before) {
|
|
23
|
+
if (!before) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const mw of before) {
|
|
27
|
+
app.use("*", mw);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function mapError(c, err) {
|
|
31
|
+
if (isRawdashError(err)) {
|
|
32
|
+
return c.json(
|
|
33
|
+
{ error: err.message, code: err.code },
|
|
34
|
+
err.status
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/sync.ts
|
|
41
|
+
function createSyncRouter(opts) {
|
|
42
|
+
const app = new Hono2();
|
|
43
|
+
applyBefore(app, opts.before);
|
|
44
|
+
app.post("/", async (c) => {
|
|
45
|
+
try {
|
|
46
|
+
return c.json(await triggerSync(makeEngineContext(c, opts)));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return mapError(c, err);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return app;
|
|
52
|
+
}
|
|
53
|
+
function createSyncStateRouter(opts) {
|
|
54
|
+
const app = new Hono2();
|
|
55
|
+
applyBefore(app, opts.before);
|
|
56
|
+
app.get("/", async (c) => {
|
|
57
|
+
try {
|
|
58
|
+
return c.json(
|
|
59
|
+
await getSyncStateHandler({
|
|
60
|
+
getConfig: () => {
|
|
61
|
+
throw new Error("getConfig should not be called by sync-state");
|
|
62
|
+
},
|
|
63
|
+
getStorage: () => opts.getStorage(c)
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return mapError(c, err);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return app;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/widgets.ts
|
|
74
|
+
import { getWidget, listWidgets } from "@rawdash/server";
|
|
75
|
+
import { Hono as Hono3 } from "hono";
|
|
76
|
+
function createWidgetsRouter(opts) {
|
|
77
|
+
const app = new Hono3();
|
|
78
|
+
applyBefore(app, opts.before);
|
|
79
|
+
app.get("/:dashboardId/widgets", async (c) => {
|
|
80
|
+
try {
|
|
81
|
+
return c.json(
|
|
82
|
+
await listWidgets(
|
|
83
|
+
makeEngineContext(c, opts),
|
|
84
|
+
c.req.param("dashboardId")
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
return mapError(c, err);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
app.get("/:dashboardId/widgets/:widgetId", async (c) => {
|
|
92
|
+
try {
|
|
93
|
+
return c.json(
|
|
94
|
+
await getWidget(
|
|
95
|
+
makeEngineContext(c, opts),
|
|
96
|
+
c.req.param("dashboardId"),
|
|
97
|
+
c.req.param("widgetId")
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return mapError(c, err);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return app;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/retention.ts
|
|
108
|
+
import {
|
|
109
|
+
DEFAULT_RETENTION_INTERVAL_MS,
|
|
110
|
+
hasPruningPolicy,
|
|
111
|
+
runRetention,
|
|
112
|
+
runRetentionOnce
|
|
113
|
+
} from "@rawdash/server";
|
|
114
|
+
import { Hono as Hono4 } from "hono";
|
|
115
|
+
function createRetentionRouter(opts) {
|
|
116
|
+
const app = new Hono4();
|
|
117
|
+
applyBefore(app, opts.before);
|
|
118
|
+
let inFlight = null;
|
|
119
|
+
app.post("/retain", async (c) => {
|
|
120
|
+
try {
|
|
121
|
+
if (!inFlight) {
|
|
122
|
+
const ctx = makeEngineContext(c, opts);
|
|
123
|
+
inFlight = runRetentionOnce(ctx).finally(() => {
|
|
124
|
+
inFlight = null;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
await inFlight;
|
|
128
|
+
return c.json({ triggered: true });
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error("retention run failed", err);
|
|
131
|
+
return mapError(c, err);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return app;
|
|
135
|
+
}
|
|
136
|
+
function startRetentionLoop(opts) {
|
|
137
|
+
let stopped = false;
|
|
138
|
+
let inFlight = null;
|
|
139
|
+
let timer = null;
|
|
140
|
+
const tick = async () => {
|
|
141
|
+
if (inFlight || stopped) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const config = await opts.getConfig();
|
|
145
|
+
if (!config.retention || !hasPruningPolicy(config.retention)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const storage = await opts.getStorage();
|
|
149
|
+
inFlight = runRetention(config, storage).finally(() => {
|
|
150
|
+
inFlight = null;
|
|
151
|
+
});
|
|
152
|
+
try {
|
|
153
|
+
await inFlight;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error("retention run failed", err);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
void (async () => {
|
|
159
|
+
const config = await opts.getConfig();
|
|
160
|
+
if (!config.retention || !hasPruningPolicy(config.retention)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const intervalMs = opts.intervalMs ?? config.retention.intervalMs ?? DEFAULT_RETENTION_INTERVAL_MS;
|
|
164
|
+
timer = setInterval(() => {
|
|
165
|
+
void tick();
|
|
166
|
+
}, intervalMs);
|
|
167
|
+
})();
|
|
168
|
+
return () => {
|
|
169
|
+
stopped = true;
|
|
170
|
+
if (timer !== null) {
|
|
171
|
+
clearInterval(timer);
|
|
172
|
+
timer = null;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/mount.ts
|
|
178
|
+
import { InMemoryStorage, ROUTES } from "@rawdash/server";
|
|
179
|
+
import { Hono as Hono5 } from "hono";
|
|
180
|
+
function mountEngine(config, options = {}) {
|
|
181
|
+
const storage = options.storage ?? new InMemoryStorage();
|
|
182
|
+
const getConfig = () => config;
|
|
183
|
+
const getStorage = () => storage;
|
|
184
|
+
const app = new Hono5();
|
|
185
|
+
app.route("/dashboards", createWidgetsRouter({ getConfig, getStorage }));
|
|
186
|
+
app.route(ROUTES.sync, createSyncRouter({ getConfig, getStorage }));
|
|
187
|
+
app.route(ROUTES.syncState, createSyncStateRouter({ getStorage }));
|
|
188
|
+
app.route("/retention", createRetentionRouter({ getConfig, getStorage }));
|
|
189
|
+
app.route(ROUTES.health, createHealthRouter());
|
|
190
|
+
let stopRetention = null;
|
|
191
|
+
if (options.startRetention !== false) {
|
|
192
|
+
stopRetention = startRetentionLoop({ getConfig, getStorage });
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
app,
|
|
196
|
+
stop() {
|
|
197
|
+
if (stopRetention) {
|
|
198
|
+
stopRetention();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
createHealthRouter,
|
|
205
|
+
createRetentionRouter,
|
|
206
|
+
createSyncRouter,
|
|
207
|
+
createSyncStateRouter,
|
|
208
|
+
createWidgetsRouter,
|
|
209
|
+
mountEngine,
|
|
210
|
+
startRetentionLoop
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/health.ts","../src/sync.ts","../src/shared.ts","../src/widgets.ts","../src/retention.ts","../src/mount.ts"],"sourcesContent":["import { getHealth } from '@rawdash/server';\nimport { Hono } from 'hono';\n\n/**\n * Liveness probe — returns `{status:'ok'}` synchronously, no storage\n * access. Mount at `/health` (or wherever your platform's probe expects).\n */\nexport function createHealthRouter(): Hono {\n const app = new Hono();\n app.get('/', (c) => c.json(getHealth()));\n return app;\n}\n","import { getSyncStateHandler, triggerSync } from '@rawdash/server';\nimport { Hono } from 'hono';\n\nimport type { HonoRouterOptions, HonoStorageRouterOptions } from './shared';\nimport { applyBefore, makeEngineContext, mapError } from './shared';\n\n/**\n * `POST /` — triggers a sync, returning immediately with\n * `{queued: true|false}`. The sync runs in the background.\n *\n * Mount at `/sync`.\n */\nexport function createSyncRouter(opts: HonoRouterOptions): Hono {\n const app = new Hono();\n applyBefore(app, opts.before);\n app.post('/', async (c) => {\n try {\n return c.json(await triggerSync(makeEngineContext(c, opts)));\n } catch (err) {\n return mapError(c, err);\n }\n });\n return app;\n}\n\n/**\n * `GET /` — returns the current `SyncState`. Mount at `/sync/state`.\n */\nexport function createSyncStateRouter(opts: HonoStorageRouterOptions): Hono {\n const app = new Hono();\n applyBefore(app, opts.before);\n app.get('/', async (c) => {\n try {\n // sync-state only needs storage, but reuse the EngineContext shape\n // for handler uniformity. getConfig is a no-op here.\n return c.json(\n await getSyncStateHandler({\n getConfig: () => {\n throw new Error('getConfig should not be called by sync-state');\n },\n getStorage: () => opts.getStorage(c),\n }),\n );\n } catch (err) {\n return mapError(c, err);\n }\n });\n return app;\n}\n","import type { EngineContext } from '@rawdash/server';\nimport type { DashboardConfig, ServerStorage } from '@rawdash/server';\nimport { isRawdashError } from '@rawdash/server';\nimport type { Context, MiddlewareHandler } from 'hono';\nimport { Hono } from 'hono';\n\n/**\n * Common options accepted by every `@rawdash/hono` router factory.\n *\n * `getConfig` / `getStorage` are invoked per-request with the Hono\n * `Context`, so adapters can derive the config or storage from request\n * state (e.g. a path parameter, an id attached by an auth middleware, or\n * environment bindings).\n *\n * `before` middleware runs before any handler — typically auth/scope\n * checks.\n */\nexport interface HonoRouterOptions {\n getConfig: (c: Context) => DashboardConfig | Promise<DashboardConfig>;\n getStorage: (c: Context) => ServerStorage | Promise<ServerStorage>;\n before?: MiddlewareHandler[];\n}\n\nexport interface HonoStorageRouterOptions {\n getStorage: (c: Context) => ServerStorage | Promise<ServerStorage>;\n before?: MiddlewareHandler[];\n}\n\nexport function makeEngineContext(\n c: Context,\n opts: HonoRouterOptions,\n): EngineContext {\n return {\n getConfig: () => opts.getConfig(c),\n getStorage: () => opts.getStorage(c),\n };\n}\n\nexport function applyBefore(app: Hono, before?: MiddlewareHandler[]): void {\n if (!before) {\n return;\n }\n for (const mw of before) {\n app.use('*', mw);\n }\n}\n\n/**\n * Translate a thrown error into a Hono JSON response. `RawdashError`\n * becomes a structured `{error, code}` body at the carried status; any\n * other error is re-thrown for Hono's own error handling.\n */\nexport function mapError(c: Context, err: unknown): Response {\n if (isRawdashError(err)) {\n return c.json(\n { error: err.message, code: err.code },\n err.status as Parameters<typeof c.json>[1],\n );\n }\n throw err;\n}\n","import { getWidget, listWidgets } from '@rawdash/server';\nimport { Hono } from 'hono';\n\nimport type { HonoRouterOptions } from './shared';\nimport { applyBefore, makeEngineContext, mapError } from './shared';\n\n/**\n * - `GET /:dashboardId/widgets` → `WidgetsListResponse`\n * - `GET /:dashboardId/widgets/:widgetId` → `CachedWidget`\n *\n * Mount at `/dashboards`.\n */\nexport function createWidgetsRouter(opts: HonoRouterOptions): Hono {\n const app = new Hono();\n applyBefore(app, opts.before);\n\n app.get('/:dashboardId/widgets', async (c) => {\n try {\n return c.json(\n await listWidgets(\n makeEngineContext(c, opts),\n c.req.param('dashboardId'),\n ),\n );\n } catch (err) {\n return mapError(c, err);\n }\n });\n\n app.get('/:dashboardId/widgets/:widgetId', async (c) => {\n try {\n return c.json(\n await getWidget(\n makeEngineContext(c, opts),\n c.req.param('dashboardId'),\n c.req.param('widgetId'),\n ),\n );\n } catch (err) {\n return mapError(c, err);\n }\n });\n\n return app;\n}\n","import type { DashboardConfig, ServerStorage } from '@rawdash/server';\nimport {\n DEFAULT_RETENTION_INTERVAL_MS,\n hasPruningPolicy,\n runRetention,\n runRetentionOnce,\n} from '@rawdash/server';\nimport { Hono } from 'hono';\n\nimport type { HonoRouterOptions } from './shared';\nimport { applyBefore, makeEngineContext, mapError } from './shared';\n\n/**\n * `POST /retain` — runs the retention policy once. Coalesces concurrent\n * calls so multiple requests during an in-flight run share the same\n * promise.\n *\n * Mount at `/retention`.\n */\nexport function createRetentionRouter(opts: HonoRouterOptions): Hono {\n const app = new Hono();\n applyBefore(app, opts.before);\n\n let inFlight: Promise<void> | null = null;\n\n app.post('/retain', async (c) => {\n try {\n if (!inFlight) {\n const ctx = makeEngineContext(c, opts);\n inFlight = runRetentionOnce(ctx).finally(() => {\n inFlight = null;\n });\n }\n await inFlight;\n return c.json({ triggered: true });\n } catch (err) {\n console.error('retention run failed', err);\n return mapError(c, err);\n }\n });\n\n return app;\n}\n\nexport interface RetentionLoopOptions {\n getConfig: () => DashboardConfig | Promise<DashboardConfig>;\n getStorage: () => ServerStorage | Promise<ServerStorage>;\n intervalMs?: number;\n}\n\n/**\n * Start a background loop that periodically runs the retention policy.\n * Returns a `stop()` function that clears the interval.\n *\n * Only useful on long-lived runtimes (Node, Bun, Deno). In serverless\n * runtimes (Workers, Lambda) use the platform's native scheduler\n * (Cron Triggers, CloudWatch Events) to call `POST /retention/retain`.\n */\nexport function startRetentionLoop(opts: RetentionLoopOptions): () => void {\n let stopped = false;\n let inFlight: Promise<void> | null = null;\n let timer: ReturnType<typeof setInterval> | null = null;\n\n const tick = async (): Promise<void> => {\n if (inFlight || stopped) {\n return;\n }\n const config = await opts.getConfig();\n if (!config.retention || !hasPruningPolicy(config.retention)) {\n return;\n }\n const storage = await opts.getStorage();\n inFlight = runRetention(config, storage).finally(() => {\n inFlight = null;\n });\n try {\n await inFlight;\n } catch (err) {\n console.error('retention run failed', err);\n }\n };\n\n void (async () => {\n const config = await opts.getConfig();\n if (!config.retention || !hasPruningPolicy(config.retention)) {\n return;\n }\n const intervalMs =\n opts.intervalMs ??\n config.retention.intervalMs ??\n DEFAULT_RETENTION_INTERVAL_MS;\n timer = setInterval(() => {\n void tick();\n }, intervalMs);\n })();\n\n return () => {\n stopped = true;\n if (timer !== null) {\n clearInterval(timer);\n timer = null;\n }\n };\n}\n","import type { DashboardConfig, ServerStorage } from '@rawdash/server';\nimport { InMemoryStorage, ROUTES } from '@rawdash/server';\nimport { Hono } from 'hono';\n\nimport { createHealthRouter } from './health';\nimport { createRetentionRouter, startRetentionLoop } from './retention';\nimport { createSyncRouter, createSyncStateRouter } from './sync';\nimport { createWidgetsRouter } from './widgets';\n\nexport interface MountEngineOptions {\n storage?: ServerStorage;\n /** Set false to skip the background retention timer (e.g. on serverless). */\n startRetention?: boolean;\n}\n\nexport interface MountEngineResult {\n app: Hono;\n stop(): void;\n}\n\n/**\n * Convenience wrapper for the common case: builds a Hono app with all\n * standard rawdash routes mounted at their canonical paths, backed by\n * one `DashboardConfig` and one `ServerStorage` (defaults to\n * `InMemoryStorage`).\n *\n * For deployments that need auth or that look up config / storage per\n * request, skip this and compose the router factories directly with\n * per-request `getConfig` / `getStorage` and `before` middleware.\n */\nexport function mountEngine(\n config: DashboardConfig,\n options: MountEngineOptions = {},\n): MountEngineResult {\n const storage: ServerStorage = options.storage ?? new InMemoryStorage();\n const getConfig = (): DashboardConfig => config;\n const getStorage = (): ServerStorage => storage;\n\n const app = new Hono();\n app.route('/dashboards', createWidgetsRouter({ getConfig, getStorage }));\n app.route(ROUTES.sync, createSyncRouter({ getConfig, getStorage }));\n app.route(ROUTES.syncState, createSyncStateRouter({ getStorage }));\n app.route('/retention', createRetentionRouter({ getConfig, getStorage }));\n app.route(ROUTES.health, createHealthRouter());\n\n let stopRetention: (() => void) | null = null;\n if (options.startRetention !== false) {\n stopRetention = startRetentionLoop({ getConfig, getStorage });\n }\n\n return {\n app,\n stop() {\n if (stopRetention) {\n stopRetention();\n }\n },\n };\n}\n"],"mappings":"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","names":["Hono","Hono","Hono","Hono","Hono","Hono","Hono","Hono"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rawdash/hono",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "Hono adapter for rawdash — router factories and helpers that mount @rawdash/server handlers onto a Hono app. Runtime-agnostic (Workers, Node, Bun, Deno).",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/rawdash/rawdash.git",
|
|
10
|
+
"directory": "packages/hono"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"@rawdash/source": "./src/index.ts",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"lint": "eslint src",
|
|
28
|
+
"test": "vitest run"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"hono": "^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@rawdash/core": "workspace:*",
|
|
35
|
+
"@rawdash/server": "workspace:*"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"hono": "^4.7.7",
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.7.2",
|
|
41
|
+
"vitest": "^4.1.4"
|
|
42
|
+
}
|
|
43
|
+
}
|