deploy-history.ts73 lines · main
1import { desc, eq } from 'drizzle-orm';
2
3import { newId } from '@briven/shared';
4
5import { getDb } from '../db/client.js';
6import { deployHistory, type DeployHistoryEntry } from '../db/schema.js';
7import { log } from '../lib/logger.js';
8
9/**
10 * Records one row in deploy_history per api boot. Best-effort: a failed
11 * insert is logged but never bubbles up — the api still serves requests
12 * if the audit-trail table is unreachable. Returns the inserted row id
13 * (or null on failure) so tests / callers can assert it ran.
14 */
15export async function recordDeploy(args: {
16 service: string;
17 buildSha: string;
18 buildAt: string | null;
19 env: string;
20}): Promise<string | null> {
21 // "dev" is the explicit "we don't have build identity" sentinel from
22 // health.ts. Recording it would pollute the deploy timeline with noise
23 // on every local `bun dev` boot — skip the row in that case.
24 if (args.buildSha === 'dev') {
25 return null;
26 }
27 try {
28 const db = getDb();
29 const id = newId('dh');
30 await db.insert(deployHistory).values({
31 id,
32 service: args.service,
33 buildSha: args.buildSha,
34 buildAt: args.buildAt,
35 env: args.env,
36 });
37 log.info('deploy_history_recorded', {
38 service: args.service,
39 buildSha: args.buildSha.slice(0, 12),
40 env: args.env,
41 });
42 return id;
43 } catch (err) {
44 log.warn('deploy_history_insert_failed', {
45 service: args.service,
46 buildSha: args.buildSha.slice(0, 12),
47 message: err instanceof Error ? err.message : String(err),
48 });
49 return null;
50 }
51}
52
53/**
54 * Most-recent N deploys for the admin "Deploys" widget. Optionally
55 * filtered to one service (api / realtime / runtime) — when omitted we
56 * return the merged stream so the operator sees the actual rollout
57 * sequence across services.
58 */
59export async function listDeploys(
60 args: { service?: string; limit?: number } = {},
61): Promise<DeployHistoryEntry[]> {
62 const db = getDb();
63 const limit = Math.min(Math.max(args.limit ?? 50, 1), 500);
64 if (args.service) {
65 return db
66 .select()
67 .from(deployHistory)
68 .where(eq(deployHistory.service, args.service))
69 .orderBy(desc(deployHistory.bootedAt))
70 .limit(limit);
71 }
72 return db.select().from(deployHistory).orderBy(desc(deployHistory.bootedAt)).limit(limit);
73}