vsrepo 1.3.3 β†’ 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.
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:
@@ -34,6 +36,7 @@ VSRepository lets you create strongly-typed repositories with:
34
36
  - [Configuring the base methods](#configuring-the-base-methods)
35
37
  - [Select Models](#select-models)
36
38
  - [Include Models](#include-models)
39
+ - [Raw include (options.include)](#raw-include-optionsinclude)
37
40
  - [Required Where](#required-where)
38
41
  - [Default Ordenation](#default-ordenation)
39
42
  - [`see` option](#see-option)
@@ -46,6 +49,7 @@ VSRepository lets you create strongly-typed repositories with:
46
49
  - [Distinct](#distinct)
47
50
  - [Method configuration](#method-configuration)
48
51
  - [Aggregate and GroupBy](#aggregate-and-groupby)
52
+ - [Query Methods](#query-methods)
49
53
  - [Relations in save](#relations-in-save)
50
54
  - [Transactions](#transactions)
51
55
  - [Extending a repository](#extending-a-repository)
@@ -181,7 +185,7 @@ await userRepository.remove(user.id);
181
185
 
182
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.
183
187
 
184
- See **[README-DynamicRepo.md](./README-DynamicRepo.md)** for full documentation on the class-based approach, including NestJS integration examples, decorator config, and a comparison with `setupVSRepo`.
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`.
185
189
 
186
190
  ---
187
191
 
@@ -572,6 +576,35 @@ await userRepository.get(id, { selectModel: "public" });
572
576
  await userRepository.get(id, { selectModel: "public", includeModel: "withPosts" });
573
577
  ```
574
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
+
575
608
  ---
576
609
 
577
610
  ## Required Where
@@ -1022,6 +1055,7 @@ Each entry in `methods` accepts the following options:
1022
1055
  | `pushWhere` | `WhereModel<M>` | β€” | Extra `where` added to the query in addition to `requiredWhere`. |
1023
1056
  | `injectOrdenation` | `OrdenationModel<M>` | β€” | Fixed ordering automatically injected into the query. |
1024
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). |
1025
1059
 
1026
1060
  ---
1027
1061
 
@@ -1044,6 +1078,77 @@ const userRepository = setupVSRepo<User, "user">()(({
1044
1078
 
1045
1079
  ---
1046
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
+
1047
1152
  ## Relations in save
1048
1153
 
1049
1154
  Configure relations so that `save` and `patch` manage them automatically (`saveList` and `patchList` also manage relations automatically).
@@ -1438,9 +1543,9 @@ Recommended `tsconfig.json`:
1438
1543
 
1439
1544
  **Select model returns unexpected fields** β€” Check that the select model defines exactly the fields your TypeScript type expects.
1440
1545
 
1441
- **`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.
1442
1547
 
1443
- **`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`.
1444
1549
 
1445
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.
1446
1551