Skip to content

Instantly share code, notes, and snippets.

@Maxiviper117
Last active July 28, 2025 09:07
Show Gist options
  • Save Maxiviper117/f8bbfe6e3d9f538fec5dae41b7249757 to your computer and use it in GitHub Desktop.
Save Maxiviper117/f8bbfe6e3d9f538fec5dae41b7249757 to your computer and use it in GitHub Desktop.
Figuring out Effect
import { Effect, Layer, Console } from "effect"
// ─────────────────────────────────────────────────────────────────────────────
// 1) CONFIG AS A SERVICE
// ─────────────────────────────────────────────────────────────────────────────
export class Config extends Effect.Service<Config>()("Config", {
accessors: true,
effect: Effect.succeed({
getConfig: Effect.succeed({
logLevel: "DEBUG",
connection: "mysql://user:pass@host:3306/db"
})
})
}) { }
export const ConfigLive = Config.Default
// ─────────────────────────────────────────────────────────────────────────────
// 2) LOGGER AS A SERVICE + TWO IMPLEMENTATIONS
// ─────────────────────────────────────────────────────────────────────────────
export class Logger extends Effect.Service<Logger>()("Logger", {
accessors: true,
effect: Effect.succeed({
log: (msg: string) => Effect.sync(() => console.log(msg))
})
}) { }
export const ConsoleLogger = Layer.effect(
Logger,
Effect.gen(function* () {
const { logLevel } = yield* Config.getConfig
return new Logger({
log: (msg: string) => Effect.sync(() => console.log(`[${logLevel}] ${msg}`))
})
})
)
export const NoOpLogger = Layer.succeed(
Logger,
new Logger({
log: (_: string) => Effect.succeed("No-op logger")
})
)
// ─────────────────────────────────────────────────────────────────────────────
// 3) DATABASE TAG
// ─────────────────────────────────────────────────────────────────────────────
export interface User { id: number; name: string }
export interface Post { id: number; title: string; userId: number }
export interface Like { id: number; postId: number; userId: number }
// Updated interface to include Logger dependency for logging results
export interface DatabaseSucceed {
getAllUsers: (_?: number) => Effect.Effect<User[]>;
getAllPosts: (_?: number) => Effect.Effect<Post[]>;
getAllLikes: (_?: number) => Effect.Effect<Like[]>;
}
export class Database extends Effect.Service<Database>()("Database", {
accessors: true,
succeed: {} as DatabaseSucceed
}) { }
// ─────────────────────────────────────────────────────────────────────────────
// 4) LIVE DATABASE LAYER
// ─────────────────────────────────────────────────────────────────────────────
export const LiveDatabase = Layer.effect(
Database,
Effect.gen(function* () {
const { logLevel } = yield* Config.getConfig
const logger = yield* Logger
return new Database({
getAllUsers: (limit?: number) => Effect.gen(function* () {
const all: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
]
const result = typeof limit === "number" ? all.slice(0, limit) : all
yield* logger.log(`SELECT * FROM users${limit ? ` LIMIT ${limit}` : ""}`)
yield* logger.log(`Users → ${JSON.stringify(result)}`)
return result
}),
getAllPosts: (limit?: number) => Effect.gen(function* () {
const all: Post[] = [
{ id: 1, title: "First Post", userId: 1 },
{ id: 2, title: "Second Post", userId: 2 },
]
const result = typeof limit === "number" ? all.slice(0, limit) : all
yield* logger.log(`SELECT * FROM posts${limit ? ` LIMIT ${limit}` : ""}`)
yield* logger.log(`Posts → ${JSON.stringify(result)}`)
return result
}),
getAllLikes: (limit?: number) => {
const all: Like[] = [
{ id: 1, postId: 1, userId: 1 },
{ id: 2, postId: 2, userId: 2 },
]
const result = typeof limit === "number" ? all.slice(0, limit) : all
return logger.log(`SELECT * FROM likes${limit ? ` LIMIT ${limit}` : ""}`).pipe(
Effect.as(result)
)
}
})
})
)
// ─────────────────────────────────────────────────────────────────────────────
// 5) MOCK DATABASE LAYER
// ─────────────────────────────────────────────────────────────────────────────
export const MockDatabase = Layer.effect(
Database,
Effect.gen(function* () {
const { logLevel, connection } = yield* Config.getConfig
// Removed: const logger = yield* Logger
return new Database({
getAllUsers: (limit?: number) => {
const all: User[] = [{ id: 42, name: "Test User" }]
const result = typeof limit === "number" ? all.slice(0, limit) : all
console.log(`[${logLevel}] [MOCK] SELECT * FROM users${limit ? ` LIMIT ${limit}` : ""} @ ${connection}`)
console.log(`[${logLevel}] Users → ${JSON.stringify(result)}`)
return Effect.succeed(result)
},
getAllPosts: (limit?: number) => {
const all: Post[] = [{ id: 99, title: "Mock Post", userId: 42 }]
const result = typeof limit === "number" ? all.slice(0, limit) : all
console.log(`[${logLevel}] [MOCK] SELECT * FROM posts${limit ? ` LIMIT ${limit}` : ""} @ ${connection}`)
console.log(`[${logLevel}] Posts → ${JSON.stringify(result)}`)
return Effect.succeed(result)
},
getAllLikes: (limit?: number) => {
const all: Like[] = [{ id: 7, postId: 99, userId: 42 }]
const result = typeof limit === "number" ? all.slice(0, limit) : all
console.log(`[${logLevel}] [MOCK] SELECT * FROM likes${limit ? ` LIMIT ${limit}` : ""} @ ${connection}`)
return Effect.succeed(result)
}
})
})
)
// ─────────────────────────────────────────────────────────────────────────────
// 6) TOP-LEVEL PROGRAM + SWAPPING EXAMPLES
// ─────────────────────────────────────────────────────────────────────────────
const program = Effect.gen(function* () {
const db = yield* Database
const users = yield* db.getAllUsers(1)
const posts = yield* db.getAllPosts(1)
// No Logger.log calls; logging handled by Database methods
return { users, posts }
})
// a) Prod wiring: console logger + live DB
const ProdLayer = LiveDatabase.pipe(
Layer.provideMerge(ConsoleLogger),
Layer.provideMerge(ConfigLive)
)
// b) Test wiring: console logger + mock DB
const TestLayer = MockDatabase.pipe(
// Layer.provideMerge(NoOpLogger),
Layer.provideMerge(ConfigLive)
)
// Run with both layers for demonstration
console.log("=== Running with ProdLayer ===");
Effect.runPromise(program.pipe(Effect.provide(ProdLayer))).then((result) => {
console.log(JSON.stringify(result, null, 2));
console.log("\n=== Running with TestLayer ===");
return Effect.runPromise(program.pipe(Effect.provide(TestLayer))).then((result) => {
console.log(JSON.stringify(result, null, 2));
});
}).catch(console.error);
import { Effect, Context } from "effect"
// ===============================================================
// Prototyping
// ===============================================================
// The shape of the Random service
interface RandomShape {
readonly next: Effect.Effect<number> // Effect that produces a number
}
// Declaring a tag for a service that generates random numbers
class RandomTag extends Context.Tag("MyRandomService")<
RandomTag,
RandomShape
>() { }
interface LoggerShape {
readonly log: (message: string) => Effect.Effect<void> // Effect that logs a message
}
// Declaring a tag for the logging service
class LoggerTag extends Context.Tag("MyLoggerService")<
LoggerTag,
LoggerShape
>() { }
const program = Effect.gen(function* () {
const random = yield* RandomTag // At runtime, this will expose the provided implementation of Random
const logger = yield* LoggerTag // At runtime, this will expose the provided implementation of Logger
const randomNumber = yield* random.next
return yield* logger.log(String(randomNumber))
})
// ===============================================================
// Implementations
// ===============================================================
// Creating an implementations of the Random service called 'random1'
const randomImpl1 = RandomTag.of({
next: Effect.sync(() => Math.random())
})
// Creating two different logger implementations
const loggerImplt1 = LoggerTag.of({
log: (message) => Effect.sync(() => console.log('[Logger1] ' + message))
})
const loggerImplt2 = LoggerTag.of({
log: (message) => Effect.sync(() => console.log('[Logger2] ' + message))
})
// ===============================================================
// Running the program with provided services
// ===============================================================
// Provide service implementations for 'Random' and 'Logger'
const runnable = program.pipe(
Effect.provideService(RandomTag, randomImpl1),
Effect.provideService(LoggerTag, loggerImplt1)
)
Effect.runPromise(runnable).then(() => {
// Now we can run the program with a different logger
const runnable2 = program.pipe(
Effect.provideService(RandomTag, randomImpl1),
Effect.provideService(LoggerTag, loggerImplt2)
)
return Effect.runPromise(runnable2)
}).catch(console.error)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment