Skip to content

Commit c04cd2f

Browse files
added drizzle kit
1 parent 97d571a commit c04cd2f

7 files changed

Lines changed: 159 additions & 12 deletions

File tree

packages/cloudflare/src/database.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@ import { PgDrizzle, make as makePgDrizzle } from "@effect/sql-drizzle/Pg"
1212
import * as Reactivity from "@effect/experimental/Reactivity"
1313
import * as SqlClient from "@effect/sql/SqlClient"
1414

15-
/**
16-
* Default database URL for local development.
17-
*/
18-
export const LOCAL_DATABASE_URL =
19-
"postgres://postgres:postgres@localhost:5432/effect_worker"
15+
2016

2117
/**
2218
* Creates a scoped PgDrizzle instance for request-scoped database access.

packages/cloudflare/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,5 @@ export { CloudflareBindingsError, DatabaseConnectionError } from "./errors"
3232
// Database utilities
3333
export {
3434
makeDrizzle,
35-
LOCAL_DATABASE_URL,
3635
PgDrizzle
3736
} from "./database"

packages/db/drizzle.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineConfig } from "drizzle-kit"
2+
3+
export default defineConfig({
4+
schema: "./src/schema.ts",
5+
out: "./drizzle",
6+
dialect: "postgresql",
7+
dbCredentials: {
8+
url: process.env.DATABASE_URL ?? ""
9+
}
10+
})

packages/db/package.json

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.0.0",
44
"type": "module",
55
"license": "MIT",
6-
"description": "Database schema for Effect Worker",
6+
"description": "Database schema and queries for Effect Worker",
77
"main": "./dist/index.js",
88
"types": "./dist/index.d.ts",
99
"exports": {
@@ -20,9 +20,19 @@
2020
"build": "tsc -p tsconfig.build.json",
2121
"check": "tsc --noEmit",
2222
"test": "vitest",
23-
"clean": "rm -rf dist"
23+
"clean": "rm -rf dist",
24+
"db:push": "drizzle-kit push",
25+
"db:studio": "drizzle-kit studio",
26+
"db:generate": "drizzle-kit generate",
27+
"db:migrate": "drizzle-kit migrate"
2428
},
2529
"dependencies": {
26-
"drizzle-orm": "^0.45.0"
30+
"drizzle-orm": "^0.45.0",
31+
"effect": "latest",
32+
"@effect/sql-drizzle": "latest",
33+
"@backpine/domain": "workspace:*"
34+
},
35+
"devDependencies": {
36+
"drizzle-kit": "^0.31.8"
2737
}
2838
}

packages/db/src/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
/**
22
* @backpine/db
33
*
4-
* Database schema for Effect Worker.
4+
* Database schema and queries for Effect Worker.
55
*
6-
* This package contains Drizzle ORM schema definitions shared across all apps.
6+
* This package contains Drizzle ORM schema definitions and
7+
* reusable Effect query programs shared across all apps.
78
*
89
* @module
910
*/
10-
export { users, type User, type NewUser } from "./schema"
11+
12+
// Schema exports
13+
export { users, type User as DbUser, type NewUser } from "./schema"
14+
15+
// Query exports
16+
export * from "./queries"

packages/db/src/queries/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Query Exports
3+
*
4+
* @module
5+
*/
6+
export * as UserQueries from "./users"

packages/db/src/queries/users.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* User Queries
3+
*
4+
* Reusable Effect programs for user database operations.
5+
*
6+
* @module
7+
*/
8+
import { DateTime, Effect } from "effect"
9+
import { PgDrizzle } from "@effect/sql-drizzle/Pg"
10+
import { eq } from "drizzle-orm"
11+
12+
import { users } from "../schema"
13+
import type { UserId, Email, User, CreateUser } from "@backpine/domain"
14+
import { UserNotFoundError, UserCreationError } from "@backpine/domain"
15+
16+
// ============================================================================
17+
// Internal Helpers
18+
// ============================================================================
19+
20+
/** Convert database ID to branded UserId */
21+
const toUserId = (id: number): UserId => `usr_${id}` as UserId
22+
23+
/** Parse UserId to database ID, returns null if invalid format */
24+
const parseUserId = (id: UserId): number | null => {
25+
const match = id.match(/^usr_(\d+)$/)
26+
return match ? parseInt(match[1]!, 10) : null
27+
}
28+
29+
/** Map database row to domain User */
30+
const toDomainUser = (row: typeof users.$inferSelect): User => ({
31+
id: toUserId(row.id),
32+
email: row.email as Email,
33+
name: row.name,
34+
createdAt: DateTime.unsafeFromDate(row.createdAt)
35+
})
36+
37+
// ============================================================================
38+
// Query Programs
39+
// ============================================================================
40+
41+
/**
42+
* Find all users.
43+
*
44+
* @returns Effect that yields array of domain Users
45+
*/
46+
export const findAllUsers: Effect.Effect<
47+
User[],
48+
never,
49+
PgDrizzle
50+
> = Effect.gen(function* () {
51+
const drizzle = yield* PgDrizzle
52+
const rows = yield* drizzle
53+
.select()
54+
.from(users)
55+
.pipe(Effect.orElseSucceed(() => []))
56+
57+
return rows.map(toDomainUser)
58+
})
59+
60+
/**
61+
* Find user by ID.
62+
*
63+
* @param id - Branded UserId
64+
* @returns Effect that yields User or fails with UserNotFoundError
65+
*/
66+
export const findUserById = (
67+
id: UserId
68+
): Effect.Effect<User, UserNotFoundError, PgDrizzle> =>
69+
Effect.gen(function* () {
70+
const dbId = parseUserId(id)
71+
72+
if (dbId === null) {
73+
return yield* Effect.fail(
74+
new UserNotFoundError({ id, message: `Invalid user ID format: ${id}` })
75+
)
76+
}
77+
78+
const drizzle = yield* PgDrizzle
79+
const rows = yield* drizzle
80+
.select()
81+
.from(users)
82+
.where(eq(users.id, dbId))
83+
.pipe(Effect.orElseSucceed(() => []))
84+
85+
const row = rows[0]
86+
87+
if (!row) {
88+
return yield* Effect.fail(
89+
new UserNotFoundError({ id, message: `User not found: ${id}` })
90+
)
91+
}
92+
93+
return toDomainUser(row)
94+
})
95+
96+
/**
97+
* Create a new user.
98+
*
99+
* @param data - CreateUser payload (email, name)
100+
* @returns Effect that yields created User or fails with UserCreationError
101+
*/
102+
export const createUser = (
103+
data: CreateUser
104+
): Effect.Effect<User, UserCreationError, PgDrizzle> =>
105+
Effect.gen(function* () {
106+
const drizzle = yield* PgDrizzle
107+
const rows = yield* drizzle
108+
.insert(users)
109+
.values({ email: data.email, name: data.name })
110+
.returning()
111+
.pipe(Effect.mapError(() => new UserCreationError(data)))
112+
113+
const row = rows[0]
114+
115+
if (!row) {
116+
return yield* Effect.fail(new UserCreationError(data))
117+
}
118+
119+
return toDomainUser(row)
120+
})

0 commit comments

Comments
 (0)