vsrepo 1.4.2 → 2.1.0

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 (151) hide show
  1. package/README.md +827 -1322
  2. package/README.pt-BR.md +833 -1325
  3. package/dist/VSRepoAdapter.d.ts +109 -0
  4. package/dist/VSRepoAdapter.js +18 -0
  5. package/dist/VSRepository.d.ts +166 -1201
  6. package/dist/VSRepository.js +327 -237
  7. package/dist/decorators/dynamic-method.decorator.d.ts +25 -0
  8. package/dist/decorators/dynamic-method.decorator.js +38 -0
  9. package/dist/decorators/query-method.decorator.d.ts +27 -0
  10. package/dist/decorators/query-method.decorator.js +45 -0
  11. package/dist/errors/VSRepoAdapterError.d.ts +22 -0
  12. package/dist/errors/VSRepoAdapterError.js +31 -0
  13. package/dist/errors/VSRepoError.d.ts +15 -0
  14. package/dist/errors/VSRepoError.js +21 -0
  15. package/dist/index.d.ts +38 -1
  16. package/dist/index.js +28 -15
  17. package/dist/internal/constants/debug-arg-symbol.constant.d.ts +1 -0
  18. package/dist/internal/constants/debug-arg-symbol.constant.js +4 -0
  19. package/dist/internal/constants/dynamic-methods-key.constant.d.ts +1 -0
  20. package/dist/internal/constants/query-methods-key.constant.d.ts +1 -0
  21. package/dist/internal/constants/query-methods-key.constant.js +4 -0
  22. package/dist/internal/enums/adapter-error-code.enum.d.ts +125 -0
  23. package/dist/internal/enums/adapter-error-code.enum.js +129 -0
  24. package/dist/internal/enums/transaction-isolation-level.enum.d.ts +16 -0
  25. package/dist/internal/enums/transaction-isolation-level.enum.js +20 -0
  26. package/dist/internal/enums/vs-log-level.enum.d.ts +18 -0
  27. package/dist/internal/enums/vs-log-level.enum.js +22 -0
  28. package/dist/internal/enums/vsrepo-error-type.enum.d.ts +19 -0
  29. package/dist/internal/enums/vsrepo-error-type.enum.js +23 -0
  30. package/dist/internal/resolvers/dynamic-methods.resolver.d.ts +23 -0
  31. package/dist/internal/resolvers/dynamic-methods.resolver.js +910 -0
  32. package/dist/internal/resolvers/merge-wheres.resolver.d.ts +7 -0
  33. package/dist/internal/resolvers/merge-wheres.resolver.js +26 -0
  34. package/dist/internal/utils/uncapitalize.util.d.ts +1 -0
  35. package/dist/internal/utils/vs-logger.util.d.ts +25 -0
  36. package/dist/internal/utils/vs-logger.util.js +139 -0
  37. package/dist/internal/validators/decorators.validator.d.ts +8 -0
  38. package/dist/internal/validators/decorators.validator.js +75 -0
  39. package/dist/internal/validators/schemas/ordering.schema.d.ts +3 -0
  40. package/dist/internal/validators/schemas/ordering.schema.js +38 -0
  41. package/dist/internal/validators/schemas/pagination.schema.d.ts +6 -0
  42. package/dist/internal/validators/schemas/pagination.schema.js +40 -0
  43. package/dist/internal/validators/schemas/where.schema.d.ts +7 -0
  44. package/dist/internal/validators/schemas/where.schema.js +42 -0
  45. package/dist/internal/validators/vsrepo.validator.d.ts +40 -0
  46. package/dist/internal/validators/vsrepo.validator.js +182 -0
  47. package/dist/types/adapter/adapter-method-options.type.d.ts +24 -0
  48. package/dist/types/adapter/adapter-query-options.type.d.ts +5 -0
  49. package/dist/types/decorators/dynamic-method-options.type.d.ts +14 -0
  50. package/dist/types/decorators/query-method-options.type.d.ts +15 -0
  51. package/dist/types/dynamic-methods/dynamic-method-customization.type.d.ts +7 -0
  52. package/dist/types/dynamic-methods/dynamic-method-info.type.d.ts +18 -0
  53. package/dist/types/dynamic-methods/dynamic-method-where-ops.type.d.ts +6 -0
  54. package/dist/types/utils/count-result.type.d.ts +9 -0
  55. package/dist/types/utils/decimal-like.type.d.ts +23 -0
  56. package/dist/types/utils/deep-partial.type.d.ts +14 -0
  57. package/dist/types/utils/keys-of-type.type.d.ts +20 -0
  58. package/dist/types/utils/methods-options.type.d.ts +23 -0
  59. package/dist/types/utils/numeric-keys.type.d.ts +24 -0
  60. package/dist/types/utils/numeric-like.type.d.ts +10 -0
  61. package/dist/types/utils/ordering.type.d.ts +39 -0
  62. package/dist/types/utils/pagination.type.d.ts +11 -0
  63. package/dist/types/utils/perform-data.type.d.ts +4 -0
  64. package/dist/types/utils/primitive.type.d.ts +7 -0
  65. package/dist/types/utils/query-method-arg.type.d.ts +27 -0
  66. package/dist/types/utils/restrict-method-options.type.d.ts +14 -0
  67. package/dist/types/utils/see-mode.type.d.ts +12 -0
  68. package/dist/types/vsrepo/vsrepo-args.type.d.ts +9 -0
  69. package/dist/types/vsrepo/vsrepo-method.type.d.ts +4 -0
  70. package/dist/types/vsrepo/vsrepo-method.type.js +2 -0
  71. package/dist/types/vsrepo/vsrepo-options.type.d.ts +34 -0
  72. package/dist/types/vsrepo/vsrepo-options.type.js +2 -0
  73. package/dist/types/vsrepo/vsrepo-orm-types.type.d.ts +17 -0
  74. package/dist/types/vsrepo/vsrepo-orm-types.type.js +2 -0
  75. package/dist/types/vsrepo/vsrepo-pretty-where.type.d.ts +7 -0
  76. package/dist/types/vsrepo/vsrepo-pretty-where.type.js +2 -0
  77. package/dist/types/vsrepo/vsrepo-query-options.type.d.ts +17 -0
  78. package/dist/types/vsrepo/vsrepo-query-options.type.js +2 -0
  79. package/dist/types/vsrepo/vsrepo-query.type.d.ts +5 -0
  80. package/dist/types/vsrepo/vsrepo-query.type.js +2 -0
  81. package/dist/types/vsrepo/vsrepo-relations.type.d.ts +26 -0
  82. package/dist/types/vsrepo/vsrepo-relations.type.js +2 -0
  83. package/dist/types/vsrepo/vsrepo-resolve-args-data.type.d.ts +19 -0
  84. package/dist/types/vsrepo/vsrepo-resolve-args-data.type.js +2 -0
  85. package/dist/types/vsrepo/vsrepo-select.type.d.ts +15 -0
  86. package/dist/types/vsrepo/vsrepo-select.type.js +2 -0
  87. package/dist/types/vsrepo/vsrepo-transaction-options.type.d.ts +12 -0
  88. package/dist/types/vsrepo/vsrepo-transaction-options.type.js +2 -0
  89. package/dist/types/vsrepo/vsrepo-ugly-where.type.d.ts +9 -0
  90. package/dist/types/vsrepo/vsrepo-ugly-where.type.js +2 -0
  91. package/dist/types/vsrepo/vsrepo-where.type.d.ts +99 -0
  92. package/dist/types/vsrepo/vsrepo-where.type.js +2 -0
  93. package/package.json +16 -37
  94. package/README-DynamicRepo.md +0 -625
  95. package/README-DynamicRepo.pt-BR.md +0 -625
  96. package/dist/DynamicRepository.d.ts +0 -497
  97. package/dist/DynamicRepository.js +0 -26
  98. package/dist/VSRepoError.d.ts +0 -83
  99. package/dist/VSRepoError.js +0 -17
  100. package/dist/internal/decorators/dynamic-method.decorator.js +0 -14
  101. package/dist/internal/decorators/query-method.decorator.js +0 -20
  102. package/dist/internal/entities/dynamic-method-metadata.entity.js +0 -26
  103. package/dist/internal/errors/vs-repo.error.js +0 -31
  104. package/dist/internal/resolvers/base-methods.resolve.js +0 -541
  105. package/dist/internal/resolvers/create-update-payloads-with-relations.resolve.js +0 -143
  106. package/dist/internal/resolvers/data-payload-with-relations.resolve.js +0 -60
  107. package/dist/internal/resolvers/dbAndPrismaArgs.resolve.js +0 -63
  108. package/dist/internal/resolvers/dynamic-method-customization.resolve.js +0 -57
  109. package/dist/internal/resolvers/dynamic-method-info.resolve.js +0 -279
  110. package/dist/internal/resolvers/dynamic-methods-metadata.resolve.js +0 -15
  111. package/dist/internal/resolvers/merge-wheres.resolve.js +0 -22
  112. package/dist/internal/resolvers/pretty-wheres.resolve.js +0 -87
  113. package/dist/internal/resolvers/select.resolve.js +0 -7
  114. package/dist/internal/resolvers/specific-where.resolve.js +0 -84
  115. package/dist/internal/resolvers/ugly-where.resolve.js +0 -178
  116. package/dist/internal/utils/logger.util.js +0 -21
  117. package/dist/internal/utils/schemas.util.js +0 -31
  118. package/dist/internal/validation/build-config.validate.js +0 -84
  119. package/dist/internal/validation/constructor-config.validate.js +0 -64
  120. package/dist/internal/validation/dynamic-method-config.validate.js +0 -19
  121. package/dist/internal/validation/extension.validate.js +0 -15
  122. package/dist/internal/validation/is-object.validate.js +0 -6
  123. package/dist/internal/validation/method-options.validate.js +0 -42
  124. package/dist/internal/validation/obj-with-relations.validate.js +0 -37
  125. package/dist/internal/validation/prisma-client.validate.js +0 -10
  126. package/dist/internal/validation/query-method-arg.validate.js +0 -22
  127. package/dist/internal/validation/query-method-options.validate.js +0 -24
  128. package/scripts/configure-prisma-import.mjs +0 -283
  129. package/scripts/copy-types.mjs +0 -24
  130. /package/dist/{internal/decorators/types/dynamic-method-config.type.js → types/adapter/adapter-method-options.type.js} +0 -0
  131. /package/dist/{internal/errors/types/vs-repo-error-type.type.js → types/adapter/adapter-query-options.type.js} +0 -0
  132. /package/dist/{internal/errors/types/vs-repo-runtime-error-code.type.js → types/decorators/dynamic-method-options.type.js} +0 -0
  133. /package/dist/{internal/validation/types → types/decorators}/query-method-options.type.js +0 -0
  134. /package/dist/{internal/resolvers/types → types/dynamic-methods}/dynamic-method-customization.type.js +0 -0
  135. /package/dist/{internal/resolvers/types → types/dynamic-methods}/dynamic-method-info.type.js +0 -0
  136. /package/dist/{internal/resolvers/types → types/dynamic-methods}/dynamic-method-where-ops.type.js +0 -0
  137. /package/dist/{internal/resolvers/types/base-method-function.type.js → types/utils/count-result.type.js} +0 -0
  138. /package/dist/{internal/resolvers/types/pretty-where.type.js → types/utils/decimal-like.type.js} +0 -0
  139. /package/dist/{internal/resolvers/types/prisma-args.type.js → types/utils/deep-partial.type.js} +0 -0
  140. /package/dist/{internal/resolvers/types/repository-build-instance.type.js → types/utils/keys-of-type.type.js} +0 -0
  141. /package/dist/{internal/resolvers/types/resolve-db-and-prisma-args-data.type.js → types/utils/methods-options.type.js} +0 -0
  142. /package/dist/{internal/resolvers/types/ugly-where.type.js → types/utils/numeric-keys.type.js} +0 -0
  143. /package/dist/{internal/validation/types/base-methods.type.js → types/utils/numeric-like.type.js} +0 -0
  144. /package/dist/{internal/validation/types/build-config.type.js → types/utils/ordering.type.js} +0 -0
  145. /package/dist/{internal/validation/types → types/utils}/pagination.type.js +0 -0
  146. /package/dist/{internal/validation/types/constructor-config.type.js → types/utils/perform-data.type.js} +0 -0
  147. /package/dist/{internal/validation/types/method-options.type.js → types/utils/primitive.type.js} +0 -0
  148. /package/dist/{internal/validation/types → types/utils}/query-method-arg.type.js +0 -0
  149. /package/dist/{internal/validation/types/method.type.js → types/utils/restrict-method-options.type.js} +0 -0
  150. /package/dist/{internal/validation/types → types/utils}/see-mode.type.js +0 -0
  151. /package/dist/{internal/validation/types/relation.type.js → types/vsrepo/vsrepo-args.type.js} +0 -0
