70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
/**
|
||
* 执行记录(架构设计 4.2:执行模块)。
|
||
*
|
||
* 平台侧的权威记录:executionId、能力、参数摘要、状态、回执、幂等键。
|
||
* A3.7 落库前的内存实现;幂等索引与参数摘要的语义不随存储变化。
|
||
*/
|
||
import { createHash } from 'node:crypto';
|
||
|
||
import type { ExecutionReceipt } from '@aps/contracts';
|
||
|
||
export interface ExecutionRecord {
|
||
executionId: string;
|
||
capabilityId: string;
|
||
providerId: string;
|
||
sessionId: string;
|
||
projectId: string | null;
|
||
subjectUserId: string;
|
||
/** 参数摘要:只存摘要不存明文(架构设计 4.1「凭据不进日志」同理) */
|
||
paramsDigest: string;
|
||
idempotencyKey?: string;
|
||
status: ExecutionReceipt['status'];
|
||
receipt: ExecutionReceipt;
|
||
createdAt: number;
|
||
updatedAt: number;
|
||
}
|
||
|
||
export function digestParams(params: unknown): string {
|
||
const canonical = JSON.stringify(params ?? null, Object.keys((params ?? {}) as Record<string, unknown>).sort());
|
||
return `sha256:${createHash('sha256').update(canonical).digest('hex').slice(0, 16)}`;
|
||
}
|
||
|
||
export class ExecutionStore {
|
||
private readonly records = new Map<string, ExecutionRecord>();
|
||
private readonly byIdempotencyKey = new Map<string, string>();
|
||
|
||
create(input: Omit<ExecutionRecord, 'status' | 'receipt' | 'createdAt' | 'updatedAt'> & { receipt: ExecutionReceipt }): ExecutionRecord {
|
||
const now = Date.now();
|
||
const record: ExecutionRecord = {
|
||
...input,
|
||
status: input.receipt.status,
|
||
createdAt: now,
|
||
updatedAt: now,
|
||
};
|
||
this.records.set(record.executionId, record);
|
||
if (record.idempotencyKey) this.byIdempotencyKey.set(record.idempotencyKey, record.executionId);
|
||
return record;
|
||
}
|
||
|
||
complete(executionId: string, receipt: ExecutionReceipt): ExecutionRecord | undefined {
|
||
const record = this.records.get(executionId);
|
||
if (!record) return undefined;
|
||
record.receipt = receipt;
|
||
record.status = receipt.status;
|
||
record.updatedAt = Date.now();
|
||
return record;
|
||
}
|
||
|
||
get(executionId: string): ExecutionRecord | undefined {
|
||
return this.records.get(executionId);
|
||
}
|
||
|
||
findByIdempotencyKey(key: string): ExecutionRecord | undefined {
|
||
const executionId = this.byIdempotencyKey.get(key);
|
||
return executionId ? this.records.get(executionId) : undefined;
|
||
}
|
||
|
||
list(limit = 50): ExecutionRecord[] {
|
||
return [...this.records.values()].sort((a, b) => b.createdAt - a.createdAt).slice(0, limit);
|
||
}
|
||
} |