npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vsrepo

v1.1.0

Published

Uma biblioteca de repository pattern para Prisma

Readme

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, 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

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:

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;

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 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ó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 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, 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 (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.