@vsrepo/prisma7-adapter 1.3.1 → 2.0.0-dev
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/CHANGELOG.md +417 -0
- package/LICENSE +20 -20
- package/README.md +303 -299
- package/README.pt-BR.md +303 -299
- package/dist/constants/provider-capabilities.constant.d.ts +17 -0
- package/dist/constants/provider-capabilities.constant.js +27 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.js +1 -7
- package/dist/parsers/data.parser.d.ts +3 -33
- package/dist/parsers/data.parser.js +7 -45
- package/dist/parsers/include.parser.d.ts +1 -1
- package/dist/parsers/include.parser.js +3 -7
- package/dist/parsers/order-by.parser.d.ts +2 -10
- package/dist/parsers/order-by.parser.js +6 -19
- package/dist/parsers/select.parser.d.ts +1 -1
- package/dist/parsers/select.parser.js +3 -7
- package/dist/parsers/where.parser.d.ts +4 -28
- package/dist/parsers/where.parser.js +49 -151
- package/dist/prisma7.adapter.d.ts +104 -81
- package/dist/prisma7.adapter.js +329 -362
- package/dist/resolvers/map-prisma-error.resolver.d.ts +0 -11
- package/dist/resolvers/map-prisma-error.resolver.js +48 -72
- package/dist/resolvers/merge-entities.resolver.d.ts +11 -15
- package/dist/resolvers/merge-entities.resolver.js +29 -38
- package/dist/resolvers/resolve-provider.resolver.d.ts +2 -22
- package/dist/resolvers/resolve-provider.resolver.js +10 -33
- package/dist/types/adapter-config.type.d.ts +7 -12
- package/dist/types/adapter-config.type.js +1 -3
- package/dist/types/adapter-relations.type.d.ts +2 -2
- package/dist/types/adapter-relations.type.js +1 -3
- package/dist/types/plain-object.type.js +1 -3
- package/dist/types/prisma-arg-like.type.js +1 -3
- package/dist/types/prisma-client-models.type.d.ts +4 -4
- package/dist/types/prisma-client-models.type.js +1 -3
- package/dist/types/prisma-provider.type.d.ts +1 -1
- package/dist/types/prisma-provider.type.js +1 -3
- package/dist/types/prisma-repository-like.type.d.ts +1 -1
- package/dist/types/prisma-repository-like.type.js +1 -3
- package/dist/types/prisma7-client-like.type.d.ts +1 -8
- package/dist/types/prisma7-client-like.type.js +1 -3
- package/dist/types/prisma7-orm-types.type.d.ts +2 -2
- package/dist/types/prisma7-orm-types.type.js +1 -3
- package/dist/types/relation.type.d.ts +25 -19
- package/dist/types/relation.type.js +1 -3
- package/dist/validators/is-decimal.validator.d.ts +2 -5
- package/dist/validators/is-decimal.validator.js +3 -6
- package/dist/validators/is-plain-object.validator.d.ts +1 -1
- package/dist/validators/is-plain-object.validator.js +3 -7
- package/dist/validators/validate-prisma-client.validator.d.ts +1 -14
- package/dist/validators/validate-prisma-client.validator.js +7 -24
- package/package.json +108 -91
- package/dist/index.js.map +0 -1
- package/dist/parsers/data.parser.js.map +0 -1
- package/dist/parsers/include.parser.js.map +0 -1
- package/dist/parsers/order-by.parser.js.map +0 -1
- package/dist/parsers/select.parser.js.map +0 -1
- package/dist/parsers/where.parser.js.map +0 -1
- package/dist/prisma7.adapter.js.map +0 -1
- package/dist/resolvers/map-prisma-error.resolver.js.map +0 -1
- package/dist/resolvers/merge-entities.resolver.js.map +0 -1
- package/dist/resolvers/resolve-provider.resolver.js.map +0 -1
- package/dist/types/adapter-config.type.js.map +0 -1
- package/dist/types/adapter-relations.type.js.map +0 -1
- package/dist/types/plain-object.type.js.map +0 -1
- package/dist/types/prisma-arg-like.type.js.map +0 -1
- package/dist/types/prisma-client-models.type.js.map +0 -1
- package/dist/types/prisma-provider.type.js.map +0 -1
- package/dist/types/prisma-repository-like.type.js.map +0 -1
- package/dist/types/prisma7-client-like.type.js.map +0 -1
- package/dist/types/prisma7-orm-types.type.js.map +0 -1
- package/dist/types/relation.type.js.map +0 -1
- package/dist/validators/is-decimal.validator.js.map +0 -1
- package/dist/validators/is-plain-object.validator.js.map +0 -1
- package/dist/validators/validate-adapter-config.validator.d.ts +0 -8
- package/dist/validators/validate-adapter-config.validator.js +0 -78
- package/dist/validators/validate-adapter-config.validator.js.map +0 -1
- package/dist/validators/validate-prisma-client.validator.js.map +0 -1
package/README.pt-BR.md
CHANGED
|
@@ -1,299 +1,303 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
<img src="https://res.cloudinary.com/ddbfifdxd/image/upload/w_200,q_auto,f_auto/v1786386427/VS_logo_TextoAbaixo_yev4tq.png" alt="VSRepository Logo" width="200"/>
|
|
3
|
-
|
|
4
|
-
<p style="margin-top: 12px;">
|
|
5
|
-
<img src="https://img.shields.io/npm/v/@vsrepo/prisma7-adapter?style=flat-square" alt="npm version"/>
|
|
6
|
-
<img src="https://img.shields.io/npm/l/@vsrepo/prisma7-adapter?style=flat-square" alt="npm license"/>
|
|
7
|
-
<img src="https://img.shields.io/npm/dt/@vsrepo/prisma7-adapter?style=flat-square" alt="npm downloads"/>
|
|
8
|
-
<img src="https://img.shields.io/badge/inspired%20by-JpaRepository-E73121?style=flat-square" alt="inspired by JpaRepository"/>
|
|
9
|
-
</p>
|
|
10
|
-
</div>
|
|
11
|
-
|
|
12
|
-
# VSRepoPrisma7Adapter
|
|
13
|
-
|
|
14
|
-
🇺🇸 [Read in English](./README.md)
|
|
15
|
-
|
|
16
|
-
>
|
|
17
|
-
|
|
18
|
-
Implementação de `VSRepoAdapter` para o [VSRepository
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Sumário
|
|
23
|
-
|
|
24
|
-
- [Instalação](#instalação)
|
|
25
|
-
- [Uso básico](#uso-básico)
|
|
26
|
-
- [Config do construtor](#config-do-construtor)
|
|
27
|
-
- [Relations](#relations)
|
|
28
|
-
- [Os dois `relations`](#os-dois-relations)
|
|
29
|
-
- [`relations` no construtor (escrita)](#relations-no-construtor-escrita)
|
|
30
|
-
- [`mode`](#mode)
|
|
31
|
-
- [`restriction`](#restriction)
|
|
32
|
-
- [`pk` e `nullable`](#pk-e-nullable)
|
|
33
|
-
- [Como cada método de escrita resolve relations](#como-cada-método-de-escrita-resolve-relations)
|
|
34
|
-
- [`relations` nas options (leitura)](#relations-nas-options-leitura)
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
type
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
logLevel: VSLogLevel.WARN, // opcional — default: VSLogLevel.WARN
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
- **`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
## Métodos atômicos e de agregação
|
|
201
|
-
|
|
202
|
-
O adapter implementa os
|
|
203
|
-
|
|
204
|
-
- `incrementOne`/`decrementOne`/`multiplyOne`/`divideOne` traduzem direto pra escrita atômica nativa de campo único do Prisma — `data: { [field]: { increment: value } }` (e `decrement`/`multiply`/`divide`) — então a operação é avaliada **server-side** contra o valor *atual* do registro (`UPDATE ... SET field = field + value`), e não como um fetch-then-save no cliente. O `update()` do Prisma já retorna a linha refletindo o estado *após* a escrita, então o adapter nunca precisa fazer uma leitura extra.
|
|
205
|
-
- `
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
await
|
|
211
|
-
await
|
|
212
|
-
await productRepository.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://res.cloudinary.com/ddbfifdxd/image/upload/w_200,q_auto,f_auto/v1786386427/VS_logo_TextoAbaixo_yev4tq.png" alt="VSRepository Logo" width="200"/>
|
|
3
|
+
|
|
4
|
+
<p style="margin-top: 12px;">
|
|
5
|
+
<img src="https://img.shields.io/npm/v/@vsrepo/prisma7-adapter?style=flat-square" alt="npm version"/>
|
|
6
|
+
<img src="https://img.shields.io/npm/l/@vsrepo/prisma7-adapter?style=flat-square" alt="npm license"/>
|
|
7
|
+
<img src="https://img.shields.io/npm/dt/@vsrepo/prisma7-adapter?style=flat-square" alt="npm downloads"/>
|
|
8
|
+
<img src="https://img.shields.io/badge/inspired%20by-JpaRepository-E73121?style=flat-square" alt="inspired by JpaRepository"/>
|
|
9
|
+
</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
# VSRepoPrisma7Adapter
|
|
13
|
+
|
|
14
|
+
🇺🇸 [Read in English](./README.md)
|
|
15
|
+
|
|
16
|
+
> 🚧 **Em desenvolvimento.** Esta é a `v2` do `@vsrepo/prisma7-adapter`, feita para o [VSRepository v3](https://github.com/VSRepository/VSRepository). É uma versão de desenvolvimento, ainda não uma release estável, então a API pode mudar. Para o VSRepository v2, use a linha 1.x (última: `1.3.2`). Por enquanto é o único adapter oficialmente suportado — adapters para outros ORMs estão planejados, mas ainda não foram publicados (veja [Status dos adapters](https://github.com/VSRepository/VSRepository/blob/main/README.pt-BR.md#status-dos-adapters) no README do VSRepository).
|
|
17
|
+
|
|
18
|
+
Implementação de `VSRepoAdapter` para o [VSRepository v3](https://github.com/VSRepository/VSRepository) usando [Prisma 7](https://www.prisma.io/). Traduz toda operação do `VSRepository` em chamadas do Prisma Client, resolvendo `VSRepoWhere`, `Ordering`, `select`/`relations` através de parsers dedicados e — quando uma config de `relations` é fornecida — resolvendo campos de relação em `create`/`update`/`upsert`/`save`.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Sumário
|
|
23
|
+
|
|
24
|
+
- [Instalação](#instalação)
|
|
25
|
+
- [Uso básico](#uso-básico)
|
|
26
|
+
- [Config do construtor](#config-do-construtor)
|
|
27
|
+
- [Relations](#relations)
|
|
28
|
+
- [Os dois `relations`](#os-dois-relations)
|
|
29
|
+
- [`relations` no construtor (escrita)](#relations-no-construtor-escrita)
|
|
30
|
+
- [`mode`](#mode)
|
|
31
|
+
- [`restriction`](#restriction)
|
|
32
|
+
- [`pk` e `nullable`](#pk-e-nullable)
|
|
33
|
+
- [Como cada método de escrita resolve relations](#como-cada-método-de-escrita-resolve-relations)
|
|
34
|
+
- [`relations` nas options (leitura)](#relations-nas-options-leitura)
|
|
35
|
+
- [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação)
|
|
36
|
+
- [`createMany`/`createManyReturning`/`updateMany`/`updateManyReturning` não suportam nested writes](#createmanycreatemanyreturningupdatemanyupdatemanyreturning-não-suportam-nested-writes)
|
|
37
|
+
- [Limitações por provider](#limitações-por-provider)
|
|
38
|
+
- [Transactions](#transactions)
|
|
39
|
+
- [Logging](#logging)
|
|
40
|
+
- [Requisitos](#requisitos)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Instalação
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install vsrepo@dev @prisma/client @vsrepo/prisma7-adapter@dev
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Enquanto a `2.0.0` está em desenvolvimento, instale o `vsrepo` e o `@vsrepo/prisma7-adapter` com a tag `dev`.
|
|
51
|
+
|
|
52
|
+
## Uso básico
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { VSRepository, VSLogLevel } from "vsrepo";
|
|
56
|
+
import { Prisma7Adapter, Prisma7OrmTypes } from "@vsrepo/prisma7-adapter";
|
|
57
|
+
import { Prisma, PrismaClient } from "./generated/prisma/client";
|
|
58
|
+
import { prisma } from "./prisma";
|
|
59
|
+
|
|
60
|
+
type User = Prisma.UserGetPayload<{ include: { posts: true } }>;
|
|
61
|
+
type MyOrmTypes = Prisma7OrmTypes<PrismaClient>;
|
|
62
|
+
|
|
63
|
+
class UserRepository extends VSRepository<User, string, MyOrmTypes> {
|
|
64
|
+
constructor() {
|
|
65
|
+
super({
|
|
66
|
+
adapter: new Prisma7Adapter(prisma, {
|
|
67
|
+
modelName: "user",
|
|
68
|
+
pkName: "id",
|
|
69
|
+
relations: {
|
|
70
|
+
posts: { mode: "otm", restriction: "add", pk: "id" },
|
|
71
|
+
},
|
|
72
|
+
logLevel: VSLogLevel.WARN,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const userRepository = new UserRepository();
|
|
79
|
+
|
|
80
|
+
const user = await userRepository.get("...", { relations: { posts: true } }); // pela primary key
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Aqui, o `relations` passado no `options` do método — com a forma `{ campo: true }` — é transformado num `include` do Prisma pelo adapter (ver [`relations` nas options (leitura)](#relations-nas-options-leitura)). Se você fornecer `select`, o `relations`/`include` é ignorado. Não confunda com o `relations` da config do construtor (que descreve como campos de relação são resolvidos em escritas) — a diferença é explicada em [Os dois `relations`](#os-dois-relations).
|
|
84
|
+
|
|
85
|
+
`Prisma7OrmTypes<DB>` amarra os tipos de retorno de `getDbClient()`/`transaction()` do `VSRepository` aos seus tipos reais e gerados do Prisma — ver [Transactions](#transactions).
|
|
86
|
+
|
|
87
|
+
## Config do construtor
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
new Prisma7Adapter(prisma, {
|
|
91
|
+
modelName: "user", // obrigatório — nome do model/delegate do Prisma Client, como em `prisma.user`
|
|
92
|
+
pkName: "id", // obrigatório — nome do campo de primary key da entidade
|
|
93
|
+
relations: { ... }, // opcional — ver "relations no construtor (escrita)" abaixo
|
|
94
|
+
provider: "postgresql", // opcional — detectado a partir do Prisma Client quando omitido
|
|
95
|
+
logLevel: VSLogLevel.WARN, // opcional — default: VSLogLevel.WARN
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Na construção, o adapter confere o Prisma Client e o model, e resolve o provider do datasource:
|
|
100
|
+
|
|
101
|
+
- um client `null`/`undefined` lança um `VSRepoAdapterError` (code `MISSING_DB_CLIENT`);
|
|
102
|
+
- um `modelName` sem delegate correspondente no client (`prisma.<modelName>`) lança `MODEL_NOT_FOUND`;
|
|
103
|
+
- o provider vem do `provider`, quando informado; senão é detectado a partir do client gerado (`_engineConfig.activeProvider`, com fallback pro `provider` do driver adapter). Se não for detectável, a construção lança `NOT_SUPPORTED` — nesse caso, informe o `provider` explicitamente (um de `postgresql`, `cockroachdb`, `mysql`, `sqlite`, `sqlserver`).
|
|
104
|
+
|
|
105
|
+
Os outros campos (`pkName`, `relations`, `logLevel`) são checados só pelo TypeScript, não em runtime.
|
|
106
|
+
|
|
107
|
+
## Relations
|
|
108
|
+
|
|
109
|
+
### Os dois `relations`
|
|
110
|
+
|
|
111
|
+
O nome `relations` aparece em **dois lugares diferentes** da API, com **formas e propósitos diferentes** — fácil de confundir. Em resumo:
|
|
112
|
+
|
|
113
|
+
| | `relations` no **construtor** | `relations` nas **options** |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| Onde você define | `new Prisma7Adapter(prisma, { relations: ... })` | `repository.get(where, { relations: ... })` — e demais métodos |
|
|
116
|
+
| Formato | Um objeto de **configuração** por campo: `{ mode, restriction, pk, nullable? }` | Um objeto por campo **só com `true` ou sub-objeto**: `{ posts: true }` |
|
|
117
|
+
| Propósito | **Escrita** — quando um payload de `create`/`update`/`upsert`/`save` tem campo de relação, diz como transformá-lo num nested write do Prisma (`create`/`connectOrCreate`/`upsert`/`disconnect`/`deleteMany`/`set`) | **Leitura** — eager loading: quais relations trazer junto no resultado (vira um `include` do Prisma) |
|
|
118
|
+
| Quem consome | `parsePrismaWriteData` (resolver de escrita) | `parsePrismaInclude` (via `resolveReadArg`) |
|
|
119
|
+
| Precisa da outra? | Não — só afeta escritas | Não — funciona mesmo sem `relations` no construtor |
|
|
120
|
+
|
|
121
|
+
Nenhum depende do outro: o `relations` das options faz eager loading de leitura mesmo quando o construtor não tem `relations`; e o `relations` do construtor governa o comportamento de escrita mesmo que você nunca passe `relations` nas options. As duas subseções abaixo detalham cada um.
|
|
122
|
+
|
|
123
|
+
### `relations` no construtor (escrita)
|
|
124
|
+
|
|
125
|
+
A config `relations` do construtor descreve, para cada campo de relação da entidade, **como o adapter deve resolver esse campo quando ele aparecer nos payloads de escrita** (`create`/`update`/`upsert`/`save`).
|
|
126
|
+
|
|
127
|
+
Cada relation é configurada pelo nome do campo:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
relations: {
|
|
131
|
+
posts: { mode: "otm", restriction: "add", pk: "id" },
|
|
132
|
+
address: { mode: "oto", restriction: "set", pk: "id" },
|
|
133
|
+
author: { mode: "mto", restriction: "set", pk: "id", nullable: true },
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Sem `relations`, todo campo — incluindo campos de relação — é repassado direto pro `where`/`data`/`create`/`update` do Prisma, como está. Isso funciona bem pra campos escalares, mas o Prisma espera um formato bem específico de nested write (`create`/`connectOrCreate`/`upsert`/`disconnect`/`deleteMany`/`set`) pra campos de relação — então, se sua entidade tem relations, normalmente você vai querer configurá-las.
|
|
138
|
+
|
|
139
|
+
#### `mode`
|
|
140
|
+
|
|
141
|
+
Cardinalidade da relation, do ponto de vista da entidade dona do campo:
|
|
142
|
+
|
|
143
|
+
| `mode` | Significado | Formato do campo que você envia |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `oto` | one-to-one | um único objeto, ou `null` |
|
|
146
|
+
| `mto` | many-to-one | um único objeto, ou `null` |
|
|
147
|
+
| `otm` | one-to-many | um array de objetos |
|
|
148
|
+
| `mtm` | many-to-many | um array de objetos |
|
|
149
|
+
|
|
150
|
+
#### `restriction`
|
|
151
|
+
|
|
152
|
+
Controla como `save`/`update`/`upsert` tratam itens de relação que já existem (casados pela `pk`) e, pra relations to-many, itens que **não** vieram no payload:
|
|
153
|
+
|
|
154
|
+
- **`"add"`** — só cria/conecta/faz upsert dos itens enviados. Itens já existentes que não estão no payload permanecem intocados.
|
|
155
|
+
- **`"set"`** — igual a `"add"`, mas também remove o que não foi enviado: em `otm` roda um `deleteMany` nos itens que faltam no array; em `mtm` reseta o vínculo (`set: []`) antes de reconectar; em `oto`/`mto` com `nullable: true`, enviar `null` apaga/desconecta a relation (ver abaixo).
|
|
156
|
+
|
|
157
|
+
#### `pk` e `nullable`
|
|
158
|
+
|
|
159
|
+
- **`pk`** — o campo usado pra identificar um registro relacionado já existente. Um item **com** `pk` no payload vira um `connectOrCreate`/`upsert`; um item **sem** `pk` vira um `create` simples.
|
|
160
|
+
- **`nullable`** — relevante pras relações to-one `oto`/`mto`. Quando `true`, enviar `null` no campo resolve pra `disconnect` no update (`delete` no caso de `oto` com `restriction: "set"` — um lado `oto` não dá só pra "desconectar", a linha possuída é removida). Quando omitido/`false`, enviar `null` numa relação to-one lança um `VSRepoAdapterError` (code `INVALID_DATA`) avisando que a relation não é nullable.
|
|
161
|
+
|
|
162
|
+
#### Como cada método de escrita resolve relations
|
|
163
|
+
|
|
164
|
+
| Método | Relations |
|
|
165
|
+
| --- | --- |
|
|
166
|
+
| `create` | Só `create`/`connectOrCreate` (sem upsert — ainda não existe nada pra atualizar) |
|
|
167
|
+
| `update` / `upsert` (metade do `update`) / `save` (branch de upsert) | Resolução completa: `create`/`connectOrCreate`/`upsert`/`disconnect`/`delete`/`deleteMany`/`set`, conforme `mode`/`restriction` |
|
|
168
|
+
| `createMany` / `createManyReturning` / `updateMany` / `updateManyReturning` | Não suportado — lança um `VSRepoAdapterError` apontando o campo problemático se o payload tiver uma relation configurada |
|
|
169
|
+
|
|
170
|
+
### `relations` nas options (leitura)
|
|
171
|
+
|
|
172
|
+
Esse é o `relations` que você passa no `options` de um método do `VSRepository` (`get`, `find`, `findOne`, etc.). A forma é bem mais simples: um objeto onde cada campo de relação aceita:
|
|
173
|
+
|
|
174
|
+
- `true` — carrega a relation completa;
|
|
175
|
+
- ou outro objeto `relations` — para eager loading aninhado (relations da relation).
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const user = await userRepository.get(
|
|
179
|
+
"...", // primary key
|
|
180
|
+
{
|
|
181
|
+
relations: {
|
|
182
|
+
posts: true, // carrega os posts junto
|
|
183
|
+
author: { profile: true }, // e, dentro do author, o profile (eager loading aninhado)
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Esse objeto é transformado num `include` do Prisma pelo adapter (`parsePrismaInclude`). Ele **não** usa a config `relations` do construtor: é puramente uma opção de leitura e funciona mesmo sem `relations` na config.
|
|
190
|
+
|
|
191
|
+
O `select` e o `relations` que você passa nas options são transformados num `select` do Prisma (`parsePrismaSelect`) e num `include` (`parsePrismaInclude`), respectivamente. Quando o `select` é fornecido, o `include` derivado do `relations` é descartado (fica `undefined`), porque o Prisma não permite combinar `include` e `select` na mesma query. Ou seja, se você já estiver usando `select`, o objeto `relations` vira redundante — por isso ele é facultativo. Essa é a lógica resolvida (de `resolveReadArg`):
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const prismaSelect = options.select && parsePrismaSelect(options.select);
|
|
195
|
+
const prismaInclude = prismaSelect
|
|
196
|
+
? undefined
|
|
197
|
+
: options.relations && parsePrismaInclude(options.relations);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Métodos atômicos e de agregação
|
|
201
|
+
|
|
202
|
+
O adapter implementa os 12 métodos abstratos pros quais `increment`/`decrement`/`multiply`/`divide`, suas versões `*Many` e `sum`/`average`/`min`/`max` do `VSRepository` delegam: `incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `incrementMany`, `decrementMany`, `multiplyMany`, `divideMany`, `sum`, `average`, `min`, `max`.
|
|
203
|
+
|
|
204
|
+
- `incrementOne`/`decrementOne`/`multiplyOne`/`divideOne` traduzem direto pra escrita atômica nativa de campo único do Prisma — `data: { [field]: { increment: value } }` (e `decrement`/`multiply`/`divide`) — então a operação é avaliada **server-side** contra o valor *atual* do registro (`UPDATE ... SET field = field + value`), e não como um fetch-then-save no cliente. O `update()` do Prisma já retorna a linha refletindo o estado *após* a escrita, então o adapter nunca precisa fazer uma leitura extra.
|
|
205
|
+
- `incrementMany`/`decrementMany`/`multiplyMany`/`divideMany` aplicam essa mesma escrita nativa em **todo** registro que casa com `where`, através do `updateMany` do Prisma, também avaliada server-side. Eles retornam um `{ count }` (`CountResult`) em vez dos registros.
|
|
206
|
+
- `sum`/`average`/`min`/`max` traduzem pro `aggregate()` do Prisma, com `_sum`/`_avg`/`_min`/`_max: { [field]: true }`. O resultado bruto (`number`, `bigint`, uma instância de `Decimal`, ou `null`) é normalizado pra `number | null` — `null` é repassado como está (espelhando o `SUM()`/`AVG()`/`MIN()`/`MAX()` do SQL, que retornam `NULL`, não `0`, sobre um conjunto vazio), e um valor `Decimal`/`DecimalLike` é convertido via seu `.toNumber()`.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Atômico — avaliado server-side, sem read-modify-write:
|
|
210
|
+
await productRepository.increment("stock", 10, { id: productId });
|
|
211
|
+
await accountRepository.decrement("balance", 50, { id: accountId });
|
|
212
|
+
await productRepository.multiply("price", 1.1, { id: productId }); // ex: um reajuste de 10% no preço
|
|
213
|
+
await productRepository.divide("price", 2, { id: productId });
|
|
214
|
+
|
|
215
|
+
// O mesmo, pra todo registro que casa com `where` — retorna `{ count }`:
|
|
216
|
+
await productRepository.incrementMany("stock", 10, { category: "books" });
|
|
217
|
+
|
|
218
|
+
// Agregação — entre todos os registros que casam com um `where` opcional (todos se omitido):
|
|
219
|
+
const total = await productRepository.sum("price"); // number | null
|
|
220
|
+
const avgPrice = await productRepository.average("price", { active: true });
|
|
221
|
+
const cheapest = await productRepository.min("price");
|
|
222
|
+
const mostExpensive = await productRepository.max("price");
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Campos Decimal** (`Prisma.Decimal`) são totalmente suportados como o `field` de todos esses métodos — o `value` dos métodos atômicos é repassado direto pro Prisma (que aceita `number`, `string`, ou `Decimal`/`DecimalJsLike`), e os métodos de agregação sempre normalizam o resultado pra `number`, nunca uma instância de `Decimal`.
|
|
226
|
+
|
|
227
|
+
## `createMany`/`createManyReturning`/`updateMany`/`updateManyReturning` não suportam nested writes
|
|
228
|
+
|
|
229
|
+
`createMany`, `createManyReturning`, `updateMany` e `updateManyReturning` só aceitam campos escalares no `data`. Se seu payload incluir um campo configurado em `relations` (independente do valor), o adapter lança um `VSRepoAdapterError` apontando o campo problemático. Pra um nested write completo, use `create`/`update`/`save` registro por registro, ou envolva várias chamadas de `save` num `saveMany`/`transaction`.
|
|
230
|
+
|
|
231
|
+
> Nota sobre a ordem de retorno: `createManyReturning` e `updateManyReturning` não garantem que os registros devolvidos seguem a ordem do payload de entrada (`objs`). Sem `select`/`relations` eles são uma única chamada `createManyAndReturn`/`updateManyAndReturn`; com eles, o resultado vem de um segundo `findMany` (re-buscando as linhas inseridas/atualizadas pela primary key, dentro de uma transaction). Em qualquer caso, não conte com a ordem batendo com a da entrada.
|
|
232
|
+
|
|
233
|
+
## Limitações por provider
|
|
234
|
+
|
|
235
|
+
Alguns recursos que esse adapter usa não estão disponíveis em todo banco. O adapter checa todos os recursos abaixo antecipadamente e lança um `VSRepoAdapterError` (code `NOT_SUPPORTED`) antes de tocar no banco.
|
|
236
|
+
|
|
237
|
+
| Recurso | Providers com suporte | Sem suporte |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| `ignoreCase` (traduzido pro `mode: "insensitive"` do Prisma; usado pelos métodos dinâmicos `findByXIgnoreCase`) | PostgreSQL, CockroachDB | MySQL, SQLite, SQL Server — MySQL e SQL Server já são case-insensitive por padrão (conforme a collation), então não precisam de `mode`; SQLite só é case-insensitive pra caracteres ASCII |
|
|
240
|
+
| `createManyReturning` / `updateManyReturning` (baseados no `createManyAndReturn`/`updateManyAndReturn` do Prisma) | PostgreSQL, CockroachDB, SQLite | MySQL, SQL Server |
|
|
241
|
+
| `ignoreConflicts: true` no `createMany` / `createManyReturning` (o `skipDuplicates` do Prisma) | PostgreSQL, MySQL, CockroachDB | SQL Server, SQLite |
|
|
242
|
+
|
|
243
|
+
O operador `like` não é suportado em nenhum provider — o Prisma não tem equivalente, então o adapter lança `NOT_SUPPORTED`. Use `contains`, `startsWith` ou `endsWith`.
|
|
244
|
+
|
|
245
|
+
> Essas limitações vêm do próprio Prisma, não do adapter — veja a [doc de case sensitivity](https://www.prisma.io/docs/orm/v7/reference/prisma-client-reference#mode) e a [referência de CRUD](https://www.prisma.io/docs/orm/v7/prisma-client/queries/crud) do Prisma pra mais detalhes. O adapter é coberto por testes de integração em PostgreSQL, MySQL, SQLite, SQL Server e CockroachDB.
|
|
246
|
+
|
|
247
|
+
## Transactions
|
|
248
|
+
|
|
249
|
+
**Todos** os métodos aceitam `options.db` e executam a operação no client/transaction passado — a diferença está em **como** cada um o trata:
|
|
250
|
+
|
|
251
|
+
- A maioria (operações de uma única chamada Prisma) roda direto em `options?.db`: você passa um transaction client e a chamada participa da transaction, sem iniciar nada novo.
|
|
252
|
+
- `saveMany` e `deleteManyReturning` — e `updateManyReturning`/`createManyReturning` quando você passa `select` ou `relations` — precisam rodar **mais de uma operação** do Prisma atomicamente (`saveMany` salva cada registro individualmente; `updateManyReturning`/`createManyReturning` fazem um `updateManyAndReturn`/`createManyAndReturn` + `findMany`; `deleteManyReturning` faz um `findMany` + `deleteMany`), então passam por `runTransactional`. Sem `select`/`relations`, `updateManyReturning`/`createManyReturning` são uma única chamada do Prisma. Se `options.db` já for um transaction client ativo, ele é reaproveitado — nenhuma transaction aninhada é criada; caso contrário, uma nova transaction é criada (em `options.db`, se fornecido, ou no client raiz caso contrário).
|
|
253
|
+
|
|
254
|
+
Um transaction client é diferenciado do `PrismaClient` raiz pelo método `$on`: o client raiz do Prisma o expõe (pra event listeners), o transaction client interativo não. É isso que `Prisma7ClientLike`/`Prisma7OrmTypes` codificam nos seus tipos.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
await userRepository.transaction(async tx => {
|
|
258
|
+
// Toda chamada de repository feita com `{ db: tx }` dentro desse
|
|
259
|
+
// callback compartilha a mesma transaction — incluindo chamadas a
|
|
260
|
+
// `saveMany`/`updateManyReturning`/`createManyReturning`/`deleteManyReturning`,
|
|
261
|
+
// que vão detectar que `tx` já é uma transaction e rodar direto nele, sem
|
|
262
|
+
// aninhar uma nova.
|
|
263
|
+
await userRepository.save(user, { db: tx });
|
|
264
|
+
await userRepository.save(outrosUsuarios, { db: tx }); // um array salva vários registros
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Concorrência em `deleteManyReturning`
|
|
269
|
+
|
|
270
|
+
O `deleteManyReturning` roda um `findMany` no `where` informado (pra capturar os registros que vai devolver) e depois deleta **esses mesmos registros pela primary key** num `deleteMany`, tudo dentro de uma transaction. Como o Prisma não tem um `deleteManyAndReturn` nativo, ele precisa dessas duas etapas. Se o seu `select` deixar a primary key de fora, o adapter a adiciona internamente na query e a remove dos registros devolvidos.
|
|
271
|
+
|
|
272
|
+
Como o delete é guiado pelas primary keys que de fato foram buscadas, uma linha **inserida depois** do `findMany` nunca é deletada por engano. Ainda assim, uma alteração concorrente entre as duas etapas pode fazer os dois divergirem:
|
|
273
|
+
|
|
274
|
+
- uma linha devolvida que foi **deletada por outra pessoa** nesse meio-tempo continua sendo devolvida, mesmo não tendo sido deletada por essa chamada;
|
|
275
|
+
- uma linha devolvida que **deixa de bater** com o `where` antes do `deleteMany` rodar ainda é deletada, já que o `where` não é re-aplicado.
|
|
276
|
+
|
|
277
|
+
Se você quiser garantir que não haverá problemas de concorrência, rode esse método **dentro** de um `repository.transaction()` num nível de isolamento mais alto — ex.: `SERIALIZABLE`:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { VSRepoIsolationLevel } from "vsrepo";
|
|
281
|
+
|
|
282
|
+
await userRepository.transaction(async tx => {
|
|
283
|
+
// Atômico: nenhum insert/update concorrente consegue entrar entre o
|
|
284
|
+
// findMany e o deleteMany, pois o SERIALIZABLE isola esta transaction.
|
|
285
|
+
// Sempre passe `{ db: tx }` pra chamada rodar dentro desta transaction.
|
|
286
|
+
const deleted = await userRepository.deleteManyReturning({ id: { in: [...] } }, { db: tx });
|
|
287
|
+
}, {
|
|
288
|
+
isolationLevel: VSRepoIsolationLevel.SERIALIZABLE,
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
> O nível de isolamento só se aplica quando a chamada roda **dentro** daquela transaction (ou seja, com `{ db: tx }`). Quando o `deleteManyReturning` inicia a própria transaction interna (sem `options.db`), ele usa o nível de isolamento padrão.
|
|
293
|
+
|
|
294
|
+
## Logging
|
|
295
|
+
|
|
296
|
+
O adapter usa o `VSLogger` (de `vsrepo`) internamente: todo método loga uma linha `DEBUG` com o arg resolvido do Prisma, além de logs de performance de início/fim. Configure `logLevel` na config do construtor pra controlar a verbosidade; o default é `VSLogLevel.WARN`.
|
|
297
|
+
|
|
298
|
+
## Requisitos
|
|
299
|
+
|
|
300
|
+
- `vsrepo` ^3.0.0-dev.2
|
|
301
|
+
- `@prisma/client` ^7.10.0
|
|
302
|
+
- Node.js `>=22`
|
|
303
|
+
- Um banco PostgreSQL, CockroachDB, MySQL, SQLite ou SQL Server (todos cobertos pelos testes de integração)
|