Understand how repoFactory creates typed repositories, the full method surface for CRUD, soft deletes, and restore, and how TypeScript infers return shapes.
Use this file to discover all available pages before exploring further.
Once you have a CastorInstance from .build(), you call repoFactory(tableName) to obtain a repository. The repository is a collection of strongly typed async methods that covers every database operation Castor supports — from single-record inserts to paginated reads with nested relations. TypeScript binds the repository to the named table at call-site, so filter paths, projection arrays, and insert shapes are all validated against your actual Drizzle column definitions.
import { schemaMetadata } from "./castor";const userRepo = schemaMetadata.repoFactory("users");
repoFactory is generic over TName, which must be a key of the metadata map you built via .table() calls. The returned Repository<TSchema, TName> exposes two categories of members: factory helpers for constructing typed query fragments, and core methods for executing operations.
Factory helpers are no-op identity functions whose only job is to capture TypeScript generics and give you autocomplete when building reusable query fragments. They do not touch the database.
defineFilter
Returns a typed FilterQuery<TEntity> value. Use this to define a reusable filter outside the call-site.
Every core method accepts an optional profile parameter as its last argument. This value is matched against the policies registered on the builder via .policies(). Pass a single string or an array of strings; if you pass an array, the RBAC engine unions the capabilities of every matched profile.
// Single profileawait userRepo.searchMany({}, "admin");// Multiple profiles — capabilities are unionedawait userRepo.searchMany({}, ["public", "editor"]);// No profile — falls back to "default"await userRepo.searchMany({});
In strict mode, calling without a profile or with a profile that has no registered policy throws an AccessDeniedError before the query runs.
All read methods accept a query object whose shape is validated against the entity type. The projection array shapes the TypeScript return type at compile time via DeepPick.
searchPage uses a CTE split-query strategy internally to avoid Cartesian fan-out when one-to-many joins are involved, keeping pagination counts accurate.
When a table has softDelete configured, its active records exclude soft-deleted rows automatically. To query the deleted records explicitly, use the searchDeleted* variants:
const deleted = await userRepo.searchDeletedOne( { filter: { id: { $eq: 1 } } }, "admin",);// Returns the soft-deleted record or null
On PostgreSQL and SQLite, updateMany uses a RETURNING clause for atomic batch updates. On MySQL, Castor falls back to a temporary-table strategy to avoid race conditions. See Multi-dialect support for details.
Soft-delete methods write the deleteValue configured on the table (e.g., { deletedFlag: 1 }) rather than removing the row. Subsequent searchOne, searchMany, and searchPage calls automatically exclude soft-deleted rows.
Soft delete
Restore
// Delete a single record by primary keyconst success = await userRepo.softDeleteOne(1, "admin");// Returns: boolean// Delete all records matching a filterconst count = await userRepo.softDeleteMany( { "settings.theme": { $eq: "light" } }, "admin",);// Returns: number (rows affected)
// Restore a single soft-deleted record by primary keyconst restored = await userRepo.restoreOne(1, "admin");// Returns: boolean// Restore all matching soft-deleted recordsconst count = await userRepo.restoreMany( { deletedAt: { $lte: Date.now() } }, "admin",);// Returns: number (rows affected)