import * as plugins from './plugins.js'; import { files as bundledFiles } from './bundled.js'; import type { SmartdbServer } from '../ts/index.js'; export interface IDebugServerOptions { /** Port to listen on (default: 4000). */ port?: number; } /** * Serves the SmartDB debug UI as a web application with API proxy endpoints. * * @example * ```typescript * import { SmartdbServer } from '@push.rocks/smartdb'; * import { SmartdbDebugServer } from '@push.rocks/smartdb/debugserver'; * * const db = new SmartdbServer({ storage: 'memory' }); * await db.start(); * * const debugServer = new SmartdbDebugServer(db, { port: 4000 }); * await debugServer.start(); * // Open http://localhost:4000 * ``` */ export class SmartdbDebugServer { private server: plugins.typedserver.TypedServer; private smartdbServer: SmartdbServer; private port: number; constructor(smartdbServer: SmartdbServer, options: IDebugServerOptions = {}) { this.smartdbServer = smartdbServer; this.port = options.port ?? 4000; this.server = new plugins.typedserver.TypedServer({ cors: true, port: this.port, bundledContent: bundledFiles, spaFallback: true, noCache: true, }); this.setupApiRoutes(); } private setupApiRoutes() { const jsonResponse = (data: any, status = 200) => new Response(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json' }, }); // Metrics this.server.addRoute('/api/smartdb/metrics', 'GET', async () => { return jsonResponse(await this.smartdbServer.getMetrics()); }); // OpLog stats this.server.addRoute('/api/smartdb/oplog/stats', 'GET', async () => { return jsonResponse(await this.smartdbServer.getOpLogStats()); }); // OpLog entries this.server.addRoute('/api/smartdb/oplog', 'GET', async (ctx) => { const result = await this.smartdbServer.getOpLog({ sinceSeq: ctx.query.sinceSeq ? parseInt(ctx.query.sinceSeq) : undefined, limit: ctx.query.limit ? parseInt(ctx.query.limit) : undefined, db: ctx.query.db || undefined, collection: ctx.query.collection || undefined, }); return jsonResponse(result); }); // Collections this.server.addRoute('/api/smartdb/collections', 'GET', async (ctx) => { const collections = await this.smartdbServer.getCollections(ctx.query.db || undefined); return jsonResponse({ collections }); }); // Documents this.server.addRoute('/api/smartdb/documents', 'GET', async (ctx) => { const { db, collection } = ctx.query; if (!db || !collection) { return jsonResponse({ error: 'db and collection required' }, 400); } const limit = parseInt(ctx.query.limit ?? '50'); const skip = parseInt(ctx.query.skip ?? '0'); const result = await this.smartdbServer.getDocuments(db, collection, limit, skip); return jsonResponse(result); }); // Revert this.server.addRoute('/api/smartdb/revert', 'GET', async (ctx) => { const { seq, dryRun } = ctx.query; if (!seq) { return jsonResponse({ error: 'seq required' }, 400); } const result = await this.smartdbServer.revertToSeq( parseInt(seq), dryRun === 'true', ); return jsonResponse(result); }); } /** * Start the debug server. */ async start(): Promise { await this.server.start(); console.log(`SmartDB Debug UI available at http://localhost:${this.port}`); } /** * Stop the debug server. */ async stop(): Promise { await this.server.stop(); } }