import type { DomainCollection, SubjectTimeInput } from "@/worker/objects/common/collection-types"; export interface CollectionCrudSpec { ensureDefaultCollection(db: TDb, subjectId: string, nowMs: number): TCollection; listCollections(db: TDb, subjectId: string, nowMs: number): TCollection[]; collectionById(db: TDb, id: string): TCollection | undefined; collectionByName(db: TDb, name: string): TCollection | undefined; validName(name: string): boolean; deleteCollection(db: TDb, id: string): boolean; record(row: TCollection): TRecord; invalidName(): TError; nameConflict(): TError; collectionNotFound(): TError; emptyDisplayName(): TError; defaultDeleteForbidden(): TError; } export function listCollectionRecords( db: TDb, input: SubjectTimeInput, spec: Pick, "listCollections" | "record">, ): { ok: true; records: TRecord[] } { return { ok: true, records: spec.listCollections(db, input.subjectId, input.nowMs).map(spec.record) }; } export function getCollectionRecord( db: TDb, input: SubjectTimeInput & { id: string }, spec: Pick< CollectionCrudSpec, "ensureDefaultCollection" | "collectionById" | "record" | "collectionNotFound" >, ): { ok: true; record: TRecord } | TError { spec.ensureDefaultCollection(db, input.subjectId, input.nowMs); const row = spec.collectionById(db, input.id); return row ? { ok: true, record: spec.record(row) } : spec.collectionNotFound(); } export function createCollectionRecord< TDb, TCollection extends DomainCollection, TRecord, TError, TInput extends SubjectTimeInput & { name: string; displayName?: string }, >( db: TDb, input: TInput, spec: Pick< CollectionCrudSpec, "validName" | "invalidName" | "ensureDefaultCollection" | "collectionByName" | "nameConflict" | "record" > & { createCollection(db: TDb, input: TInput, displayName: string): TCollection; }, ): { ok: true; record: TRecord } | TError { if (!spec.validName(input.name)) return spec.invalidName(); spec.ensureDefaultCollection(db, input.subjectId, input.nowMs); if (spec.collectionByName(db, input.name)) return spec.nameConflict(); const row = spec.createCollection(db, input, input.displayName ?? input.name); return { ok: true, record: spec.record(row) }; } export function updateCollectionRecord< TDb, TCollection extends DomainCollection, TRecord, TError, TInput extends SubjectTimeInput & { id: string; displayName?: string }, >( db: TDb, input: TInput, spec: Pick< CollectionCrudSpec, "ensureDefaultCollection" | "record" | "collectionNotFound" | "emptyDisplayName" > & { updateCollection(db: TDb, input: TInput, displayName: string | undefined): TCollection | undefined; }, ): { ok: true; record: TRecord } | TError { spec.ensureDefaultCollection(db, input.subjectId, input.nowMs); const displayName = input.displayName?.trim(); if (displayName !== undefined && displayName.length === 0) return spec.emptyDisplayName(); const row = spec.updateCollection(db, input, displayName); return row ? { ok: true, record: spec.record(row) } : spec.collectionNotFound(); } export function deleteCollectionRecord( db: TDb, input: SubjectTimeInput & { id: string }, spec: Pick< CollectionCrudSpec, "ensureDefaultCollection" | "collectionById" | "deleteCollection" | "collectionNotFound" | "defaultDeleteForbidden" >, ): { ok: true } | TError { spec.ensureDefaultCollection(db, input.subjectId, input.nowMs); return deleteExistingCollectionRecord(db, spec.collectionById(db, input.id), spec); } export function deleteCollectionRecordByName( db: TDb, input: SubjectTimeInput & { name: string }, spec: Pick< CollectionCrudSpec, | "ensureDefaultCollection" | "collectionByName" | "deleteCollection" | "collectionNotFound" | "defaultDeleteForbidden" >, ): { ok: true } | TError { spec.ensureDefaultCollection(db, input.subjectId, input.nowMs); return deleteExistingCollectionRecord(db, spec.collectionByName(db, input.name), spec); } function deleteExistingCollectionRecord( db: TDb, collection: TCollection | undefined, spec: Pick< CollectionCrudSpec, "deleteCollection" | "collectionNotFound" | "defaultDeleteForbidden" >, ): { ok: true } | TError { if (!collection) return spec.collectionNotFound(); if (collection.name === "default") return spec.defaultDeleteForbidden(); spec.deleteCollection(db, collection.id); return { ok: true }; }