Add @ditto/router package

This commit is contained in:
Alex Gleason 2025-02-20 11:19:50 -06:00
parent f5947eda8b
commit 5c0a350776
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
9 changed files with 146 additions and 0 deletions

View file

@ -9,6 +9,7 @@
"./packages/metrics", "./packages/metrics",
"./packages/policies", "./packages/policies",
"./packages/ratelimiter", "./packages/ratelimiter",
"./packages/router",
"./packages/translators", "./packages/translators",
"./packages/uploaders" "./packages/uploaders"
], ],

View file

@ -0,0 +1,23 @@
import { DittoConf } from '@ditto/conf';
import { DittoDB } from '@ditto/db';
import { Hono } from '@hono/hono';
import { MockRelay } from '@nostrify/nostrify/test';
import { DittoApp } from './DittoApp.ts';
import { DittoRoute } from './DittoRoute.ts';
Deno.test('DittoApp', async () => {
await using db = DittoDB.create('memory://');
const conf = new DittoConf(new Map());
const relay = new MockRelay();
const app = new DittoApp({ conf, db, relay });
const hono = new Hono();
const route = new DittoRoute();
app.route('/', route);
// @ts-expect-error Passing a non-DittoRoute to route.
app.route('/', hono);
});

View file

@ -0,0 +1,21 @@
import { Hono } from '@hono/hono';
import type { HonoOptions } from '@hono/hono/hono-base';
import type { DittoEnv } from './DittoEnv.ts';
export class DittoApp extends Hono<DittoEnv> {
// @ts-ignore Require a DittoRoute for type safety.
declare route: (path: string, app: Hono<DittoEnv>) => Hono<DittoEnv>;
constructor(vars: Omit<DittoEnv['Variables'], 'signal'>, opts: HonoOptions<DittoEnv> = {}) {
super(opts);
this.use((c, next) => {
c.set('db', vars.db);
c.set('conf', vars.conf);
c.set('relay', vars.relay);
c.set('signal', c.req.raw.signal);
return next();
});
}
}

View file

@ -0,0 +1,20 @@
import type { DittoConf } from '@ditto/conf';
import type { DittoDatabase } from '@ditto/db';
import type { Env } from '@hono/hono';
import type { NRelay } from '@nostrify/nostrify';
export interface DittoEnv extends Env {
Variables: {
/** Ditto site configuration. */
conf: DittoConf;
/** Relay store. */
relay: NRelay;
/**
* Database object.
* @deprecated Store data as Nostr events instead.
*/
db: DittoDatabase;
/** Abort signal for the request. */
signal: AbortSignal;
};
}

View file

@ -0,0 +1,5 @@
import type { MiddlewareHandler } from '@hono/hono';
import type { DittoEnv } from './DittoEnv.ts';
// deno-lint-ignore ban-types
export type DittoMiddleware<T extends {}> = MiddlewareHandler<DittoEnv & { Variables: T }>;

View file

@ -0,0 +1,12 @@
import { assertEquals } from '@std/assert';
import { DittoRoute } from './DittoRoute.ts';
Deno.test('DittoRoute', async () => {
const route = new DittoRoute();
const response = await route.request('/');
const body = await response.json();
assertEquals(response.status, 500);
assertEquals(body, { error: 'Missing required variable: db' });
});

View file

@ -0,0 +1,53 @@
import { type ErrorHandler, Hono } from '@hono/hono';
import { HTTPException } from '@hono/hono/http-exception';
import type { HonoOptions } from '@hono/hono/hono-base';
import type { DittoEnv } from './DittoEnv.ts';
/**
* Ditto base route class.
* Ensures that required variables are set for type safety.
*/
export class DittoRoute extends Hono<DittoEnv> {
constructor(opts: HonoOptions<DittoEnv> = {}) {
super(opts);
this.use((c, next) => {
this.assertVars(c.var);
return next();
});
this.onError(this._errorHandler);
}
private assertVars(vars: Partial<DittoEnv['Variables']>): DittoEnv['Variables'] {
if (!vars.db) this.throwMissingVar('db');
if (!vars.conf) this.throwMissingVar('conf');
if (!vars.relay) this.throwMissingVar('relay');
if (!vars.signal) this.throwMissingVar('signal');
return {
...vars,
db: vars.db,
conf: vars.conf,
relay: vars.relay,
signal: vars.signal,
};
}
private throwMissingVar(name: string): never {
throw new HTTPException(500, { message: `Missing required variable: ${name}` });
}
private _errorHandler: ErrorHandler = (error, c) => {
if (error instanceof HTTPException) {
if (error.res) {
return error.res;
} else {
return c.json({ error: error.message }, error.status);
}
}
return c.json({ error: 'Something went wrong' }, 500);
};
}

View file

@ -0,0 +1,7 @@
{
"name": "@ditto/router",
"version": "1.1.0",
"exports": {
".": "./mod.ts"
}
}

4
packages/router/mod.ts Normal file
View file

@ -0,0 +1,4 @@
export { DittoApp } from './DittoApp.ts';
export { DittoRoute } from './DittoRoute.ts';
export type { DittoEnv } from './DittoEnv.ts';