11import { FileSystem } from "@effect/platform" ;
22import { Path } from "@effect/platform" ;
3- import { Config , Context , Effect , Layer , Option , Schema } from "effect" ;
3+ import { Config , Effect , Option , Schema } from "effect" ;
44import { formatSchemaError , pickDefined } from "./shared.js" ;
55import { AppConfig , OutputFormat } from "../domain/config.js" ;
66import { ConfigError } from "../domain/errors.js" ;
@@ -81,17 +81,16 @@ type AppConfigOverrides = Partial<AppConfig>;
8181 * }).pipe(Effect.provide(overridesLayer));
8282 * ```
8383 */
84- export class ConfigOverrides extends Context . Tag ( "@skygent/ConfigOverrides" ) <
85- ConfigOverrides ,
86- AppConfigOverrides
87- > ( ) {
84+ export class ConfigOverrides extends Effect . Service < ConfigOverrides > ( ) ( "@skygent/ConfigOverrides" , {
85+ succeed : { } as AppConfigOverrides
86+ } ) {
8887 /**
8988 * Default empty configuration overrides layer.
9089 *
9190 * Use this as a base layer when no overrides are needed, or extend it
9291 * with custom overrides using Layer.succeed.
9392 */
94- static readonly layer = Layer . succeed ( ConfigOverrides , { } ) ;
93+ static readonly layer = ConfigOverrides . Default ;
9594}
9695
9796const PartialAppConfig = Schema . Struct ( {
@@ -197,10 +196,54 @@ const envOutputFormat = Config.literal("json", "ndjson", "markdown", "table")(
197196 * const runnable = program.pipe(Effect.provide(AppConfigService.layer));
198197 * ```
199198 */
200- export class AppConfigService extends Context . Tag ( "@skygent/AppConfig" ) <
201- AppConfigService ,
202- AppConfig
203- > ( ) {
199+ export class AppConfigService extends Effect . Service < AppConfigService > ( ) ( "@skygent/AppConfig" , {
200+ effect : Effect . gen ( function * ( ) {
201+ const { _tag : _ , ...overrides } = yield * ConfigOverrides ;
202+ const path = yield * Path . Path ;
203+ const defaultRoot = resolveDefaultRoot ( path ) ;
204+ const configPath = path . join ( defaultRoot , configFileName ) ;
205+
206+ const fileConfig = yield * loadFileConfig ( configPath ) ;
207+
208+ const envService = yield * Config . string ( "SKYGENT_SERVICE" ) . pipe ( Config . option ) ;
209+ const envStoreRoot = yield * Config . string ( "SKYGENT_STORE_ROOT" ) . pipe ( Config . option ) ;
210+ const envFormat = yield * envOutputFormat . pipe ( Config . option ) ;
211+ const envIdentifier = yield * Config . string ( "SKYGENT_IDENTIFIER" ) . pipe ( Config . option ) ;
212+
213+ const envConfig = pickDefined ( {
214+ service : Option . getOrUndefined ( envService ) ,
215+ storeRoot : Option . getOrUndefined ( envStoreRoot ) ,
216+ outputFormat : Option . getOrUndefined ( envFormat ) ,
217+ identifier : Option . getOrUndefined ( envIdentifier )
218+ } ) ;
219+
220+ const merged = {
221+ service : defaultService ,
222+ storeRoot : defaultRoot ,
223+ outputFormat : defaultOutputFormat ,
224+ ...fileConfig ,
225+ ...envConfig ,
226+ ...pickDefined ( overrides as Record < string , unknown > )
227+ } ;
228+
229+ const resolvedStoreRoot = merged . storeRoot ?? defaultRoot ;
230+ const normalized = {
231+ ...merged ,
232+ storeRoot : normalizeStoreRoot ( path , resolvedStoreRoot )
233+ } ;
234+
235+ const decoded = yield * Schema . decodeUnknown ( AppConfig ) ( normalized ) . pipe (
236+ Effect . mapError ( ( error ) =>
237+ ConfigError . make ( {
238+ message : `Invalid config: ${ formatSchemaError ( error ) } ` ,
239+ path : configPath ,
240+ cause : error
241+ } )
242+ )
243+ ) ;
244+ return decoded ;
245+ } )
246+ } ) {
204247 /**
205248 * Layer that constructs the AppConfigService by resolving configuration
206249 * from all sources in priority order.
@@ -226,53 +269,5 @@ export class AppConfigService extends Context.Tag("@skygent/AppConfig")<
226269 * );
227270 * ```
228271 */
229- static readonly layer = Layer . effect (
230- AppConfigService ,
231- Effect . gen ( function * ( ) {
232- const overrides = yield * ConfigOverrides ;
233- const path = yield * Path . Path ;
234- const defaultRoot = resolveDefaultRoot ( path ) ;
235- const configPath = path . join ( defaultRoot , configFileName ) ;
236-
237- const fileConfig = yield * loadFileConfig ( configPath ) ;
238-
239- const envService = yield * Config . string ( "SKYGENT_SERVICE" ) . pipe ( Config . option ) ;
240- const envStoreRoot = yield * Config . string ( "SKYGENT_STORE_ROOT" ) . pipe ( Config . option ) ;
241- const envFormat = yield * envOutputFormat . pipe ( Config . option ) ;
242- const envIdentifier = yield * Config . string ( "SKYGENT_IDENTIFIER" ) . pipe ( Config . option ) ;
243-
244- const envConfig = pickDefined ( {
245- service : Option . getOrUndefined ( envService ) ,
246- storeRoot : Option . getOrUndefined ( envStoreRoot ) ,
247- outputFormat : Option . getOrUndefined ( envFormat ) ,
248- identifier : Option . getOrUndefined ( envIdentifier )
249- } ) ;
250-
251- const merged = {
252- service : defaultService ,
253- storeRoot : defaultRoot ,
254- outputFormat : defaultOutputFormat ,
255- ...fileConfig ,
256- ...envConfig ,
257- ...pickDefined ( overrides as Record < string , unknown > )
258- } ;
259-
260- const resolvedStoreRoot = merged . storeRoot ?? defaultRoot ;
261- const normalized = {
262- ...merged ,
263- storeRoot : normalizeStoreRoot ( path , resolvedStoreRoot )
264- } ;
265-
266- const decoded = yield * Schema . decodeUnknown ( AppConfig ) ( normalized ) . pipe (
267- Effect . mapError ( ( error ) =>
268- ConfigError . make ( {
269- message : `Invalid config: ${ formatSchemaError ( error ) } ` ,
270- path : configPath ,
271- cause : error
272- } )
273- )
274- ) ;
275- return AppConfigService . of ( decoded ) ;
276- } )
277- ) ;
272+ static readonly layer = AppConfigService . Default ;
278273}
0 commit comments