vsrepo 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Biblioteca de repository pattern para projetos que usam **Prisma**, com suporte
7
7
 
8
8
  O VSRepository permite criar repositories fortemente tipados com:
9
9
 
10
- - **Métodos base** automáticos: `get`, `getOrThrow`, `save`, `remove`
10
+ - **Métodos base** automáticos: `get`, `getOrThrow`, `save`, `remove`, `removeList`, `getAll`, `total`, `has`
11
11
  - **Métodos dinâmicos** inferidos pelo nome: `findByEmail`, `findManyPaginated`, `updateById`, `deleteManyByIdIn`
12
12
  - **Select models** reutilizáveis para diferentes projeções de dados
13
13
  - **Type safety** em 100% das operações
@@ -24,7 +24,7 @@ O VSRepository permite criar repositories fortemente tipados com:
24
24
  - [Integração com NestJS](#integração-com-nestjs)
25
25
  - [Métodos base](#métodos-base)
26
26
  - [Select Models](#select-models)
27
- - [requiredWhere](#requiredwhere)
27
+ - [Required Where](#requiredwhere)
28
28
  - [Métodos dinâmicos](#métodos-dinâmicos)
29
29
  - [Prefixos disponíveis](#prefixos-disponíveis)
30
30
  - [Filtros de campo](#filtros-de-campo)
@@ -38,6 +38,7 @@ O VSRepository permite criar repositories fortemente tipados com:
38
38
  - [Tratamento de erros](#tratamento-de-erros)
39
39
  - [Tipos utilitários](#tipos-utilitários)
40
40
  - [API Reference](#api-reference)
41
+ - [Contribuindo](#contribuindo)
41
42
  - [Requisitos](#requisitos)
42
43
  - [Troubleshooting](#troubleshooting)
43
44
 
@@ -94,9 +95,11 @@ src/generated/vsrepo/
94
95
  Após gerar, importe sempre a partir da pasta gerada:
95
96
 
96
97
  ```ts
98
+ // CORRETO ✅
97
99
  import { setupVSRepo } from "../generated/vsrepo";
98
- // ou
99
- import { setupVSRepo, type SelectModels, type WhereModel } from "../generated/vsrepo";
100
+
101
+ // ERRADO ❌
102
+ import { setupVSRepo } from "vsrepo";
100
103
  ```
101
104
 
102
105
  ---
@@ -128,22 +131,11 @@ import type { Usuario } from "../generated/prisma/client";
128
131
  const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
129
132
  tableName: "usuario",
130
133
  pkName: "id",
131
-
132
134
  selectModels: {
133
135
  public: { id: true, nome: true, email: true },
134
- minimal: { id: true },
135
136
  },
136
137
  defaultSelectModel: "public",
137
-
138
138
  requiredWhere: { ativo: true },
139
-
140
- methods: {
141
- findByEmail: { map: true, fbMode: "one" },
142
- findManyPaginated: { map: true },
143
- updateById: { map: true },
144
- deleteManyByIdIn: { map: true, whereType: "overwrite" },
145
- count: { map: true },
146
- },
147
139
  }).build(prisma);
148
140
 
149
141
  export default usuarioRepository;
@@ -163,9 +155,11 @@ const usuario = await usuarioRepository.save({
163
155
  });
164
156
 
165
157
  const encontrado = await usuarioRepository.get(usuario.id);
166
- const porEmail = await usuarioRepository.findByEmail("joao@email.com");
158
+ const todos = await usuarioRepository.getAll();
167
159
 
168
- await usuarioRepository.updateById(usuario.id, { nome: "Joao Pedro" });
160
+ usuario.nome = "Joao Pedro";
161
+
162
+ await usuarioRepository.save(usuario);
169
163
  await usuarioRepository.remove(usuario.id);
170
164
  ```
171
165
 
@@ -332,23 +326,27 @@ export class UserController {
332
326
 
333
327
  ## Métodos base
334
328
 
335
- Ao chamar `.build(prisma)`, quatro métodos são automaticamente disponibilizados:
329
+ Ao chamar `.build(prisma)` os métodos base abaixo são automaticamente disponibilizados:
336
330
 
337
331
  | Método | Descrição |
338
332
  | ------------ | ------------------------------------------ |
339
333
  | `get(pk)` | Busca um registro pela primary key |
340
334
  | `getOrThrow(pk)` | Busca um registro pela primary key e lança erro se não encontrar |
341
- | `save(obj)` | Cria ou atualiza (upsert pela primary key) |
335
+ | `save(obj)` | Cria ou atualiza (se o objeto passado tiver a `pk` faz `upsert`, se não faz `create`) |
342
336
  | `remove(pk)` | Remove um registro pela primary key |
337
+ | `removeList(pks)` | Remove vários registros pela lista de primary keys — retorna `{ count }` |
338
+ | `getAll()` | Retorna todos os registros (aceita `pagination` e `order` no `options`) |
339
+ | `total()` | Retorna o total de registros |
340
+ | `has(pk)` | Verifica existência de um registro pela primary key — retorna `boolean` |
343
341
 
344
- Todos aceitam `options?: { selectModel?, db? }` como último argumento.
342
+ Todos aceitam `options` como último argumento.
345
343
 
346
344
  ### Configurando os métodos base
347
345
 
348
346
  ```ts
349
347
  usuarioVSRepo.build(prisma, {
350
- freeze: true, // Congela o objeto (padrão: true)
351
- showWorking: false, // Exibe logs do VSRepository no console, ótimo para debugar as queries criadas e os objetos passados para o prisma
348
+ freeze: true, // Congela o objeto (padrão = true)
349
+ showWorking: true, // Exibe logs do VSRepository no console, ótimo para debugar as queries criadas e os objetos passados para o prisma
352
350
 
353
351
  baseMethods: {
354
352
  get: {
@@ -360,9 +358,11 @@ usuarioVSRepo.build(prisma, {
360
358
  defaultSelect: "minimal",
361
359
  },
362
360
  save: {
363
- active: true,
364
- ignoreRequiredWhere: true, // Não aplica requiredWhere no upsert
361
+ ignoreRequiredWhere: true, // Não aplica requiredWhere no save
365
362
  },
363
+ has: {
364
+ active: false, // Desativa o 'has' (padrão = true)
365
+ }
366
366
  },
367
367
  });
368
368
  ```
@@ -398,7 +398,7 @@ const usuarioCompleto = await usuarioRepository.get(id, { selectModel: false });
398
398
 
399
399
  ---
400
400
 
401
- ## requiredWhere
401
+ ## Required Where
402
402
 
403
403
  `requiredWhere` define filtros aplicados automaticamente em todas as queries do repository.
404
404
 
@@ -487,10 +487,10 @@ Os filtros são sufixos aplicados ao nome do campo dentro do método. O campo em
487
487
  | `GreaterThanEqual` | `gte` | sim |
488
488
  | `LessThan` | `lt` | sim |
489
489
  | `LessThanEqual` | `lte` | sim |
490
- | `IsNull` | `null` | não (zero-arg) |
491
- | `IsNotNull` | `not: null` | não (zero-arg) |
492
- | `IsTrue` | `true` | não (zero-arg) |
493
- | `IsFalse` | `false` | não (zero-arg) |
490
+ | `IsNull` | `null` | não |
491
+ | `IsNotNull` | `not: null` | não |
492
+ | `IsTrue` | `true` | não |
493
+ | `IsFalse` | `false` | não |
494
494
  | `Insensitive` | `mode: 'insensitive'` | combinador |
495
495
 
496
496
  `Insensitive` é um combinador e pode ser usado junto com outro filtro de texto:
@@ -833,7 +833,7 @@ try {
833
833
 
834
834
  | Classe | Quando é lançada |
835
835
  | -------------------- | ------------------------------------------------------- |
836
- | `VSRepoConfigError` | Configuração inválida em `setupVSRepo` ou `build` |
836
+ | `VSRepoConfigError` | Configuração inválida em `setupVSRepo` |
837
837
  | `VSRepoBuildError` | Nome de método inválido ou desconhecido no `build` |
838
838
  | `VSRepoExtendError` | Argumento inválido em `extend` |
839
839
  | `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
@@ -998,13 +998,24 @@ setupVSRepo<TPayload, TTableName>()({
998
998
 
999
999
  ```ts
1000
1000
  vsRepo.build(prisma, {
1001
- freeze?: boolean; // Congela o objeto (padrão: true)
1002
- showWorking?: boolean; // Exibe logs internos no console
1001
+ freeze?: boolean; // Congela o objeto (default = true)
1002
+ showWorking?: boolean; // Exibe logs internos no console (default = false)
1003
1003
 
1004
+ // `active` serve para definir se o método vai existir depois do build (default = true)
1005
+ // `defaultSelect` serve para definir qual `selectModel` esse método vai usar por default (default = `defaultSelectModel` definido no `setupVSRepo`)
1006
+ // `ignoreRequiredWhere` serve para definir se o método vai ignorar o `requiredWhere` definido no `setupVSRepo` (default = false)
1004
1007
  baseMethods?: {
1005
- get?: { active?: boolean; defaultSelect?: string };
1006
- remove?: { active?: boolean; defaultSelect?: string };
1007
- save?: { active?: boolean; ignoreRequiredWhere?: boolean };
1008
+ // Métodos que podem utilizar um `defaultSelect`
1009
+ get?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1010
+ getOrThrow?:{ active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1011
+ remove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1012
+ save?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1013
+ getAll?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1014
+
1015
+ // Métodos que NÃO aceitam `defaultSelect`
1016
+ removeList?: { active?: boolean; ignoreRequiredWhere?: boolean };
1017
+ total?: { active?: boolean; ignoreRequiredWhere?: boolean };
1018
+ has?: { active?: boolean; ignoreRequiredWhere?: boolean };
1008
1019
  };
1009
1020
  });
1010
1021
  ```
@@ -1013,12 +1024,25 @@ vsRepo.build(prisma, {
1013
1024
 
1014
1025
  ```ts
1015
1026
  repo.extend((repo) => ({
1016
- meuMetodo() { ... }
1027
+ meuMetodo: () => { ... }
1017
1028
  }));
1018
1029
  ```
1019
1030
 
1020
1031
  ---
1021
1032
 
1033
+ ## Contribuindo
1034
+
1035
+ 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](https://github.com/jaobrabo123/VSRepository)**):
1036
+
1037
+ 1. Faça um **Fork** do projeto.
1038
+ 2. Crie uma nova branch com a sua alteração: `git checkout -b corrigindo-bug`.
1039
+ 3. Faça o push para a sua branch: `git push origin corrigindo-bug`.
1040
+ 4. Abra um **Pull Request**.
1041
+
1042
+ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
1043
+
1044
+ ---
1045
+
1022
1046
  ## Requisitos
1023
1047
 
1024
1048
  - Node.js 16+ (ESM)
@@ -1051,4 +1075,4 @@ repo.extend((repo) => ({
1051
1075
 
1052
1076
  **`proxyTo` obrigatório** — Nomes fora dos moldes (ex.: `buscarPorEmail`) não são parseados diretamente. Use `proxyTo: "findByEmail"` nesses casos.
1053
1077
 
1054
- **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.
1078
+ **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.
@@ -1,84 +1,127 @@
1
- import { VSRepoConfigError } from "./VSRepoError.js";
1
+ import z from "zod";
2
+ import { VSRepoConfigError, VSRepoBuildError } from "./VSRepoError.js";
2
3
 
3
- export class FieldValidator {
4
- constructor(throws) {
5
- this.throws = throws;
6
- }
4
+ const stringSchema = z.string().trim().nonempty();
5
+ const objectSchema = z.looseObject({});
6
+ const booleanSchema = z.boolean();
7
7
 
8
- validate(
9
- field,
10
- fieldName,
11
- mustBe,
12
- undefinedAble,
13
- nullAble,
14
- customReason
15
- ) {
16
- if (field === undefined) {
17
- if (undefinedAble) return;
18
- throw this.instanceError(fieldName, customReason ?? "cannot be undefined");
8
+ export function validateConfig(config) {
9
+ const configSchema = z.strictObject({
10
+ tableName: stringSchema,
11
+ pkName: stringSchema,
12
+ selectModels: z.record(stringSchema, objectSchema).optional(),
13
+ defaultSelectModel: stringSchema.optional(),
14
+ requiredWhere: objectSchema.optional(),
15
+ relations: z.record(
16
+ stringSchema,
17
+ z.strictObject({
18
+ mode: z.enum(['otm', 'mtm', 'mto', 'oto']),
19
+ restriction: z.enum(['set', 'add']),
20
+ pk: stringSchema,
21
+ nullAble: booleanSchema.optional(),
22
+ })
23
+ ).optional(),
24
+ methods: z.record(stringSchema, z.strictObject({
25
+ map: booleanSchema,
26
+ selectModel: stringSchema.or(z.literal(false)).optional(),
27
+ whereType: z.enum(["overwrite", "extending"]).optional(),
28
+ proxyTo: stringSchema.optional(),
29
+ pushWhere: objectSchema.optional(),
30
+ fbMode: z.enum(["one", "list"]).optional(),
31
+ injectOrdenation: objectSchema.or(z.array(objectSchema)).optional(),
32
+ injectPagination: objectSchema.optional()
33
+ })).optional()
34
+ }).superRefine((config, ctx)=>{
35
+ if (config.defaultSelectModel && !config.selectModels?.[config.defaultSelectModel]) {
36
+ ctx.addIssue({
37
+ code: "vs_custom",
38
+ path: ["defaultSelectModel"],
39
+ message: "must be a valid key of 'selectModels'",
40
+ });
19
41
  }
20
42
 
21
- if (field === null) {
22
- if (nullAble) return;
23
- throw this.instanceError(fieldName, customReason ?? "cannot be null");
43
+ if (config.methods) {
44
+ for (const [key, method] of Object.entries(config.methods)) {
45
+ if (method.selectModel && !config.selectModels?.[method.selectModel]) {
46
+ ctx.addIssue({
47
+ code: "vs_custom",
48
+ path: ["methods", key, "selectModel"],
49
+ message: "must be a valid key of 'selectModels'",
50
+ });
51
+ }
52
+ }
24
53
  }
54
+ });
25
55
 
26
- const condition = typeof mustBe === "function" ? !mustBe(field) : typeof field !== mustBe;
56
+ const configParsed = configSchema.safeParse(config);
27
57
 
28
- if(condition) {
29
- throw this.instanceError(fieldName, customReason ?? `must be a valid ${mustBe}`);
30
- };
58
+ if(!configParsed.success) {
59
+ const firstIssue = configParsed.error.issues[0];
60
+ const path = firstIssue.path.length ? firstIssue.path.join(".") : "config";
61
+ throw new VSRepoConfigError(`[VSRepository] (config) ${path}: ${firstIssue.message}`)
31
62
  }
32
63
 
33
- instanceError(fieldName, reason) {
34
- const error = new this.throws("");
35
- error.message = `[VSRepository] (${error.type}) '${fieldName}' ${reason}.`;
36
- return error;
37
- }
64
+ return configParsed.data;
38
65
  }
39
66
 
40
- export function validateConfig(config) {
41
- const validator = new FieldValidator(VSRepoConfigError);
42
-
43
- validator.validate(config, "config", "object");
44
- validator.validate(config.tableName, "tableName", "string");
45
- validator.validate(config.pkName, "pkName", "string");
46
- validator.validate(config.selectModels, "selectModels", "object", true);
47
-
48
- if(config.defaultSelectModel !== undefined) {
49
- validator.validate(config.defaultSelectModel, "defaultSelectModel", "string");
50
-
51
- validator.validate(config.defaultSelectModel, "defaultSelectModel", (field) => {
52
- return !!(config.selectModels && Object.keys(config.selectModels).includes(config.defaultSelectModel))
53
- }, false, false, "must be a valid key of 'selectModels'");
67
+ export function validateBuildConfig(buildConfig, buildInstance) {
68
+ const activeSchema = booleanSchema.default(true);
69
+ const defaultSelectSchema = buildInstance.selectModels ?
70
+ z.enum(Object.keys(buildInstance.selectModels)).optional()
71
+ : z.never("can only be passed if you have configured 'selectModels'.");
72
+ const ignoreRequiredWhereSchema = booleanSchema.default(false);
73
+
74
+ const buildConfigSchema = z.strictObject({
75
+ freeze: booleanSchema.default(true),
76
+ showWorking: booleanSchema.default(false),
77
+ baseMethods: z.strictObject({
78
+ get: z.strictObject({
79
+ active: activeSchema,
80
+ defaultSelect: defaultSelectSchema,
81
+ ignoreRequiredWhere: ignoreRequiredWhereSchema
82
+ }).optional(),
83
+ getOrThrow: z.strictObject({
84
+ active: activeSchema,
85
+ defaultSelect: defaultSelectSchema,
86
+ ignoreRequiredWhere: ignoreRequiredWhereSchema
87
+ }).optional(),
88
+ getAll: z.strictObject({
89
+ active: activeSchema,
90
+ defaultSelect: defaultSelectSchema,
91
+ ignoreRequiredWhere: ignoreRequiredWhereSchema
92
+ }).optional(),
93
+ remove: z.strictObject({
94
+ active: activeSchema,
95
+ defaultSelect: defaultSelectSchema,
96
+ ignoreRequiredWhere: ignoreRequiredWhereSchema
97
+ }).optional(),
98
+ save: z.strictObject({
99
+ active: activeSchema,
100
+ defaultSelect: defaultSelectSchema,
101
+ ignoreRequiredWhere: ignoreRequiredWhereSchema
102
+ }).optional(),
103
+ removeList: z.strictObject({
104
+ active: activeSchema,
105
+ ignoreRequiredWhere: ignoreRequiredWhereSchema
106
+ }).optional(),
107
+ total: z.strictObject({
108
+ active: activeSchema,
109
+ ignoreRequiredWhere: ignoreRequiredWhereSchema
110
+ }).optional(),
111
+ has: z.strictObject({
112
+ active: activeSchema,
113
+ ignoreRequiredWhere: ignoreRequiredWhereSchema
114
+ }).optional(),
115
+ }).optional()
116
+ });
117
+
118
+ const buildConfigParsed = buildConfigSchema.safeParse(buildConfig);
119
+
120
+ if(!buildConfigParsed.success) {
121
+ const firstIssue = buildConfigParsed.error.issues[0];
122
+ const path = firstIssue.path.length ? firstIssue.path.join(".") : "config";
123
+ throw new VSRepoBuildError(`[VSRepository] (build) ${path}: ${firstIssue.message}`)
54
124
  }
55
125
 
56
- if(config.relations !== undefined) {
57
- validator.validate(config.relations, "relations", "object");
58
-
59
- for (const key of Object.keys(config.relations)) {
60
- const relation = config.relations[key];
61
-
62
- validator.validate(relation.pk, "pk", "string", false, false, `of relation '${key}' must be a string`);
63
- validator.validate(relation.mode, "mode", (field) => ['otm', 'mtm', 'mto', 'oto'].includes(field), false, false, `of relation '${key}' must be 'otm', 'mtm', 'mto' or 'oto'`);
64
- validator.validate(relation.restriction, "restriction", (field) => ['set', 'add'].includes(field), false, false, `of relation '${key}' must be 'set' or 'add'`);
65
- }
66
- }
67
-
68
- validator.validate(config.requiredWhere, "requiredWhere", "object", true);
69
-
70
- if(config.methods !== undefined) {
71
- validator.validate(config.methods, "methods", "object");
72
-
73
- for (const [key, value] of Object.entries(config.methods)) {
74
- validator.validate(value.map, "map", "boolean", false, false, `on ${key} must be a valid boolean`);
75
- validator.validate(value.whereType, "whereType", (field) => ['extending', 'overwrite'].includes(field), true, false, `on ${key} must be 'extending' or 'overwrite'`);
76
- validator.validate(value.selectModel, "selectModel", (field) => !!(field === false || (config.selectModels && Object.keys(config.selectModels).includes(field))), true, false, `on ${key} must be a valid key of 'selectModels'`);
77
- validator.validate(value.proxyTo, "proxyTo", "string", true, false, `on ${key} must be a valid string`);
78
- validator.validate(value.fbMode, "fbMode", (field) => ['one', 'list'].includes(field), true, false, `on ${key} must be 'one' or 'list'`);
79
- validator.validate(value.pushWhere, "pushWhere", "object", true, false, `on ${key} must be a valid object`);
80
- validator.validate(value.injectOrdenation, "injectOrdenation", "object", true, false, `on ${key} must be a valid object or array`);
81
- validator.validate(value.injectPagination, "injectPagination", "object", true, false, `on ${key} must be a valid object`);
82
- }
83
- }
84
- }
126
+ return buildConfigParsed.data;
127
+ }
@@ -348,7 +348,7 @@ type BaseMethodConfig<TSelectKeys extends PropertyKey = string> = {
348
348
  active?: boolean;
349
349
  /** Select model padrão usado pelo método base. */
350
350
  defaultSelect?: TSelectKeys;
351
- /** Ignora o `requiredWhere` no `save`. */
351
+ /** Ignora o `requiredWhere`. */
352
352
  ignoreRequiredWhere?: boolean;
353
353
  };
354
354
 
@@ -372,10 +372,18 @@ export type BuildConfig<TSelectKeys extends PropertyKey = string> = {
372
372
  remove?: BaseMethodConfig<TSelectKeys>;
373
373
  /** Configuração do método `save`. */
374
374
  save?: BaseMethodConfig<TSelectKeys>;
375
+ /** Configuração do método `removeList` (exclusão em lote). Não aceita select. */
376
+ removeList?: Omit<BaseMethodConfig<TSelectKeys>, 'defaultSelect'>;
377
+ /** Configuração do método `getAll` (listagem total). */
378
+ getAll?: BaseMethodConfig<TSelectKeys>;
379
+ /** Configuração do método `total` (contagem). Não aceita select. */
380
+ total?: Omit<BaseMethodConfig<TSelectKeys>, 'defaultSelect'>;
381
+ /** Configuração do método `has` (verificação de existência). Não aceita select. */
382
+ has?: Omit<BaseMethodConfig<TSelectKeys>, 'defaultSelect'>;
375
383
  };
376
384
  };
377
385
 
378
- type ResolveMethodDefaultSelect<Config, C, Method extends 'get' | 'getOrThrow' | 'remove' | 'save', TSelects = ExtractSelectModels<Config>> =
386
+ type ResolveMethodDefaultSelect<Config, C, Method extends 'get' | 'getOrThrow' | 'remove' | 'save' | 'getAll', TSelects = ExtractSelectModels<Config>> =
379
387
  C extends { baseMethods?: { [_ in Method]?: { defaultSelect?: infer D } } }
380
388
  ? D extends keyof TSelects
381
389
  ? D
@@ -475,11 +483,59 @@ type InjectedSave<
475
483
  ): Promise<RefineSaveResult<ResolveCurrentReturn<M, TSelects, S, TDefault>, O>>;
476
484
  };
477
485
 
486
+ type InjectedRemoveList<
487
+ T, M extends Prisma.ModelName, Config, C extends BuildConfig<any> | undefined,
488
+ TPk = ExtractPkName<T, Config>
489
+ > = C extends { baseMethods: { removeList: { active: false } } } ? {} : {
490
+ /** Remove múltiplos registros de uma vez pelas suas chaves primárias. */
491
+ removeList(
492
+ pks: T[TPk extends keyof T ? TPk : never][],
493
+ options?: { db?: ClientOrTransaction }
494
+ ): Promise<{ count: number }>;
495
+ };
496
+
497
+ type InjectedGetAll<
498
+ T, M extends Prisma.ModelName, Config, C extends BuildConfig<any> | undefined,
499
+ TSelects = ExtractSelectModels<Config>,
500
+ TDefault = ExtractDefaultSelect<Config>,
501
+ I = PrismaModelInputs<M>
502
+ > = C extends { baseMethods: { getAll: { active: false } } } ? {} : {
503
+ /** Busca todos os registros, respeitando o `requiredWhere` se houver. */
504
+ getAll<S extends keyof TSelects | false = ResolveMethodDefaultSelect<Config, C, 'getAll', TSelects>>(
505
+ options?: MethodOptions<S> & {
506
+ pagination?: PaginationOptions<I extends { cursorInput: infer Curs } ? Curs : unknown>;
507
+ order?: I extends { orderByInput: infer OB } ? OB : OrderOptions;
508
+ }
509
+ ): Promise<ResolveCurrentReturn<M, TSelects, S, TDefault>[]>;
510
+ };
511
+
512
+ type InjectedTotal<
513
+ T, M extends Prisma.ModelName, Config, C extends BuildConfig<any> | undefined
514
+ > = C extends { baseMethods: { total: { active: false } } } ? {} : {
515
+ /** Retorna a quantidade total de registros, respeitando o `requiredWhere` se houver. */
516
+ total(options?: { db?: ClientOrTransaction }): Promise<number>;
517
+ };
518
+
519
+ type InjectedHas<
520
+ T, M extends Prisma.ModelName, Config, C extends BuildConfig<any> | undefined,
521
+ TPk = ExtractPkName<T, Config>
522
+ > = C extends { baseMethods: { has: { active: false } } } ? {} : {
523
+ /** Verifica de forma otimizada se um registro existe pela chave primária. */
524
+ has(
525
+ pk: T[TPk extends keyof T ? TPk : never],
526
+ options?: { db?: ClientOrTransaction }
527
+ ): Promise<boolean>;
528
+ };
529
+
478
530
  type InjectedBaseMethods<T, M extends Prisma.ModelName, Config, C extends BuildConfig<any> | undefined> =
479
531
  InjectedGet<T, M, Config, C> &
480
532
  InjectedGetOrThrow<T, M, Config, C> &
481
533
  InjectedRemove<T, M, Config, C> &
482
- InjectedSave<T, M, Config, C>;
534
+ InjectedSave<T, M, Config, C> &
535
+ InjectedRemoveList<T, M, Config, C> &
536
+ InjectedGetAll<T, M, Config, C> &
537
+ InjectedTotal<T, M, Config, C> &
538
+ InjectedHas<T, M, Config, C>;
483
539
 
484
540
  /**
485
541
  * Infere automaticamente a configuração de relação possível a partir de um campo.
@@ -534,6 +590,11 @@ export type BuiltRepository<T extends object, M extends Prisma.ModelName, Config
534
590
  * Estende o repository com métodos personalizados sem perder a tipagem.
535
591
  */
536
592
  extend<E>(extensionFunc: (repo: BuiltRepository<T, M, Config, C>) => E): BuiltRepository<T, M, Config, C> & E;
593
+
594
+ /**
595
+ * Instância do Prisma Client passada no `build`.
596
+ */
597
+ readonly prisma: DbClient;
537
598
  } & DynamicMethods<T, M, Config, PrismaModelInputs<M>> & InjectedBaseMethods<T, M, Config, C>;
538
599
 
539
600
  /**
@@ -549,7 +610,7 @@ export declare class VSRepository<T extends object, M extends Prisma.ModelName,
549
610
  /**
550
611
  * Constrói o repository final com os métodos base e dinâmicos.
551
612
  */
552
- build<C extends BuildConfig<any>>(prisma: DbClient, config?: C): BuiltRepository<T, M, Config, C>;
613
+ build<C extends BuildConfig<keyof ExtractSelectModels<Config>>>(prisma: DbClient, config?: C): BuiltRepository<T, M, Config, C>;
553
614
  vsrepocache: never;
554
615
  }
555
616
 
@@ -8,8 +8,8 @@
8
8
  export * from "./VSRepoError.js";
9
9
 
10
10
  // * Importando classes de erro
11
- import { VSRepoConfigError, VSRepoBuildError, VSRepoExtendError, VSRepoRuntimeError } from "./VSRepoError.js";
12
- import { validateConfig } from "./VSRepoUtils.js";
11
+ import { VSRepoBuildError, VSRepoExtendError, VSRepoRuntimeError } from "./VSRepoError.js";
12
+ import { validateBuildConfig, validateConfig } from "./VSRepoUtils.js";
13
13
 
14
14
  // * Essa é a classe pincipal, é a partir dela que serão criados os objetos dos repositories
15
15
  export class VSRepository {
@@ -29,7 +29,7 @@ export class VSRepository {
29
29
  methods;
30
30
 
31
31
  constructor(config) {
32
- validateConfig(config);
32
+ config = validateConfig(config);
33
33
 
34
34
  this.vsrepocache = new Map();
35
35
  this.tableName = config.tableName;
@@ -59,18 +59,17 @@ export class VSRepository {
59
59
  return extended;
60
60
  }
61
61
 
62
- build(prisma, config = {}) {
63
- const showWorking = config.showWorking;
64
-
65
- if(typeof config !== 'object' || config === null) {
66
- throw new VSRepoBuildError(`[VSRepository] (build) 'config' must be a valid object.`);
67
- }
68
- if(config.freeze === undefined) {
69
- config.freeze = true;
62
+ build(prisma, config = { freeze: true, showWorking: false }) {
63
+ if(typeof prisma !== 'object' || prisma === null) {
64
+ throw new VSRepoBuildError(`[VSRepository] (build) 'prisma' must be a valid Prisma Client.`);
70
65
  }
71
66
 
72
67
  const buildInstance = Object.create(this);
73
68
 
69
+ config = validateBuildConfig(config, buildInstance);
70
+
71
+ const showWorking = config.showWorking;
72
+
74
73
  const relationsKeys = buildInstance.relations ? Object.keys(buildInstance.relations) : [];
75
74
 
76
75
  const methods = buildInstance.methods;
@@ -487,10 +486,6 @@ export class VSRepository {
487
486
  whereArgs.push(buildedWhere);
488
487
  }
489
488
 
490
- if(showWorking) {
491
- console.log(`[VSRepository] (build) Where object builded to ${keyToMap}:\n`, JSON.stringify(buildedWhere, null, 2));
492
- }
493
-
494
489
  if(buildedWhere.autoInjectVal === undefined) argsCount++;
495
490
  }
496
491
 
@@ -700,9 +695,9 @@ export class VSRepository {
700
695
  const missingParams = whereParams.concat(otherParams).slice(args.length);
701
696
  throw new VSRepoRuntimeError(`[VSRepository] (runtime) Missing parameters: ${missingParams.join(', ')}`);
702
697
  } else if(args.length > argsCount) {
703
- const optionsArg = args[args.length - 1]
698
+ const optionsArg = args[args.length - 1];
704
699
  db = optionsArg.db ?? db;
705
- optionsSelectModel = optionsArg.selectModel
700
+ optionsSelectModel = optionsArg.selectModel;
706
701
  } else {
707
702
  args.push('1')
708
703
  }
@@ -740,10 +735,7 @@ export class VSRepository {
740
735
 
741
736
  if(config.baseMethods?.get?.active !== false) {
742
737
  buildInstance.get = async(pk, options = {}) => {
743
- let db;
744
- if(options?.db) db = options.db;
745
- else db = prisma;
746
-
738
+ let db = options?.db ?? prisma;
747
739
  const prismaArgs = {};
748
740
 
749
741
  const where = { [buildInstance.pkName]: pk };
@@ -782,10 +774,7 @@ export class VSRepository {
782
774
  }
783
775
  if(config.baseMethods?.getOrThrow?.active !== false) {
784
776
  buildInstance.getOrThrow = async(pk, options = {}) => {
785
- let db;
786
- if(options?.db) db = options.db;
787
- else db = prisma;
788
-
777
+ let db = options?.db ?? prisma;
789
778
  const prismaArgs = {};
790
779
 
791
780
  const where = { [buildInstance.pkName]: pk };
@@ -824,10 +813,7 @@ export class VSRepository {
824
813
  }
825
814
  if(config.baseMethods?.remove?.active !== false) {
826
815
  buildInstance.remove = async (pk, options = {}) => {
827
- let db;
828
- if(options?.db) db = options.db;
829
- else db = prisma;
830
-
816
+ let db = options?.db ?? prisma;
831
817
  const prismaArgs = {};
832
818
 
833
819
  const where = { [buildInstance.pkName]: pk };
@@ -865,10 +851,7 @@ export class VSRepository {
865
851
  }
866
852
  if(config.baseMethods?.save?.active !== false) {
867
853
  buildInstance.save = async (obj, options = {}) => {
868
- let db;
869
- if(options?.db) db = options.db;
870
- else db = prisma;
871
-
854
+ let db = options?.db ?? prisma;
872
855
  const prismaArgs = {};
873
856
 
874
857
  let selectModelKey = options?.selectModel ?? config.baseMethods?.save?.defaultSelect ?? buildInstance.defaultSelectModel;
@@ -1077,9 +1060,142 @@ export class VSRepository {
1077
1060
  throw err;
1078
1061
  }
1079
1062
  }
1063
+ if(config.baseMethods?.removeList?.active !== false) {
1064
+ buildInstance.removeList = async (pks, options = {}) => {
1065
+ let db = options?.db ?? prisma;
1066
+ const prismaArgs = {};
1067
+
1068
+ const where = { [buildInstance.pkName]: { in: pks } };
1069
+ if(buildInstance.requiredWhere && !config.baseMethods?.removeList?.ignoreRequiredWhere) {
1070
+ Object.assign(where, buildInstance.requiredWhere);
1071
+ }
1072
+ prismaArgs.where = where;
1073
+
1074
+ let start;
1075
+ if(showWorking){
1076
+ console.log(`[VSRepository] (runtime) Executing removeList on ${buildInstance.tableName}.`);
1077
+ console.log(`[VSRepository] (runtime) Built arguments to removeList on ${buildInstance.tableName}:\n`, JSON.stringify(prismaArgs, null, 2));
1078
+ start = performance.now();
1079
+ }
1080
+ try {
1081
+ const result = await db[buildInstance.tableName].deleteMany(prismaArgs);
1082
+ if(showWorking) {
1083
+ const end = performance.now();
1084
+ console.log(`[VSRepository] (runtime) Executed removeList on ${buildInstance.tableName} (took: ${(end - start).toFixed(2)}ms).`);
1085
+ }
1086
+ return result;
1087
+ } catch (err) {
1088
+ console.log(`[VSRepository] (runtime) Fatal error when trying to removeList on ${buildInstance.tableName}:\n`, JSON.stringify({ prismaArgs }, null, 2));
1089
+ throw err;
1090
+ }
1091
+ }
1092
+ }
1093
+ if(config.baseMethods?.getAll?.active !== false) {
1094
+ buildInstance.getAll = async (options = {}) => {
1095
+ let db = options?.db ?? prisma;
1096
+ const prismaArgs = {};
1097
+
1098
+ if(buildInstance.requiredWhere && !config.baseMethods?.getAll?.ignoreRequiredWhere) {
1099
+ prismaArgs.where = { ...buildInstance.requiredWhere };
1100
+ }
1101
+
1102
+ let selectModelKey = options?.selectModel ?? config.baseMethods?.getAll?.defaultSelect ?? buildInstance.defaultSelectModel;
1103
+ if(selectModelKey){
1104
+ prismaArgs.select = buildInstance.selectModels[selectModelKey];
1105
+ }
1106
+
1107
+ if(options.pagination) {
1108
+ prismaArgs.skip = options.pagination.skip;
1109
+ prismaArgs.take = options.pagination.take;
1110
+ prismaArgs.cursor = options.pagination.cursor;
1111
+ }
1112
+ if(options.order) {
1113
+ prismaArgs.orderBy = options.order;
1114
+ }
1115
+
1116
+ let start;
1117
+ if(showWorking){
1118
+ console.log(`[VSRepository] (runtime) Executing getAll on ${buildInstance.tableName}.`);
1119
+ console.log(`[VSRepository] (runtime) Built arguments to getAll on ${buildInstance.tableName}:\n`, JSON.stringify(prismaArgs, null, 2));
1120
+ start = performance.now();
1121
+ }
1122
+ try {
1123
+ const result = await db[buildInstance.tableName].findMany(prismaArgs);
1124
+ if(showWorking) {
1125
+ const end = performance.now();
1126
+ console.log(`[VSRepository] (runtime) Executed getAll on ${buildInstance.tableName} (took: ${(end - start).toFixed(2)}ms).`);
1127
+ }
1128
+ return result;
1129
+ } catch (err) {
1130
+ console.log(`[VSRepository] (runtime) Fatal error when trying to getAll on ${buildInstance.tableName}:\n`, JSON.stringify({ prismaArgs }, null, 2));
1131
+ throw err;
1132
+ }
1133
+ }
1134
+ }
1135
+ if(config.baseMethods?.total?.active !== false) {
1136
+ buildInstance.total = async (options = {}) => {
1137
+ let db = options?.db ?? prisma;
1138
+ const prismaArgs = {};
1139
+
1140
+ if(buildInstance.requiredWhere && !config.baseMethods?.total?.ignoreRequiredWhere) {
1141
+ prismaArgs.where = { ...buildInstance.requiredWhere };
1142
+ }
1143
+
1144
+ let start;
1145
+ if(showWorking){
1146
+ console.log(`[VSRepository] (runtime) Executing total on ${buildInstance.tableName}.`);
1147
+ console.log(`[VSRepository] (runtime) Built arguments to total on ${buildInstance.tableName}:\n`, JSON.stringify(prismaArgs, null, 2));
1148
+ start = performance.now();
1149
+ }
1150
+ try {
1151
+ const result = await db[buildInstance.tableName].count(prismaArgs);
1152
+ if(showWorking) {
1153
+ const end = performance.now();
1154
+ console.log(`[VSRepository] (runtime) Executed total on ${buildInstance.tableName} (took: ${(end - start).toFixed(2)}ms).`);
1155
+ }
1156
+ return result;
1157
+ } catch (err) {
1158
+ console.log(`[VSRepository] (runtime) Fatal error when trying to total on ${buildInstance.tableName}:\n`, JSON.stringify({ prismaArgs }, null, 2));
1159
+ throw err;
1160
+ }
1161
+ }
1162
+ }
1163
+ if(config.baseMethods?.has?.active !== false) {
1164
+ buildInstance.has = async (pk, options = {}) => {
1165
+ let db = options?.db ?? prisma;
1166
+ const prismaArgs = {};
1167
+
1168
+ const where = { [buildInstance.pkName]: pk };
1169
+ if(buildInstance.requiredWhere && !config.baseMethods?.has?.ignoreRequiredWhere) {
1170
+ Object.assign(where, buildInstance.requiredWhere);
1171
+ }
1172
+ prismaArgs.where = where;
1173
+ prismaArgs.select = { [buildInstance.pkName]: true };
1174
+
1175
+ let start;
1176
+ if(showWorking){
1177
+ console.log(`[VSRepository] (runtime) Executing has on ${buildInstance.tableName}.`);
1178
+ console.log(`[VSRepository] (runtime) Built arguments to has on ${buildInstance.tableName}:\n`, JSON.stringify(prismaArgs, null, 2));
1179
+ start = performance.now();
1180
+ }
1181
+ try {
1182
+ const result = await db[buildInstance.tableName].findUnique(prismaArgs);
1183
+ if(showWorking) {
1184
+ const end = performance.now();
1185
+ console.log(`[VSRepository] (runtime) Executed has on ${buildInstance.tableName} (took: ${(end - start).toFixed(2)}ms).`);
1186
+ }
1187
+ return !!result;
1188
+ } catch (err) {
1189
+ console.log(`[VSRepository] (runtime) Fatal error when trying to has on ${buildInstance.tableName}:\n`, JSON.stringify({ prismaArgs }, null, 2));
1190
+ throw err;
1191
+ }
1192
+ }
1193
+ }
1080
1194
  }
1081
1195
 
1082
- if(config.freeze) Object.freeze(buildInstance)
1196
+ buildInstance.prisma = prisma;
1197
+
1198
+ if(config.freeze) Object.freeze(buildInstance);
1083
1199
  return buildInstance;
1084
1200
  }
1085
1201
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "vsrepo",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Uma biblioteca de repository pattern para Prisma",
5
5
  "homepage": "https://github.com/jaobrabo123/VSRepository#readme",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/jaobrabo123/VSRepository"
8
+ "url": "git+https://github.com/jaobrabo123/VSRepository.git"
9
9
  },
10
10
  "main": "./VSRepository/index.js",
11
11
  "types": "./VSRepository/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "scripts"
33
33
  ],
34
34
  "bin": {
35
- "vsrepo": "./scripts/configure-prisma-import.mjs"
35
+ "vsrepo": "scripts/configure-prisma-import.mjs"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=18"
@@ -63,5 +63,8 @@
63
63
  "prisma": "^7.8.0",
64
64
  "tsx": "^4.22.3",
65
65
  "typescript": "^6.0.3"
66
+ },
67
+ "dependencies": {
68
+ "zod": "^4.4.3"
66
69
  }
67
- }
70
+ }