Skip to content

Commit f2d86f4

Browse files
geclosclaude
andcommitted
refactor: make withPostgres/withClickHouse return pipe-compatible providers
Previously withPostgres and withClickHouse returned a Layer, requiring callers to wrap every call site with Effect.provide(). They also accepted (client, orgId, ...layers) which was hard to type correctly with variadic layer arguments. New signature: (layer, client, orgId?) => pipe-compatible function via Effect.provide internally. Call sites can now use effect.pipe(withPostgres(...)) directly without the outer Effect.provide wrapper. - Rename withClickHouse to the same shape as withPostgres - Migrate all call sites in apps/web and apps/api to the new signature - Consolidate auth functions to withPostgres instead of multiple Effect.provide(RepositoryLive) + Effect.provide(SqlClientLive) chains - Switch exchangeCliSession from adminClient to scoped postgresClient since API key creation is org-scoped Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bad4f64 commit f2d86f4

File tree

10 files changed

+67
-82
lines changed

10 files changed

+67
-82
lines changed

apps/api/src/routes/api-keys.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export const createApiKeysRoutes = () => {
175175

176176
const apiKey = await Effect.runPromise(
177177
generateApiKeyUseCase({ name }).pipe(
178-
Effect.provide(withPostgres(c.var.postgresClient, c.var.organization.id, ApiKeyRepositoryLive)),
178+
withPostgres(ApiKeyRepositoryLive, c.var.postgresClient, c.var.organization.id),
179179
),
180180
)
181181
return c.json(toApiKeyResponse(apiKey), 201)
@@ -186,7 +186,7 @@ export const createApiKeysRoutes = () => {
186186
Effect.gen(function* () {
187187
const repo = yield* ApiKeyRepository
188188
return yield* repo.findAll()
189-
}).pipe(Effect.provide(withPostgres(c.var.postgresClient, c.var.organization.id, ApiKeyRepositoryLive))),
189+
}).pipe(withPostgres(ApiKeyRepositoryLive, c.var.postgresClient, c.var.organization.id)),
190190
)
191191
return c.json({ apiKeys: apiKeys.map(toApiKeyListItemResponse) }, 200)
192192
})
@@ -197,7 +197,7 @@ export const createApiKeysRoutes = () => {
197197
await Effect.runPromise(
198198
revokeApiKeyUseCase({ id: ApiKeyId(idParam) }).pipe(
199199
Effect.provideService(ApiKeyCacheInvalidator, createApiKeyCacheInvalidator(c.var.redis)),
200-
Effect.provide(withPostgres(c.var.postgresClient, c.var.organization.id, ApiKeyRepositoryLive)),
200+
withPostgres(ApiKeyRepositoryLive, c.var.postgresClient, c.var.organization.id),
201201
),
202202
)
203203
return c.body(null, 204)

apps/api/src/routes/projects.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ export const createProjectsRoutes = () => {
220220

221221
const project = await Effect.runPromise(
222222
createProjectUseCase(input).pipe(
223-
Effect.provide(withPostgres(c.var.postgresClient, c.var.organization.id, ProjectRepositoryLive)),
223+
withPostgres(ProjectRepositoryLive, c.var.postgresClient, c.var.organization.id),
224224
),
225225
)
226226
return c.json(toProjectResponse(project), 201)
@@ -231,7 +231,7 @@ export const createProjectsRoutes = () => {
231231
Effect.gen(function* () {
232232
const repo = yield* ProjectRepository
233233
return yield* repo.findAll()
234-
}).pipe(Effect.provide(withPostgres(c.var.postgresClient, c.var.organization.id, ProjectRepositoryLive))),
234+
}).pipe(withPostgres(ProjectRepositoryLive, c.var.postgresClient, c.var.organization.id)),
235235
)
236236

237237
return c.json({ projects: projects.map(toProjectResponse) }, 200)
@@ -245,7 +245,7 @@ export const createProjectsRoutes = () => {
245245
Effect.gen(function* () {
246246
const repo = yield* ProjectRepository
247247
return yield* repo.findById(id)
248-
}).pipe(Effect.provide(withPostgres(c.var.postgresClient, c.var.organization.id, ProjectRepositoryLive))),
248+
}).pipe(withPostgres(ProjectRepositoryLive, c.var.postgresClient, c.var.organization.id)),
249249
)
250250

251251
return c.json(toProjectResponse(project), 200)
@@ -261,7 +261,7 @@ export const createProjectsRoutes = () => {
261261
id,
262262
...(body.name !== undefined ? { name: body.name } : {}),
263263
...(body.description !== undefined ? { description: body.description } : {}),
264-
}).pipe(Effect.provide(withPostgres(c.var.postgresClient, c.var.organization.id, ProjectRepositoryLive))),
264+
}).pipe(withPostgres(ProjectRepositoryLive, c.var.postgresClient, c.var.organization.id)),
265265
)
266266

