File
Blob: src/worker/do/repo/db/dal/packCatalog.ts
| 1 | import type { DrizzleSqliteDODatabase } from "drizzle-orm/durable-sqlite"; |
| 2 | import type { PackCatalogRow } from "../schema"; |
| 3 | |
| 4 | import { desc, eq, inArray, sql } from "drizzle-orm"; |
| 5 | import { packCatalog } from "../schema"; |
| 6 | import { SAFE_ROWS_1COL } from "./shared"; |
| 7 | |
| 8 | export async function getPackCatalogCount(db: DrizzleSqliteDODatabase): Promise<number> { |
| 9 | return await db.$count(packCatalog); |
| 10 | } |
| 11 | |
| 12 | /** Count only active (non-superseded) packs — used by idle cleanup to determine repo emptiness. */ |
| 13 | export async function getActivePackCatalogCount(db: DrizzleSqliteDODatabase): Promise<number> { |
| 14 | const rows = await db |
| 15 | .select({ count: sql<number>`count(*)` }) |
| 16 | .from(packCatalog) |
| 17 | .where(eq(packCatalog.state, "active")); |
| 18 | return rows[0]?.count ?? 0; |
| 19 | } |
| 20 | |
| 21 | export async function listPackCatalog(db: DrizzleSqliteDODatabase): Promise<PackCatalogRow[]> { |
| 22 | return await db |
| 23 | .select() |
| 24 | .from(packCatalog) |
| 25 | .orderBy(desc(packCatalog.seqHi), desc(packCatalog.tier)); |
| 26 | } |
| 27 | |
| 28 | export async function listActivePackCatalog( |
| 29 | db: DrizzleSqliteDODatabase |
| 30 | ): Promise<PackCatalogRow[]> { |
| 31 | return await db |
| 32 | .select() |
| 33 | .from(packCatalog) |
| 34 | .where(eq(packCatalog.state, "active")) |
| 35 | .orderBy(desc(packCatalog.seqHi), desc(packCatalog.tier)); |
| 36 | } |
| 37 | |
| 38 | export async function getPackCatalogRow( |
| 39 | db: DrizzleSqliteDODatabase, |
| 40 | packKey: string |
| 41 | ): Promise<PackCatalogRow | undefined> { |
| 42 | const rows = await db.select().from(packCatalog).where(eq(packCatalog.packKey, packKey)).limit(1); |
| 43 | return rows[0]; |
| 44 | } |
| 45 | |
| 46 | export async function getPackCatalogSeqMax(db: DrizzleSqliteDODatabase): Promise<number> { |
| 47 | const rows = await db |
| 48 | .select({ |
| 49 | maxSeqHi: sql<number>`coalesce(max(${packCatalog.seqHi}), 0)`, |
| 50 | }) |
| 51 | .from(packCatalog); |
| 52 | return rows[0]?.maxSeqHi || 0; |
| 53 | } |
| 54 | |
| 55 | export async function upsertPackCatalogRow( |
| 56 | db: DrizzleSqliteDODatabase, |
| 57 | row: PackCatalogRow |
| 58 | ): Promise<void> { |
| 59 | await db |
| 60 | .insert(packCatalog) |
| 61 | .values(row) |
| 62 | .onConflictDoUpdate({ |
| 63 | target: packCatalog.packKey, |
| 64 | set: { |
| 65 | kind: row.kind, |
| 66 | state: row.state, |
| 67 | tier: row.tier, |
| 68 | seqLo: row.seqLo, |
| 69 | seqHi: row.seqHi, |
| 70 | objectCount: row.objectCount, |
| 71 | packBytes: row.packBytes, |
| 72 | idxBytes: row.idxBytes, |
| 73 | createdAt: row.createdAt, |
| 74 | supersededBy: row.supersededBy, |
| 75 | }, |
| 76 | }); |
| 77 | } |
| 78 | |
| 79 | export async function supersedePackCatalogRows( |
| 80 | db: DrizzleSqliteDODatabase, |
| 81 | packKeys: string[], |
| 82 | supersededBy: string | null = null |
| 83 | ): Promise<void> { |
| 84 | if (!packKeys.length) return; |
| 85 | for (let i = 0; i < packKeys.length; i += SAFE_ROWS_1COL) { |
| 86 | const batch = packKeys.slice(i, i + SAFE_ROWS_1COL); |
| 87 | await db |
| 88 | .update(packCatalog) |
| 89 | .set({ state: "superseded", supersededBy }) |
| 90 | .where(inArray(packCatalog.packKey, batch)); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | export async function deletePackCatalogRows( |
| 95 | db: DrizzleSqliteDODatabase, |
| 96 | packKeys: string[] |
| 97 | ): Promise<void> { |
| 98 | if (!packKeys.length) return; |
| 99 | for (let i = 0; i < packKeys.length; i += SAFE_ROWS_1COL) { |
| 100 | const batch = packKeys.slice(i, i + SAFE_ROWS_1COL); |
| 101 | await db.delete(packCatalog).where(inArray(packCatalog.packKey, batch)); |
| 102 | } |
| 103 | } |