Micro for Effect Users

On this page

The Micro module is currently in its experimental stages. We encourage your feedback to further improve its features.

The Micro module in Effect is designed as a lighter alternative to the standard Effect module, tailored for situations where it is beneficial to reduce the bundle size.

This module is standalone and does not include more complex functionalities such as Layer, Ref, Queue, and Deferred. This feature set makes Micro especially suitable for libraries that wish to utilize Effect functionalities while keeping the bundle size to a minimum, particularly for those aiming to provide Promise-based APIs.

Micro also supports use cases where a client application uses Micro, and a server employs the full suite of Effect features, maintaining both compatibility and logical consistency across various application components.

Integrating Micro adds a minimal footprint to your bundle, starting at 5kb gzipped, which may increase depending on the features you use.

Utilizing major Effect modules beyond basic data modules like Option, Either, Array, will incorporate the Effect runtime into your bundle, negating the benefits of Micro.

Importing Micro

Micro is a part of the Effect library and can be imported just like any other module:

ts
import * as Micro from "effect/Micro"
ts
import * as Micro from "effect/Micro"

Main Types

Micro

The Micro type uses three type parameters:

ts
Micro<Success, Error, Requirements>
ts
Micro<Success, Error, Requirements>

which mirror those of the Effect type.

MicroExit

The MicroExit type is a streamlined version of the Exit type, designed to capture the outcome of a Micro computation. It uses the Either data type to distinguish between successful outcomes and failures:

ts
type MicroExit<A, E = never> = Either<A, MicroCause<E>>
ts
type MicroExit<A, E = never> = Either<A, MicroCause<E>>

MicroCause

The MicroCause type is a streamlined version of the Cause type.

Similar to how Cause is a union of types, MicroCause consists of three specific types:

ts
type MicroCause<E> = Die | Fail<E> | Interrupt
ts
type MicroCause<E> = Die | Fail<E> | Interrupt
Failure TypeDescription
DieIndicates an unforeseen defect that wasn't planned for in the system's logic.
Fail<E>Covers anticipated errors that are recognized and typically handled within the application.
InterruptSignifies an operation that has been purposefully stopped.

MicroSchedule

The MicroSchedule type is a streamlined version of the Schedule type.

ts
type MicroSchedule = (attempt: number, elapsed: number) => Option<number>
ts
type MicroSchedule = (attempt: number, elapsed: number) => Option<number>

Represents a function that can be used to calculate the delay between repeats.

The function takes the current attempt number and the elapsed time since the first attempt, and returns the delay for the next attempt. If the function returns None, the repetition will stop.

How to Use This Guide

Below, you'll find a series of comparisons between the functionalities of Effect and Micro. Each table lists a functionality of Effect alongside its counterpart in Micro. The icons used have the following meanings:

  • ⚠️: The feature is available in Micro, but with some differences from Effect.
  • ❌: The feature is not available in Effect.

Creating Effects

EffectMicro
Effect.try⚠️ Micro.tryrequires a try block
Effect.tryPromise⚠️ Micro.tryPromiserequires a try block
Effect.sleep⚠️ Micro.sleeponly handles milliseconds
Effect.failCause⚠️ Micro.failWithuses MicroCause instead of Cause
Effect.failCauseSync⚠️ Micro.failWithSyncuses MicroCause instead of Cause
Micro.make
Micro.fromOption
Micro.fromEither

Running Effects

EffectMicro
Effect.runSyncExit⚠️ Micro.runSyncExitreturns a MicroExit instead of an Exit
Effect.runPromiseExit⚠️ Micro.runPromiseExitreturns a MicroExit instead of an Exit
Effect.runFork⚠️ Micro.runForkreturns a Handle instead of a RuntimeFiber

runSyncExit

The Micro.runSyncExit function is used to execute an Effect synchronously, which means it runs immediately and returns the result as a MicroExit.

ts
import * as Micro from "effect/Micro"
 
const result1 = Micro.runSyncExit(Micro.succeed(1))
console.log(result1)
/*
Output:
{ _id: 'Either', _tag: 'Right', right: 1 }
*/
 
