@vsrepo/prisma7-adapter 1.3.2 → 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 +1 -10
- package/dist/parsers/order-by.parser.js +3 -16
- 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 -29
- package/dist/parsers/where.parser.js +49 -148
- 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.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
|
-
🇧🇷 [Ler em português](./README.pt-BR.md)
|
|
15
|
-
|
|
16
|
-
>
|
|
17
|
-
|
|
18
|
-
`VSRepoAdapter` implementation for [VSRepository
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Table of contents
|
|
23
|
-
|
|
24
|
-
- [Installation](#installation)
|
|
25
|
-
- [Basic usage](#basic-usage)
|
|
26
|
-
- [Constructor config](#constructor-config)
|
|
27
|
-
- [Relations](#relations)
|
|
28
|
-
- [The two `relations`](#the-two-relations)
|
|
29
|
-
- [`relations` in the constructor (write)](#relations-in-the-constructor-write)
|
|
30
|
-
- [`mode`](#mode)
|
|
31
|
-
- [`restriction`](#restriction)
|
|
32
|
-
- [`pk` and `nullable`](#pk-and-nullable)
|
|
33
|
-
- [How each write method resolves relations](#how-each-write-method-resolves-relations)
|
|
34
|
-
- [`relations` in method options (read)](#relations-in-method-options-read)
|
|
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, // optional — default: VSLogLevel.WARN
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
The
|
|
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
|
-
## Atomic and aggregation methods
|
|
201
|
-
|
|
202
|
-
The adapter implements the
|
|
203
|
-
|
|
204
|
-
- `incrementOne`/`decrementOne`/`multiplyOne`/`divideOne` translate directly into Prisma's native single-field atomic write — `data: { [field]: { increment: value } }` (and `decrement`/`multiply`/`divide`) — so the operation is evaluated **server-side** against the row's *current* value (`UPDATE ... SET field = field + value`), not as a fetch-then-save round trip on the client. Prisma's `update()` already returns the row reflecting the state *after* the write, so the adapter never issues a follow-up read.
|
|
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
|
+
🇧🇷 [Ler em português](./README.pt-BR.md)
|
|
15
|
+
|
|
16
|
+
> 🚧 **In development.** This is the `v2` of `@vsrepo/prisma7-adapter`, targeting [VSRepository v3](https://github.com/VSRepository/VSRepository). It's a development version, not a stable release yet, so its API may still change. For VSRepository v2, use the 1.x line (latest: `1.3.2`). It's currently the only officially supported adapter — adapters for other ORMs are planned but not published yet (see [Adapter status](https://github.com/VSRepository/VSRepository#adapter-status) in the VSRepository README).
|
|
17
|
+
|
|
18
|
+
`VSRepoAdapter` implementation for [VSRepository v3](https://github.com/VSRepository/VSRepository) backed by [Prisma 7](https://www.prisma.io/). It translates every `VSRepository` operation into Prisma Client calls, resolving `VSRepoWhere`, `Ordering`, `select`/`relations` through dedicated parsers, and — when a `relations` config is provided — resolving relation fields on `create`/`update`/`upsert`/`save`.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Table of contents
|
|
23
|
+
|
|
24
|
+
- [Installation](#installation)
|
|
25
|
+
- [Basic usage](#basic-usage)
|
|
26
|
+
- [Constructor config](#constructor-config)
|
|
27
|
+
- [Relations](#relations)
|
|
28
|
+
- [The two `relations`](#the-two-relations)
|
|
29
|
+
- [`relations` in the constructor (write)](#relations-in-the-constructor-write)
|
|
30
|
+
- [`mode`](#mode)
|
|
31
|
+
- [`restriction`](#restriction)
|
|
32
|
+
- [`pk` and `nullable`](#pk-and-nullable)
|
|
33
|
+
- [How each write method resolves relations](#how-each-write-method-resolves-relations)
|
|
34
|
+
- [`relations` in method options (read)](#relations-in-method-options-read)
|
|
35
|
+
- [Atomic and aggregation methods](#atomic-and-aggregation-methods)
|
|
36
|
+
- [`createMany`/`createManyReturning`/`updateMany`/`updateManyReturning` don't support nested writes](#createmanycreatemanyreturningupdatemanyupdatemanyreturning-dont-support-nested-writes)
|
|
37
|
+
- [Provider-specific limitations](#provider-specific-limitations)
|
|
38
|
+
- [Transactions](#transactions)
|
|
39
|
+
- [Logging](#logging)
|
|
40
|
+
- [Requirements](#requirements)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install vsrepo@dev @prisma/client @vsrepo/prisma7-adapter@dev
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
While `2.0.0` is in development, install both `vsrepo` and `@vsrepo/prisma7-adapter` with the `dev` tag.
|
|
51
|
+
|
|
52
|
+
## Basic usage
|
|
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 } }); // by primary key
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Here the `relations` you pass in the method `options` — shaped like `{ field: true }` — is transformed into a Prisma `include` by the adapter (see [`relations` in method options (read)](#relations-in-method-options-read)). If you supply `select`, the `relations`/`include` is ignored. Don't confuse it with the constructor-config `relations`, which describes how relation fields are resolved in write payloads — the difference is explained in [The two `relations`](#the-two-relations).
|
|
84
|
+
|
|
85
|
+
`Prisma7OrmTypes<DB>` ties `VSRepository`'s `getDbClient()`/`transaction()` return types to your real, generated Prisma types — see [Transactions](#transactions).
|
|
86
|
+
|
|
87
|
+
## Constructor config
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
new Prisma7Adapter(prisma, {
|
|
91
|
+
modelName: "user", // required — the Prisma Client model/delegate name, as in `prisma.user`
|
|
92
|
+
pkName: "id", // required — the entity's primary key field name
|
|
93
|
+
relations: { ... }, // optional — see "relations in the constructor (write)" below
|
|
94
|
+
provider: "postgresql", // optional — detected from the Prisma Client when omitted
|
|
95
|
+
logLevel: VSLogLevel.WARN, // optional — default: VSLogLevel.WARN
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
At construction, the adapter checks the Prisma Client and the model, and resolves the datasource provider:
|
|
100
|
+
|
|
101
|
+
- a `null`/`undefined` client throws a `VSRepoAdapterError` (code `MISSING_DB_CLIENT`);
|
|
102
|
+
- a `modelName` with no matching delegate on the client (`prisma.<modelName>`) throws `MODEL_NOT_FOUND`;
|
|
103
|
+
- the provider comes from `provider` when set; otherwise it's detected from the generated client (`_engineConfig.activeProvider`, falling back to the driver adapter's `provider`). If it can't be detected, construction throws `NOT_SUPPORTED` — set `provider` explicitly in that case (one of `postgresql`, `cockroachdb`, `mysql`, `sqlite`, `sqlserver`).
|
|
104
|
+
|
|
105
|
+
The other fields (`pkName`, `relations`, `logLevel`) are checked by TypeScript only, not at runtime.
|
|
106
|
+
|
|
107
|
+
## Relations
|
|
108
|
+
|
|
109
|
+
### The two `relations`
|
|
110
|
+
|
|
111
|
+
The name `relations` appears in **two different places** in the API, with **different shapes and purposes** — easy to confuse. In a nutshell:
|
|
112
|
+
|
|
113
|
+
| | `relations` in the **constructor** | `relations` in **method options** |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| Where you define it | `new Prisma7Adapter(prisma, { relations: ... })` | `repository.get(where, { relations: ... })` — and other methods |
|
|
116
|
+
| Shape | One **config object** per field: `{ mode, restriction, pk, nullable? }` | One **`true`/sub-object** per field: `{ posts: true }` |
|
|
117
|
+
| Purpose | **Write** — when a `create`/`update`/`upsert`/`save` payload contains a relation field, tells the adapter how to turn it into a Prisma nested write (`create`/`connectOrCreate`/`upsert`/`disconnect`/`deleteMany`/`set`) | **Read** — eager loading: which relations to fetch alongside the result (becomes a Prisma `include`) |
|
|
118
|
+
| Consumed by | `parsePrismaWriteData` (write resolver) | `parsePrismaInclude` (via `resolveReadArg`) |
|
|
119
|
+
| Depends on the other? | No — it only affects writes | No — it works even with no `relations` in the constructor |
|
|
120
|
+
|
|
121
|
+
Neither depends on the other: the method-options `relations` does eager loading even when the constructor has no `relations`, and the constructor `relations` governs write behavior even if you never pass `relations` in options. The two subsections below cover each one.
|
|
122
|
+
|
|
123
|
+
### `relations` in the constructor (write)
|
|
124
|
+
|
|
125
|
+
The `relations` config on the constructor describes, for each relation field of your entity, **how the adapter should resolve that field when it shows up in write payloads** (`create`/`update`/`upsert`/`save`).
|
|
126
|
+
|
|
127
|
+
Each relation is configured by its field name:
|
|
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
|
+
Without `relations`, every field — including relation fields — is passed straight through to Prisma's `where`/`data`/`create`/`update`, as-is. That's fine for scalar fields, but Prisma expects a very specific nested-write shape (`create`/`connectOrCreate`/`upsert`/`disconnect`/`deleteMany`/`set`) for relation fields, so if your entity has relations you'll usually want to configure them.
|
|
138
|
+
|
|
139
|
+
#### `mode`
|
|
140
|
+
|
|
141
|
+
Cardinality of the relation, from the point of view of the entity that owns the field:
|
|
142
|
+
|
|
143
|
+
| `mode` | Meaning | Field shape you send |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `oto` | one-to-one | a single object, or `null` |
|
|
146
|
+
| `mto` | many-to-one | a single object, or `null` |
|
|
147
|
+
| `otm` | one-to-many | an array of objects |
|
|
148
|
+
| `mtm` | many-to-many | an array of objects |
|
|
149
|
+
|
|
150
|
+
#### `restriction`
|
|
151
|
+
|
|
152
|
+
Controls how `save`/`update`/`upsert` handle relation items that already exist (matched by `pk`) and, for to-many relations, items that were **not** included in the payload:
|
|
153
|
+
|
|
154
|
+
- **`"add"`** — only creates/connects/upserts the items you send. Existing items that aren't in the payload are left untouched.
|
|
155
|
+
- **`"set"`** — same as `"add"`, but also removes what wasn't sent: for `otm` it runs a `deleteMany` on the items missing from the array; for `mtm` it resets the join (`set: []`) before reconnecting; for `oto`/`mto` with `nullable: true`, sending `null` deletes/disconnects the relation (see below).
|
|
156
|
+
|
|
157
|
+
#### `pk` and `nullable`
|
|
158
|
+
|
|
159
|
+
- **`pk`** — the field used to identify an existing related record. An item **with** `pk` in the payload becomes a `connectOrCreate`/`upsert`; an item **without** it becomes a plain `create`.
|
|
160
|
+
- **`nullable`** — relevant for `oto`/`mto` to-one relations. When `true`, sending `null` for the field resolves to `disconnect` on update (`delete` instead for `oto` with `restriction: "set"` — an `oto` side can't just be "disconnected", the owned row is removed). When omitted/`false`, sending `null` for a to-one relation throws a `VSRepoAdapterError` (code `INVALID_DATA`) telling you the relation is not nullable.
|
|
161
|
+
|
|
162
|
+
#### How each write method resolves relations
|
|
163
|
+
|
|
164
|
+
| Method | Relations |
|
|
165
|
+
| --- | --- |
|
|
166
|
+
| `create` | `create`/`connectOrCreate` only (no upsert — there's nothing to update yet) |
|
|
167
|
+
| `update` / `upsert` (`update` half) / `save` (upsert branch) | Full resolution: `create`/`connectOrCreate`/`upsert`/`disconnect`/`delete`/`deleteMany`/`set`, per `mode`/`restriction` |
|
|
168
|
+
| `createMany` / `createManyReturning` / `updateMany` / `updateManyReturning` | Not supported — throws a `VSRepoAdapterError` naming the offending field if the payload contains a configured relation |
|
|
169
|
+
|
|
170
|
+
### `relations` in method options (read)
|
|
171
|
+
|
|
172
|
+
This is the `relations` you pass in the `options` of a `VSRepository` method (`get`, `find`, `findOne`, etc.). The shape is much simpler: an object where each relation field accepts:
|
|
173
|
+
|
|
174
|
+
- `true` — load the relation as-is;
|
|
175
|
+
- or another `relations` object — for nested eager loading (relations of the relation).
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const user = await userRepository.get(
|
|
179
|
+
"...", // primary key
|
|
180
|
+
{
|
|
181
|
+
relations: {
|
|
182
|
+
posts: true, // fetch posts along
|
|
183
|
+
author: { profile: true }, // and, inside author, its profile (nested eager loading)
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
This object is turned into a Prisma `include` by the adapter (`parsePrismaInclude`). It does **not** use the constructor's `relations` config: it's purely a read-side option and works even with no `relations` in the config.
|
|
190
|
+
|
|
191
|
+
The `select` and `relations` you pass in the options are turned into a Prisma `select` (`parsePrismaSelect`) and a Prisma `include` (`parsePrismaInclude`), respectively. When `select` is provided, the `include` derived from `relations` is discarded (`undefined`), because Prisma doesn't allow combining `include` and `select` in a single query. In other words, if you're already using `select`, the `relations` object becomes redundant — so it's optional. Here's the resolved logic (from `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
|
+
## Atomic and aggregation methods
|
|
201
|
+
|
|
202
|
+
The adapter implements the 12 abstract methods `VSRepository`'s `increment`/`decrement`/`multiply`/`divide`, their `*Many` counterparts, and `sum`/`average`/`min`/`max` delegate to: `incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `incrementMany`, `decrementMany`, `multiplyMany`, `divideMany`, `sum`, `average`, `min`, `max`.
|
|
203
|
+
|
|
204
|
+
- `incrementOne`/`decrementOne`/`multiplyOne`/`divideOne` translate directly into Prisma's native single-field atomic write — `data: { [field]: { increment: value } }` (and `decrement`/`multiply`/`divide`) — so the operation is evaluated **server-side** against the row's *current* value (`UPDATE ... SET field = field + value`), not as a fetch-then-save round trip on the client. Prisma's `update()` already returns the row reflecting the state *after* the write, so the adapter never issues a follow-up read.
|
|
205
|
+
- `incrementMany`/`decrementMany`/`multiplyMany`/`divideMany` apply that same native write to **every** record matching `where`, through Prisma's `updateMany`, also evaluated server-side. They return a `{ count }` (`CountResult`) instead of the records.
|
|
206
|
+
- `sum`/`average`/`min`/`max` translate into Prisma's `aggregate()` with `_sum`/`_avg`/`_min`/`_max: { [field]: true }`. The raw result (`number`, `bigint`, a `Decimal` instance, or `null`) is normalized to `number | null` — `null` is returned as-is (mirroring SQL's `SUM()`/`AVG()`/`MIN()`/`MAX()`, which return `NULL`, not `0`, over an empty set), and a `Decimal`/`DecimalLike` value is converted via its `.toNumber()`.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Atomic — evaluated server-side, no 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 }); // e.g. a 10% price bump
|
|
213
|
+
await productRepository.divide("price", 2, { id: productId });
|
|
214
|
+
|
|
215
|
+
// Same, for every record matching `where` — returns `{ count }`:
|
|
216
|
+
await productRepository.incrementMany("stock", 10, { category: "books" });
|
|
217
|
+
|
|
218
|
+
// Aggregation — across every record matching an optional `where` (all if omitted):
|
|
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
|
+
**Decimal fields** (`Prisma.Decimal`) are fully supported as the `field` for all of these methods — `value` for the atomic methods is passed straight through to Prisma (which accepts a `number`, `string`, or `Decimal`/`DecimalJsLike`), and the aggregate methods always normalize the result to a `number`, never a `Decimal` instance.
|
|
226
|
+
|
|
227
|
+
## `createMany`/`createManyReturning`/`updateMany`/`updateManyReturning` don't support nested writes
|
|
228
|
+
|
|
229
|
+
`createMany`, `createManyReturning`, `updateMany` and `updateManyReturning` only accept scalar fields in their `data`. If your payload includes a field configured in `relations` (regardless of its value), the adapter throws a `VSRepoAdapterError` naming the offending field. For a full nested write, use `create`/`update`/`save` one record at a time, or wrap several `save` calls in a `saveMany`/`transaction`.
|
|
230
|
+
|
|
231
|
+
> Note on return order: `createManyReturning` and `updateManyReturning` don't guarantee the returned records follow the order of the input payload (`objs`). Without `select`/`relations` they are a single `createManyAndReturn`/`updateManyAndReturn` call; with them, the result comes from a second `findMany` (re-querying the inserted/updated rows by primary key, inside a transaction). Either way, don't rely on the order matching the input.
|
|
232
|
+
|
|
233
|
+
## Provider-specific limitations
|
|
234
|
+
|
|
235
|
+
Some features this adapter relies on aren't available on every database provider. The adapter checks every feature below upfront and throws a `VSRepoAdapterError` (code `NOT_SUPPORTED`) before touching the database.
|
|
236
|
+
|
|
237
|
+
| Feature | Supported providers | Not supported |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| `ignoreCase` (translated to Prisma's `mode: "insensitive"`; used by `findByXIgnoreCase` dynamic methods) | PostgreSQL, CockroachDB | MySQL, SQLite, SQL Server — MySQL and SQL Server are case-insensitive by default (depending on the collation), so no `mode` is needed there; SQLite is only case-insensitive for ASCII characters |
|
|
240
|
+
| `createManyReturning` / `updateManyReturning` (built on Prisma's `createManyAndReturn`/`updateManyAndReturn`) | PostgreSQL, CockroachDB, SQLite | MySQL, SQL Server |
|
|
241
|
+
| `ignoreConflicts: true` on `createMany` / `createManyReturning` (Prisma's `skipDuplicates`) | PostgreSQL, MySQL, CockroachDB | SQL Server, SQLite |
|
|
242
|
+
|
|
243
|
+
The `like` operator isn't supported on any provider — Prisma has no equivalent, so the adapter throws `NOT_SUPPORTED`. Use `contains`, `startsWith` or `endsWith` instead.
|
|
244
|
+
|
|
245
|
+
> These limitations come from Prisma itself, not from this adapter — see [Prisma's case sensitivity docs](https://www.prisma.io/docs/orm/v7/reference/prisma-client-reference#mode) and [CRUD reference](https://www.prisma.io/docs/orm/v7/prisma-client/queries/crud) for details. The adapter is covered by integration tests on PostgreSQL, MySQL, SQLite, SQL Server and CockroachDB.
|
|
246
|
+
|
|
247
|
+
## Transactions
|
|
248
|
+
|
|
249
|
+
**Every** method accepts `options.db` and runs its operation on the client/transaction you pass — the difference is in **how** each one treats it:
|
|
250
|
+
|
|
251
|
+
- Most methods (single, one-call Prisma operations) simply run directly on `options?.db`: hand them a transaction client and the call participates in that transaction, starting nothing new.
|
|
252
|
+
- `saveMany` and `deleteManyReturning` — and `updateManyReturning`/`createManyReturning` when you pass `select` or `relations` — need to run **more than one** Prisma operation atomically (`saveMany` saves each record individually; `updateManyReturning`/`createManyReturning` do an `updateManyAndReturn`/`createManyAndReturn` + `findMany`; `deleteManyReturning` does a `findMany` + `deleteMany`), so they go through `runTransactional`. Without `select`/`relations`, `updateManyReturning`/`createManyReturning` are a single Prisma call. If `options.db` is already an active transaction client, it's reused — no nested transaction is started; otherwise, a new transaction is created (on `options.db` if provided, or on the root client otherwise).
|
|
253
|
+
|
|
254
|
+
A transaction client is told apart from the root `PrismaClient` by the `$on` method: Prisma's root client exposes it (for event listeners), the interactive transaction client doesn't. This is what `Prisma7ClientLike`/`Prisma7OrmTypes` encode in their types.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
await userRepository.transaction(async tx => {
|
|
258
|
+
// Every repository call made with `{ db: tx }` inside this callback
|
|
259
|
+
// shares the same transaction — including calls to `saveMany`/
|
|
260
|
+
// `updateManyReturning`/`createManyReturning`/`deleteManyReturning`, which
|
|
261
|
+
// will detect `tx` is already a transaction and run directly on it instead
|
|
262
|
+
// of nesting a new one.
|
|
263
|
+
await userRepository.save(user, { db: tx });
|
|
264
|
+
await userRepository.save(otherUsers, { db: tx }); // an array saves several records
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Concurrency in `deleteManyReturning`
|
|
269
|
+
|
|
270
|
+
`deleteManyReturning` runs a `findMany` on the given `where` (to capture the records it will return) and then deletes **those same records by primary key** with a `deleteMany`, all inside one transaction. Prisma has no native `deleteManyAndReturn`, which is why it takes these two steps. If your `select` leaves the primary key out, the adapter adds it to the query internally and strips it from the returned records.
|
|
271
|
+
|
|
272
|
+
Since the delete is driven by the primary keys that were actually fetched, a row **inserted after** the `findMany` is never deleted by mistake. A concurrent change between the two steps can still make them diverge, though:
|
|
273
|
+
|
|
274
|
+
- a returned row that was **deleted by someone else** in the meantime is still returned, even though this call didn't delete it;
|
|
275
|
+
- a returned row that **stops matching** the `where` before the `deleteMany` runs is still deleted, since the `where` isn't re-applied.
|
|
276
|
+
|
|
277
|
+
If you need to guarantee there are no concurrency issues, run this method inside a `repository.transaction()` at a higher isolation level — e.g. `SERIALIZABLE`:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { VSRepoIsolationLevel } from "vsrepo";
|
|
281
|
+
|
|
282
|
+
await userRepository.transaction(async tx => {
|
|
283
|
+
// Atomic: no concurrent insert/update can slip between the findMany
|
|
284
|
+
// and the deleteMany, since SERIALIZABLE isolates this transaction.
|
|
285
|
+
// Always pass `{ db: tx }` so the call runs inside this transaction.
|
|
286
|
+
const deleted = await userRepository.deleteManyReturning({ id: { in: [...] } }, { db: tx });
|
|
287
|
+
}, {
|
|
288
|
+
isolationLevel: VSRepoIsolationLevel.SERIALIZABLE,
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
> The isolation level applies only when the repository call actually runs **inside** that transaction (i.e. with `{ db: tx }`). When `deleteManyReturning` starts its own internal transaction (no `options.db` passed), it falls back to the default isolation level.
|
|
293
|
+
|
|
294
|
+
## Logging
|
|
295
|
+
|
|
296
|
+
The adapter uses `VSLogger` (from `vsrepo`) internally: every method logs a `DEBUG` line with the resolved Prisma arg, and start/end performance logs. Set `logLevel` in the constructor config to control verbosity; it defaults to `VSLogLevel.WARN`.
|
|
297
|
+
|
|
298
|
+
## Requirements
|
|
299
|
+
|
|
300
|
+
- `vsrepo` ^3.0.0-dev.2
|
|
301
|
+
- `@prisma/client` ^7.10.0
|
|
302
|
+
- Node.js `>=22`
|
|
303
|
+
- A PostgreSQL, CockroachDB, MySQL, SQLite or SQL Server database (all covered by the integration tests)
|