typed-repository-z
v0.1.0
Published
Typed, transaction-aware repository with codegen support
Maintainers
Readme
🧱 typed-repository-z
LIVE EXAMPLE
Ultra-typed, schema-first repository layer for TypeScript.
Designed for intent-driven logic, multiple adapters, and zero runtime magic.
Write business logic once. Swap persistence later.
Why typed-repository-z
- Strongly typed
where,include,orderBy - Schema-first, codegen-friendly
- Adapter-based (memory / prisma / custom)
- Works perfectly with intent / logic runtimes
- Zero decorators, zero reflection
Mental Model
intent / service
↓
repository
↓
typed query
↓
adapter
↓
persistenceNo ORM magic. No hidden behavior.
Installation
npm install typed-repository-zDefine an Entity
export interface User {
id: number
name: string
age: number
}Define Runtime Schema
import { defineSchema } from "typed-repository-z"
export const UserSchema = defineSchema<User>({
id: { primary: true },
name: { required: true },
age: { default: 0 },
})Create Repository
import { createRepository } from "typed-repository-z"
import { createMemoryAdapter } from "typed-repository-z/adapters"
const adapter = createMemoryAdapter<User>()
export const UserRepository = createRepository(adapter, {
schema: UserSchema,
})Querying
Simple where
const users = await UserRepository.find({
where: {
age: { gt: 18 },
},
})Logical conditions
await UserRepository.find({
where: {
OR: [
{ age: { lt: 18 } },
{ name: { eq: "Admin" } },
],
},
})Ordering & pagination
await UserRepository.find({
orderBy: { age: "desc" },
limit: 10,
offset: 20,
})Insert / Update / Delete
await UserRepository.insert({
id: 1,
name: "Alice",
})
await UserRepository.update(1, { age: 30 })
await UserRepository.delete(1)Transactions
await UserRepository.transaction(async () => {
await UserRepository.insert({ id: 2, name: "Bob" })
throw new Error("rollback")
})Rollback-safe by adapter.
Relations (codegen)
// generated via typed-repo-gen
const posts = await UserRelations(user).posts()CLI Codegen (typed-repo-gen)
typed-repo-genis shipped with typed-repository-z
Folder structure
src/
├─ schema/
│ ├─ user.schema.ts
│ ├─ post.schema.ts
│
├─ adapters/
│ └─ index.ts
│
├─ generated/
│ ├─ User.repository.ts
│ └─ Post.repository.tsUser schema (codegen DSL)
// src/schema/user.schema.ts
import type { EntitySchema } from "typed-repository-z"
export const User = { name: "User" }
const schema: EntitySchema = {
name: "User",
fields: {
id: { type: "number", primary: true },
name: { type: "string", required: true },
age: { type: "number" },
},
relations: {
posts: {
target: () => ({ name: "Post" }),
foreignKey: "userId",
type: "many",
},
},
}
export default schemaPost schema
// src/schema/post.schema.ts
import type { EntitySchema } from "typed-repository-z"
export const Post = { name: "Post" }
const schema: EntitySchema = {
name: "Post",
fields: {
id: { type: "number", primary: true },
title: { type: "string" },
userId: { type: "number" },
},
relations: {
user: {
target: () => ({ name: "User" }),
foreignKey: "userId",
type: "one",
},
},
}
export default schemaAdapter
// src/adapters/index.ts
import { createMemoryAdapter } from "typed-repository-z/adapters"
export const adapter = createMemoryAdapter({
debug: process.env.NODE_ENV !== "production",
})Run codegen
npx typed-repo-gen User src/schema/user.schema.ts
npx typed-repo-gen Post src/schema/post.schema.tsUse generated repository
import { UserRepository, UserRelations } from "./generated/User.repository"
const users = await UserRepository.find({
where: { age: { gt: 18 } },
})
const user = users[0]
const posts = await UserRelations(user).posts()Philosophy
- Data access should be explicit
- Types are your first line of defense
- Repositories are infrastructure, not magic
License
MIT
