// "Export my data": every installed module's share of the account, composed into one document. // // Each module that keeps something about the person contributes an export to kit-tenancy's // `tenancy.account` point -- this module's own rows among them -- and every one of them is a section. // A module that fails is a `failed` section and the export is `complete: false`, naming it; a module // that can erase but not export is `not_exported`. Nothing is left out without saying so (joint // contract ยง12). import { AppError, notFound } from "@conusai-kit/core" import { ACCOUNT_EXPORT_FORMAT } from "../contract/constants.js" import type { AccountExport, ExportSection } from "../contract/procedures.js" import type { AccountDeps } from "./ports.js" /** The problem code of a refusal, and whether trying again later may help. Never its detail. */ export function refusalOf(error: unknown): { code: string; retryable: boolean } { if (error instanceof AppError) return { code: error.code, retryable: error.status >= 500 || error.status === 429 } return { code: "unavailable", retryable: true } } export async function exportAccount(deps: AccountDeps, userId: string): Promise { if ((await deps.store.emailOf(userId)) === null) throw notFound("no such user") const sections = await Promise.all( deps.contributions.map(async (contribution): Promise<[string, ExportSection]> => { if (!contribution.export) return [contribution.key, { status: "not_exported" }] try { const data = await contribution.export({ subject: userId, container: deps.container, ...(deps.signal ? { signal: deps.signal } : {}), }) return [contribution.key, { status: "ok", data }] } catch (error) { return [contribution.key, { status: "failed", ...refusalOf(error) }] } }), ) const failed = sections.filter(([, section]) => section.status === "failed").map(([key]) => key) return { format: ACCOUNT_EXPORT_FORMAT, exportedAt: new Date(deps.now()).toISOString(), subject: userId, complete: sections.every(([, section]) => section.status === "ok"), failed, sections: Object.fromEntries(sections), } }