Skip to content

Commit 245e3b7

Browse files
authored
Merge pull request #22 from jmbish04/claude/migrate-drizzle-orm-01RRGUxNh4c8T71fvks6KRL8
Migrate from raw D1 SQL to Drizzle ORM
2 parents 2dce6f4 + a90c4f4 commit 245e3b7

8 files changed

Lines changed: 204 additions & 83 deletions

File tree

drizzle.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Config } from 'drizzle-kit'
2+
3+
export default {
4+
schema: './src/db/schema.ts',
5+
out: './migrations',
6+
driver: 'd1',
7+
dbCredentials: {
8+
wranglerConfigPath: './wrangler.jsonc',
9+
dbName: 'core-github-api',
10+
},
11+
} satisfies Config

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"migrate:local": "wrangler d1 migrations apply DB --local",
1010
"migrate:remote": "wrangler d1 migrations apply DB --remote",
1111
"deploy:only": "wrangler deploy",
12-
"deploy": "npm run migrate:remote && npm run deploy:only"
12+
"deploy": "npm run db:generate && npm run migrate:remote && npm run deploy:only",
13+
"db:generate": "drizzle-kit generate:sqlite --config=drizzle.config.ts"
1314
},
1415
"dependencies": {
1516
"@cloudflare/containers": "latest",
@@ -34,7 +35,7 @@
3435
"devDependencies": {
3536
"@cloudflare/vitest-pool-workers": "^0.10.5",
3637
"@types/node": "^20.14.10",
37-
"json5": "^2.2.3",
38+
"drizzle-kit": "^0.23.0",
3839
"typescript": "^5.6.0",
3940
"vitest": "~3.2.0",
4041
"wrangler": "^4.45.0"

src/agents/orchestrator.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
*/
77

88
import { Agent } from 'agents'
9+
import { getDb, schema } from '../db'
10+
import { eq, desc } from 'drizzle-orm'
911
// No longer need a direct import of the workflow class to call it
1012

1113
export class OrchestratorAgent extends Agent {
@@ -16,21 +18,25 @@ export class OrchestratorAgent extends Agent {
1618
async start(prompt: string) {
1719
const sessionId = crypto.randomUUID()
1820
const searchIds = []
21+
const db = getDb(this.env.DB)
1922

2023
// 1. Persist the session to D1
21-
await this.env.DB.prepare(
22-
'INSERT INTO sessions (session_id, prompt) VALUES (?, ?)'
23-
).bind(sessionId, prompt).run()
24+
await db.insert(schema.sessions).values({
25+
sessionId,
26+
prompt
27+
})
2428

2529
// 2. Generate search terms
2630
const searchTerms = await this.generateSearchTerms(prompt)
2731

2832
// 3. Launch a workflow for each search term
2933
for (const searchTerm of searchTerms) {
30-
const search = await this.env.DB.prepare(
31-
'INSERT INTO searches (session_id, search_term) VALUES (?, ?)'
32-
).bind(sessionId, searchTerm).run()
33-
const searchId = search.meta.last_row_id
34+
const search = await db.insert(schema.searches).values({
35+
sessionId,
36+
searchTerm
37+
}).returning({ id: schema.searches.id })
38+
39+
const searchId = search[0].id
3440
searchIds.push(searchId)
3541

3642
// Use the binding's .create() method to start the workflow
@@ -57,9 +63,14 @@ export class OrchestratorAgent extends Agent {
5763
return { status: 'pending', results: [] }
5864
}
5965

60-
const { results } = await this.env.DB.prepare(
61-
'SELECT * FROM repo_analysis WHERE session_id = ? ORDER BY relevancy_score DESC LIMIT 10'
62-
).bind(sessionId).all()
66+
const db = getDb(this.env.DB)
67+
const results = await db.select()
68+
.from(schema.repoAnalysis)
69+
.where(eq(schema.repoAnalysis.sessionId, sessionId))
70+
.orderBy(desc(schema.repoAnalysis.relevancyScore))
71+
.limit(10)
72+
.all()
73+
6374
return { status: 'completed', results }
6475
}
6576

src/db/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @file src/db/index.ts
3+
* @description Drizzle ORM database client initialization
4+
* @owner AI-Builder
5+
*/
6+
7+
import { drizzle } from 'drizzle-orm/d1'
8+
import type { D1Database } from '@cloudflare/workers-types'
9+
import * as schema from './schema'
10+
11+
/**
12+
* Get a Drizzle client instance for the D1 database
13+
* @param d1 - The D1Database instance from the Cloudflare Worker environment
14+
* @returns A Drizzle client instance with the schema attached
15+
*/
16+
export function getDb(d1: D1Database) {
17+
return drizzle(d1, { schema })
18+
}
19+
20+
// Re-export the schema for convenience
21+
export { schema }

src/db/schema.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* @file src/db/schema.ts
3+
* @description Drizzle ORM schema definitions for all D1 tables
4+
* @owner AI-Builder
5+
*/
6+
7+
import { sqliteTable, text, integer, real, index, uniqueIndex } from 'drizzle-orm/sqlite-core'
8+
9+
/**
10+
* Request logs table - stores HTTP request logs for monitoring
11+
*/
12+
export const requestLogs = sqliteTable('request_logs', {
13+
id: integer('id').primaryKey({ autoIncrement: true }),
14+
timestamp: text('timestamp').notNull(),
15+
level: text('level').notNull(),
16+
message: text('message').notNull(),
17+
method: text('method').notNull(),
18+
path: text('path').notNull(),
19+
status: integer('status').notNull(),
20+
latencyMs: integer('latency_ms').notNull(),
21+
payloadSizeBytes: integer('payload_size_bytes').notNull(),
22+
correlationId: text('correlation_id').notNull(),
23+
metadata: text('metadata'),
24+
}, (table) => ({
25+
timestampIdx: index('idx_request_logs_timestamp').on(table.timestamp),
26+
correlationIdIdx: index('idx_request_logs_correlation_id').on(table.correlationId),
27+
}))
28+
29+
/**
30+
* Sessions table - stores user session information for agent workflows
31+
*/
32+
export const sessions = sqliteTable('sessions', {
33+
id: integer('id').primaryKey({ autoIncrement: true }),
34+
sessionId: text('session_id').unique(),
35+
prompt: text('prompt'),
36+
createdAt: text('created_at').default('CURRENT_TIMESTAMP'),
37+
})
38+
39+
/**
40+
* Searches table - stores search term information for repository discovery
41+
*/
42+
export const searches = sqliteTable('searches', {
43+
id: integer('id').primaryKey({ autoIncrement: true }),
44+
sessionId: text('session_id').references(() => sessions.sessionId),
45+
searchTerm: text('search_term'),
46+
status: text('status').default('pending'),
47+
createdAt: text('created_at').default('CURRENT_TIMESTAMP'),
48+
})
49+
50+
/**
51+
* Repository analysis table - stores AI analysis results for discovered repositories
52+
*/
53+
export const repoAnalysis = sqliteTable('repo_analysis', {
54+
id: integer('id').primaryKey({ autoIncrement: true }),
55+
sessionId: text('session_id').references(() => sessions.sessionId),
56+
searchId: integer('search_id').references(() => searches.id),
57+
repoFullName: text('repo_full_name'),
58+
repoUrl: text('repo_url'),
59+
description: text('description'),
60+
relevancyScore: real('relevancy_score'),
61+
analyzedAt: text('analyzed_at').default('CURRENT_TIMESTAMP'),
62+
}, (table) => ({
63+
// Unique constraint on session_id and repo_full_name combination
64+
sessionRepoUnique: uniqueIndex('repo_analysis_session_id_repo_full_name_unique').on(
65+
table.sessionId,
66+
table.repoFullName
67+
),
68+
}))
69+
70+
/**
71+
* GitHub management config table - stores GitHub workflow retrofit operations
72+
*/
73+
export const ghManagementConfig = sqliteTable('gh_management_config', {
74+
id: integer('id').primaryKey({ autoIncrement: true }),
75+
timestamp: text('timestamp').notNull().default('CURRENT_TIMESTAMP'),
76+
repoName: text('repo_name').notNull(),
77+
action: text('action').notNull(),
78+
status: text('status').notNull(),
79+
statusDetails: text('status_details'),
80+
createdAt: text('created_at').default('CURRENT_TIMESTAMP'),
81+
}, (table) => ({
82+
timestampIdx: index('idx_gh_management_config_timestamp').on(table.timestamp),
83+
repoNameIdx: index('idx_gh_management_config_repo_name').on(table.repoName),
84+
actionIdx: index('idx_gh_management_config_action').on(table.action),
85+
statusIdx: index('idx_gh_management_config_status').on(table.status),
86+
}))

src/flows/index.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
*/
66

77
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
8-
import type { D1Database } from '@cloudflare/workers-types'
98
import { getOctokit } from '../octokit/core'
109
import { Bindings } from '../utils/hono'
1110
import { DEFAULT_WORKFLOWS, shouldIncludeCloudflareWorkflow } from './workflowTemplates'
1211
import { encode } from '../utils/base64'
12+
import { getDb, schema } from '../db'
1313

1414
// --- 1. Zod Schema Definitions ---
1515

@@ -166,23 +166,20 @@ const flows = new OpenAPIHono<{ Bindings: Bindings }>()
166166
* Helper function to log retrofit operation to D1
167167
*/
168168
async function logRetrofitOperation(
169-
db: D1Database,
169+
db: ReturnType<typeof getDb>,
170170
repoName: string,
171171
action: string,
172172
status: 'success' | 'skipped' | 'failure',
173173
statusDetails: Record<string, any>
174174
): Promise<void> {
175175
try {
176-
await db.prepare(
177-
`INSERT INTO gh_management_config (timestamp, repo_name, action, status, status_details)
178-
VALUES (?, ?, ?, ?, ?)`
179-
).bind(
180-
new Date().toISOString(),
176+
await db.insert(schema.ghManagementConfig).values({
177+
timestamp: new Date().toISOString(),
181178
repoName,
182179
action,
183180
status,
184-
JSON.stringify(statusDetails)
185-
).run()
181+
statusDetails: JSON.stringify(statusDetails)
182+
})
186183
} catch (error) {
187184
console.error('Failed to log retrofit operation:', error)
188185
}
@@ -403,10 +400,8 @@ flows.openapi(createNewRepoRoute, async (c) => {
403400
}
404401

405402
// 4. Log the operation
406-
// --- MODIFICATION: Changed c.env.CORE_GITHUB_API to c.env.DB ---
407403
await logRetrofitOperation(
408-
c.env.DB,
409-
// --- END MODIFICATION ---
404+
getDb(c.env.DB),
410405
repo.full_name,
411406
'create_new_repo',
412407
'success',
@@ -491,6 +486,9 @@ flows.openapi(retrofitWorkflowsRoute, async (c) => {
491486
let skippedCount = 0
492487
let failedCount = 0
493488

489+
// Initialize DB instance once, outside the loop
490+
const db = getDb(c.env.DB)
491+
494492
for (const repo of targetRepos) {
495493
console.log(`[flows/retrofit-workflows] Processing ${repo.full_name}`)
496494

@@ -561,10 +559,8 @@ flows.openapi(retrofitWorkflowsRoute, async (c) => {
561559
}
562560

563561
// Log the operation
564-
// --- MODIFICATION: Changed c.env.CORE_GITHUB_API to c.env.DB ---
565562
await logRetrofitOperation(
566-
c.env.DB,
567-
// --- END MODIFICATION ---
563+
db,
568564
repo.full_name,
569565
'retrofit_workflows',
570566
repoStatus,
@@ -585,12 +581,10 @@ flows.openapi(retrofitWorkflowsRoute, async (c) => {
585581
} catch (error: any) {
586582
const errorMessage = error.message || 'Unknown error'
587583
console.error(`[flows/retrofit-workflows] Error processing ${repo.full_name}:`, errorMessage)
588-
584+
589585
failedCount++
590-
// --- MODIFICATION: Changed c.env.CORE_GITHUB_API to c.env.DB ---
591586
await logRetrofitOperation(
592-
c.env.DB,
593-
// --- END MODIFICATION ---
587+
db,
594588
repo.full_name,
595589
'retrofit_workflows',
596590
'failure',

0 commit comments

Comments
 (0)