vsrepo
v1.1.0
Published
Uma biblioteca de repository pattern para Prisma
Maintainers
Readme
VSRepository
Biblioteca de repository pattern para projetos que usam Prisma, com suporte completo a TypeScript e type inference automático.
O VSRepository permite criar repositories fortemente tipados com:
- Métodos base automáticos:
get,save,remove - Métodos dinâmicos inferidos pelo nome:
findByEmail,findManyPaginated,updateById,deleteManyByIdIn - Select models reutilizáveis para diferentes projeções de dados
- Type safety em 100% das operações
- Transações nativas do Prisma
- Extensibilidade com métodos personalizados
Sumário
- Instalação
- Gerando os tipos
- Uso básico
- Integração com NestJS
- Métodos base
- Select Models
- requiredWhere
- Métodos dinâmicos
- Relações no save
- Transações
- Extendendo um repository
- Tratamento de erros
- Tipos utilitários
- API Reference
- Requisitos
- Troubleshooting
Instalação
npm i vsrepo @prisma/clientGere o Prisma Client:
npx prisma generateGerando os tipos
O VSRepository precisa conhecer o caminho real do seu Prisma Client para gerar as tipagens corretamente.
npx vsrepo generateEquivale a:
npx vsrepo generate \
--output src/generated/vsrepo \
--prisma src/generated/prismaFlags disponíveis:
| Flag | Alias | Padrão |
| ---------- | ----- | ---------------------- |
| --output | -o | src/generated/vsrepo |
| --prisma | -p | src/generated/prisma |
Arquivos gerados:
src/generated/vsrepo/
├── VSRepoError.ts
├── VSRepoError.types.d.ts
├── VSRepository.ts
├── VSRepository.types.d.ts
└── index.tsApós gerar, importe sempre a partir da pasta gerada:
import { setupVSRepo } from "../generated/vsrepo";
// ou
import { setupVSRepo, type SelectModels, type WhereModel } from "../generated/vsrepo";Uso básico
Configurando o Prisma Client
// src/configs/db.ts
import { PrismaClient } from '../generated/prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import 'dotenv/config';
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
export default prisma;Criando um repository
// src/repositories/usuarioRepository.ts
import prisma from "../configs/db";
import { setupVSRepo } from "../generated/vsrepo";
import type { Usuario } from "../generated/prisma/client";
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
tableName: "usuario",
pkName: "id",
selectModels: {
public: { id: true, nome: true, email: true },
minimal: { id: true },
},
defaultSelectModel: "public",
requiredWhere: { ativo: true },
methods: {
findByEmail: { map: true, fbMode: "one" },
findManyPaginated: { map: true },
updateById: { map: true },
deleteManyByIdIn: { map: true, whereType: "overwrite" },
count: { map: true },
},
}).build(prisma);
export default usuarioRepository;
selectModelserequiredWherepodem ser declarados fora dosetupVSRepose você precisar exportá-los para uso em outros arquivos.
Usando o repository
import usuarioRepository from "./repositories/usuarioRepository";
const usuario = await usuarioRepository.save({
nome: "Joao",
email: "[email protected]",
senha: "password",
});
const encontrado = await usuarioRepository.get(usuario.id);
const porEmail = await usuarioRepository.findByEmail("[email protected]");
await usuarioRepository.updateById(usuario.id, { nome: "Joao Pedro" });
await usuarioRepository.remove(usuario.id);Integração com NestJS
O VSRepository pode ser facilmente integrado em projetos NestJS através de providers. Abaixo está um exemplo completo usando o padrão de injeção de dependência do NestJS.
Configurando o repository como provider
// src/repositories/user.repository.ts
import { Provider } from "@nestjs/common";
import { PrismaService } from "../../database/prisma.service";
import { UserGetPayload } from "../../generated/prisma/models";
import { RepositoryOf, setupVSRepo } from "../../generated/vsrepo";
const userVSRepo = setupVSRepo<
UserGetPayload<{ include: { profile: true } }>,
"User"
>()({
tableName: "user",
pkName: "id",
selectModels: {
public: {
id: true,
email: true,
createdAt: true,
updatedAt: true,
},
auth: {
id: true,
email: true,
password: true,
},
},
defaultSelectModel: "public",
requiredWhere: {
deletedAt: null,
},
relations: {
profile: {
mode: "oto",
pk: "id",
restriction: "add",
},
},
methods: {
findAuthByEmail: {
map: true,
proxyTo: "findUniqueByEmail",
selectModel: "auth",
},
},
});
export type UserRepository = RepositoryOf<typeof userVSRepo>;
export const USER_REPOSITORY = Symbol("USER_REPOSITORY");
export const UserRepositoryProvider: Provider = {
provide: USER_REPOSITORY,
inject: [PrismaService],
useFactory: (prisma: PrismaService) => {
return userVSRepo.build(prisma);
},
};Registrando o provider no módulo
// src/modules/user/user.module.ts
import { Module } from "@nestjs/common";
import { UserRepositoryProvider } from "../../repositories/user.repository";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
@Module({
imports: [DatabaseModule],
providers: [UserRepositoryProvider, UserService],
controllers: [UserController],
exports: [UserService],
})
export class UserModule {}Utilizando o repository em um serviço
// src/modules/user/user.service.ts
import { Injectable, Inject } from "@nestjs/common";
import { USER_REPOSITORY, UserRepository } from "../../repositories/user.repository";
@Injectable()
export class UserService {
constructor(
@Inject(USER_REPOSITORY)
private readonly userRepository: UserRepository,
) {}
async getUserById(id: string) {
return this.userRepository.get(id);
}
async getUserAuthByEmail(email: string) {
return this.userRepository.findAuthByEmail(email);
}
async createUser(data: { email: string; password: string; name: string }) {
return this.userRepository.save({
email: data.email,
password: data.password,
name: data.name,
});
}
}Usando em um controller
// src/modules/user/user.controller.ts
import { Controller, Get, Post, Body, Param, Patch, Delete } from "@nestjs/common";
import { UserService } from "./user.service";
@Controller("users")
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(":id")
async getUser(@Param("id") id: string) {
return this.userService.getUserById(id);
}
@Post()
async createUser(
@Body() data: { email: string; password: string; name: string }
) {
return this.userService.createUser(data);
}
}Benefícios desta abordagem:
- ✅ Type-safe repositories com injeção de dependência
- ✅ Fácil de testar (mock do
USER_REPOSITORY) - ✅ Isolamento da lógica de persistência
- ✅ Reutilização do repository em múltiplos serviços
- ✅ Suporte a transações via
PrismaService
Métodos base
Ao chamar .build(prisma), três métodos são automaticamente disponibilizados:
| Método | Descrição |
| ------------ | ------------------------------------------ |
| get(pk) | Busca um registro pela primary key |
| save(obj) | Cria ou atualiza (upsert pela primary key) |
| remove(pk) | Remove um registro pela primary key |
Todos aceitam options?: { selectModel?, db? } como último argumento.
Configurando os métodos base
usuarioVSRepo.build(prisma, {
freeze: true, // Congela o objeto (padrão: true)
showWorking: false, // Exibe logs do VSRepository no console, ótimo para debugar as queries criadas e os objetos passados para o prisma
baseMethods: {
get: {
active: true,
defaultSelect: "public",
},
remove: {
active: true,
defaultSelect: "minimal",
},
save: {
active: true,
ignoreRequiredWhere: true, // Não aplica requiredWhere no upsert
},
},
});Select Models
selectModels define projeções de dados nomeadas e reutilizáveis.
selectModels: {
public: { id: true, nome: true, email: true },
internal: { id: true, nome: true, email: true, senha: true },
minimal: { id: true },
},
defaultSelectModel: "public",defaultSelectModel define qual select é usado automaticamente quando nenhum é especificado na chamada. É recomendado sempre definí-lo junto com selectModels.
Usando um select específico na chamada:
const usuario = await usuarioRepository.get(id, { selectModel: "minimal" });Retornando o payload padrão do Prisma (sem select):
const usuarioCompleto = await usuarioRepository.get(id, { selectModel: false });requiredWhere
requiredWhere define filtros aplicados automaticamente em todas as queries do repository.
requiredWhere: { ativo: true },Agora toda query incluirá ativo: true automaticamente:
// Internamente: WHERE ativo = true
const usuarios = await usuarioRepository.findMany();
// Internamente: WHERE email = '[email protected]' AND ativo = true
const usuario = await usuarioRepository.findByEmail("[email protected]");Útil para soft-deletes, multi-tenancy e filtros globais de qualquer natureza.
Métodos dinâmicos
Métodos dinâmicos são definidos na propriedade methods e têm seus comportamentos inferidos a partir do nome.
methods: {
findByEmail: { map: true, fbMode: "one" },
findManyPaginated: { map: true },
updateById: { map: true },
deleteManyByIdIn: { map: true, whereType: "overwrite" },
}Prefixos disponíveis
O prefixo do nome do método determina qual operação Prisma será chamada e quais argumentos serão esperados.
| Prefixo | Operação Prisma | Retorno | Observações |
| ------------------------- | ------------------------ | ---------------------- | -------------------------------------------------------- |
| findBy | findMany / findFirst | T[] ou T \| null | Padrão é lista; use fbMode: "one" para retorno único |
| findUniqueBy | findUnique | T \| null | |
| findFirstBy | findFirst | T \| null | Aceita campos como filtro |
| findFirst | findFirst | T \| null | Sem filtros de campo; aplica só requiredWhere e pushWhere |
| findManyBy | findMany | T[] | Aceita campos como filtro |
| findMany | findMany | T[] | Sem filtros de campo; aplica só requiredWhere e pushWhere |
| findWhere | findFirst | T \| null | Recebe um objeto where explícito como argumento |
| findListWhere | findMany | T[] | Recebe um objeto where explícito como argumento |
| existsBy | findFirst | boolean | Retorna true se encontrar, false caso contrário |
| countBy | count | number | Aceita campos como filtro |
| count | count | number | Sem filtros de campo; aplica só requiredWhere e pushWhere |
| create | create | T | Recebe data como argumento |
| createMany | createMany | { count: number } | Recebe data como argumento; suporta SkipDuplicates |
| createManyAndReturn | createManyAndReturn | T[] | Recebe data como argumento; suporta SkipDuplicates |
| updateBy | update | T | Recebe data como argumento |
| updateManyBy | updateMany | { count: number } | Recebe data como argumento |
| updateManyAndReturnBy | updateManyAndReturn | T[] | Recebe data como argumento |
| upsertBy | upsert | T | Recebe update e create como argumentos |
| deleteBy | delete | T | |
| deleteManyBy | deleteMany | { count: number } | |
Filtros de campo
Os filtros são sufixos aplicados ao nome do campo dentro do método. O campo em si vem capitalizado logo após o prefixo (ou após By).
| Sufixo | Operador Prisma | Argumento necessário |
| ------------------ | --------------------- | -------------------- |
| (sem sufixo) | igualdade (=) | sim |
| Not | not | sim |
| In | in | sim (array) |
| NotIn | notIn | sim (array) |
| Contains | contains | sim |
| NotContains | not.contains | sim |
| StartsWith | startsWith | sim |
| NotStartsWith | not.startsWith | sim |
| EndsWith | endsWith | sim |
| NotEndsWith | not.endsWith | sim |
| GreaterThan | gt | sim |
| GreaterThanEqual | gte | sim |
| LessThan | lt | sim |
| LessThanEqual | lte | sim |
| IsNull | null | não (zero-arg) |
| IsNotNull | not: null | não (zero-arg) |
| IsTrue | true | não (zero-arg) |
| IsFalse | false | não (zero-arg) |
| Insensitive | mode: 'insensitive' | combinador |
Insensitive é um combinador e pode ser usado junto com outro filtro de texto:
findByNomeContainsInsensitive // { nome: { contains: valor, mode: 'insensitive' } }
findByEmailStartsWithInsensitive // { email: { startsWith: valor, mode: 'insensitive' } }
findByNomeInsensitive // { nome: { equals: valor, mode: 'insensitive' } }O sufixo Optional pode ser adicionado a qualquer campo para tornar o argumento opcional:
findByNomeOptionalAndEmail // nome é opcional (pode ser passado como undefined no parâmetro), email é obrigatórioOperadores lógicos
| Operador | Uso no nome | Exemplo |
| --------- | ---------------------------- | -------------------------------- |
| And | entre dois campos | findByIdAndEmail |
| Or | entre dois campos | findByNomeOrEmail |
| AND | separa bloco final em AND | findByEmailOrNameANDActiveStatus |
AND (em capslock) tem uma regra específica:
- Só pode existir um
ANDpor método. - Todos os campos (conectados por
And) depois deANDsão injetados dentro deAND: []. - Depois de um
ANDnão pode terOr.
Exemplo:
methods: {
findByIdAndEmail: { map: true, fbMode: "one" },
findByNomeOrEmail: { map: true },
findUniqueByIdOrEmailAndNome: { map: true },
findByEmailOrNameANDActiveStatusAndIdadeGreaterThan: { map: true }
}
// Uso
await usuarioRepository.findByIdAndEmail(1, "[email protected]");
await usuarioRepository.findByNomeOrEmail("Joao", "[email protected]");
await usuarioRepository.findUniqueByIdOrEmailAndNome(1, "[email protected]", "Joao");
await usuarioRepository.findByEmailOrNameANDActiveStatusAndIdadeGreaterThan("[email protected]", "Joao", true, 17)Gera (findByIdAndEmail):
{
id: 1,
email: "[email protected]"
}Gera (findByNomeOrEmail):
{
OR: [
{ nome: "Joao" },
{ email: "[email protected]" }
]
}Gera (findUniqueByIdOrEmailAndNome):
{
OR: [
{ id: 1 },
{
email: "[email protected]",
nome: "Joao"
}
]
}Gera (findByEmailOrNameANDActiveStatusAndIdadeGreaterThan):
{
OR: [
{ email: "[email protected]" },
{ name: "Joao" }
],
AND: [
{ activeStatus: true },
{ idade: { gt: 17 } }
]
}Filtros de relação
Permitem filtrar por campos de modelos relacionados.
| Sufixo de relação | Operador Prisma | Observação |
| ---------------------- | --------------- | -------------------------------------------------- |
| Some | some: {} | Relação tem algum registro |
| SomeField | some.field | Filtra dentro dos registros da relação |
| EveryField | every.field | Filtra dentro dos registros da relação |
| None | none: {} | Relação não tem nenhum registro |
| NoneField | none.field | Filtra dentro dos registros da relação |
| With | is: {} | Relação existe (não é null) |
| WithField | is.field | Filtra campo dentro da relação |
| Without | isNot: {} | Relação não existe (é null) |
| WithoutField | isNot.field | Filtra campo dentro da relação com negação |
Exemplos:
methods: {
findByPostagensSomeTituloContains: { map: true }, // postagens: { some: { titulo: { contains: valor } } }
findByPerfilWith: { map: true }, // perfil: { is: {} }
findByPerfilWithout: { map: true }, // perfil: { isNot: {} }
findByPostagensSome: { map: true }, // postagens: { some: {} }
findByPostagensEveryAtivoIsTrue: { map: true }, // postagens: { every: { ativo: true } }
}Sufixos de paginação e ordenação
Aplicados ao final do nome do método (após os filtros de campo), eles injetam automaticamente os argumentos de paginação e ordenação.
| Sufixo | Argumentos adicionais |
| --------------------- | ----------------------------- |
| Paginated | (pagination) |
| Ordered | (order) |
| OrderedAndPaginated | (order, pagination) |
| PaginatedAndOrdered | (pagination, order) |
Para createMany e createManyAndReturn, o sufixo SkipDuplicates está disponível:
| Sufixo | Efeito |
| ----------------- | ---------------------------------------- |
| SkipDuplicates | Ignora registros duplicados na inserção |
Exemplos completos:
methods: {
findManyPaginated: { map: true },
findManyByAtivoOrderedAndPaginated: { map: true },
findByEmailOrderedAndPaginated: { map: true },
createManyAndReturnSkipDuplicates: { map: true },
}
// Uso
await usuarioRepository.findManyPaginated({ skip: 0, take: 10 });
await usuarioRepository.findManyByAtivoOrderedAndPaginated(
true,
{ dataCriacao: "desc" },
{ skip: 0, take: 10 }
);PaginationOptions:
type PaginationOptions<TCursor = unknown> = {
skip?: number;
take?: number;
cursor?: TCursor;
};OrderOptions:
type OrderOptions = OrderPattern | OrderPattern[];
// Exemplo: { dataCriacao: "desc" } ou [{ dataCriacao: "desc" }, { nome: "asc" }]Configuração de métodos
Cada entrada em methods aceita as seguintes opções:
| Opção | Tipo | Padrão | Descrição |
| ------------------- | ------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
| map | boolean | — | Obrigatório. Define se o método será exposto no repository. |
| whereType | 'extending' | 'overwrite' | extending | extending combina com requiredWhere. overwrite ignora o requiredWhere. |
| selectModel | keyof SelectModels \| false | — | Sobrescreve o defaultSelectModel para este método. |
| fbMode | 'one' | 'list' | 'list' | Somente para findBy. 'one' retorna T \| null; 'list' retorna T[]. |
| proxyTo | Padrão de método válido | — | Delega a lógica para outro padrão de método válido. Útil para methods com nomes personalizados. |
| pushWhere | WhereModel<M> | — | Where extra adicionado à query além do requiredWhere. |
| injectOrdenation | OrdenationModel<M> | — | Ordenação fixa injetada automaticamente na query. |
| injectPagination | PaginationModel<M> | — | Paginação fixa injetada automaticamente na query. |
Exemplos:
methods: {
// Retorna um único resultado em vez de array
findByEmail: { map: true, fbMode: "one" },
// Ignora o requiredWhere
deleteManyByIdIn: { map: true, whereType: "overwrite" },
// Usa um select model específico neste método
findManyByAtivo: { map: true, selectModel: "minimal" },
// Adiciona um where extra além do requiredWhere
findManyByPerfil: { map: true, pushWhere: { deletedAt: null } },
// Ordenação fixa sem precisar passar como argumento
findManyPaginado: { map: true, injectOrdenation: { dataCriacao: "desc" } },
// Nome personalizado precisa de proxyTo
buscarPorEmailEPerfil: { map: true, proxyTo: "findByEmailAndPerfil" },
}Relações no save
Configure relações para que o save as gerencie automaticamente. Para que as relações apareçam no autocomplete, o tipo genérico deve incluir as relações usando GetPayload:
import type { Prisma } from "../generated/prisma/client";
type Usuario = Prisma.usuarioGetPayload<{
include: { perfil: true; postagens: true };
}>;
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
tableName: "usuario",
pkName: "id",
relations: {
perfil: {
pk: "id",
mode: "oto",
restriction: "set",
},
postagens: {
pk: "id",
mode: "otm",
restriction: "set",
},
},
}).build(prisma);Dica: Se o tipo genérico não incluir
include: { relação: true }, o VSRepository ainda funcionará, mas o autocomplete não sugerirá as relações nosave. O tipo recomendado é sempreGetPayload<{ include: { /* suas relações */ } }>para melhor experiência de desenvolvimento.
Modos de relação:
| Modo | Relação |
| ----- | ------------ |
| oto | one-to-one |
| otm | one-to-many |
| mto | many-to-one |
| mtm | many-to-many |
Restrições:
| Restrição | Comportamento no update |
| --------- | ----------------------------------------------------------- |
| set | Substitui completamente (remove os que não foram enviados) |
| add | Adiciona/atualiza sem remover os existentes |
Transações
Todos os métodos aceitam options.db para participar de uma transação:
await prisma.$transaction(async (tx) => {
const usuario = await usuarioRepository.save(
{ nome: "Maria", email: "[email protected]", senha: "password" },
{ db: tx }
);
await usuarioRepository.updateById(
usuario.id,
{ ativo: true },
{ db: tx }
);
});Extendendo um repository
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
tableName: "usuario",
pkName: "id",
methods: {
findByEmailEndsWith: { map: true, fbMode: "one" },
},
})
.build(prisma)
.extend((repo) => ({
buscarAtivosPorDominio: async (dominio: string) => {
return repo.findByEmailEndsWith(`@${dominio}`);
},
ativarMultiplos: async (ids: string[]) => {
return repo.updateManyByIdIn(ids, { ativo: true });
},
}));Tratamento de erros
O VSRepository lança VSRepoError e suas subclasses em situações específicas (OBS: Erros do Prisma não são sobrescritos como VSRepoError):
import { VSRepoError } from "../generated/vsrepo";
try {
const usuario = await usuarioRepository.get();
} catch (error) {
if (error instanceof VSRepoError) {
console.error("Erro no repository:", error.message);
}
}Subclasses disponíveis:
| Classe | Quando é lançada |
| -------------------- | ------------------------------------------------------- |
| VSRepoConfigError | Configuração inválida em setupVSRepo ou build |
| VSRepoBuildError | Nome de método inválido ou desconhecido no build |
| VSRepoExtendError | Argumento inválido em extend |
| VSRepoRuntimeError | Erro em tempo de execução durante uma operação |
Tipos utilitários
O VSRepository exporta os seguintes tipos para uso nas suas aplicações:
Tipos de cliente
import type { DbClient, DbTransaction, ClientOrTransaction } from "../generated/vsrepo";
type DbClient = PrismaClient;
type DbTransaction = Prisma.TransactionClient;
type ClientOrTransaction = DbClient | DbTransaction;Tipos derivados do modelo Prisma
import type {
SelectModel,
SelectModels,
WhereModel,
OrdenationModel,
PaginationModel,
ModelUpsertInput,
} from "../generated/vsrepo";
// Select de um campo específico
type UsuarioSelect = SelectModel<"usuario">;
// Mapa de selects nomeados
type UsuarioSelectModels = SelectModels<"usuario">;
// Where clause do modelo
type UsuarioWhere = WhereModel<"usuario">;
// OrderBy do modelo
type UsuarioOrder = OrdenationModel<"usuario">;
// Opções de paginação com cursor tipado
type UsuarioPagination = PaginationModel<"usuario">;
// Payload de criação do modelo (para upsert)
type UsuarioUpsertInput = ModelUpsertInput<"usuario">;Todos os inputs do modelo
import type { PrismaModelInputs } from "../generated/vsrepo";
type UsuarioInputs = PrismaModelInputs<"usuario">;
// Contém:
// select, createInput, createManyInput, updateInput, updateManyInput,
// whereInput, orderByInput, cursorInput, upsertCreateInput, upsertUpdateInputTipos de opções de método
import type { MethodOptions, MethodOptionsModel } from "../generated/vsrepo";
// MethodOptions<S> — opções passadas nos métodos do repository
// S = chave do select model ou false
type Opts = MethodOptions<"public" | "minimal">;
// MethodOptionsModel<T> — versão derivada do tipo do repository
type OptsModel = MethodOptionsModel<typeof usuarioSelectModels>;Tipos de configuração
import type {
MethodConfig,
RepoConfig,
BuildConfig,
RepositoryRelations,
ExtractRelationConfig,
UpsertWithRelations,
} from "../generated/vsrepo";
// Configuração de um método dinâmico
type MeuMethodConfig = MethodConfig<"usuario", typeof meuSelectModels>;
// Configuração completa do repository
type MeuRepoConfig = RepoConfig<Usuario, "usuario">;
// Configuração do build
type MeuBuildConfig = BuildConfig<"public" | "minimal">;
// Tipo de relações inferidas automaticamente
type UsuarioRelations = RepositoryRelations<Usuario>;
// Configuração de relação inferida a partir de um campo
type PerfilRelationConfig = ExtractRelationConfig<Usuario["perfil"]>;
// Payload do save com relações
type UsuarioComRelacoes = UpsertWithRelations<Usuario, "usuario", typeof relations>;Tipo do repository construído
import type { BuiltRepository, RepositoryOf } from "../generated/vsrepo";
// Tipo completo de um repository construído
type MeuRepo = BuiltRepository<Usuario, "usuario", typeof config, typeof buildConfig>;
// Inferência a partir de uma instância VSRepository (útil para injeção de dependência)
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({ ... });
type UsuarioRepository = RepositoryOf<typeof usuarioVSRepo>;RepositoryOf aceita três parâmetros:
type RepositoryOf<TRepo, C extends BuildConfig | undefined = undefined, E = unknown>
// ^ BuildConfig (opcional) ^ tipo do extend (opcional)Exemplo com extend tipado:
const extension = { buscarPorDominio: (dominio: string) => Promise<Usuario[]> };
type UsuarioRepositoryExtended = RepositoryOf<typeof usuarioVSRepo, undefined, typeof extension>;Tipo auxiliar
import type { DistributiveOmit } from "../generated/vsrepo";
// Omit distributivo — preserva unions ao omitir propriedades
type SemEmail = DistributiveOmit<Usuario | Perfil, "email">;API Reference
setupVSRepo<T, M>()(config)
setupVSRepo<TPayload, TTableName>()({
tableName: Uncapitalize<M>; // Nome da tabela no Prisma
pkName: keyof T; // Nome da primary key
selectModels?: SelectModels<M>; // Projeções de dados nomeadas
defaultSelectModel?: keyof SM; // Select aplicado por padrão
requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
relations?: RepositoryRelations<T>; // Configuração de relações
methods?: Record<string, MethodConfig<M, SM>>; // Métodos dinâmicos
});.build(prisma, config?)
vsRepo.build(prisma, {
freeze?: boolean; // Congela o objeto (padrão: true)
showWorking?: boolean; // Exibe logs internos no console
baseMethods?: {
get?: { active?: boolean; defaultSelect?: string };
remove?: { active?: boolean; defaultSelect?: string };
save?: { active?: boolean; ignoreRequiredWhere?: boolean };
};
});.extend(fn)
repo.extend((repo) => ({
meuMetodo() { ... }
}));Requisitos
- Node.js 16+ (ESM)
- Prisma
- TypeScript (opcional, mas fortemente recomendado)
"moduleResolution": "bundler"ou"nodenext"no tsconfig
tsconfig.json recomendado:
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"skipLibCheck": true,
"lib": ["ES2020"]
}
}Troubleshooting
Tipos genéricos não inferidos — Verifique se strict: true e moduleResolution: "bundler" ou "nodenext" estão no tsconfig.json.
Método dinâmico não existe em runtime — O campo referenciado no nome do método deve existir no modelo Prisma. Ex.: findByEmail exige que o modelo tenha um campo email.
proxyTo obrigatório — Nomes fora dos moldes (ex.: buscarPorEmail) não são parseados diretamente. Use proxyTo: "findByEmail" nesses casos.
Select model retorna campos inesperados — Verifique se o select model define exatamente os campos que o seu tipo TypeScript espera. Campos com false não serão retornados pelo Prisma.
