Welcome to VibeLang
VibeLang is a language for writing Effect programs with TypeScript syntax. Its compiler checks .vibe and .ts files together and emits JavaScript and TypeScript declarations.
// run: Console.log(Effect.provide(greet("ada"), Layer.succeed(Users, { find: (id) => ({ id, name: "Ada" }) })))
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
// Output: Hello, Adaclass UserNotFound extends Schema.TaggedError<UserNotFound>()("UserNotFound", {
id: Schema.String
}) {}
class Users extends Context.Service<Users, {
find(id: string): Effect.Effect<User, UserNotFound>
}>()("app/Users") {}
const greet = Effect.fn("greet")(function* (id: string) {
const users = yield* Users
const user = yield* users.find(id).pipe(
Effect.catchTag("UserNotFound", () => Effect.succeed({ name: "guest" }))
)
return `Hello, ${user.name}`
})A user service, a typed error, and a fallback, in Effect and in VibeLang. The comment under greet is the type your editor shows: string!!Users is VibeLang's spelling of Effect<string, never, Users>. It returns a string, has no expected failures, and needs the Users service. Defects and interruption remain possible. The compiler infers all three from the body, and TypeScript code that imports greet sees the Effect type. The TypeScript example adds a tracing span with Effect.fn("greet"); the VibeLang function is untraced by default.
VibeLang is v0. The language will change in major, breaking ways, and it is not ready for production use.
Why a language
Effect tracks failures and services. A TypeScript signature says what a function returns, but not what can fail or what it depends on. Effect puts all three in one type, Effect<Success, Error, Requirements>, and the compiler checks it. Retries, timeouts, concurrency, resource cleanup, and tracing come with it.
Effect expresses sequencing through library calls. Effect is a library, so it builds every feature out of what TypeScript already has: generators and yield* to run steps in order, pipe to compose them, and classes to declare errors and services. The code reads like a separate language written in library calls.
VibeLang adds language syntax. Each Effect idea gets its own syntax. Calls run in order, throw fails with a typed error, catch handles it, service declares a dependency, and fork starts a fiber. The compiler builds the same Effect program you would have written by hand.
TypeScript stays one step away
VibeLang shares TypeScript's ecosystem in two ways:
- It shares TypeScript's compiler and types. VibeLang's compiler is TypeScript's compiler, extended. It checks
.vibeand.tsfiles together and emits JavaScript and.d.tsfiles, so you can use existing Effect code and npm packages through TypeScript interop. TypeScript code calls exported VibeLang functions as ordinary Effect functions. - You can opt out to plain TypeScript.
sync,try, andcallbacktake a plain TypeScript function, and a.tsfile can sit beside any.vibefile.
// run: Console.log(slug("Hello VibeLang"))
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!!
// Output: hello-vibelangA hatch's function is self-contained: it reads the .vibe file's values only as the arguments passed after it, here title.
Effect's features, in VibeLang syntax
VibeLang runs on Effect and uses its library modules. Common operations have language syntax:
| Effect feature | VibeLang |
|---|---|
| Effect types | User!NotFound!Users: returns User, fails with NotFound, needs Users |
| Typed errors | error NotFound { id: string }, throw new NotFound({ id }), x catch fallback |
| Dependency injection | service Users { ... }, provide UsersLive { ... } |
| Structured concurrency | fork, join, await.all [a(), b()], for concurrent(8) (const id of ids) |
| Retries and timeouts | work() |> retry(%, policy) |> timeout(%, "1 second") |
| Resource safety | using conn = connect(), finally |
| Tracing | withSpan("checkout") { ... } |
| Schema validation | schema User { id: string; role: "admin" | "member" } |
| Pattern matching | match (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.
Check application code
The compiler tracks expected failures and required services for each function. A declared contract lets it reject code that adds an unhandled failure or dependency. Run focused tests to check behavior, including recovery, cancellation, and cleanup; successful type checking does not establish those results.
Try it
bun create vibelang@latest my-app
cd my-app
bun run devThen open the folder in VS Code or Cursor and install the editor extension that comes with the compiler: code --install-extension node_modules/vibelang/editor/vibelang.vsix. Installation covers existing projects.
Where to go next
- Coming from Effect: the cheat sheet shows every change, and VibeLang for Effect users explains the rules.
- New to Effect: start with The Effect Type, then Creating Effects and Expected Errors.
- The full language: the specification defines every rule.
The matching Effect page: effect.website/docs/v4/onboarding