feat(pagination): add generic typed pagination with ordering to all list endpoints

- add PaginationQueryDto<TOrderBy> generic with orderBy and orderDir fields
- add SessionOrderBy, EnvironmentOrderBy, ScenarioOrderBy type aliases
- update session, environment, scenario services and controllers to use typed pagination
- update MCP list_sessions, list_environments, list_scenarios tools to expose orderBy/orderDir
- update tests for all list endpoints to cover page, limit, ordering, and invalid param rejection
This commit is contained in:
2026-04-07 17:54:23 +03:00
parent d9cdb64ee2
commit 36f8b8c0ca
16 changed files with 337 additions and 81 deletions
+28 -10
View File
@@ -55,9 +55,17 @@ export class McpService {
server.registerTool(
'list_sessions',
{ description: 'List all stored sessions (id, sessionName, createdAt, updatedAt)' },
async () => {
const sessions = await this.sessionService.findAll();
{
description: 'List all stored sessions (id, sessionName, createdAt, updatedAt), paginated',
inputSchema: {
page: z.number().int().min(1).optional().describe('Page number (default 1)'),
limit: z.number().int().min(1).optional().describe('Items per page (default 20)'),
orderBy: z.enum(['id', 'sessionName', 'createdAt', 'updatedAt']).optional().describe('Field to order by (default id)'),
orderDir: z.enum(['ASC', 'DESC']).optional().describe('Sort direction (default ASC)'),
},
},
async ({ page, limit, orderBy, orderDir }) => {
const sessions = await this.sessionService.findAll({ page, limit, orderBy, orderDir });
return { content: [{ type: 'text' as const, text: JSON.stringify(sessions) }] };
},
);
@@ -71,8 +79,8 @@ export class McpService {
},
},
async ({ id }) => {
const sessions = await this.sessionService.findAll();
if (!sessions.find(s => s.id === id)) {
const { data } = await this.sessionService.findAll();
if (!data.find(s => s.id === id)) {
return { isError: true, content: [{ type: 'text' as const, text: `Session ${id} not found` }] };
}
await this.sessionService.remove(id);
@@ -84,9 +92,17 @@ export class McpService {
server.registerTool(
'list_environments',
{ description: 'List all environments' },
async () => {
const envs = await this.environmentService.findAll();
{
description: 'List all environments, paginated',
inputSchema: {
page: z.number().int().min(1).optional().describe('Page number (default 1)'),
limit: z.number().int().min(1).optional().describe('Items per page (default 20)'),
orderBy: z.enum(['id', 'name', 'createdAt', 'updatedAt']).optional().describe('Field to order by (default id)'),
orderDir: z.enum(['ASC', 'DESC']).optional().describe('Sort direction (default ASC)'),
},
},
async ({ page, limit, orderBy, orderDir }) => {
const envs = await this.environmentService.findAll({ page, limit, orderBy, orderDir });
return { content: [{ type: 'text' as const, text: JSON.stringify(envs) }] };
},
);
@@ -218,10 +234,12 @@ export class McpService {
inputSchema: {
page: z.number().int().min(1).optional().describe('Page number (default 1)'),
limit: z.number().int().min(1).optional().describe('Items per page (default 20)'),
orderBy: z.enum(['id', 'name', 'createdAt', 'updatedAt']).optional().describe('Field to order by (default id)'),
orderDir: z.enum(['ASC', 'DESC']).optional().describe('Sort direction (default ASC)'),
},
},
async ({ page, limit }) => {
const result = await this.scenarioService.findAll({ page, limit });
async ({ page, limit, orderBy, orderDir }) => {
const result = await this.scenarioService.findAll({ page, limit, orderBy, orderDir });
return { content: [{ type: 'text' as const, text: JSON.stringify(result) }] };
},
);