Skip to content
LogoLogo
Effect, as a language

VibeLang

VibeLang gives Effect a simple, readable syntax that compiles to TypeScript, with first-class access to the TypeScript ecosystem.

DocsCheat sheet
npm create vibelang@latest my-app
cd my-app
npm run start
stageEarly Alpha
version0.0.1

Overview

VibeLang is Effect as its own language, written in TypeScript syntax. Effect puts what a function returns, what can fail, and what it depends on into one type, Effect<Success, Error, Requirements>, and the compiler checks it. As a library, Effect builds every feature out of generators, yield*, pipe, and classes. VibeLang gives each Effect idea its own syntax, and its compiler builds the same Effect program you would have written by hand.

Every function is an Effect

interface User { id: string; name: string }
 
error UserNotFound { id: string }
 
service Users {
  find(id: string): User!UserNotFound
}
 
function greet(id: string) {
  const name = Users.find(id).name catch "guest"
  return `Hello, ${name}`
}
// string!!Users

The comment under greet is the type your editor shows. string!!Users is VibeLang's spelling of Effect<string, never, Users>: greet returns a string, cannot fail, and needs the Users service. The compiler infers all three from the body, and TypeScript code that imports greet sees the Effect type.

Calls run in order, throw fails with a typed error, catch handles it, service declares a dependency, and fork starts a fiber.

Retries, timeouts, and concurrency

error ApiError { status: number }
 
service Users {
  name(id: string): string!ApiError
}
 
function nameOf(id: string) {
  return Users.name(id)
    |> retry(%, Schedule.exponential("100 millis"))
    |> timeout(%, "2 seconds")
    catch "unknown"
}
 
export function names(ids: ReadonlyArray<string>) {
  const all = forEach(ids, nameOf, { concurrency: 8 })
  log(`Loaded ${all.length} names`)
  return all
}

Both versions compile. The pipe operator |> passes Users.name(id) to retry and timeout by name, so they can run it again. catch "unknown" removes every failure, including the timeout, so nameOf is string!!Users. forEach with { concurrency: 8 } loads up to eight names at once.

Hello, VibeLang

Create a project from the starter. You need Node 26 or later and Bun 1.4 or later.

npm create vibelang@latest my-app
cd my-app
npm run start

The starter's two source files:

export error EmptyName
 
export function greeting(name: string) {
  if (name.length === 0) throw new EmptyName()
  return `Hello, ${name}!`
}

npm run start compiles the project and runs main, which prints:

Hello, VibeLang!
Recovered from EmptyName

Getting Started walks through the starter, and Installation covers editors and existing projects.

TypeScript stays one step away

VibeLang's compiler is TypeScript's compiler, extended. It checks .vibe and .ts files together and emits JavaScript and .d.ts files, so npm packages, your .ts files, and every Effect library work unchanged. TypeScript code calls VibeLang functions as ordinary Effect functions.

When you need plain TypeScript, sync, try, and callback take a plain TypeScript function, and a .ts file can sit beside any .vibe file.

function slug(title: string) {
  return sync((title) => {
    // Plain TypeScript: nothing in here is an Effect.
    let s = title.trim().toLowerCase()
    s = s.replaceAll(/[^a-z0-9]+/g, "-")
    return s
  }, title)
}
// string!!

Features

Everything Effect does, VibeLang does, because the output is Effect. The common features have their own syntax:

Effect featureVibeLang
Effect typesUser!NotFound!Users: returns User, fails with NotFound, needs Users
Typed errorserror NotFound { id: string }, throw new NotFound({ id }), x catch fallback
Dependency injectionservice Users { ... }, provide UsersLive { ... }
Structured concurrencyfork, join, await.all [a(), b()], for concurrent(8) (const id of ids)
Retries and timeoutswork() |> retry(%, policy) |> timeout(%, "1 second")
Resource safetyusing conn = connect(), finally
TracingwithSpan("checkout") { ... }
Schema validationschema User { id: string; role: "admin" | "member" }
Pattern matchingmatch (e) { when NotFound: ...; }

Streams, configuration, and Effect's HTTP, SQL, CLI, and AI packages need no syntax of their own: call them as you would in TypeScript. The cheat sheet puts every VibeLang form beside the Effect code it replaces.

Built for coding agents

When coding agents write a growing share of your code, the hard part moves from writing code to trusting it.

  • The compiler checks what agents write. Every function's type lists what it can fail with and what it needs, and code that breaks a declared type does not compile.
  • People can review what agents write. VibeLang code is about half the size of the Effect code it replaces, and it reads as plain TypeScript.
  • Agents write less. Fewer tokens per program means less to generate and less context to read back.

Where to go next

VibeLang is in early alpha. The language will change in major, breaking ways, and it is not ready for production use. Version 0.0.1 is tested on macOS on Apple silicon and on Linux on x64.