import { LONGFORM_SNAPSHOT_SECTION_ID } from "./personal-report-longform-snapshot"; import type { PersonalReportAgentOutput } from "@/mastra/personal-report"; export type PersonalReportSectionPayload = PersonalReportAgentOutput["thematicNarrative"][number]; export type PersonalReportSectionStatus = "pending" | "ready" | "blocked"; export type PersonalReportSectionRecord = Readonly<{ userId: string; requestId: string; sectionId: string; payload: PersonalReportSectionPayload | null; status: PersonalReportSectionStatus; attemptCount: number; maxAttempts: number; lastErrorCode: string | null; createdAt: string; updatedAt: string; }>; type SectionIdentity = Readonly<{ userId: string; requestId: string; sectionId: string }>; export type PersonalReportSectionService = Readonly<{ ensure(input: SectionIdentity & { maxAttempts: number }): Promise; list(userId: string, requestId: string): Promise; start(input: SectionIdentity): Promise; complete(input: SectionIdentity & { payload: PersonalReportSectionPayload }): Promise; block(input: SectionIdentity & { errorCode: string }): Promise; }>; export type PersonalReportSectionQueryResult = Readonly<{ data: unknown; error: Readonly<{ message: string; code?: string }> | null; }>; type QueryBuilder = PromiseLike & { select(columns: string): QueryBuilder; eq(column: string, value: unknown): QueryBuilder; order(column: string, options?: Readonly<{ ascending?: boolean }>): QueryBuilder; maybeSingle(): PromiseLike; }; type DataClient = { from(table: string): QueryBuilder; rpc(functionName: string, args?: Readonly>): PromiseLike; }; const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; const sectionIdPattern = /^[a-z][a-z0-9_-]{0,95}$/; const errorCodePattern = /^[a-z][a-z0-9_]{0,63}$/; const columns = [ "user_id", "request_id", "section_id", "payload", "status", "attempt_count", "max_attempts", "last_error_code", "created_at", "updated_at", ].join(","); type DbRow = Record; function requireUuid(value: string, field: string): void { if (!uuidPattern.test(value)) throw new Error(`${field} is invalid`); } function requireSectionId(value: string): void { if (!sectionIdPattern.test(value)) throw new Error("sectionId is invalid"); } function row(value: unknown): PersonalReportSectionRecord { if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("personal report section row is invalid"); const source = value as DbRow; const userId = String(source.user_id); const requestId = String(source.request_id); const sectionId = String(source.section_id); requireUuid(userId, "userId"); requireUuid(requestId, "requestId"); requireSectionId(sectionId); const status = source.status; if (status !== "pending" && status !== "ready" && status !== "blocked") throw new Error("section status is invalid"); const payload = source.payload === null || source.payload === undefined ? null : source.payload as PersonalReportSectionPayload; if (status === "ready" && payload === null) throw new Error("ready section payload is missing"); if (status !== "ready" && payload !== null) throw new Error("non-ready section payload is unexpected"); const attemptCount = Number(source.attempt_count); const maxAttempts = Number(source.max_attempts); if (!Number.isInteger(attemptCount) || !Number.isInteger(maxAttempts) || attemptCount < 0 || maxAttempts < 1 || attemptCount > maxAttempts) { throw new Error("section attempt budget is invalid"); } const createdAt = String(source.created_at); const updatedAt = String(source.updated_at); if (!Number.isFinite(Date.parse(createdAt)) || !Number.isFinite(Date.parse(updatedAt))) throw new Error("section timestamp is invalid"); return { userId, requestId, sectionId, payload, status, attemptCount, maxAttempts, lastErrorCode: source.last_error_code === null || source.last_error_code === undefined ? null : String(source.last_error_code), createdAt, updatedAt, }; } function first(data: unknown): unknown { return Array.isArray(data) ? data[0] ?? null : data; } function requireData(result: PersonalReportSectionQueryResult): unknown { if (result.error) throw new Error(result.error.message); return result.data; } export function createPersonalReportSectionService(client: DataClient): PersonalReportSectionService { return { async ensure(input) { requireUuid(input.userId, "userId"); requireUuid(input.requestId, "requestId"); requireSectionId(input.sectionId); if (!Number.isInteger(input.maxAttempts) || input.maxAttempts < 1 || input.maxAttempts > 10) throw new Error("maxAttempts is invalid"); const result = await client.rpc("ensure_personal_report_section", { p_user_id: input.userId, p_request_id: input.requestId, p_section_id: input.sectionId, p_max_attempts: input.maxAttempts, }); const value = requireData(result); const parsed = row(first(value)); if (!parsed) throw new Error("section ensure returned no row"); return parsed; }, async list(userId, requestId) { requireUuid(userId, "userId"); requireUuid(requestId, "requestId"); const result = await client.from("personal_report_sections").select(columns).eq("user_id", userId).eq("request_id", requestId).order("section_id", { ascending: true }); const value = requireData(result); if (!Array.isArray(value)) throw new Error("section list returned invalid data"); return value.filter(value => (value as DbRow)?.section_id !== LONGFORM_SNAPSHOT_SECTION_ID).map(row); }, async start(input) { const result = await client.rpc("start_personal_report_section", { p_user_id: input.userId, p_request_id: input.requestId, p_section_id: input.sectionId }); const value = requireData(result); const parsed = first(value); return parsed === null ? null : row(parsed); }, async complete(input) { const result = await client.rpc("complete_personal_report_section", { p_user_id: input.userId, p_request_id: input.requestId, p_section_id: input.sectionId, p_payload: input.payload }); const value = requireData(result); const parsed = first(value); return parsed === null ? null : row(parsed); }, async block(input) { if (!errorCodePattern.test(input.errorCode)) throw new Error("errorCode is invalid"); const result = await client.rpc("block_personal_report_section", { p_user_id: input.userId, p_request_id: input.requestId, p_section_id: input.sectionId, p_error_code: input.errorCode }); const value = requireData(result); const parsed = first(value); return parsed === null ? null : row(parsed); }, }; }