Skip to content

Commit 8e50dbb

Browse files
authored
Merge pull request #229 from bpingris/fix/content-environment-config-valid-apis
fix(doc): update environment-config pattern to use valid Effect api
2 parents a35eba6 + d2e35a1 commit 8e50dbb

1 file changed

Lines changed: 57 additions & 60 deletions

File tree

content/published/patterns/schema/environment-config/env-variables.mdx

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,27 @@ import { Schema, Effect } from "effect"
2323

2424
// 1. Define environment schema
2525
const EnvironmentSchema = Schema.Struct({
26-
DATABASE_URL: Schema.String.pipe(
27-
Schema.description("PostgreSQL connection string")
28-
),
29-
API_KEY: Schema.String.pipe(
30-
Schema.minLength(32),
31-
Schema.description("API authentication key (min 32 chars)")
32-
),
33-
PORT: Schema.pipe(
34-
Schema.String,
35-
Schema.parseNumber(),
36-
Schema.int(),
37-
Schema.between(1024, 65535),
38-
Schema.description("Server port (1024-65535)")
39-
),
40-
LOG_LEVEL: Schema.Literal("debug", "info", "warn", "error").pipe(
41-
Schema.description("Logging level")
42-
),
43-
NODE_ENV: Schema.Literal("development", "staging", "production").pipe(
44-
Schema.description("Deployment environment")
45-
),
26+
DATABASE_URL: Schema.String.pipe(
27+
Schema.annotations({ description: 'PostgreSQL connection string' }),
28+
),
29+
API_KEY: Schema.String.pipe(
30+
Schema.minLength(32),
31+
Schema.annotations({
32+
description: 'API authentication key (min 32 chars)',
33+
}),
34+
),
35+
PORT: Schema.String.pipe(
36+
Schema.parseNumber,
37+
Schema.int(),
38+
Schema.between(1024, 65535),
39+
Schema.annotations({ description: 'Server port (1024-65535)' }),
40+
),
41+
LOG_LEVEL: Schema.Literal('debug', 'info', 'warn', 'error').pipe(
42+
Schema.annotations({ description: 'Logging level' }),
43+
),
44+
NODE_ENV: Schema.Literal('development', 'staging', 'production').pipe(
45+
Schema.annotations({ description: 'Deployment environment' }),
46+
),
4647
})
4748

4849
type Environment = typeof EnvironmentSchema.Type
@@ -51,56 +52,52 @@ type Environment = typeof EnvironmentSchema.Type
5152
const validateEnv = Schema.decodeUnknown(EnvironmentSchema)
5253

5354
// 3. Load and validate environment
54-
const loadEnvironment = (): Effect.Effect<Environment, Error> =>
55-
Effect.gen(function* () {
56-
const envVars = process.env
57-
58-
const validated = yield* Effect.tryPromise({
59-
try: () => validateEnv(envVars),
60-
catch: (error) => {
61-
const errorMsg =
62-
error instanceof Error ? error.message : String(error)
63-
return new Error(`Environment validation failed: ${errorMsg}`)
64-
},
65-
})
66-
67-
console.log(`✅ Environment loaded: NODE_ENV=${validated.NODE_ENV}`)
68-
return validated
69-
})
55+
const loadEnvironment = Effect.fn(function* () {
56+
const validated = yield* validateEnv(process.env)
7057

71-
// 4. Create service to provide environment
72-
class EnvironmentService {
73-
constructor(readonly env: Environment) {}
58+
console.log(`✅ Environment loaded: NODE_ENV=${validated.NODE_ENV}`)
59+
return validated
60+
})
7461

75-
isDev = () => this.env.NODE_ENV === "development"
76-
isProd = () => this.env.NODE_ENV === "production"
77-
isStaging = () => this.env.NODE_ENV === "staging"
62+
// 4. Create service to provide environment
63+
export class EnvironmentService extends Context.Tag('@app/EnvironmentService')<
64+
EnvironmentService,
65+
Environment & {
66+
isDev: () => boolean
67+
isStaging: () => boolean
68+
isProd: () => boolean
69+
}
70+
>() {
71+
static layer = Layer.effect(
72+
this,
73+
Effect.gen(function* () {
74+
const env = yield* loadEnvironment()
75+
return {
76+
...env,
77+
isDev: () => env.NODE_ENV === 'development',
78+
isStaging: () => env.NODE_ENV === 'staging',
79+
isProd: () => env.NODE_ENV === 'production',
80+
}
81+
}),
82+
)
7883
}
7984

80-
// 5. Provide environment as a service
81-
const EnvironmentServiceLive = Effect.gen(function* () {
82-
const env = yield* loadEnvironment()
83-
return new EnvironmentService(env)
84-
}).pipe(Effect.layer)
85-
8685
// Usage
87-
const appLogic = Effect.gen(function* () {
88-
const envService = yield* Effect.service(EnvironmentService)
86+
const program = Effect.gen(function* () {
87+
const envService = yield* EnvironmentService
8988

90-
console.log(`Database: ${envService.env.DATABASE_URL}`)
91-
console.log(`Port: ${envService.env.PORT}`)
92-
console.log(`Log level: ${envService.env.LOG_LEVEL}`)
93-
console.log(`Is production: ${envService.isProd()}`)
89+
console.log(`Database: ${envService.DATABASE_URL}`)
90+
console.log(`Port: ${envService.PORT}`)
91+
console.log(`Log level: ${envService.LOG_LEVEL}`)
92+
console.log(`Is production: ${envService.isProd()}`)
9493

95-
return envService.env.PORT
94+
return envService.PORT
9695
})
9796

9897
// Run with environment layer
99-
Effect.runPromise(
100-
appLogic.pipe(Effect.provide(EnvironmentServiceLive))
101-
)
102-
.then((port) => console.log(`Server starting on port ${port}`))
103-
.catch((error) => console.error(`Failed to start: ${error.message}`))
98+
Effect.runPromise(program.pipe(Effect.provide(EnvironmentService.layer)))
99+
.then((port) => console.log(`Server starting on port ${port}`))
100+
.catch((error) => console.error(`Failed to start: ${error.message}`))
104101
```
105102

106103
# Why This Works

0 commit comments

Comments
 (0)