267267
return c.json(toProjectResponse(updatedProject), 200)
@@ -275,7 +275,7 @@ export const createProjectsRoutes = () => {
275275
Effect.gen(function* () {
276276
const repo = yield* ProjectRepository
277277
return yield* repo.softDelete(id)
278-
}).pipe(Effect.provide(withPostgres(c.var.postgresClient, c.var.organization.id, ProjectRepositoryLive))),
278+
}).pipe(withPostgres(ProjectRepositoryLive, c.var.postgresClient, c.var.organization.id)),
279279
)
280280
return c.body(null, 204)
281281
})

apps/web/src/domains/api-keys/api-keys.functions.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const listApiKeys = createServerFn({ method: "GET" })
3838
Effect.gen(function* () {
3939
const repo = yield* ApiKeyRepository
4040
return yield* repo.findAll()
41-
}).pipe(Effect.provide(withPostgres(client, organizationId, ApiKeyRepositoryLive))),
41+
}).pipe(withPostgres(ApiKeyRepositoryLive, client, organizationId)),
4242
)
4343

4444
return apiKeys.map(toRecord)
@@ -52,9 +52,7 @@ export const createApiKey = createServerFn({ method: "POST" })
5252
const client = getPostgresClient()
5353

5454
const apiKey = await Effect.runPromise(
55-
generateApiKeyUseCase({ name: data.name }).pipe(
56-
Effect.provide(withPostgres(client, organizationId, ApiKeyRepositoryLive)),
57-
),
55+
generateApiKeyUseCase({ name: data.name }).pipe(withPostgres(ApiKeyRepositoryLive, client, organizationId)),
5856
)
5957

6058
return toRecord(apiKey)
@@ -69,7 +67,7 @@ export const updateApiKey = createServerFn({ method: "POST" })
6967

7068
const apiKey = await Effect.runPromise(
7169
updateApiKeyUseCase({ id: ApiKeyId(data.id), name: data.name }).pipe(
72-
Effect.provide(withPostgres(client, organizationId, ApiKeyRepositoryLive)),
70+
withPostgres(ApiKeyRepositoryLive, client, organizationId),
7371
),
7472
)
7573

@@ -87,6 +85,6 @@ export const deleteApiKey = createServerFn({ method: "POST" })
8785
Effect.gen(function* () {
8886
const repo = yield* ApiKeyRepository
8987
yield* repo.delete(ApiKeyId(data.id))
90-
}).pipe(Effect.provide(withPostgres(client, organizationId, ApiKeyRepositoryLive))),
88+
}).pipe(withPostgres(ApiKeyRepositoryLive, client, organizationId)),
9189
)
9290
})

apps/web/src/domains/auth/auth.functions.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ import {
1111
AuthIntentRepositoryLive,
1212
MembershipRepositoryLive,
1313
OrganizationRepositoryLive,
14-
SqlClientLive,
1514
UserRepositoryLive,
1615
withPostgres,
1716
} from "@platform/db-postgres"
1817
import { createServerFn } from "@tanstack/react-start"
19-
import { Effect } from "effect"
18+
import { Effect, Layer } from "effect"
2019
import { z } from "zod"
21-
import { getAdminPostgresClient, getRedisClient } from "../../server/clients.ts"
20+
import { getAdminPostgresClient, getPostgresClient, getRedisClient } from "../../server/clients.ts"
2221
import { errorHandler } from "../../server/middlewares.ts"
2322
import { ensureSession } from "../sessions/session.functions.ts"
2423
import {
@@ -39,9 +38,7 @@ export const createLoginIntent = createServerFn({ method: "POST" })
3938

4039
const intent = await Effect.runPromise(
4140
createLoginIntentUseCase({ email: data.email }).pipe(
42-
Effect.provide(UserRepositoryLive),
43-
Effect.provide(AuthIntentRepositoryLive),
44-
Effect.provide(SqlClientLive(adminClient)),
41+
withPostgres(Layer.mergeAll(UserRepositoryLive, AuthIntentRepositoryLive), adminClient),
4542
),
4643
)
4744

@@ -59,11 +56,7 @@ export const createSignupIntent = createServerFn({ method: "POST" })
5956
name: data.name,
6057
email: data.email,
6158
organizationName: data.organizationName,
62-
}).pipe(
63-
Effect.provide(UserRepositoryLive),
64-
Effect.provide(AuthIntentRepositoryLive),
65-
Effect.provide(SqlClientLive(adminClient)),
66-
),
59+
}).pipe(withPostgres(Layer.mergeAll(UserRepositoryLive, AuthIntentRepositoryLive), adminClient)),
6760
)
6861

