mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 09:58:46 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
BoundedTaskListProjectionUnavailableError,
|
||||
InvalidBoundedTaskListProjectionError,
|
||||
executeBoundedTaskListProjection,
|
||||
type BoundedTaskListInput,
|
||||
} from '@qinglong/runtime-core/bounded-task-list-projection';
|
||||
import type { TaskDefinitionSource } from '@qinglong/runtime-core/task-definition';
|
||||
|
||||
import type { LocalApiResponse } from '../transport/contract';
|
||||
|
||||
export interface LocalApiTaskListRequest {
|
||||
readonly projectId: string;
|
||||
readonly input: Readonly<BoundedTaskListInput>;
|
||||
}
|
||||
|
||||
export interface LocalApiTaskListRoute {
|
||||
handle(request: Readonly<LocalApiTaskListRequest>): Promise<LocalApiResponse>;
|
||||
}
|
||||
|
||||
function unavailable(): LocalApiResponse {
|
||||
return Object.freeze({
|
||||
statusCode: 503,
|
||||
body: Object.freeze({ code: 'task_list_unavailable' }),
|
||||
});
|
||||
}
|
||||
|
||||
export function createLocalApiTaskListRoute(
|
||||
tasks: Pick<TaskDefinitionSource, 'listTaskDefinitions'>,
|
||||
): Readonly<LocalApiTaskListRoute> {
|
||||
if (!tasks || typeof tasks.listTaskDefinitions !== 'function') {
|
||||
throw new TypeError('Local API Task list repository is invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
async handle(request: Readonly<LocalApiTaskListRequest>) {
|
||||
try {
|
||||
const result = await executeBoundedTaskListProjection(
|
||||
tasks,
|
||||
request.projectId,
|
||||
request.input,
|
||||
);
|
||||
return Object.freeze({
|
||||
statusCode: 200,
|
||||
body: Object.freeze({ ...result }),
|
||||
});
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof InvalidBoundedTaskListProjectionError ||
|
||||
error instanceof BoundedTaskListProjectionUnavailableError
|
||||
) {
|
||||
return unavailable();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
BoundedTaskReadProjectionUnavailableError,
|
||||
InvalidBoundedTaskReadProjectionError,
|
||||
executeBoundedTaskReadProjection,
|
||||
} from '@qinglong/runtime-core/bounded-task-read-projection';
|
||||
import type { TaskDefinitionSource } from '@qinglong/runtime-core/task-definition';
|
||||
|
||||
import type { LocalApiResponse } from '../transport/contract';
|
||||
|
||||
export interface LocalApiTaskReadRequest {
|
||||
readonly projectId: string;
|
||||
readonly taskId: string;
|
||||
}
|
||||
|
||||
export interface LocalApiTaskReadRoute {
|
||||
handle(request: Readonly<LocalApiTaskReadRequest>): Promise<LocalApiResponse>;
|
||||
}
|
||||
|
||||
function response(
|
||||
statusCode: number,
|
||||
body: Readonly<Record<string, unknown>>,
|
||||
): LocalApiResponse {
|
||||
return Object.freeze({ statusCode, body: Object.freeze(body) });
|
||||
}
|
||||
|
||||
export function createLocalApiTaskReadRoute(
|
||||
tasks: Pick<TaskDefinitionSource, 'findCurrentTaskDefinition'>,
|
||||
): Readonly<LocalApiTaskReadRoute> {
|
||||
if (!tasks || typeof tasks.findCurrentTaskDefinition !== 'function') {
|
||||
throw new TypeError('Local API Task read repository is invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
async handle(request: Readonly<LocalApiTaskReadRequest>) {
|
||||
try {
|
||||
const projection = await executeBoundedTaskReadProjection(
|
||||
tasks,
|
||||
request.projectId,
|
||||
request.taskId,
|
||||
);
|
||||
if (projection.found !== true) {
|
||||
return response(404, { code: 'task_not_found' });
|
||||
}
|
||||
const { found: _found, ...task } = projection;
|
||||
return response(200, { task: Object.freeze(task) });
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof InvalidBoundedTaskReadProjectionError ||
|
||||
error instanceof BoundedTaskReadProjectionUnavailableError
|
||||
) {
|
||||
return response(503, { code: 'task_query_unavailable' });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
TASK_START_SCHEMA,
|
||||
InvalidTaskStartError,
|
||||
TaskStartFenceRejectedError,
|
||||
TaskStartNotFoundError,
|
||||
TaskStartUnavailableError,
|
||||
createTaskStartResponseBody,
|
||||
parseTaskStartRequestBody,
|
||||
type TaskStartRepository,
|
||||
} from '@qinglong/runtime-core/task-start';
|
||||
import type {
|
||||
SecurityPolicyFence,
|
||||
SecurityPrincipal,
|
||||
} from '@qinglong/runtime-core/security';
|
||||
|
||||
import type { LocalApiResponse } from '../transport/contract';
|
||||
|
||||
export interface LocalApiTaskStartRequest {
|
||||
readonly projectId: string;
|
||||
readonly taskId: string;
|
||||
readonly body: unknown | null;
|
||||
readonly principal: Readonly<SecurityPrincipal>;
|
||||
readonly policyFence: Readonly<SecurityPolicyFence> | null;
|
||||
}
|
||||
|
||||
export interface LocalApiTaskStartRoute {
|
||||
handle(request: Readonly<LocalApiTaskStartRequest>): Promise<LocalApiResponse>;
|
||||
}
|
||||
|
||||
export type LocalApiTaskStartIdFactory = () => string;
|
||||
|
||||
function response(
|
||||
statusCode: number,
|
||||
body: Readonly<Record<string, unknown>>,
|
||||
): LocalApiResponse {
|
||||
return Object.freeze({ statusCode, body: Object.freeze(body) });
|
||||
}
|
||||
|
||||
export function createLocalApiTaskStartRoute(
|
||||
repository: TaskStartRepository,
|
||||
createId: LocalApiTaskStartIdFactory,
|
||||
): Readonly<LocalApiTaskStartRoute> {
|
||||
if (
|
||||
!repository ||
|
||||
typeof repository.startTask !== 'function' ||
|
||||
typeof createId !== 'function'
|
||||
) {
|
||||
throw new TypeError('Local API Task start route is invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
async handle(request: Readonly<LocalApiTaskStartRequest>) {
|
||||
let body;
|
||||
try {
|
||||
body = parseTaskStartRequestBody(request.body);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidTaskStartError) {
|
||||
return response(400, {
|
||||
code: 'invalid_task_start_request',
|
||||
schema: TASK_START_SCHEMA,
|
||||
});
|
||||
}
|
||||
return response(503, { code: 'task_start_unavailable' });
|
||||
}
|
||||
if (!request.policyFence || request.policyFence.bindingVersion === null) {
|
||||
return response(503, { code: 'task_start_unavailable' });
|
||||
}
|
||||
try {
|
||||
const result = await repository.startTask({
|
||||
projectId: request.projectId,
|
||||
taskId: request.taskId,
|
||||
mutationId: body.mutationId,
|
||||
expectedRevision: body.expectedRevision,
|
||||
expectedContentDigest: body.expectedContentDigest,
|
||||
runId: createId(),
|
||||
attemptId: createId(),
|
||||
createdEventId: createId(),
|
||||
queuedEventId: createId(),
|
||||
subject: request.principal.subject,
|
||||
policyFence: request.policyFence,
|
||||
});
|
||||
return response(
|
||||
result.status === 'accepted' ? 202 : 200,
|
||||
createTaskStartResponseBody(result),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof TaskStartNotFoundError) {
|
||||
return response(404, { code: 'task_not_found' });
|
||||
}
|
||||
if (error instanceof TaskStartFenceRejectedError) {
|
||||
return response(409, {
|
||||
code: 'task_start_fence_rejected',
|
||||
reason: error.reason,
|
||||
});
|
||||
}
|
||||
if (
|
||||
error instanceof InvalidTaskStartError ||
|
||||
error instanceof TaskStartUnavailableError
|
||||
) {
|
||||
return response(503, { code: 'task_start_unavailable' });
|
||||
}
|
||||
return response(503, { code: 'task_start_unavailable' });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user