import type { Db } from "../db.ts"; import { inTransaction } from "../db.ts"; import { createArtifact, getArtifact, getArtifactByAlias, getArtifactTrash, linkArtifacts, listArtifactTrash, purgeDueArtifacts, queryArtifactEvents, queryArtifacts, restoreArtifact, trashArtifact, unlinkArtifacts, updateArtifactContent, updateExtra, updateStatus, } from "../ops.ts"; import type { Artifact, ArtifactEdge, ArtifactGraphOptions, ArtifactLink, ArtifactQuery, CreateArtifactInput, RelationshipQuery, UpdateArtifactInput, } from "./artifact.ts"; import type { ArtifactEventContext, ArtifactEventPage, ArtifactEventQuery } from "./artifact-event.ts"; import type { ArtifactEventReader } from "./artifact-event-reader.ts"; import type { ArtifactTrashRecord } from "./artifact-trash.ts"; import type { ArtifactTrashStore } from "./artifact-trash-store.ts"; import type { AtomicArtifactStore } from "./atomic-artifact-store.ts"; export class SQLiteArtifactStore implements AtomicArtifactStore, ArtifactTrashStore, ArtifactEventReader { constructor(private readonly db: Db) {} atomic(operation: () => T): T { return inTransaction(this.db, operation); } create(input: CreateArtifactInput, context?: ArtifactEventContext): Artifact { return createArtifact(this.db, input, context); } get(id: string, options?: ArtifactGraphOptions): Artifact | null { return getArtifact(this.db, id, options); } getByAlias(alias: string): Artifact | null { return getArtifactByAlias(this.db, alias); } query(filter: ArtifactQuery): Artifact[] { return queryArtifacts(this.db, filter); } link(link: ArtifactLink, context?: ArtifactEventContext): void { linkArtifacts(this.db, link.from, link.relation, link.to, context); } unlink(link: ArtifactLink, context?: ArtifactEventContext): boolean { return unlinkArtifacts(this.db, link.from, link.relation, link.to, context); } setStatus(id: string, status: string, context?: ArtifactEventContext): Artifact | null { return updateStatus(this.db, id, status, context); } setExtra(id: string, extra: Record, context?: ArtifactEventContext): Artifact | null { return updateExtra(this.db, id, extra, context); } updateContent(id: string, input: UpdateArtifactInput, context?: ArtifactEventContext): Artifact | null { return updateArtifactContent(this.db, id, input, context); } events(query: ArtifactEventQuery): ArtifactEventPage { return queryArtifactEvents(this.db, query); } trash(id: string, options?: { reason?: string; context?: ArtifactEventContext }): ArtifactTrashRecord { return trashArtifact(this.db, id, { reason: options?.reason, context: options?.context }); } restore(id: string, context?: ArtifactEventContext): { restored: boolean } { return restoreArtifact(this.db, id, context); } trashStatus(id: string): ArtifactTrashRecord | null { return getArtifactTrash(this.db, id); } listTrash(): ArtifactTrashRecord[] { return listArtifactTrash(this.db); } purgeDueTrash(): number { return purgeDueArtifacts(this.db); } relationships(filter: RelationshipQuery = {}): ArtifactEdge[] { if (filter.artifactIds?.length === 0) return []; const conditions: string[] = []; const parameters: unknown[] = []; if (filter.kind) { conditions.push("source.kind = ? AND target.kind = ?"); parameters.push(filter.kind, filter.kind); } if (filter.artifactIds) { const placeholders = filter.artifactIds.map(() => "?").join(", "); conditions.push(`(edges.from_id IN (${placeholders}) OR edges.to_id IN (${placeholders}))`); parameters.push(...filter.artifactIds, ...filter.artifactIds); } const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; let limit = ""; if (filter.limit !== undefined) { if (!Number.isInteger(filter.limit) || filter.limit < 1) throw new Error("relationship limit must be a positive integer"); limit = "LIMIT ?"; parameters.push(filter.limit); } return this.db .prepare(` SELECT edges.from_id AS "from", edges.relation, edges.to_id AS "to" FROM edges JOIN artifacts AS source ON source.id = edges.from_id JOIN artifacts AS target ON target.id = edges.to_id ${where} ORDER BY edges.rowid ${limit} `) .all(...parameters) as ArtifactEdge[]; } }