// // Copyright 2025 DXOS.org // import * as FileSystem from '@effect/platform/FileSystem'; import * as Context from 'effect/Context'; import * as Effect from 'effect/Effect'; import * as Layer from 'effect/Layer'; import * as Option from 'effect/Option'; import { dirname } from 'node:path'; import * as Yaml from 'yaml'; import { DX_CONFIG, DX_DATA, getProfileConfigPath, getProfilePath } from '@dxos/client-protocol'; import { invariant } from '@dxos/invariant'; import { Config } from './config'; export const memoryConfig = new Config({ runtime: { client: { edgeFeatures: { echoReplicator: true, feedReplicator: true, signaling: true, agents: true, }, }, }, }); export const defaultConfig = new Config({ runtime: { client: { edgeFeatures: { echoReplicator: true, feedReplicator: true, signaling: true, agents: true, }, storage: { persistent: true, }, }, services: { edge: { url: 'wss://edge-production.dxos.workers.dev/', }, iceProviders: [ { urls: 'https://edge-production.dxos.workers.dev/ice', }, ], ai: { server: 'https://ai-service.dxos.workers.dev', }, ipfs: { server: 'https://api.ipfs.dxos.network/api/v0', gateway: 'https://gateway.ipfs.dxos.network/ipfs', }, }, }, }); export class ConfigService extends Context.Tag('ConfigService')() { static layerMemory = Layer.effect(ConfigService, Effect.succeed(memoryConfig)); static fromConfig = (config: Config) => Layer.succeed(ConfigService, config); static load = (args: { config: Option.Option; profile: string }) => { const defaultConfigPath = getProfileConfigPath(DX_CONFIG, args.profile); return Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const configPath = Option.getOrElse(args.config, () => defaultConfigPath); const configContent = yield* fs.readFileString(configPath); const configValues = Yaml.parse(configContent); return ConfigService.of(new Config(configValues, profileBuiltinDefaults(args.profile).values)); }).pipe( // If the config file doesn't exist, create it. Effect.catchTag('SystemError', () => Effect.gen(function* () { const configValues = defaultConfig.values; const fs = yield* FileSystem.FileSystem; const pathToCreate = Option.getOrElse(args.config, () => defaultConfigPath); yield* fs.makeDirectory(dirname(pathToCreate), { recursive: true }); yield* fs.writeFileString(pathToCreate, Yaml.stringify(configValues)); return ConfigService.of(new Config(configValues)); }), ), ); }; } /** * Default config for a profile. * Always merged with the default config. */ const profileBuiltinDefaults = (profile: string) => { invariant(!profile.endsWith('.yml')); return new Config({ runtime: { client: { edgeFeatures: { echoReplicator: true, feedReplicator: true, signaling: true, agents: true, }, storage: { persistent: true, dataRoot: getProfilePath(DX_DATA, profile), }, }, }, }); };