mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-25 12:23:34 +00:00
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com> Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: James Long <longster@gmail.com> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Ben Guthrie <benjee.012@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com>
140 lines
4.8 KiB
TypeScript
140 lines
4.8 KiB
TypeScript
export * as Credential from "./credential"
|
|
|
|
import { asc, eq } from "drizzle-orm"
|
|
import { Context, Effect, Layer, Schema } from "effect"
|
|
import { Credential } from "@opencode-ai/schema/credential"
|
|
import { Integration } from "@opencode-ai/schema/integration"
|
|
import { Database } from "./database/database"
|
|
import { makeGlobalNode } from "./effect/app-node"
|
|
import { CredentialTable } from "./credential/sql"
|
|
|
|
export const ID = Credential.ID
|
|
export type ID = Credential.ID
|
|
|
|
export const OAuth = Credential.OAuth
|
|
export type OAuth = Credential.OAuth
|
|
|
|
export const Key = Credential.Key
|
|
export type Key = Credential.Key
|
|
|
|
export const Value = Credential.Value
|
|
export type Value = Credential.Value
|
|
|
|
export class Info extends Schema.Class<Info>("Credential.Info")({
|
|
id: ID,
|
|
integrationID: Integration.ID,
|
|
label: Schema.String,
|
|
value: Value,
|
|
}) {}
|
|
|
|
export interface Interface {
|
|
/** Returns every stored credential. */
|
|
readonly all: () => Effect.Effect<Info[]>
|
|
/** Returns stored credentials belonging to one integration. */
|
|
readonly list: (integrationID: Integration.ID) => Effect.Effect<Info[]>
|
|
/** Returns one stored credential by ID. */
|
|
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
|
/** Replaces any credential for an integration and returns the new record. */
|
|
readonly create: (input: {
|
|
readonly integrationID: Integration.ID
|
|
readonly value: Value
|
|
readonly label?: string
|
|
}) => Effect.Effect<Info>
|
|
/** Updates the label or secret value of a stored credential. */
|
|
readonly update: (id: ID, updates: Partial<Pick<Info, "label" | "value">>) => Effect.Effect<void>
|
|
/** Removes a stored credential. */
|
|
readonly remove: (id: ID) => Effect.Effect<void>
|
|
}
|
|
|
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Credential") {}
|
|
|
|
export const layer = Layer.effect(
|
|
Service,
|
|
Effect.gen(function* () {
|
|
const { db } = yield* Database.Service
|
|
const decode = Schema.decodeUnknownSync(Value)
|
|
const stored = (row: typeof CredentialTable.$inferSelect) => {
|
|
if (!row.integration_id) return
|
|
return new Info({
|
|
id: row.id,
|
|
integrationID: row.integration_id,
|
|
label: row.label,
|
|
value: decode(row.value),
|
|
})
|
|
}
|
|
|
|
return Service.of({
|
|
all: Effect.fn("Credential.all")(function* () {
|
|
return (yield* db
|
|
.select()
|
|
.from(CredentialTable)
|
|
.orderBy(asc(CredentialTable.time_created))
|
|
.all()
|
|
.pipe(Effect.orDie)).flatMap((row) => {
|
|
const credential = stored(row)
|
|
return credential ? [credential] : []
|
|
})
|
|
}),
|
|
list: Effect.fn("Credential.list")(function* (integrationID) {
|
|
return (yield* db
|
|
.select()
|
|
.from(CredentialTable)
|
|
.where(eq(CredentialTable.integration_id, integrationID))
|
|
.orderBy(asc(CredentialTable.time_created))
|
|
.all()
|
|
.pipe(Effect.orDie)).flatMap((row) => {
|
|
const credential = stored(row)
|
|
return credential ? [credential] : []
|
|
})
|
|
}),
|
|
get: Effect.fn("Credential.get")(function* (id) {
|
|
const row = yield* db.select().from(CredentialTable).where(eq(CredentialTable.id, id)).get().pipe(Effect.orDie)
|
|
return row ? stored(row) : undefined
|
|
}),
|
|
create: Effect.fn("Credential.create")(function* (input) {
|
|
const credential = new Info({
|
|
id: ID.create(),
|
|
integrationID: input.integrationID,
|
|
label: input.label ?? "default",
|
|
value: input.value,
|
|
})
|
|
yield* db
|
|
.transaction((tx) =>
|
|
Effect.gen(function* () {
|
|
yield* tx
|
|
.delete(CredentialTable)
|
|
.where(eq(CredentialTable.integration_id, credential.integrationID))
|
|
.run()
|
|
yield* tx
|
|
.insert(CredentialTable)
|
|
.values({
|
|
id: credential.id,
|
|
integration_id: credential.integrationID,
|
|
label: credential.label,
|
|
value: credential.value,
|
|
})
|
|
.run()
|
|
}),
|
|
)
|
|
.pipe(Effect.orDie)
|
|
return credential
|
|
}),
|
|
update: Effect.fn("Credential.update")(function* (id, updates) {
|
|
if (!updates.label && !updates.value) return
|
|
yield* db
|
|
.update(CredentialTable)
|
|
.set({ label: updates.label, value: updates.value })
|
|
.where(eq(CredentialTable.id, id))
|
|
.run()
|
|
.pipe(Effect.orDie)
|
|
}),
|
|
remove: Effect.fn("Credential.remove")(function* (id) {
|
|
yield* db.delete(CredentialTable).where(eq(CredentialTable.id, id)).run().pipe(Effect.orDie)
|
|
}),
|
|
})
|
|
}),
|
|
)
|
|
|
|
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
|
|
|
export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] })
|