VibeLang

Warning: VibeLang is v0. The language will change in major, breaking ways. It is not suitable for production use.

Cheat sheet

Every VibeLang change next to the Effect code it replaces. The left column is Effect 4 TypeScript, and the right column is VibeLang. The specification states the rules behind each change. The examples share these declarations:

VibeLang
// run: Console.log(JSON.stringify([getUser("1"), getOrders("1"), handle(() => release("v1"), Approval, { approve: (_request, k) => resume(k, true) })]))
error NotFound { id: string }

interface User { id: string; name: string }

function getUser(id: string): User!NotFound {
  sleep("5 millis")
  if (id === "missing") throw new NotFound({ id })
  return { id, name: "Ada" }
}

function getOrders(id: string): number!! {
  return id.length
}

control Approval {
  approve(request: string): boolean!!
}

function release(version: string) {
  return Approval.approve(`deploy ${version}`) ? `released ${version}` : "declined"
}
// Output: [{"id":"1","name":"Ada"},1,"released v1"]

Types in this example

error
class NotFound
NotFound
class NotFound
id
(property) id: string
User
interface User
id
(property) User.id: string
name
(property) User.name: string
getUser
function getUser(id: string): User!NotFound!
id
(parameter) id: string
sleep
const sleep: (duration: Duration.Input) => void!!Runs here and returns void.Returns an effect that suspends the current fiber for the specified duration without blocking a JavaScript thread.
NotFound
constructor NotFound<{ readonly id: string; }>(args: { readonly id: string; }): NotFound
function
function getOrders(id: string): number!!
getOrders
function getOrders(id: string): numberPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<number, never, never>.
length
(property) String.length: numberReturns the length of a String object.
control
abstract class Approval
Approval
abstract class Approval
approve
(method) Approval.approve(request: string): boolean!!
request
(parameter) request: string
release
function release(version: string): string!!Approval
version
(parameter) version: string
approve
(method) Approval.approve(request: string): boolean!!Runs here and returns boolean.

Types

Effect types (D127). An empty slot is never. Dependencies left out are inferred from a function's body, and are never anywhere else.

Effect today (TypeScript)
type Find = (id: string) => Effect.Effect<User, NotFound>
type Release = (version: string) => Effect.Effect<string, never, Approval>
type Load = () => Effect.Effect<string>
const hello = (id: string): Effect.Effect<string, NotFound> =>
  Effect.map(getUser(id), (user) => `Hello, ${user.name}`)
VibeLang
// run: Console.log(hello("1"))
type Find = (id: string) => User!NotFound
type Release = (version: string) => string!!Approval
type Load = () => string!!
function hello(id: string): string!NotFound {
  return `Hello, ${getUser(id).name}`
}
// Output: Hello, Ada

Types in this example

Find
type Find = (id: string) => User!NotFound!
id
(parameter) id: string
User
(alias) interface User
NotFound
(alias) class NotFound
Release
type Release = (version: string) => string!!Approval
version
(parameter) version: string
Approval
(alias) abstract class Approval
Load
type Load = () => string!!
hello
function hello(id: string): string!NotFound!
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
name
(property) User.name: string

Declarations

Errors (D126).

Effect today (TypeScript)
class Missing extends Data.TaggedError("Missing")<{ readonly id: string }> {}
class Timeout extends Data.TaggedError("Timeout") {}
VibeLang
error Missing { id: string }
error Timeout

Types in this example

error
class Missing
Missing
class Missing
id
(property) id: string
error
class Timeout
Timeout
class Timeout

Services (D128, D223). Members have no bodies, and value members are readonly.

Effect today (TypeScript)
class Users extends Context.Service<Users, {
  find(id: string): Effect.Effect<User, NotFound>
  readonly region: string
}>()("app/Users") {}
VibeLang
service Users {
  find(id: string): User!NotFound
  region: string
}

Types in this example

service
abstract class Users
Users
abstract class Users
find
(method) Users.find(id: string): User!NotFound!
id
(parameter) id: string
User
(alias) interface User
NotFound
(alias) class NotFound
region
(property) Users.region: string

Control operations (D128, D223). Effect has none, so the left column is VibeLang's long form.

VibeLang long form
abstract class Review extends Control {
  abstract approve(request: string): Effect<boolean, never, never>
}

Types in this example

Review
abstract class Review
Control
(alias) abstract class ControlD2: control operations are abstract classes that extend Control; the compiler rewrites them.
approve
(method) Review.approve(request: string): boolean!!
request
(parameter) request: string
Effect
type A!E = never!R = never = A!E!RD4: in a .vibe file, Effect<A, E, R> is Effect.Effect<A, E, R>.
VibeLang
control Review {
  approve(request: string): boolean!!
}

Types in this example

control
abstract class Review
Review
abstract class Review
approve
(method) Review.approve(request: string): boolean!!
request
(parameter) request: string

Layers (D221). A layer is Effect's own call: the block after Layer.effect(Users) is its effect (block params, D188), its statements run once when the layer builds, and the service types the returned members. Without setup, write Layer.succeed(Users, { ... }).

Effect today (TypeScript)
const UsersLive = Layer.effect(Users, Effect.gen(function* () {
  const seen = new Set<string>()
  return Users.of({ find: (id) => getUser(id), region: "eu" })
}))
VibeLang
// run: provide UsersLive { Console.log(JSON.stringify(Users.find("1"))); Console.log(Users().region) }
const UsersLive = Layer.effect(Users) {
  const seen = new Set<string>()
  return {
    find(id) { return getUser(id) },
    region: "eu"
  }
}
// Output: {"id":"1","name":"Ada"}
// Output: eu

Types in this example

UsersLive
const UsersLive: Layer<Users, never, never>Layer: provides Users; needs nothing.
Layer
(alias) module "effect/Layer"
effect
const effect: <Users, { find(id: string): User!NotFound!; region: string; }, never, never>(service: Key<Users, { find(id: string): User!NotFound!; region: string; }>, effect: { find(id: string): User!NotFound!; region: string; }!!) => Layer<Users, never, never>Constructs a layer from an effect that produces a single service.
Users
(alias) abstract class Users
seen
const seen: Set<string>
Set
var Set: new <string>(iterable?: Iterable<string> | null | undefined) => Set<string>
find
(method) find(id: string): User!NotFound!
id
(parameter) id: string
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
region
(property) region: string

Schemas (D133). One declaration gives the schema and its type.

Effect today (TypeScript)
const Account = Schema.Struct({
  id: Schema.String,
  nickname: Schema.optional(Schema.String),
  role: Schema.Literals(["admin", "member"])
})
interface Account extends Schema.Schema.Type<typeof Account> {}
VibeLang
// run: Console.log(JSON.stringify(Schema.decodeUnknownEffect(Account)({ id: "1", role: "admin" })))
schema Account {
  id: string
  nickname?: string
  role: "admin" | "member"
}
// Output: {"id":"1","role":"admin"}

Types in this example

