Last active
July 28, 2025 09:07
-
-
Save Maxiviper117/f8bbfe6e3d9f538fec5dae41b7249757 to your computer and use it in GitHub Desktop.
Figuring out Effect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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