refactor: normalize AccountRepo to canonical Effect service pattern (#22991)
This commit is contained in:
@@ -181,10 +181,10 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Account") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Account") {}
|
||||||
|
|
||||||
export const layer: Layer.Layer<Service, never, AccountRepo | HttpClient.HttpClient> = Layer.effect(
|
export const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpClient> = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const repo = yield* AccountRepo
|
const repo = yield* AccountRepo.Service
|
||||||
const http = yield* HttpClient.HttpClient
|
const http = yield* HttpClient.HttpClient
|
||||||
const httpRead = withTransientReadRetry(http)
|
const httpRead = withTransientReadRetry(http)
|
||||||
const httpOk = HttpClient.filterStatusOk(http)
|
const httpOk = HttpClient.filterStatusOk(http)
|
||||||
|
|||||||
@@ -13,154 +13,154 @@ type DbTransactionCallback<A> = Parameters<typeof Database.transaction<A>>[0]
|
|||||||
|
|
||||||
const ACCOUNT_STATE_ID = 1
|
const ACCOUNT_STATE_ID = 1
|
||||||
|
|
||||||
export namespace AccountRepo {
|
export interface Interface {
|
||||||
export interface Service {
|
readonly active: () => Effect.Effect<Option.Option<Info>, AccountRepoError>
|
||||||
readonly active: () => Effect.Effect<Option.Option<Info>, AccountRepoError>
|
readonly list: () => Effect.Effect<Info[], AccountRepoError>
|
||||||
readonly list: () => Effect.Effect<Info[], AccountRepoError>
|
readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountRepoError>
|
||||||
readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountRepoError>
|
readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountRepoError>
|
||||||
readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountRepoError>
|
readonly getRow: (accountID: AccountID) => Effect.Effect<Option.Option<AccountRow>, AccountRepoError>
|
||||||
readonly getRow: (accountID: AccountID) => Effect.Effect<Option.Option<AccountRow>, AccountRepoError>
|
readonly persistToken: (input: {
|
||||||
readonly persistToken: (input: {
|
accountID: AccountID
|
||||||
accountID: AccountID
|
accessToken: AccessToken
|
||||||
accessToken: AccessToken
|
refreshToken: RefreshToken
|
||||||
refreshToken: RefreshToken
|
expiry: Option.Option<number>
|
||||||
expiry: Option.Option<number>
|
}) => Effect.Effect<void, AccountRepoError>
|
||||||
}) => Effect.Effect<void, AccountRepoError>
|
readonly persistAccount: (input: {
|
||||||
readonly persistAccount: (input: {
|
id: AccountID
|
||||||
id: AccountID
|
email: string
|
||||||
email: string
|
url: string
|
||||||
url: string
|
accessToken: AccessToken
|
||||||
accessToken: AccessToken
|
refreshToken: RefreshToken
|
||||||
refreshToken: RefreshToken
|
expiry: number
|
||||||
expiry: number
|
orgID: Option.Option<OrgID>
|
||||||
orgID: Option.Option<OrgID>
|
}) => Effect.Effect<void, AccountRepoError>
|
||||||
}) => Effect.Effect<void, AccountRepoError>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccountRepo extends Context.Service<AccountRepo, AccountRepo.Service>()("@opencode/AccountRepo") {
|
export class Service extends Context.Service<Service, Interface>()("@opencode/AccountRepo") {}
|
||||||
static readonly layer: Layer.Layer<AccountRepo> = Layer.effect(
|
|
||||||
AccountRepo,
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const decode = Schema.decodeUnknownSync(Info)
|
|
||||||
|
|
||||||
const query = <A>(f: DbTransactionCallback<A>) =>
|
export const layer: Layer.Layer<Service> = Layer.effect(
|
||||||
Effect.try({
|
Service,
|
||||||
try: () => Database.use(f),
|
Effect.gen(function* () {
|
||||||
catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }),
|
const decode = Schema.decodeUnknownSync(Info)
|
||||||
|
|
||||||
|
const query = <A>(f: DbTransactionCallback<A>) =>
|
||||||
|
Effect.try({
|
||||||
|
try: () => Database.use(f),
|
||||||
|
catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const tx = <A>(f: DbTransactionCallback<A>) =>
|
||||||
|
Effect.try({
|
||||||
|
try: () => Database.transaction(f),
|
||||||
|
catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const current = (db: DbClient) => {
|
||||||
|
const state = db.select().from(AccountStateTable).where(eq(AccountStateTable.id, ACCOUNT_STATE_ID)).get()
|
||||||
|
if (!state?.active_account_id) return
|
||||||
|
const account = db.select().from(AccountTable).where(eq(AccountTable.id, state.active_account_id)).get()
|
||||||
|
if (!account) return
|
||||||
|
return { ...account, active_org_id: state.active_org_id ?? null }
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = (db: DbClient, accountID: AccountID, orgID: Option.Option<OrgID>) => {
|
||||||
|
const id = Option.getOrNull(orgID)
|
||||||
|
return db
|
||||||
|
.insert(AccountStateTable)
|
||||||
|
.values({ id: ACCOUNT_STATE_ID, active_account_id: accountID, active_org_id: id })
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: AccountStateTable.id,
|
||||||
|
set: { active_account_id: accountID, active_org_id: id },
|
||||||
})
|
})
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
const tx = <A>(f: DbTransactionCallback<A>) =>
|
const active = Effect.fn("AccountRepo.active")(() =>
|
||||||
Effect.try({
|
query((db) => current(db)).pipe(Effect.map((row) => (row ? Option.some(decode(row)) : Option.none()))),
|
||||||
try: () => Database.transaction(f),
|
)
|
||||||
catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }),
|
|
||||||
})
|
|
||||||
|
|
||||||
const current = (db: DbClient) => {
|
const list = Effect.fn("AccountRepo.list")(() =>
|
||||||
const state = db.select().from(AccountStateTable).where(eq(AccountStateTable.id, ACCOUNT_STATE_ID)).get()
|
query((db) =>
|
||||||
if (!state?.active_account_id) return
|
db
|
||||||
const account = db.select().from(AccountTable).where(eq(AccountTable.id, state.active_account_id)).get()
|
.select()
|
||||||
if (!account) return
|
.from(AccountTable)
|
||||||
return { ...account, active_org_id: state.active_org_id ?? null }
|
.all()
|
||||||
}
|
.map((row: AccountRow) => decode({ ...row, active_org_id: null })),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const state = (db: DbClient, accountID: AccountID, orgID: Option.Option<OrgID>) => {
|
const remove = Effect.fn("AccountRepo.remove")((accountID: AccountID) =>
|
||||||
const id = Option.getOrNull(orgID)
|
tx((db) => {
|
||||||
return db
|
db.update(AccountStateTable)
|
||||||
.insert(AccountStateTable)
|
.set({ active_account_id: null, active_org_id: null })
|
||||||
.values({ id: ACCOUNT_STATE_ID, active_account_id: accountID, active_org_id: id })
|
.where(eq(AccountStateTable.active_account_id, accountID))
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: AccountStateTable.id,
|
|
||||||
set: { active_account_id: accountID, active_org_id: id },
|
|
||||||
})
|
|
||||||
.run()
|
.run()
|
||||||
}
|
db.delete(AccountTable).where(eq(AccountTable.id, accountID)).run()
|
||||||
|
}).pipe(Effect.asVoid),
|
||||||
|
)
|
||||||
|
|
||||||
const active = Effect.fn("AccountRepo.active")(() =>
|
const use = Effect.fn("AccountRepo.use")((accountID: AccountID, orgID: Option.Option<OrgID>) =>
|
||||||
query((db) => current(db)).pipe(Effect.map((row) => (row ? Option.some(decode(row)) : Option.none()))),
|
query((db) => state(db, accountID, orgID)).pipe(Effect.asVoid),
|
||||||
)
|
)
|
||||||
|
|
||||||
const list = Effect.fn("AccountRepo.list")(() =>
|
const getRow = Effect.fn("AccountRepo.getRow")((accountID: AccountID) =>
|
||||||
query((db) =>
|
query((db) => db.select().from(AccountTable).where(eq(AccountTable.id, accountID)).get()).pipe(
|
||||||
db
|
Effect.map(Option.fromNullishOr),
|
||||||
.select()
|
),
|
||||||
.from(AccountTable)
|
)
|
||||||
.all()
|
|
||||||
.map((row: AccountRow) => decode({ ...row, active_org_id: null })),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const remove = Effect.fn("AccountRepo.remove")((accountID: AccountID) =>
|
const persistToken = Effect.fn("AccountRepo.persistToken")((input) =>
|
||||||
tx((db) => {
|
query((db) =>
|
||||||
db.update(AccountStateTable)
|
db
|
||||||
.set({ active_account_id: null, active_org_id: null })
|
.update(AccountTable)
|
||||||
.where(eq(AccountStateTable.active_account_id, accountID))
|
.set({
|
||||||
.run()
|
access_token: input.accessToken,
|
||||||
db.delete(AccountTable).where(eq(AccountTable.id, accountID)).run()
|
refresh_token: input.refreshToken,
|
||||||
}).pipe(Effect.asVoid),
|
token_expiry: Option.getOrNull(input.expiry),
|
||||||
)
|
})
|
||||||
|
.where(eq(AccountTable.id, input.accountID))
|
||||||
|
.run(),
|
||||||
|
).pipe(Effect.asVoid),
|
||||||
|
)
|
||||||
|
|
||||||
const use = Effect.fn("AccountRepo.use")((accountID: AccountID, orgID: Option.Option<OrgID>) =>
|
const persistAccount = Effect.fn("AccountRepo.persistAccount")((input) =>
|
||||||
query((db) => state(db, accountID, orgID)).pipe(Effect.asVoid),
|
tx((db) => {
|
||||||
)
|
const url = normalizeServerUrl(input.url)
|
||||||
|
|
||||||
const getRow = Effect.fn("AccountRepo.getRow")((accountID: AccountID) =>
|
db.insert(AccountTable)
|
||||||
query((db) => db.select().from(AccountTable).where(eq(AccountTable.id, accountID)).get()).pipe(
|
.values({
|
||||||
Effect.map(Option.fromNullishOr),
|
id: input.id,
|
||||||
),
|
email: input.email,
|
||||||
)
|
url,
|
||||||
|
access_token: input.accessToken,
|
||||||
const persistToken = Effect.fn("AccountRepo.persistToken")((input) =>
|
refresh_token: input.refreshToken,
|
||||||
query((db) =>
|
token_expiry: input.expiry,
|
||||||
db
|
})
|
||||||
.update(AccountTable)
|
.onConflictDoUpdate({
|
||||||
.set({
|
target: AccountTable.id,
|
||||||
access_token: input.accessToken,
|
set: {
|
||||||
refresh_token: input.refreshToken,
|
|
||||||
token_expiry: Option.getOrNull(input.expiry),
|
|
||||||
})
|
|
||||||
.where(eq(AccountTable.id, input.accountID))
|
|
||||||
.run(),
|
|
||||||
).pipe(Effect.asVoid),
|
|
||||||
)
|
|
||||||
|
|
||||||
const persistAccount = Effect.fn("AccountRepo.persistAccount")((input) =>
|
|
||||||
tx((db) => {
|
|
||||||
const url = normalizeServerUrl(input.url)
|
|
||||||
|
|
||||||
db.insert(AccountTable)
|
|
||||||
.values({
|
|
||||||
id: input.id,
|
|
||||||
email: input.email,
|
email: input.email,
|
||||||
url,
|
url,
|
||||||
access_token: input.accessToken,
|
access_token: input.accessToken,
|
||||||
refresh_token: input.refreshToken,
|
refresh_token: input.refreshToken,
|
||||||
token_expiry: input.expiry,
|
token_expiry: input.expiry,
|
||||||
})
|
},
|
||||||
.onConflictDoUpdate({
|
})
|
||||||
target: AccountTable.id,
|
.run()
|
||||||
set: {
|
void state(db, input.id, input.orgID)
|
||||||
email: input.email,
|
}).pipe(Effect.asVoid),
|
||||||
url,
|
)
|
||||||
access_token: input.accessToken,
|
|
||||||
refresh_token: input.refreshToken,
|
|
||||||
token_expiry: input.expiry,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.run()
|
|
||||||
void state(db, input.id, input.orgID)
|
|
||||||
}).pipe(Effect.asVoid),
|
|
||||||
)
|
|
||||||
|
|
||||||
return AccountRepo.of({
|
return Service.of({
|
||||||
active,
|
active,
|
||||||
list,
|
list,
|
||||||
remove,
|
remove,
|
||||||
use,
|
use,
|
||||||
getRow,
|
getRow,
|
||||||
persistToken,
|
persistToken,
|
||||||
persistAccount,
|
persistAccount,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
export * as AccountRepo from "./repo"
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ const it = testEffect(Layer.merge(AccountRepo.layer, truncate))
|
|||||||
|
|
||||||
it.live("list returns empty when no accounts exist", () =>
|
it.live("list returns empty when no accounts exist", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const accounts = yield* AccountRepo.use((r) => r.list())
|
const accounts = yield* AccountRepo.Service.use((r) => r.list())
|
||||||
expect(accounts).toEqual([])
|
expect(accounts).toEqual([])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("active returns none when no accounts exist", () =>
|
it.live("active returns none when no accounts exist", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const active = yield* AccountRepo.use((r) => r.active())
|
const active = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
expect(Option.isNone(active)).toBe(true)
|
expect(Option.isNone(active)).toBe(true)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -33,7 +33,7 @@ it.live("active returns none when no accounts exist", () =>
|
|||||||
it.live("persistAccount inserts and getRow retrieves", () =>
|
it.live("persistAccount inserts and getRow retrieves", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "test@example.com",
|
email: "test@example.com",
|
||||||
@@ -45,13 +45,13 @@ it.live("persistAccount inserts and getRow retrieves", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
expect(Option.isSome(row)).toBe(true)
|
expect(Option.isSome(row)).toBe(true)
|
||||||
const value = Option.getOrThrow(row)
|
const value = Option.getOrThrow(row)
|
||||||
expect(value.id).toBe(AccountID.make("user-1"))
|
expect(value.id).toBe(AccountID.make("user-1"))
|
||||||
expect(value.email).toBe("test@example.com")
|
expect(value.email).toBe("test@example.com")
|
||||||
|
|
||||||
const active = yield* AccountRepo.use((r) => r.active())
|
const active = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-1"))
|
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-1"))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -60,7 +60,7 @@ it.live("persistAccount normalizes trailing slashes in stored server URLs", () =
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "test@example.com",
|
email: "test@example.com",
|
||||||
@@ -72,9 +72,9 @@ it.live("persistAccount normalizes trailing slashes in stored server URLs", () =
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
const active = yield* AccountRepo.use((r) => r.active())
|
const active = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
const list = yield* AccountRepo.use((r) => r.list())
|
const list = yield* AccountRepo.Service.use((r) => r.list())
|
||||||
|
|
||||||
expect(Option.getOrThrow(row).url).toBe("https://control.example.com")
|
expect(Option.getOrThrow(row).url).toBe("https://control.example.com")
|
||||||
expect(Option.getOrThrow(active).url).toBe("https://control.example.com")
|
expect(Option.getOrThrow(active).url).toBe("https://control.example.com")
|
||||||
@@ -87,7 +87,7 @@ it.live("persistAccount sets the active account and org", () =>
|
|||||||
const id1 = AccountID.make("user-1")
|
const id1 = AccountID.make("user-1")
|
||||||
const id2 = AccountID.make("user-2")
|
const id2 = AccountID.make("user-2")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id: id1,
|
id: id1,
|
||||||
email: "first@example.com",
|
email: "first@example.com",
|
||||||
@@ -99,7 +99,7 @@ it.live("persistAccount sets the active account and org", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id: id2,
|
id: id2,
|
||||||
email: "second@example.com",
|
email: "second@example.com",
|
||||||
@@ -112,7 +112,7 @@ it.live("persistAccount sets the active account and org", () =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Last persisted account is active with its org
|
// Last persisted account is active with its org
|
||||||
const active = yield* AccountRepo.use((r) => r.active())
|
const active = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
expect(Option.isSome(active)).toBe(true)
|
expect(Option.isSome(active)).toBe(true)
|
||||||
expect(Option.getOrThrow(active).id).toBe(AccountID.make("user-2"))
|
expect(Option.getOrThrow(active).id).toBe(AccountID.make("user-2"))
|
||||||
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
|
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
|
||||||
@@ -124,7 +124,7 @@ it.live("list returns all accounts", () =>
|
|||||||
const id1 = AccountID.make("user-1")
|
const id1 = AccountID.make("user-1")
|
||||||
const id2 = AccountID.make("user-2")
|
const id2 = AccountID.make("user-2")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id: id1,
|
id: id1,
|
||||||
email: "a@example.com",
|
email: "a@example.com",
|
||||||
@@ -136,7 +136,7 @@ it.live("list returns all accounts", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id: id2,
|
id: id2,
|
||||||
email: "b@example.com",
|
email: "b@example.com",
|
||||||
@@ -148,7 +148,7 @@ it.live("list returns all accounts", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const accounts = yield* AccountRepo.use((r) => r.list())
|
const accounts = yield* AccountRepo.Service.use((r) => r.list())
|
||||||
expect(accounts.length).toBe(2)
|
expect(accounts.length).toBe(2)
|
||||||
expect(accounts.map((a) => a.email).sort()).toEqual(["a@example.com", "b@example.com"])
|
expect(accounts.map((a) => a.email).sort()).toEqual(["a@example.com", "b@example.com"])
|
||||||
}),
|
}),
|
||||||
@@ -158,7 +158,7 @@ it.live("remove deletes an account", () =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "test@example.com",
|
email: "test@example.com",
|
||||||
@@ -170,9 +170,9 @@ it.live("remove deletes an account", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) => r.remove(id))
|
yield* AccountRepo.Service.use((r) => r.remove(id))
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
expect(Option.isNone(row)).toBe(true)
|
expect(Option.isNone(row)).toBe(true)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -182,7 +182,7 @@ it.live("use stores the selected org and marks the account active", () =>
|
|||||||
const id1 = AccountID.make("user-1")
|
const id1 = AccountID.make("user-1")
|
||||||
const id2 = AccountID.make("user-2")
|
const id2 = AccountID.make("user-2")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id: id1,
|
id: id1,
|
||||||
email: "first@example.com",
|
email: "first@example.com",
|
||||||
@@ -194,7 +194,7 @@ it.live("use stores the selected org and marks the account active", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id: id2,
|
id: id2,
|
||||||
email: "second@example.com",
|
email: "second@example.com",
|
||||||
@@ -206,13 +206,13 @@ it.live("use stores the selected org and marks the account active", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) => r.use(id1, Option.some(OrgID.make("org-99"))))
|
yield* AccountRepo.Service.use((r) => r.use(id1, Option.some(OrgID.make("org-99"))))
|
||||||
const active1 = yield* AccountRepo.use((r) => r.active())
|
const active1 = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
expect(Option.getOrThrow(active1).id).toBe(id1)
|
expect(Option.getOrThrow(active1).id).toBe(id1)
|
||||||
expect(Option.getOrThrow(active1).active_org_id).toBe(OrgID.make("org-99"))
|
expect(Option.getOrThrow(active1).active_org_id).toBe(OrgID.make("org-99"))
|
||||||
|
|
||||||
yield* AccountRepo.use((r) => r.use(id1, Option.none()))
|
yield* AccountRepo.Service.use((r) => r.use(id1, Option.none()))
|
||||||
const active2 = yield* AccountRepo.use((r) => r.active())
|
const active2 = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
expect(Option.getOrThrow(active2).active_org_id).toBeNull()
|
expect(Option.getOrThrow(active2).active_org_id).toBeNull()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -221,7 +221,7 @@ it.live("persistToken updates token fields", () =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "test@example.com",
|
email: "test@example.com",
|
||||||
@@ -234,7 +234,7 @@ it.live("persistToken updates token fields", () =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
const expiry = Date.now() + 7200_000
|
const expiry = Date.now() + 7200_000
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistToken({
|
r.persistToken({
|
||||||
accountID: id,
|
accountID: id,
|
||||||
accessToken: AccessToken.make("new_token"),
|
accessToken: AccessToken.make("new_token"),
|
||||||
@@ -243,7 +243,7 @@ it.live("persistToken updates token fields", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
const value = Option.getOrThrow(row)
|
const value = Option.getOrThrow(row)
|
||||||
expect(value.access_token).toBe(AccessToken.make("new_token"))
|
expect(value.access_token).toBe(AccessToken.make("new_token"))
|
||||||
expect(value.refresh_token).toBe(RefreshToken.make("new_refresh"))
|
expect(value.refresh_token).toBe(RefreshToken.make("new_refresh"))
|
||||||
@@ -255,7 +255,7 @@ it.live("persistToken with no expiry sets token_expiry to null", () =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "test@example.com",
|
email: "test@example.com",
|
||||||
@@ -267,7 +267,7 @@ it.live("persistToken with no expiry sets token_expiry to null", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistToken({
|
r.persistToken({
|
||||||
accountID: id,
|
accountID: id,
|
||||||
accessToken: AccessToken.make("new_token"),
|
accessToken: AccessToken.make("new_token"),
|
||||||
@@ -276,7 +276,7 @@ it.live("persistToken with no expiry sets token_expiry to null", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
expect(Option.getOrThrow(row).token_expiry).toBeNull()
|
expect(Option.getOrThrow(row).token_expiry).toBeNull()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -285,7 +285,7 @@ it.live("persistAccount upserts on conflict", () =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "test@example.com",
|
email: "test@example.com",
|
||||||
@@ -297,7 +297,7 @@ it.live("persistAccount upserts on conflict", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "test@example.com",
|
email: "test@example.com",
|
||||||
@@ -309,14 +309,14 @@ it.live("persistAccount upserts on conflict", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const accounts = yield* AccountRepo.use((r) => r.list())
|
const accounts = yield* AccountRepo.Service.use((r) => r.list())
|
||||||
expect(accounts.length).toBe(1)
|
expect(accounts.length).toBe(1)
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
const value = Option.getOrThrow(row)
|
const value = Option.getOrThrow(row)
|
||||||
expect(value.access_token).toBe(AccessToken.make("at_v2"))
|
expect(value.access_token).toBe(AccessToken.make("at_v2"))
|
||||||
|
|
||||||
const active = yield* AccountRepo.use((r) => r.active())
|
const active = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
|
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -325,7 +325,7 @@ it.live("remove clears active state when deleting the active account", () =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "test@example.com",
|
email: "test@example.com",
|
||||||
@@ -337,16 +337,16 @@ it.live("remove clears active state when deleting the active account", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) => r.remove(id))
|
yield* AccountRepo.Service.use((r) => r.remove(id))
|
||||||
|
|
||||||
const active = yield* AccountRepo.use((r) => r.active())
|
const active = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
expect(Option.isNone(active)).toBe(true)
|
expect(Option.isNone(active)).toBe(true)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("getRow returns none for nonexistent account", () =>
|
it.live("getRow returns none for nonexistent account", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(AccountID.make("nope")))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(AccountID.make("nope")))
|
||||||
expect(Option.isNone(row)).toBe(true)
|
expect(Option.isNone(row)).toBe(true)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ it.live("login maps transport failures to account transport errors", () =>
|
|||||||
|
|
||||||
it.live("orgsByAccount groups orgs per account", () =>
|
it.live("orgsByAccount groups orgs per account", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id: AccountID.make("user-1"),
|
id: AccountID.make("user-1"),
|
||||||
email: "one@example.com",
|
email: "one@example.com",
|
||||||
@@ -134,7 +134,7 @@ it.live("orgsByAccount groups orgs per account", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id: AccountID.make("user-2"),
|
id: AccountID.make("user-2"),
|
||||||
email: "two@example.com",
|
email: "two@example.com",
|
||||||
@@ -177,7 +177,7 @@ it.live("token refresh persists the new token", () =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
@@ -206,7 +206,7 @@ it.live("token refresh persists the new token", () =>
|
|||||||
expect(Option.getOrThrow(token)).toBeDefined()
|
expect(Option.getOrThrow(token)).toBeDefined()
|
||||||
expect(String(Option.getOrThrow(token))).toBe("at_new")
|
expect(String(Option.getOrThrow(token))).toBe("at_new")
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
const value = Option.getOrThrow(row)
|
const value = Option.getOrThrow(row)
|
||||||
expect(value.access_token).toBe(AccessToken.make("at_new"))
|
expect(value.access_token).toBe(AccessToken.make("at_new"))
|
||||||
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
|
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
|
||||||
@@ -218,7 +218,7 @@ it.live("token refreshes before expiry when inside the eager refresh window", ()
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
@@ -251,7 +251,7 @@ it.live("token refreshes before expiry when inside the eager refresh window", ()
|
|||||||
expect(String(Option.getOrThrow(token))).toBe("at_new")
|
expect(String(Option.getOrThrow(token))).toBe("at_new")
|
||||||
expect(refreshCalls).toBe(1)
|
expect(refreshCalls).toBe(1)
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
const value = Option.getOrThrow(row)
|
const value = Option.getOrThrow(row)
|
||||||
expect(value.access_token).toBe(AccessToken.make("at_new"))
|
expect(value.access_token).toBe(AccessToken.make("at_new"))
|
||||||
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
|
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
|
||||||
@@ -262,7 +262,7 @@ it.live("concurrent config and token requests coalesce token refresh", () =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
@@ -315,7 +315,7 @@ it.live("concurrent config and token requests coalesce token refresh", () =>
|
|||||||
expect(String(Option.getOrThrow(token))).toBe("at_new")
|
expect(String(Option.getOrThrow(token))).toBe("at_new")
|
||||||
expect(refreshCalls).toBe(1)
|
expect(refreshCalls).toBe(1)
|
||||||
|
|
||||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
|
||||||
const value = Option.getOrThrow(row)
|
const value = Option.getOrThrow(row)
|
||||||
expect(value.access_token).toBe(AccessToken.make("at_new"))
|
expect(value.access_token).toBe(AccessToken.make("at_new"))
|
||||||
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
|
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
|
||||||
@@ -326,7 +326,7 @@ it.live("config sends the selected org header", () =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const id = AccountID.make("user-1")
|
const id = AccountID.make("user-1")
|
||||||
|
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.Service.use((r) =>
|
||||||
r.persistAccount({
|
r.persistAccount({
|
||||||
id,
|
id,
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
@@ -388,7 +388,7 @@ it.live("poll stores the account and first org on success", () =>
|
|||||||
expect(res.email).toBe("user@example.com")
|
expect(res.email).toBe("user@example.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
const active = yield* AccountRepo.use((r) => r.active())
|
const active = yield* AccountRepo.Service.use((r) => r.active())
|
||||||
expect(Option.getOrThrow(active)).toEqual(
|
expect(Option.getOrThrow(active)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "user-1",
|
id: "user-1",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const share = (id: SessionID) =>
|
|||||||
Database.use((db) => db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, id)).get())
|
Database.use((db) => db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, id)).get())
|
||||||
|
|
||||||
const seed = (url: string, org?: string) =>
|
const seed = (url: string, org?: string) =>
|
||||||
AccountRepo.use((repo) =>
|
AccountRepo.Service.use((repo) =>
|
||||||
repo.persistAccount({
|
repo.persistAccount({
|
||||||
id: AccountID.make("account-1"),
|
id: AccountID.make("account-1"),
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
|
|||||||
Reference in New Issue
Block a user