- Introduction
- Pitch
- Hogwarts
- Live Demo
- MVP
- Roadmap
- Launch Sprint
- PRD
- Get Started
- Localhost
- Architecture
- Structure
- Pattern
- Page
- Layout
- Content
- Types
- Config
- Actions
- Queries
- Authorization
- Validation
- Form
- Table
- Detail
- Card
- Util
- Hooks
- List Params
- Views
- README.md
- ISSUE.md
- Technology Stack
- Database
- File
- CDN Assets
- Entry Points
- Dashboard
- Authentication
- Credentials
- OAuth
- Flow Diagrams
- Multi-Tenancy
- Offline
- Onboarding
- Onboarding Videos
- Add Values
- Admission
- Application
- Attendance
- Compliance
- Profile
- Exams
- Exam Wizard
- Timetable
- Classrooms
- Notifications
- Conference
- LMS (Lumos)
Finance
- Finance
- Fee Management
- Invoice
- Wallet
- Salary
- Payroll
- Timesheet
- Expenses
- Budget
- Receipt
- Accounts
- Banking
- Reports
- Dashboard
- Permissions
- Messages
- Integration Flow
- Provision
- AI Document Processing
- Document Intelligence
- Internationalization
- Translation
- Translation Guide
- Icons
- Docs Factory
- Inspiration
- Listings
- Teachers
- Students
- Catalog
- Library
- Contributing
- Code of conduct
- GitHub Workflow
- Database Seeds
- Database Safety
- Test Accounts
- Playwright
- Prettier
- Block Rebound
Sales & GTM
queries.ts holds read-only database queries. Mutations live in actions.ts. Reads and writes never share a file.
Categories
| Category | Shape | Reference |
|---|---|---|
| CRUD entity | Single-model queries: getList, getDetail, getStats (200–350 lines) | listings/teachers/queries.ts |
| Multi-entity domain | Multiple related models with separate select / where / list | admission/queries.ts |
Rules
| # | Rule |
|---|---|
| 1 | schoolId as the first parameter — multi-tenant isolation enforced at the signature |
| 2 | as const on select objects — locks the shape so TypeScript narrows correctly |
| 3 | Promise.all for parallel reads (list + count) |
| 4 | Named exports only |
| 5 | No mutations — db.create/update/delete belong in actions.ts |
| 6 | No formatting helpers — move to util.ts |
| 7 | Stay under ~400 lines — split into queries/list.ts, queries/detail.ts |
| 8 | import "server-only" so misuse fails at build time |
Canonical example
import "server-only"
import type { Prisma } from "@prisma/client"
import { db } from "@/lib/db"
const teacherListSelect = {
id: true,
firstName: true,
lastName: true,
email: true,
phone: true,
active: true,
} as const
export interface TeacherQueryParams {
q?: string
active?: boolean
page?: number
pageSize?: number
}
export async function getTeacherList(
schoolId: string,
params: Partial<TeacherQueryParams> = {}
) {
const { q, active, page = 1, pageSize = 20 } = params
const where: Prisma.TeacherWhereInput = {
schoolId,
...(active !== undefined && { active }),
...(q && {
OR: [{ firstName: { contains: q } }, { lastName: { contains: q } }],
}),
}
const [data, total] = await Promise.all([
db.teacher.findMany({
where,
select: teacherListSelect,
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { lastName: "asc" },
}),
db.teacher.count({ where }),
])
return { data, total, page, pageSize }
}
export async function getTeacherDetail(schoolId: string, id: string) {
return db.teacher.findUnique({
where: { id_schoolId: { id, schoolId } },
select: { ...teacherListSelect, qualifications: true, classes: true },
})
}Anti-patterns
- Reads in
actions.ts— ~70% of features still mix them. Move toqueries.ts. - Missing
queries.ts— ~25 features have reads inactions.tsbut noqueries.ts. - Inline
db.model.findMany()withoutselect(~25 features). PaginationParams/SortParamredefined (17 files) — lift tosrc/lib/types.ts.- Formatters in
queries.ts— move toutil.ts. - Returning Prisma payloads directly — wrap in feature DTOs from
types.ts.
Naming
- File:
queries.ts. - Functions:
get<Entity><Variant>—getTeacherList,getTeacherDetail,getTeacherStats. - Select objects:
<entity><Variant>Select—teacherListSelect,teacherDetailSelect.