feat(smartdb): add operation log APIs, point-in-time revert support, and a web-based debug dashboard
This commit is contained in:
11
ts_debugserver/bundled.ts
Normal file
11
ts_debugserver/bundled.ts
Normal file
File diff suppressed because one or more lines are too long
120
ts_debugserver/classes.debugserver.ts
Normal file
120
ts_debugserver/classes.debugserver.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
2
ts_debugserver/index.ts
Normal file
2
ts_debugserver/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { SmartdbDebugServer } from './classes.debugserver.js';
|
||||
export type { IDebugServerOptions } from './classes.debugserver.js';
|
||||
3
ts_debugserver/plugins.ts
Normal file
3
ts_debugserver/plugins.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
|
||||
export { typedserver };
|
||||
Reference in New Issue
Block a user