src/features/reviews/review-store.ts
Linked repository file from this build.
import type { ReviewIdentity } from "./review-key";
import { initialPolicy } from "@/programs/snap-negative/policy";
import { normalizeReviewAnswers } from "@/programs/snap-negative/field-catalog";
import { LEGACY_CALC_VERSION, LEGACY_CHECK_VERSION } from "@/programs/snap-negative/rule-versions";
import type { SnapReview } from "@/programs/snap-negative/types";
import type { ActorRef } from "./types";
export interface StoredReview {
review: SnapReview;
version: number;
requestId: string;
actor: ActorRef;
savedAt: string;
}
export interface ReviewStore {
load(reviewKey: string): Promise<StoredReview | undefined>;
findBusiness(identity: ReviewIdentity): Promise<StoredReview | undefined>;
findRequest(requestId: string): Promise<StoredReview | undefined>;
/** Pass `previous` when the caller already loaded the current version. */
append(value: StoredReview, previous?: SnapReview): Promise<void>;
list(): Promise<StoredReview[]>;
}
/** Old snapshots bind to fixed legacy IDs, never to the newest deployed rules. */
export function bindStoredRules(stored: StoredReview | undefined): StoredReview | undefined {
if (!stored) return stored;
return {
...stored,
review: bindReviewRules(stored.review),
};
}
export function bindReviewRules(review: SnapReview): SnapReview {
return {
...review,
policyId: review.policyId ?? initialPolicy.id,
policy:
review.policy ??
(review.policyId && review.policyId !== initialPolicy.id ? undefined : initialPolicy),
facts: { ...review.facts, answers: normalizeReviewAnswers(review.facts.answers) },
calcVersion: review.calcVersion ?? LEGACY_CALC_VERSION,
checkVersion: review.checkVersion ?? LEGACY_CHECK_VERSION,
};
}
/** Classify storage failures at the adapter boundary, separate from command rejection. */
export class ReviewStoreUnavailableError extends Error {
constructor(cause: unknown) {
super("Review storage is unavailable. Please retry.", { cause });
}
}
export function protectReviewStore(store: ReviewStore): ReviewStore {
async function run<T>(operation: () => Promise<T>): Promise<T> {
try {
return await operation();
} catch (cause) {
throw new ReviewStoreUnavailableError(cause);
}
}
return {
load: (number) => run(async () => bindStoredRules(await store.load(number))),
findBusiness: (identity) =>
run(async () => bindStoredRules(await store.findBusiness(identity))),
findRequest: (id) => run(async () => bindStoredRules(await store.findRequest(id))),
append: (value, previous) => run(() => store.append(value, previous)),
list: () => run(async () => (await store.list()).map((stored) => bindStoredRules(stored)!)),
};
}