Account
const Account: Struct<{ readonly id: String; readonly nickname: optional<String>; readonly role: Literals<readonly ["admin", "member"]>; }>
id
(property) id: String
nickname
(property) nickname: optional<String>
role
(property) role: Literals<readonly ["admin", "member"]>

Readonly by default (D134). Write mutable for a member you assign.

Effect today (TypeScript)
interface Cart { readonly id: string; items: Array<string> }
VibeLang
interface Cart { id: string; mutable items: Array<string> }

Types in this example

Cart
interface Cart
id
(property) Cart.id: string
items
(property) Cart.items: string[]
Array
interface Array<T>

Programs

Statements instead of generators. Calls run; there is no yield*.

Effect today (TypeScript)
const greetUser = Effect.fn("greetUser")(function* (id: string) {
  const user = yield* getUser(id)
  yield* Effect.Console.log(`Hello, ${user.name}`)
  return user.name
})
VibeLang
// run: Console.log(Effect.provide(() => greetUser("1"), Logger.layer([sync(() => Logger.make(({ message }) => console.log(...(message as unknown[]))))])))
function greetUser(id: string) {
  const user = getUser(id)
  Console.log(`Hello, ${user.name}`)
  return user.name
}
// Output: Hello, Ada
// Output: Ada

Types in this example

greetUser
function greetUser(id: string): string!NotFound!
id
(parameter) id: string
user
const user: User
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
Console
(alias) module "effect/Console"
log
const log: (...args: ReadonlyArray<any>) => void!!Runs here and returns void.Logs a general-purpose message to the console.
name
(property) User.name: string

Programs passed by name (D147). A call runs where it stands, and is passed as a program where a parameter expects one.

Effect today (TypeScript)
const user = yield* getUser("1")
const first = yield* Effect.race(getUser("1"), getUser("2"))
VibeLang
// run: Console.log(fastest().name)
function fastest() {
  const user = getUser("1")
  return race(getUser("1"), getUser("2"))
}
// Output: Ada

Types in this example

fastest
function fastest(): User!NotFound!
user
const user: User
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
race
const race: <User, NotFound, never, User, NotFound, never>(self: User!NotFound!, that: User!NotFound!, options?: { readonly onWinner?: (options: { readonly fiber: Fiber<any, any>; readonly index: number; readonly parentFiber: Fiber<any, any>; }) => void; }) => User!NotFound!Runs here and returns User.Races two effects and returns the first successful result.
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to race as a program: it runs when that program runs, not here, and returns User.

Providing layers (D130). return inside the block returns from the function.

Effect today (TypeScript)
const program = Effect.gen(function* () {
  const users = yield* Users
  return yield* users.find("1")
}).pipe(Effect.provide(UsersLive))
VibeLang
// run: Console.log(JSON.stringify(program()))
function program() {
  provide UsersLive {
    return Users.find("1")
  }
}
// Output: {"id":"1","name":"Ada"}

Types in this example

program
function program(): User!NotFound!
UsersLive
(alias) const UsersLive: Layer<Users, never, never>Layer: provides Users; needs nothing.
Users
(alias) abstract class Users
find
(method) Users.find(id: string): User!NotFound!Runs here and returns User.

Providing one service (D173, D174). provide Key = value provides one implementation without a layer; layers and services mix in one list, and an effect module such as Random stands for its key, Random.Random.

Effect today (TypeScript)
const seeded = { nextIntUnsafe: () => 4, nextDoubleUnsafe: () => 0.5 }
const roll = Random.nextInt.pipe(
  Effect.provideService(Random.Random, seeded),
  Effect.provide(Logger.layer([]))
)
VibeLang
// run: Console.log(roll())
const seeded = { nextIntUnsafe: () => 4, nextDoubleUnsafe: () => 0.5 }

function roll(): number!! {
  provide Logger.layer([]), Random = seeded {
    return Random.nextInt()
  }
}
// Output: 4

Types in this example

seeded
const seeded: { nextIntUnsafe: () => number; nextDoubleUnsafe: () => number; }
nextIntUnsafe
(property) nextIntUnsafe: () => number
nextDoubleUnsafe
(property) nextDoubleUnsafe: () => number
roll
function roll(): number!!
Logger
(alias) module "effect/Logger"
layer
const layer: <readonly []>(loggers: readonly [], options?: { readonly mergeWithExisting?: boolean | undefined; } | undefined) => Layer<never, never, never>Creates a Layer which will overwrite the current set of loggers with the specified array of loggers.
Random
(alias) module "effect/Random"Provides the service Random.Random to this provide block's statements.
Random
(alias) module "effect/Random"
nextInt
const nextInt: () => number!!Runs here and returns number.Generates a random integer between Number.MIN_SAFE_INTEGER (inclusive) and Number.MAX_SAFE_INTEGER (inclusive).

Catching by tag (D216). Catch the failure and match on it. A match that names every failure needs no rethrow; default: throw e passes on the ones it does not name.

Effect today (TypeScript)
const describe = (id: string) =>
  Effect.map(getUser(id), (user) => user.name).pipe(
    Effect.catchTag("NotFound", (e) => Effect.succeed(`no user ${e.id}`))
  )
VibeLang
// run: Console.log(JSON.stringify([describe("1"), describe("missing")]))
function describe(id: string) {
  try {
    return getUser(id).name
  } catch (e) {
    return match (e) {
      when NotFound: `no user ${e.id}`;
    }
  }
}
// Output: ["Ada","no user missing"]

Types in this example

describe
function describe(id: string): string!!
id
(parameter) id: string
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
name
(property) User.name: string
e
var e: NotFound
NotFound
(alias) class NotFound
id
(property) id: string

Falling back in an expression (D236). x catch fallback runs the fallback only after an expected failure; defects and interrupts pass through.

Effect today (TypeScript)
const nameOrGuest = (id: string) =>
  Effect.map(getUser(id), (user) => user.name).pipe(
    Effect.orElseSucceed(() => "guest")
  )
VibeLang
// run: Console.log(JSON.stringify([nameOrGuest("1"), nameOrGuest("missing")]))
function nameOrGuest(id: string) {
  return getUser(id).name catch "guest"
}
// Output: ["Ada","guest"]

Types in this example

nameOrGuest
function nameOrGuest(id: string): string!!
id
(parameter) id: string
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
name
(property) User.name: string
catch
catchRuns the left side and, after an expected failure (NotFound), the fallback instead (Effect.orElseSucceed). Returns string. Defects and interrupts pass through.

Catching in an expression (D236). x catch (e) => … passes the failure to a handler, which can match on it; a match that names every failure removes them all.

Effect today (TypeScript)
const nameOrReason = (id: string) =>
  Effect.map(getUser(id), (user) => user.name).pipe(
    Effect.catch((e) => Effect.succeed(`no user ${e.id}`))
  )
VibeLang
// run: Console.log(JSON.stringify([nameOrReason("1"), nameOrReason("missing")]))
function nameOrReason(id: string) {
  return getUser(id).name catch (e) => match (e) {
    when NotFound: `no user ${e.id}`;
  }
}
// Output: ["Ada","no user missing"]

Types in this example

