109 lines
4.4 KiB
TypeScript
109 lines
4.4 KiB
TypeScript
type HwpodOperationLedgerRecord = {
|
|
planId: string;
|
|
nodeId: string;
|
|
operation: Record<string, unknown>;
|
|
status: "started" | "accepted" | "completed";
|
|
result: any | null;
|
|
updatedAt: string;
|
|
};
|
|
|
|
export function createHwpodOperationLedger(options: any = {}) {
|
|
const runtimeStore = options.runtimeStore;
|
|
const now = options.now ?? (() => new Date().toISOString());
|
|
const retentionMs = positiveInteger(options.retentionMs, 0);
|
|
if (!runtimeStore || typeof runtimeStore.query !== "function") return createMemoryHwpodOperationLedger({ now, retentionMs });
|
|
|
|
async function get(planId: string) {
|
|
const result = await runtimeStore.query(
|
|
"SELECT operation_json FROM hardware_operations WHERE id = $1 AND operation_type = 'hwpod.node-ops' LIMIT 1",
|
|
[planId]
|
|
);
|
|
return result.rows?.[0] ? parseRecord(result.rows[0].operation_json) : null;
|
|
}
|
|
|
|
async function write(record: HwpodOperationLedgerRecord) {
|
|
await cleanup();
|
|
await runtimeStore.query(
|
|
`INSERT INTO hardware_operations (id, project_id, requested_by, operation_type, operation_json, status, requested_at, updated_at)
|
|
VALUES ($1, NULL, 'hwlab-cloud-api', 'hwpod.node-ops', $2, $3, $4, $4)
|
|
ON CONFLICT (id) DO UPDATE SET operation_json = EXCLUDED.operation_json, status = EXCLUDED.status, updated_at = EXCLUDED.updated_at`,
|
|
[record.planId, JSON.stringify(record), record.status, record.updatedAt]
|
|
);
|
|
return record;
|
|
}
|
|
|
|
async function start(input: Omit<HwpodOperationLedgerRecord, "status" | "result" | "updatedAt">) {
|
|
const existing = await get(input.planId);
|
|
return existing ?? write({ ...input, status: "started", result: null, updatedAt: now() });
|
|
}
|
|
|
|
async function accept(planId: string) {
|
|
const existing = await get(planId);
|
|
return existing ? write({ ...existing, status: "accepted", updatedAt: now() }) : null;
|
|
}
|
|
|
|
async function complete(planId: string, result: any) {
|
|
const existing = await get(planId);
|
|
return existing ? write({ ...existing, status: "completed", result, updatedAt: now() }) : null;
|
|
}
|
|
|
|
async function cleanup() {
|
|
if (!retentionMs) return;
|
|
await runtimeStore.query(
|
|
"DELETE FROM hardware_operations WHERE operation_type = 'hwpod.node-ops' AND updated_at < $1",
|
|
[new Date(Date.now() - retentionMs).toISOString()]
|
|
);
|
|
}
|
|
|
|
return { get, start, accept, complete, retentionMs };
|
|
}
|
|
|
|
export function createMemoryHwpodOperationLedger(options: any = {}) {
|
|
const now = options.now ?? (() => new Date().toISOString());
|
|
const retentionMs = positiveInteger(options.retentionMs, 0);
|
|
const records = options.records ?? new Map<string, HwpodOperationLedgerRecord>();
|
|
function cleanup() {
|
|
if (!retentionMs) return;
|
|
const cutoff = Date.now() - retentionMs;
|
|
for (const [planId, record] of records) if (Date.parse(record.updatedAt) < cutoff) records.delete(planId);
|
|
}
|
|
async function get(planId: string) { cleanup(); return records.get(planId) ?? null; }
|
|
async function start(input: Omit<HwpodOperationLedgerRecord, "status" | "result" | "updatedAt">) {
|
|
cleanup();
|
|
const existing = records.get(input.planId);
|
|
if (existing) return existing;
|
|
const record = { ...input, status: "started", result: null, updatedAt: now() } satisfies HwpodOperationLedgerRecord;
|
|
records.set(input.planId, record);
|
|
return record;
|
|
}
|
|
async function accept(planId: string) {
|
|
const existing = records.get(planId);
|
|
if (!existing) return null;
|
|
const record = { ...existing, status: "accepted", updatedAt: now() } satisfies HwpodOperationLedgerRecord;
|
|
records.set(planId, record);
|
|
return record;
|
|
}
|
|
async function complete(planId: string, result: any) {
|
|
const existing = records.get(planId);
|
|
if (!existing) return null;
|
|
const record = { ...existing, status: "completed", result, updatedAt: now() } satisfies HwpodOperationLedgerRecord;
|
|
records.set(planId, record);
|
|
return record;
|
|
}
|
|
return { get, start, accept, complete, retentionMs, records };
|
|
}
|
|
|
|
function parseRecord(value: unknown): HwpodOperationLedgerRecord | null {
|
|
try {
|
|
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as HwpodOperationLedgerRecord : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function positiveInteger(value: unknown, fallback: number) {
|
|
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
}
|