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,102 @@
|
||||
import {
|
||||
RUN_CANCELLATION_SCHEMA,
|
||||
InvalidRunCancellationError,
|
||||
RunCancellationFenceRejectedError,
|
||||
RunCancellationNotFoundError,
|
||||
RunCancellationUnavailableError,
|
||||
createRunCancellationResponseBody,
|
||||
parseRunCancellationRequestBody,
|
||||
type RunCancellationRepository,
|
||||
} from '@qinglong/runtime-core/run-cancellation';
|
||||
import type {
|
||||
SecurityPolicyFence,
|
||||
SecurityPrincipal,
|
||||
} from '@qinglong/runtime-core/security';
|
||||
|
||||
import type { LocalApiResponse } from '../transport/contract';
|
||||
|
||||
export interface LocalApiRunCancellationRequest {
|
||||
readonly projectId: string;
|
||||
readonly runId: string;
|
||||
readonly body: unknown | null;
|
||||
readonly principal: Readonly<SecurityPrincipal>;
|
||||
readonly policyFence: Readonly<SecurityPolicyFence> | null;
|
||||
}
|
||||
|
||||
export interface LocalApiRunCancellationRoute {
|
||||
handle(
|
||||
request: Readonly<LocalApiRunCancellationRequest>,
|
||||
): Promise<LocalApiResponse>;
|
||||
}
|
||||
|
||||
export type LocalApiRunCancellationEventIdFactory = () => string;
|
||||
|
||||
function response(
|
||||
statusCode: number,
|
||||
body: Readonly<Record<string, unknown>>,
|
||||
): LocalApiResponse {
|
||||
return Object.freeze({ statusCode, body: Object.freeze(body) });
|
||||
}
|
||||
|
||||
export function createLocalApiRunCancellationRoute(
|
||||
repository: RunCancellationRepository,
|
||||
createEventId: LocalApiRunCancellationEventIdFactory,
|
||||
): Readonly<LocalApiRunCancellationRoute> {
|
||||
if (
|
||||
!repository ||
|
||||
typeof repository.requestUserCancellation !== 'function' ||
|
||||
typeof createEventId !== 'function'
|
||||
) {
|
||||
throw new TypeError('Local API Run cancellation route is invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
async handle(request: Readonly<LocalApiRunCancellationRequest>) {
|
||||
let body;
|
||||
try {
|
||||
body = parseRunCancellationRequestBody(request.body);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidRunCancellationError) {
|
||||
return response(400, {
|
||||
code: 'invalid_run_cancellation_request',
|
||||
schema: RUN_CANCELLATION_SCHEMA,
|
||||
});
|
||||
}
|
||||
return response(503, { code: 'run_cancellation_unavailable' });
|
||||
}
|
||||
if (!request.policyFence || request.policyFence.bindingVersion === null) {
|
||||
return response(503, { code: 'run_cancellation_unavailable' });
|
||||
}
|
||||
try {
|
||||
const result = await repository.requestUserCancellation({
|
||||
projectId: request.projectId,
|
||||
runId: request.runId,
|
||||
mutationId: body.mutationId,
|
||||
eventId: createEventId(),
|
||||
subject: request.principal.subject,
|
||||
policyFence: request.policyFence,
|
||||
});
|
||||
return response(
|
||||
result.status === 'accepted' ? 202 : 200,
|
||||
createRunCancellationResponseBody(result),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof RunCancellationNotFoundError) {
|
||||
return response(404, { code: 'run_not_found' });
|
||||
}
|
||||
if (error instanceof RunCancellationFenceRejectedError) {
|
||||
return response(409, {
|
||||
code: 'run_cancellation_fence_rejected',
|
||||
reason: error.reason,
|
||||
});
|
||||
}
|
||||
if (
|
||||
error instanceof InvalidRunCancellationError ||
|
||||
error instanceof RunCancellationUnavailableError
|
||||
) {
|
||||
return response(503, { code: 'run_cancellation_unavailable' });
|
||||
}
|
||||
return response(503, { code: 'run_cancellation_unavailable' });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
BoundedRunEventListProjectionUnavailableError,
|
||||
InvalidBoundedRunEventListProjectionError,
|
||||
executeBoundedRunEventListProjection,
|
||||
type BoundedRunEventListInput,
|
||||
} from '@qinglong/runtime-core/bounded-run-event-list-projection';
|
||||
import type { RunRepositoryReader } from '@qinglong/runtime-core/run-repository';
|
||||
|
||||
import type { LocalApiResponse } from '../transport/contract';
|
||||
|
||||
export interface LocalApiRunEventListRequest {
|
||||
readonly projectId: string;
|
||||
readonly runId: string;
|
||||
readonly input: Readonly<BoundedRunEventListInput>;
|
||||
}
|
||||
|
||||
export interface LocalApiRunEventListRoute {
|
||||
handle(
|
||||
request: Readonly<LocalApiRunEventListRequest>,
|
||||
): Promise<LocalApiResponse>;
|
||||
}
|
||||
|
||||
function response(
|
||||
statusCode: number,
|
||||
body: Readonly<Record<string, unknown>>,
|
||||
): LocalApiResponse {
|
||||
return Object.freeze({ statusCode, body: Object.freeze(body) });
|
||||
}
|
||||
|
||||
export function createLocalApiRunEventListRoute(
|
||||
runs: Pick<RunRepositoryReader, 'findRunById' | 'listEvents'>,
|
||||
): Readonly<LocalApiRunEventListRoute> {
|
||||
if (
|
||||
!runs ||
|
||||
typeof runs.findRunById !== 'function' ||
|
||||
typeof runs.listEvents !== 'function'
|
||||
) {
|
||||
throw new TypeError('Local API Run event list repository is invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
async handle(request: Readonly<LocalApiRunEventListRequest>) {
|
||||
try {
|
||||
const result = await executeBoundedRunEventListProjection(
|
||||
runs,
|
||||
request.projectId,
|
||||
request.runId,
|
||||
request.input,
|
||||
);
|
||||
if (!result.found) {
|
||||
return response(404, { code: 'run_not_found' });
|
||||
}
|
||||
const { found: _found, ...timeline } = result;
|
||||
return response(200, { ...timeline });
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof InvalidBoundedRunEventListProjectionError ||
|
||||
error instanceof BoundedRunEventListProjectionUnavailableError
|
||||
) {
|
||||
return response(503, { code: 'run_event_list_unavailable' });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
BoundedRunListProjectionUnavailableError,
|
||||
InvalidBoundedRunListProjectionError,
|
||||
executeBoundedRunListProjection,
|
||||
type BoundedRunListInput,
|
||||
} from '@qinglong/runtime-core/bounded-run-list-projection';
|
||||
import type { ProjectRunListReader } from '@qinglong/runtime-core/project-run-list';
|
||||
|
||||
import type { LocalApiResponse } from '../transport/contract';
|
||||
|
||||
export interface LocalApiRunListRequest {
|
||||
readonly projectId: string;
|
||||
readonly input: Readonly<BoundedRunListInput>;
|
||||
}
|
||||
|
||||
export interface LocalApiRunListRoute {
|
||||
handle(request: Readonly<LocalApiRunListRequest>): Promise<LocalApiResponse>;
|
||||
}
|
||||
|
||||
function unavailable(): LocalApiResponse {
|
||||
return Object.freeze({
|
||||
statusCode: 503,
|
||||
body: Object.freeze({ code: 'run_list_unavailable' }),
|
||||
});
|
||||
}
|
||||
|
||||
export function createLocalApiRunListRoute(
|
||||
runs: ProjectRunListReader,
|
||||
): Readonly<LocalApiRunListRoute> {
|
||||
if (!runs || typeof runs.listRunsByProject !== 'function') {
|
||||
throw new TypeError('Local API Run list repository is invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
async handle(request: Readonly<LocalApiRunListRequest>) {
|
||||
try {
|
||||
const result = await executeBoundedRunListProjection(
|
||||
runs,
|
||||
request.projectId,
|
||||
request.input,
|
||||
);
|
||||
return Object.freeze({
|
||||
statusCode: 200,
|
||||
body: Object.freeze({ ...result }),
|
||||
});
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof InvalidBoundedRunListProjectionError ||
|
||||
error instanceof BoundedRunListProjectionUnavailableError
|
||||
) {
|
||||
return unavailable();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
BoundedRunReadProjectionUnavailableError,
|
||||
executeBoundedRunReadProjection,
|
||||
} from '@qinglong/runtime-core/bounded-run-read-projection';
|
||||
import type { RunRepositoryReader } from '@qinglong/runtime-core/run-repository';
|
||||
|
||||
import type { LocalApiResponse } from '../transport/contract';
|
||||
|
||||
export interface LocalApiRunReadRequest {
|
||||
readonly projectId: string;
|
||||
readonly runId: string;
|
||||
}
|
||||
|
||||
export interface LocalApiRunReadRoute {
|
||||
handle(request: Readonly<LocalApiRunReadRequest>): Promise<LocalApiResponse>;
|
||||
}
|
||||
|
||||
function response(
|
||||
statusCode: number,
|
||||
body: Readonly<Record<string, unknown>>,
|
||||
): LocalApiResponse {
|
||||
return Object.freeze({ statusCode, body: Object.freeze(body) });
|
||||
}
|
||||
|
||||
export function createLocalApiRunReadRoute(
|
||||
runs: Pick<RunRepositoryReader, 'findRunById'>,
|
||||
): Readonly<LocalApiRunReadRoute> {
|
||||
if (!runs || typeof runs.findRunById !== 'function') {
|
||||
throw new TypeError('Local API Run read repository is invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
async handle(request: Readonly<LocalApiRunReadRequest>) {
|
||||
try {
|
||||
const projection = await executeBoundedRunReadProjection(
|
||||
runs,
|
||||
request.projectId,
|
||||
request.runId,
|
||||
);
|
||||
if (projection.found !== true) {
|
||||
return response(404, { code: 'run_not_found' });
|
||||
}
|
||||
const { found: _found, ...view } = projection;
|
||||
return response(200, {
|
||||
run: Object.freeze({ projectId: request.projectId, ...view }),
|
||||
});
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof BoundedRunReadProjectionUnavailableError ||
|
||||
error instanceof TypeError
|
||||
) {
|
||||
return response(503, { code: 'run_query_unavailable' });
|
||||
}
|
||||
return response(503, { code: 'run_query_unavailable' });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
BoundedRunStepListProjectionUnavailableError,
|
||||
InvalidBoundedRunStepListProjectionError,
|
||||
executeBoundedRunStepListProjection,
|
||||
type BoundedRunStepListInput,
|
||||
} from '@qinglong/runtime-core/bounded-run-step-list-projection';
|
||||
import type { RunRepositoryReader } from '@qinglong/runtime-core/run-repository';
|
||||
import type { StepRunRepository } from '@qinglong/runtime-core/step-run';
|
||||
|
||||
import type { LocalApiResponse } from '../transport/contract';
|
||||
|
||||
export interface LocalApiRunStepListRequest {
|
||||
readonly projectId: string;
|
||||
readonly runId: string;
|
||||
readonly input: Readonly<BoundedRunStepListInput>;
|
||||
}
|
||||
|
||||
export interface LocalApiRunStepListRoute {
|
||||
handle(
|
||||
request: Readonly<LocalApiRunStepListRequest>,
|
||||
): Promise<LocalApiResponse>;
|
||||
}
|
||||
|
||||
function response(
|
||||
statusCode: number,
|
||||
body: Readonly<Record<string, unknown>>,
|
||||
): LocalApiResponse {
|
||||
return Object.freeze({ statusCode, body: Object.freeze(body) });
|
||||
}
|
||||
|
||||
export function createLocalApiRunStepListRoute(
|
||||
runs: Pick<RunRepositoryReader, 'findRunById'>,
|
||||
stepRuns: Pick<StepRunRepository, 'listByRun'>,
|
||||
): Readonly<LocalApiRunStepListRoute> {
|
||||
if (
|
||||
!runs ||
|
||||
typeof runs.findRunById !== 'function' ||
|
||||
!stepRuns ||
|
||||
typeof stepRuns.listByRun !== 'function'
|
||||
) {
|
||||
throw new TypeError('Local API Run Step list repository is invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
async handle(request: Readonly<LocalApiRunStepListRequest>) {
|
||||
try {
|
||||
const result = await executeBoundedRunStepListProjection(
|
||||
runs,
|
||||
stepRuns,
|
||||
request.projectId,
|
||||
request.runId,
|
||||
request.input,
|
||||
);
|
||||
if (!result.found) {
|
||||
return response(404, { code: 'run_not_found' });
|
||||
}
|
||||
const { found: _found, ...page } = result;
|
||||
return response(200, { ...page });
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof InvalidBoundedRunStepListProjectionError ||
|
||||
error instanceof BoundedRunStepListProjectionUnavailableError
|
||||
) {
|
||||
return response(503, { code: 'run_step_list_unavailable' });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user