21efc4cdc4
新增 ClientOperation 状态、身份和 stale-result 规则 让认证 refresh 与首页创建记录统一 operation phase 和 scope 为 dev-stack 与 AGC Vite marker 增加工作树和实例身份门禁 补充稳定版切换主规范、里程碑计划和开发运维踩坑文档
106 lines
2.4 KiB
TypeScript
106 lines
2.4 KiB
TypeScript
export type ClientOperationPhase =
|
|
| 'idle'
|
|
| 'network'
|
|
| 'runner'
|
|
| 'project'
|
|
| 'success'
|
|
| 'retryable-failure'
|
|
| 'unknown';
|
|
|
|
export type ClientOperationScope = {
|
|
projectPath?: string;
|
|
apiBaseUrl?: string;
|
|
sessionGeneration?: number;
|
|
};
|
|
|
|
export type ClientOperation<
|
|
TKind extends string = string,
|
|
TPayload = unknown,
|
|
> = {
|
|
operationId: string;
|
|
requestId: string;
|
|
kind: TKind;
|
|
phase: ClientOperationPhase;
|
|
startedAt: number;
|
|
deadlineAt: number | null;
|
|
scope: ClientOperationScope;
|
|
payload: TPayload;
|
|
cancellable: boolean;
|
|
cancelled: boolean;
|
|
};
|
|
|
|
let operationSequence = 0;
|
|
|
|
function randomOperationPart() {
|
|
try {
|
|
return crypto.randomUUID();
|
|
} catch {
|
|
operationSequence += 1;
|
|
return `${Date.now().toString(36)}-${operationSequence.toString(36)}`;
|
|
}
|
|
}
|
|
|
|
export function createClientOperation<TKind extends string, TPayload>(
|
|
kind: TKind,
|
|
payload: TPayload,
|
|
options: {
|
|
scope?: ClientOperationScope;
|
|
deadlineMs?: number | null;
|
|
cancellable?: boolean;
|
|
} = {},
|
|
): ClientOperation<TKind, TPayload> {
|
|
const startedAt = Date.now();
|
|
const deadlineMs = options.deadlineMs ?? null;
|
|
return {
|
|
operationId: `op-${randomOperationPart()}`,
|
|
requestId: `req-${randomOperationPart()}`,
|
|
kind,
|
|
phase: 'idle',
|
|
startedAt,
|
|
deadlineAt:
|
|
deadlineMs === null ? null : startedAt + Math.max(1, deadlineMs),
|
|
scope: { ...options.scope },
|
|
payload,
|
|
cancellable: options.cancellable ?? true,
|
|
cancelled: false,
|
|
};
|
|
}
|
|
|
|
export function transitionClientOperation<TKind extends string, TPayload>(
|
|
operation: ClientOperation<TKind, TPayload>,
|
|
phase: ClientOperationPhase,
|
|
patch: Partial<
|
|
Pick<ClientOperation<TKind, TPayload>, 'scope' | 'payload'>
|
|
> = {},
|
|
) {
|
|
return {
|
|
...operation,
|
|
...patch,
|
|
scope: patch.scope
|
|
? { ...operation.scope, ...patch.scope }
|
|
: operation.scope,
|
|
phase,
|
|
};
|
|
}
|
|
|
|
export function cancelClientOperation<TKind extends string, TPayload>(
|
|
operation: ClientOperation<TKind, TPayload>,
|
|
) {
|
|
return { ...operation, phase: 'unknown' as const, cancelled: true };
|
|
}
|
|
|
|
export function isCurrentClientOperation(
|
|
operation: ClientOperation | null | undefined,
|
|
operationId: string,
|
|
) {
|
|
return Boolean(
|
|
operation && operation.operationId === operationId && !operation.cancelled,
|
|
);
|
|
}
|
|
|
|
export function clientOperationCanRetry(
|
|
operation: ClientOperation | null | undefined,
|
|
) {
|
|
return operation?.phase === 'retryable-failure';
|
|
}
|