feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,529 @@
import type {
RunAttemptRecord,
RunEventRecord,
RunRecord,
RunRetryPolicyRecord,
} from '@qinglong/runtime-core/run-repository';
import {
EXECUTION_ORIGINS,
MAX_RUN_EVENT_PAYLOAD_BYTES,
RUN_ATTEMPT_STATUSES,
RUN_CANCELLATION_REASONS,
RUN_EVENT_ACTOR_TYPES,
RUN_RETRY_SAFETIES,
RUN_STATUSES,
RunEventPayloadTooLargeError,
RunRepositoryBusyError,
RunRepositoryConstraintError,
RunRepositoryError,
RunRepositoryOperationError,
assertRunRetryPolicyRecord,
} from '@qinglong/runtime-core/run-repository';
import type { DatabaseSync } from 'node:sqlite';
import {
createSqlitePersistencePrimitives,
isSqliteDriverError,
sqliteDriverErrorCode,
sqliteDriverErrorMessage,
sqliteDriverErrorNumber,
type SqliteQueryRow,
} from '../storage/sqlitePersistence';
interface ColumnDefinition {
readonly column: string;
readonly property: string;
}
export type QueryRow = SqliteQueryRow;
const RUN_SQLITE_PERSISTENCE = createSqlitePersistencePrimitives({
invalidRowValue: (property) =>
new RunRepositoryConstraintError(
`Local SQLite Run row has an invalid ${property}`,
),
invalidJson: (property) =>
new RunRepositoryConstraintError(
`Local SQLite Run row has invalid ${property} JSON`,
),
unsupportedRowValue: (property) =>
new RunRepositoryConstraintError(
`Local SQLite Run row has an unsupported ${property}`,
),
duplicateIdentityRows: () =>
new RunRepositoryConstraintError(
'Local SQLite Run repository returned duplicate identity rows',
),
mapDriverError: mapSqliteError,
});
export const RUN_COLUMNS: readonly ColumnDefinition[] = Object.freeze([
{ column: 'id', property: 'id' },
{ column: 'project_id', property: 'projectId' },
{ column: 'task_id', property: 'taskId' },
{ column: 'task_revision', property: 'taskRevision' },
{ column: 'task_name', property: 'taskName' },
{ column: 'task_snapshot_ref', property: 'taskSnapshotRef' },
{ column: 'legacy_cron_id', property: 'legacyCronId' },
{ column: 'parent_run_id', property: 'parentRunId' },
{ column: 'retry_of_run_id', property: 'retryOfRunId' },
{ column: 'trigger_id', property: 'triggerId' },
{ column: 'trigger_type', property: 'triggerType' },
{ column: 'execution_origin', property: 'executionOrigin' },
{ column: 'execution_owner', property: 'executionOwner' },
{ column: 'triggered_by', property: 'triggeredBy' },
{ column: 'request_id', property: 'requestId' },
{ column: 'scheduled_for_ms', property: 'scheduledForMs' },
{ column: 'status', property: 'status' },
{ column: 'version', property: 'version' },
{ column: 'event_sequence', property: 'eventSequence' },
{ column: 'priority', property: 'priority' },
{ column: 'idempotency_key', property: 'idempotencyKey' },
{ column: 'input_ref', property: 'inputRef' },
{ column: 'output_ref', property: 'outputRef' },
{ column: 'created_at_ms', property: 'createdAtMs' },
{ column: 'queued_at_ms', property: 'queuedAtMs' },
{ column: 'started_at_ms', property: 'startedAtMs' },
{ column: 'finished_at_ms', property: 'finishedAtMs' },
{ column: 'cancel_requested_at_ms', property: 'cancelRequestedAtMs' },
{ column: 'cancel_reason', property: 'cancelReason' },
{ column: 'error_code', property: 'errorCode' },
{ column: 'error_summary', property: 'errorSummary' },
]);
export const ATTEMPT_COLUMNS: readonly ColumnDefinition[] = Object.freeze([
{ column: 'id', property: 'id' },
{ column: 'run_id', property: 'runId' },
{ column: 'step_run_id', property: 'stepRunId' },
{ column: 'attempt', property: 'attempt' },
{ column: 'status', property: 'status' },
{ column: 'executor_type', property: 'executorType' },
{ column: 'worker_id', property: 'workerId' },
{ column: 'worker_session_id', property: 'workerSessionId' },
{ column: 'worker_generation', property: 'workerGeneration' },
{ column: 'executor_handle', property: 'executorHandle' },
{ column: 'pid', property: 'pid' },
{ column: 'log_artifact_id', property: 'logArtifactId' },
{ column: 'lease_token', property: 'leaseToken' },
{ column: 'lease_token_digest', property: 'leaseTokenDigest' },
{ column: 'lease_generation', property: 'leaseGeneration' },
{ column: 'lease_version', property: 'leaseVersion' },
{ column: 'lease_expires_at_ms', property: 'leaseExpiresAtMs' },
{ column: 'offer_id', property: 'offerId' },
{ column: 'deadline_at_ms', property: 'deadlineAtMs' },
{ column: 'callback_token_hash', property: 'callbackTokenHash' },
{ column: 'callback_sequence', property: 'callbackSequence' },
{ column: 'created_at_ms', property: 'createdAtMs' },
{ column: 'started_at_ms', property: 'startedAtMs' },
{ column: 'finished_at_ms', property: 'finishedAtMs' },
{ column: 'exit_code', property: 'exitCode' },
{ column: 'error_code', property: 'errorCode' },
{ column: 'error_summary', property: 'errorSummary' },
]);
export const EVENT_COLUMNS: readonly ColumnDefinition[] = Object.freeze([
{ column: 'id', property: 'id' },
{ column: 'run_id', property: 'runId' },
{ column: 'sequence', property: 'sequence' },
{ column: 'type', property: 'type' },
{ column: 'dedupe_key', property: 'dedupeKey' },
{ column: 'actor_type', property: 'actorType' },
{ column: 'actor_id', property: 'actorId' },
{ column: 'attempt_id', property: 'attemptId' },
{ column: 'step_run_id', property: 'stepRunId' },
{ column: 'payload', property: 'payload' },
{ column: 'created_at_ms', property: 'createdAtMs' },
]);
export const RETRY_POLICY_COLUMNS: readonly ColumnDefinition[] = Object.freeze([
{ column: 'run_id', property: 'runId' },
{ column: 'max_attempts', property: 'maxAttempts' },
{ column: 'retry_on_lost', property: 'retryOnLost' },
{ column: 'safety', property: 'safety' },
{ column: 'backoff_base_ms', property: 'backoffBaseMs' },
{ column: 'backoff_max_ms', property: 'backoffMaxMs' },
{ column: 'next_attempt_at_ms', property: 'nextAttemptAtMs' },
{ column: 'version', property: 'version' },
{ column: 'created_at_ms', property: 'createdAtMs' },
{ column: 'updated_at_ms', property: 'updatedAtMs' },
]);
function quoted(identifier: string): string {
return `"${identifier}"`;
}
function selectColumns(columns: readonly ColumnDefinition[]): string {
return columns
.map(({ column, property }) => `${quoted(column)} AS ${quoted(property)}`)
.join(', ');
}
function insertSql(
tableName: string,
columns: readonly ColumnDefinition[],
): string {
return `INSERT INTO ${quoted(tableName)} (${columns
.map(({ column }) => quoted(column))
.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
}
function updateSql(
tableName: string,
columns: readonly ColumnDefinition[],
predicate: string,
): string {
return `UPDATE ${quoted(tableName)} SET ${columns
.slice(1)
.map(({ column }) => `${quoted(column)} = ?`)
.join(', ')} WHERE ${predicate}`;
}
export function writeValues(
record: object,
columns: readonly ColumnDefinition[],
): (string | number | bigint | Uint8Array | null)[] {
const values = record as Record<string, unknown>;
return columns.map(({ property }) => {
const value = values[property];
if (value === undefined || value === null) return null;
if (typeof value === 'boolean') return value ? 1 : 0;
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'bigint' ||
value instanceof Uint8Array
) {
return value;
}
throw new RunRepositoryConstraintError(
`Local SQLite write value ${property} is invalid`,
);
});
}
export function requiredString(row: QueryRow, property: string): string {
return RUN_SQLITE_PERSISTENCE.requiredString(row, property);
}
export function optionalString(
row: QueryRow,
property: string,
): string | undefined {
return RUN_SQLITE_PERSISTENCE.optionalString(row, property);
}
export function requiredInteger(row: QueryRow, property: string): number {
return RUN_SQLITE_PERSISTENCE.requiredInteger(row, property);
}
export function requiredBlob(row: QueryRow, property: string): Buffer {
try {
return RUN_SQLITE_PERSISTENCE.requiredBlob(row, property);
} catch (error) {
if (error instanceof RunRepositoryConstraintError) {
throw new RunRepositoryConstraintError(
`Local SQLite row has an invalid ${property}`,
);
}
throw error;
}
}
export function optionalInteger(
row: QueryRow,
property: string,
): number | undefined {
return RUN_SQLITE_PERSISTENCE.optionalInteger(row, property);
}
export function requiredBoolean(row: QueryRow, property: string): boolean {
return RUN_SQLITE_PERSISTENCE.requiredBoolean(row, property);
}
export function requiredJson(row: QueryRow, property: string): unknown {
return RUN_SQLITE_PERSISTENCE.requiredJson(row, property);
}
export function requiredEnum<T extends string>(
row: QueryRow,
property: string,
allowed: readonly T[],
): T {
return RUN_SQLITE_PERSISTENCE.requiredEnum(row, property, allowed);
}
function assignOptional<T extends object, K extends keyof T>(
record: T,
key: K,
value: T[K] | undefined,
): void {
if (value !== undefined) record[key] = value;
}
export function rowToRun(row: QueryRow): RunRecord {
const run: RunRecord = {
id: requiredString(row, 'id'),
projectId: requiredString(row, 'projectId'),
taskId: requiredString(row, 'taskId'),
taskRevision: requiredString(row, 'taskRevision'),
triggerType: requiredString(row, 'triggerType'),
executionOrigin: requiredEnum(row, 'executionOrigin', EXECUTION_ORIGINS),
executionOwner: requiredEnum(row, 'executionOwner', [
'legacy',
'runtime',
] as const),
status: requiredEnum(row, 'status', RUN_STATUSES),
version: requiredInteger(row, 'version'),
eventSequence: requiredInteger(row, 'eventSequence'),
priority: requiredInteger(row, 'priority'),
createdAtMs: requiredInteger(row, 'createdAtMs'),
};
assignOptional(run, 'taskName', optionalString(row, 'taskName'));
assignOptional(
run,
'taskSnapshotRef',
optionalString(row, 'taskSnapshotRef'),
);
assignOptional(run, 'legacyCronId', optionalInteger(row, 'legacyCronId'));
assignOptional(run, 'parentRunId', optionalString(row, 'parentRunId'));
assignOptional(run, 'retryOfRunId', optionalString(row, 'retryOfRunId'));
assignOptional(run, 'triggerId', optionalString(row, 'triggerId'));
assignOptional(run, 'triggeredBy', optionalString(row, 'triggeredBy'));
assignOptional(run, 'requestId', optionalString(row, 'requestId'));
assignOptional(run, 'scheduledForMs', optionalInteger(row, 'scheduledForMs'));
assignOptional(run, 'idempotencyKey', optionalString(row, 'idempotencyKey'));
assignOptional(run, 'inputRef', optionalString(row, 'inputRef'));
assignOptional(run, 'outputRef', optionalString(row, 'outputRef'));
assignOptional(run, 'queuedAtMs', optionalInteger(row, 'queuedAtMs'));
assignOptional(run, 'startedAtMs', optionalInteger(row, 'startedAtMs'));
assignOptional(run, 'finishedAtMs', optionalInteger(row, 'finishedAtMs'));
assignOptional(
run,
'cancelRequestedAtMs',
optionalInteger(row, 'cancelRequestedAtMs'),
);
if (row.cancelReason !== null && row.cancelReason !== undefined) {
run.cancelReason = requiredEnum(
row,
'cancelReason',
RUN_CANCELLATION_REASONS,
);
}
assignOptional(run, 'errorCode', optionalString(row, 'errorCode'));
assignOptional(run, 'errorSummary', optionalString(row, 'errorSummary'));
return run;
}
export function rowToAttempt(row: QueryRow): RunAttemptRecord {
const attempt: RunAttemptRecord = {
id: requiredString(row, 'id'),
runId: requiredString(row, 'runId'),
attempt: requiredInteger(row, 'attempt'),
status: requiredEnum(row, 'status', RUN_ATTEMPT_STATUSES),
executorType: requiredString(row, 'executorType'),
callbackSequence: requiredInteger(row, 'callbackSequence'),
createdAtMs: requiredInteger(row, 'createdAtMs'),
};
assignOptional(attempt, 'stepRunId', optionalString(row, 'stepRunId'));
assignOptional(attempt, 'workerId', optionalString(row, 'workerId'));
assignOptional(
attempt,
'workerSessionId',
optionalString(row, 'workerSessionId'),
);
assignOptional(
attempt,
'workerGeneration',
optionalInteger(row, 'workerGeneration'),
);
assignOptional(
attempt,
'executorHandle',
optionalString(row, 'executorHandle'),
);
assignOptional(attempt, 'pid', optionalInteger(row, 'pid'));
assignOptional(
attempt,
'logArtifactId',
optionalString(row, 'logArtifactId'),
);
assignOptional(attempt, 'leaseToken', optionalString(row, 'leaseToken'));
assignOptional(
attempt,
'leaseTokenDigest',
optionalString(row, 'leaseTokenDigest'),
);
assignOptional(
attempt,
'leaseGeneration',
optionalInteger(row, 'leaseGeneration'),
);
assignOptional(attempt, 'leaseVersion', optionalInteger(row, 'leaseVersion'));
assignOptional(
attempt,
'leaseExpiresAtMs',
optionalInteger(row, 'leaseExpiresAtMs'),
);
assignOptional(attempt, 'offerId', optionalString(row, 'offerId'));
assignOptional(attempt, 'deadlineAtMs', optionalInteger(row, 'deadlineAtMs'));
assignOptional(
attempt,
'callbackTokenHash',
optionalString(row, 'callbackTokenHash'),
);
assignOptional(attempt, 'startedAtMs', optionalInteger(row, 'startedAtMs'));
assignOptional(attempt, 'finishedAtMs', optionalInteger(row, 'finishedAtMs'));
assignOptional(attempt, 'exitCode', optionalInteger(row, 'exitCode'));
assignOptional(attempt, 'errorCode', optionalString(row, 'errorCode'));
assignOptional(attempt, 'errorSummary', optionalString(row, 'errorSummary'));
return attempt;
}
function normalizePayload(payload: unknown): Readonly<Record<string, unknown>> {
let value = payload;
if (typeof value === 'string') {
try {
value = JSON.parse(value);
} catch (error) {
throw new RunRepositoryConstraintError(
'Local SQLite RunEvent payload is invalid JSON',
error,
);
}
}
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new RunRepositoryConstraintError(
'Local SQLite RunEvent payload is not a JSON object',
);
}
return value as Readonly<Record<string, unknown>>;
}
export function rowToEvent(row: QueryRow): RunEventRecord {
const event: RunEventRecord = {
id: requiredString(row, 'id'),
runId: requiredString(row, 'runId'),
sequence: requiredInteger(row, 'sequence'),
type: requiredString(row, 'type'),
actorType: requiredEnum(row, 'actorType', RUN_EVENT_ACTOR_TYPES),
payload: normalizePayload(row.payload),
createdAtMs: requiredInteger(row, 'createdAtMs'),
};
assignOptional(event, 'dedupeKey', optionalString(row, 'dedupeKey'));
assignOptional(event, 'actorId', optionalString(row, 'actorId'));
assignOptional(event, 'attemptId', optionalString(row, 'attemptId'));
assignOptional(event, 'stepRunId', optionalString(row, 'stepRunId'));
return event;
}
export function rowToRetryPolicy(row: QueryRow): RunRetryPolicyRecord {
const policy: RunRetryPolicyRecord = {
runId: requiredString(row, 'runId'),
maxAttempts: requiredInteger(row, 'maxAttempts'),
retryOnLost: requiredBoolean(row, 'retryOnLost'),
safety: requiredEnum(row, 'safety', RUN_RETRY_SAFETIES),
backoffBaseMs: requiredInteger(row, 'backoffBaseMs'),
backoffMaxMs: requiredInteger(row, 'backoffMaxMs'),
version: requiredInteger(row, 'version'),
createdAtMs: requiredInteger(row, 'createdAtMs'),
updatedAtMs: requiredInteger(row, 'updatedAtMs'),
};
assignOptional(
policy,
'nextAttemptAtMs',
optionalInteger(row, 'nextAttemptAtMs'),
);
assertRunRetryPolicyRecord(policy);
return policy;
}
function sqliteErrorCode(error: unknown): string | undefined {
return sqliteDriverErrorCode(error);
}
function sqliteErrorNumber(error: unknown): number | undefined {
return sqliteDriverErrorNumber(error);
}
export function sqliteErrorMessage(error: unknown): string {
return sqliteDriverErrorMessage(error);
}
export function isSqliteError(error: unknown): boolean {
return isSqliteDriverError(error);
}
export function mapSqliteError(error: unknown): RunRepositoryError {
if (error instanceof RunRepositoryError) return error;
const baseCode = (sqliteErrorNumber(error) ?? 0) & 0xff;
if (baseCode === 5 || baseCode === 6) {
return new RunRepositoryBusyError(error);
}
if (baseCode === 19 || sqliteErrorCode(error) === 'ERR_SQLITE_CONSTRAINT') {
return new RunRepositoryConstraintError(
'Local SQLite Run repository constraint violation',
error,
);
}
return new RunRepositoryOperationError(error);
}
export function assertEventPayloadSize(event: RunEventRecord): string {
let serialized: string | undefined;
try {
serialized = JSON.stringify(event.payload);
} catch (error) {
throw new RunRepositoryConstraintError(
'RunEvent payload is not JSON serializable',
error,
);
}
if (serialized === undefined) {
throw new RunRepositoryConstraintError(
'RunEvent payload is not JSON serializable',
);
}
const bytes = Buffer.byteLength(serialized, 'utf8');
if (bytes > MAX_RUN_EVENT_PAYLOAD_BYTES) {
throw new RunEventPayloadTooLargeError(bytes, MAX_RUN_EVENT_PAYLOAD_BYTES);
}
return serialized;
}
export function queryRows(
client: DatabaseSync,
sql: string,
values: readonly (string | number | bigint | Uint8Array | null)[] = [],
): QueryRow[] {
return RUN_SQLITE_PERSISTENCE.queryRows(client, sql, values);
}
export function singleRow(rows: QueryRow[]): QueryRow | null {
return RUN_SQLITE_PERSISTENCE.singleRow(rows);
}
export const RUN_SELECT = selectColumns(RUN_COLUMNS);
export const ATTEMPT_SELECT = selectColumns(ATTEMPT_COLUMNS);
export const EVENT_SELECT = selectColumns(EVENT_COLUMNS);
export const RETRY_POLICY_SELECT = selectColumns(RETRY_POLICY_COLUMNS);
export const INSERT_RUN_SQL = insertSql('Runs', RUN_COLUMNS);
export const INSERT_ATTEMPT_SQL = insertSql('RunAttempts', ATTEMPT_COLUMNS);
export const INSERT_EVENT_SQL = insertSql('RunEvents', EVENT_COLUMNS);
export const INSERT_RETRY_POLICY_SQL = insertSql(
'RunRetryPolicies',
RETRY_POLICY_COLUMNS,
);
export const UPDATE_RUN_SQL = updateSql(
'Runs',
RUN_COLUMNS,
'"id" = ? AND "version" = ?',
);
export const UPDATE_ATTEMPT_SQL = updateSql(
'RunAttempts',
ATTEMPT_COLUMNS,
'"id" = ? AND "status" = ? AND "callback_sequence" = ?',
);
export const UPDATE_RETRY_POLICY_SQL = updateSql(
'RunRetryPolicies',
RETRY_POLICY_COLUMNS,
'"run_id" = ? AND "version" = ?',
);