Skip to content

Commit 0aff845

Browse files
committed
refactor(internal): split db/schema.ts into domain files (Commit 3.2)
- Create schema/ subdirectory with domain-specific files: - enums.ts: All pgEnum definitions - users.ts: user, account, session, verification, fingerprint, apiKeys, referral - billing.ts: creditLedger, syncFailure - organizations.ts: org, orgMember, orgRepo, orgInvite, orgFeature - agents.ts: publisher, agentConfig, agentRun, agentStep, GitEval types - misc.ts: message, adImpression, gitEvalResults - index.ts: Barrel re-export - schema.ts now re-exports from schema/index.ts for backwards compatibility - All 13 package typechecks pass
1 parent d73af9f commit 0aff845

File tree

8 files changed

+790
-724
lines changed

8 files changed

+790
-724
lines changed

packages/internal/src/db/schema.ts

Lines changed: 3 additions & 724 deletions
Large diffs are not rendered by default.
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import { sql } from 'drizzle-orm'
2+
import {
3+
boolean,
4+
check,
5+
index,
6+
integer,
7+
jsonb,
8+
numeric,
9+
pgTable,
10+
primaryKey,
11+
text,
12+
timestamp,
13+
uniqueIndex,
14+
} from 'drizzle-orm/pg-core'
15+
16+
import type { SQL } from 'drizzle-orm'
17+
18+
import { agentRunStatus, agentStepStatus } from './enums'
19+
import { org } from './organizations'
20+
import { user } from './users'
21+
22+
export const publisher = pgTable(
23+
'publisher',
24+
{
25+
id: text('id').primaryKey().notNull(), // user-selectable id (must match /^[a-z0-9-]+$/)
26+
name: text('name').notNull(),
27+
email: text('email'), // optional, for support
28+
verified: boolean('verified').notNull().default(false),
29+
bio: text('bio'),
30+
avatar_url: text('avatar_url'),
31+
32+
// Ownership - exactly one must be set
33+
user_id: text('user_id').references(() => user.id, {
34+
onDelete: 'no action',
35+
}),
36+
org_id: text('org_id').references(() => org.id, { onDelete: 'no action' }),
37+
38+
created_by: text('created_by')
39+
.notNull()
40+
.references(() => user.id),
41+
created_at: timestamp('created_at', { mode: 'date', withTimezone: true })
42+
.notNull()
43+
.defaultNow(),
44+
updated_at: timestamp('updated_at', { mode: 'date', withTimezone: true })
45+
.notNull()
46+
.defaultNow(),
47+
},
48+
(table) => [
49+
// Constraint to ensure exactly one owner type
50+
check(
51+
'publisher_single_owner',
52+
sql`(${table.user_id} IS NOT NULL AND ${table.org_id} IS NULL) OR
53+
(${table.user_id} IS NULL AND ${table.org_id} IS NOT NULL)`,
54+
),
55+
],
56+
)
57+
58+
export const agentConfig = pgTable(
59+
'agent_config',
60+
{
61+
id: text('id')
62+
.notNull()
63+
.$defaultFn(() => crypto.randomUUID()),
64+
version: text('version').notNull(), // Semantic version e.g., '1.0.0'
65+
publisher_id: text('publisher_id')
66+
.notNull()
67+
.references(() => publisher.id),
68+
major: integer('major').generatedAlwaysAs(
69+
(): SQL =>
70+
sql`CAST(SPLIT_PART(${agentConfig.version}, '.', 1) AS INTEGER)`,
71+
),
72+
minor: integer('minor').generatedAlwaysAs(
73+
(): SQL =>
74+
sql`CAST(SPLIT_PART(${agentConfig.version}, '.', 2) AS INTEGER)`,
75+
),
76+
patch: integer('patch').generatedAlwaysAs(
77+
(): SQL =>
78+
sql`CAST(SPLIT_PART(${agentConfig.version}, '.', 3) AS INTEGER)`,
79+
),
80+
data: jsonb('data').notNull(), // All agentConfig details
81+
created_at: timestamp('created_at', { mode: 'date', withTimezone: true })
82+
.notNull()
83+
.defaultNow(),
84+
updated_at: timestamp('updated_at', { mode: 'date', withTimezone: true })
85+
.notNull()
86+
.defaultNow(),
87+
},
88+
(table) => [
89+
primaryKey({ columns: [table.publisher_id, table.id, table.version] }),
90+
index('idx_agent_config_publisher').on(table.publisher_id),
91+
],
92+
)
93+
94+
export const agentRun = pgTable(
95+
'agent_run',
96+
{
97+
id: text('id')
98+
.primaryKey()
99+
.$defaultFn(() => crypto.randomUUID()),
100+
101+
// Identity and relationships
102+
user_id: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
103+
104+
// Agent identity (either "publisher/agent@version" OR a plain string with no '/' or '@')
105+
agent_id: text('agent_id').notNull(),
106+
107+
// Agent identity (full versioned ID like "CodebuffAI/reviewer@1.0.0")
108+
publisher_id: text('publisher_id').generatedAlwaysAs(
109+
sql`CASE
110+
WHEN agent_id ~ '^[^/@]+/[^/@]+@[^/@]+$'
111+
THEN split_part(agent_id, '/', 1)
112+
ELSE NULL
113+
END`,
114+
),
115+
// agent_name: middle part for full pattern; otherwise the whole id
116+
agent_name: text('agent_name').generatedAlwaysAs(
117+
sql`CASE
118+
WHEN agent_id ~ '^[^/@]+/[^/@]+@[^/@]+$'
119+
THEN split_part(split_part(agent_id, '/', 2), '@', 1)
120+
ELSE agent_id
121+
END`,
122+
),
123+
agent_version: text('agent_version').generatedAlwaysAs(
124+
sql`CASE
125+
WHEN agent_id ~ '^[^/@]+/[^/@]+@[^/@]+$'
126+
THEN split_part(agent_id, '@', 2)
127+
ELSE NULL
128+
END`,
129+
),
130+
131+
// Hierarchy tracking
132+
ancestor_run_ids: text('ancestor_run_ids').array(), // array of ALL run IDs from root (inclusive) to self (exclusive)
133+
// Derived from ancestor_run_ids - root is first element
134+
root_run_id: text('root_run_id').generatedAlwaysAs(
135+
sql`CASE WHEN array_length(ancestor_run_ids, 1) >= 1 THEN ancestor_run_ids[1] ELSE id END`,
136+
),
137+
// Derived from ancestor_run_ids - parent is second-to-last element
138+
parent_run_id: text('parent_run_id').generatedAlwaysAs(
139+
sql`CASE WHEN array_length(ancestor_run_ids, 1) >= 1 THEN ancestor_run_ids[array_length(ancestor_run_ids, 1)] ELSE NULL END`,
140+
),
141+
// Derived from ancestor_run_ids - depth is array length minus 1
142+
depth: integer('depth').generatedAlwaysAs(
143+
sql`COALESCE(array_length(ancestor_run_ids, 1), 1)`,
144+
),
145+
146+
// Performance metrics
147+
duration_ms: integer('duration_ms').generatedAlwaysAs(
148+
sql`CASE WHEN completed_at IS NOT NULL THEN EXTRACT(EPOCH FROM (completed_at - created_at)) * 1000 ELSE NULL END::integer`,
149+
), // total time from start to completion in milliseconds
150+
total_steps: integer('total_steps').default(0), // denormalized count
151+
152+
// Credit tracking
153+
direct_credits: numeric('direct_credits', {
154+
precision: 10,
155+
scale: 6,
156+
}).default('0'), // credits used by this agent only
157+
total_credits: numeric('total_credits', {
158+
precision: 10,
159+
scale: 6,
160+
}).default('0'), // credits used by this agent + all descendants
161+
162+
// Status tracking
163+
status: agentRunStatus('status').notNull().default('running'),
164+
error_message: text('error_message'),
165+
166+
// Timestamps
167+
created_at: timestamp('created_at', { mode: 'date', withTimezone: true })
168+
.notNull()
169+
.defaultNow(),
170+
completed_at: timestamp('completed_at', {
171+
mode: 'date',
172+
withTimezone: true,
173+
}),
174+
},
175+
(table) => [
176+
// Performance indices
177+
index('idx_agent_run_user_id').on(table.user_id, table.created_at),
178+
index('idx_agent_run_parent').on(table.parent_run_id),
179+
index('idx_agent_run_root').on(table.root_run_id),
180+
index('idx_agent_run_agent_id').on(table.agent_id, table.created_at),
181+
index('idx_agent_run_publisher').on(table.publisher_id, table.created_at),
182+
index('idx_agent_run_status')
183+
.on(table.status)
184+
.where(sql`${table.status} = 'running'`),
185+
index('idx_agent_run_ancestors_gin').using('gin', table.ancestor_run_ids),
186+
// Performance indexes for agent store
187+
index('idx_agent_run_completed_publisher_agent')
188+
.on(table.publisher_id, table.agent_name)
189+
.where(sql`${table.status} = 'completed'`),
190+
index('idx_agent_run_completed_recent')
191+
.on(table.created_at, table.publisher_id, table.agent_name)
192+
.where(sql`${table.status} = 'completed'`),
193+
index('idx_agent_run_completed_version')
194+
.on(
195+
table.publisher_id,
196+
table.agent_name,
197+
table.agent_version,
198+
table.created_at,
199+
)
200+
.where(sql`${table.status} = 'completed'`),
201+
index('idx_agent_run_completed_user')
202+
.on(table.user_id)
203+
.where(sql`${table.status} = 'completed'`),
204+
],
205+
)
206+
207+
export const agentStep = pgTable(
208+
'agent_step',
209+
{
210+
id: text('id')
211+
.primaryKey()
212+
.$defaultFn(() => crypto.randomUUID()),
213+
214+
// Relationship to run
215+
agent_run_id: text('agent_run_id')
216+
.notNull()
217+
.references(() => agentRun.id, { onDelete: 'cascade' }),
218+
step_number: integer('step_number').notNull(), // sequential within the run
219+
220+
// Performance metrics
221+
duration_ms: integer('duration_ms').generatedAlwaysAs(
222+
sql`CASE WHEN completed_at IS NOT NULL THEN EXTRACT(EPOCH FROM (completed_at - created_at)) * 1000 ELSE NULL END::integer`,
223+
), // total time from start to completion in milliseconds
224+
credits: numeric('credits', {
225+
precision: 10,
226+
scale: 6,
227+
})
228+
.notNull()
229+
.default('0'), // credits used by this step
230+
231+
// Spawned agents tracking
232+
child_run_ids: text('child_run_ids').array(), // array of agent_run IDs created by this step
233+
spawned_count: integer('spawned_count').generatedAlwaysAs(
234+
sql`array_length(child_run_ids, 1)`,
235+
),
236+
237+
// Message tracking (if applicable)
238+
message_id: text('message_id'), // reference to message table if needed
239+
240+
// Status
241+
status: agentStepStatus('status').notNull().default('completed'),
242+
error_message: text('error_message'),
243+
244+
// Timestamps
245+
created_at: timestamp('created_at', { mode: 'date', withTimezone: true })
246+
.notNull()
247+
.defaultNow(),
248+
completed_at: timestamp('completed_at', {
249+
mode: 'date',
250+
withTimezone: true,
251+
})
252+
.notNull()
253+
.defaultNow(),
254+
},
255+
(table) => [
256+
// Unique constraint for step numbers per run
257+
uniqueIndex('unique_step_number_per_run').on(
258+
table.agent_run_id,
259+
table.step_number,
260+
),
261+
// Performance indices
262+
index('idx_agent_step_run_id').on(table.agent_run_id),
263+
index('idx_agent_step_children_gin').using('gin', table.child_run_ids),
264+
],
265+
)
266+
267+
export type GitEvalMetadata = {
268+
numCases?: number // Number of eval cases successfully run (total)
269+
avgScore?: number // Average score across all cases
270+
avgCompletion?: number // Average completion across all cases
271+
avgEfficiency?: number // Average efficiency across all cases
272+
avgCodeQuality?: number // Average code quality across all cases
273+
avgDuration?: number // Average duration across all cases
274+
suite?: string // Name of the repo (eg: codebuff, manifold)
275+
avgTurns?: number // Average number of user turns across all cases
276+
}
277+
278+
// Request type for the insert API
279+
export interface GitEvalResultRequest {
280+
cost_mode?: string
281+
reasoner_model?: string
282+
agent_model?: string
283+
metadata?: GitEvalMetadata
284+
cost?: number
285+
}
286+
287+
export const gitEvalResults = pgTable('git_eval_results', {
288+
id: text('id')
289+
.primaryKey()
290+
.$defaultFn(() => crypto.randomUUID()),
291+
cost_mode: text('cost_mode'),
292+
reasoner_model: text('reasoner_model'),
293+
agent_model: text('agent_model'),
294+
metadata: jsonb('metadata'), // GitEvalMetadata
295+
cost: integer('cost').notNull().default(0),
296+
is_public: boolean('is_public').notNull().default(false),
297+
created_at: timestamp('created_at', { mode: 'date', withTimezone: true })
298+
.notNull()
299+
.defaultNow(),
300+
})
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { sql } from 'drizzle-orm'
2+
import {
3+
index,
4+
integer,
5+
pgTable,
6+
text,
7+
timestamp,
8+
} from 'drizzle-orm/pg-core'
9+
10+
import { grantTypeEnum } from './enums'
11+
import { org } from './organizations'
12+
import { user } from './users'
13+
14+
export const creditLedger = pgTable(
15+
'credit_ledger',
16+
{
17+
operation_id: text('operation_id').primaryKey(),
18+
user_id: text('user_id')
19+
.notNull()
20+
.references(() => user.id, { onDelete: 'cascade' }),
21+
principal: integer('principal').notNull(),
22+
balance: integer('balance').notNull(),
23+
type: grantTypeEnum('type').notNull(),
24+
description: text('description'),
25+
priority: integer('priority').notNull(),
26+
expires_at: timestamp('expires_at', { mode: 'date', withTimezone: true }),
27+
created_at: timestamp('created_at', { mode: 'date', withTimezone: true })
28+
.notNull()
29+
.defaultNow(),
30+
org_id: text('org_id').references(() => org.id, { onDelete: 'cascade' }),
31+
},
32+
(table) => [
33+
index('idx_credit_ledger_active_balance')
34+
.on(
35+
table.user_id,
36+
table.balance,
37+
table.expires_at,
38+
table.priority,
39+
table.created_at,
40+
)
41+
.where(sql`${table.balance} != 0 AND ${table.expires_at} IS NULL`),
42+
index('idx_credit_ledger_org').on(table.org_id),
43+
],
44+
)
45+
46+
export const syncFailure = pgTable(
47+
'sync_failure',
48+
{
49+
id: text('id').primaryKey(),
50+
provider: text('provider').notNull(),
51+
created_at: timestamp('created_at', {
52+
mode: 'date',
53+
withTimezone: true,
54+
})
55+
.notNull()
56+
.defaultNow(),
57+
last_attempt_at: timestamp('last_attempt_at', {
58+
mode: 'date',
59+
withTimezone: true,
60+
})
61+
.notNull()
62+
.defaultNow(),
63+
retry_count: integer('retry_count').notNull().default(1),
64+
last_error: text('last_error').notNull(),
65+
},
66+
(table) => [
67+
index('idx_sync_failure_retry')
68+
.on(table.retry_count, table.last_attempt_at)
69+
.where(sql`${table.retry_count} < 5`),
70+
],
71+
)

0 commit comments

Comments
 (0)