nameOrReason
function nameOrReason(id: string): string!!
id
(parameter) id: string
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
name
(property) User.name: string
catch
catchRuns the left side and passes an expected failure, NotFound, to the handler (Effect.catch). Returns string. Defects and interrupts pass through.
e
(parameter) e: NotFound
NotFound
(alias) class NotFound
id
(property) id: string

Handling control operations (D132). Effect has none, so the left column is VibeLang's long form.

VibeLang long form
// run: Console.log(auto("v1"))
function auto(version: string) {
  return handle(() => release(version), Approval, {
    approve(_request, continuation) {
      return resume(continuation, true)
    }
  })
}
// Output: released v1

Types in this example

auto
function auto(version: string): string!!
version
(parameter) version: string
handle
function handle<string, never, Approval, Approval, Approval, { approve(_request: string, continuation: Continuation<boolean, never, string>): string!!; }>(program: string!!Approval, control: Key<Approval, Approval>, clauses: { approve(_request: string, continuation: Continuation<boolean, never, string>): string!!; }): string!!Runs here and returns string.handle(program, Control, clauses, onReturn?): runs program with a deep handler for Control's operations. Each clause receives the operation's arguments and k, the rest of the program. The result is what the program returns (through onReturn when given) or what a clause returns without resuming. Inside a clause, resume(k, v) has the program's own result type; with onReturn it has type never, so a clause's tail resume adds nothing to the result (D97).
release
(alias) function release(version: string): string!!ApprovalRuns here and returns string.
Approval
(alias) abstract class Approval
approve
(method) approve(_request: string, continuation: Continuation<boolean, never, string>): string!!
_request
(parameter) _request: string
continuation
(parameter) continuation: Continuation<boolean, never, string>
resume
const resume: <boolean, never, string>(k: Continuation<boolean, never, string>, value: boolean) => string!!Runs here and returns string.Continues the program with value at the perform site; returns what the handle then produces.
VibeLang
// run: Console.log(auto("v1"))
function auto(version: string) {
  try {
    return release(version)
  } handle Approval.approve(request) {
    resume(true)
  }
}
// Output: released v1

Types in this example

auto
function auto(version: string): string!!
version
(parameter) version: string
release
(alias) function release(version: string): string!!ApprovalRuns here and returns string.
Approval
(alias) abstract class Approval
approve
(method) Approval.approve(request: string): boolean!!
request
const request: string
resume
const resume: <boolean, never, never>(k: Continuation<boolean, never, never>, value: boolean) => !!Runs here and returns never.Continues the program with value at the perform site; returns what the handle then produces.

Entry point (D135). An exported main runs when Node starts its module, and not when a test imports it.

Effect today (TypeScript)
// main.ts
export const main = Effect.log("Hello")
// run.ts
NodeRuntime.runMain(main)
VibeLang
// run: Effect.provide(main(), Logger.layer([sync(() => Logger.make(({ message }) => console.log(...(message as unknown[]))))]))
export function main() {
  log("Hello")
}
// Output: Hello

Types in this example

main
function main(): void!!
log
const log: (...message: ReadonlyArray<any>) => void!!Runs here and returns void.Logs one or more messages using the default log level.

Causes, exits, and interruption

Catch every cause (D217). match on exit(…): a Failure exit's cause also holds defects and interrupts.

Effect today (TypeScript)
const safe = (id: string) =>
  Effect.map(getUser(id), (user) => user.name).pipe(
    Effect.catchCause((cause) => Effect.succeed(`failed: ${Cause.pretty(cause)}`))
  )
VibeLang
// run: Console.log(safe("1")); Console.log(safe("missing").includes("NotFound"))
function safe(id: string) {
  return match (exit(getUser(id))) {
    when Exit.Success(let user): user.name;
    when Exit.Failure(let cause): `failed: ${Cause.pretty(cause)}`;
  }
}
// Output: Ada
// Output: true

Types in this example

safe
function safe(id: string): string!!
id
(parameter) id: string
exit
const exit: <User, NotFound, never>(self: User!NotFound!) => Exit<User, NotFound>!!Runs here and returns Exit<User, NotFound>.Transforms an effect to encapsulate both failure and success using the Exit data type.
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to exit as a program: it runs when that program runs, not here, and returns User.
user
let user: User
name
(property) User.name: string
cause
let cause: Cause<NotFound>
Cause
(alias) module "effect/Cause"
pretty
const pretty: <NotFound>(cause: Cause<NotFound>) => stringFormats a Cause as a human-readable string for logging or debugging.

Rethrow a cause (D177). throw with a Cause fails with every reason it holds.

Effect today (TypeScript)
const logged = getUser("1").pipe(
  Effect.catchCause((cause) => Effect.andThen(Effect.logError(cause), Effect.failCause(cause)))
)
VibeLang
// run: Console.log(JSON.stringify(logged()))
function logged() {
  const outcome = exit(getUser("1"))
  if (outcome is Exit.Failure(let cause)) {
    logError(cause)
    throw cause
  }
  return outcome.value
}
// Output: {"id":"1","name":"Ada"}

Types in this example

logged
function logged(): User!NotFound!
outcome
const outcome: Exit<User, NotFound>
exit
const exit: <User, NotFound, never>(self: User!NotFound!) => Exit<User, NotFound>!!Runs here and returns Exit<User, NotFound>.Transforms an effect to encapsulate both failure and success using the Exit data type.
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to exit as a program: it runs when that program runs, not here, and returns User.
cause
let cause: Cause<NotFound>
logError
const logError: (...message: ReadonlyArray<any>) => void!!Runs here and returns void.Logs one or more messages at the ERROR level.
outcome
const outcome: Success<User, NotFound>
value
(property) Success<User, NotFound>.value: User

Run on every exit (D178). The block runs without interruption; a success carries no value.

Effect today (TypeScript)
const tracked = getUser("1").pipe(
  Effect.onExit((exit) => Effect.log(`lookup ${exit._tag}`))
)
VibeLang
// run: Console.log(Effect.provide(() => tracked().name, Logger.layer([sync(() => Logger.make(({ message }) => console.log(...(message as unknown[]))))])))
function tracked() {
  try {
    return getUser("1")
  } finally (exit) {
    log(`lookup ${exit._tag}`)
  }
}
// Output: lookup Success
// Output: Ada

Types in this example

tracked
function tracked(): User!NotFound!
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
exit
const exit: Failure<void, NotFound> | Success<void, NotFound>
log
const log: (...message: ReadonlyArray<any>) => void!!Runs here and returns void.Logs one or more messages using the default log level.
_tag
(property) _tag: "Failure" | "Success"

Exit, yield, and interrupt (D179). exit takes its program by name.

Effect today (TypeScript)
const result = yield* Effect.exit(getUser("1"))
yield* Effect.yieldNow
if (result._tag === "Failure") yield* Effect.interrupt
VibeLang
// run: Console.log(outcome()._tag)
function outcome() {
  const result = exit(getUser("1"))
  yieldNow()
  if (result is Exit.Failure) interrupt()
  return result
}
// Output: Success

Types in this example

