2026-04-30 02:22:32 +00:00
import { Config , Context , Effect , Layer } from "effect"
type ConfigMap = Record < string , Config.Config < unknown > >
/ * *
* The service shape inferred from an object of Effect ` Config ` definitions .
* /
export type Shape < Fields extends ConfigMap > = {
readonly [ Key in keyof Fields ] : Config . Success < Fields [ Key ] >
}
/ * *
* A Context service class with generated layers for config - backed services .
* /
export type ServiceClass < Self , Id extends string , Service > = Context . ServiceClass < Self , Id , Service > & {
/** Provide already-parsed config, useful in tests. */
readonly layer : ( input : Service ) = > Layer . Layer < Self >
/** Parse config once from the active Effect ConfigProvider and provide the service. */
readonly defaultLayer : Layer.Layer < Self , Config.ConfigError >
}
/ * *
* Create a Context service whose implementation is derived from Effect ` Config ` .
*
* This keeps Effect ` Config ` as the source of truth for env names , defaults , and
* validation while generating a typed service plus convenient production / test
* layers .
*
* ` ` ` ts
* class ServerAuthConfig extends ConfigService . Service < ServerAuthConfig > ( ) (
* "@opencode/ServerAuthConfig" ,
* {
* password : Config.string ( "OPENCODE_SERVER_PASSWORD" ) . pipe ( Config . option ) ,
* username : Config.string ( "OPENCODE_SERVER_USERNAME" ) . pipe ( Config . withDefault ( "opencode" ) ) ,
* } ,
* ) { }
*
* const live = ServerAuthConfig . defaultLayer
* const test = ServerAuthConfig . layer ( { password : Option.some ( "secret" ) , username : "kit" } )
* ` ` `
* /
export const Service =
< Self > ( ) = >
< const Id extends string , const Fields extends ConfigMap > ( id : Id , fields : Fields ) = > {
class ConfigTag extends Context . Service < Self , Shape < Fields > > ( ) ( id ) {
static layer ( input : Shape < Fields > ) {
return Layer . succeed ( this , this . of ( input ) )
}
static get defaultLayer() {
2026-05-20 20:52:33 +00:00
const tag = this
2026-04-30 02:22:32 +00:00
return Layer . effect (
2026-05-20 20:52:33 +00:00
tag ,
Effect . gen ( function * ( ) {
const config = yield * Config . all ( fields )
2026-05-20 19:09:19 +00:00
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Config.all preserves the field shape, but its conditional return type also supports iterable inputs.
2026-05-20 20:52:33 +00:00
return tag . of ( config as Shape < Fields > )
} ) ,
2026-04-30 02:22:32 +00:00
)
}
}
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- The generated class carries typed static helpers.
return ConfigTag as ServiceClass < Self , Id , Shape < Fields > >
}
export * as ConfigService from "./config-service"