@@ -1,625 +0,0 @@
1
- # DynamicRepository (Class-based approach)
2
-
3
- 🇺🇸 You're reading the English version. [🇧🇷 Ler em português](./README-DynamicRepo.pt-BR.md)
4
-
5
- VSRepository offers two ways to create repositories: the functional `setupVSRepo` approach and the OOP `DynamicRepository` class-based approach. This document covers the class-based approach using `DynamicRepository` and the `@DynamicMethod` decorator.
6
-
7
- > For the functional approach, see the main [README.md](./README.md).
8
-
9
- ## Table of contents
10
-
11
- - [When to use DynamicRepository](#when-to-use-dynamicrepository)
12
- - [Requirements](#requirements)
13
- - [Creating a class](#creating-a-class)
14
- - [The @DynamicMethod decorator](#the-dynamicmethod-decorator)
15
- - [Decorator config options](#decorator-config-options)
16
- - [The @QueryMethod decorator](#the-querymethod-decorator)
17
- - [Base methods](#base-methods)
18
- - [Working with relations](#working-with-relations)
19
- - [Transactions](#transactions)
20
- - [Working with includes](#working-with-includes)
21
- - [DynamicMethodOptions](#dynamicmethodoptions)
22
- - [NestJS integration](#nestjs-integration)
23
- - [API Reference](#api-reference)
24
- - [Differences from setupVSRepo](#differences-from-setupvsrepo)
25
-
26
- ---
27
-
28
- ## When to use DynamicRepository
29
-
30
- Use `DynamicRepository` when you prefer an **OOP style with decorators** over the functional `setupVSRepo` approach. Key characteristics:
31
-
32
- - Methods are defined as `declare` fields with `@DynamicMethod()` decorators
33
- - The repository is a class you can extend and inject via dependency injection
34
- - `selectModels` and `includeModels` are **not supported** (use raw `select`/`include` via `DynamicMethodOptions` instead)
35
- - Base methods are always active (no `active` toggle per method)
36
- - The repository is built automatically in the constructor (no explicit `.build()` call)
37
-
38
- ---
39
-
40
- ## Requirements
41
-
42
- `DynamicRepository` relies on TypeScript's legacy decorators, so your project's `tsconfig.json` must have:
43
-
44
- ```json
45
- {
46
- "compilerOptions": {
47
- "experimentalDecorators": true
48
- }
49
- }
50
- ```
51
-
52
- `reflect-metadata` is already a dependency of `vsrepo` and is imported internally — you don't need to import it yourself.
53
-
54
- Without `experimentalDecorators: true`, `@DynamicMethod()` will fail to compile (or silently fail to register the method at runtime, depending on your build tool).
55
-
56
- ---
57
-
58
- ## Creating a class
59
-
60
- Extend `DynamicRepository` with four generic parameters:
61
-
62
- ```typescript
63
- import {
64
- DynamicRepository,
65
- DynamicMethod,
66
- DynamicMethodOptions,
67
- PaginationModel,
68
- } from "../../generated/vsrepo";
69
- import type { Prisma } from "../../generated/prisma/client";
70
- import { PrismaClient } from "../../generated/prisma/client";
71
-
72
- type User = Prisma.UserGetPayload<{
73
- include: { address: true; posts: true };
74
- }>;
75
-
76
- class UserRepository extends DynamicRepository<
77
- User, // Entity type (with relations included)
78
- "User", // Prisma model name
79
- string, // Primary key type
80
- { address: true; posts: true } // Which fields are relations (flags)
81
- > {
82
- constructor(prisma: PrismaClient) {
83
- super(prisma, {
84
- tableName: "user",
85
- pkName: "id",
86
- relations: {
87
- address: { mode: "oto", pk: "id", restriction: "set" },
88
- posts: { mode: "otm", pk: "id", restriction: "add" },
89
- },
90
- requiredWhere: { active: true },
91
- build: {
92
- showWorking: false,
93
- baseMethods: {
94
- save: { ignoreRequiredWhere: true },
95
- },
96
- },
97
- });
98
- }
99
- }
100
- ```
101
-
102
- **Generic parameters:**
103
-
104
- | Parameter | Description |
105
- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
106
- | `TEntity` | The full entity type, including any relations you want available |
107
- | `UName` | The Prisma model name as a string literal (capitalized, e.g. `"User"`) |
108
- | `VPKType` | The type of the primary key (`string`, `number`, etc.) |
109
- | `WRelations` *(optional)* | An object flags indicating which fields are relations (e.g. `{ address: true }`). Only needed if you plan to configure the repository's relations — omit it otherwise |
110
-
111
- ---
112
-
113
- ## The @DynamicMethod decorator
114
-
115
- Declare dynamic methods as class fields using `declare` and decorate them with `@DynamicMethod()`:
116
-
117
- ```typescript
118
- class UserRepository extends DynamicRepository<User, "User", string> {
119
- // Simple dynamic method - name determines behavior
120
- @DynamicMethod()
121
- declare findByEmail: (email: string) => Promise<User | null>;
122
-
123
- // With decorator config
124
- @DynamicMethod<"User">({ proxyTo: "findMany", pushWhere: { active: false } })
125
- declare findDisabled: () => Promise<User[]>;
126
-
127
- // Proxy to another method
128
- @DynamicMethod<"User">({ proxyTo: "findOneByEmail", whereType: "overwrite" })
129
- declare findInternalByEmail: (email: string) => Promise<User | null>;
130
- }
131
- ```
132
-
133
- The method **name** determines the behavior (same rules as the functional approach). The decorator config object adjusts that behavior.
134
-
135
- > **Important:** Always use `declare` (not a regular property) for decorated fields. The decorator provides runtime metadata; the `declare` keyword tells TypeScript the property exists without emitting initialization code.
136
-
137
- ---
138
-
139
- ## Decorator config options
140
-
141
- The `@DynamicMethod<M>()` decorator accepts an optional config object:
142
-
143
- ```typescript
144
- @DynamicMethod<"User">({
145
- proxyTo: "findByEmail", // Delegate to another method pattern
146
- whereType: "overwrite", // "extending" (default) or "overwrite"
147
- pushWhere: { active: false }, // Extra where clause
148
- injectOrdering: [{ name: "asc" }], // Fixed ordering
149
- injectPagination: { skip: 0, take: 10 }, // Fixed pagination
150
- })
151
- ```
152
-
153
- | Option | Type | Description |
154
- | ------------------ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------- |
155
- | `proxyTo` | `string` | Delegates to another valid method pattern (e.g. `"findOneByEmail"`) |
156
- | `whereType` | `"extending" \| "overwrite"` | `extending` combines with `requiredWhere`; `overwrite` ignores it |
157
- | `pushWhere` | `WhereModel<M>` | Extra `where` clause added on top of `requiredWhere` |
158
- | `injectOrdering` | `OrderingModel<M>` | Fixed ordering injected into the query |
159
- | `injectPagination` | `PaginationModel<M>` | Fixed pagination injected into the query |
160
- | `fbMode` | `"one" \| "list"` | **Deprecated.** Only relevant for `findBy`-prefixed methods. Use `findOneBy` instead if you want a single result. |
161
-
162
- ---
163
-
164
- ## The @QueryMethod decorator
165
-
166
- `@QueryMethod` declares a **raw SQL query method** on a `declare` class field, completely bypassing the name-based method parser used by `@DynamicMethod`. It's useful for complex queries (heavy joins, CTEs, database-specific functions) that aren't practical to express through the standard prefixes/suffixes.
167
-
168
- Under the hood, the SQL is executed through Prisma using `$queryRawUnsafe` (reads) or `$executeRawUnsafe` (writes), and the values passed in `args` are injected as **positional parameters** (`$1`, `$2`, ...) — the same prepared-statement technique Prisma itself uses. Values are never concatenated into the SQL string, which is what actually prevents SQL injection.
169
-
170
- ```typescript
171
- class UserRepository extends DynamicRepository<User, "User", string> {
172
- // Read query method (non-modifying) — return type comes from the field declaration
173
- @QueryMethod('SELECT * FROM "user" WHERE email = $1')
174
- declare findByEmailRaw: (arg: QueryMethodArg<[email: string]>) => Promise<User[]>;
175
-
176
- // Write query method — must always resolve to 'number'
177
- @QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
178
- declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
179
- }
180
-
181
- const userRepository = new UserRepository(prisma, { tableName: "user", pkName: "id" });
182
-
183
- const users = await userRepository.findByEmailRaw({ args: ["joao@email.com"] });
184
- const affected = await userRepository.activateUser({ args: ["1"] });
185
- ```
186
-
187
- > [!WARNING]
188
- > `$1`, `$2`, ... must always represent **values**, never column/table names or dynamic SQL fragments. Identifier names can't be passed as a positional parameter — if a method needs to vary those, build the SQL from a fixed, known set of options in your own code, never from untrusted input.
189
-
190
- Since `@QueryMethod` skips name parsing, there's no automatic type inference for the field: the method's parameter and return types come entirely from how you `declare` the field. Use `QueryMethodArg<T>` to type the single `{ args, db? }` argument the method receives.
191
-
192
- | Option | Type | Default | Description |
193
- | ---------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
194
- | `value` (1st argument) | `string` | — | **Required.** Raw SQL to execute. Use `$1`, `$2`, ... for the `args` placeholders. |
195
- | `options.modifying` | `boolean` | `false` | When `true`, runs via `$executeRawUnsafe`; the field must be declared to return `Promise<number>`. When `false`, runs via `$queryRawUnsafe`. |
196
-
197
- > [!NOTE]
198
- > `@QueryMethod` ignores every other dynamic-method concept — `requiredWhere`, `pushWhere`, `whereType`, `selectModels`/`includeModels`, `injectOrdering`, `injectPagination`. None of it applies here.
199
-
200
- ---
201
-
202
- ## Base methods
203
-
204
- All `DynamicRepository` instances automatically include these methods:
205
-
206
- | Method | Description |
207
- | --------------------- | -------------------------------------------------------------------------------------------------------- |
208
- | `get(pk)` | Fetch a record by primary key |
209
- | `getOrThrow(pk)` | Fetch by PK, throws if not found |
210
- | `getList(pks)` | Fetch multiple records by PKs |
211
- | `save(obj)` | Create or upsert a record |
212
- | `saveList(objs)` | Batch save in an automatic transaction |
213
- | `patch(pk, obj)` | Partial update by PK |
214
- | `patchList(tuples)` | Batch partial update via `[pk, obj]` tuples |
215
- | `merge(pk, obj)` | Fetch and deep-merge in memory (does not persist) |
216
- | `remove(pk)` | Delete a record by PK |
217
- | `removeList(pks)` | Batch delete by PKs |
218
- | `getAll()` | Fetch all records (respects `requiredWhere`). Accepts `pagination` and `order` in `options` — see below |
219
- | `total()` | Count all records |
220
- | `has(pk)` | Check if a record exists |
221
- | `softRemove(pk)` | Soft-delete (requires `softRemovekName` config) |
222
- | `softRemoveList(pks)` | Batch soft-delete |
223
- | `restore(pk)` | Restore soft-deleted record |
224
- | `restoreList(pks)` | Batch restore |
225
-
226
- All methods accept an optional `options` argument based on `DynamicMethodOptions` (`db`, `see`, `include`, `select`), but a few methods narrow it further:
227
-
228
- - **`getAll`** additionally accepts `pagination?: PaginationOptions` and `order?: OrderingModel<UName>` (falls back to `defaultOrdering` when omitted).
229
- - **`saveList` / `patchList`** omit `include` and `select`, and `db` only accepts a `DbTransaction` (the return of `prisma.$transaction`) — not the plain Prisma client.
230
- - **`removeList`, `total`, `has`** omit `include` and `select`.
231
- - **`softRemove`, `restore`** omit `see` (soft-delete visibility doesn't apply to the record being changed).
232
- - **`softRemoveList`, `restoreList`** omit `see`, `include`, and `select`.
233
-
234
- ```typescript
235
- // getAll with pagination and ordering
236
- const page = await userRepository.getAll({
237
- pagination: { skip: 0, take: 20 },
238
- order: { createdAt: "desc" },
239
- });
240
- ```
241
-
242
- ---
243
-
244
- ## Working with relations
245
-
246
- Configure relations in the constructor so `save` and `patch` manage them automatically:
247
-
248
- ```typescript
249
- class UserRepository extends DynamicRepository<
250
- User, "User", string,
251
- { address: true; posts: true }
252
- > {
253
- constructor(prisma: PrismaClient) {
254
- super(prisma, {
255
- tableName: "user",
256
- pkName: "id",
257
- relations: {
258
- address: { mode: "oto", pk: "id", restriction: "set" },
259
- posts: { mode: "otm", pk: "id", restriction: "add" },
260
- },
261
- });
262
- }
263
- }
264
- ```
265
-
266
- The fourth generic parameter (`WRelations`) is **optional** — it's only needed when you're configuring relations for the repository. When provided, it must flag which fields are relations. This ensures `DynamicSaveInput` and `DynamicPatchInput` resolve those fields into their nested Prisma create/update payload shapes. If your repository doesn't manage any relations, you can simply omit this parameter.
267
-
268
- **Relation modes:** `oto` (one-to-one), `otm` (one-to-many), `mto` (many-to-one), `mtm` (many-to-many).
269
-
270
- **Restrictions:** `set` (replace all) or `add` (add/update without removing).
271
-
272
- See the main [README.md](./README.md#relations-in-save) for full details on relation behavior.
273
-
274
- ---
275
-
276
- ## Transactions
277
-
278
- All methods accept `{ db: tx }` to participate in a transaction:
279
-
280
- ```typescript
281
- await userRepository.prisma.$transaction(async (tx) => {
282
- const user = await userRepository.save(
283
- { name: "Mary", email: "mary@email.com", password: "password" },
284
- { db: tx }
285
- );
286
-
287
- await postRepository.save(
288
- { title: "First Post", authorId: user.id },
289
- { db: tx }
290
- );
291
- });
292
- ```
293
-
294
- Access the Prisma client via `repository.prisma`.
295
-
296
- ---
297
-
298
- ## Working with includes
299
-
300
- `DynamicRepository` does not support `includeModels` (named presets), but you can use raw Prisma `include` via `DynamicMethodOptions`:
301
-
302
- ```typescript
303
- // Include address only
304
- const user = await userRepository.get(id, {
305
- include: { address: true },
306
- });
307
-
308
- // Nested include
309
- const userFull = await userRepository.get(id, {
310
- include: {
311
- address: true,
312
- posts: { include: { tags: true } },
313
- },
314
- });
315
-
316
- // Works on any method that accepts options
317
- const all = await userRepository.getAll({
318
- include: { posts: true },
319
- });
320
- ```
321
-
322
- ---
323
-
324
- ## DynamicMethodOptions
325
-
326
- Every base method and every decorated dynamic method accepts an optional second argument of type `DynamicMethodOptions`. This object has four optional fields:
327
-
328
- ```typescript
329
- type DynamicMethodOptions<TName extends PrismaModelName> = {
330
- db?: ClientOrTransaction; // Prisma client or transaction
331
- see?: "active" | "removed" | "all"; // Soft-delete visibility
332
- include?: IncludeModel<TName>; // Raw Prisma include
333
- select?: SelectModel<TName>; // Raw Prisma select
334
- };
335
- ```
336
-
337
- > `include` and `select` are mutually exclusive. Unlike the functional `setupVSRepo` API, `DynamicRepository`'s simpler options type doesn't enforce this at compile time — passing both raises a `VSRepoRuntimeError` at runtime.
338
-
339
- ### `db` — Using a transaction
340
-
341
- Pass `{ db: tx }` to route a single method call inside an existing transaction:
342
-
343
- ```typescript
344
- await userRepository.prisma.$transaction(async (tx) => {
345
- const user = await userRepository.save(
346
- { name: "Mary", email: "mary@email.com", password: "password" },
347
- { db: tx },
348
- );
349
-
350
- await postRepository.save(
351
- { title: "First Post", authorId: user.id },
352
- { db: tx },
353
- );
354
- });
355
- ```
356
-
357
- You can also use the transaction for reads:
358
-
359
- ```typescript
360
- await userRepository.prisma.$transaction(async (tx) => {
361
- const user = await userRepository.get(id, { db: tx });
362
- const total = await userRepository.total({ db: tx });
363
- });
364
- ```
365
-
366
- ### `see` — Soft-delete visibility
367
-
368
- If `softRemovekName` is configured, the `see` field controls which records are visible:
369
-
370
- | Value | Behavior |
371
- | -------------------- | ------------------------------- |
372
- | `"active"` (default) | Only non-deleted records |
373
- | `"removed"` | Only soft-deleted records |
374
- | `"all"` | Both active and deleted records |
375
-
376
- ```typescript
377
- // Fetch only soft-deleted users
378
- const deleted = await userRepository.getAll({ see: "removed" });
379
-
380
- // Fetch all users including soft-deleted
381
- const all = await userRepository.getAll({ see: "all" });
382
-
383
- // Restore a soft-deleted user by fetching it first
384
- const [removed] = await userRepository.getAll({ see: "removed", include: { address: true } });
385
- ```
386
-
387
- ### `include` — Raw Prisma include
388
-
389
- Use `include` to eagerly load relations in any method call. This is the equivalent of `includeModels` in the functional approach, but with raw Prisma include syntax:
390
-
391
- ```typescript
392
- // Simple include
393
- const user = await userRepository.get(id, {
394
- include: { address: true },
395
- });
396
-
397
- // Nested include
398
- const userFull = await userRepository.get(id, {
399
- include: {
400
- address: true,
401
- posts: { include: { author: true } },
402
- },
403
- });
404
-
405
- // Works on dynamic methods too
406
- const admin = await userRepository.findAdminByEmail(email, {
407
- include: { address: true, posts: true },
408
- });
409
- ```
410
-
411
- ### `select` — Raw Prisma select
412
-
413
- Use `select` to project a specific set of fields in any method call. This is the equivalent of `selectModels` in the functional approach, but with raw Prisma select syntax:
414
-
415
- ```typescript
416
- // Simple select
417
- const user = await userRepository.get(id, {
418
- select: { id: true, email: true },
419
- });
420
-
421
- // Works on dynamic methods too
422
- const admin = await userRepository.findAdminByEmail(email, {
423
- select: { id: true, email: true },
424
- });
425
- ```
426
-
427
- > Because `DynamicRepository` has no `selectModels`/`selectedModel`-driven type narrowing, the return type stays `TEntity` regardless of the `select` passed — the runtime result will only contain the selected fields, but TypeScript won't narrow it for you. Cast or destructure as needed.
428
-
429
- ### Combining options
430
-
431
- `db` and `see` can be freely combined with either `include` or `select` (but not both `include` and `select` together):
432
-
433
- ```typescript
434
- // Inside a transaction, fetch a user with relations, including soft-deleted
435
- await userRepository.prisma.$transaction(async (tx) => {
436
- const user = await userRepository.get(id, {
437
- db: tx,
438
- see: "all",
439
- include: { address: true, posts: true },
440
- });
441
- });
442
- ```
443
-
444
- ---
445
-
446
- ## NestJS integration
447
-
448
- `DynamicRepository` works naturally with NestJS dependency injection.
449
-
450
- ### Repository provider
451
-
452
- ```typescript
453
- // src/modules/user/user.repository.ts
454
- import { Injectable } from "@nestjs/common";
455
- import { PrismaService } from "../../database/prisma.service";
456
- import { DynamicRepository, DynamicMethod, DynamicMethodOptions } from "../../../generated/vsrepo";
457
-
458
- type User = /* Prisma UserGetPayload with relations */;
459
-
460
- @Injectable()
461
- class UserRepository extends DynamicRepository<User, "User", string, { profile: true }> {
462
- constructor(prisma: PrismaService) {
463
- super(prisma, {
464
- tableName: "user",
465
- pkName: "id",
466
- relations: {
467
- profile: { mode: "oto", pk: "id", restriction: "add" },
468
- },
469
- build: {
470
- baseMethods: {
471
- save: { ignoreRequiredWhere: true },
472
- },
473
- },
474
- });
475
- }
476
-
477
- @DynamicMethod()
478
- declare findByEmail: (email: string, options?: DynamicMethodOptions<"User">) => Promise<User | null>;
479
- }
480
-
481
- ```
482
-
483
- ### Registering the module
484
-
485
- ```typescript
486
- // src/modules/user/user.module.ts
487
- import { Module } from "@nestjs/common";
488
- import { UserRepository } from "./user.repository";
489
- import { UserService } from "./user.service";
490
- import { UserController } from "./user.controller";
491
-
492
- @Module({
493
- providers: [UserRepository, UserService],
494
- controllers: [UserController],
495
- exports: [UserService],
496
- })
497
- export class UserModule {}
498
- ```
499
-
500
- ### Using in a service
501
-
502
- ```typescript
503
- // src/modules/user/user.service.ts
504
- import { Injectable, Inject } from "@nestjs/common";
505
- import { UserRepository } from "./user.repository";
506
-
507
- @Injectable()
508
- export class UserService {
509
- constructor(
510
- private readonly userRepository: UserRepository,
511
- ) {}
512
-
513
- async getUserById(id: string) {
514
- return this.userRepository.get(id);
515
- }
516
-
517
- async getUserAuthByEmailWithProfile(email: string) {
518
- return this.userRepository.findByEmail(email, { include: { profile: true } });
519
- }
520
-
521
- async createUser(data: { email: string; password: string; name: string }) {
522
- return this.userRepository.save({
523
- email: data.email,
524
- password: data.password,
525
- name: data.name,
526
- });
527
- }
528
- }
529
- ```
530
-
531
- ---
532
-
533
- ## API Reference
534
-
535
- ### `DynamicRepository<TEntity, UName, VPKType, WRelations>`
536
-
537
- ```typescript
538
- abstract class DynamicRepository<
539
- TEntity extends object,
540
- UName extends PrismaModelName,
541
- VPKType,
542
- WRelations extends Partial<Record<keyof TEntity, true>> | undefined = undefined,
543
- >
544
- ```
545
-
546
- > `WRelations` is optional (defaults to `undefined`) and only needs to be provided when you're configuring the repository's relations.
547
-
548
- **Constructor:**
549
-
550
- ```typescript
551
- constructor(prisma: DbClient, config: DynamicRepositoryConstructorConfig<TEntity, UName>)
552
- ```
553
-
554
- ### DynamicRepositoryConstructorConfig
555
-
556
- | Property | Type | Description |
557
- | -------------------- | ------------------------------ | ------------------------------ |
558
- | `tableName` | `Uncapitalize<UName>` | Table name in Prisma |
559
- | `pkName` | `keyof TEntity` | Primary key field |
560
- | `softRemovekName?` | `keyof TEntity` | DateTime field for soft-delete |
561
- | `requiredWhere?` | `WhereModel<UName>` | Global filters |
562
- | `defaultOrdering?` | `OrderingModel<UName>` | Default ordering |
563
- | `relations?` | `RepositoryRelations<TEntity>` | Relation configuration |
564
- | `build?` | `DynamicRepositoryBuildConfig` | Build-time options |
565
-
566
- ### DynamicRepositoryBuildConfig
567
-
568
- | Property | Type | Description |
569
- | -------------- | --------------------------------------------------- | ------------------------------------- |
570
- | `showWorking?` | `boolean` | Show internal logs (default: `false`) |
571
- | `baseMethods?` | `Record<string, { ignoreRequiredWhere?: boolean }>` | Per-method config |
572
-
573
- ### @DynamicMethod\<M>(config?)
574
-
575
- ```typescript
576
- function DynamicMethod<M extends PrismaModelName>(
577
- config?: DynamicMethodConfig<M>,
578
- ): PropertyDecorator;
579
- ```
580
-
581
- ### DynamicMethodOptions\<TName>
582
-
583
- | Property | Type | Description |
584
- | ---------- | -------------------------------- | ------------------------------ |
585
- | `db?` | `ClientOrTransaction` | Database client or transaction |
586
- | `see?` | `"active" \| "removed" \| "all"` | Soft-delete visibility |
587
- | `include?` | `IncludeModel<TName>` | Raw Prisma include |
588
- | `select?` | `SelectModel<TName>` | Raw Prisma select |
589
-
590
- ### @QueryMethod(value, options?)
591
-
592
- ```typescript
593
- function QueryMethod(value: string, options?: QueryMethodOptions): PropertyDecorator;
594
- ```
595
-
596
- ### QueryMethodArg\<T>
597
-
598
- | Property | Type | Description |
599
- | -------- | --------------------- | -------------------------------------------------------------------------- |
600
- | `args` | `T` (tuple) | Positional parameters injected into the SQL placeholders (`$1`, `$2`, ...) |
601
- | `db?` | `ClientOrTransaction` | Transaction client to run this query in |
602
-
603
- ### QueryMethodOptions
604
-
605
- | Property | Type | Default | Description |
606
- | ---------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
607
- | `modifying?` | `boolean` | `false` | `true` executes via `$executeRawUnsafe` (field must return `Promise<number>`); `false` executes via `$queryRawUnsafe` |
608
-
609
- ---
610
-
611
- ## Differences from setupVSRepo
612
-
613
- | Aspect | `setupVSRepo` | `DynamicRepository` |
614
- | ----------------------- | -------------------------------------- | ----------------------------------------------------- |
615
- | **Style** | Functional / curried | OOP / class-based |
616
- | **Methods defined via** | `methods` config object | `@DynamicMethod()` decorators |
617
- | **selectModels** | Supported | Not supported |
618
- | **includeModels** | Supported | Not supported |
619
- | **Default select** | `defaultSelectModel` config | Not available |
620
- | **Build step** | Explicit `.build(prisma)` | Automatic in constructor |
621
- | **Base method toggles** | `active`, `defaultSelect` per method | Always active, no defaultSelect |
622
- | **Prisma instance** | Passed at `.build()` time | Passed to `super()` in constructor |
623
- | **Extensibility** | `.extend()` method | Class inheritance |
624
- | **Raw includes** | Via `options.include` | Via `DynamicMethodOptions.include` |
625
- | **Raw selects** | Via `options.select` (type-narrowed) | Via `DynamicMethodOptions.select` (not type-narrowed) |