outcome
function outcome(): Exit<User, NotFound>!!
result
const result: Exit<User, NotFound>
exit
const exit: <User, NotFound, never>(self: User!NotFound!) => Exit<User, NotFound>!!Runs here and returns Exit<User, NotFound>.Transforms an effect to encapsulate both failure and success using the Exit data type.
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to exit as a program: it runs when that program runs, not here, and returns User.
yieldNow
const yieldNow: (priority?: number) => void!!Runs here and returns void.Bare yieldNow() (D179): yields to the scheduler, with an optional priority.
interrupt
const interrupt: () => !!Runs here and returns never.Bare interrupt() (D179): interrupts the running fiber, as Effect.interrupt.

Interruption (D180). The blocks keep return and break meaning what they mean in the function; uninterruptible(program) also works.

Effect today (TypeScript)
const guarded = Effect.uninterruptibleMask((restore) =>
  Effect.gen(function* () {
    const user = yield* getUser("1")
    const orders = yield* restore(getOrders(user.id))
    return { user, orders }
  })
)
const cancellable = Effect.interruptible(getOrders("1"))
VibeLang
// run: Console.log(JSON.stringify([guarded(), cancellable()]))
function guarded() {
  uninterruptible {
    const user = getUser("1")
    restore {
      return { user, orders: getOrders(user.id) }
    }
  }
}

function cancellable() {
  interruptible {
    return getOrders("1")
  }
}
// Output: [{"user":{"id":"1","name":"Ada"},"orders":1},1]

Types in this example

guarded
function guarded(): { user: User; orders: number; }!NotFound!
user
const user: User
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
user
(property) user: User
orders
(property) orders: number
getOrders
function getOrders(id: string): numberPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<number, never, never>.
id
(property) User.id: string
cancellable
function cancellable(): number!!

Calls

A tagged template is a call of its tag (D267). If sql returns an Effect, the statement sql`CREATE TABLE example (id TEXT)` runs it, and const rows = sql`SELECT * FROM example` receives its success value. Plain tags keep JavaScript behavior. A discarded structured Effect subtype, such as SQL Statement, reports EK4301: call the result explicitly, or annotate the tag as returning Effect<A, E, R>. A parameter that expects a program takes an Effectful template by name, just as it takes a call (D147).

No imports from effect (D146).

Effect today (TypeScript)
import { Console } from "effect"

const ready = Console.log("ready")
VibeLang
// run: ready()
function ready() {
  Console.log("ready")
}
// Output: ready

Types in this example

ready
function ready(): void!!
Console
(alias) module "effect/Console"
log
const log: (...args: ReadonlyArray<any>) => void!!Runs here and returns void.Logs a general-purpose message to the console.

Common functions without the Effect. prefix (D151): sleep, log, retry, repeat, timeout, race, all, forEach, die, withSpan, and the log levels.

Effect today (TypeScript)
const patient = Effect.gen(function* () {
  yield* Effect.sleep("10 millis")
  return yield* Effect.timeout(Effect.retry(getUser("1"), Schedule.recurs(2)), "1 second")
})
VibeLang
// run: Console.log(JSON.stringify(patient()))
function patient() {
  sleep("10 millis")
  return timeout(retry(getUser("1"), Schedule.recurs(2)), "1 second")
}
// Output: {"id":"1","name":"Ada"}

Types in this example

patient
function patient(): User!NotFound | TimeoutError!
sleep
const sleep: (duration: Duration.Input) => void!!Runs here and returns void.Returns an effect that suspends the current fiber for the specified duration without blocking a JavaScript thread.
timeout
const timeout: <User, NotFound, never>(self: User!NotFound!, duration: Duration.Input) => User!NotFound | TimeoutError!Runs here and returns User.Adds a time limit to an effect, triggering a timeout if the effect exceeds the duration.
retry
const retry: <User, NotFound, never, number, never, never>(self: User!NotFound!, policy: Schedule<number, NotFound, never, never>) => User!NotFound!Passed by name to timeout as a program: it runs when that program runs, not here, and returns User.Retries typed failures from an effect according to a retry policy.
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to retry as a program: it runs when that program runs, not here, and returns User.
Schedule
(alias) module "effect/Schedule"
recurs
const recurs: (times: number) => Schedule<number>Returns a Schedule which can only be stepped the specified number of times before it terminates.

Methods on Effect's data types (D152).

Effect today (TypeScript)
const hot = Stream.take(
  Stream.map(Stream.filter(Stream.make(18, 25, 30), (c) => c >= 25), (c) => `${c} C`),
  2
)
VibeLang
// run: Console.log(JSON.stringify(Stream.runCollect(hot())))
function hot() {
  return Stream.make(18, 25, 30).filter((c) => c >= 25).map((c) => `${c} C`).take(2)
}
// Output: ["25 C","30 C"]

Types in this example

function
function hot(): Stream<string, never, never>!!
hot
function hot(): Stream<string, never, never>Plain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<Stream<string, never, never>, never, never>.
Stream
(alias) module "effect/Stream"
make
const make: <readonly [18, 25, 30]>(values_0: 18, values_1: 25, values_2: 30) => Stream<18 | 25 | 30, never, never>Creates a stream from a sequence of values.
filter
(property) filter: <18 | 25 | 30, never, never>(self: Stream<18 | 25 | 30, never, never>, predicate: Predicate<18 | 25 | 30>) => Stream<18 | 25 | 30, never, never>Method syntax: calls Stream.filter(Stream.make(18, 25, 30), …).Filters a stream to the elements that satisfy a predicate.
c
(parameter) c: 18 | 25 | 30
map
(property) map: <18 | 25 | 30, never, never, string>(self: Stream<18 | 25 | 30, never, never>, f: (a: 18 | 25 | 30, i: number) => string) => Stream<string, never, never>Method syntax: calls Stream.map(Stream.make(18, 25, 30).filter((c) => c >= 25), …).Transforms the elements of this stream using the supplied function.
take
(property) take: <string, never, never>(self: Stream<string, never, never>, n: number) => Stream<string, never, never>Method syntax: calls Stream.take(Stream.make(18, 25, 30).filter((c) => c >= 25).map((c) => ${c} C), …).Takes the first n elements from this stream.

The pipe operator (D154). % is the value on the left; where it lands in a program parameter, the left side is passed as a program.

Effect today (TypeScript)
const piped = getUser("1").pipe(
  Effect.retry(Schedule.recurs(2)),
  Effect.timeout("1 second")
)
VibeLang
// run: Console.log(JSON.stringify(piped()))
function piped() {
  return getUser("1") |> retry(%, Schedule.recurs(2)) |> timeout(%, "1 second")
}
// Output: {"id":"1","name":"Ada"}

Types in this example

