Coding conventions for Effect TypeScript and Elm in this repository
Code Style
This repository uses two primary languages: Effect TypeScript for all runtime packages and Elm for both the DevTools panel UI (devtools-ui) and the documentation site. Each has its own conventions.
Effect TypeScript Conventions
Prefer Effect.fnUntraced Over Effect.gen Wrappers
Instead of writing a function that returns Effect.gen:
// Avoid
const fetchUser = (id: string) =>
Effect.gen(function* () {
const client = yield* HttpClient;
return yield* client.get(`/users/${id}`);
});
Use Effect.fnUntraced:
// Preferred
const fetchUser = Effect.fnUntraced(function* (id: string) {
const client = yield* HttpClient;
return yield* client.get(`/users/${id}`);
});
Use Context.Service Class Syntax
Define services using the class-based syntax:
import { Context } from 'effect';
class UserRepository extends Context.Service<
UserRepository,
{
readonly findById: (id: string) => Effect.Effect<User, NotFoundError>;
readonly save: (user: User) => Effect.Effect<void, DatabaseError>;
}
>()('UserRepository') {}
Never Use async/await or try/catch
All asynchronous and error-handling code must use Effect APIs:
// Bad
const data = await fetch(url);
// Good
const data = yield * Effect.tryPromise(() => fetch(url));
Never Use Date.now() or new Date()
Use the Effect Clock module for time operations. In tests, use TestClock to control time:
import { Clock } from 'effect';
const now = yield * Clock.currentTimeMillis;
Use Schema.TaggedStruct for Discriminated Unions
const AuthEvent = Schema.TaggedStruct('AuthEvent', {
type: Schema.String,
timestamp: Schema.Number,
data: Schema.Unknown,
});
Testing Conventions
Use it.effect for All Effect Tests
import { assert, describe, it } from '@effect/vitest';
describe('UserRepository', () => {
it.effect('finds a user by id', () =>
Effect.gen(function* () {
const repo = yield* UserRepository;
const user = yield* repo.findById('123');
assert.strictEqual(user.name, 'Alice');
}),
);
});
Use assert Instead of expect
The @effect/vitest package provides assert methods. Never import expect from vitest in Effect tests:
// Bad
expect(result).toBe(42);
// Good
assert.strictEqual(result, 42);
Test File Location
Test files are co-located with source files in packages/<name>/src/ (e.g. davinci-bridge.test.ts next to davinci-bridge.ts).
Elm Conventions
The docs site follows standard Elm conventions:
- elm-format — All Elm files must be formatted with
elm-format. The pre-commit hook runs this automatically. - Module naming — Modules use PascalCase and match their file path (e.g.
Searchlives atsrc/Search.elm). - Type annotations — Every top-level function must have a type annotation.
- No partial functions — Never use
List.head,Maybe.withDefaultwithout documenting why, or any function that can crash. Use pattern matching instead.
Changesets
Every pull request that changes a publishable package must include a changeset:
pnpm changeset
Follow the prompts to select the affected packages and the semver bump level. Write a concise description of what changed and why.
Look at existing code in the repository before writing new code. The patterns directory (.patterns/) contains additional examples.