const result2 = Micro.runSyncExit(Micro.fail("my error"))
console.log(result2)
/*
Output:
{ _id: 'Either', _tag: 'Left', left: MicroCause.Fail: my error }
*/
ts
import * as Micro from "effect/Micro"
 
const result1 = Micro.runSyncExit(Micro.succeed(1))
console.log(result1)
/*
Output:
{ _id: 'Either', _tag: 'Right', right: 1 }
*/
 
const result2 = Micro.runSyncExit(Micro.fail("my error"))
console.log(result2)
/*
Output:
{ _id: 'Either', _tag: 'Left', left: MicroCause.Fail: my error }
*/

runPromiseExit

The Micro.runPromiseExit function is used to execute an Effect and obtain the result as a Promise that resolves to a MicroExit.

ts
import * as Micro from "effect/Micro"
 
Micro.runPromiseExit(Micro.succeed(1)).then(console.log)
/*
Output:
{ _id: 'Either', _tag: 'Right', right: 1 }
*/
 
Micro.runPromiseExit(Micro.fail("my error")).then(console.log)
/*
Output:
{ _id: 'Either', _tag: 'Left', left: MicroCause.Fail: my error }
*/
ts
import * as Micro from "effect/Micro"
 
Micro.runPromiseExit(Micro.succeed(1)).then(console.log)
/*
Output:
{ _id: 'Either', _tag: 'Right', right: 1 }
*/
 
Micro.runPromiseExit(Micro.fail("my error")).then(console.log)
/*
Output:
{ _id: 'Either', _tag: 'Left', left: MicroCause.Fail: my error }
*/

runFork

The Micro.runFork function executes the effect and return a Handle that can be awaited, joined, or aborted.

You can listen for the result by adding an observer using the handle's addObserver method.

ts
import * as Micro from "effect/Micro"
 
const handle = Micro.succeed(42).pipe(Micro.delay(1000), Micro.runFork)
 
handle.addObserver((result) => {
console.log(result)
})
console.log("observing...")
/*
Output:
observing...
{ _id: 'Either', _tag: 'Right', right: 42 }
*/
ts
import * as Micro from "effect/Micro"
 
const handle = Micro.succeed(42).pipe(Micro.delay(1000), Micro.runFork)
 
handle.addObserver((result) => {
console.log(result)
})
console.log("observing...")
/*
Output:
observing...
{ _id: 'Either', _tag: 'Right', right: 42 }
*/

Building Pipelines

EffectMicro
Effect.andThen⚠️ Micro.andThendoesn't handle Promise or () => Promise as argument
Effect.tap⚠️ Micro.tapdoesn't handle () => Promise as argument
Effect.all⚠️ Micro.allno batching and mode options
Effect.forEach⚠️ Micro.forEachno batching option
Effect.filter⚠️ Micro.filterno batching option
Effect.filterMap⚠️ Micro.filterMapeffectful

Expected Errors

EffectMicro
Effect.exit⚠️ Micro.exitreturns a MicroExit instead of an Exit

Unexpected Errors

EffectMicro
Micro.catchCauseIf

Timing Out

EffectMicro
Micro.timeoutOrElse

Requirements Management

To access a service while using Micro.gen, you need to wrap the service tag using the Micro.service function:

ts
import * as Context from "effect/Context"
import * as Micro from "effect/Micro"
 
class Random extends Context.Tag("MyRandomService")<
Random,
{ readonly next: Micro.Micro<number> }
>() {}
 
const program = Micro.gen(function* () {
// const random = yield* Random // this doesn't work
const random = yield* Micro.service(Random)
const randomNumber = yield* random.next
console.log(`random number: ${randomNumber}`)
})
 
const runnable = Micro.provideService(program, Random, {
next: Micro.sync(() => Math.random())
})
 
Micro.runPromise(runnable)
/*
Example Output:
random number: 0.8241872233134417
*/
ts
import * as Context from "effect/Context"
import * as Micro from "effect/Micro"
 