6962
return {
@@ -89,7 +82,7 @@ export const getAuthIntentInfo = createServerFn({ method: "POST" })
8982
Effect.gen(function* () {
9083
const repo = yield* AuthIntentRepository
9184
return yield* repo.findById(data.intentId).pipe(Effect.catchTag("NotFoundError", () => Effect.succeed(null)))
92-
}).pipe(Effect.provide(AuthIntentRepositoryLive), Effect.provide(SqlClientLive(adminClient))),
85+
}).pipe(withPostgres(AuthIntentRepositoryLive, adminClient)),
9386
)
9487

9588
if (!intent) {
@@ -121,11 +114,15 @@ export const completeAuthIntent = createServerFn({ method: "POST" })
121114
intentId: data.intentId,
122115
session: { userId, email, name },
123116
}).pipe(
124-
Effect.provide(AuthIntentRepositoryLive),
125-
Effect.provide(MembershipRepositoryLive),
126-
Effect.provide(UserRepositoryLive),
127-
Effect.provide(OrganizationRepositoryLive),
128-
Effect.provide(SqlClientLive(adminClient)),
117+
withPostgres(
118+
Layer.mergeAll(
119+
AuthIntentRepositoryLive,
120+
MembershipRepositoryLive,
121+
UserRepositoryLive,
122+
OrganizationRepositoryLive,
123+
),
124+
adminClient,
125+
),
129126
),
130127
)
131128
})
@@ -151,16 +148,15 @@ export const exchangeCliSession = createServerFn({ method: "POST" })
151148

152149
const sessionData = session.session as Record<string, unknown>
153150
const activeOrganizationId = sessionData.activeOrganizationId as string | undefined
154-
155151
if (!activeOrganizationId) {
156152
throw new Error("No active organization. Please complete your account setup first.")
157153
}
158154

159-
const adminClient = getAdminPostgresClient()
155+
const client = getPostgresClient()
160156
const createdAt = new Date().toISOString().slice(0, 10) // YYYY-MM-DD
161157
const apiKey = await Effect.runPromise(
162158
generateApiKeyUseCase({ name: `CLI (${createdAt})` }).pipe(
163-
Effect.provide(withPostgres(adminClient, OrganizationId(activeOrganizationId), ApiKeyRepositoryLive)),
159+
withPostgres(ApiKeyRepositoryLive, client, OrganizationId(activeOrganizationId)),
164160
),
165161
)
166162

apps/web/src/domains/datasets/datasets.functions.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ export const listDatasetsQuery = createServerFn({ method: "GET" })
134134
organizationId: orgId,
135135
projectId: ProjectId(data.projectId),
136136
}).pipe(
137-
Effect.provide(withPostgres(getPostgresClient(), orgId, DatasetRepositoryLive)),
138-
Effect.provide(withClickHouse(getClickhouseClient(), orgId, DatasetRowRepositoryLive)),
137+
withPostgres(DatasetRepositoryLive, getPostgresClient(), orgId),
138+
withClickHouse(DatasetRowRepositoryLive, getClickhouseClient(), orgId),
139139
),
140140
)
141141

@@ -166,8 +166,8 @@ export const listRowsQuery = createServerFn({ method: "GET" })
166166
limit: data.limit,
167167
offset: data.offset,
168168
}).pipe(
169-
Effect.provide(withPostgres(getPostgresClient(), orgId, DatasetRepositoryLive)),
170-
Effect.provide(withClickHouse(getClickhouseClient(), orgId, DatasetRowRepositoryLive)),
169+
withPostgres(DatasetRepositoryLive, getPostgresClient(), orgId),
170+
withClickHouse(DatasetRowRepositoryLive, getClickhouseClient(), orgId),
171171
),
172172
)
173173

@@ -187,8 +187,8 @@ export const createDatasetMutation = createServerFn({ method: "POST" })
187187
projectId: ProjectId(data.projectId),
188188
name: data.name,
189189
}).pipe(
190-
Effect.provide(withPostgres(getPostgresClient(), orgId, DatasetRepositoryLive)),
191-
Effect.provide(withClickHouse(getClickhouseClient(), orgId, DatasetRowRepositoryLive)),
190+
withPostgres(DatasetRepositoryLive, getPostgresClient(), orgId),
191+
withClickHouse(DatasetRowRepositoryLive, getClickhouseClient(), orgId),
192192
),
193193
)
194194

