Skip to content

Commit c1e659a

Browse files
committed
Migrate raw SQL to Kysely and create projects service
- Add projects and projectRoutes tables to Database interface - Create src/services/db/projects.ts with type-safe queries: - isProjectMainWorker() checks if worker is project main - deleteProject() deletes a project - getProjectEnvironmentId() gets project environment - upsertStorageRoutes() manages storage routes - upsertFunctionRoute() manages function routes - Migrate src/services/projects.ts to use new service - Clean up src/routes/workers.ts to use projects service - Remove raw SQL from user-facing code (keep PL/pgSQL function calls) Bundle size: 450KB (down from 585KB)
1 parent 35f8655 commit c1e659a

4 files changed

Lines changed: 123 additions & 33 deletions

File tree

src/routes/workers.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { workersService } from '../services/workers';
44
import { cronsService } from '../services/crons';
55
import { checkWorkerNameExists, findWorkerAssetsBinding } from '../services/db/workers';
66
import { sql } from '../services/db/client';
7+
import * as projectsDb from '../services/db/projects';
78
import { WorkerCreateInputSchema, WorkerUpdateInputSchema, WorkerSchema } from '../types';
89
import { jsonResponse, jsonArrayResponse, parseAndValidate } from '../utils/validate';
910
import { S3Client } from '../utils/s3';
@@ -178,20 +179,13 @@ workers.delete('/:id', async (c) => {
178179
// we detect if this worker is a main worker and delete the project instead.
179180
// This avoids the "Cannot delete main worker" constraint error.
180181
// TODO: Remove this when UI properly distinguishes projects from workers.
181-
const projectCheck = await sql('SELECT id FROM projects WHERE id = $1::uuid AND user_id = $2::uuid', [
182-
worker.id,
183-
userId
184-
]);
182+
const isProject = await projectsDb.isProjectMainWorker(userId, worker.id);
185183

186184
let deleted: number;
187185

188-
if (projectCheck.length > 0) {
186+
if (isProject) {
189187
// This is a main worker - delete the project instead (cascades to all workers)
190-
const deleteResult = await sql('DELETE FROM projects WHERE id = $1::uuid AND user_id = $2::uuid RETURNING id', [
191-
worker.id,
192-
userId
193-
]);
194-
deleted = deleteResult.length;
188+
deleted = await projectsDb.deleteProject(userId, worker.id);
195189
} else {
196190
// Regular worker - delete normally
197191
deleted = await workersService.delete(userId, worker.id);

src/services/db/kysely-client.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,24 @@ export interface Database {
158158
message: string | null;
159159
createdAt: Generated<Date>;
160160
};
161+
projects: {
162+
id: Generated<string>;
163+
name: string;
164+
desc: string | null;
165+
userId: string;
166+
environmentId: string | null;
167+
createdAt: Generated<Date>;
168+
updatedAt: Generated<Date>;
169+
};
170+
projectRoutes: {
171+
projectId: string;
172+
pattern: string;
173+
priority: number;
174+
backendType: 'worker' | 'storage';
175+
workerId: string | null;
176+
createdAt: Generated<Date>;
177+
updatedAt: Generated<Date>;
178+
};
161179
// Add more tables here as needed...
162180
}
163181

src/services/db/projects.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { kysely } from './kysely-client';
2+
import { uuid, uuidOrNull, enumCast } from './kysely-helpers';
3+
4+
/**
5+
* Check if a worker is a project's main worker
6+
*/
7+
export async function isProjectMainWorker(userId: string, workerId: string): Promise<boolean> {
8+
const project = await kysely
9+
.selectFrom('projects')
10+
.select('id')
11+
.where('id', '=', uuid(workerId))
12+
.where('userId', '=', uuid(userId))
13+
.executeTakeFirst();
14+
15+
return project !== undefined;
16+
}
17+
18+
/**
19+
* Delete a project by ID
20+
* Returns the number of deleted projects
21+
*/
22+
export async function deleteProject(userId: string, projectId: string): Promise<number> {
23+
const result = await kysely
24+
.deleteFrom('projects')
25+
.where('id', '=', uuid(projectId))
26+
.where('userId', '=', uuid(userId))
27+
.returning('id')
28+
.execute();
29+
30+
return result.length;
31+
}
32+
33+
/**
34+
* Get project's environment ID
35+
*/
36+
export async function getProjectEnvironmentId(projectId: string): Promise<string | null> {
37+
const project = await kysely
38+
.selectFrom('projects')
39+
.select('environmentId')
40+
.where('id', '=', uuid(projectId))
41+
.executeTakeFirst();
42+
43+
return project?.environmentId ?? null;
44+
}
45+
46+
/**
47+
* Create or update storage routes for a project
48+
*/
49+
export async function upsertStorageRoutes(projectId: string, patterns: string[], priority: number): Promise<void> {
50+
for (const pattern of patterns) {
51+
await kysely
52+
.insertInto('projectRoutes')
53+
.values({
54+
projectId: uuid(projectId),
55+
pattern,
56+
priority,
57+
backendType: enumCast('storage', 'enum_backend_type'),
58+
workerId: null
59+
})
60+
.onConflict((oc) =>
61+
oc.columns(['projectId', 'pattern']).doUpdateSet({
62+
priority,
63+
backendType: enumCast('storage', 'enum_backend_type')
64+
})
65+
)
66+
.execute();
67+
}
68+
}
69+
70+
/**
71+
* Create or update a function route for a project
72+
*/
73+
export async function upsertFunctionRoute(
74+
projectId: string,
75+
pattern: string,
76+
workerId: string,
77+
priority: number
78+
): Promise<void> {
79+
await kysely
80+
.insertInto('projectRoutes')
81+
.values({
82+
projectId: uuid(projectId),
83+
pattern,
84+
priority,
85+
backendType: enumCast('worker', 'enum_backend_type'),
86+
workerId: uuid(workerId)
87+
})
88+
.onConflict((oc) =>
89+
oc.columns(['projectId', 'pattern']).doUpdateSet({
90+
priority,
91+
backendType: enumCast('worker', 'enum_backend_type'),
92+
workerId: uuid(workerId)
93+
})
94+
)
95+
.execute();
96+
}

src/services/projects.ts

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
import { strFromU8 } from 'fflate';
2-
import { sql } from './db/client';
32
import { workersService } from './workers';
3+
import * as db from './db/projects';
44

55
/**
66
* Create storage routes for a project
77
*/
88
export async function createStorageRoutes(projectId: string, patterns: string[], priority: number): Promise<void> {
9-
for (const pattern of patterns) {
10-
await sql(
11-
`INSERT INTO project_routes (project_id, pattern, priority, backend_type)
12-
VALUES ($1::uuid, $2, $3, 'storage'::enum_backend_type)
13-
ON CONFLICT (project_id, pattern) DO UPDATE
14-
SET priority = $3, backend_type = 'storage'::enum_backend_type`,
15-
[projectId, pattern, priority]
16-
);
17-
}
9+
await db.upsertStorageRoutes(projectId, patterns, priority);
1810
}
1911

2012
/**
@@ -32,11 +24,7 @@ export async function createFunctionRoutes(
3224
const priority = 10;
3325

3426
// Get project's environment_id (required for all project workers)
35-
const projectRows = await sql<{ environment_id?: string }>(
36-
'SELECT environment_id FROM projects WHERE id = $1::uuid',
37-
[projectId]
38-
);
39-
const projectEnvironmentId = projectRows[0]?.environment_id;
27+
const projectEnvironmentId = await db.getProjectEnvironmentId(projectId);
4028

4129
for (const func of functions) {
4230
try {
@@ -56,7 +44,7 @@ export async function createFunctionRoutes(
5644
script,
5745
language: 'javascript',
5846
projectId,
59-
environmentId: projectEnvironmentId
47+
environmentId: projectEnvironmentId ?? undefined
6048
});
6149

6250
if (!newWorker) {
@@ -65,13 +53,7 @@ export async function createFunctionRoutes(
6553
}
6654

6755
// Create route pointing to this function worker
68-
await sql(
69-
`INSERT INTO project_routes (project_id, pattern, priority, backend_type, worker_id)
70-
VALUES ($1::uuid, $2, $3, 'worker'::enum_backend_type, $4::uuid)
71-
ON CONFLICT (project_id, pattern) DO UPDATE
72-
SET priority = $3, backend_type = 'worker'::enum_backend_type, worker_id = $4::uuid`,
73-
[projectId, func.pattern, priority, newWorker.id]
74-
);
56+
await db.upsertFunctionRoute(projectId, func.pattern, newWorker.id, priority);
7557
} catch (error) {
7658
console.error(`Failed to process function ${func.worker}:`, error);
7759
// Continue with other functions

0 commit comments

Comments
 (0)