class Random extends Context.Tag("MyRandomService")<
Random,
{ readonly next: Micro.Micro<number> }
>() {}
 
const program = Micro.gen(function* () {
// const random = yield* Random // this doesn't work
const random = yield* Micro.service(Random)
const randomNumber = yield* random.next
console.log(`random number: ${randomNumber}`)
})
 
const runnable = Micro.provideService(program, Random, {
next: Micro.sync(() => Math.random())
})
 
Micro.runPromise(runnable)
/*
Example Output:
random number: 0.8241872233134417
*/

Scope

EffectMicro
Scope⚠️ MicroScopereturns a MicroScope instead of a Scope
Scope.make⚠️ Micro.scopeMakereturns a MicroScope instead of a Scope
ts
import * as Micro from "effect/Micro"
 
const consoleLog = (message: string) => Micro.sync(() => console.log(message))
 
const program =
// create a new scope
Micro.scopeMake.pipe(
// add finalizer 1
Micro.tap((scope) => scope.addFinalizer(() => consoleLog("finalizer 1"))),
// add finalizer 2
Micro.tap((scope) => scope.addFinalizer(() => consoleLog("finalizer 2"))),
// close the scope
Micro.andThen((scope) =>
scope.close(Micro.exitSucceed("scope closed successfully"))
)
)
 
Micro.runPromise(program)
/*
Output:
finalizer 2 <-- finalizers are closed in reverse order
finalizer 1
*/
ts
import * as Micro from "effect/Micro"
 
const consoleLog = (message: string) => Micro.sync(() => console.log(message))
 
const program =
// create a new scope
Micro.scopeMake.pipe(
// add finalizer 1
Micro.tap((scope) => scope.addFinalizer(() => consoleLog("finalizer 1"))),
// add finalizer 2
Micro.tap((scope) => scope.addFinalizer(() => consoleLog("finalizer 2"))),
// close the scope
Micro.andThen((scope) =>
scope.close(Micro.exitSucceed("scope closed successfully"))
)
)
 
Micro.runPromise(program)
/*
Output:
finalizer 2 <-- finalizers are closed in reverse order
finalizer 1
*/

Retrying

EffectMicro
Effect.retry⚠️ Micro.retrydifferent options

Repetition

EffectMicro
Effect.repeat⚠️ Micro.repeatdifferent options
❌ (Effect.exit + Effect.repeat)⚠️ Micro.repeatExit

Timing out

EffectMicro
Micro.timeoutOrElse

Sandboxing

EffectMicro
Effect.sandbox⚠️ Micro.sandboxMicroCause<E> instead of Cause<E>

Error Channel Operations

EffectMicro
Micro.filterOrFailWith
Effect.tapErrorCause⚠️ Micro.tapErrorCauseMicroCause<E> instead of Cause<E>
Micro.tapCauseIf
Effect.tapDefect⚠️ Micro.tapDefectunknown instead of Cause<never>

Requirements Management

EffectMicro
Effect.provide⚠️ Micro.provideContextonly handles Context
Micro.provideScope
Micro.service

Scoping, Resources and Finalization

EffectMicro
Effect.addFinalizer⚠️ Micro.addFinalizerMicroExit instead of Exit and no R
Effect.acquireRelease⚠️ Micro.acquireReleaseMicroExit instead of Exit
Effect.acquireUseRelease⚠️ Micro.acquireUseReleaseMicroExit instead of Exit
Effect.onExit⚠️ Micro.onExitMicroExit instead of Exit
Effect.onError⚠️ Micro.onErroruses MicroCause instead of Cause
Micro.onExitIf

Concurrency

EffectMicro
Effect.fork⚠️ Micro.forkHandle instead of RuntimeFiber
Effect.forkDaemon⚠️ Micro.forkDaemonHandle instead of RuntimeFiber
Effect.forkIn⚠️ Micro.forkInHandle instead of RuntimeFiber
Effect.forkScoped⚠️ Micro.forkScopedHandle instead of RuntimeFiber