piped
function piped(): User!NotFound | TimeoutError!
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to retry as a program: it runs when that program runs, not here, and returns User.
retry
const retry: <User, NotFound, never, number, never, never>(self: User!NotFound!, policy: Schedule<number, NotFound, never, never>) => User!NotFound!Passed by name to timeout as a program: it runs when that program runs, not here, and returns User.Retries typed failures from an effect according to a retry policy.
%
(topic) %: User!NotFound!Passed by name to retry as a program: getUser("1") runs when that program runs, not here.
Schedule
(alias) module "effect/Schedule"
recurs
const recurs: (times: number) => Schedule<number>Returns a Schedule which can only be stepped the specified number of times before it terminates.
timeout
const timeout: <User, NotFound, never>(self: User!NotFound!, duration: Duration.Input) => User!NotFound | TimeoutError!Runs here and returns User.Adds a time limit to an effect, triggering a timeout if the effect exceeds the duration.
%
(topic) %: User!NotFound!Passed by name to timeout as a program: getUser("1") |> retry(%, Schedule.recurs(2)) runs when that program runs, not here.

TC39 forms

Throw in an expression (D186, throw expressions).

Effect today (TypeScript)
const findUser = (users: Map<string, User>, id: string) =>
  Effect.fromNullishOr(users.get(id)).pipe(Effect.mapError(() => new NotFound({ id })))
VibeLang
// run: Console.log(findUser(new Map([["1", { id: "1", name: "Ada" }]]), "1").name); Console.log(Effect.result(findUser(new Map<string, User>(), "missing"))._tag)
function findUser(users: Map<string, User>, id: string) {
  return users.get(id) ?? throw new NotFound({ id })
}
// Output: Ada
// Output: Failure

Types in this example

findUser
function findUser(users: Map<string, User>, id: string): User!NotFound!
users
(parameter) users: Map<string, User>
Map
interface Map<K, V>
User
(alias) interface User
id
(parameter) id: string
get
(method) Map<string, User>.get(key: string): User | undefinedReturns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
NotFound
(alias) constructor NotFound<{ readonly id: string; }>(args: { readonly id: string; }): NotFound
id
(property) id: string

Blocks as values (D187, do expressions). The value is the last statement's.

Effect today (TypeScript)
const label = (id: string) =>
  Effect.map(getUser(id), (user) => (user.name.length > 3 ? user.name : "short"))
VibeLang
// run: Console.log(label("1"))
function label(id: string) {
  const name = do {
    const user = getUser(id)
    if (user.name.length > 3) user.name
    else "short"
  }
  return name
}
// Output: short

Types in this example

label
function label(id: string): string!NotFound!
id
(parameter) id: string
name
const name: string
user
const user: User
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
name
(property) User.name: string
length
(property) String.length: numberReturns the length of a String object.

Blocks after calls (D188, block params). The block is the call's program; it needs return for a value.

Effect today (TypeScript)
const loaded = Effect.withSpan(
  Effect.gen(function* () {
    const user = yield* getUser("1")
    return user.name
  }),
  "load"
)

it.live("finds a user", () =>
  Effect.gen(function* () {
    const user = yield* getUser("1")
    assert.strictEqual(user.name, "Ada")
  }))
VibeLang
// run: Console.log(loaded())
function loaded() {
  return withSpan("load") {
    const user = getUser("1")
    return user.name
  }
}

// Output: Ada

Types in this example

loaded
function loaded(): string!NotFound!
withSpan
const withSpan: <string, NotFound, never>(name: string, self: string!NotFound!) => string!NotFound!Runs here and returns string.Wraps the effect with a child span for tracing.
user
const user: User
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
name
(property) User.name: string
VibeLang
// block-params.test.vibe
import { assert, it } from "@effect/vitest"

it.live("finds a user") {
  const user = getUser("1")
  assert.strictEqual(user.name, "Ada")
}
// Output:

Types in this example

assert
(alias) const assert: Chai.Assert
it
(alias) const it: Vitest.Methods<never, {}>Vitest test API extended with Effect test helpers.
live
(property) Vitest.Methods<never, {}>.live: <void, NotFound>(name: string, self: Vitest.TestFunction<void, NotFound, Scope, [TestContext]>, timeout?: number | TestOptions) => void
user
const user: User
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
strictEqual
(method) Chai.Assert.strictEqual<string>(actual: string, expected: string, message?: string): voidAsserts strict equality (===) of actual and expected.
name
(property) User.name: string

it.live uses the live clock because this page's getUser sleeps. it.effect supplies a test clock; a test that sleeps there must advance it with TestClock.adjust.

Pattern matching

From the TC39 pattern matching proposal (stage 1). See Pattern matching with match and is.

Match a value (D206, D209). Without default, a missing case of a finite union is EK4701, as with Match.exhaustive.

Effect today (TypeScript)
const phaseLabel = (phase: "draft" | "published") =>
  Match.value(phase).pipe(
    Match.when("draft", () => "Draft"),
    Match.when("published", () => "Published"),
    Match.exhaustive
  )
VibeLang
// run: Console.log(JSON.stringify([phaseLabel("draft"), phaseLabel("published")]))
function phaseLabel(phase: "draft" | "published") {
  return match (phase) {
    when "draft": "Draft";
    when "published": "Published";
  }
}
// Output: ["Draft","Published"]

Types in this example

function
function phaseLabel(phase: "draft" | "published"): "Draft" | "Published"!!
phaseLabel
function phaseLabel(phase: "draft" | "published"): "Draft" | "Published"Plain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<"Draft" | "Published", never, never>.
phase
(parameter) phase: "draft" | "published"

Match errors by class (D215). An error class is a pattern; and { let id } binds a property.

Effect today (TypeScript)
const describeFailure = (failure: NotFound | Timeout) =>
  Match.value(failure).pipe(
    Match.tag("NotFound", (e) => `no user ${e.id}`),
    Match.tag("Timeout", () => "retry"),
    Match.exhaustive
  )
VibeLang
// run: Console.log(JSON.stringify([describeFailure(new NotFound({ id: "7" })), describeFailure(new Timeout())]))
function describeFailure(failure: NotFound | Timeout) {
  return match (failure) {
    when NotFound and { let id }: `no user ${id}`;
    when Timeout: "retry";
  }
}
// Output: ["no user 7","retry"]

Types in this example

function
function describeFailure(failure: NotFound | Timeout): string!!
describeFailure
function describeFailure(failure: NotFound | Timeout): stringPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<string, never, never>.
failure
(parameter) failure: NotFound | Timeout
NotFound
(alias) class NotFound
Timeout
(alias) class Timeout
id
let id: string

Option, Exit, and Result (D208). The arms can run Effect functions.

Effect today (TypeScript)
const greet = (name: Option.Option<string>) =>
  Option.match(name, { onNone: () => "Hello", onSome: (n) => `Hello, ${n}` })
const failed = (outcome: Exit.Exit<User, NotFound>) => Exit.isFailure(outcome)
VibeLang
// run: Console.log(greet(Option.some("Ada"))); Console.log(greet(Option.none())); Console.log(failed(Exit.fail(new NotFound({ id: "7" })))); Console.log(failed(Exit.succeed({ id: "1", name: "Ada" })))
function greet(name: Option.Option<string>) {
  return match (name) {
    when Option.Some(let n): `Hello, ${n}`;
    when Option.None: "Hello";
  }
}

