vsrepo 1.3.2 β†’ 1.3.4

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.
Files changed (26) hide show
  1. package/README.md +116 -2
  2. package/README.pt-BR.md +1556 -0
  3. package/dist/DynamicRepository.d.ts +476 -0
  4. package/dist/DynamicRepository.js +26 -0
  5. package/dist/VSRepoError.d.ts +63 -54
  6. package/dist/VSRepository.d.ts +1162 -1056
  7. package/dist/VSRepository.js +51 -16
  8. package/dist/internal/constants/dinamic-methods-key.constant.js +4 -0
  9. package/dist/internal/decorators/dynamic-method.decorator.js +14 -0
  10. package/dist/internal/decorators/query-method.decorator.js +16 -0
  11. package/dist/internal/decorators/types/dynamic-method-config.type.js +2 -0
  12. package/dist/internal/entities/dynamic-method-metadata.entity.js +26 -0
  13. package/dist/internal/errors/vs-repo.error.js +5 -1
  14. package/dist/internal/resolvers/dbAndPrismaArgs.resolve.js +3 -0
  15. package/dist/internal/resolvers/dynamic-methods-metadata.resolve.js +15 -0
  16. package/dist/internal/utils/logger.util.js +1 -1
  17. package/dist/internal/utils/schemas.util.js +16 -1
  18. package/dist/internal/validation/constructor-config.validate.js +1 -10
  19. package/dist/internal/validation/dynamic-method-config.validate.js +19 -0
  20. package/dist/internal/validation/method-options.validate.js +1 -0
  21. package/dist/internal/validation/query-method-arg.validate.js +22 -0
  22. package/dist/internal/validation/query-method-options.validate.js +24 -0
  23. package/dist/internal/validation/types/query-method-arg.type.js +2 -0
  24. package/dist/internal/validation/types/query-method-options.type.js +2 -0
  25. package/package.json +78 -72
  26. package/scripts/configure-prisma-import.mjs +58 -9
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
  ![NPM License](https://img.shields.io/npm/l/vsrepo?style=flat-square)
5
5
  ![NPM Downloads](https://img.shields.io/npm/dt/vsrepo?style=flat-square)
6
6
 
7
+ πŸ‡ΊπŸ‡Έ You're reading the English version. [πŸ‡§πŸ‡· Ler em portuguΓͺs](./README.pt-BR.md)
8
+
7
9
  Repository pattern library for projects using **Prisma**, with full **TypeScript** support and automatic **type inference**.
8
10
 
9
11
  VSRepository lets you create strongly-typed repositories with:
@@ -25,6 +27,7 @@ VSRepository lets you create strongly-typed repositories with:
25
27
  - [Installation](#installation)
26
28
  - [Generating the types](#generating-the-types)
27
29
  - [Basic usage](#basic-usage)
30
+ - [Class-based approach (DynamicRepository)](#class-based-approach-dynamicrepository)
28
31
  - [NestJS integration](#nestjs-integration)
29
32
  - [Base methods](#base-methods)
30
33
  - [Soft-delete](#soft-delete)
@@ -33,6 +36,7 @@ VSRepository lets you create strongly-typed repositories with:
33
36
  - [Configuring the base methods](#configuring-the-base-methods)
34
37
  - [Select Models](#select-models)
35
38
  - [Include Models](#include-models)
39
+ - [Raw include (options.include)](#raw-include-optionsinclude)
36
40
  - [Required Where](#required-where)
37
41
  - [Default Ordenation](#default-ordenation)
38
42
  - [`see` option](#see-option)
@@ -45,6 +49,7 @@ VSRepository lets you create strongly-typed repositories with:
45
49
  - [Distinct](#distinct)
46
50
  - [Method configuration](#method-configuration)
47
51
  - [Aggregate and GroupBy](#aggregate-and-groupby)
52
+ - [Query Methods](#query-methods)
48
53
  - [Relations in save](#relations-in-save)
49
54
  - [Transactions](#transactions)
50
55
  - [Extending a repository](#extending-a-repository)
@@ -176,6 +181,14 @@ await userRepository.remove(user.id);
176
181
 
177
182
  ---
178
183
 
184
+ ## Class-based approach (DynamicRepository)
185
+
186
+ If you prefer an OOP style with decorators instead of the functional `setupVSRepo` approach, VSRepository also provides `DynamicRepository` β€” a class you can extend with `@DynamicMethod()` decorators to define your dynamic methods.
187
+
188
+ See **[README-DynamicRepo.md](./README-DynamicRepo.md)** (or the [pt-BR version](./README-DynamicRepo.pt-BR.md)) for full documentation on the class-based approach, including NestJS integration examples, decorator config, and a comparison with `setupVSRepo`.
189
+
190
+ ---
191
+
179
192
  ## NestJS integration
180
193
 
181
194
  VSRepository can be easily integrated into NestJS projects through providers. Below is a complete example using NestJS's dependency injection pattern.
@@ -563,6 +576,35 @@ await userRepository.get(id, { selectModel: "public" });
563
576
  await userRepository.get(id, { selectModel: "public", includeModel: "withPosts" });
564
577
  ```
565
578
 
579
+ ### Raw `include` (`options.include`)
580
+
581
+ Besides `includeModel` (named, pre-configured in `includeModels`), you can pass a raw Prisma `include` directly in the call, without registering it beforehand on the repository.
582
+
583
+ ```ts
584
+ const user = await userRepository.get(id, {
585
+ include: { posts: true, profile: true },
586
+ });
587
+ ```
588
+
589
+ `options.include` accepts any valid `include` for the repository's Prisma model β€” it's fully typed and offers the same autocomplete/validation as calling `prisma.user.findMany({ include: ... })` directly.
590
+
591
+ **Rules and behavior:**
592
+
593
+ - **Mutually exclusive with `selectModel` and `includeModel`.** Only one of the three can be provided per call; the types enforce this β€” passing more than one is a compile-time error.
594
+ - **Ad hoc, not reusable.** Unlike `includeModel`, it doesn't need to be declared in `includeModels`. Use it for one-off includes that don't justify a named model.
595
+ - **No `selectModel` default is applied.** As with `includeModel`, when `include` is provided the select (including `defaultSelectModel`) is ignored and only the `include` is sent to Prisma.
596
+
597
+ ```ts
598
+ // CORRECT βœ… β€” raw include only
599
+ await userRepository.get(id, { include: { posts: true } });
600
+
601
+ // WRONG ❌ β€” combining include with selectModel/includeModel is not allowed
602
+ await userRepository.get(id, { selectModel: "public", include: { posts: true } });
603
+ await userRepository.get(id, { includeModel: "withPosts", include: { posts: true } });
604
+ ```
605
+
606
+ > **When to use `includeModel` vs. `include`:** prefer `includeModel` for includes reused across multiple calls (defined once in `includeModels`); use `include` for specific, occasional includes that don't need a name.
607
+
566
608
  ---
567
609
 
568
610
  ## Required Where
@@ -1013,6 +1055,7 @@ Each entry in `methods` accepts the following options:
1013
1055
  | `pushWhere` | `WhereModel<M>` | β€” | Extra `where` added to the query in addition to `requiredWhere`. |
1014
1056
  | `injectOrdenation` | `OrdenationModel<M>` | β€” | Fixed ordering automatically injected into the query. |
1015
1057
  | `injectPagination` | `PaginationModel<M>` | β€” | Fixed pagination automatically injected into the query. |
1058
+ | `query` | `{ value: string; modifying?: boolean }` | β€” | Turns the method into a **Query Method** (raw SQL). Ignores every other option above β€” see [Query Methods](#query-methods). |
1016
1059
 
1017
1060
  ---
1018
1061
 
@@ -1035,6 +1078,77 @@ const userRepository = setupVSRepo<User, "user">()(({
1035
1078
 
1036
1079
  ---
1037
1080
 
1081
+ ### Query Methods
1082
+
1083
+ Query Methods let a method run **raw SQL** directly, completely bypassing the dynamic-method name parser. They're useful for complex queries (heavy joins, CTEs, database-specific functions) that aren't practical to express with the standard prefixes/suffixes.
1084
+
1085
+ Internally, VSRepository executes the SQL through Prisma using `$queryRawUnsafe` (for reads) or `$executeRawUnsafe` (for writes), and the values in the `args` array are passed as **positional parameters** (`$1`, `$2`, ...) β€” the same prepared-statement technique Prisma itself uses. This means the values are never concatenated into the SQL string, which is what actually prevents SQL injection.
1086
+
1087
+ > [!WARNING]
1088
+ > `$1`, `$2`, ... in your SQL must always represent **values** (data parameters), never column names, table names, or dynamic SQL fragments. Identifier names (columns/tables) can't be passed as a positional parameter β€” if your method needs to vary those, build the SQL from a fixed, known set of options in your own code, never from untrusted input.
1089
+
1090
+ ```ts
1091
+ const userRepository = setupVSRepo<User, "user">()({
1092
+ tableName: "user",
1093
+ pkName: "id",
1094
+ methods: {
1095
+ // Read query method (non-modifying)
1096
+ findActiveUsersRaw: {
1097
+ map: true,
1098
+ query: {
1099
+ value: 'SELECT * FROM "user" WHERE active = $1',
1100
+ },
1101
+ },
1102
+
1103
+ // Write query method (modifying: true)
1104
+ deactivateUsersOlderThanRaw: {
1105
+ map: true,
1106
+ query: {
1107
+ value: 'UPDATE "user" SET active = false WHERE "createdAt" < $1',
1108
+ modifying: true,
1109
+ },
1110
+ },
1111
+ },
1112
+ }).build(prisma);
1113
+ ```
1114
+
1115
+ **Calling a Query Method:**
1116
+
1117
+ Every Query Method takes a single argument shaped as `{ args: [...], db? }`:
1118
+
1119
+ ```ts
1120
+ // Non-modifying: returns 'any' by default, but accepts a generic to
1121
+ // infer/assert the return type right at the call site
1122
+ const activeUsers = await userRepository.findActiveUsersRaw<User[]>({
1123
+ args: [true],
1124
+ });
1125
+
1126
+ // Modifying: always returns 'number' (count of affected rows)
1127
+ const affected = await userRepository.deactivateUsersOlderThanRaw({
1128
+ args: [new Date("2024-01-01")],
1129
+ });
1130
+
1131
+ // Participating in a transaction, via 'db'
1132
+ await userRepository.prisma.$transaction(async (tx) => {
1133
+ await userRepository.deactivateUsersOlderThanRaw({
1134
+ args: [new Date("2024-01-01")],
1135
+ db: tx,
1136
+ });
1137
+ });
1138
+ ```
1139
+
1140
+ | Option | Type | Default | Description |
1141
+ | ------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------|
1142
+ | `value` | `string` | β€” | **Required.** Raw SQL to execute. Use `$1`, `$2`, ... for the placeholders of the values in `args`. |
1143
+ | `modifying` | `boolean` | `false` | When `true`, executes via `$executeRawUnsafe` and the method always resolves to `number`. When `false`, executes via `$queryRawUnsafe` and the method resolves to `TReturn` (`any` by default, inferable via a generic at the call site). |
1144
+
1145
+ > [!NOTE]
1146
+ > Unlike the other dynamic methods, Query Methods **completely ignore** `selectModels`, `requiredWhere`, `pushWhere`, `whereType`, `injectOrdenation`, `injectPagination`, and `proxyTo` β€” none of that applies, since there's no name parsing or `where`/`select` assembly by VSRepository. Free-form method names (outside the `findBy`, `updateBy`, etc. patterns) also **don't** require `proxyTo`.
1147
+
1148
+ The same functionality is available in the class-based approach via the `@QueryMethod` decorator β€” see [README-DynamicRepo.md](./README-DynamicRepo.md#the-querymethod-decorator).
1149
+
1150
+ ---
1151
+
1038
1152
  ## Relations in save
1039
1153
 
1040
1154
  Configure relations so that `save` and `patch` manage them automatically (`saveList` and `patchList` also manage relations automatically).
@@ -1429,9 +1543,9 @@ Recommended `tsconfig.json`:
1429
1543
 
1430
1544
  **Select model returns unexpected fields** β€” Check that the select model defines exactly the fields your TypeScript type expects.
1431
1545
 
1432
- **`selectModel` and `includeModel` together in the same call** β€” Not allowed. Choose one or the other: if `includeModel` is provided, the `select` (including `defaultSelectModel`) is ignored and only the `include` is sent to Prisma.
1546
+ **`selectModel`, `includeModel` and `include` together in the same call** β€” Not allowed. Only one of the three can be provided per call: if `includeModel` or `include` is provided, the `select` (including `defaultSelectModel`) is ignored and only the `include` is sent to Prisma.
1433
1547
 
1434
- **`includeModel` doesn't appear as a default repository option** β€” This is expected. Unlike `defaultSelectModel`, there's no `defaultIncludeModel`/`defaultInclude`. An `includeModel` can only be set in the method call, via `options.includeModel`.
1548
+ **`includeModel` doesn't appear as a default repository option** β€” This is expected. Unlike `defaultSelectModel`, there's no `defaultIncludeModel`/`defaultInclude`. An `includeModel` can only be set in the method call, via `options.includeModel`. A raw, ad hoc include can be set via `options.include`, without needing to be registered in `includeModels`.
1435
1549
 
1436
1550
  **`softRemovekName` throws an error at build** β€” The provided field must be of type `DateTime` in the Prisma schema. Types like `Boolean` or `String` are not accepted.
1437
1551