Files
fusion/plugins/fusion-plugin-reports/src/runs-store.ts
Fusion 8089953710 feat(FN-3860): add cadence resolution seam (+7 more)
Commits merged:
- feat(FN-3860): complete Step 8 — documentation and delivery
- test(FN-3860): complete Step 7 — add scaffold seam coverage
- feat(FN-3860): complete Step 6 — re-export scaffold seams
- feat(FN-3860): complete Step 5 — add pipeline orchestrator seam
- feat(FN-3860): complete Step 4 — add in-memory runs store seam
- feat(FN-3860): complete Step 3 — add aggregation seam
- feat(FN-3860): complete Step 2 — add cadence resolution seam
- feat(FN-3846): short-circuit phantom merges for already-landed branches

Files changed:
.changeset/fn-3860-reports-scaffold-files.md       |  5 ++
 plugins/fusion-plugin-reports/README.md            |  9 +++
 .../src/__tests__/scaffold.test.ts                 | 60 +++++++++++++++++++
 plugins/fusion-plugin-reports/src/aggregation.ts   | 23 ++++++++
 plugins/fusion-plugin-reports/src/cadence.ts       | 23 ++++++++
 plugins/fusion-plugin-reports/src/index.ts         |  4 ++
 plugins/fusion-plugin-reports/src/pipeline.ts      | 58 ++++++++++++++++++
 plugins/fusion-plugin-reports/src/runs-store.ts    | 69 ++++++++++++++++++++++
 8 files changed, 251 insertions(+)

Fusion-Task-Id: FN-3860
2026-05-11 08:02:53 -07:00

70 lines
2.0 KiB
TypeScript

/**
* INTERIM IN-MEMORY STORE — replaced by FN-3784's persistent storage.
* The `ReportRunRecord` shape is intended to be a strict subset of FN-3784's eventual schema.
*/
import type { ReportsCadence } from "./cadence.js";
export type ReportRunStatus = "queued" | "running" | "review" | "approved" | "published" | "failed";
export interface ReportRunRecord {
id: string;
cadence: ReportsCadence;
status: ReportRunStatus;
createdAt: string;
updatedAt: string;
error?: string;
reportId?: string;
}
export interface ReportsRunsStore {
create(record: ReportRunRecord): Promise<ReportRunRecord>;
update(
id: string,
patch: Partial<Omit<ReportRunRecord, "id" | "createdAt">>,
): Promise<ReportRunRecord | undefined>;
get(id: string): Promise<ReportRunRecord | undefined>;
list(limit?: number): Promise<ReportRunRecord[]>;
}
function cloneRecord(record: ReportRunRecord): ReportRunRecord {
return { ...record };
}
export function createInMemoryReportsRunsStore(seed: ReportRunRecord[] = []): ReportsRunsStore {
const records = new Map<string, ReportRunRecord>(seed.map((record) => [record.id, cloneRecord(record)]));
return {
async create(record) {
const stored = cloneRecord(record);
records.set(stored.id, stored);
return cloneRecord(stored);
},
async update(id, patch) {
const current = records.get(id);
if (!current) return undefined;
const next: ReportRunRecord = {
...current,
...patch,
updatedAt: patch.updatedAt ?? new Date().toISOString(),
};
records.set(id, next);
return cloneRecord(next);
},
async get(id) {
const record = records.get(id);
return record ? cloneRecord(record) : undefined;
},
async list(limit = 50) {
const clampedLimit = Math.max(0, limit);
return Array.from(records.values())
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
.slice(0, clampedLimit)
.map((record) => cloneRecord(record));
},
};
}