Skip to content

jaobrabo123/VSRepository

Repository files navigation

VSRepository

npm license

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, getOrThrow, save, remove, removeList, getAll, total, has
  • 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

npm i vsrepo @prisma/client

Gere o Prisma Client:

npx prisma generate

Gerando os tipos

O VSRepository precisa conhecer o caminho real do seu Prisma Client para gerar as tipagens corretamente.

npx vsrepo generate

Equivale a:

npx vsrepo generate \
  --output src/generated/vsrepo \
  --prisma src/generated/prisma

Flags 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.ts

Após gerar, importe sempre a partir da pasta gerada:

// CORRETO ✅
import { setupVSRepo } from "../generated/vsrepo";

// ERRADO ❌
import { setupVSRepo } from "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 },
  },
  defaultSelectModel: "public",
  requiredWhere: { ativo: true },
}).build(prisma);

export default usuarioRepository;

selectModels e requiredWhere podem ser declarados fora do setupVSRepo se 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 todos = await usuarioRepository.getAll();

usuario.nome = "Joao Pedro";

await usuarioRepository.save(usuario);
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 { 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",
        },
        findByEmailEndsWith: {
            map: true,
        }
    },
});

const setupUserRepository = (prisma: PrismaService) => {
    return userVSRepo.build(prisma).extend((repo) => ({
        buscarPorDominio: async (dominio: string) => {
            return repo.findByEmailEndsWith(`@${dominio}`);
        },
    }));
};

export type UserRepository = ReturnType<typeof setupUserRepository>;

export const USER_REPOSITORY = Symbol("USER_REPOSITORY");

export const UserRepositoryProvider: Provider = {
    provide: USER_REPOSITORY,
    inject: [PrismaService],
    useFactory: setupUserRepository,
};

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) os métodos base abaixo são automaticamente disponibilizados:

Método Descrição
get(pk) Busca um registro pela primary key
getOrThrow(pk) Busca um registro pela primary key e lança erro se não encontrar
save(obj) Cria ou atualiza (se o objeto passado tiver a pk faz upsert, se não faz create)
remove(pk) Remove um registro pela primary key
removeList(pks) Remove vários registros pela lista de primary keys — retorna { count }
getAll() Retorna todos os registros (aceita pagination e order no options)
total() Retorna o total de registros
has(pk) Verifica existência de um registro pela primary key — retorna boolean

Todos aceitam options como último argumento.

Configurando os métodos base

usuarioVSRepo.build(prisma, {
  freeze: true,        // Congela o objeto (padrão = true)
  showWorking: true,  // 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: {
      ignoreRequiredWhere: true, // Não aplica requiredWhere no save
    },
    has: {
      active: false, // Desativa o 'has' (padrão = true)
    }
  },
});

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 });

Required Where

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
findUniqueOrThrowBy findUniqueOrThrow T Lança erro se não encontrar
findFirstBy findFirst T | null Aceita campos como filtro
findFirstOrThrowBy findFirstOrThrow T Aceita campos como filtro; lança erro se não encontrar
findFirst findFirst T | null Sem filtros de campo; aplica só requiredWhere e pushWhere
findFirstOrThrow findFirstOrThrow T Sem filtros de campo; aplica só requiredWhere e pushWhere; lança erro se não encontrar
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
IsNotNull not: null não
IsTrue true não
IsFalse false não
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ório

Operadores 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 AND por método.
  • Todos os campos (conectados por And) depois de AND são injetados dentro de AND: [].
  • Depois de um AND não pode ter Or.

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 no save. O tipo recomendado é sempre GetPayload<{ 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
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, upsertUpdateInput

Tipos 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 (default = true)
  showWorking?: boolean;   // Exibe logs internos no console (default = false)

  // `active` serve para definir se o método vai existir depois do build (default = true)
  // `defaultSelect` serve para definir qual `selectModel` esse método vai usar por default (default = `defaultSelectModel` definido no `setupVSRepo`)
  // `ignoreRequiredWhere` serve para definir se o método vai ignorar o `requiredWhere` definido no `setupVSRepo` (default = false)
  baseMethods?: {
    // Métodos que podem utilizar um `defaultSelect`
    get?:       { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
    getOrThrow?:{ active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
    remove?:    { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
    save?:      { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
    getAll?:    { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };

    // Métodos que NÃO aceitam `defaultSelect`
    removeList?: { active?: boolean; ignoreRequiredWhere?: boolean };
    total?:      { active?: boolean; ignoreRequiredWhere?: boolean };
    has?:        { active?: boolean; ignoreRequiredWhere?: boolean };
  };
});

.extend(fn)

repo.extend((repo) => ({
  meuMetodo: () => { ... }
}));

Contribuindo

Contribuições são bem-vindas! Se você encontrou um bug, tem uma ideia de melhoria ou quer ajudar com a documentação, sinta-se à vontade para participar (Repositório do GitHub):

  1. Faça um Fork do projeto.
  2. Crie uma nova branch com a sua alteração: git checkout -b corrigindo-bug.
  3. Faça o push para a sua branch: git push origin corrigindo-bug.
  4. Abra um Pull Request.

Para reportar problemas ou sugerir novas funcionalidades, abra uma Issue.


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.

About

Uma biblioteca de repository pattern para Prisma

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors