vsrepo 1.3.6 → 1.3.8
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-DynamicRepo.md +625 -0
- package/README-DynamicRepo.pt-BR.md +625 -0
- package/package.json +6 -2
- package/scripts/configure-prisma-import.mjs +23 -10
|
@@ -0,0 +1,625 @@
|
|
|
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) |
|
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
# DynamicRepository (Abordagem baseada em classes)
|
|
2
|
+
|
|
3
|
+
🇧🇷 Você está lendo a versão em português. [🇺🇸 Read in English](./README-DynamicRepo.md)
|
|
4
|
+
|
|
5
|
+
O VSRepository oferece duas formas de criar repositórios: a abordagem funcional `setupVSRepo` e a abordagem OOP baseada em classes `DynamicRepository`. Este documento cobre a abordagem baseada em classes usando `DynamicRepository` e o decorator `@DynamicMethod`.
|
|
6
|
+
|
|
7
|
+
> Para a abordagem funcional, veja o [README.pt-BR.md](./README.pt-BR.md) principal.
|
|
8
|
+
|
|
9
|
+
## Sumário
|
|
10
|
+
|
|
11
|
+
- [Quando usar o DynamicRepository](#quando-usar-o-dynamicrepository)
|
|
12
|
+
- [Requisitos](#requisitos)
|
|
13
|
+
- [Criando uma classe](#criando-uma-classe)
|
|
14
|
+
- [O decorator @DynamicMethod](#o-decorator-dynamicmethod)
|
|
15
|
+
- [Opções de configuração do decorator](#opções-de-configuração-do-decorator)
|
|
16
|
+
- [O decorator @QueryMethod](#o-decorator-querymethod)
|
|
17
|
+
- [Métodos base](#métodos-base)
|
|
18
|
+
- [Trabalhando com relações](#trabalhando-com-relações)
|
|
19
|
+
- [Transações](#transações)
|
|
20
|
+
- [Trabalhando com includes](#trabalhando-com-includes)
|
|
21
|
+
- [DynamicMethodOptions](#dynamicmethodoptions)
|
|
22
|
+
- [Integração com NestJS](#integração-com-nestjs)
|
|
23
|
+
- [Referência da API](#referência-da-api)
|
|
24
|
+
- [Diferenças em relação ao setupVSRepo](#diferenças-em-relação-ao-setupvsrepo)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quando usar o DynamicRepository
|
|
29
|
+
|
|
30
|
+
Use `DynamicRepository` quando preferir um **estilo OOP com decorators** em vez da abordagem funcional `setupVSRepo`. Características principais:
|
|
31
|
+
|
|
32
|
+
- Métodos são definidos como campos `declare` com decorators `@DynamicMethod()`
|
|
33
|
+
- O repositório é uma classe que você pode estender e injetar via injeção de dependência
|
|
34
|
+
- `selectModels` e `includeModels` **não são suportados** (use `select`/`include` brutos via `DynamicMethodOptions`)
|
|
35
|
+
- Os métodos base estão sempre ativos (sem toggle `active` por método)
|
|
36
|
+
- O repositório é construído automaticamente no construtor (sem chamada explícita a `.build()`)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Requisitos
|
|
41
|
+
|
|
42
|
+
`DynamicRepository` depende dos decorators legados do TypeScript, então o `tsconfig.json` do seu projeto precisa ter:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"compilerOptions": {
|
|
47
|
+
"experimentalDecorators": true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`reflect-metadata` já é uma dependência do `vsrepo` e é importado internamente — você não precisa importá-lo manualmente.
|
|
53
|
+
|
|
54
|
+
Sem `experimentalDecorators: true`, o `@DynamicMethod()` não vai compilar (ou vai falhar silenciosamente ao registrar o método em tempo de execução, dependendo da sua ferramenta de build).
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Criando uma classe
|
|
59
|
+
|
|
60
|
+
Estenda `DynamicRepository` com quatro parâmetros genéricos:
|
|
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, // Tipo da entidade (com relações incluídas)
|
|
78
|
+
"User", // Nome do modelo no Prisma
|
|
79
|
+
string, // Tipo da chave primária
|
|
80
|
+
{ address: true; posts: true } // Quais campos são relações (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
|
+
**Parâmetros genéricos:**
|
|
103
|
+
|
|
104
|
+
| Parâmetro | Descrição |
|
|
105
|
+
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| `TEntity` | O tipo completo da entidade, incluindo as relações que você quer disponíveis |
|
|
107
|
+
| `UName` | O nome do modelo no Prisma como string literal (capitalizado, ex.: `"User"`) |
|
|
108
|
+
| `VPKType` | O tipo da chave primária (`string`, `number`, etc.) |
|
|
109
|
+
| `WRelations` *(opcional)* | Um objeto de flags indicando quais campos são relações (ex.: `{ address: true }`). Só é necessário se você for configurar as relações do repository — caso contrário, pode ser omitido |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## O decorator @DynamicMethod
|
|
114
|
+
|
|
115
|
+
Declare métodos dinâmicos como campos de classe usando `declare` e decore-os com `@DynamicMethod()`:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
class UserRepository extends DynamicRepository<User, "User", string> {
|
|
119
|
+
// Método dinâmico simples - o nome determina o comportamento
|
|
120
|
+
@DynamicMethod()
|
|
121
|
+
declare findByEmail: (email: string) => Promise<User | null>;
|
|
122
|
+
|
|
123
|
+
// Com configuração do decorator
|
|
124
|
+
@DynamicMethod<"User">({ proxyTo: "findMany", pushWhere: { active: false } })
|
|
125
|
+
declare findDisabled: () => Promise<User[]>;
|
|
126
|
+
|
|
127
|
+
// Proxy para outro método
|
|
128
|
+
@DynamicMethod<"User">({ proxyTo: "findOneByEmail", whereType: "overwrite" })
|
|
129
|
+
declare findInternalByEmail: (email: string) => Promise<User | null>;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
O **nome** do método determina o comportamento (mesmas regras da abordagem funcional). O objeto de configuração do decorator ajusta esse comportamento.
|
|
134
|
+
|
|
135
|
+
> **Importante:** Sempre use `declare` (não uma propriedade comum) para campos decorados. O decorator fornece metadados em tempo de execução; a palavra-chave `declare` informa ao TypeScript que a propriedade existe sem emitir código de inicialização.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Opções de configuração do decorator
|
|
140
|
+
|
|
141
|
+
O decorator `@DynamicMethod<M>()` aceita um objeto de configuração opcional:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
@DynamicMethod<"User">({
|
|
145
|
+
proxyTo: "findByEmail", // Delega para outro padrão de método
|
|
146
|
+
whereType: "overwrite", // "extending" (padrão) ou "overwrite"
|
|
147
|
+
pushWhere: { active: false }, // Cláusula where extra
|
|
148
|
+
injectOrdering: [{ name: "asc" }], // Ordenação fixa
|
|
149
|
+
injectPagination: { skip: 0, take: 10 }, // Paginação fixa
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Opção | Tipo | Descrição |
|
|
154
|
+
| ------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
155
|
+
| `proxyTo` | `string` | Delega para outro padrão de método válido (ex.: `"findOneByEmail"`) |
|
|
156
|
+
| `whereType` | `"extending" \| "overwrite"` | `extending` combina com `requiredWhere`; `overwrite` o ignora |
|
|
157
|
+
| `pushWhere` | `WhereModel<M>` | Cláusula `where` extra adicionada além do `requiredWhere` |
|
|
158
|
+
| `injectOrdering` | `OrderingModel<M>` | Ordenação fixa injetada na query |
|
|
159
|
+
| `injectPagination` | `PaginationModel<M>` | Paginação fixa injetada na query |
|
|
160
|
+
| `fbMode` | `"one" \| "list"` | **Deprecated.** Relevante apenas para métodos com prefixo `findBy`. Use `findOneBy` em vez disso se quiser um resultado único. |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## O decorator @QueryMethod
|
|
165
|
+
|
|
166
|
+
`@QueryMethod` declara um **método de SQL bruto** em um campo de classe com `declare`, contornando totalmente o parser de nomes usado por `@DynamicMethod`. É útil para consultas complexas (joins pesados, CTEs, funções específicas do banco) que não são práticas de expressar com os prefixos/sufixos padrão.
|
|
167
|
+
|
|
168
|
+
Por baixo dos panos, a SQL é executada via Prisma usando `$queryRawUnsafe` (leitura) ou `$executeRawUnsafe` (escrita), e os valores passados em `args` são injetados como **parâmetros posicionais** (`$1`, `$2`, ...) — a mesma técnica de *prepared statements* usada pelo próprio Prisma. Os valores nunca são concatenados na string SQL, o que é o que efetivamente previne SQL Injection.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
class UserRepository extends DynamicRepository<User, "User", string> {
|
|
172
|
+
// Query method de leitura (não-modifying) — o tipo de retorno vem da declaração do campo
|
|
173
|
+
@QueryMethod('SELECT * FROM "user" WHERE email = $1')
|
|
174
|
+
declare findByEmailRaw: (arg: QueryMethodArg<[email: string]>) => Promise<User[]>;
|
|
175
|
+
|
|
176
|
+
// Query method de escrita — deve sempre resolver para '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 usuarios = await userRepository.findByEmailRaw({ args: ["joao@email.com"] });
|
|
184
|
+
const afetados = await userRepository.activateUser({ args: ["1"] });
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
> [!WARNING]
|
|
188
|
+
> `$1`, `$2`, ... devem sempre representar **valores**, nunca nomes de colunas/tabelas ou trechos de SQL dinâmicos. Nomes de identificadores não podem ser passados como parâmetro posicional — se um método precisar variar isso, monte a SQL a partir de um conjunto fixo e conhecido de opções no seu próprio código, nunca a partir de entrada não confiável.
|
|
189
|
+
|
|
190
|
+
Como `@QueryMethod` pula o parsing de nome, não há inferência automática de tipos para o campo: os tipos de parâmetro e retorno do método vêm inteiramente de como você declara o campo com `declare`. Use `QueryMethodArg<T>` para tipar o único argumento `{ args, db? }` recebido pelo método.
|
|
191
|
+
|
|
192
|
+
| Opção | Tipo | Padrão | Descrição |
|
|
193
|
+
| ---------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
194
|
+
| `value` (1º argumento) | `string` | — | **Obrigatório.** SQL bruto a ser executado. Use `$1`, `$2`, ... para os placeholders de `args`. |
|
|
195
|
+
| `options.modifying` | `boolean` | `false` | Quando `true`, executa via `$executeRawUnsafe`; o campo deve ser declarado retornando `Promise<number>`. Quando `false`, executa via `$queryRawUnsafe`. |
|
|
196
|
+
|
|
197
|
+
> [!NOTE]
|
|
198
|
+
> `@QueryMethod` ignora todos os outros conceitos de método dinâmico — `requiredWhere`, `pushWhere`, `whereType`, `selectModels`/`includeModels`, `injectOrdering`, `injectPagination`. Nada disso se aplica aqui.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Métodos base
|
|
203
|
+
|
|
204
|
+
Todas as instâncias de `DynamicRepository` incluem automaticamente estes métodos:
|
|
205
|
+
|
|
206
|
+
| Método | Descrição |
|
|
207
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
208
|
+
| `get(pk)` | Busca um registro pela chave primária |
|
|
209
|
+
| `getOrThrow(pk)` | Busca pela PK, lança erro se não encontrado |
|
|
210
|
+
| `getList(pks)` | Busca múltiplos registros pelas PKs |
|
|
211
|
+
| `save(obj)` | Cria ou faz upsert de um registro |
|
|
212
|
+
| `saveList(objs)` | Salva em lote em uma transação automática |
|
|
213
|
+
| `patch(pk, obj)` | Atualização parcial pela PK |
|
|
214
|
+
| `patchList(tuples)` | Atualização parcial em lote via tuplas `[pk, obj]` |
|
|
215
|
+
| `merge(pk, obj)` | Busca e faz deep-merge em memória (não persiste) |
|
|
216
|
+
| `remove(pk)` | Apaga um registro pela PK |
|
|
217
|
+
| `removeList(pks)` | Apaga em lote pelas PKs |
|
|
218
|
+
| `getAll()` | Busca todos os registros (respeita `requiredWhere`). Aceita `pagination` e `order` em `options` — veja abaixo |
|
|
219
|
+
| `total()` | Conta todos os registros |
|
|
220
|
+
| `has(pk)` | Verifica se um registro existe |
|
|
221
|
+
| `softRemove(pk)` | Soft-delete (requer configuração de `softRemovekName`) |
|
|
222
|
+
| `softRemoveList(pks)` | Soft-delete em lote |
|
|
223
|
+
| `restore(pk)` | Restaura registro com soft-delete |
|
|
224
|
+
| `restoreList(pks)` | Restauração em lote |
|
|
225
|
+
|
|
226
|
+
Todos os métodos aceitam um argumento opcional `options` baseado em `DynamicMethodOptions` (`db`, `see`, `include`, `select`), mas alguns métodos restringem esse tipo:
|
|
227
|
+
|
|
228
|
+
- **`getAll`** aceita adicionalmente `pagination?: PaginationOptions` e `order?: OrderingModel<UName>` (usa `defaultOrdering` quando omitido).
|
|
229
|
+
- **`saveList` / `patchList`** omitem `include` e `select`, e `db` só aceita uma `DbTransaction` (o retorno de `prisma.$transaction`) — não o client Prisma comum.
|
|
230
|
+
- **`removeList`, `total`, `has`** omitem `include` e `select`.
|
|
231
|
+
- **`softRemove`, `restore`** omitem `see` (a visibilidade de soft-delete não se aplica ao registro sendo alterado).
|
|
232
|
+
- **`softRemoveList`, `restoreList`** omitem `see`, `include` e `select`.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// getAll com paginação e ordenação
|
|
236
|
+
const page = await userRepository.getAll({
|
|
237
|
+
pagination: { skip: 0, take: 20 },
|
|
238
|
+
order: { createdAt: "desc" },
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Trabalhando com relações
|
|
245
|
+
|
|
246
|
+
Configure as relações no construtor para que `save` e `patch` as gerenciem automaticamente:
|
|
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
|
+
O quarto parâmetro genérico (`WRelations`) é **opcional** — só é necessário quando você for configurar as relações do repository. Quando fornecido, ele deve sinalizar quais campos são relações. Isso garante que `DynamicSaveInput` e `DynamicPatchInput` resolvam esses campos para o formato de payload aninhado de create/update do Prisma. Se o seu repository não gerenciar nenhuma relação, você pode simplesmente omitir esse parâmetro.
|
|
267
|
+
|
|
268
|
+
**Modos de relação:** `oto` (um-para-um), `otm` (um-para-muitos), `mto` (muitos-para-um), `mtm` (muitos-para-muitos).
|
|
269
|
+
|
|
270
|
+
**Restrições:** `set` (substitui tudo) ou `add` (adiciona/atualiza sem remover).
|
|
271
|
+
|
|
272
|
+
Veja o [README.pt-BR.md](./README.pt-BR.md#relações-no-save) principal para todos os detalhes sobre o comportamento das relações.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Transações
|
|
277
|
+
|
|
278
|
+
Todos os métodos aceitam `{ db: tx }` para participar de uma transação:
|
|
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
|
+
Acesse o client do Prisma via `repository.prisma`.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Trabalhando com includes
|
|
299
|
+
|
|
300
|
+
`DynamicRepository` não suporta `includeModels` (presets nomeados), mas você pode usar um `include` bruto do Prisma via `DynamicMethodOptions`:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// Incluir apenas address
|
|
304
|
+
const user = await userRepository.get(id, {
|
|
305
|
+
include: { address: true },
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Include aninhado
|
|
309
|
+
const userFull = await userRepository.get(id, {
|
|
310
|
+
include: {
|
|
311
|
+
address: true,
|
|
312
|
+
posts: { include: { tags: true } },
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Funciona em qualquer método que aceite options
|
|
317
|
+
const all = await userRepository.getAll({
|
|
318
|
+
include: { posts: true },
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## DynamicMethodOptions
|
|
325
|
+
|
|
326
|
+
Todo método base e todo método dinâmico decorado aceita um segundo argumento opcional do tipo `DynamicMethodOptions`. Esse objeto tem quatro campos opcionais:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
type DynamicMethodOptions<TName extends PrismaModelName> = {
|
|
330
|
+
db?: ClientOrTransaction; // Client ou transação do Prisma
|
|
331
|
+
see?: "active" | "removed" | "all"; // Visibilidade do soft-delete
|
|
332
|
+
include?: IncludeModel<TName>; // Include bruto do Prisma
|
|
333
|
+
select?: SelectModel<TName>; // Select bruto do Prisma
|
|
334
|
+
};
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
> `include` e `select` são mutuamente exclusivos. Diferente da API funcional `setupVSRepo`, o tipo de options mais simples do `DynamicRepository` não garante isso em tempo de compilação — passar os dois gera um `VSRepoRuntimeError` em tempo de execução.
|
|
338
|
+
|
|
339
|
+
### `db` — Usando uma transação
|
|
340
|
+
|
|
341
|
+
Passe `{ db: tx }` para direcionar uma única chamada de método para dentro de uma transação existente:
|
|
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
|
+
Você também pode usar a transação para leituras:
|
|
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` — Visibilidade do soft-delete
|
|
367
|
+
|
|
368
|
+
Se `softRemovekName` estiver configurado, o campo `see` controla quais registros ficam visíveis:
|
|
369
|
+
|
|
370
|
+
| Valor | Comportamento |
|
|
371
|
+
| ------------------- | -------------------------------- |
|
|
372
|
+
| `"active"` (padrão) | Apenas registros não removidos |
|
|
373
|
+
| `"removed"` | Apenas registros com soft-delete |
|
|
374
|
+
| `"all"` | Registros ativos e removidos |
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// Busca apenas usuários com soft-delete
|
|
378
|
+
const deleted = await userRepository.getAll({ see: "removed" });
|
|
379
|
+
|
|
380
|
+
// Busca todos os usuários, incluindo os com soft-delete
|
|
381
|
+
const all = await userRepository.getAll({ see: "all" });
|
|
382
|
+
|
|
383
|
+
// Restaura um usuário com soft-delete buscando-o primeiro
|
|
384
|
+
const [removed] = await userRepository.getAll({ see: "removed", include: { address: true } });
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### `include` — Include bruto do Prisma
|
|
388
|
+
|
|
389
|
+
Use `include` para carregar relações de forma antecipada (eager loading) em qualquer chamada de método. É o equivalente ao `includeModels` da abordagem funcional, mas com a sintaxe bruta de include do Prisma:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
// Include simples
|
|
393
|
+
const user = await userRepository.get(id, {
|
|
394
|
+
include: { address: true },
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Include aninhado
|
|
398
|
+
const userFull = await userRepository.get(id, {
|
|
399
|
+
include: {
|
|
400
|
+
address: true,
|
|
401
|
+
posts: { include: { author: true } },
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Também funciona em métodos dinâmicos
|
|
406
|
+
const admin = await userRepository.findAdminByEmail(email, {
|
|
407
|
+
include: { address: true, posts: true },
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### `select` — Select bruto do Prisma
|
|
412
|
+
|
|
413
|
+
Use `select` para projetar um conjunto específico de campos em qualquer chamada de método. É o equivalente ao `selectModels` da abordagem funcional, mas com a sintaxe bruta de select do Prisma:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// Select simples
|
|
417
|
+
const user = await userRepository.get(id, {
|
|
418
|
+
select: { id: true, email: true },
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Também funciona em métodos dinâmicos
|
|
422
|
+
const admin = await userRepository.findAdminByEmail(email, {
|
|
423
|
+
select: { id: true, email: true },
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
> Como o `DynamicRepository` não tem estreitamento de tipo orientado por `selectModels`/`selectedModel`, o tipo de retorno permanece `TEntity` independentemente do `select` passado — o resultado em tempo de execução conterá apenas os campos selecionados, mas o TypeScript não vai estreitar isso para você. Faça cast ou desestruture conforme necessário.
|
|
428
|
+
|
|
429
|
+
### Combinando opções
|
|
430
|
+
|
|
431
|
+
`db` e `see` podem ser combinados livremente com `include` ou `select` (mas não com os dois juntos):
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
// Dentro de uma transação, busca um usuário com relações, incluindo os com soft-delete
|
|
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
|
+
## Integração com NestJS
|
|
447
|
+
|
|
448
|
+
`DynamicRepository` funciona naturalmente com a injeção de dependência do NestJS.
|
|
449
|
+
|
|
450
|
+
### Provider do repositório
|
|
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 com relações */;
|
|
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
|
+
### Registrando o módulo
|
|
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
|
+
### Usando em um 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
|
+
## Referência da API
|
|
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` é opcional (o padrão é `undefined`) e só precisa ser informado quando você for configurar as relações do repository.
|
|
547
|
+
|
|
548
|
+
**Construtor:**
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
constructor(prisma: DbClient, config: DynamicRepositoryConstructorConfig<TEntity, UName>)
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### DynamicRepositoryConstructorConfig
|
|
555
|
+
|
|
556
|
+
| Propriedade | Tipo | Descrição |
|
|
557
|
+
| -------------------- | ------------------------------ | ------------------------------- |
|
|
558
|
+
| `tableName` | `Uncapitalize<UName>` | Nome da tabela no Prisma |
|
|
559
|
+
| `pkName` | `keyof TEntity` | Campo da chave primária |
|
|
560
|
+
| `softRemovekName?` | `keyof TEntity` | Campo DateTime para soft-delete |
|
|
561
|
+
| `requiredWhere?` | `WhereModel<UName>` | Filtros globais |
|
|
562
|
+
| `defaultOrdering?` | `OrderingModel<UName>` | Ordenação padrão |
|
|
563
|
+
| `relations?` | `RepositoryRelations<TEntity>` | Configuração de relações |
|
|
564
|
+
| `build?` | `DynamicRepositoryBuildConfig` | Opções de build |
|
|
565
|
+
|
|
566
|
+
### DynamicRepositoryBuildConfig
|
|
567
|
+
|
|
568
|
+
| Propriedade | Tipo | Descrição |
|
|
569
|
+
| -------------- | --------------------------------------------------- | ------------------------------------- |
|
|
570
|
+
| `showWorking?` | `boolean` | Exibe logs internos (padrão: `false`) |
|
|
571
|
+
| `baseMethods?` | `Record<string, { ignoreRequiredWhere?: boolean }>` | Configuração por método |
|
|
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
|
+
| Propriedade | Tipo | Descrição |
|
|
584
|
+
| ----------- | -------------------------------- | ------------------------------------- |
|
|
585
|
+
| `db?` | `ClientOrTransaction` | Client ou transação do banco de dados |
|
|
586
|
+
| `see?` | `"active" \| "removed" \| "all"` | Visibilidade do soft-delete |
|
|
587
|
+
| `include?` | `IncludeModel<TName>` | Include bruto do Prisma |
|
|
588
|
+
| `select?` | `SelectModel<TName>` | Select bruto do Prisma |
|
|
589
|
+
|
|
590
|
+
### @QueryMethod(value, options?)
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
function QueryMethod(value: string, options?: QueryMethodOptions): PropertyDecorator;
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### QueryMethodArg\<T>
|
|
597
|
+
|
|
598
|
+
| Propriedade | Tipo | Descrição |
|
|
599
|
+
| ----------- | --------------------- | -------------------------------------------------------------------------- |
|
|
600
|
+
| `args` | `T` (tupla) | Parâmetros posicionais injetados nos placeholders da SQL (`$1`, `$2`, ...) |
|
|
601
|
+
| `db?` | `ClientOrTransaction` | Client de transação para executar essa query |
|
|
602
|
+
|
|
603
|
+
### QueryMethodOptions
|
|
604
|
+
|
|
605
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
606
|
+
|------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
607
|
+
| `modifying?` | `boolean` | `false` | `true` executa via `$executeRawUnsafe` (o campo deve retornar `Promise<number>`); `false` executa via `$queryRawUnsafe` |
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## Diferenças em relação ao setupVSRepo
|
|
612
|
+
|
|
613
|
+
| Aspecto | `setupVSRepo` | `DynamicRepository` |
|
|
614
|
+
| -------------------------- | ------------------------------------------------ | ------------------------------------------------------------- |
|
|
615
|
+
| **Estilo** | Funcional / curried | OOP / baseado em classes |
|
|
616
|
+
| **Métodos definidos via** | Objeto de configuração `methods` | Decorators `@DynamicMethod()` |
|
|
617
|
+
| **selectModels** | Suportado | Não suportado |
|
|
618
|
+
| **includeModels** | Suportado | Não suportado |
|
|
619
|
+
| **Select padrão** | Configuração `defaultSelectModel` | Não disponível |
|
|
620
|
+
| **Etapa de build** | `.build(prisma)` explícito | Automático no construtor |
|
|
621
|
+
| **Toggles de método base** | `active`, `defaultSelect` por método | Sempre ativo, sem defaultSelect |
|
|
622
|
+
| **Instância do Prisma** | Passada no `.build()` | Passada para `super()` no construtor |
|
|
623
|
+
| **Extensibilidade** | Método `.extend()` | Herança de classe |
|
|
624
|
+
| **Includes brutos** | Via `options.include` | Via `DynamicMethodOptions.include` |
|
|
625
|
+
| **Selects brutos** | Via `options.select` (com estreitamento de tipo) | Via `DynamicMethodOptions.select` (sem estreitamento de tipo) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vsrepo",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"description": "A repository pattern library for Prisma",
|
|
5
5
|
"homepage": "https://github.com/jaobrabo123/VSRepository#readme",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,11 @@
|
|
|
34
34
|
"type": "commonjs",
|
|
35
35
|
"files": [
|
|
36
36
|
"dist",
|
|
37
|
-
"scripts"
|
|
37
|
+
"scripts",
|
|
38
|
+
"README.md",
|
|
39
|
+
"README.pt-BR.md",
|
|
40
|
+
"README-DynamicRepo.md",
|
|
41
|
+
"README-DynamicRepo.pt-BR.md"
|
|
38
42
|
],
|
|
39
43
|
"bin": {
|
|
40
44
|
"vsrepo": "scripts/configure-prisma-import.mjs"
|
|
@@ -236,16 +236,29 @@ for (const file of tsFiles) {
|
|
|
236
236
|
console.log(`Gerado: ${path.relative(workspaceRoot, targetFile)}`);
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
// Copia
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
239
|
+
// Copia os READMEs da raiz do PACOTE vsrepo (nao do projeto do consumidor)
|
|
240
|
+
// para a pasta 'docs' do diretório de output.
|
|
241
|
+
const readmeFiles = [
|
|
242
|
+
'README.md',
|
|
243
|
+
'README.pt-BR.md',
|
|
244
|
+
'README-DynamicRepo.md',
|
|
245
|
+
'README-DynamicRepo.pt-BR.md',
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
const readmeOutputDir = path.join(outputDir, 'docs');
|
|
249
|
+
fs.mkdirSync(readmeOutputDir, { recursive: true });
|
|
250
|
+
|
|
251
|
+
for (const fileName of readmeFiles) {
|
|
252
|
+
const readmeSource = path.join(packageRoot, fileName);
|
|
253
|
+
const readmeTarget = path.join(readmeOutputDir, fileName);
|
|
254
|
+
|
|
255
|
+
if (fs.existsSync(readmeSource)) {
|
|
256
|
+
fs.copyFileSync(readmeSource, readmeTarget);
|
|
257
|
+
console.log(`Gerado: ${path.relative(workspaceRoot, readmeTarget)}`);
|
|
258
|
+
} else {
|
|
259
|
+
console.warn(`README nao encontrado no pacote em: ${readmeSource}. Ignorando a copia.`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
249
262
|
|
|
250
263
|
console.log('\nVSRepository gerado com tipagem do Prisma.');
|
|
251
264
|
console.log(`Output: ${path.relative(workspaceRoot, outputDir)}`);
|