@rawdash/hono 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 +2 -2
- package/dist/index.js +27 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -137,8 +137,8 @@ Handler errors translate to JSON:
|
|
|
137
137
|
## Links
|
|
138
138
|
|
|
139
139
|
- [rawdash docs](https://rawdash.dev)
|
|
140
|
-
- [`@rawdash/server`](
|
|
141
|
-
- [`@rawdash/client`](
|
|
140
|
+
- [`@rawdash/server`](https://www.npmjs.com/package/@rawdash/server) — pure handlers + engine (the package this wraps)
|
|
141
|
+
- [`@rawdash/client`](https://www.npmjs.com/package/@rawdash/client) — typed HTTP client (speaks the same wire contract)
|
|
142
142
|
- [GitHub](https://github.com/rawdash/rawdash)
|
|
143
143
|
- [Issues](https://github.com/rawdash/rawdash/issues)
|
|
144
144
|
|
package/dist/index.js
CHANGED
|
@@ -141,29 +141,41 @@ function startRetentionLoop(opts) {
|
|
|
141
141
|
if (inFlight || stopped) {
|
|
142
142
|
return;
|
|
143
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
144
|
try {
|
|
145
|
+
const config = await opts.getConfig();
|
|
146
|
+
if (!config.retention || !hasPruningPolicy(config.retention)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const storage = await opts.getStorage();
|
|
150
|
+
inFlight = runRetention(config, storage).finally(() => {
|
|
151
|
+
inFlight = null;
|
|
152
|
+
});
|
|
153
153
|
await inFlight;
|
|
154
154
|
} catch (err) {
|
|
155
155
|
console.error("retention run failed", err);
|
|
156
156
|
}
|
|
157
157
|
};
|
|
158
158
|
void (async () => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
try {
|
|
160
|
+
const config = await opts.getConfig();
|
|
161
|
+
if (!config.retention || !hasPruningPolicy(config.retention)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (stopped) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const intervalMs = opts.intervalMs ?? config.retention.intervalMs ?? DEFAULT_RETENTION_INTERVAL_MS;
|
|
168
|
+
const created = setInterval(() => {
|
|
169
|
+
void tick();
|
|
170
|
+
}, intervalMs);
|
|
171
|
+
if (stopped) {
|
|
172
|
+
clearInterval(created);
|
|
173
|
+
} else {
|
|
174
|
+
timer = created;
|
|
175
|
+
}
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error("retention loop startup failed", err);
|
|
162
178
|
}
|
|
163
|
-
const intervalMs = opts.intervalMs ?? config.retention.intervalMs ?? DEFAULT_RETENTION_INTERVAL_MS;
|
|
164
|
-
timer = setInterval(() => {
|
|
165
|
-
void tick();
|
|
166
|
-
}, intervalMs);
|
|
167
179
|
})();
|
|
168
180
|
return () => {
|
|
169
181
|
stopped = true;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 try {\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 await inFlight;\n } catch (err) {\n console.error('retention run failed', err);\n }\n };\n\n void (async () => {\n try {\n const config = await opts.getConfig();\n if (!config.retention || !hasPruningPolicy(config.retention)) {\n return;\n }\n // Re-check `stopped` after the async getConfig in case stop()\n // was called while we were awaiting it.\n if (stopped) {\n return;\n }\n const intervalMs =\n opts.intervalMs ??\n config.retention.intervalMs ??\n DEFAULT_RETENTION_INTERVAL_MS;\n const created = setInterval(() => {\n void tick();\n }, intervalMs);\n // Race with stop(): if stop() ran between the check above and\n // setInterval, clear it immediately rather than leaving a\n // lingering timer behind.\n if (stopped) {\n clearInterval(created);\n } else {\n timer = created;\n }\n } catch (err) {\n console.error('retention loop startup failed', err);\n }\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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawdash/hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
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
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|