@vsrepo/prisma7-adapter 2.0.0-dev → 2.0.0-dev.1
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 +147 -29
- package/README.md +142 -51
- package/README.pt-BR.md +142 -51
- package/dist/index.d.ts +2 -1
- package/dist/parsers/data.parser.d.ts +1 -1
- package/dist/parsers/data.parser.js +66 -27
- package/dist/prisma7.adapter.d.ts +12 -0
- package/dist/prisma7.adapter.js +24 -8
- package/dist/types/adapter-config.type.d.ts +16 -0
- package/dist/types/adapter-relations.type.d.ts +13 -7
- package/dist/types/prisma7-global-config.type.d.ts +17 -0
- package/dist/types/prisma7-global-config.type.js +1 -0
- package/dist/types/relation.type.d.ts +110 -22
- package/package.json +7 -7
|
@@ -12,42 +12,53 @@ function splitByPk(items, pk) {
|
|
|
12
12
|
}
|
|
13
13
|
return { withPk, withoutPk };
|
|
14
14
|
}
|
|
15
|
+
/** `Relation.onUpdate` of a to-one relation when the config doesn't set one. */
|
|
16
|
+
const DEFAULT_TO_ONE_STRATEGY = "connect";
|
|
17
|
+
/** `Relation.onUpdate` of a to-many relation when the config doesn't set one. */
|
|
18
|
+
const DEFAULT_TO_MANY_STRATEGY = "add";
|
|
19
|
+
/** `Relation.onDisconnect` of a to-one relation when the config doesn't set one. */
|
|
20
|
+
const DEFAULT_DISCONNECT_STRATEGY = "disconnect";
|
|
21
|
+
/**
|
|
22
|
+
* `update` half of a to-one relation whose related object has its `pk`,
|
|
23
|
+
* according to the relation's `onUpdate` ({@link ToOneStrategy}).
|
|
24
|
+
*/
|
|
25
|
+
function resolveExistingToOneUpdate(strategy, pk, where, field) {
|
|
26
|
+
switch (strategy) {
|
|
27
|
+
case "connect":
|
|
28
|
+
return { connect: where };
|
|
29
|
+
case "connectOrCreate":
|
|
30
|
+
return { connectOrCreate: { where, create: field } };
|
|
31
|
+
case "upsert":
|
|
32
|
+
return { upsert: { where, create: field, update: omitKey(field, pk) } };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
15
35
|
function parseToOneRelation(field, relation) {
|
|
36
|
+
const updateStrategy = relation.onUpdate ?? DEFAULT_TO_ONE_STRATEGY;
|
|
16
37
|
if (field === null) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return { update: { delete: true } };
|
|
23
|
-
}
|
|
24
|
-
return { update: { disconnect: true } };
|
|
25
|
-
}
|
|
26
|
-
// relation.mode === "mto"
|
|
27
|
-
return { update: { disconnect: true } };
|
|
38
|
+
const disconnectStrategy = relation.onDisconnect ?? DEFAULT_DISCONNECT_STRATEGY;
|
|
39
|
+
return disconnectStrategy === "delete" ? { update: { delete: true } } : { update: { disconnect: true } };
|
|
40
|
+
}
|
|
41
|
+
if (relation.fkThere !== undefined) {
|
|
42
|
+
field = omitKey(field, relation.fkThere);
|
|
28
43
|
}
|
|
29
44
|
const pkValue = field[relation.pk];
|
|
30
45
|
if (pkValue === undefined) {
|
|
31
46
|
return {
|
|
32
47
|
create: { create: field },
|
|
33
|
-
update:
|
|
48
|
+
update: updateStrategy === "upsert" ? { upsert: { create: field, update: field } } : { create: field },
|
|
34
49
|
};
|
|
35
50
|
}
|
|
36
|
-
const
|
|
51
|
+
const where = { [relation.pk]: pkValue };
|
|
37
52
|
return {
|
|
38
|
-
create: { connectOrCreate },
|
|
39
|
-
update: relation.
|
|
40
|
-
? { connectOrCreate }
|
|
41
|
-
: {
|
|
42
|
-
upsert: {
|
|
43
|
-
where: { [relation.pk]: pkValue },
|
|
44
|
-
create: field,
|
|
45
|
-
update: omitKey(field, relation.pk),
|
|
46
|
-
},
|
|
47
|
-
},
|
|
53
|
+
create: { connectOrCreate: { where, create: field } },
|
|
54
|
+
update: resolveExistingToOneUpdate(updateStrategy, relation.pk, where, field),
|
|
48
55
|
};
|
|
49
56
|
}
|
|
50
57
|
function parseToManyRelation(field, relation) {
|
|
58
|
+
const updateStrategy = relation.onUpdate ?? DEFAULT_TO_MANY_STRATEGY;
|
|
59
|
+
if (relation.fkThere !== undefined) {
|
|
60
|
+
field = field.map(f => omitKey(f, relation.fkThere));
|
|
61
|
+
}
|
|
51
62
|
const { withPk, withoutPk } = splitByPk(field, relation.pk);
|
|
52
63
|
const connectOrCreate = withPk.map(item => ({
|
|
53
64
|
where: { [relation.pk]: item[relation.pk] },
|
|
@@ -61,7 +72,7 @@ function parseToManyRelation(field, relation) {
|
|
|
61
72
|
update: omitKey(item, relation.pk),
|
|
62
73
|
}));
|
|
63
74
|
const create = { create: withoutPk, connectOrCreate };
|
|
64
|
-
if (
|
|
75
|
+
if (updateStrategy === "add") {
|
|
65
76
|
return {
|
|
66
77
|
create,
|
|
67
78
|
update: relation.mode === "mtm" ? { create: withoutPk, connectOrCreate } : { create: withoutPk, upsert },
|
|
@@ -80,9 +91,12 @@ function parseToManyRelation(field, relation) {
|
|
|
80
91
|
},
|
|
81
92
|
};
|
|
82
93
|
}
|
|
83
|
-
export function parsePrismaWriteData(obj, pkName, relations, omitPkInUpdate = false) {
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
export function parsePrismaWriteData(obj, pkName, relations, omitPkInUpdate = false, allowOmitPkWhenNeeded = false) {
|
|
95
|
+
let create = {};
|
|
96
|
+
let update = {};
|
|
97
|
+
/** Relation fields that hold their FK in this entity, and those FK fields. */
|
|
98
|
+
const fkHereRelations = [];
|
|
99
|
+
const keysToOmit = [];
|
|
86
100
|
for (const [key, field] of Object.entries(obj)) {
|
|
87
101
|
if (field === undefined)
|
|
88
102
|
continue;
|
|
@@ -93,7 +107,13 @@ export function parsePrismaWriteData(obj, pkName, relations, omitPkInUpdate = fa
|
|
|
93
107
|
update[key] = field;
|
|
94
108
|
continue;
|
|
95
109
|
}
|
|
110
|
+
if (relation === "ignore")
|
|
111
|
+
continue;
|
|
96
112
|
if (relation.mode === "oto" || relation.mode === "mto") {
|
|
113
|
+
if (relation.fkHere !== undefined) {
|
|
114
|
+
fkHereRelations.push(key);
|
|
115
|
+
keysToOmit.push(relation.fkHere);
|
|
116
|
+
}
|
|
97
117
|
const parsed = parseToOneRelation(field, relation);
|
|
98
118
|
if (parsed.create !== undefined)
|
|
99
119
|
create[key] = parsed.create;
|
|
@@ -105,5 +125,24 @@ export function parsePrismaWriteData(obj, pkName, relations, omitPkInUpdate = fa
|
|
|
105
125
|
create[key] = parsed.create;
|
|
106
126
|
update[key] = parsed.update;
|
|
107
127
|
}
|
|
128
|
+
if (fkHereRelations.length) {
|
|
129
|
+
if (obj[pkName] !== undefined) {
|
|
130
|
+
if (!allowOmitPkWhenNeeded) {
|
|
131
|
+
const fields = fkHereRelations.map(name => `'${name}'`).join(", ");
|
|
132
|
+
throw new VSRepoAdapterError(`Cannot send the primary key '${pkName}' together with the relation field(s) ${fields}: ` +
|
|
133
|
+
`they hold their foreign key in this same entity, and Prisma rejects the primary key ` +
|
|
134
|
+
`in a nested write of that kind ("Unknown argument"). ` +
|
|
135
|
+
`Either remove '${pkName}' from the payload, or set 'allowOmitPk: true' (in the adapter ` +
|
|
136
|
+
`config, or globally through 'setGlobalConfig') so the adapter drops it. Note that, with ` +
|
|
137
|
+
`'allowOmitPk', a record that doesn't exist yet is created with a database-generated ` +
|
|
138
|
+
`'${pkName}' instead of the one you sent.`, AdapterErrorCode.INVALID_DATA, null);
|
|
139
|
+
}
|
|
140
|
+
keysToOmit.push(pkName);
|
|
141
|
+
}
|
|
142
|
+
for (const key of keysToOmit) {
|
|
143
|
+
create = omitKey(create, key);
|
|
144
|
+
update = omitKey(update, key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
108
147
|
return { create, update };
|
|
109
148
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AdapterMethodOptions, AdapterQueryOptions, CountResult, DeepPartial, NumericKeys, RestrictAdapterMethodOptions, SimpleAdapterMethodOptions, VSRepoAdapter, VSRepoTransactionOptions, VSRepoWhere, WithIgnoreConflicts } from "vsrepo";
|
|
2
2
|
import { Prisma7AdapterConfig } from "./types/adapter-config.type.js";
|
|
3
3
|
import { Prisma7ClientLike } from "./types/prisma7-client-like.type.js";
|
|
4
|
+
import { Prisma7GlobalConfig } from "./types/prisma7-global-config.type.js";
|
|
4
5
|
/**
|
|
5
6
|
* `VSRepoAdapter` implementation for Prisma 7.
|
|
6
7
|
*
|
|
@@ -15,7 +16,16 @@ export declare class VSRepoPrisma7Adapter<T, K extends Prisma7ClientLike = Prism
|
|
|
15
16
|
private readonly relations?;
|
|
16
17
|
private readonly logger;
|
|
17
18
|
private readonly provider;
|
|
19
|
+
/** Whether the primary key may be dropped from a write payload when Prisma can't take it. */
|
|
20
|
+
private readonly allowOmitPk?;
|
|
21
|
+
private static globalConfig;
|
|
18
22
|
constructor(prisma: K, config: Prisma7AdapterConfig<T, K>);
|
|
23
|
+
/**
|
|
24
|
+
* Sets defaults (`provider`, `logLevel`, `allowOmitPk`) for every adapter
|
|
25
|
+
* created **after** this call. It replaces the previous global config, and
|
|
26
|
+
* the config passed to each adapter's constructor takes precedence.
|
|
27
|
+
*/
|
|
28
|
+
static setGlobalConfig(globalConfig: Prisma7GlobalConfig): void;
|
|
19
29
|
private getPrismaRepository;
|
|
20
30
|
/** Resolves the "read" part of a Prisma arg: select/include/orderBy/pagination. */
|
|
21
31
|
private resolveReadArg;
|
|
@@ -89,6 +99,8 @@ export declare class VSRepoPrisma7Adapter<T, K extends Prisma7ClientLike = Prism
|
|
|
89
99
|
/** Runs `fn` inside a native transaction of the underlying ORM/database. */
|
|
90
100
|
runInTransaction<R>(fn: (tx: any) => Promise<R>, options?: VSRepoTransactionOptions): Promise<R>;
|
|
91
101
|
private execMethod;
|
|
102
|
+
/** Runs `parsePrismaWriteData` with this adapter's `pkName`, `relations` and `allowOmitPk`. */
|
|
103
|
+
private parseData;
|
|
92
104
|
/** Fetches a single record matching `where`. */
|
|
93
105
|
findOne(where?: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T | null>;
|
|
94
106
|
/** Fetches a single record matching `where`, throwing if none is found. */
|
package/dist/prisma7.adapter.js
CHANGED
|
@@ -24,6 +24,9 @@ export class VSRepoPrisma7Adapter extends VSRepoAdapter {
|
|
|
24
24
|
relations;
|
|
25
25
|
logger;
|
|
26
26
|
provider;
|
|
27
|
+
/** Whether the primary key may be dropped from a write payload when Prisma can't take it. */
|
|
28
|
+
allowOmitPk;
|
|
29
|
+
static globalConfig = {};
|
|
27
30
|
constructor(prisma, config) {
|
|
28
31
|
super();
|
|
29
32
|
this.prisma = prisma;
|
|
@@ -31,9 +34,18 @@ export class VSRepoPrisma7Adapter extends VSRepoAdapter {
|
|
|
31
34
|
this.modelName = config.modelName;
|
|
32
35
|
this.pkName = config.pkName;
|
|
33
36
|
this.relations = config.relations;
|
|
34
|
-
this.provider = resolveProvider(prisma, config.provider);
|
|
35
|
-
this.logger = new VSLogger(config.logLevel ?? VSLogLevel.WARN, this.constructor.name + "Logger");
|
|
36
|
-
this.
|
|
37
|
+
this.provider = resolveProvider(prisma, config.provider ?? VSRepoPrisma7Adapter.globalConfig.provider);
|
|
38
|
+
this.logger = new VSLogger(config.logLevel ?? VSRepoPrisma7Adapter.globalConfig.logLevel ?? VSLogLevel.WARN, this.constructor.name + "Logger");
|
|
39
|
+
this.allowOmitPk = config.allowOmitPk ?? VSRepoPrisma7Adapter.globalConfig.allowOmitPk;
|
|
40
|
+
this.logger.logInfo(`${this.constructor.name} initialized for table '${this.modelName}' (pkName: '${this.pkName}'${this.relations ? `, relations: [${Object.keys(this.relations).join(", ")}]` : ""}, provider: ${this.provider ? `'${this.provider}'` : "<not detected>"}${this.allowOmitPk ? ", allowOmitPk: true" : ""})`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sets defaults (`provider`, `logLevel`, `allowOmitPk`) for every adapter
|
|
44
|
+
* created **after** this call. It replaces the previous global config, and
|
|
45
|
+
* the config passed to each adapter's constructor takes precedence.
|
|
46
|
+
*/
|
|
47
|
+
static setGlobalConfig(globalConfig) {
|
|
48
|
+
this.globalConfig = globalConfig;
|
|
37
49
|
}
|
|
38
50
|
getPrismaRepository(db) {
|
|
39
51
|
return db ? db[this.modelName] : this.prisma[this.modelName];
|
|
@@ -240,6 +252,10 @@ export class VSRepoPrisma7Adapter extends VSRepoAdapter {
|
|
|
240
252
|
throw mapPrismaError(error, methodName);
|
|
241
253
|
}
|
|
242
254
|
}
|
|
255
|
+
/** Runs `parsePrismaWriteData` with this adapter's `pkName`, `relations` and `allowOmitPk`. */
|
|
256
|
+
parseData(obj, omitPkInUpdate) {
|
|
257
|
+
return parsePrismaWriteData(obj, this.pkName, this.relations, omitPkInUpdate, this.allowOmitPk);
|
|
258
|
+
}
|
|
243
259
|
/** Fetches a single record matching `where`. */
|
|
244
260
|
async findOne(where, options) {
|
|
245
261
|
const arg = { ...this.resolveReadArg(options), where: this.parseWhere(where) };
|
|
@@ -261,7 +277,7 @@ export class VSRepoPrisma7Adapter extends VSRepoAdapter {
|
|
|
261
277
|
const pkValue = objAny[this.pkName];
|
|
262
278
|
const readArg = this.resolveReadArg({ select: options?.select, relations: options?.relations });
|
|
263
279
|
const repo = this.getPrismaRepository(options?.db);
|
|
264
|
-
const { create, update } =
|
|
280
|
+
const { create, update } = this.parseData(objAny, true);
|
|
265
281
|
if (pkValue === undefined) {
|
|
266
282
|
const arg = { ...readArg, data: create };
|
|
267
283
|
return this.execMethod(() => repo.create(arg), "save (create)", arg);
|
|
@@ -362,7 +378,7 @@ export class VSRepoPrisma7Adapter extends VSRepoAdapter {
|
|
|
362
378
|
}
|
|
363
379
|
/** Updates a single record matching `where`. */
|
|
364
380
|
async update(where, obj, options) {
|
|
365
|
-
const { update } =
|
|
381
|
+
const { update } = this.parseData(obj);
|
|
366
382
|
const arg = {
|
|
367
383
|
...this.resolveReadArg({ select: options?.select, relations: options?.relations }),
|
|
368
384
|
where: this.parseWhere(where),
|
|
@@ -445,7 +461,7 @@ export class VSRepoPrisma7Adapter extends VSRepoAdapter {
|
|
|
445
461
|
}
|
|
446
462
|
/** Creates a single record. */
|
|
447
463
|
async create(obj, options) {
|
|
448
|
-
const { create } =
|
|
464
|
+
const { create } = this.parseData(obj);
|
|
449
465
|
const arg = {
|
|
450
466
|
...this.resolveReadArg({ select: options?.select, relations: options?.relations }),
|
|
451
467
|
data: create,
|
|
@@ -531,8 +547,8 @@ export class VSRepoPrisma7Adapter extends VSRepoAdapter {
|
|
|
531
547
|
// }
|
|
532
548
|
/** Creates a record if none matches `where`, otherwise updates it. */
|
|
533
549
|
async upsert(where, create, update, options) {
|
|
534
|
-
const parsedCreate =
|
|
535
|
-
const parsedUpdate =
|
|
550
|
+
const parsedCreate = this.parseData(create).create;
|
|
551
|
+
const parsedUpdate = this.parseData(update).update;
|
|
536
552
|
const arg = {
|
|
537
553
|
...this.resolveReadArg({ select: options?.select, relations: options?.relations }),
|
|
538
554
|
where: this.parseWhere(where),
|
|
@@ -30,4 +30,20 @@ export interface Prisma7AdapterConfig<T = any, K extends Prisma7ClientLike = Pri
|
|
|
30
30
|
provider?: PrismaProvider;
|
|
31
31
|
/** Minimum log level for the adapter's internal `VSLogger`. @default VSLogLevel.WARN */
|
|
32
32
|
logLevel?: VSLogLevel;
|
|
33
|
+
/**
|
|
34
|
+
* Lets the adapter drop the primary key from a write payload when it can't
|
|
35
|
+
* be sent. That happens when the payload has a relation that holds its
|
|
36
|
+
* foreign key in this same entity (`fkHere`, e.g. `Address.user`): Prisma
|
|
37
|
+
* doesn't accept the primary key in that kind of nested write. When `false`,
|
|
38
|
+
* such a payload throws `INVALID_DATA`.
|
|
39
|
+
*
|
|
40
|
+
* **Careful:** with this on, `save` with a primary key that doesn't exist
|
|
41
|
+
* yet creates the record with a database-generated key, not the one you
|
|
42
|
+
* sent. Only enable it if you really need it and if the database (not your
|
|
43
|
+
* application) generates the ids. Falls back to the global config
|
|
44
|
+
* (`setGlobalConfig`) when omitted.
|
|
45
|
+
*
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
allowOmitPk?: boolean;
|
|
33
49
|
}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import type { RelationKeys } from "vsrepo";
|
|
2
2
|
import { Relation } from "./relation.type.js";
|
|
3
3
|
/**
|
|
4
|
-
* Relation configuration for entity `T`, used by the
|
|
5
|
-
*
|
|
6
|
-
* `Relation`).
|
|
4
|
+
* Relation configuration for entity `T`, used by the write parsers to resolve
|
|
5
|
+
* relation fields into Prisma nested writes (see `Relation`).
|
|
7
6
|
*
|
|
8
|
-
* Keyed by the relation fields of `T` (`RelationKeys<T>`).
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Keyed by the relation fields of `T` (`RelationKeys<T>`). Each value is either:
|
|
8
|
+
*
|
|
9
|
+
* - a `Relation` config, whose options depend on its `mode`; or
|
|
10
|
+
* - `"ignore"`, which leaves the field out of every write (`create`, `update`,
|
|
11
|
+
* `save`...). Use it for a relation you load but don't want to persist
|
|
12
|
+
* through this entity. Since the relation object is dropped, a scalar FK in
|
|
13
|
+
* the payload (e.g. `userId`) is sent as is, together with the primary key.
|
|
14
|
+
*
|
|
15
|
+
* Without this configuration (adapter built without `relations`), relation
|
|
16
|
+
* fields are passed through to Prisma as-is, with no special resolution.
|
|
11
17
|
*
|
|
12
18
|
* @publicApi
|
|
13
19
|
*/
|
|
14
20
|
export type AdapterRelations<T> = Partial<{
|
|
15
|
-
[P in RelationKeys<T>]: Relation<NonNullable<T[P]> extends Array<infer U> ? U : NonNullable<T[P]
|
|
21
|
+
[P in RelationKeys<T>]: "ignore" | Relation<NonNullable<T[P]> extends Array<infer U> ? U : NonNullable<T[P]>, T>;
|
|
16
22
|
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { VSLogLevel } from "vsrepo";
|
|
2
|
+
import { PrismaProvider } from "./prisma-provider.type.js";
|
|
3
|
+
/**
|
|
4
|
+
* Defaults applied to every adapter instance, set through
|
|
5
|
+
* `VSRepoPrisma7Adapter.setGlobalConfig`. A field set in an adapter's own
|
|
6
|
+
* config wins over the global one.
|
|
7
|
+
*
|
|
8
|
+
* @publicApi
|
|
9
|
+
*/
|
|
10
|
+
export type Prisma7GlobalConfig = {
|
|
11
|
+
/** Default `provider` (see `Prisma7AdapterConfig.provider`). */
|
|
12
|
+
provider?: PrismaProvider;
|
|
13
|
+
/** Default `logLevel` (see `Prisma7AdapterConfig.logLevel`). */
|
|
14
|
+
logLevel?: VSLogLevel;
|
|
15
|
+
/** Default `allowOmitPk` (see `Prisma7AdapterConfig.allowOmitPk`). @default false */
|
|
16
|
+
allowOmitPk?: boolean;
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,35 +1,123 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Cardinality of a relation from the point of view of the entity that owns the
|
|
3
|
+
* field: `oto` = one-to-one, `mto` = many-to-one, `otm` = one-to-many and
|
|
4
|
+
* `mtm` = many-to-many.
|
|
3
5
|
*
|
|
4
6
|
* @publicApi
|
|
5
7
|
*/
|
|
6
|
-
export
|
|
8
|
+
export type RelationMode = "oto" | "mto" | "otm" | "mtm";
|
|
9
|
+
/**
|
|
10
|
+
* How a to-one relation (`oto`/`mto`) is written on update when the related
|
|
11
|
+
* object has its `pk`. Pick it through `Relation.onUpdate`.
|
|
12
|
+
*
|
|
13
|
+
* - `"connect"` (default) — only connects the record. It never creates nor
|
|
14
|
+
* overwrites the related record, so re-sending the one that is already
|
|
15
|
+
* connected is a no-op and switching to another record works. Fails with
|
|
16
|
+
* Prisma's `P2025` when the record doesn't exist.
|
|
17
|
+
* - `"connectOrCreate"` — connects the record, creating it when missing.
|
|
18
|
+
* - `"upsert"` — updates the related record with the fields you sent (everything
|
|
19
|
+
* but its `pk`), creating it when missing.
|
|
20
|
+
*
|
|
21
|
+
* Without the related `pk` there is nothing to connect to: the record is
|
|
22
|
+
* created (`"upsert"` still sends its fields as the `update`).
|
|
23
|
+
*
|
|
24
|
+
* @publicApi
|
|
25
|
+
*/
|
|
26
|
+
export type ToOneStrategy = "connect" | "connectOrCreate" | "upsert";
|
|
27
|
+
/**
|
|
28
|
+
* How a to-many relation (`otm`/`mtm`) is written on update. Pick it through
|
|
29
|
+
* `Relation.onUpdate`.
|
|
30
|
+
*
|
|
31
|
+
* - `"add"` (default) — only creates/connects/upserts the items you send.
|
|
32
|
+
* Whatever else is already related is left untouched.
|
|
33
|
+
* - `"set"` — also removes (`otm`: `deleteMany`) or disconnects (`mtm`: `set`)
|
|
34
|
+
* every related item that wasn't sent.
|
|
35
|
+
*
|
|
36
|
+
* @publicApi
|
|
37
|
+
*/
|
|
38
|
+
export type ToManyStrategy = "add" | "set";
|
|
39
|
+
/**
|
|
40
|
+
* What sending `null` to a to-one relation does on update. Pick it through
|
|
41
|
+
* `Relation.onDisconnect`.
|
|
42
|
+
*
|
|
43
|
+
* - `"disconnect"` (default) — detaches the related record and keeps it.
|
|
44
|
+
* Needs the relation (its FK) to be optional.
|
|
45
|
+
* - `"delete"` — deletes the related record. It is always opt-in. On an `mto`
|
|
46
|
+
* the related record is shared with other rows, so deleting it affects all of them.
|
|
47
|
+
*
|
|
48
|
+
* @publicApi
|
|
49
|
+
*/
|
|
50
|
+
export type DisconnectStrategy = "disconnect" | "delete";
|
|
51
|
+
/** Options shared by every relation mode. */
|
|
52
|
+
type RelationBase<T, M extends RelationMode> = {
|
|
7
53
|
/**
|
|
8
54
|
* Cardinality of the relation from the point of view of the entity
|
|
9
|
-
* that owns the field
|
|
10
|
-
* `otm` = one-to-many, `mtm` = many-to-many).
|
|
55
|
+
* that owns the field. It decides which of the other options are available.
|
|
11
56
|
*/
|
|
12
|
-
mode:
|
|
13
|
-
/**
|
|
14
|
-
* How `save`/`update`/`upsert` handle to-many relations
|
|
15
|
-
* (`otm`/`mtm`) and to-one relations pointing to an already-existing record
|
|
16
|
-
* (matched by `pk`) — `"add"` only creates/connects the items that were
|
|
17
|
-
* sent (upsert per item), `"set"` also removes/disconnects whatever wasn't
|
|
18
|
-
* sent.
|
|
19
|
-
*/
|
|
20
|
-
restriction: "set" | "add";
|
|
57
|
+
mode: M;
|
|
21
58
|
/**
|
|
22
59
|
* Name of the field used as the identifier of the related record,
|
|
23
60
|
* used to decide between `create` (no pk in the payload) and
|
|
24
|
-
*
|
|
61
|
+
* connecting to / upserting an existing record (pk present).
|
|
62
|
+
*/
|
|
63
|
+
pk: keyof T & string;
|
|
64
|
+
/**
|
|
65
|
+
* Name of the scalar foreign key that lives in the **related** entity —
|
|
66
|
+
* e.g. `userId` in `User.address`, where `Address` holds the FK. The
|
|
67
|
+
* adapter strips it from the related payload(s) before building the
|
|
68
|
+
* nested write, since the relation already sets it.
|
|
69
|
+
*/
|
|
70
|
+
fkThere?: keyof T & string;
|
|
71
|
+
};
|
|
72
|
+
/** Options of the `oto` and `mto` relations. */
|
|
73
|
+
type ToOneOptions<K> = {
|
|
74
|
+
/**
|
|
75
|
+
* Name of the scalar foreign key that lives in **this** entity (the one
|
|
76
|
+
* that owns the relation field) — e.g. `userId` in `Address.user`. Set it
|
|
77
|
+
* on the owning side of the relation.
|
|
78
|
+
*
|
|
79
|
+
* Prisma doesn't accept the scalar FK next to the relation object, so when
|
|
80
|
+
* the relation is in the payload the adapter drops this field from
|
|
81
|
+
* `create` and `update`. For the same reason it also can't keep the
|
|
82
|
+
* entity's primary key: if the payload has one, the write throws
|
|
83
|
+
* `INVALID_DATA` unless `allowOmitPk` is enabled (see
|
|
84
|
+
* `Prisma7AdapterConfig.allowOmitPk`).
|
|
85
|
+
*/
|
|
86
|
+
fkHere?: keyof K & string;
|
|
87
|
+
/**
|
|
88
|
+
* How the related record is written on update when the payload has its `pk`.
|
|
89
|
+
* @default "connect"
|
|
90
|
+
*/
|
|
91
|
+
onUpdate?: ToOneStrategy;
|
|
92
|
+
/**
|
|
93
|
+
* What sending `null` does on update.
|
|
94
|
+
* @default "disconnect"
|
|
25
95
|
*/
|
|
26
|
-
|
|
96
|
+
onDisconnect?: DisconnectStrategy;
|
|
97
|
+
};
|
|
98
|
+
/** Options of the `otm` and `mtm` relations. */
|
|
99
|
+
type ToManyOptions = {
|
|
27
100
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* (`oto` + `restriction: "set"`) or `disconnect` (`oto` + `restriction:
|
|
31
|
-
* "add"`, and `mto`). When `false`/omitted, sending `null` for a to-one
|
|
32
|
-
* relation throws a `VSRepoAdapterError` (code `INVALID_DATA`).
|
|
101
|
+
* How the related items are written on update.
|
|
102
|
+
* @default "add"
|
|
33
103
|
*/
|
|
34
|
-
|
|
35
|
-
}
|
|
104
|
+
onUpdate?: ToManyStrategy;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Configuration of a single relation field:
|
|
108
|
+
*
|
|
109
|
+
* - `oto` and `mto`: `fkHere`, `fkThere`, `onUpdate` ({@link ToOneStrategy}) and
|
|
110
|
+
* `onDisconnect` ({@link DisconnectStrategy}).
|
|
111
|
+
* - `otm` and `mtm`: `fkThere` and `onUpdate` ({@link ToManyStrategy}).
|
|
112
|
+
*
|
|
113
|
+
* Set a relation to `"ignore"` in `AdapterRelations` to leave it out of every
|
|
114
|
+
* write (see `AdapterRelations`).
|
|
115
|
+
*
|
|
116
|
+
* @template T Type of the related entity.
|
|
117
|
+
* @template K Type of the entity that owns the relation field.
|
|
118
|
+
* @template M Narrows the config to some of the modes (all of them by default).
|
|
119
|
+
*
|
|
120
|
+
* @publicApi
|
|
121
|
+
*/
|
|
122
|
+
export type Relation<T = any, K = any, M extends RelationMode = RelationMode> = M extends "oto" | "mto" ? RelationBase<T, M> & ToOneOptions<K> : M extends "otm" | "mtm" ? RelationBase<T, M> & ToManyOptions : never;
|
|
123
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vsrepo/prisma7-adapter",
|
|
3
|
-
"version": "2.0.0-dev",
|
|
3
|
+
"version": "2.0.0-dev.1",
|
|
4
4
|
"author": {
|
|
5
5
|
"email": "joaodev.azevedo@outlook.com",
|
|
6
6
|
"url": "https://github.com/jaobrabo123",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"@prisma/adapter-better-sqlite3": "^7.10.1",
|
|
16
16
|
"@prisma/adapter-mariadb": "^7.10.1",
|
|
17
17
|
"@prisma/adapter-mssql": "^7.10.1",
|
|
18
|
-
"@prisma/adapter-pg": "^7.10.
|
|
18
|
+
"@prisma/adapter-pg": "^7.10.1",
|
|
19
19
|
"@types/better-sqlite3": "^9.6.0",
|
|
20
20
|
"@types/mssql": "^12.3.0",
|
|
21
21
|
"@types/node": "^26.4.0",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"oxlint-tsgolint": "^7.0.2005",
|
|
27
27
|
"pg": "^8.23.0",
|
|
28
28
|
"prettier": "^3.9.10",
|
|
29
|
-
"prisma": "^7.10.
|
|
29
|
+
"prisma": "^7.10.1",
|
|
30
30
|
"tsx": "^4.23.12",
|
|
31
31
|
"typescript": "^7.0.2",
|
|
32
32
|
"vitest": "^5.0.5"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@prisma/client": "^7.10.
|
|
36
|
-
"vsrepo": "^3.0.0-dev.
|
|
35
|
+
"@prisma/client": "^7.10.1",
|
|
36
|
+
"vsrepo": "^3.0.0-dev.3"
|
|
37
37
|
},
|
|
38
38
|
"exports": {
|
|
39
39
|
".": {
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"lint:fix": "oxlint --fix src/ test/",
|
|
79
79
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
80
80
|
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
81
|
-
"format:docs": "prettier --write \"
|
|
82
|
-
"format:docs:check": "prettier --check \"
|
|
81
|
+
"format:docs": "prettier --write \"./*.md\"",
|
|
82
|
+
"format:docs:check": "prettier --check \"./*.md\"",
|
|
83
83
|
"test": "vitest run",
|
|
84
84
|
"test:watch": "vitest",
|
|
85
85
|
"test:cov": "vitest run --coverage",
|