Skip to content

Commit 9c87a14

Browse files
authored
refactor: normalize AccountRepo to canonical Effect service pattern (#22991)
1 parent 5b9fa32 commit 9c87a14

5 files changed

Lines changed: 192 additions & 192 deletions

File tree

packages/opencode/src/account/account.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,10 @@ export interface Interface {
181181

182182
export class Service extends Context.Service<Service, Interface>()("@opencode/Account") {}
183183

184-
export const layer: Layer.Layer<Service, never, AccountRepo | HttpClient.HttpClient> = Layer.effect(
184+
export const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpClient> = Layer.effect(
185185
Service,
186186
Effect.gen(function* () {
187-
const repo = yield* AccountRepo
187+
const repo = yield* AccountRepo.Service
188188
const http = yield* HttpClient.HttpClient
189189
const httpRead = withTransientReadRetry(http)
190190
const httpOk = HttpClient.filterStatusOk(http)

packages/opencode/src/account/repo.ts

Lines changed: 140 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -13,154 +13,154 @@ type DbTransactionCallback<A> = Parameters<typeof Database.transaction<A>>[0]
1313

1414
const ACCOUNT_STATE_ID = 1
1515

16-
export namespace AccountRepo {
17-
export interface Service {
18-
readonly active: () => Effect.Effect<Option.Option<Info>, AccountRepoError>
19-
readonly list: () => Effect.Effect<Info[], AccountRepoError>
20-
readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountRepoError>
21-
readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountRepoError>
22-
readonly getRow: (accountID: AccountID) => Effect.Effect<Option.Option<AccountRow>, AccountRepoError>
23-
readonly persistToken: (input: {
24-
accountID: AccountID
25-
accessToken: AccessToken
26-
refreshToken: RefreshToken
27-
expiry: Option.Option<number>
28-
}) => Effect.Effect<void, AccountRepoError>
29-
readonly persistAccount: (input: {
30-
id: AccountID
31-
email: string
32-
url: string
33-
accessToken: AccessToken
34-
refreshToken: RefreshToken
35-
expiry: number
36-
orgID: Option.Option<OrgID>
37-
}) => Effect.Effect<void, AccountRepoError>
38-
}
16+
export interface Interface {
17+
readonly active: () => Effect.Effect<Option.Option<Info>, AccountRepoError>
18+
readonly list: () => Effect.Effect<Info[], AccountRepoError>
19+
readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountRepoError>
20+
readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountRepoError>
21+
readonly getRow: (accountID: AccountID) => Effect.Effect<Option.Option<AccountRow>, AccountRepoError>
22+
readonly persistToken: (input: {
23+
accountID: AccountID
24+
accessToken: AccessToken
25+
refreshToken: RefreshToken
26+
expiry: Option.Option<number>
27+
}) => Effect.Effect<void, AccountRepoError>
28+
readonly persistAccount: (input: {
29+
id: AccountID
30+
email: string
31+
url: string
32+
accessToken: AccessToken
33+
refreshToken: RefreshToken
34+
expiry: number
35+
orgID: Option.Option<OrgID>
36+
}) => Effect.Effect<void, AccountRepoError>
3937
}
4038

41-
export class AccountRepo extends Context.Service<AccountRepo, AccountRepo.Service>()("@opencode/AccountRepo") {
42-
static readonly layer: Layer.Layer<AccountRepo> = Layer.effect(
43-
AccountRepo,
44-
Effect.gen(function* () {
45-
const decode = Schema.decodeUnknownSync(Info)
39+
export class Service extends Context.Service<Service, Interface>()("@opencode/AccountRepo") {}
4640

47-
const query = <A>(f: DbTransactionCallback<A>) =>
48-
Effect.try({
49-
try: () => Database.use(f),
50-
catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }),
51-
})
41+
export const layer: Layer.Layer<Service> = Layer.effect(
42+
Service,
43+
Effect.gen(function* () {
44+
const decode = Schema.decodeUnknownSync(Info)
5245

53-
const tx = <A>(f: DbTransactionCallback<A>) =>
54-
Effect.try({
55-
try: () => Database.transaction(f),
56-
catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }),
57-
})
46+
const query = <A>(f: DbTransactionCallback<A>) =>
47+
Effect.try({
48+
try: () => Database.use(f),
49+
catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }),
50+
})
5851

59-
const current = (db: DbClient) => {
60-
const state = db.select().from(AccountStateTable).where(eq(AccountStateTable.id, ACCOUNT_STATE_ID)).get()
61-
if (!state?.active_account_id) return
62-
const account = db.select().from(AccountTable).where(eq(AccountTable.id, state.active_account_id)).get()
63-
if (!account) return
64-
return { ...account, active_org_id: state.active_org_id ?? null }
65-
}
66-
67-
const state = (db: DbClient, accountID: AccountID, orgID: Option.Option<OrgID>) => {
68-
const id = Option.getOrNull(orgID)
69-
return db
70-
.insert(AccountStateTable)
71-
.values({ id: ACCOUNT_STATE_ID, active_account_id: accountID, active_org_id: id })
72-
.onConflictDoUpdate({
73-
target: AccountStateTable.id,
74-
set: { active_account_id: accountID, active_org_id: id },
75-
})
52+
const tx = <A>(f: DbTransactionCallback<A>) =>
53+
Effect.try({
54+
try: () => Database.transaction(f),
55+
catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }),
56+
})
57+
58+
const current = (db: DbClient) => {
59+
const state = db.select().from(AccountStateTable).where(eq(AccountStateTable.id, ACCOUNT_STATE_ID)).get()
60+
if (!state?.active_account_id) return
61+
const account = db.select().from(AccountTable).where(eq(AccountTable.id, state.active_account_id)).get()
62+
if (!account) return
63+
return { ...account, active_org_id: state.active_org_id ?? null }
64+
}
65+
66+
const state = (db: DbClient, accountID: AccountID, orgID: Option.Option<OrgID>) => {
67+
const id = Option.getOrNull(orgID)
68+
return db
69+
.insert(AccountStateTable)
70+
.values({ id: ACCOUNT_STATE_ID, active_account_id: accountID, active_org_id: id })
71+
.onConflictDoUpdate({
72+
target: AccountStateTable.id,
73+
set: { active_account_id: accountID, active_org_id: id },
74+
})
75+
.run()
76+
}
77+
78+
const active = Effect.fn("AccountRepo.active")(() =>
79+
query((db) => current(db)).pipe(Effect.map((row) => (row ? Option.some(decode(row)) : Option.none()))),
80+
)
81+
82+
const list = Effect.fn("AccountRepo.list")(() =>
83+
query((db) =>
84+
db
85+
.select()
86+
.from(AccountTable)
87+
.all()
88+
.map((row: AccountRow) => decode({ ...row, active_org_id: null })),
89+
),
90+
)
91+
92+
const remove = Effect.fn("AccountRepo.remove")((accountID: AccountID) =>
93+
tx((db) => {
94+
db.update(AccountStateTable)
95+
.set({ active_account_id: null, active_org_id: null })
96+
.where(eq(AccountStateTable.active_account_id, accountID))
7697
.run()
77-
}
78-
79-
const active = Effect.fn("AccountRepo.active")(() =>
80-
query((db) => current(db)).pipe(Effect.map((row) => (row ? Option.some(decode(row)) : Option.none()))),
81-
)
82-
83-
const list = Effect.fn("AccountRepo.list")(() =>
84-
query((db) =>
85-
db
86-
.select()
87-
.from(AccountTable)
88-
.all()
89-
.map((row: AccountRow) => decode({ ...row, active_org_id: null })),
90-
),
91-
)
92-
93-
const remove = Effect.fn("AccountRepo.remove")((accountID: AccountID) =>
94-
tx((db) => {
95-
db.update(AccountStateTable)
96-
.set({ active_account_id: null, active_org_id: null })
97-
.where(eq(AccountStateTable.active_account_id, accountID))
98-
.run()
99-
db.delete(AccountTable).where(eq(AccountTable.id, accountID)).run()
100-
}).pipe(Effect.asVoid),
101-
)
102-
103-
const use = Effect.fn("AccountRepo.use")((accountID: AccountID, orgID: Option.Option<OrgID>) =>
104-
query((db) => state(db, accountID, orgID)).pipe(Effect.asVoid),
105-
)
106-
107-
const getRow = Effect.fn("AccountRepo.getRow")((accountID: AccountID) =>
108-
query((db) => db.select().from(AccountTable).where(eq(AccountTable.id, accountID)).get()).pipe(
109-
Effect.map(Option.fromNullishOr),
110-
),
111-
)
112-
113-
const persistToken = Effect.fn("AccountRepo.persistToken")((input) =>
114-
query((db) =>
115-
db
116-
.update(AccountTable)
117-
.set({
118-
access_token: input.accessToken,
119-
refresh_token: input.refreshToken,
120-
token_expiry: Option.getOrNull(input.expiry),
121-
})
122-
.where(eq(AccountTable.id, input.accountID))
123-
.run(),
124-
).pipe(Effect.asVoid),
125-
)
126-
127-
const persistAccount = Effect.fn("AccountRepo.persistAccount")((input) =>
128-
tx((db) => {
129-
const url = normalizeServerUrl(input.url)
130-
131-
db.insert(AccountTable)
132-
.values({
133-
id: input.id,
98+
db.delete(AccountTable).where(eq(AccountTable.id, accountID)).run()
99+
}).pipe(Effect.asVoid),
100+
)
101+
102+
const use = Effect.fn("AccountRepo.use")((accountID: AccountID, orgID: Option.Option<OrgID>) =>
103+
query((db) => state(db, accountID, orgID)).pipe(Effect.asVoid),
104+
)
105+
106+
const getRow = Effect.fn("AccountRepo.getRow")((accountID: AccountID) =>
107+
query((db) => db.select().from(AccountTable).where(eq(AccountTable.id, accountID)).get()).pipe(
108+
Effect.map(Option.fromNullishOr),
109+
),
110+
)
111+
112+
const persistToken = Effect.fn("AccountRepo.persistToken")((input) =>
113+
query((db) =>
114+
db
115+
.update(AccountTable)
116+
.set({
117+
access_token: input.accessToken,
118+
refresh_token: input.refreshToken,
119+
token_expiry: Option.getOrNull(input.expiry),
120+
})
121+
.where(eq(AccountTable.id, input.accountID))
122+
.run(),
123+
).pipe(Effect.asVoid),
124+
)
125+
126+
const persistAccount = Effect.fn("AccountRepo.persistAccount")((input) =>
127+
tx((db) => {
128+
const url = normalizeServerUrl(input.url)
129+
130+
db.insert(AccountTable)
131+
.values({
132+
id: input.id,
133+
email: input.email,
134+
url,
135+
access_token: input.accessToken,
136+
refresh_token: input.refreshToken,
137+
token_expiry: input.expiry,
138+
})
139+
.onConflictDoUpdate({
140+
target: AccountTable.id,
141+
set: {
134142
email: input.email,
135143
url,
136144
access_token: input.accessToken,
137145
refresh_token: input.refreshToken,
138146
token_expiry: input.expiry,
139-
})
140-
.onConflictDoUpdate({
141-
target: AccountTable.id,
142-
set: {
143-
email: input.email,
144-
url,
145-
access_token: input.accessToken,
146-
refresh_token: input.refreshToken,
147-
token_expiry: input.expiry,
148-
},
149-
})
150-
.run()
151-
void state(db, input.id, input.orgID)
152-
}).pipe(Effect.asVoid),
153-
)
154-
155-
return AccountRepo.of({
156-
active,
157-
list,
158-
remove,
159-
use,
160-
getRow,
161-
persistToken,
162-
persistAccount,
163-
})
164-
}),
165-
)
166-
}
147+
},
148+
})
149+
.run()
150+
void state(db, input.id, input.orgID)
151+
}).pipe(Effect.asVoid),
152+
)
153+
154+
return Service.of({
155+
active,
156+
list,
157+
remove,
158+
use,
159+
getRow,
160+
persistToken,
161+
persistAccount,
162+
})
163+
}),
164+
)
165+
166+
export * as AccountRepo from "./repo"

0 commit comments

Comments
 (0)