@projectplaceholders/domain
v0.1.0
Published
Three small primitives that make illegal states hard to write — and therefore hard for an agent to write.
Readme
@projectplaceholders/domain
Three small primitives that make illegal states hard to write — and therefore hard for an agent to write.
No runtime dependencies. zod is an optional peer, needed only for @projectplaceholders/domain/zod.
Result<T, E>
A thrown exception is invisible. Nothing in function charge(): Receipt warns a reader —
human or agent — that it can fail, or how. Result puts the failure in the signature where
the compiler can hold callers to it.
import { andThen, err, isOk, map, match, ok, type Result } from "@projectplaceholders/domain"
function findUser(id: string): Result<User, "not-found"> {
const user = db.get(id)
return user ? ok(user) : err("not-found")
}
const label = match(findUser("u_1"), {
ok: (user) => user.email,
err: (reason) => `lookup failed: ${reason}`,
})ok · err · isOk · isErr · map · mapErr · andThen · unwrapOr · match
There is deliberately no unwrap() that throws. It would let a caller discard the error
type in one keystroke, which is the exact habit this type exists to prevent. Use unwrapOr
or match.
Brand<T, K>
import { brand, type Brand } from "@projectplaceholders/domain"
type UserId = Brand<string, "UserId">
type OrgId = Brand<string, "OrgId">
const userId = brand<UserId>("u_1")
const orgId: OrgId = userId // ✗ compile error, even though both are stringsThe tag sits on a unique symbol: it cannot collide with a real property, and it never
appears in Object.keys or JSON.stringify.
brand() asserts, it does not validate. Call it where the value has already been checked —
right after parsing a request, or reading a verified row. Reaching for it to quiet the
compiler defeats the point.
assertNever
import { assertNever } from "@projectplaceholders/domain"
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.r ** 2
case "square": return shape.side ** 2
default: return assertNever(shape)
}
}Add a variant to Shape and this stops compiling until the new case is handled. The runtime
throw only covers values that slipped past the type system entirely.
@projectplaceholders/domain/zod
import { parse } from "@projectplaceholders/domain/zod"
const User = z.object({ id: z.string(), email: z.email() })
type User = z.infer<typeof User> // derived — never hand-written
const result = parse(User, await request.json()) // Result<User, ZodError>The schema owns the truth. Declare the schema, derive the type with z.infer. Writing
the type by hand beside the schema creates two definitions that drift, and an agent reading
the file has no way to tell which one is authoritative.
Tests
pnpm test # 22 runtime assertions
pnpm typecheck # also runs test/types.ts — the type-level assertionstest/types.ts holds claims that certain code must not compile, each marked with
@ts-expect-error. If a type ever loosens enough to accept one, TypeScript flags the
directive as unused and the typecheck fails. That file is the only place in the repo where
@ts-expect-error is allowed: there it asserts strictness rather than suppressing it.