function failed(outcome: Exit.Exit<User, NotFound>) {
  return outcome is Exit.Failure
}
// Output: Hello, Ada
// Output: Hello
// Output: true
// Output: false

Types in this example

function
function greet(name: Option.Option<string>): string!!
greet
function greet(name: Option.Option<string>): stringPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<string, never, never>.
name
(parameter) name: Option<string>
Option
(alias) module "effect/Option"
Option
type Option<A> = None<A> | Some<A>The Option data type represents optional values. An Option<A> is either Some<A>, containing a value of type A, or None, representing absence.
n
let n: string
function
function failed(outcome: Exit.Exit<User, NotFound>): boolean!!
failed
function failed(outcome: Exit.Exit<User, NotFound>): booleanPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<boolean, never, never>.
outcome
(parameter) outcome: Exit<User, NotFound>
Exit
(alias) module "effect/Exit"
Exit
type Exit<A, E = never> = Failure<A, E> | Success<A, E>Represents the result of an Effect computation.
User
(alias) interface User
NotFound
(alias) class NotFound

Test and bind (D211). Bindings in an is work in an if or while condition.

Effect today (TypeScript)
const firstFailure = (outcome: Exit.Exit<User, NotFound>) =>
  Exit.isFailure(outcome) && Cause.hasFails(outcome.cause) ? Cause.pretty(outcome.cause) : "ok"
VibeLang
// run: Console.log(firstFailure(Exit.succeed({ id: "1", name: "Ada" }))); Console.log(firstFailure(Exit.fail(new NotFound({ id: "7" }))).includes("NotFound"))
function firstFailure(outcome: Exit.Exit<User, NotFound>) {
  if (outcome is Exit.Failure(let cause) && Cause.hasFails(cause)) return Cause.pretty(cause)
  return "ok"
}
// Output: ok
// Output: true

Types in this example

function
function firstFailure(outcome: Exit.Exit<User, NotFound>): string!!
firstFailure
function firstFailure(outcome: Exit.Exit<User, NotFound>): stringPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<string, never, never>.
outcome
(parameter) outcome: Exit<User, NotFound>
Exit
(alias) module "effect/Exit"
Exit
type Exit<A, E = never> = Failure<A, E> | Success<A, E>Represents the result of an Effect computation.
User
(alias) interface User
NotFound
(alias) class NotFound
cause
let cause: Cause<NotFound>
Cause
(alias) module "effect/Cause"
hasFails
const hasFails: <NotFound>(self: Cause<NotFound>) => booleanReturns true if the cause contains at least one Fail reason.
pretty
const pretty: <NotFound>(cause: Cause<NotFound>) => stringFormats a Cause as a human-readable string for logging or debugging.

Escape hatches

Plain, self-contained TypeScript inside sync, try, and callback (D148, D229-D232). A throw in sync is a defect; in try it is an expected failure. Async bodies are Effect.promise and Effect.tryPromise. A hatch's function reads values of the .vibe file only as inputs, the arguments after it, which fill its first parameters; try's error mapper stays second (EK4405). runPromise leads back in from a hatch's body or a .ts file.

Effect today (TypeScript)
const id = Effect.sync(() => crypto.randomUUID())
const parsed = Effect.try({ try: () => JSON.parse(text), catch: (error) => new BadJson({ error }) })
const loaded = Effect.promise(() => Promise.resolve(42))
const fetched = Effect.tryPromise((signal) => fetch(url, { signal }))
const tick = Effect.callback<number>((resume) => {
  const timer = setTimeout(() => resume(Effect.succeed(7)), 10)
  return Effect.sync(() => clearTimeout(timer))
})
const name = await Effect.runPromise(greetUser("1"))
VibeLang
// run: provide Logger.layer([sync(() => Logger.make(({ message }) => console.log(...(message as unknown[]))))]) { const value = hatches('{"ok":true}', "data:text/plain,hello"); Console.log(JSON.stringify({ ...value, id: /^[0-9a-f-]{36}$/.test(value.id) })) }
error BadJson { error: unknown }

function hatches(text: string, url: string) {
  const id = sync(crypto.randomUUID())
  const parsed = try((text) => JSON.parse(text), (error) => new BadJson({ error }), text)
  const loaded = sync(async () => Promise.resolve(42))
  const fetched = try(async (url, signal) => fetch(url, { signal }), url)
  const tick = callback<number>((resume) => {
    const timer = setTimeout(() => resume(7), 10)
    return () => clearTimeout(timer)
  })
  const name = sync(async (greet) => runPromise(greet("1")), greetUser)
  return { id, parsed, loaded, status: fetched.status, tick, name }
}
// Output: Hello, Ada
// Output: {"id":true,"parsed":{"ok":true},"loaded":42,"status":200,"tick":7,"name":"Ada"}

Types in this example

error
class BadJson
BadJson
class BadJson
error
(property) error: unknown
hatches
function hatches(text: string, url: string): { id: `${string}-${string}-${string}-${string}-${string}`; parsed: any; loaded: number; status: number; tick: number; name: string; }!BadJson | UnknownError!
text
(parameter) text: string
url
(parameter) url: string
id
const id: `${string}-${string}-${string}-${string}-${string}`
sync
function sync<`${string}-${string}-${string}-${string}-${string}`>(value: `${string}-${string}-${string}-${string}-${string}`): `${string}-${string}-${string}-${string}-${string}`!!An escape hatch: its first argument is self-contained TypeScript (D229).Runs here and returns ${string}-${string}-${string}-${string}-${string}.
crypto
var crypto: CryptoMDN Reference
randomUUID
(method) Crypto.randomUUID(): `${string}-${string}-${string}-${string}-${string}`The randomUUID() method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator. Available only in secure contexts.
parsed
const parsed: any
try
(alias) function attempt<any, BadJson, [string]>(evaluate: (args_0: string) => any, mapError: (error: unknown) => BadJson, args_0: string): any!BadJson!An escape hatch: its first argument is self-contained TypeScript (D229). Inputs, which fill its first parameters: text.Runs here and returns any.
JSON
var JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
parse
(method) JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): anyConverts a JavaScript Object Notation (JSON) string into an object.
error
(parameter) error: unknown
BadJson
constructor BadJson<{ readonly error: unknown; }>(args: { readonly error: unknown; }): BadJson
loaded
const loaded: number
sync
function sync<number, []>(evaluate: (args_0: AbortSignal) => PromiseLike<number>): number!!An escape hatch: its first argument is self-contained TypeScript (D229).Runs here and returns number.
Promise
var Promise: PromiseConstructor
resolve
(method) PromiseConstructor.resolve<number>(value: number): Promise<number>Creates a new resolved promise for the provided value.
fetched
const fetched: Response
try
(alias) function attempt<Response, [string]>(evaluate: (args_0: string, args_1: AbortSignal) => PromiseLike<Response>, args_0: string): Response!UnknownError!An escape hatch: its first argument is self-contained TypeScript (D229). Inputs, which fill its first parameters: url.Runs here and returns Response.
signal
(parameter) signal: AbortSignal
fetch
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>
signal
(property) RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.
tick
const tick: number
callback
function callback<number, never, []>(register: (args_0: Resume<number, never>, args_1: AbortSignal) => void | (() => void)): number!!An escape hatch: its first argument is self-contained TypeScript (D229).Runs here and returns number.
resume
(parameter) resume: Resume<number, never>
timer
const timer: NodeJS.Timeout
setTimeout
function setTimeout<[]>(callback: () => void, delay?: number): NodeJS.Timeout namespace setTimeout
resume
(parameter) resume: (value: number) => voidSucceeds with value.
clearTimeout
function clearTimeout(timeout: NodeJS.Timeout | string | number | undefined): void
name
const name: string
sync
function sync<string, [(id: string) => string!NotFound!]>(evaluate: (args_0: (id: string) => string!NotFound!, args_1: AbortSignal) => PromiseLike<string>, args_0: (id: string) => string!NotFound!): string!!An escape hatch: its first argument is self-contained TypeScript (D229). Inputs, which fill its first parameters: greetUser.Runs here and returns string.
greet
(parameter) greet: (id: string) => string!NotFound!
runPromise
(alias) const runPromise: <string, NotFound>(effect: string!NotFound!, options?: RunOptions | undefined) => Promise<string>Executes an effect and returns the result as a Promise.
greetUser
(alias) function greetUser(id: string): string!NotFound!
id
(property) id: `${string}-${string}-${string}-${string}-${string}`
parsed
(property) parsed: any
loaded
(property) loaded: number
status
(property) status: number
status
(property) Response.status: numberThe status read-only property of the Response interface contains the HTTP status codes of the response.
tick
(property) tick: number
name
(property) name: string