@@ -235,7 +235,7 @@ export const saveDatasetCsv = createServerFn({ method: "POST" })
235235
Effect.gen(function* () {
236236
const repo = yield* DatasetRepository
237237
return yield* repo.updateFileKey({ id: DatasetId(datasetId), fileKey })
238-
}).pipe(Effect.provide(withPostgres(getPostgresClient(), orgId, DatasetRepositoryLive))),
238+
}).pipe(withPostgres(DatasetRepositoryLive, getPostgresClient(), orgId)),
239239
)
240240

241241
const parsed = Papa.parse<Record<string, string>>(content, { header: true, skipEmptyLines: true })
@@ -252,8 +252,8 @@ export const saveDatasetCsv = createServerFn({ method: "POST" })
252252
rows: mappedRows,
253253
source: "csv",
254254
}).pipe(
255-
Effect.provide(withPostgres(getPostgresClient(), orgId, DatasetRepositoryLive)),
256-
Effect.provide(withClickHouse(getClickhouseClient(), orgId, DatasetRowRepositoryLive)),
255+
withPostgres(DatasetRepositoryLive, getPostgresClient(), orgId),
256+
withClickHouse(DatasetRowRepositoryLive, getClickhouseClient(), orgId),
257257
),
258258
)
259259

apps/web/src/domains/members/members.functions.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
withPostgres,
99
} from "@platform/db-postgres"
1010
import { createServerFn } from "@tanstack/react-start"
11-
import { Effect } from "effect"
11+
import { Effect, Layer } from "effect"
1212
import { z } from "zod"
1313
import { requireSession } from "../../server/auth.ts"
1414
import { getPostgresClient } from "../../server/clients.ts"
@@ -42,7 +42,7 @@ export const listMembers = createServerFn({ method: "GET" })
4242
const pendingInvites = yield* intentRepo.findPendingInvitesByOrganizationId(organizationId)
4343

4444
return [members, pendingInvites] as const
45-
}).pipe(Effect.provide(withPostgres(client, organizationId, MembershipRepositoryLive, AuthIntentRepositoryLive))),
45+
}).pipe(withPostgres(Layer.mergeAll(MembershipRepositoryLive, AuthIntentRepositoryLive), client, organizationId)),
4646
)
4747

4848
const activeMembers: MemberRecord[] = members.map((m) => ({
@@ -86,7 +86,7 @@ export const inviteMember = createServerFn({ method: "POST" })
8686
Effect.gen(function* () {
8787
const repo = yield* OrganizationRepository
8888
return yield* repo.findById(organizationId)
89-
}).pipe(Effect.provide(withPostgres(client, organizationId, OrganizationRepositoryLive))),
89+
}).pipe(withPostgres(OrganizationRepositoryLive, client, organizationId)),
9090
)
9191
const organizationName = org.name
9292

@@ -96,7 +96,7 @@ export const inviteMember = createServerFn({ method: "POST" })
9696
organizationId,
9797
organizationName,
9898
inviterName,
99-
}).pipe(Effect.provide(withPostgres(client, organizationId, AuthIntentRepositoryLive, UserRepositoryLive))),
99+
}).pipe(withPostgres(Layer.mergeAll(AuthIntentRepositoryLive, UserRepositoryLive), client, organizationId)),
100100
)
101101

102102
return { intentId: intent.id }
@@ -113,6 +113,6 @@ export const removeMember = createServerFn({ method: "POST" })
113113
removeMemberUseCase({
114114
membershipId: data.membershipId,
115115
requestingUserId: userId,
116-
}).pipe(Effect.provide(withPostgres(client, organizationId, MembershipRepositoryLive))),
116+
}).pipe(withPostgres(MembershipRepositoryLive, client, organizationId)),
117117
)
118118
})

apps/web/src/domains/organizations/organizations.functions.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import { MembershipRepository, OrganizationRepository } from "@domain/organizations"
2-
import {
3-
MembershipRepositoryLive,
4-
OrganizationRepositoryLive,
5-
SqlClientLive,
6-
withPostgres,
7-
} from "@platform/db-postgres"
2+
import { MembershipRepositoryLive, OrganizationRepositoryLive, withPostgres } from "@platform/db-postgres"
83
import { createServerFn } from "@tanstack/react-start"
94
import { Effect } from "effect"
105
import { requireSession } from "../../server/auth.ts"
@@ -26,7 +21,7 @@ export const countUserOrganizations = createServerFn({ method: "GET" })
2621
Effect.gen(function* () {
2722
const repo = yield* MembershipRepository
2823
return yield* repo.findByUserId(userId)
29-
}).pipe(Effect.provide(MembershipRepositoryLive), Effect.provide(SqlClientLive(adminClient))),
24+
}).pipe(withPostgres(MembershipRepositoryLive, adminClient)),
3025
)
3126
return members.length
3227
})
@@ -41,7 +36,7 @@ export const getOrganization = createServerFn({ method: "GET" })
4136
Effect.gen(function* () {
4237
const repo = yield* OrganizationRepository
4338
return yield* repo.findById(organizationId)
44-
}).pipe(Effect.provide(withPostgres(client, organizationId, OrganizationRepositoryLive))),
39+
}).pipe(withPostgres(OrganizationRepositoryLive, client, organizationId)),
4540
)
4641
return { id: org.id, name: org.name }
4742
})

