diff --git a/pages/docs/essentials/pipeline.mdx b/pages/docs/essentials/pipeline.mdx index 0b20061ac..ed91088a5 100644 --- a/pages/docs/essentials/pipeline.mdx +++ b/pages/docs/essentials/pipeline.mdx @@ -265,6 +265,51 @@ const program = pipe( console.log(Effect.runSync(program)) // Output: "Result is: 6" ``` +## The pipe method + +Effect provides a `pipe` method that works similarly to the `pipe` method found in [`rxjs`](https://rxjs.dev/api/index/function/pipe). This method allows you to chain multiple operations together, making your code more concise and readable. + +Here's how the `pipe` **method** works: + +```ts +const result = effect.pipe(func1, func2, ..., funcN) +``` + +This is equivalent to using the `pipe` **function** like this: + +```ts +const result = pipe(effect, func1, func2, ..., funcN) +``` + +The `pipe` method is available on all effects and many other data types, eliminating the need to import the `pipe` function from the `@effect/data/Function` module and saving you some keystrokes. + +Let's rewrite the previous example using the `pipe` method: + +```ts {16} +import * as Effect from "@effect/io/Effect" + +const increment = (x: number) => x + 1 + +function divide(a: number, b: number): Effect.Effect { + if (b === 0) { + return Effect.fail(new Error("Cannot divide by zero")) + } + return Effect.succeed(a / b) +} + +const foo = Effect.succeed(10) +const bar = Effect.succeed(2) + +// Effect +const program = Effect.all(foo, bar).pipe( + Effect.flatMap(([a, b]) => divide(a, b)), + Effect.map((n1) => increment(n1)), + Effect.map((n2) => `Result is: ${n2}`) +) + +console.log(Effect.runSync(program)) // Output: "Result is: 6" +``` + ## Cheatsheet Let's summarize the transformation functions we have seen so far: diff --git a/pages/docs/tutorial/configuration.mdx b/pages/docs/tutorial/configuration.mdx index a5173a70c..9d5221e9b 100644 --- a/pages/docs/tutorial/configuration.mdx +++ b/pages/docs/tutorial/configuration.mdx @@ -26,16 +26,14 @@ Let's start with a simple example of how to read configuration from environment ```ts filename="primitives.ts" showLineNumbers -import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" import * as Config from "@effect/io/Config" // Effect -const program = pipe( - Effect.all( - Effect.config(Config.string("HOST")), - Effect.config(Config.float("PORT")) - ), +const program = Effect.all( + Effect.config(Config.string("HOST")), + Effect.config(Config.float("PORT")) +).pipe( Effect.flatMap(([host, port]) => Effect.sync(() => console.log(`Application started: ${host}:${port}`)) ) @@ -49,7 +47,7 @@ If we run this application we will get the following output: ```bash filename="Terminal" {3-6} ts-node primitives.ts -/path/to/primitives.ts:8 +/path/to/primitives.ts:6 Effect.config(Config.string("HOST")), ^ (Missing data at HOST: "Expected HOST to exist in the process context") @@ -144,7 +142,7 @@ If we use this customized configuration in our application: -```ts filename="HostPort.ts" {20} +```ts filename="HostPort.ts" {19} import { pipe } from "@effect/data/Function" import * as Config from "@effect/io/Config" import * as Effect from "@effect/io/Effect" @@ -163,8 +161,7 @@ export const config: Config.Config = pipe( ) // Effect -export const program = pipe( - Effect.config(config), +export const program = Effect.config(config).pipe( Effect.flatMap((hostPort) => Effect.sync(() => console.log(`Application started: ${hostPort.url}`)) ) @@ -221,9 +218,9 @@ So far, we have learned how to define configurations in a top-level manner, whet Let's assume we have a `ServiceConfig` data type that consists of two fields: `hostPort` and `timeout`. ```ts filename="ServiceConfig.ts" +import { pipe } from "@effect/data/Function" import * as HostPort from "./HostPort" import * as Config from "@effect/io/Config" -import { pipe } from "@effect/data/Function" class ServiceConfig { constructor(readonly hostPort: HostPort.HostPort, readonly timeout: number) {} diff --git a/pages/docs/tutorial/context-management/layers.mdx b/pages/docs/tutorial/context-management/layers.mdx index 841ebc393..4f889c3d2 100644 --- a/pages/docs/tutorial/context-management/layers.mdx +++ b/pages/docs/tutorial/context-management/layers.mdx @@ -297,7 +297,7 @@ we can provide it to our program to satisfy the program's requirements using `Ef -```ts filename="main.ts" {28} +```ts filename="main.ts" {30} import { pipe } from "@effect/data/Function" import * as Layer from "@effect/io/Layer" import { FlourLive, SugarLive } from "./Ingredients" @@ -316,11 +316,13 @@ const MainLive = pipe( ) // Effect -const program = pipe( - Recipe, +const program = Recipe.pipe( Effect.flatMap((recipe) => recipe.steps), Effect.flatMap((steps) => - Effect.forEachDiscard(steps, (step) => Effect.logInfo(step)) + Effect.forEach(steps, (step) => Effect.log(step), { + concurrency: "unbounded", + discard: true, + }) ) ) @@ -361,7 +363,7 @@ const program = Effect.gen(function* (_) { const recipe = yield* _(Recipe) const steps = yield* _(recipe.steps) for (const step of steps) { - yield* _(Effect.logInfo(step)) + yield* _(Effect.log(step)) } }) diff --git a/pages/docs/tutorial/context-management/services.mdx b/pages/docs/tutorial/context-management/services.mdx index 95adbc0c7..7c33c1eaa 100644 --- a/pages/docs/tutorial/context-management/services.mdx +++ b/pages/docs/tutorial/context-management/services.mdx @@ -68,10 +68,9 @@ Now that we have our service interface defined, let's see how we can use it by b ```ts // Effect -const program = pipe( - Random, +const program = Random.pipe( Effect.flatMap((random) => random.next()), - Effect.flatMap((randomNumber) => Effect.logInfo(`${randomNumber}`)) + Effect.flatMap((randomNumber) => Effect.log(`${randomNumber}`)) ) ``` @@ -87,7 +86,7 @@ We then use the `Effect.logInfo` utility to log the generated random number. const program = Effect.gen(function* (_) { const random = yield* _(Random) const randomNumber = yield* _(random.next()) - return yield* _(Effect.logInfo(`${randomNumber}`)) + return yield* _(Effect.log(`${randomNumber}`)) }) ``` @@ -129,14 +128,12 @@ In order to provide an actual implementation of the `Random` service, we can uti ```ts // Effect -const runnable = pipe( +const runnable = Effect.provideService( program, - Effect.provideService( - Random, - Random.of({ - next: () => Effect.succeed(Math.random()), - }) - ) + Random, + Random.of({ + next: () => Effect.succeed(Math.random()), + }) ) Effect.runSync(runnable) @@ -144,7 +141,7 @@ Effect.runSync(runnable) ``` In the code snippet above, we call the `program` that we defined earlier and provide it with an implementation of the `Random` service. -We use the `Effect.provideService(tag, implementation){:ts}` function to associate the `Random` service with its implementation, an object with a `next` method that generates a random number passed to `Random.of(){:ts}` (a convenience method to construct and return the service with the correct type). +We use the `Effect.provideService(effect, tag, implementation){:ts}` function to associate the `Random` service with its implementation, an object with a `next` method that generates a random number passed to `Random.of(){:ts}` (a convenience method to construct and return the service with the correct type). Notice that the `R` type parameter of the `runnable` effect is now `never`. This indicates that the effect no longer requires any context to be provided. With the implementation of the `Random` service in place, we are able to run the program without any further dependencies. @@ -173,20 +170,16 @@ To determine if we can use the service, we can use the `Option.match` function p import * as Option from "@effect/data/Option" // Effect -const program = pipe( - Effect.serviceOption(Random), +const program = Effect.serviceOption(Random).pipe( Effect.flatMap((maybeRandom) => - pipe( - maybeRandom, - Option.match( - // the service is not available, return a default value - () => Effect.succeed(-1), - // the service is available - (random) => random.next() - ) - ) + Option.match(maybeRandom, { + // the service is not available, return a default value + onNone: () => Effect.succeed(-1), + // the service is available + onSome: (random) => random.next(), + }) ), - Effect.flatMap((randomNumber) => Effect.logInfo(`${randomNumber}`)) + Effect.flatMap((randomNumber) => Effect.log(`${randomNumber}`)) ) ``` @@ -206,7 +199,7 @@ const program = Effect.gen(function* (_) { yield* _(Effect.succeed(-1)) : // the service is available yield* _(maybeRandom.value.next()) - return yield* _(Effect.logInfo(`${randomNumber}`)) + return yield* _(Effect.log(`${randomNumber}`)) }) ``` @@ -228,14 +221,12 @@ However, if we provide the `Random` service implementation: ```ts Effect.runSync( - pipe( + Effect.provideService( program, - Effect.provideService( - Random, - Random.of({ - next: () => Effect.succeed(Math.random()), - }) - ) + Random, + Random.of({ + next: () => Effect.succeed(Math.random()), + }) ) ) // Output: ...more output... message=0.9957979486841035 @@ -256,7 +247,6 @@ By passing a tuple of tags, we can access the corresponding tuple of services: ```ts import * as Effect from "@effect/io/Effect" import * as Context from "@effect/data/Context" -import { pipe } from "@effect/data/Function" interface Random { readonly next: () => Effect.Effect @@ -271,8 +261,7 @@ interface Logger { const Logger = Context.Tag() // Effect -const program = pipe( - Effect.all(Random, Logger), +const program = Effect.all(Random, Logger).pipe( Effect.flatMap(([random, logger]) => Effect.flatMap(random.next(), (randomNumber) => logger.log(String(randomNumber)) @@ -323,8 +312,7 @@ Effect.Effect To execute the `program`, we need to provide implementations for both services: ```ts -const runnable = pipe( - program, +const runnable = program.pipe( Effect.provideService( Random, Random.of({ @@ -334,7 +322,7 @@ const runnable = pipe( Effect.provideService( Logger, Logger.of({ - log: Effect.logInfo, + log: Effect.log, }) ) ) @@ -344,13 +332,12 @@ Alternatively, instead of calling `provideService` multiple times, we can combin ```ts // Context -const context = pipe( - Context.empty(), +const context = Context.empty().pipe( Context.add(Random, Random.of({ next: () => Effect.succeed(Math.random()) })), Context.add( Logger, Logger.of({ - log: Effect.logInfo, + log: Effect.log, }) ) ) @@ -359,7 +346,7 @@ const context = pipe( and then provide the entire context using the `provideContext` function: ```ts -const runnable = pipe(program, Effect.provideContext(context)) +const runnable = program.pipe(Effect.provideContext(context)) ``` By providing the necessary implementations for each service, we ensure that the `runnable` effect can access and utilize both services when it is executed. diff --git a/pages/docs/tutorial/error-management/expected-errors.mdx b/pages/docs/tutorial/error-management/expected-errors.mdx index 1e9dace4a..b6c53a2da 100644 --- a/pages/docs/tutorial/error-management/expected-errors.mdx +++ b/pages/docs/tutorial/error-management/expected-errors.mdx @@ -60,7 +60,6 @@ The following program serves as an illustration of how errors are automatically ```ts import * as Effect from "@effect/io/Effect" import * as Random from "@effect/io/Random" -import { pipe } from "@effect/data/Function" class FooError { readonly _tag = "FooError" @@ -70,23 +69,20 @@ class BarError { readonly _tag = "BarError" } -const flakyFoo = pipe( - Random.next(), +const flakyFoo = Random.next.pipe( Effect.flatMap((n1) => n1 > 0.5 ? Effect.succeed("yay!") : Effect.fail(new FooError()) ) ) -const flakyBar = pipe( - Random.next(), +const flakyBar = Random.next.pipe( Effect.flatMap((n2) => n2 > 0.5 ? Effect.succeed("yay!") : Effect.fail(new BarError()) ) ) // Effect -const program = pipe( - Effect.all(flakyFoo, flakyBar), +const program = Effect.all(flakyFoo, flakyBar).pipe( Effect.map(([foo, bar]) => foo + bar) ) ``` @@ -111,8 +107,8 @@ class BarError { // Effect const program = Effect.gen(function* (_) { - const n1 = yield* _(Random.next()) - const n2 = yield* _(Random.next()) + const n1 = yield* _(Random.next) + const n2 = yield* _(Random.next) const foo = n1 > 0.5 @@ -154,7 +150,6 @@ In simpler terms, the short-circuiting behavior ensures that if something goes w ```ts -import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" const operation1 = Effect.sync(() => console.log("operation1")) @@ -162,18 +157,16 @@ const operation2 = Effect.fail(new Error("Something went wrong!")) const operation3 = Effect.sync(() => console.log("operation3")) const result = Effect.runSyncExit( - pipe( - operation1, + operation1.pipe( Effect.flatMap(() => operation2), Effect.flatMap(() => operation3) // This computation won't be executed because the previous one fails ) ) -/* -operation1 -*/ console.log(result) + /* +operation1 Failure("Something went wrong!") */ ``` @@ -195,12 +188,11 @@ const result = Effect.runSyncExit( yield* _(operation3) // This computation won't be executed because the previous one fails }) ) -/* -operation1 -*/ console.log(result) + /* +operation1 Failure("Something went wrong!") */ ``` @@ -224,10 +216,9 @@ The `catchAll` function allows you to catch any error that occurs in the program -```ts {4} +```ts {3} // Effect -const recovered = pipe( - program, +const recovered = program.pipe( Effect.catchAll((error) => Effect.succeed(`Recovering from ${error._tag}`)) ) ``` @@ -266,10 +257,9 @@ Suppose we only want to handle `FooError`. In that case, we can use the `catchTa -```ts {4-6} +```ts {3-5} // Effect -const recovered = pipe( - program, +const recovered = program.pipe( Effect.catchTag("FooError", (fooError) => Effect.succeed("Recovering from FooError") ) @@ -315,10 +305,9 @@ If we also wanted to handle `BarError`, we can simply add another `catchTag`. -```ts {7-9} +```ts {6-8} // Effect -const recovered = pipe( - program, +const recovered = program.pipe( Effect.catchTag("FooError", (fooError) => Effect.succeed("Recovering from FooError") ), @@ -364,10 +353,9 @@ Instead of using the `catchTag` combinator multiple times to handle individual e -```ts {4-7} +```ts {3-6} // Effect -const recovered = pipe( - program, +const recovered = program.pipe( Effect.catchTags({ FooError: (fooError) => Effect.succeed(`Recovering from FooError`), BarError: (barError) => Effect.succeed(`Recovering from BarError`), @@ -419,10 +407,9 @@ In such cases, you can use the `mapError` function. -```ts {4-10} +```ts {3-9} // Effect -const modified = pipe( - program, +const modified = program.pipe( Effect.mapError((error) => { if (error._tag === "FooError") { return new Error("Something went wrong with Foo") diff --git a/pages/docs/tutorial/error-management/retrying.mdx b/pages/docs/tutorial/error-management/retrying.mdx index 3f049e211..555c77730 100644 --- a/pages/docs/tutorial/error-management/retrying.mdx +++ b/pages/docs/tutorial/error-management/retrying.mdx @@ -21,7 +21,6 @@ Effect.retry(action, policy) ```ts import * as Effect from "@effect/io/Effect" import * as Schedule from "@effect/io/Schedule" -import * as Duration from "@effect/data/Duration" let count = 0 @@ -38,7 +37,7 @@ const action = Effect.async((resume) => { }) // Define a repetition policy using a fixed delay between retries -const policy = Schedule.fixed(Duration.millis(100)) +const policy = Schedule.fixed("100 millis") const repeated = Effect.retry(action, policy) @@ -67,10 +66,9 @@ Effect.retry(action, policy, fallback) **Example** ```ts +import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" import * as Schedule from "@effect/io/Schedule" -import * as Duration from "@effect/data/Duration" -import { pipe } from "@effect/data/Function" let count = 0 @@ -89,7 +87,7 @@ const action = Effect.async((resume) => { // Define a repetition policy using Schedule functions const policy = pipe( Schedule.recurs(2), // Retry for a maximum of 2 times - Schedule.addDelay(() => Duration.millis(100)) // Add a delay of 100 milliseconds between retries + Schedule.addDelay(() => "100 millis") // Add a delay of 100 milliseconds between retries ) // Create a new effect that retries the action with the specified policy, diff --git a/pages/docs/tutorial/error-management/unexpected-errors.mdx b/pages/docs/tutorial/error-management/unexpected-errors.mdx index 9b34b9cb6..5cf5da271 100644 --- a/pages/docs/tutorial/error-management/unexpected-errors.mdx +++ b/pages/docs/tutorial/error-management/unexpected-errors.mdx @@ -16,22 +16,21 @@ Effect provides two functions that allow you to handle unexpected errors that ma The `catchAllDefect` operator allows you to recover from all defects using a provided function. Here's an example: ```ts -import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" import * as Cause from "@effect/io/Cause" // Effect -const program = pipe( - Effect.dieMessage("Boom!"), // Simulating a runtime error - Effect.catchAllDefect((defect) => { - if (Cause.isRuntimeException(defect)) { - return Effect.logFatal( - `RuntimeException defect caught: ${defect.message}` - ) - } - return Effect.logFatal("Unknown defect caught.") - }) -) +const program = Effect.dieMessage("Boom!") // Simulating a runtime error + .pipe( + Effect.catchAllDefect((defect) => { + if (Cause.isRuntimeException(defect)) { + return Effect.log(`RuntimeException defect caught: ${defect.message}`, { + level: "Fatal", + }) + } + return Effect.log("Unknown defect caught.", { level: "Fatal" }) + }) + ) // We get an Exit.Success because we caught all defects console.log(JSON.stringify(Effect.runSyncExit(program))) @@ -58,19 +57,20 @@ import * as Cause from "@effect/io/Cause" import * as Option from "@effect/data/Option" // Effect -const program = pipe( - Effect.dieMessage("Boom!"), // Simulating a runtime error - Effect.catchSomeDefect((defect) => { - if (Cause.isIllegalArgumentException(defect)) { - return Option.some( - Effect.logFatal( - `Caught an IllegalArgumentException defect: ${defect.message}` +const program = Effect.dieMessage("Boom!") // Simulating a runtime error + .pipe( + Effect.catchSomeDefect((defect) => { + if (Cause.isIllegalArgumentException(defect)) { + return Option.some( + Effect.log( + `Caught an IllegalArgumentException defect: ${defect.message}`, + { level: "Fatal" } + ) ) - ) - } - return Option.none() - }) -) + } + return Option.none() + }) + ) // Since we are only catching IllegalArgumentException // we will get an Exit.Failure because we simulated a runtime error. diff --git a/pages/docs/tutorial/interruption-model.mdx b/pages/docs/tutorial/interruption-model.mdx index e188c8c9d..acd9b530c 100644 --- a/pages/docs/tutorial/interruption-model.mdx +++ b/pages/docs/tutorial/interruption-model.mdx @@ -60,13 +60,10 @@ Without interruptions ```ts import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" -import { pipe } from "@effect/data/Function" -const program = pipe( - Effect.logInfo("start"), - Effect.flatMap(() => Effect.sleep(Duration.seconds(2))), - Effect.flatMap(() => Effect.logInfo("done")) +const program = Effect.log("start").pipe( + Effect.flatMap(() => Effect.sleep("2 seconds")), + Effect.flatMap(() => Effect.log("done")) ) Effect.runPromise(program).catch((error) => @@ -83,12 +80,11 @@ timestamp=...656Z level=INFO fiber=#0 message=done ```ts import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" const program = Effect.gen(function* (_) { - yield* _(Effect.logInfo("start")) - yield* _(Effect.sleep(Duration.seconds(2))) - yield* _(Effect.logInfo("done")) + yield* _(Effect.log("start")) + yield* _(Effect.sleep("2 seconds")) + yield* _(Effect.log("done")) }) Effect.runPromise(program).catch((error) => @@ -108,16 +104,13 @@ With interruptions -```ts {8} +```ts {5} import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" -import { pipe } from "@effect/data/Function" - -const program = pipe( - Effect.logInfo("start"), - Effect.flatMap(() => Effect.sleep(Duration.seconds(2))), - Effect.flatMap(() => Effect.interrupt()), - Effect.flatMap(() => Effect.logInfo("done")) + +const program = Effect.log("start").pipe( + Effect.flatMap(() => Effect.sleep("2 seconds")), + Effect.flatMap(() => Effect.interrupt), + Effect.flatMap(() => Effect.log("done")) ) Effect.runPromise(program).catch((error) => @@ -132,15 +125,14 @@ interrupted: All fibers interrupted without errors. -```ts {7} +```ts {6} import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" const program = Effect.gen(function* (_) { - yield* _(Effect.logInfo("start")) - yield* _(Effect.sleep(Duration.seconds(2))) - yield* _(Effect.interrupt()) - yield* _(Effect.logInfo("done")) + yield* _(Effect.log("start")) + yield* _(Effect.sleep("2 seconds")) + yield* _(Effect.interrupt) + yield* _(Effect.log("done")) }) Effect.runPromise(program).catch((error) => @@ -157,29 +149,29 @@ interrupted: All fibers interrupted without errors. ### Interruption of Parallel Effects -When we combine multiple parallel effects using functions like `Effect.forEachPar`, it's important to note that if one of the effects is interrupted, all the other parallel effects will also be interrupted. Let's take a look at an example: +When we combine multiple parallel effects using functions like `Effect.forEach`, it's important to note that if one of the effects is interrupted, all the other parallel effects will also be interrupted. Let's take a look at an example: ```ts import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" -import { pipe } from "@effect/data/Function" - -const program = Effect.forEachPar([1, 2, 3], (n) => - pipe( - Effect.logInfo(`start #${n}`), - Effect.flatMap(() => { - const effect = Effect.sleep(Duration.seconds(n)) - if (n > 1) { - return Effect.flatMap(effect, () => Effect.interrupt()) - } else { - return effect - } - }), - Effect.flatMap(() => Effect.logInfo(`done #${n}`)) - ) + +const program = Effect.forEach( + [1, 2, 3], + (n) => + Effect.log(`start #${n}`).pipe( + Effect.flatMap(() => { + const effect = Effect.sleep(`${n} seconds`) + if (n > 1) { + return Effect.flatMap(effect, () => Effect.interrupt) + } else { + return effect + } + }), + Effect.flatMap(() => Effect.log(`done #${n}`)) + ), + { concurrency: "unbounded" } ) Effect.runPromise(program).catch((error) => @@ -199,17 +191,19 @@ interrupted: All fibers interrupted without errors. ```ts import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" - -const program = Effect.forEachPar([1, 2, 3], (n) => - Effect.gen(function* (_) { - yield* _(Effect.logInfo(`start #${n}`)) - yield* _(Effect.sleep(Duration.seconds(n))) - if (n > 1) { - yield* _(Effect.interrupt()) - } - yield* _(Effect.logInfo(`done #${n}`)) - }) + +const program = Effect.forEach( + [1, 2, 3], + (n) => + Effect.gen(function* (_) { + yield* _(Effect.log(`start #${n}`)) + yield* _(Effect.sleep(`${n} seconds`)) + if (n > 1) { + yield* _(Effect.interrupt) + } + yield* _(Effect.log(`done #${n}`)) + }), + { concurrency: "unbounded" } ) Effect.runPromise(program).catch((error) => @@ -227,7 +221,7 @@ interrupted: All fibers interrupted without errors. -In this example, we have an array `[1, 2, 3]` representing three parallel tasks. We use `Effect.forEachPar` to iterate over each element and perform some operations. The `Effect.logInfo` function is used to log messages indicating the start and completion of each task. +In this example, we have an array `[1, 2, 3]` representing three parallel tasks. We use `Effect.forEach` to iterate over each element and perform some operations. The `Effect.log` function is used to log messages indicating the start and completion of each task. Looking at the output, we can see that the task with `n=1` starts and completes successfully. However, the task with `n=2` is interrupted using `Effect.interrupt` before it finishes. As a result, all the fibers are interrupted, and the program terminates with the message "All fibers interrupted without errors." diff --git a/pages/docs/tutorial/observability/logging.mdx b/pages/docs/tutorial/observability/logging.mdx index 39ebfacc0..6918b7667 100644 --- a/pages/docs/tutorial/observability/logging.mdx +++ b/pages/docs/tutorial/observability/logging.mdx @@ -28,7 +28,7 @@ The log message contains the following information: - `message`: The content of the log message. - `span`: (Optional) The duration of the span in milliseconds. -## logDebug +## Debug By default, `DEBUG` messages are **not** printed. @@ -39,32 +39,27 @@ Here's an example that demonstrates how to enable `DEBUG` messages for a specifi -```ts {11} -import { pipe } from "@effect/data/Function" +```ts {8} import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" import * as Logger from "@effect/io/Logger" import * as LoggerLevel from "@effect/io/Logger/Level" // Effect -const task1 = pipe( - Effect.sleep(Duration.seconds(2)), - Effect.flatMap(() => Effect.logDebug("task1 done")), +const task1 = Effect.sleep("2 seconds").pipe( + Effect.flatMap(() => Effect.log("task1 done", { level: "Debug" })), Logger.withMinimumLogLevel(LoggerLevel.Debug) ) // Effect -const task2 = pipe( - Effect.sleep(Duration.seconds(1)), - Effect.flatMap(() => Effect.logDebug("task2 done")) +const task2 = Effect.sleep("1 seconds").pipe( + Effect.flatMap(() => Effect.log("task2 done", { level: "Debug" })) ) // Effect -const program = pipe( - Effect.logInfo("start"), +const program = Effect.log("start").pipe( Effect.flatMap(() => task1), Effect.flatMap(() => task2), - Effect.flatMap(() => Effect.logInfo("done")) + Effect.flatMap(() => Effect.log("done")) ) Effect.runPromise(program) @@ -80,34 +75,29 @@ Effect.runPromise(program) -```ts {13} -import { pipe } from "@effect/data/Function" +```ts {9} import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" import * as Logger from "@effect/io/Logger" import * as LoggerLevel from "@effect/io/Logger/Level" // Effect -const task1 = pipe( - Effect.gen(function* (_) { - yield* _(Effect.sleep(Duration.seconds(2))) - yield* _(Effect.logDebug("task1 done")) - }), - Logger.withMinimumLogLevel(LoggerLevel.Debug) -) +const task1 = Effect.gen(function* (_) { + yield* _(Effect.sleep("2 seconds")) + yield* _(Effect.log("task1 done", { level: "Debug" })) +}).pipe(Logger.withMinimumLogLevel(LoggerLevel.Debug)) // Effect const task2 = Effect.gen(function* (_) { - yield* _(Effect.sleep(Duration.seconds(1))) - yield* _(Effect.logDebug("task2 done")) + yield* _(Effect.sleep("1 seconds")) + yield* _(Effect.log("task2 done", { level: "Debug" })) }) // Effect const program = Effect.gen(function* (_) { - yield* _(Effect.logInfo("start")) + yield* _(Effect.log("start")) yield* _(task1) yield* _(task2) - yield* _(Effect.logInfo("done")) + yield* _(Effect.log("done")) }) Effect.runPromise(program) @@ -127,7 +117,7 @@ In the above example, we enable `DEBUG` messages specifically for `task1` by usi By using `Logger.withMinimumLogLevel(effect, level){:ts}`, you have the flexibility to selectively enable different log levels for specific effects in your program. This allows you to control the level of detail in your logs and focus on the information that is most relevant to your debugging and troubleshooting needs. -## logInfo +## Info By default, `INFO` messages are printed. @@ -135,22 +125,19 @@ By default, `INFO` messages are printed. ```ts -import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" // Effect -const task1 = Effect.sleep(Duration.seconds(2)) +const task1 = Effect.sleep("2 seconds") // Effect -const task2 = Effect.sleep(Duration.seconds(1)) +const task2 = Effect.sleep("1 seconds") // Effect -const program = pipe( - Effect.logInfo("start"), +const program = Effect.log("start").pipe( Effect.flatMap(() => task1), Effect.flatMap(() => task2), - Effect.flatMap(() => Effect.logInfo("done")) + Effect.flatMap(() => Effect.log("done")) ) Effect.runPromise(program) @@ -166,20 +153,19 @@ Effect.runPromise(program) ```ts import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" // Effect -const task1 = Effect.sleep(Duration.seconds(2)) +const task1 = Effect.sleep("2 seconds") // Effect -const task2 = Effect.sleep(Duration.seconds(1)) +const task2 = Effect.sleep("1 seconds") // Effect const program = Effect.gen(function* (_) { - yield* _(Effect.logInfo("start")) + yield* _(Effect.log("start")) yield* _(task1) yield* _(task2) - yield* _(Effect.logInfo("done")) + yield* _(Effect.log("done")) }) Effect.runPromise(program) @@ -195,7 +181,7 @@ Effect.runPromise(program) In the above example, the `Effect.logInfo` function is used to log an `INFO` message with the content `"start"` and `"done"`. These messages will be printed during the execution of the program. -## logWarning +## Warning By default, `WARN` messages are printed. @@ -203,13 +189,11 @@ By default, `WARN` messages are printed. ```ts -import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" // Effect -const program = pipe( - Effect.fail("Something went wrong!"), - Effect.catchAll((error) => Effect.logWarning(String(error))) +const program = Effect.fail("Something went wrong!").pipe( + Effect.catchAll((error) => Effect.log(String(error), { level: "Warning" })) ) Effect.runPromise(program) @@ -232,7 +216,7 @@ const program = Effect.gen(function* (_) { ) if (Either.isLeft(successOrFailure)) { const error = successOrFailure.left - yield* _(Effect.logWarning(String(error))) + yield* _(Effect.log(String(error), { level: "Warning" })) return undefined } return successOrFailure.right @@ -247,7 +231,7 @@ timestamp=... level=WARN fiber=#0 message="Something went wrong!" -## logError +## Error By default, `ERROR` messages are printed. @@ -255,13 +239,11 @@ By default, `ERROR` messages are printed. ```ts -import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" // Effect -const program = pipe( - Effect.fail("Something went wrong!"), - Effect.catchAll((error) => Effect.logError(String(error))) +const program = Effect.fail("Something went wrong!").pipe( + Effect.catchAll((error) => Effect.log(String(error), { level: "Error" })) ) Effect.runPromise(program) @@ -284,7 +266,7 @@ const program = Effect.gen(function* (_) { ) if (Either.isLeft(successOrFailure)) { const error = successOrFailure.left - yield* _(Effect.logError(String(error))) + yield* _(Effect.log(String(error), { level: "Error" })) return undefined } return successOrFailure.right @@ -307,15 +289,12 @@ Effect also provides support for spans, allowing you to measure the duration of ```ts /myspan=1011ms/ -import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" // Effect -const program = pipe( - Effect.sleep(Duration.seconds(1)), - Effect.flatMap(() => Effect.logInfo("The job is finished!")), - Effect.logSpan("myspan") +const program = Effect.sleep("1 seconds").pipe( + Effect.flatMap(() => Effect.log("The job is finished!")), + Effect.withLogSpan("myspan") ) Effect.runPromise(program) @@ -328,18 +307,13 @@ timestamp=... level=INFO fiber=#0 message="The job is finished!" myspan=1011ms ```ts -import { pipe } from "@effect/data/Function" import * as Effect from "@effect/io/Effect" -import * as Duration from "@effect/data/Duration" // Effect -const program = pipe( - Effect.gen(function* (_) { - yield* _(Effect.sleep(Duration.seconds(1))) - yield* _(Effect.logInfo("The job is finished!")) - }), - Effect.logSpan("myspan") -) +const program = Effect.gen(function* (_) { + yield* _(Effect.sleep("1 seconds")) + yield* _(Effect.log("The job is finished!")) +}).pipe(Effect.withLogSpan("myspan")) Effect.runPromise(program) /* diff --git a/pages/docs/tutorial/resource-management/scope.mdx b/pages/docs/tutorial/resource-management/scope.mdx index 7c5f47fc7..6612bf3a8 100644 --- a/pages/docs/tutorial/resource-management/scope.mdx +++ b/pages/docs/tutorial/resource-management/scope.mdx @@ -10,13 +10,12 @@ By combining `Scope` with the Effect context, we gain a powerful way to manage r ## Defining Resources -We can define a resource using operators like `Effect.acquireRelease(acquire, release){:ts}`, which allows us to create a scoped value from an `acquire` and `release` workflow. +We can define a resource using operators like `Effect.acquireRelease(options: { acquire, release }){:ts}`, which allows us to create a scoped value from an `acquire` and `release` workflow. For example, let's define a simple resource: ```ts import * as Effect from "@effect/io/Effect" -import type { Scope } from "@effect/io/Scope" // Define the interface for the resource interface MyResource { @@ -36,20 +35,20 @@ const getMyResource = (): Promise => }) // Define the acquisition of the resource with error handling -const acquire = Effect.tryCatchPromise( - () => +const acquire = Effect.tryPromise({ + try: () => getMyResource().then((res) => { console.log("Resource acquired") return res }), - () => new Error("getMyResourceError") -) + catch: () => new Error("getMyResourceError"), +}) // Define the release of the resource const release = (res: MyResource) => Effect.promise(() => res.close()) // Effect -const resource = Effect.acquireRelease(acquire, release) +const resource = Effect.acquireRelease({ acquire, release }) ``` Notice that the `acquireRelease` operator added a `Scope` to the context required by the workflow: @@ -67,8 +66,7 @@ We can continue working with the resource for as long as we want by using `flatM ```ts // Effect -const program = pipe( - Effect.acquireRelease(acquire, release), +const program = Effect.acquireRelease({ acquire, release }).pipe( Effect.flatMap((resource) => Effect.sync(() => console.log(`content is ${resource.contents}`)) ) @@ -81,7 +79,7 @@ const program = pipe( ```ts // Effect const program = Effect.gen(function* (_) { - const resource = yield* _(Effect.acquireRelease(acquire, release)) + const resource = yield* _(Effect.acquireRelease({ acquire, release })) console.log(`content is ${resource.contents}`) }) ``` @@ -96,8 +94,7 @@ Once we are done working with the resource, we can close the scope using the `Ef ```ts /Effect.scoped/ // Effect -const program = pipe( - Effect.acquireRelease(acquire, release), +const program = Effect.acquireRelease({ acquire, release }).pipe( Effect.flatMap((resource) => Effect.sync(() => console.log(`content is ${resource.contents}`)) ), @@ -112,7 +109,7 @@ const program = pipe( // Effect const program = Effect.scoped( Effect.gen(function* (_) { - const resource = yield* _(Effect.acquireRelease(acquire, release)) + const resource = yield* _(Effect.acquireRelease({ acquire, release })) console.log(`content is ${resource.contents}`) }) ) @@ -136,7 +133,7 @@ Resource released ## acquireUseRelease -The `Effect.acquireUseRelease(acquire, use, release){:ts}` function is a specialized version of the `acquireRelease` function that simplifies resource management by automatically handling the scoping of resources. +The `Effect.acquireUseRelease(options: { acquire, use, release }){:ts}` function is a specialized version of the `acquireRelease` function that simplifies resource management by automatically handling the scoping of resources. The main difference is that `acquireUseRelease` eliminates the need to manually call `Effect.scoped` to manage the resource's scope. It has additional knowledge about when you are done using the resource created with the `acquire` step. This is achieved by providing a `use` argument, which represents the function that operates on the acquired resource. As a result, `acquireUseRelease` can automatically determine when it should execute the release step. @@ -160,25 +157,25 @@ const getMyResource = (): Promise => }), }) -const acquire = Effect.tryCatchPromise( - () => +const acquire = Effect.tryPromise({ + try: () => getMyResource().then((res) => { console.log("Resource acquired") return res }), - () => new Error("getMyResourceError") -) + catch: () => new Error("getMyResourceError"), +}) const release = (res: MyResource) => Effect.promise(() => res.close()) const use = (res: MyResource) => Effect.sync(() => console.log(`content is ${res.contents}`)) -const program: Effect.Effect = Effect.acquireUseRelease( +const program: Effect.Effect = Effect.acquireUseRelease({ acquire, use, - release -) + release, +}) Effect.runPromise(program) /*