121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
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<void> {
|
|
await this.server.start();
|
|
console.log(`SmartDB Debug UI available at http://localhost:${this.port}`);
|
|
}
|
|
|
|
/**
|
|
* Stop the debug server.
|
|
*/
|
|
async stop(): Promise<void> {
|
|
await this.server.stop();
|
|
}
|
|
}
|