apps/web/src/domains/projects/projects.functions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const listProjects = createServerFn({ method: "GET" })
4141
Effect.gen(function* () {
4242
const repo = yield* ProjectRepository
4343
return yield* repo.findAll()
44-
}).pipe(Effect.provide(withPostgres(client, organizationId, ProjectRepositoryLive))),
44+
}).pipe(withPostgres(ProjectRepositoryLive, client, organizationId)),
4545
)
4646

4747
return projects.map(toRecord)
@@ -59,7 +59,7 @@ export const createProject = createServerFn({ method: "POST" })
5959
name: data.name,
6060
...(data.description !== undefined ? { description: data.description } : {}),
6161
createdById: UserId(userId),
62-
}).pipe(Effect.provide(withPostgres(client, organizationId, ProjectRepositoryLive))),
62+
}).pipe(withPostgres(ProjectRepositoryLive, client, organizationId)),
6363
)
6464

6565
return toRecord(project)
@@ -83,7 +83,7 @@ export const updateProject = createServerFn({ method: "POST" })
8383
id: ProjectId(data.id),
8484
...(data.name !== undefined ? { name: data.name } : {}),
8585
...(data.description !== undefined ? { description: data.description } : {}),
86-
}).pipe(Effect.provide(withPostgres(client, organizationId, ProjectRepositoryLive))),
86+
}).pipe(withPostgres(ProjectRepositoryLive, client, organizationId)),
8787
)
8888

8989
return toRecord(updatedProject)
@@ -100,6 +100,6 @@ export const deleteProject = createServerFn({ method: "POST" })
100100
Effect.gen(function* () {
101101
const repo = yield* ProjectRepository
102102
return yield* repo.softDelete(ProjectId(data.id))
103-
}).pipe(Effect.provide(withPostgres(client, organizationId, ProjectRepositoryLive))),
103+
}).pipe(withPostgres(ProjectRepositoryLive, client, organizationId)),
104104
)
105105
})
Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
import type { ClickHouseClient } from "@clickhouse/client"
2-
import type { ChSqlClient, OrganizationId } from "@domain/shared"
3-
import { Layer } from "effect"
2+
import { OrganizationId, type OrganizationId as OrganizationIdType } from "@domain/shared"
3+
import { Effect, Layer } from "effect"
44
import { ChSqlClientLive } from "./ch-sql-client.ts"
55

66
/**
7-
* Bundle one or more ClickHouse repository layers with their ChSqlClient dependency.
7+
* Bundle one or more ClickHouse repository layers with their ChSqlClient dependency,
8+
* returning a pipe-compatible provider function.
89
*
910
* @example
1011
* ```ts
1112
* effect.pipe(
12-
* Effect.provide(withClickHouse(chClient, orgId, DatasetRowRepositoryLive)),
13+
* withClickHouse(DatasetRowRepositoryLive, chClient, orgId),
1314
* )
1415
* ```
1516
*/
16-
export const withClickHouse = (
17+
export const withClickHouse = <A, E, R>(
18+
layer: Layer.Layer<A, E, R>,
1719
client: ClickHouseClient,
18-
organizationId: OrganizationId,
19-
// biome-ignore lint/suspicious/noExplicitAny: Layer type variance requires any for repo union
20-
first: Layer.Layer<any, any, ChSqlClient>,
21-
// biome-ignore lint/suspicious/noExplicitAny: Layer type variance requires any for repo union
22-
...rest: Layer.Layer<any, any, ChSqlClient>[]
23-
) => Layer.mergeAll(first, ...rest).pipe(Layer.provideMerge(ChSqlClientLive(client, organizationId)))
20+
organizationId: OrganizationIdType = OrganizationId("system"),
21+
) => Effect.provide(layer.pipe(Layer.provideMerge(ChSqlClientLive(client, organizationId))))

0 commit comments

Comments
 (0)