Fibers

Fork and join (D166, D225). A fiber belongs to the block that forks it. join [a, b] joins several and interrupts the rest on a failure.

Effect today (TypeScript)
const user = yield* Effect.forkChild(getUser("1"))
const orders = yield* Effect.forkChild(getOrders("1"))
return [yield* Fiber.join(user), yield* Fiber.join(orders)]
VibeLang
// run: Console.log(JSON.stringify(both()))
function both() {
  const user = fork getUser("1")
  const orders = fork getOrders("1")
  return [join user, join orders]
}
// Output: [{"id":"1","name":"Ada"},1]

Types in this example

both
function both(): (number | User)[]!NotFound!
user
const user: Fiber<User, NotFound>
fork
const fork: <User!NotFound!>(effectOrOptions?: (User!NotFound!) | undefined, options?: { readonly startImmediately?: boolean | undefined; readonly uninterruptible?: boolean | "inherit" | undefined; } | undefined) => Fiber<User, NotFound>!!ScopeRuns here and returns Fiber<User, NotFound>.The prelude's fork, for TypeScript callers and type positions.
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to fork as a program: it runs when that program runs, not here, and returns User.
orders
const orders: Fiber<number, never>
fork
const fork: <number!!>(effectOrOptions?: (number!!) | undefined, options?: { readonly startImmediately?: boolean | undefined; readonly uninterruptible?: boolean | "inherit" | undefined; } | undefined) => Fiber<number, never>!!ScopeRuns here and returns Fiber<number, never>.The prelude's fork, for TypeScript callers and type positions.
getOrders
function getOrders(id: string): numberPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<number, never, never>.
join
joinJoins the fiber, or every fiber of the array or object, keeping the shape (Fiber.join), and returns User. A failure interrupts the rest. Its failures propagate: NotFound.
join
joinJoins the fiber, or every fiber of the array or object, keeping the shape (Fiber.join), and returns number. A failure interrupts the rest.

Wait for several (D196). await.all behaves like Promise.all: it starts each element in its own fiber, keeps the shape, and a failure interrupts the rest.

Effect today (TypeScript)
const [user, orders] = yield* Effect.all([getUser("1"), getOrders("1")], { concurrency: "unbounded" })
const pair = yield* Effect.all({ user: getUser("1"), orders: getOrders("1") }, { concurrency: "unbounded" })
VibeLang
// run: Console.log(JSON.stringify(many()))
function many() {
  const [user, orders] = await.all [getUser("1"), getOrders("1")]
  const pair = await.all { user: getUser("1"), orders: getOrders("1") }
  return { user, orders, pair }
}
// Output: {"user":{"id":"1","name":"Ada"},"orders":1,"pair":{"user":{"id":"1","name":"Ada"},"orders":1}}

Types in this example

many
function many(): { user: User; orders: number; pair: { user: User; orders: number; }; }!NotFound!
user
const user: User
orders
const orders: number
all
await.allStarts each program in its own fiber and waits for all of them, keeping the shape (Effect.all, unbounded). A failure interrupts the rest. Returns [User, number]. Its failures propagate: NotFound.
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to $awaitAll as a program: it runs when that program runs, not here, and returns User.
getOrders
function getOrders(id: string): numberPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<number, never, never>.
pair
const pair: { user: User; orders: number; }
all
await.allStarts each program in its own fiber and waits for all of them, keeping the shape (Effect.all, unbounded). A failure interrupts the rest. Returns { user: User; orders: number; }. Its failures propagate: NotFound.
user
(property) user: User!NotFound!
orders
(property) orders: number!!
user
(property) user: User
orders
(property) orders: number
pair
(property) pair: { user: User; orders: number; }

First success, expected outcomes (D196, D258). await.any waits for the first success. await.allSettled returns successes and typed failures as Result values, rather than JavaScript's settlement objects, and never fails with a typed error. Defects and interruption propagate exactly as in Effect.all with mode: "result"; a cause with no typed failure still propagates. Wrap each element in exit(...) when every cause must become data.

Effect today (TypeScript)
const fastest = yield* Effect.raceAll([getUser("1"), getUser("2")])
const outcomes = yield* Effect.all([getUser("1"), getOrders("1")], { concurrency: "unbounded", mode: "result" })
VibeLang
// run: const value = outcomes(); Console.log(value.fastest.name); Console.log(JSON.stringify(value.results.map((result) => result._tag))); Console.log(JSON.stringify((await.allSettled [getUser("missing"), getOrders("1")]).map((result) => result._tag))); Console.log(Exit.isFailure(exit(await.allSettled [die("defect"), getOrders("1")])))
function outcomes() {
  const fastest = await.any [getUser("1"), getUser("2")]
  const results = await.allSettled [getUser("1"), getOrders("1")]
  return { fastest, results }
}
// Output: Ada
// Output: ["Success","Success"]
// Output: ["Failure","Success"]
// Output: true

Types in this example

outcomes
function outcomes(): { fastest: User; results: [Result<User, NotFound>, Result<number, never>]; }!NotFound!
fastest
const fastest: User
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to $awaitAny as a program: it runs when that program runs, not here, and returns User.
results
const results: [Result<User, NotFound>, Result<number, never>]
allSettled
await.allSettledStarts each program in its own fiber and waits for all of them, returning each outcome as a Result (Effect.all with mode "result"). It never fails. Returns [Result<User, NotFound>, Result<number, never>].
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to $awaitAllSettled as a program: it runs when that program runs, not here, and returns User.
getOrders
function getOrders(id: string): numberPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<number, never, never>.
fastest
(property) fastest: User
results
(property) results: [Result<User, NotFound>, Result<number, never>]

