@rybosome/type-a
v1.0.2
Published
Declarative class models with runtime validation for TypeScript
Maintainers
Readme
type-a
Overview
A minimal, class-first schema library with lightweight reflection for TypeScript.
📚 Documentation: https://rybosome.github.io/type-a/
import { describe, it, expect } from "vitest";
import { Schema, one, constraints as c, typing as t } from "@rybosome/type-a";
class User extends Schema.from({
id: one(t.string, { is: c.aUUID }),
age: one(t.number, { is: c.atLeast(0) }),
}) {
greet() {
return `Hello! My ID is ${this.id} and I'm ${this.age} years old.`;
}
}
describe("README hero example", () => {
it("initialises and greets", () => {
const u = new User({
id: "550e8400-e29b-41d4-a716-446655440000",
age: 42,
});
expect(u.greet()).toBe(
"Hello! My ID is 550e8400-e29b-41d4-a716-446655440000 and I'm 42 years old.",
);
});
});📦 Installation
- npm:
npm install @rybosome/type-a - pnpm:
pnpm add @rybosome/type-a
✨ Highlights
- Class-based API with native
this.propertyaccess - Schema and validation co-located with the class definition
- Type-safe constructor inference
- No decorators or
reflect-metadata - Zero duplication — the schema drives both runtime behaviour and static types
- Lightweight and dependency-free
🔍 Comparison
type-a lives in a rich ecosystem of declarative model libraries, each with its
own trade-offs:
| Feature | type-a | Zod | class-validator + transformer | ArkType | Typia | | ---------------------- | :----: | :-: | :---------------------------: | :-----: | :---: | | Class syntax | ✅ | ❌ | ✅ | ❌ | ✅ | | Avoids decorators | ✅ | ✅ | ❌ | ✅ | ✅ | | Avoids code generation | ✅ | ✅ | ✅ | ✅ | ❌ | | Mature | ❌ | ✅ | ✅ | ✅ | ✅ |
type-a is interesting if you want a lean, class-centric model with some
reflection but without decorator overhead.
Features
Standard class usage
import { describe, it, expect } from "vitest";
import { Schema, one, constraints as c, typing as t } from "@rybosome/type-a";
class User extends Schema.from({
id: one(t.string, { is: c.aUUID }),
age: one(t.number, { is: c.atLeast(0) }),
}) {
greet() {
return `Hello! My ID is ${this.id} and I'm ${this.age} years old.`;
}
}
describe("Standard class usage", () => {
it("constructs and accesses properties", () => {
const u1 = new User({
id: "550e8400-e29b-41d4-a716-446655440000",
age: 30,
});
expect(typeof u1.id).toBe("string");
expect(typeof u1.age).toBe("number");
expect(u1.greet()).toBe(
"Hello! My ID is 550e8400-e29b-41d4-a716-446655440000 and I'm 30 years old.",
);
});
});Validation
new construction is statically type-checked by TypeScript. For runtime
validation there is a fromJSON entry-point which returns a Maybe — either
the fully-typed value at .val or a structured error log at .errs.
import { describe, it, expect } from "vitest";
import { Schema, one, constraints as c, typing as t } from "@rybosome/type-a";
class User extends Schema.from({
id: one(t.string, { is: c.aUUID }),
age: one(t.number, { is: c.atLeast(0) }),
}) {
greet() {
return `Hello! My ID is ${this.id} and I'm ${this.age} years old.`;
}
}
describe("Runtime validation", () => {
it("returns structured errors", () => {
const goodResult = User.fromJSON({
id: "550e8400-e29b-41d4-a716-446655440000",
age: 30,
});
expect(goodResult.val).toBeDefined();
const badResult = User.fromJSON({ id: "not a UUID", age: 25 });
const errs = badResult.errs!;
expect(errs).toBeDefined();
});
});🔄 Custom serialization / deserialization
import { describe, it, expect } from "vitest";
import { Schema, one, typing as t } from "@rybosome/type-a";
const serializeDate = (d: Date) => d.toISOString();
const deserializeDate = (s: string) => new Date(s);
class Event extends Schema.from({
title: one(t.string),
when: one(t.serdes(Date, t.string), {
serdes: [serializeDate, deserializeDate],
}),
}) {}
describe("Custom serdes", () => {
it("round-trips Dates transparently", () => {
const e = new Event({
title: "Launch",
when: "2025-12-31T23:59:59.000Z",
});
expect(e.when).toBeInstanceOf(Date);
expect(JSON.parse(JSON.stringify(e)).when).toBe("2025-12-31T23:59:59.000Z");
});
});JSON Schema generation
import { describe, it, expect } from "vitest";
import { Schema, one, typing as t } from "@rybosome/type-a";
class Product extends Schema.from({
name: one(t.string),
price: one(t.number),
}) {}
describe("JSON Schema generation", () => {
it("emits draft-07 schema", () => {
const schema = Product.jsonSchema();
expect(schema).toHaveProperty("properties.price.type", "number");
});
});Documentation
Below are direct links to the rendered guides hosted at https://rybosome.github.io/type-a/. Browse the handbook, API reference and examples without leaving GitHub:
