diff --git a/content/tutorials/basics/100-your-first-effect.mdx b/content/tutorials/basics/100-your-first-effect.mdx
index ed85e9573..5c428691b 100644
--- a/content/tutorials/basics/100-your-first-effect.mdx
+++ b/content/tutorials/basics/100-your-first-effect.mdx
@@ -1,32 +1,76 @@
---
-title: Your first Effect
-excerpt: Learn the basics of Effect
-section: Learn the basics
+title: Your First Effect
---
### What is an Effect?
-The `Effect` type represents an immutable value that lazily describes a workflow
-or job.
-
-In some ways you can think of them like lazy Promises - but Effects are not
-actually Promises, of course. They can model synchronous, asynchronous,
+The `Effect` type represents an **immutable value** that **lazily describes** a
+computation, workflow, or job. In some ways you can think of an `Effect` like a
+lazy `Promise`. But `Effect`s are not actually `Promise`s, of course - they are
+much more powerful and can be used to model synchronous, asynchronous,
concurrent, and resourceful computations.
-To get started, let's implement a "Hello world!" program! In the editor you can
-see an example of the `Effect.log` function. This effect will log a message to
-the console.
+The `Effect` type has three generic type parameters which allow
+us to fully describe all aspects of a program. Let's take a closer look:
+
+
+
+The `Success` type represents the type of value that an effect can succeed with
+when executed. If this type parameter is `void`, it means the effect produces no
+useful information, while if it is `never`, it means the effect will run forever
+(or until it fails).
+
+The `Error` type represents **expected** errors that can occur when executing an
+effect. If this type parameter is `never`, it means the effect cannot fail
+because there are no values of type `never`.
+
+The `Requirements` type represents the contextual data required by the effect in
+order to be executed. This data is stored internally within a specialized
+collection called `Context`
+(docs). If this type
+parameter is `never`, it means the effect does not have any requirements and
+that the underlying `Context` collection is empty.
+
+
+In the Effect ecosystem, you may often encounter the type parameters of the
+`Effect` type abbreviated as `A`, `E`, and `R` respectively. This is just
+shorthand for the success value of type `A`, `E`rror, and `R`equirements.
+Additionally, you may also hear these type parameters referred to as _channels_
+of an `Effect`.
+
+
+### Describing Computations
-But how do we run it?
+We previously mentioned that an `Effect` is actually an immutable and lazy
+_description_ of a computation. This means that when you create an `Effect`,
+nothing actually happens. You are only _describing_ what you want your program
+to do. The fact that an `Effect` is an inherently lazy, immutable description of
+a program is a key component to what powers many of the features of the library.
-### Running Effect's
+Similar to calling a function, to actually execute a program described by an
+`Effect`, you must explicitly run the `Effect` using Effect's runtime system.
+
+To get a better understanding of what we mean, let's implement a simple program
+that outputs `"Hello, World!"` to the console.
+
+### Running an Effect
+
+In the editor to the right, you can see an example of the `Effect.log` function.
+This effect will log the provided message (in this case `Hello, World!`) to the
+console, along with some other relevant metadata.
+
+But how do we actually run it?
If you type `Effect.run` into the editor, you will see several different options
-to choose from.
+to choose from. For this example let's try running our `Effect.log` program with
+the `Effect.runPromise` function.
-Similar to calling a function, to execute an Effect we need to explicitly run it.
-For this example let's try running the `Effect.log` effect with the
-`Effect.runPromise` function.
+
+To learn more about the different ways you can execute an `Effect`, checkout
+the documentation on Running Effects.
+
To use `Effect.runPromise`:
@@ -34,10 +78,17 @@ To use `Effect.runPromise`:
Effect.runPromise(/* your effect goes here */)
```
-Remember, if you ever get stuck try clicking the "Solve" button.
+Additionally, take a look at the type of our `Effect.log` program by hovering
+over the `program` variable in the editor. We can see that:
+
+ - The program has `void` in the `Success` channel, which means that no value
+ will be produced by the program
+ - The program has `never` in the `Error` channel, which means that no expected
+ errors will be produced by the program
+ - The program has `never` in the `Requirements` channel, which means that the
+ program has no other requirements in order to be run
- To learn more about running effects, we have a page in the documentation
- with more information:
- Running Effects
+Remember, if you ever get stuck try clicking the "Show Solution" button in the
+upper right-hand corner of the editor.
diff --git a/content/tutorials/basics/200-creating-effects.mdx b/content/tutorials/basics/200-creating-effects.mdx
new file mode 100644
index 000000000..7c4028199
--- /dev/null
+++ b/content/tutorials/basics/200-creating-effects.mdx
@@ -0,0 +1,169 @@
+---
+title: Creating Effects
+---
+
+### Returning Values
+
+Congratulations on running your first Effect!
+
+However, in the previous section our program had the type `void` in the
+`Success` channel of the effect, indicating that the program did not return
+any meaningful value.
+
+In this section, we are going to explore how we can create effects that _do_
+return meaningful values.
+
+### Modeling Synchronous Computations
+
+#### Effect.succeed
+
+To create an effect that returns the same value every time that it is run, you
+can use the `Effect.succeed` constructor. It takes a **pure value** as input and
+produces an effect that succeeds with that value.
+
+Here's an example:
+
+```ts
+Effect.succeed(42)
+```
+
+When run, this effect will always succeed with the value `42`.
+
+#### Effect.sync
+
+If you want to create an effect that executes a synchronous function each time
+it is run, you can use the `Effect.sync` constructor. It takes a thunk (i.e. a
+function with no arguments) as input, and calls that function each time the
+effect is run. This is particularly useful when you need to encapsulate
+operations within effect that have side-effects (e.g. logging to the console).
+
+Here's an example:
+
+```ts
+Effect.sync(() => Date.now())
+```
+
+This effect will return the current date each time. Note that if we used
+`Effect.succeed(Date.now())` instead, the date would be fixed to the time when
+the effect was created instead of being re-evaluated each time the effect is
+run.
+
+
+The thunk passed to `Effect.sync` should never throw errors.
+
+
+If, despite warnings, the thunk passed to `Effect.sync` does throw an error, the
+Effect runtime will create an `Effect` containing a "defect" (see
+Unexpected Errors
+in the documentation).
+
+#### Effect.try
+
+For situations where you need to perform a synchronous computation that may
+fail, you can use the `Effect.try` constructor. This constructor has two
+variants that can be used depending upon whether or not you would like to
+customize error handling.
+
+For example, let's say we have a function `numberOrFail` that can potentially
+throw an `Error`:
+
+```ts
+function numberOrFail(num: number) {
+ if (Math.random() > 0.5) {
+ throw new Error("failed")
+ }
+ return num
+}
+```
+
+If we do not need to customize error handling at all, we can directly pass our
+`numberOrFail` function to `Effect.try`. In this case, any errors thrown by the
+function passed to `Effect.try` will be wrapped by an `UnknownException`:
+
+```ts
+Effect.try(() => numberOrFail(42))
+```
+
+However, if we _do_ need to customize error handling, we can use the other
+variant of `Effect.try` which allows us to catch and handle any errors thrown
+from our synchronous function:
+
+```ts
+Effect.try({
+ // Try to run our synchronous function
+ try: () => numberOrFail(42),
+ // Catch and re-map the error to something possibly more useful
+ catch: (error) => new Error(`An error occurred: ${error}`)
+})
+```
+
+This pattern is analagous to a standard try-catch block in JavaScript.
+
+### Modeling Asynchronous Computations
+
+#### Effect.promise
+
+If you want to create an effect from an asynchronous function that returns a
+`Promise`, you can use the `Effect.promise` constructor. The resulting effect
+will resolve the returned `Promise` and succeed with it's value.
+
+Here's an example:
+
+```ts
+Effect.promise(
+ () => fetch("https://api.github.com/users/octocat")
+)
+```
+
+This effect will fetch the GitHub user `octocat` and succeed with the response.
+
+
+The `Promise` returned by the thunk passed to `Effect.promise` should never
+reject.
+
+
+If, despite warnings, the thunk passed to `Effect.promise` does reject, the
+Effect runtime will create an `Effect` containing a "defect" (see
+Unexpected Errors
+in the documentation).
+
+#### Effect.tryPromise
+
+For situations where you need to perform an asynchronous computation that may
+fail, you can use the `Effect.tryPromise` constructor. Similar to `Effect.try`,
+the `Effect.tryPromise` constructor has two variants that can be used depending
+upon whether or not you would like to customize error handling.
+
+If you do not need to customize error handling, the syntax is similar to
+`Effect.promise`, the difference being that if the promise rejects, the error
+will be wrapped into an `UnknownException`:
+
+```ts
+Effect.tryPromise(
+ () => fetch("https://api.github.com/users/octocat")
+)
+```
+
+However, if you _do_ need to customize error handling, you can use the other
+variant of `Effect.tryPromise` which allows for catching and handling the error
+returned by the rejected `Promise`:
+
+```ts
+Effect.tryPromise({
+ // Try to run the promise
+ try: () => fetch("https://api.github.com/users/octocat"),
+ // Catch and re-map the error to something possibly more useful
+ catch: (error) => new Error(`An error occurred: ${error}`)
+})
+```
+
+This is similar to adding a `.catch` handler to a `Promise`.
+
+### Exercise
+
+Using the Effect functions we just learned, complete the TODO's in the editor.
+
+
+Remember, if you ever get stuck try clicking the "Show Solution" button in the
+upper right-hand corner of the editor.
+
diff --git a/content/tutorials/basics/200-returning-values.mdx b/content/tutorials/basics/200-returning-values.mdx
deleted file mode 100644
index 902f106a5..000000000
--- a/content/tutorials/basics/200-returning-values.mdx
+++ /dev/null
@@ -1,51 +0,0 @@
----
-title: Returning values
-excerpt: Learn the basics of Effect
----
-
-Congratulations on running your first Effect!
-
-Next, let's take a look at the different ways you can return values from your
-Effects.
-
-### Effect.succeed
-
-To create an effect that returns the same value everytime, you can use the
-`Effect.succeed` constructor. It takes a value as input and produces an effect
-that succeeds with that value. Here's an example:
-
-```ts
-Effect.succeed(42)
-```
-
-This effect will always succeed with the value `42`.
-
-### Effect.sync
-
-If you want to create an effect that runs a synchronous function for each run,
-you can use the `Effect.sync` constructor. It takes a function as with no
-arguments as input, and calls the function for each run. Here's an example:
-
-```ts
-Effect.sync(() => Date.now())
-```
-
-This effect will return the current date each time. Note that if we used
-`Effect.succeed(Date.now())` instead, the date would be fixed to the time when
-the effect was created.
-
-### Effect.promise
-
-If you want to create an effect from an asynchronous function that returns a
-Promise, you can use the `Effect.promise` constructor. The resulting effect will
-resolve the returned Promise and succeed with the value. Here's an example:
-
-```ts
-Effect.promise(() => fetch('https://api.github.com/users/octocat'))
-```
-
-This effect will fetch the GitHub user `octocat` and succeed with the response.
-
-### Exercise
-
-Using the Effect functions we just learned, complete the TODO's in the editor.
\ No newline at end of file
diff --git a/content/tutorials/basics/300-combining-effects.mdx b/content/tutorials/basics/300-combining-effects.mdx
deleted file mode 100644
index 201d8115c..000000000
--- a/content/tutorials/basics/300-combining-effects.mdx
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: Combining Effects
-excerpt: Learn the basics of Effect
----
-
-Now that you can create and run effects, let's take a look at how you can
-combine them together.
-
-### Effect.andThen
-
-To combine two effects together, you can use `Effect.andThen`. It can be used
-inside a `pipe` to run the first effect, and then run another effect if the
-first one succeeds. Here's an example:
-
-```ts
-Effect.succeed(42).pipe(
- Effect.andThen((value) => Effect.succeed(value * 2))
-)
-```
-
-The result of this effect will be the equivilent of `Effect.succeed(42 * 2)`.
-
-### Effect.tap
-
-If you want to combine two effects together without altering the return value,
-you can use `Effect.tap`. Like `Effect.andThen`, it can be used in a pipeline.
-Here's an example:
-
-```ts
-Effect.succeed(42).pipe(
- Effect.tap((value) => Effect.log(value))
-)
-```
-
-The result of this effect will be `42`, which will additionally be logged to the console.
-
-### Exercise
-
-Using `Effect.andThen` & `Effect.tap`, complete the TODOs in the editor.
diff --git a/content/tutorials/basics/300-writing-programs.mdx b/content/tutorials/basics/300-writing-programs.mdx
new file mode 100644
index 000000000..b63d2dd7c
--- /dev/null
+++ b/content/tutorials/basics/300-writing-programs.mdx
@@ -0,0 +1,109 @@
+---
+title: Writing Programs with Effect
+---
+
+In the previous sections, we learned how to create effects and execute them. In
+this section, we will learn how to write simple programs with Effect.
+
+There are two main styles for writing programs with Effect - using generators
+and using pipelines - both of which can be freely mixed and matched.
+
+### Writing Programs with Generators
+
+The `Effect.gen` utility provided by Effect offers a convenient syntax,
+similar to `async`/`await`, which can be used to write effectful code using
+JavaScript's generator functions.
+
+This allows you to write code in a more imperative style which can enhance
+readability especially for those who are new to Effect.
+
+Let's take a look at a very simple example:
+
+```ts
+Effect.gen(function*() {
+ const n = yield* Effect.sync(() => Math.random())
+
+ if (n > 0.5) {
+ yield* Effect.log(`The number ${n} is greater than 0.5`)
+ } else {
+ yield* Effect.log(`The number ${n} is less than 0.5`)
+ }
+
+ return n
+})
+```
+
+To learn about writing programs with `Effect.gen`, please visit the corresponding
+page in the documentation
+and return here once you are finished reading.
+
+
+The use of generators is an **optional feature** in Effect. If you find
+generators unfamiliar or prefer a different coding style, you can skip this
+section and move on to [Writing Programs with Pipelines](#writing-programs-with-pipelines)
+the next section of the tutorial which covers building pipelines with Effect.
+
+
+Some key takeaways from the documentation on using generators with Effect
+include:
+
+ - Logic goes inside a generator function wrapped by `Effect.gen`
+ - i.e. `Effect.gen(function*() { /* Logic goes here */ })`
+ - Use `yield*` to get the return value of effects
+ - Standard control flow constructs can be used
+ - Errors short-circuit evaluation
+
+### Writing Programs with Pipelines
+
+Using pipelines to create programs with Effect allows for the composition and
+sequencing of operations on values, enabling the transformation and manipulation
+of data in a clear, concise, and modular manner.
+
+The `pipe` utility enables the creation of pipelines with Effect by taking the
+output of one function and passing it as the input to the next function in the
+pipeline. The sequential composition of functions is why we refer to this style
+of writing Effect programs as "creating pipelines".
+
+Let's take a look at the same example from the previous section re-written to
+use `pipe`:
+
+```ts
+Effect.sync(() => Math.random()).pipe(
+ Effect.tap((n) =>
+ n > 0.5
+ ? Effect.log(`The number ${n} is greater than 0.5`)
+ : Effect.log(`The number ${n} is less than 0.5`)
+ )
+)
+```
+
+
+The Effect.tap
+method allows us to access the value of the previous Effect, perform some logic,
+and then ignore the result of said logic, returning the value of the effect
+prior to executing `Effect.tap`.
+
+
+To learn about creating pipelines with `pipe`, please visit the corresponding
+page in the documentation
+and return here once you are finished reading.
+
+Some key takeaways from the documentation on creating pipelines with Effect
+include:
+
+ - `pipe` will apply all functions in the pipeline to a value sequentially
+ - The `.pipe` method is available as a convenience method on most data types
+ in the Effect ecosystem
+
+### Exercise
+
+Using the concepts we just learned about writing programs with Effect, complete
+the `TODO`s in the editor.
+
+**Bonus**: re-write the program in the editor to utilize a pipeline instead of a
+generator.
+
+
+Remember, if you ever get stuck try clicking the "Show Solution" button in the
+upper right-hand corner of the editor.
+
diff --git a/content/tutorials/basics/400-managing-errors.mdx b/content/tutorials/basics/400-managing-errors.mdx
new file mode 100644
index 000000000..b9a92daee
--- /dev/null
+++ b/content/tutorials/basics/400-managing-errors.mdx
@@ -0,0 +1,86 @@
+---
+title: Managing Errors
+---
+
+In the previous section of this tutorial, we learned how to write simple
+programs with Effect. In this section, we will expand upon that knowledge to
+learn about managing errors with Effect.
+
+### Two Types of Errors
+
+Just like any other program, Effect programs may fail for expected or unexpected
+reasons. The difference between a non-Effect program and an Effect program is in
+the detail provided to you when your program fails. Effect attempts to preserve
+as much information as possible about what caused your program to fail to
+produce a detailed, comprehensive, and human readable failure message.
+
+In an Effect program, there are two possible ways for a program to fail:
+
+ - **Expected Errors**: These are errors that developers anticipate and expect
+ as part of normal program execution
+ - **Unexpected Errors**: These are errors that occur unexpectedly and are not
+ part of the intended program flow
+
+### Expected Errors
+
+These errors, also referred to as _failures_, _typed errors_ or _recoverable
+errors_, are errors that a developer anticipates as part of the normal program
+execution. They serve a similar purpose to checked exceptions and play a role
+in defining the program's domain and control flow.
+
+As we learned in a previous section of this tutorial, expected errors are
+**tracked at the type level** by the `Effect` data type (in the `Error` channel).
+
+For example, it is evident from the type that the Effect program in the
+following code snippet can fail with an error of type `HttpError` or an error of
+type `NetworkError`:
+
+
+
+
+Before continuing with this tutorial, please read through the section on
+**expected errors** in
+the documentation.
+
+
+### Unexpected Errors
+
+Unexpected errors, also referred to as _defects_, _untyped errors_, or
+_unrecoverable errors_, are errors that developers do not anticipate occurring
+during normal program execution. Unlike expected errors, which are considered
+part of a program's domain and control flow, unexpected errors resemble
+unchecked exceptions and lie outside the expected behavior of the program.
+
+Since these errors are not expected, Effect **does not track them at the type
+level**. However, the Effect runtime does keep track of these errors and
+provides several methods to aid in recovering from unexpected errors.
+
+
+Before continuing with this tutorial, please read through the section on
+**unexpected errors** in
+the documentation.
+
+
+### Exercise
+
+Using the concepts we just learned about managing errors with Effect, complete
+the following tasks (also marked with `TODO`s in the editor):
+
+ - Create a custom `HttpError` class with a `_tag` attribute that is set to
+ the value `"HttpError"`
+ - Create a custom `NetworkError` class with a `_tag` attribute that is set to
+ the value `"NetworkError"`
+ - Add the necessary code to the pipeline to retry the program on errors for a
+ maximum of three times, but only if the error is an `HttpError`
+ - Add the necessary code to the pipeline to transform all expected errors
+ remaining after the retries into defects
+
+**Bonus**: Use the Data
+module to create the custom error types with `Data.TaggedError`.
+
+
+Remember, if you ever get stuck try clicking the "Show Solution" button in the
+upper right-hand corner of the editor.
+
\ No newline at end of file
diff --git a/content/tutorials/basics/400-using-generators.mdx b/content/tutorials/basics/400-using-generators.mdx
deleted file mode 100644
index 366a5ca57..000000000
--- a/content/tutorials/basics/400-using-generators.mdx
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Using generators
-excerpt: Learn the basics of Effect
----
-
-To make using Effect more approachable, we can use generators to write our
-programs in an imperative style.
diff --git a/content/tutorials/basics/500-managing-requirements.mdx b/content/tutorials/basics/500-managing-requirements.mdx
new file mode 100644
index 000000000..fb8c2fb7b
--- /dev/null
+++ b/content/tutorials/basics/500-managing-requirements.mdx
@@ -0,0 +1,5 @@
+---
+title: Managing Requirements
+---
+
+
\ No newline at end of file
diff --git a/content/tutorials/basics/600-managing-resources.mdx b/content/tutorials/basics/600-managing-resources.mdx
new file mode 100644
index 000000000..d26519b70
--- /dev/null
+++ b/content/tutorials/basics/600-managing-resources.mdx
@@ -0,0 +1,5 @@
+---
+title: Managing Resources
+---
+
+
\ No newline at end of file
diff --git a/content/tutorials/basics/index.mdx b/content/tutorials/basics/index.mdx
index ff83b1e1a..045abb82d 100644
--- a/content/tutorials/basics/index.mdx
+++ b/content/tutorials/basics/index.mdx
@@ -1,26 +1,29 @@
---
title: Welcome
-excerpt: Learn the basics of Effect
-section: Learn the basics
+excerpt: Learn the basics of Effect by exploring fundamental concepts and core data types.
+section: The Basics of Effect
+difficulty: beginner
+prerequisites:
+ - Familiarity with asynchronous programming in JavaScript
+ - Basic understanding of TypeScript syntax and features
---
-Welcome to the Effect tutorials!
+### Welcome!
+
+Welcome to Effect tutorials! Here, you will find clear, concise, and interactive
+guides designed to enhance your skills with Effect and deepen your understanding
+of all that Effect and its ecosystem provides!
+
+Let's get started!
### What is Effect?
-Effect is a powerful TypeScript library designed to help developers easily create complex, synchronous, and asynchronous programs.
+Effect is a powerful TypeScript library designed to help developers easily
+create complex, synchronous, and asynchronous programs.
-The benefits include:
+Some of the many benefits of Effect include:
- Maximum Type-safety (incl. error handling)
- Makes your code more composable, reusable and testable
- Extensive library with a rich ecosystem of packages
- Clustering and Workflows (Alpha)
-
-### How to use this tutorial
-
-To the right is an editor & console window. As you make changes to the code in the editor, the program will re-run and show the output in the console.
-
-Each section will present an exercise designed to illustrate a feature. Later exercises build on the knowledge gained in earlier ones, so it's recommended that you go from start to finish. If necessary, you can navigate via the menu above.
-
-If you want to view the recommended solution to an exercise, click the "Solve" button.
diff --git a/content/tutorials/incremental-adoption/100-where-to-start.mdx b/content/tutorials/incremental-adoption/100-getting-started.mdx
similarity index 78%
rename from content/tutorials/incremental-adoption/100-where-to-start.mdx
rename to content/tutorials/incremental-adoption/100-getting-started.mdx
index d35476b10..130fd110e 100644
--- a/content/tutorials/incremental-adoption/100-where-to-start.mdx
+++ b/content/tutorials/incremental-adoption/100-getting-started.mdx
@@ -1,11 +1,9 @@
---
-title: Where to Start
-excerpt: Learn how to incrementally adopt Effect into your application
-section: Incremental Adoption
+title: Getting Started
workspace: express
---
-### Where to Start
+### Getting Started
When adopting Effect into an existing codebase, it's often best to start with discrete, self-contained pieces of code. You can then use features of Effect to easily interop with your existing code and gradually continue to adopt Effect at your own pace.
@@ -42,6 +40,12 @@ async function main() {
### Exercise
-The team in charge of the `TodoRepository` has re-factored the `create` method from promises to Effect. The method now returns an `Effect` instead of a `Promise`.
+The team in charge of the `TodoRepository` has re-factored the `create` method. The method now returns an `Effect` instead of a `Promise`.
+
+Using what we have learned above, your tasks for this exercise include:
+
+ - Fix all type errors within the `POST /todos` route of our Express API
+ - Ensure that the Express API responds with the created `Todo` as JSON
+
+Modify the code in the editor to the right to accomplish your tasks.
-Your job is to fix the `POST /todos` route of our Express API. In the editor to the right, modify the code to restore functionality to our server.
diff --git a/content/tutorials/incremental-adoption/200-dealing-with-errors.mdx b/content/tutorials/incremental-adoption/200-dealing-with-errors.mdx
new file mode 100644
index 000000000..239e6a27e
--- /dev/null
+++ b/content/tutorials/incremental-adoption/200-dealing-with-errors.mdx
@@ -0,0 +1,173 @@
+---
+title: Dealing with Errors
+workspace: express
+---
+
+### Dealing with Errors
+
+As you continue to adopt Effect into your codebase, you may encounter situations where you need to propagate error information from Effect code to non-Effect code.
+
+For example, perhaps your business requirements dictate that you must log all errors to a third-party log aggregator.
+
+In this section, we will explore several strategies for allowing your existing application to access both expected and unexpected errors coming from Effect code.
+
+
+For more information about managing errors within your Effect programs, checkout the section on Error Management in the documentation.
+
+
+### Example Business Case
+
+Let's imagine that we are working on refactoring a part of our application to use Effect. The part of our application we are refactoring involves communicating with one of our company's REST APIs to retrieve the price of a particular product we sell. If requests to our company's API are unsuccessful, the program will fail with an `HttpError`.
+
+Our business requirements dictate the following:
+- Our application's `ErrorReporter` must be used to report all errors occurring within our application to an external error tracking service
+- Our application's `LogAggregator` must be used to log the results of successful API calls to an external log aggregation service
+
+### Working with Expected Errors
+
+Effect provides a variety of tools to make working with expected errors in your program as simple as possible. These tools can also be used to provide the surrounding application with error information coming from your Effect code.
+
+In the first example below, we utilize `Effect.catchTag` to "catch" the `HttpError` in our Effect program. We then use `Effect.promise` to allow our application's `ErrorReporter` to report on the error.
+
+```ts twoslash
+import { Data, Effect } from "effect"
+
+// === Effect Code ===
+class HttpError extends Data.TaggedError("HttpError") {}
+
+const program = Effect.gen(function*() {
+ // Simulate the possibility of error
+ if (Math.random() > 0.5) {
+ return yield* new HttpError()
+ }
+ // Simulate a response from our API
+ return yield* Effect.succeed(42)
+})
+
+// === Application Code ===
+interface LogAggregator {
+ log(value: any): Promise
+}
+interface ErrorReporter {
+ report(error: any): Promise
+}
+declare const logger: LogAggregator
+declare const reporter: ErrorReporter
+
+async function main() {
+ await program.pipe(
+ Effect.andThen((result) => logger.log(result)),
+ Effect.catchTag("HttpError", (error) =>
+ Effect.promise(() => reporter.report(error))
+ ),
+ Effect.runPromise
+ )
+}
+```
+
+In the next example below, we wrap our _entire program_ in a call to `Effect.either`. This operator will convert an `Effect` into an `Effect, never, R>`, thereby exposing expected errors in the success channel of your program.
+
+We can then use methods from the `Either` module to handle error and success cases separately.
+
+```ts twoslash
+import { Data, Effect, Either } from "effect"
+
+// === Effect Code ===
+class HttpError extends Data.TaggedError("HttpError") {}
+
+const program = Effect.gen(function*() {
+ // Simulate the possibility of error
+ if (Math.random() > 0.5) {
+ return yield* new HttpError()
+ }
+ return yield* Effect.succeed(42)
+})
+
+// === Application Code ===
+interface LogAggregator {
+ log(value: any): Promise
+}
+interface ErrorReporter {
+ report(error: any): Promise
+}
+declare const logger: LogAggregator
+declare const reporter: ErrorReporter
+
+async function main() {
+ await Effect.runPromise(Effect.either(program)).then(
+ Either.match({
+ // Handle the error case
+ onLeft: (error) => reporter.report(error),
+ // Handle the success case
+ onRight: (value) => logger.log(value)
+ })
+ )
+}
+```
+
+### Expected and Unexpected Errors
+
+The simplest method of gaining access to both expected _and_ unexpected errors returned from an Effect program is to simply run your program with `Effect.runPromise` and then catch any thrown errors.
+
+```ts twoslash
+import { Data, Effect } from "effect"
+
+// === Effect Code ===
+class HttpError extends Data.TaggedError("HttpError") {}
+
+const program = Effect.gen(function*() {
+ // Simulate the possibility of error
+ if (Math.random() > 0.5) {
+ return yield* new HttpError()
+ }
+ return yield* Effect.succeed(42)
+})
+
+// === Application Code ===
+interface LogAggregator {
+ log(value: any): Promise
+}
+interface ErrorReporter {
+ report(error: any): Promise
+}
+declare const logger: LogAggregator
+declare const reporter: ErrorReporter
+
+async function main() {
+ await Effect.runPromise(program)
+ .then((value) => logger.log(value))
+ .catch((error) => reporter.report(error))
+}
+```
+
+The benefit of this approach is that errors returned by `Effect.runPromise` will be automatically wrapped by Effect's special `FiberFailure` error. The `FiberFailure` error type will prettify the `Cause` of the failure for you (see the Cause documentation for more information), making it easier to log the error and any associated stack trace.
+
+However, there are several downsides to this approach:
+
+- There is not an "easy" way to introspect the `Cause` of the failure for additional information
+- If there are multiple errors in the underlying `Cause`, only the first error will be rendered by the `FiberFailure`
+
+### Introspecting the Cause
+
+Introspecting the full `Cause` of failure of our Effect programs can be extremely useful when we need granular information about the failure(s) that occurred.
+
+For example, perhaps we need to respond to an `Interrupt` cause in a different manner than a `Fail` cause.
+
+If we want to perform some operations on the full `Cause` of failure of our program, we have a variety options available to us:
+
+- We could utilize sandboxing to expose the full `Cause` of failure in our program's error channel
+- We could run our program to an Exit using `Effect.runPromiseExit` and then match on the success and error cases
+
+### Exercise
+
+The team in charge of the `TodoRepository` has continued to refactor the `create` method. The method now returns an `Effect`.
+
+Using what we have learned above, your tasks for this exercise include:
+
+ - If creation of a `Todo` results in a `CreateTodoError`
+ - Set the response status code to `404`
+ - Return a JSON response that conforms to the following `{ "type": "CreateTodoError", "text": }`
+
+Modify the code in the editor to the right to accomplish your tasks.
+
+Please note that there is no one "correct" answer, as there are multiple ways to achieve the desired outcome.
diff --git a/content/tutorials/incremental-adoption/300-handling-requirements.mdx b/content/tutorials/incremental-adoption/300-handling-requirements.mdx
new file mode 100644
index 000000000..fe86c5592
--- /dev/null
+++ b/content/tutorials/incremental-adoption/300-handling-requirements.mdx
@@ -0,0 +1,112 @@
+---
+title: Handling Requirements
+workspace: express
+---
+
+### Handling Requirements
+
+Utilizing `Effect.runPromise` to interop with your existing application is fine when you are just getting started with adopting Effect. However, it will quickly become apparent that this approach does not scale, especially once you start using Effect to manage the requirements of your application and `Layer`s to compose the dependency graph between services.
+
+
+For a detailed walkthrough of how to manage requirements within your Effect applications, take a look at the Requirements Management section of the documentation.
+
+
+### Understanding the Problem
+
+To understand the problem, let's take a look at a simple example where we create a `Layer` which logs `"Hello, World"` when constructed. The layer is then provided to two Effect programs which are executed at two separate execution boundaries.
+
+```ts twoslash
+import { Console, Effect, Layer } from "effect"
+
+const HelloWorldLive = Layer.effectDiscard(
+ Console.log("Hello, World!")
+)
+
+async function main() {
+ // Execution Boundary #1
+ await Effect.succeed(1).pipe(
+ Effect.provide(HelloWorldLive),
+ Effect.runPromise
+ )
+
+ // Execution Boundary #2
+ await Effect.succeed(2).pipe(
+ Effect.provide(HelloWorldLive),
+ Effect.runPromise
+ )
+}
+
+main()
+/**
+ * Output:
+ * Hello, World!
+ * Hello, World!
+ */
+```
+
+As you can see from the output, the message `"Hello, World!"` is logged twice. This is because each call to `Effect.provide` will fully construct the dependency graph specified by the `Layer` and then provide it to the Effect program.
+
+This can create problems when your layers are meant to encapsulate logic that is only meant to be executed **once** (for example, creating a database connection pool) or when layer construction is **expensive** (for example, fetching a large number of remote assets and caching them in memory).
+
+To solve this problem, we need some sort of top-level, re-usable Effect `Runtime` which contains our fully constructed dependency graph, and then use that `Runtime` to execute our Effect programs instead of the default `Runtime` used by the `Effect.run*` methods.
+
+### Managed Runtimes
+
+The `ManagedRuntime` data type in Effect allows us to create a top-level, re-usable Effect `Runtime` which encapsulates a fully constructed dependency graph. In addition, `ManagedRuntime` gives us explicit control over when resources acquired by the runtime should be disposed.
+
+Let's take a look at an example where we refactor the code from above to utilize `ManagedRuntime`:
+
+```ts twoslash
+import { Console, Effect, Layer, ManagedRuntime } from "effect"
+
+const HelloWorldLive = Layer.effectDiscard(
+ Console.log("Hello, World!")
+)
+
+// Create a managed runtime from our layer
+const runtime = ManagedRuntime.make(HelloWorldLive)
+
+async function main() {
+ // Execution Boundary #1
+ await Effect.succeed(1).pipe(
+ runtime.runPromise
+ )
+
+ // Execution Boundary #2
+ await Effect.succeed(2).pipe(
+ runtime.runPromise
+ )
+
+ // Dispose of resources when no longer needed
+ await runtime.dispose()
+}
+
+main()
+/**
+ * Output:
+ * Hello, World!
+ */
+```
+
+Some things to note about the program above include:
+
+ - `"Hello, World!"` is only logged to the console once
+ - We no longer have to provide `HelloWorldLive` to each Effect program
+ - Resources acquired by the `ManagedRuntime` must be manually disposed of
+
+### Exercise
+
+The team in charge of the `TodoRepository` has been hard at work and has managed to convert the `TodoRepository` into a completely Effect-based service complete with a `Layer` for service construction.
+
+Using what we have learned above, your tasks for this exercise include:
+
+ - Create a `ManagedRuntime` which takes in the `TodoRepository` layer
+ - Use the `ManagedRuntime` to run the Effect programs within the Express route handlers
+ - For any Effect program which may result in a `TodoNotFoundError`:
+ - Set the response status code to `404`
+ - Return a JSON response that conforms to the following `{ "type": "TodoNotFound", "id": }`
+ - **BONUS**: properly dispose of the `ManagedRuntime` when the server shuts down
+
+Modify the code in the editor to the right to accomplish your tasks.
+
+Please note that there is no one "correct" answer, as there are multiple ways to achieve the desired outcome.
diff --git a/content/tutorials/incremental-adoption/index.mdx b/content/tutorials/incremental-adoption/index.mdx
index 17375e954..1a09544d7 100644
--- a/content/tutorials/incremental-adoption/index.mdx
+++ b/content/tutorials/incremental-adoption/index.mdx
@@ -1,17 +1,25 @@
---
title: Introduction
-excerpt: Learn how to incrementally adopt Effect into your application
+excerpt: Explore how Effect can be adopted incrementally into existing applications.
section: Incremental Adoption
+difficulty: intermediate
workspace: express
+prerequisites:
+ - Completion of The Basics of Effect
+ - Understanding of how effects are executed
+ - Ability to handle errors with Effect
+ - Comfort managing requirements with Effect
---
### Incrementally Adopting Effect
-Effect was designed from the start with incremental adoption in mind. You can continue to use your existing code and incorporate Effect as much (or as little) as you prefer, where it makes the most sense within your application. In this way, you can immediately begin to benefit from Effect without needing to make drastic changes to your codebase.
+Effect was designed from the start with incremental adoption in mind. You can continue to use your existing code and incorporate Effect as much (or as little) as you prefer, and only where it makes the most sense within your application.
+
+In this way, you can immediately begin to benefit from Effect without needing to make drastic changes to your codebase.
### What We'll Be Doing
-In this tutorial we will walk through strategies for incrementally adopting Effect into an existing application. Our goal will be to taking a REST API built with [Express](https://expressjs.com) and gradually refactor portions of the application to utilize Effect.
+In this tutorial we will walk through strategies for incrementally adopting Effect into an existing application. Our goal will be to gradually refactor the code within an existing REST API built with [Express](https://expressjs.com) to use Effect.
In the editor to the right, you will see the following files in the `src` directory:
@@ -24,4 +32,4 @@ To the right is an editor and a console window. As you make changes to the code
Each section will present an exercise designed to illustrate a feature. Later exercises build on the knowledge gained in earlier ones, so it's recommended that you go from start to finish. If necessary, you can navigate via the menu above.
-If you want to view the recommended solution to an exercise, click the "Solve" button.
+If you want to view the recommended solution to an exercise, click the "Show Solution" button in the upper-right corner of the page.
diff --git a/next.config.js b/next.config.js
index ec6db0ec5..89f31ae59 100644
--- a/next.config.js
+++ b/next.config.js
@@ -72,6 +72,19 @@ const nextConfig = {
value: "same-origin"
}
]
+ },
+ {
+ source: "/tutorials/(.*)",
+ headers: [
+ {
+ key: "Cross-Origin-Embedder-Policy",
+ value: "require-corp"
+ },
+ {
+ key: "Cross-Origin-Opener-Policy",
+ value: "same-origin"
+ }
+ ]
}
]
}
diff --git a/package.json b/package.json
index db9659946..1b6ecda0a 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"@headlessui/react": "1.7.17",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
+ "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e91a4c1d7..16ba71a21 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -38,6 +38,9 @@ importers:
'@radix-ui/react-alert-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.0.0(react@18.0.0))(react@18.0.0)
+ '@radix-ui/react-checkbox':
+ specifier: ^1.0.4
+ version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.0.0(react@18.0.0))(react@18.0.0)
'@radix-ui/react-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.0.0(react@18.0.0))(react@18.0.0)
@@ -1315,6 +1318,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-checkbox@1.0.4':
+ resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-collapsible@1.0.3':
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
peerDependencies:
@@ -6482,6 +6498,23 @@ snapshots:
'@types/react': 18.3.2
'@types/react-dom': 18.3.0
+ '@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.0.0(react@18.0.0))(react@18.0.0)':
+ dependencies:
+ '@babel/runtime': 7.24.6
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.2)(react@18.0.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.3.2)(react@18.0.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.0.0(react@18.0.0))(react@18.0.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.0.0(react@18.0.0))(react@18.0.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.2)(react@18.0.0)
+ '@radix-ui/react-use-previous': 1.0.1(@types/react@18.3.2)(react@18.0.0)
+ '@radix-ui/react-use-size': 1.0.1(@types/react@18.3.2)(react@18.0.0)
+ react: 18.0.0
+ react-dom: 18.0.0(react@18.0.0)
+ optionalDependencies:
+ '@types/react': 18.3.2
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.0.0(react@18.0.0))(react@18.0.0)':
dependencies:
'@babel/runtime': 7.24.6
diff --git a/src/app/tutorials/[...slug]/components/Navigation.tsx b/src/app/tutorials/[...slug]/components/Navigation.tsx
new file mode 100644
index 000000000..029159589
--- /dev/null
+++ b/src/app/tutorials/[...slug]/components/Navigation.tsx
@@ -0,0 +1,115 @@
+"use server"
+
+import React from "react"
+import Link from "next/link"
+import { Icon } from "@/components/icons"
+import { Tutorial } from "contentlayer/generated"
+import { cn } from "@/lib/utils"
+import {
+ groupedTutorials,
+ tutorialSection
+} from "@/workspaces/domain/tutorial"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from "@/components/ui/dropdown-menu"
+
+export declare namespace Navigation {
+ export interface Props {
+ readonly tutorial: Tutorial
+ }
+}
+
+export const Navigation: React.FC = async ({
+ tutorial
+}) => {
+ const group = groupedTutorials[tutorialSection(tutorial)]
+ const index = group.children.indexOf(tutorial)
+ const previous = group.children[index - 1]
+ const next = group.children[index + 1]
+
+ return (
+