@vsrepo/prisma7-adapter 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/README.pt-BR.md +233 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/parsers/data.parser.d.ts +34 -0
- package/dist/parsers/data.parser.js +134 -0
- package/dist/parsers/include.parser.d.ts +3 -0
- package/dist/parsers/include.parser.js +16 -0
- package/dist/parsers/order-by.parser.d.ts +15 -0
- package/dist/parsers/order-by.parser.js +38 -0
- package/dist/parsers/select.parser.d.ts +3 -0
- package/dist/parsers/select.parser.js +16 -0
- package/dist/parsers/where.parser.d.ts +31 -0
- package/dist/parsers/where.parser.js +187 -0
- package/dist/prisma7.adapter.d.ts +125 -0
- package/dist/prisma7.adapter.js +505 -0
- package/dist/resolvers/map-prisma-error.resolver.d.ts +13 -0
- package/dist/resolvers/map-prisma-error.resolver.js +100 -0
- package/dist/resolvers/merge-entities.resolver.d.ts +19 -0
- package/dist/resolvers/merge-entities.resolver.js +73 -0
- package/dist/types/adapter-config.type.d.ts +22 -0
- package/dist/types/adapter-config.type.js +2 -0
- package/dist/types/adapter-relations.type.d.ts +16 -0
- package/dist/types/adapter-relations.type.js +2 -0
- package/dist/types/plain-object.type.d.ts +1 -0
- package/dist/types/plain-object.type.js +2 -0
- package/dist/types/prisma-arg-like.type.d.ts +14 -0
- package/dist/types/prisma-arg-like.type.js +2 -0
- package/dist/types/prisma-repository-like.type.d.ts +20 -0
- package/dist/types/prisma-repository-like.type.js +2 -0
- package/dist/types/prisma7-client-like.type.d.ts +19 -0
- package/dist/types/prisma7-client-like.type.js +2 -0
- package/dist/types/prisma7-orm-types.type.d.ts +19 -0
- package/dist/types/prisma7-orm-types.type.js +2 -0
- package/dist/types/relation.type.d.ts +26 -0
- package/dist/types/relation.type.js +2 -0
- package/dist/validators/adapter-config.schema.d.ts +13 -0
- package/dist/validators/adapter-config.schema.js +45 -0
- package/dist/validators/is-decimal.validator.d.ts +5 -0
- package/dist/validators/is-decimal.validator.js +10 -0
- package/dist/validators/is-plain-object.validator.d.ts +2 -0
- package/dist/validators/is-plain-object.validator.js +11 -0
- package/dist/validators/relation.schema.d.ts +8 -0
- package/dist/validators/relation.schema.js +44 -0
- package/dist/validators/validate-adapter-config.validator.d.ts +8 -0
- package/dist/validators/validate-adapter-config.validator.js +54 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 João Pedro Azevedo Freire Mecenas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
> ✅ **Released.** This adapter is published to npm as `@vsrepo/prisma7-adapter`, targeting [VSRepository](https://github.com/jaobrabo123/VSRepository). It's currently the only officially published adapter for v2 — adapters for other ORMs are planned but not published yet (see [Adapter status](https://github.com/jaobrabo123/VSRepository#adapter-status) in the VSRepository README).
|
|
17
|
+
|
|
18
|
+
`VSRepoAdapter` implementation for [VSRepository v2](https://github.com/jaobrabo123/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`/`merge` the same way the v1 of VSRepository used to.
|
|
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
|
+
- [`merge`](#merge)
|
|
36
|
+
- [`createMany`/`createManyReturning`/`updateMany`/`updateManyReturning` don't support nested writes](#createmanycreatemanyreturningupdatemanyupdatemanyreturning-dont-support-nested-writes)
|
|
37
|
+
- [Transactions](#transactions)
|
|
38
|
+
- [Logging](#logging)
|
|
39
|
+
- [Requirements](#requirements)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install vsrepo @prisma/client @vsrepo/prisma7-adapter
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Both `vsrepo` and `@vsrepo/prisma7-adapter` are published to npm and ready to use.
|
|
50
|
+
|
|
51
|
+
## Basic usage
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { VSRepository, VSLogLevel } from "vsrepo";
|
|
55
|
+
import { VSRepoPrisma7Adapter, Prisma7OrmTypes } from "@vsrepo/prisma7-adapter";
|
|
56
|
+
import { Prisma, PrismaClient } from "./generated/prisma/client";
|
|
57
|
+
import { prisma } from "./prisma";
|
|
58
|
+
|
|
59
|
+
type User = Prisma.UserGetPayload<{ include: { posts: true } }>;
|
|
60
|
+
type MyOrmTypes = Prisma7OrmTypes<PrismaClient, Prisma.TransactionClient>;
|
|
61
|
+
|
|
62
|
+
class UserRepository extends VSRepository<User, string, MyOrmTypes> {
|
|
63
|
+
constructor() {
|
|
64
|
+
super({
|
|
65
|
+
adapter: new VSRepoPrisma7Adapter(prisma, {
|
|
66
|
+
tableName: "user",
|
|
67
|
+
pkName: "id",
|
|
68
|
+
relations: {
|
|
69
|
+
posts: { mode: "otm", restriction: "add", pk: "id" },
|
|
70
|
+
},
|
|
71
|
+
logLevel: VSLogLevel.WARN,
|
|
72
|
+
}),
|
|
73
|
+
pkName: "id",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const userRepository = new UserRepository();
|
|
79
|
+
|
|
80
|
+
const user = await userRepository.get({ id: "..." }, { relations: { posts: true } });
|
|
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, TX>` 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 VSRepoPrisma7Adapter(prisma, {
|
|
91
|
+
tableName: "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
|
+
logLevel: VSLogLevel.WARN, // optional — default: VSLogLevel.WARN
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The config is validated with [valibot](https://valibot.dev/) — an invalid `tableName`/`pkName`/`relations`/`logLevel` throws a `VSRepoPrisma7AdapterError` naming the offending field.
|
|
99
|
+
|
|
100
|
+
## Relations
|
|
101
|
+
|
|
102
|
+
### The two `relations`
|
|
103
|
+
|
|
104
|
+
The name `relations` appears in **two different places** in the API, with **different shapes and purposes** — easy to confuse. In a nutshell:
|
|
105
|
+
|
|
106
|
+
| | `relations` in the **constructor** | `relations` in **method options** |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| Where you define it | `new VSRepoPrisma7Adapter(prisma, { relations: ... })` | `repository.get(where, { relations: ... })` — and other methods |
|
|
109
|
+
| Shape | One **config object** per field: `{ mode, restriction, pk, nullable? }` | One **`true`/sub-object** per field: `{ posts: true }` |
|
|
110
|
+
| Purpose | **Write** — when a `create`/`update`/`upsert`/`save`/`merge` 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`) |
|
|
111
|
+
| Consumed by | `parsePrismaWriteData` / `mergeEntities` (write resolvers) | `parsePrismaInclude` (via `resolveReadArg`) |
|
|
112
|
+
| Depends on the other? | No — it only affects writes/`merge` | No — it works even with no `relations` in the constructor |
|
|
113
|
+
|
|
114
|
+
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.
|
|
115
|
+
|
|
116
|
+
### `relations` in the constructor (write)
|
|
117
|
+
|
|
118
|
+
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`/`merge`) — the same behavior the v1 of VSRepository had.
|
|
119
|
+
|
|
120
|
+
Each relation is configured by its field name:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
relations: {
|
|
124
|
+
posts: { mode: "otm", restriction: "add", pk: "id" },
|
|
125
|
+
address: { mode: "oto", restriction: "set", pk: "id" },
|
|
126
|
+
author: { mode: "mto", restriction: "set", pk: "id", nullable: true },
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
#### `mode`
|
|
133
|
+
|
|
134
|
+
Cardinality of the relation, from the point of view of the entity that owns the field:
|
|
135
|
+
|
|
136
|
+
| `mode` | Meaning | Field shape you send |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `oto` | one-to-one | a single object, or `null` |
|
|
139
|
+
| `mto` | many-to-one | a single object, or `null` |
|
|
140
|
+
| `otm` | one-to-many | an array of objects |
|
|
141
|
+
| `mtm` | many-to-many | an array of objects |
|
|
142
|
+
|
|
143
|
+
#### `restriction`
|
|
144
|
+
|
|
145
|
+
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:
|
|
146
|
+
|
|
147
|
+
- **`"add"`** — only creates/connects/upserts the items you send. Existing items that aren't in the payload are left untouched.
|
|
148
|
+
- **`"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` sending `null` disconnects/deletes the relation (see below).
|
|
149
|
+
|
|
150
|
+
#### `pk` and `nullable`
|
|
151
|
+
|
|
152
|
+
- **`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`.
|
|
153
|
+
- **`nullable`** — only relevant for `mto`. When `true`, sending `null` for the field resolves to `disconnect` on update. For `oto` with `restriction: "set"`, sending `null` resolves to `delete` instead (an `oto` side can't just be "disconnected", the owned row is removed).
|
|
154
|
+
|
|
155
|
+
#### How each write method resolves relations
|
|
156
|
+
|
|
157
|
+
| Method | Relations |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `create` | `create`/`connectOrCreate` only (no upsert — there's nothing to update yet) |
|
|
160
|
+
| `update` / `upsert` (`update` half) / `save` (upsert branch) | Full resolution: `create`/`connectOrCreate`/`upsert`/`disconnect`/`delete`/`deleteMany`/`set`, per `mode`/`restriction` |
|
|
161
|
+
| `createMany` / `createManyReturning` / `updateMany` / `updateManyReturning` | Not supported — throws a `VSRepoPrisma7AdapterError` naming the offending field if the payload contains a configured relation |
|
|
162
|
+
|
|
163
|
+
### `relations` in method options (read)
|
|
164
|
+
|
|
165
|
+
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:
|
|
166
|
+
|
|
167
|
+
- `true` — load the relation as-is;
|
|
168
|
+
- or another `relations` object — for nested eager loading (relations of the relation).
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const user = await userRepository.get(
|
|
172
|
+
{ id: "..." },
|
|
173
|
+
{
|
|
174
|
+
relations: {
|
|
175
|
+
posts: true, // fetch posts along
|
|
176
|
+
author: { profile: true }, // and, inside author, its profile (nested eager loading)
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
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.
|
|
183
|
+
|
|
184
|
+
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`):
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const prismaSelect = options.select && parsePrismaSelect(options.select);
|
|
188
|
+
const prismaInclude = prismaSelect
|
|
189
|
+
? undefined
|
|
190
|
+
: options.relations && parsePrismaInclude(options.relations);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## `merge`
|
|
194
|
+
|
|
195
|
+
`merge(where, obj, options)` fetches the record matching `where` and returns it **deep-merged, in memory**, with `obj` — it does **not** write anything to the database. This mirrors how `merge` worked in the v1 of VSRepository: it's meant to build a full, merged entity that you then pass to `save`/`update` yourself, not to persist a partial update directly.
|
|
196
|
+
|
|
197
|
+
For to-many relations (`otm`/`mtm`), items in the stored record and items in `obj` are matched by the configured `pk`: a match merges the two items, a new `pk` (or no `pk`) is appended. Nothing is ever removed by `merge`.
|
|
198
|
+
|
|
199
|
+
## `createMany`/`createManyReturning`/`updateMany`/`updateManyReturning` don't support nested writes
|
|
200
|
+
|
|
201
|
+
`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 `VSRepoPrisma7AdapterError` 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`.
|
|
202
|
+
|
|
203
|
+
> Note on return order: `createManyReturning` don't guarantee the returned records follow the order of the input payload (`objs`). Their result comes from a second `findMany` (re-querying the inserted/updated rows by primary key), so the order is only guaranteed when you pass `order` in the options.
|
|
204
|
+
|
|
205
|
+
## Transactions
|
|
206
|
+
|
|
207
|
+
**Every** method accepts `options.db` and runs its operation on the client/transaction you pass — the difference is in **how** each one treats it:
|
|
208
|
+
|
|
209
|
+
- 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.
|
|
210
|
+
- `saveMany`, `updateManyReturning`, `createManyReturning` and `deleteManyReturning` 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`. 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).
|
|
211
|
+
|
|
212
|
+
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.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
await userRepository.transaction(async tx => {
|
|
216
|
+
// Every repository call made with `{ db: tx }` inside this callback
|
|
217
|
+
// shares the same transaction — including calls to `saveMany`/
|
|
218
|
+
// `updateManyReturning`/`createManyReturning`/`deleteManyReturning`, which
|
|
219
|
+
// will detect `tx` is already a transaction and run directly on it instead
|
|
220
|
+
// of nesting a new one.
|
|
221
|
+
await userRepository.save(user, { db: tx });
|
|
222
|
+
await userRepository.saveList(otherUsers, { db: tx });
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Logging
|
|
227
|
+
|
|
228
|
+
The adapter uses `VSLogger` (from `vsrepo`) internally: every method logs a `DEBUG` line with the resolved Prisma arg, and start/end performance logs (surfacing a `WARN` for slow operations). Set `logLevel` in the constructor config to control verbosity; it defaults to `VSLogLevel.WARN`.
|
|
229
|
+
|
|
230
|
+
## Requirements
|
|
231
|
+
|
|
232
|
+
- `vsrepo` v2
|
|
233
|
+
- `@prisma/client` ^7.10.0
|
package/README.pt-BR.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
> ✅ **Lançado.** Este adapter já foi publicado no npm como `@vsrepo/prisma7-adapter`, feito para o [VSRepository](https://github.com/jaobrabo123/VSRepository). Por enquanto é o único adapter oficial publicado para a v2 — adapters para outros ORMs estão planejados, mas ainda não foram publicados (veja [Status dos adapters](https://github.com/jaobrabo123/VSRepository/blob/main/README.pt-BR.md#status-dos-adapters) no README do VSRepository).
|
|
17
|
+
|
|
18
|
+
Implementação de `VSRepoAdapter` para o [VSRepository v2](https://github.com/jaobrabo123/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`/`merge` da mesma forma que a v1 do VSRepository fazia.
|
|
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
|
+
- [`merge`](#merge)
|
|
36
|
+
- [`createMany`/`createManyReturning`/`updateMany`/`updateManyReturning` não suportam nested writes](#createmanycreatemanyreturningupdatemanyupdatemanyreturning-não-suportam-nested-writes)
|
|
37
|
+
- [Transactions](#transactions)
|
|
38
|
+
- [Logging](#logging)
|
|
39
|
+
- [Requisitos](#requisitos)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Instalação
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install vsrepo @prisma/client @vsrepo/prisma7-adapter
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Tanto o `vsrepo` quanto o `@vsrepo/prisma7-adapter` já foram publicados no npm e estão prontos para uso.
|
|
50
|
+
|
|
51
|
+
## Uso básico
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { VSRepository, VSLogLevel } from "vsrepo";
|
|
55
|
+
import { VSRepoPrisma7Adapter, Prisma7OrmTypes } from "@vsrepo/prisma7-adapter";
|
|
56
|
+
import { Prisma, PrismaClient } from "./generated/prisma/client";
|
|
57
|
+
import { prisma } from "./prisma";
|
|
58
|
+
|
|
59
|
+
type User = Prisma.UserGetPayload<{ include: { posts: true } }>;
|
|
60
|
+
type MyOrmTypes = Prisma7OrmTypes<PrismaClient, Prisma.TransactionClient>;
|
|
61
|
+
|
|
62
|
+
class UserRepository extends VSRepository<User, string, MyOrmTypes> {
|
|
63
|
+
constructor() {
|
|
64
|
+
super({
|
|
65
|
+
adapter: new VSRepoPrisma7Adapter(prisma, {
|
|
66
|
+
tableName: "user",
|
|
67
|
+
pkName: "id",
|
|
68
|
+
relations: {
|
|
69
|
+
posts: { mode: "otm", restriction: "add", pk: "id" },
|
|
70
|
+
},
|
|
71
|
+
logLevel: VSLogLevel.WARN,
|
|
72
|
+
}),
|
|
73
|
+
pkName: "id",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const userRepository = new UserRepository();
|
|
79
|
+
|
|
80
|
+
const user = await userRepository.get({ id: "..." }, { relations: { posts: true } });
|
|
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, TX>` 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 VSRepoPrisma7Adapter(prisma, {
|
|
91
|
+
tableName: "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
|
+
logLevel: VSLogLevel.WARN, // opcional — default: VSLogLevel.WARN
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A config é validada com [valibot](https://valibot.dev/) — um `tableName`/`pkName`/`relations`/`logLevel` inválido lança um `VSRepoPrisma7AdapterError` apontando o campo problemático.
|
|
99
|
+
|
|
100
|
+
## Relations
|
|
101
|
+
|
|
102
|
+
### Os dois `relations`
|
|
103
|
+
|
|
104
|
+
O nome `relations` aparece em **dois lugares diferentes** da API, com **formas e propósitos diferentes** — fácil de confundir. Em resumo:
|
|
105
|
+
|
|
106
|
+
| | `relations` no **construtor** | `relations` nas **options** |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| Onde você define | `new VSRepoPrisma7Adapter(prisma, { relations: ... })` | `repository.get(where, { relations: ... })` — e demais métodos |
|
|
109
|
+
| Formato | Um objeto de **configuração** por campo: `{ mode, restriction, pk, nullable? }` | Um objeto por campo **só com `true` ou sub-objeto**: `{ posts: true }` |
|
|
110
|
+
| Propósito | **Escrita** — quando um payload de `create`/`update`/`upsert`/`save`/`merge` 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) |
|
|
111
|
+
| Quem consome | `parsePrismaWriteData` / `mergeEntities` (resolvers de escrita) | `parsePrismaInclude` (via `resolveReadArg`) |
|
|
112
|
+
| Precisa da outra? | Não — só afeta escritas/`merge` | Não — funciona mesmo sem `relations` no construtor |
|
|
113
|
+
|
|
114
|
+
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.
|
|
115
|
+
|
|
116
|
+
### `relations` no construtor (escrita)
|
|
117
|
+
|
|
118
|
+
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`/`merge`) — o mesmo comportamento que a v1 do VSRepository tinha.
|
|
119
|
+
|
|
120
|
+
Cada relation é configurada pelo nome do campo:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
relations: {
|
|
124
|
+
posts: { mode: "otm", restriction: "add", pk: "id" },
|
|
125
|
+
address: { mode: "oto", restriction: "set", pk: "id" },
|
|
126
|
+
author: { mode: "mto", restriction: "set", pk: "id", nullable: true },
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
#### `mode`
|
|
133
|
+
|
|
134
|
+
Cardinalidade da relation, do ponto de vista da entidade dona do campo:
|
|
135
|
+
|
|
136
|
+
| `mode` | Significado | Formato do campo que você envia |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `oto` | one-to-one | um único objeto, ou `null` |
|
|
139
|
+
| `mto` | many-to-one | um único objeto, ou `null` |
|
|
140
|
+
| `otm` | one-to-many | um array de objetos |
|
|
141
|
+
| `mtm` | many-to-many | um array de objetos |
|
|
142
|
+
|
|
143
|
+
#### `restriction`
|
|
144
|
+
|
|
145
|
+
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:
|
|
146
|
+
|
|
147
|
+
- **`"add"`** — só cria/conecta/faz upsert dos itens enviados. Itens já existentes que não estão no payload permanecem intocados.
|
|
148
|
+
- **`"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` enviar `null` desconecta/apaga a relation (ver abaixo).
|
|
149
|
+
|
|
150
|
+
#### `pk` e `nullable`
|
|
151
|
+
|
|
152
|
+
- **`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.
|
|
153
|
+
- **`nullable`** — só relevante pra `mto`. Quando `true`, enviar `null` no campo resolve pra `disconnect` no update. Pra `oto` com `restriction: "set"`, enviar `null` resolve pra `delete` (um lado `oto` não dá só pra "desconectar", a linha possuída é removida).
|
|
154
|
+
|
|
155
|
+
#### Como cada método de escrita resolve relations
|
|
156
|
+
|
|
157
|
+
| Método | Relations |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `create` | Só `create`/`connectOrCreate` (sem upsert — ainda não existe nada pra atualizar) |
|
|
160
|
+
| `update` / `upsert` (metade do `update`) / `save` (branch de upsert) | Resolução completa: `create`/`connectOrCreate`/`upsert`/`disconnect`/`delete`/`deleteMany`/`set`, conforme `mode`/`restriction` |
|
|
161
|
+
| `createMany` / `createManyReturning` / `updateMany` / `updateManyReturning` | Não suportado — lança um `VSRepoPrisma7AdapterError` apontando o campo problemático se o payload tiver uma relation configurada |
|
|
162
|
+
|
|
163
|
+
### `relations` nas options (leitura)
|
|
164
|
+
|
|
165
|
+
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:
|
|
166
|
+
|
|
167
|
+
- `true` — carrega a relation completa;
|
|
168
|
+
- ou outro objeto `relations` — para eager loading aninhado (relations da relation).
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const user = await userRepository.get(
|
|
172
|
+
{ id: "..." },
|
|
173
|
+
{
|
|
174
|
+
relations: {
|
|
175
|
+
posts: true, // carrega os posts junto
|
|
176
|
+
author: { profile: true }, // e, dentro do author, o profile (eager loading aninhado)
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
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.
|
|
183
|
+
|
|
184
|
+
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`):
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const prismaSelect = options.select && parsePrismaSelect(options.select);
|
|
188
|
+
const prismaInclude = prismaSelect
|
|
189
|
+
? undefined
|
|
190
|
+
: options.relations && parsePrismaInclude(options.relations);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## `merge`
|
|
194
|
+
|
|
195
|
+
`merge(where, obj, options)` busca o registro que casa com `where` e devolve ele **deep-merged, em memória**, com `obj` — ele **não** escreve nada no banco. Isso reflete exatamente como o `merge` funcionava na v1 do VSRepository: ele serve pra montar uma entidade completa e mesclada, que você depois passa pro `save`/`update`, e não pra persistir um update parcial diretamente.
|
|
196
|
+
|
|
197
|
+
Pra relations to-many (`otm`/`mtm`), os itens do registro salvo e os itens de `obj` são casados pela `pk` configurada: um match faz merge dos dois itens, uma `pk` nova (ou sem `pk`) é apenas adicionada. `merge` nunca remove nada.
|
|
198
|
+
|
|
199
|
+
## `createMany`/`createManyReturning`/`updateMany`/`updateManyReturning` não suportam nested writes
|
|
200
|
+
|
|
201
|
+
`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 `VSRepoPrisma7AdapterError` 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`.
|
|
202
|
+
|
|
203
|
+
> Nota sobre a ordem de retorno: `createManyReturning` não garante que os registros devolvidos seguem a ordem do payload de entrada (`objs`). O resultado vem de um segundo `findMany` (re-buscando as linhas inseridas/atualizadas pela primary key), então a ordem só é garantida quando você passa `order` nas options.
|
|
204
|
+
|
|
205
|
+
## Transactions
|
|
206
|
+
|
|
207
|
+
**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:
|
|
208
|
+
|
|
209
|
+
- 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.
|
|
210
|
+
- `saveMany`, `updateManyReturning`, `createManyReturning` e `deleteManyReturning` 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`. 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).
|
|
211
|
+
|
|
212
|
+
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.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
await userRepository.transaction(async tx => {
|
|
216
|
+
// Toda chamada de repository feita com `{ db: tx }` dentro desse
|
|
217
|
+
// callback compartilha a mesma transaction — incluindo chamadas a
|
|
218
|
+
// `saveMany`/`updateManyReturning`/`createManyReturning`/`deleteManyReturning`,
|
|
219
|
+
// que vão detectar que `tx` já é uma transaction e rodar direto nele, sem
|
|
220
|
+
// aninhar uma nova.
|
|
221
|
+
await userRepository.save(user, { db: tx });
|
|
222
|
+
await userRepository.saveList(outrosUsuarios, { db: tx });
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Logging
|
|
227
|
+
|
|
228
|
+
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 (com `WARN` pra operações lentas). Configure `logLevel` na config do construtor pra controlar a verbosidade; o default é `VSLogLevel.WARN`.
|
|
229
|
+
|
|
230
|
+
## Requisitos
|
|
231
|
+
|
|
232
|
+
- `vsrepo` v2
|
|
233
|
+
- `@prisma/client` ^7.10.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { VSRepoPrisma7Adapter } from "./prisma7.adapter";
|
|
2
|
+
export type { Relation } from "./types/relation.type";
|
|
3
|
+
export type { Prisma7OrmTypes } from "./types/prisma7-orm-types.type";
|
|
4
|
+
export type { Prisma7ClientLike } from "./types/prisma7-client-like.type";
|
|
5
|
+
export type { AdapterRelations } from "./types/adapter-relations.type";
|
|
6
|
+
export type { VSRepoPrisma7AdapterConfig } from "./types/adapter-config.type";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VSRepoPrisma7Adapter = void 0;
|
|
4
|
+
var prisma7_adapter_1 = require("./prisma7.adapter");
|
|
5
|
+
Object.defineProperty(exports, "VSRepoPrisma7Adapter", { enumerable: true, get: function () { return prisma7_adapter_1.VSRepoPrisma7Adapter; } });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser que converte um `DeepPartial<T>` (formato "cru" vindo do vsrepo) nos
|
|
3
|
+
* payloads `create`/`update` esperados pelo Prisma para escritas com relations,
|
|
4
|
+
* replicando o comportamento da v1 (`resolveCreateUpdatePayloadsWithRelations`).
|
|
5
|
+
*
|
|
6
|
+
* Sem `relations` configurado no adapter, cada campo é repassado como está —
|
|
7
|
+
* exceto `pkName`, que é omitido do `update` (não faz sentido reatribuir a PK
|
|
8
|
+
* num update parcial).
|
|
9
|
+
*
|
|
10
|
+
* Com `relations`, cada campo de relação é resolvido de acordo com seu `mode`/
|
|
11
|
+
* `restriction` (ver `Relation`):
|
|
12
|
+
* - to-one (`oto`/`mto`) sem PK no valor enviado -> `create` (e `upsert` no
|
|
13
|
+
* update, a não ser que `restriction: "add"`, que usa só `create`)
|
|
14
|
+
* - to-one com PK no valor enviado -> `connectOrCreate` (e
|
|
15
|
+
* `upsert` no update, a não ser que `restriction: "add"`)
|
|
16
|
+
* - to-one enviado como `null` -> `delete` (`oto` +
|
|
17
|
+
* `restriction: "set"`) ou `disconnect` (`mto` + `nullable`)
|
|
18
|
+
* - to-many (`otm`/`mtm`) — itens são separados entre "com PK" e "sem PK":
|
|
19
|
+
* os sem PK viram `create`, os com PK viram `connectOrCreate` no create e
|
|
20
|
+
* `upsert` no update; com `restriction: "set"`, itens de `otm` que não
|
|
21
|
+
* vieram no array são removidos (`deleteMany`) e itens de `mtm` têm o
|
|
22
|
+
* vínculo resetado (`set: []`) antes de reconectar
|
|
23
|
+
*
|
|
24
|
+
* Essa mesma saída `{ create, update }` é reaproveitada em todos os métodos de
|
|
25
|
+
* escrita do adapter: `create()` usa só `.create`, `update()`/`updateMany()`
|
|
26
|
+
* usam só `.update`, e `save()`/`upsert()` usam os dois.
|
|
27
|
+
*/
|
|
28
|
+
import { AdapterRelations } from "../types/adapter-relations.type";
|
|
29
|
+
import { PlainObject } from "../types/plain-object.type";
|
|
30
|
+
export interface PrismaWriteData {
|
|
31
|
+
create: PlainObject;
|
|
32
|
+
update: PlainObject;
|
|
33
|
+
}
|
|
34
|
+
export declare function parsePrismaWriteData<T>(obj: PlainObject, pkName: string, relations?: AdapterRelations<T>): PrismaWriteData;
|