Fork a block (D166). The loop stops when the enclosing block exits.

Effect today (TypeScript)
const withHeartbeat = Effect.scoped(Effect.gen(function* () {
  yield* Effect.forkScoped(Effect.forever(Effect.andThen(Effect.log("tick"), Effect.sleep("1 second"))))
  return yield* getUser("1")
}))
VibeLang
// run: provide Logger.layer([]) { Console.log(withHeartbeat().name) }
function withHeartbeat() {
  fork {
    while (true) {
      log("tick")
      sleep("1 second")
    }
  }
  return getUser("1")
}
// Output: Ada

Types in this example

withHeartbeat
function withHeartbeat(): User!NotFound!
fork
const fork: <!!>(effectOrOptions?: (!!) | undefined, options?: { readonly startImmediately?: boolean | undefined; readonly uninterruptible?: boolean | "inherit" | undefined; } | undefined) => Fiber<never, never>!!ScopeRuns here and returns Fiber<never, never>.The prelude's fork, for TypeScript callers and type positions.
log
const log: (...message: ReadonlyArray<any>) => void!!Runs here and returns void.Logs one or more messages using the default log level.
sleep
const sleep: (duration: Duration.Input) => void!!Runs here and returns void.Returns an effect that suspends the current fiber for the specified duration without blocking a JavaScript thread.
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.

Detached and scoped fibers (D170).

Effect today (TypeScript)
yield* Effect.forkDetach(getUser("1"))
const scope = yield* Scope.make()
const fiber = yield* Effect.forkIn(getUser("2"), scope)
VibeLang
// run: Console.log(Exit.isFailure(Fiber.await(background())))
function background() {
  detach(getUser("1"))
  const scope = Scope.make()
  const fiber = forkIn(scope, getUser("2"))
  Scope.close(scope, Exit.void)
  return fiber
}
// Output: true

Types in this example

background
function background(): Fiber<User, NotFound>!!
detach
const detach: <User, NotFound, never>(program: User!NotFound!) => Fiber<User, NotFound>!!Runs here and returns Fiber<User, NotFound>.D170: starts a program in a fiber that nothing owns (Effect.forkDetach).
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to detach as a program: it runs when that program runs, not here, and returns User.
scope
const scope: Closeable
Scope
(alias) module "effect/Scope"
make
const make: (finalizerStrategy?: "sequential" | "parallel") => Closeable!!Runs here and returns Closeable.Creates a new Scope with the specified finalizer strategy.
fiber
const fiber: Fiber<User, NotFound>
forkIn
const forkIn: <User, NotFound, never>(scope: Scope, program: User!NotFound!) => Fiber<User, NotFound>!!Runs here and returns Fiber<User, NotFound>.D170: starts a program in a fiber that scope owns (Effect.forkIn).
getUser
(alias) function getUser(id: string): User!NotFound!Passed by name to forkIn as a program: it runs when that program runs, not here, and returns User.
close
const close: <void, never>(self: Closeable, exit: Exit<void, never>) => void!!Runs here and returns void.Closes a scope and runs its registered finalizers.
Exit
(alias) module "effect/Exit"
void
(alias) const void_: Exit<void, never>

Wait for a Deferred (D227). await waits for promises in JavaScript, so a Deferred uses its method.

Effect today (TypeScript)
const ready = yield* Deferred.make<string>()
yield* Deferred.succeed(ready, "go")
const value = yield* Deferred.await(ready)
VibeLang
// run: Console.log(signal())
function signal() {
  const ready = Deferred.make<string>()
  Deferred.succeed(ready, "go")
  return ready.await()
}
// Output: go

Types in this example

signal
function signal(): string!!
ready
const ready: Deferred<string, never>
Deferred
(alias) module "effect/Deferred"
make
const make: <string, never>() => Deferred<string, never>!!Runs here and returns Deferred<string, never>.Creates a new Deferred.
succeed
const succeed: <string, never>(self: Deferred<string, never>, value: string) => boolean!!Runs here and returns boolean.Attempts to complete the Deferred with the specified value.
await
(property) await: <string, never>(self: Deferred<string, never>) => string!!Method syntax: calls Deferred.await(ready, …).Runs here and returns string.

Race (D196, D197). await.race behaves like Promise.race: the first to finish wins, success or failure, and the rest are interrupted. It replaced select (EK4607); poll with Queue.poll and an if.

Effect today (TypeScript)
const next = yield* Effect.raceFirst(
  Effect.map(Queue.take(jobs), (job) => `ran ${job}`),
  Effect.as(Effect.sleep("1 second"), "timeout")
)
VibeLang
// run: Console.log(next())
function next() {
  const jobs = Queue.unbounded<string>()
  Queue.offer(jobs, "build")
  return await.race [
    `ran ${Queue.take(jobs)}`,
    do {
      sleep("1 second")
      "timeout"
    }
  ]
}
// Output: ran build

Types in this example

next
function next(): string!!
jobs
const jobs: Queue<string, never>
Queue
(alias) module "effect/Queue"
unbounded
const unbounded: <string, never>() => Queue<string, never>!!Runs here and returns Queue<string, never>.Creates an unbounded queue that can grow to any size without blocking producers.
offer
const offer: <string, never>(self: Enqueue<string, never>, message: string) => boolean!!Runs here and returns boolean.Adds a message to the queue. Returns false if the queue is done.
race
await.raceStarts each program in its own fiber; the first to finish wins, success or failure, and the others are interrupted (Effect.raceFirst). Returns string.
take
const take: <string, never>(self: Dequeue<string, never>) => string!!Passed by name to $awaitRace as a program: it runs when that program runs, not here, and returns string.Takes a single message from the queue, or wait for a message to be available.
sleep
const sleep: (duration: Duration.Input) => void!!Runs here and returns void.Returns an effect that suspends the current fiber for the specified duration without blocking a JavaScript thread.

A concurrent loop (D169). At most n items run at once, and all finish before the loop ends.

Effect today (TypeScript)
yield* Effect.forEach(ids, (id) => Effect.flatMap(getUser(id), (user) => Effect.log(user.name)), {
  concurrency: 8,
  discard: true
})
VibeLang
// run: Effect.provide(logAll(["1", "2"]), Logger.layer([sync(() => Logger.make(({ message }) => console.log(...(message as unknown[]))))]))
function logAll(ids: string[]) {
  for concurrent(8) (const id of ids) log(getUser(id).name)
}
// Output: Ada
// Output: Ada

Types in this example

logAll
function logAll(ids: string[]): void!NotFound!
ids
(parameter) ids: string[]
concurrent
for concurrentRuns the body for each item in its own fiber and joins them all before the loop ends; a failure interrupts the rest.
id
(parameter) id: string
log
const log: (...message: ReadonlyArray<any>) => void!!Runs here and returns void.Logs one or more messages using the default log level.
getUser
(alias) function getUser(id: string): User!NotFound!Runs here and returns User.
name
(property) User.name: string