Merge branch '5-add-http-and-sse' into 6-add-websocket-transport
This commit is contained in:
@@ -39,6 +39,7 @@ export class App {
|
||||
// Both transports share one ApplicationRouter. Routes use the shared
|
||||
// Broadcaster directly, while HTTP and WebSocket remain protocol adapters.
|
||||
const http = new HttpTransportRouter(router, debug);
|
||||
|
||||
const ws = new WsTransportRouter(router, debug, config.server.maxRequestBodyBytes);
|
||||
const host = new ServerHost(config, debug, [http, ws]);
|
||||
|
||||
@@ -53,6 +54,7 @@ export class App {
|
||||
) {}
|
||||
|
||||
async start(): Promise<void> {
|
||||
await this.database.start();
|
||||
await this.host.start();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { BaseStream } from '../services/stream/base-stream.js';
|
||||
|
||||
export type RouteSendOptions = {
|
||||
|
||||
/** Defaults to `response`; any other value sends an application event. */
|
||||
type?: string;
|
||||
|
||||
@@ -15,6 +16,7 @@ export type RouteSendOptions = {
|
||||
* connection lifetime are shared with other requests on the same connection.
|
||||
*/
|
||||
export interface RouteStream {
|
||||
|
||||
/** Connection shared by every request on the same transport session. */
|
||||
readonly connection: BaseStream;
|
||||
|
||||
@@ -29,6 +31,7 @@ export type RouteHandler = (stream: RouteStream) => void | Promise<void>;
|
||||
|
||||
/** An exact application route with no transport-specific metadata. */
|
||||
export type RouteDefinition = {
|
||||
|
||||
/** Exact route name. Parameter and wildcard syntax are not supported. */
|
||||
url: string;
|
||||
handler: RouteHandler;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ApplicationError } from '../errors/index.ts';
|
||||
|
||||
/** Request-scoped view from which the broadcaster obtains a stable connection. */
|
||||
export interface BroadcastStream {
|
||||
|
||||
/** Connection identity shared by every request on the same transport session. */
|
||||
readonly connection: BaseStream;
|
||||
|
||||
@@ -15,6 +16,7 @@ export interface BroadcastStream {
|
||||
|
||||
/** One pending subscribe call and the topics whose removal will resolve it. */
|
||||
interface SubscriptionWaiter {
|
||||
|
||||
/** Only topics newly introduced by this particular subscribe call. */
|
||||
readonly remainingTopics: Set<string>;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { BaseStream } from './stream/base-stream.ts';
|
||||
|
||||
/** Canonical request produced by every transport adapter. */
|
||||
export type ApplicationRequest = {
|
||||
|
||||
/** Exact application route name. */
|
||||
path: string;
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ export class ServerHost {
|
||||
|
||||
const corsMiddleware = cors({
|
||||
origin: corsConfig.origin ?? '*',
|
||||
allowMethods: corsConfig.methods ?? ['POST', 'OPTIONS'],
|
||||
allowHeaders: corsConfig.allowedHeaders ?? ['Content-Type', 'Accept'],
|
||||
allowMethods: corsConfig.methods ?? [ 'POST', 'OPTIONS' ],
|
||||
allowHeaders: corsConfig.allowedHeaders ?? [ 'Content-Type', 'Accept' ],
|
||||
});
|
||||
|
||||
this.app.use('*', corsMiddleware);
|
||||
@@ -77,7 +77,7 @@ export class ServerHost {
|
||||
throw new Error('ServerHost supports only one WebSocket upgrade server');
|
||||
}
|
||||
|
||||
const [upgradeTransport] = upgradeTransports;
|
||||
const [ upgradeTransport ] = upgradeTransports;
|
||||
|
||||
this.server = serve({
|
||||
fetch: this.app.fetch,
|
||||
@@ -124,7 +124,7 @@ export class ServerHost {
|
||||
const closeTransports = this.transports.map((transport) => Promise.resolve(transport.stop?.()));
|
||||
|
||||
// Create a promise that resolves when the server and transports are closed
|
||||
this.stopPromise = Promise.all([closeServer, ...closeTransports]).then(() => {
|
||||
this.stopPromise = Promise.all([ closeServer, ...closeTransports ]).then(() => {
|
||||
this.stopPromise = undefined;
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Logger } from '../../utils/logger.ts';
|
||||
|
||||
/** Options required to open a SQLite database connection. */
|
||||
export type DatabaseOptions = {
|
||||
|
||||
/** Filesystem path to the SQLite database file. */
|
||||
path: string;
|
||||
|
||||
@@ -39,9 +40,6 @@ export class Database {
|
||||
this.kysely = new Kysely<DatabaseTables>({
|
||||
dialect: this.dialect,
|
||||
});
|
||||
|
||||
// Configure the SQLite pragmas.
|
||||
this.configurePragmas();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +51,13 @@ export class Database {
|
||||
return this.kysely;
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
this.debug('starting database connection');
|
||||
|
||||
// Configure the SQLite pragmas.
|
||||
await this.configurePragmas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the database connection.
|
||||
*/
|
||||
@@ -66,10 +71,10 @@ export class Database {
|
||||
*
|
||||
* WAL improves write concurrency; foreign keys enforce referential integrity.
|
||||
*/
|
||||
private configurePragmas(): void {
|
||||
private async configurePragmas(): Promise<void> {
|
||||
this.debug('configuring SQLite pragmas');
|
||||
|
||||
this.kysely.executeQuery(CompiledQuery.raw('PRAGMA journal_mode = WAL'));
|
||||
this.kysely.executeQuery(CompiledQuery.raw('PRAGMA foreign_keys = ON'));
|
||||
await this.kysely.executeQuery(CompiledQuery.raw('PRAGMA journal_mode = WAL'));
|
||||
await this.kysely.executeQuery(CompiledQuery.raw('PRAGMA foreign_keys = ON'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export const up = async (db: Kysely<DatabaseTables>): Promise<void> => {
|
||||
.addColumn('public_key', 'text', (col) => col.notNull())
|
||||
.addColumn('blob', 'blob', (col) => col.notNull())
|
||||
.addColumn('timestamp', 'integer', (col) => col.notNull().defaultTo(millisecondTime))
|
||||
.addPrimaryKeyConstraint('pk_resource_data', ['resource_id', 'public_key'])
|
||||
.addPrimaryKeyConstraint('pk_resource_data', [ 'resource_id', 'public_key' ])
|
||||
.execute();
|
||||
};
|
||||
|
||||
@@ -33,5 +33,6 @@ export const up = async (db: Kysely<DatabaseTables>): Promise<void> => {
|
||||
* @param db - Kysely database to apply the rollback against.
|
||||
*/
|
||||
export const down = async (db: Kysely<DatabaseTables>): Promise<void> => {
|
||||
await db.schema.dropTable('resource_data').ifExists().execute();
|
||||
await db.schema.dropTable('resource_data').ifExists()
|
||||
.execute();
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ export type BlobColumn = ColumnType<Buffer, Buffer | Uint8Array, Buffer>;
|
||||
* One row per (resource_id, public_key). Each publicKey owns a slot within a shared resource.
|
||||
*/
|
||||
export interface ResourceDataTable {
|
||||
|
||||
/** Shared resource identifier grouping related instances. */
|
||||
resource_id: string;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** A normal request/response result before transport encoding. */
|
||||
export type StreamResponse = {
|
||||
|
||||
/** Optional correlation ID for multiplexed transports. */
|
||||
id?: string;
|
||||
|
||||
@@ -15,6 +16,7 @@ export type StreamResponse = {
|
||||
|
||||
/** An application event before a transport applies its wire encoding. */
|
||||
export type StreamEvent = {
|
||||
|
||||
/** Optional event or correlation ID. */
|
||||
id?: string;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Hono } from 'hono';
|
||||
/** Hono variables populated by transport-boundary middleware. */
|
||||
export type AppEnv = {
|
||||
Variables: {
|
||||
|
||||
/** Decoded Extended JSON request body, when present. */
|
||||
parsedBody?: unknown;
|
||||
|
||||
@@ -19,6 +20,7 @@ export type AppEnv = {
|
||||
* not application routing. Implementations remain unaware of route modules.
|
||||
*/
|
||||
export interface TransportRouter {
|
||||
|
||||
/**
|
||||
* Attach wire endpoints and middleware to the shared Hono application.
|
||||
*
|
||||
@@ -32,6 +34,7 @@ export interface TransportRouter {
|
||||
|
||||
/** A transport which also supplies the WebSocket server used during upgrade. */
|
||||
export interface UpgradeTransportRouter extends TransportRouter {
|
||||
|
||||
/** WebSocket server instance passed to the Node HTTP listener. */
|
||||
readonly websocketServer: WebSocketServerLike;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user