Skip to content

Commit 8a7755b

Browse files
committed
change to a more robust approach
- rely on the nested create path to determine the parent model context and detect if it's implicitly set the fk field
1 parent 7cf0e98 commit 8a7755b

File tree

2 files changed

+103
-73
lines changed

2 files changed

+103
-73
lines changed

packages/runtime/src/enhancements/node/default-auth.ts

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -117,55 +117,75 @@ class DefaultAuthHandler extends DefaultPrismaProxyHandler {
117117
authDefaultValue: unknown,
118118
context: NestedWriteVisitorContext
119119
) {
120-
if (fieldInfo.isForeignKey && fieldInfo.relationField && fieldInfo.relationField in data) {
121-
// if the field is a fk, and the relation field is already set, we should not override it
122-
return;
123-
}
120+
if (fieldInfo.isForeignKey) {
121+
// if the field being inspected is a fk field, there are several cases we should not
122+
// set the default value or should not set directly
124123

125-
if (context.field?.backLink) {
126-
const modelInfo = getModelInfo(this.options.modelMeta, model);
127-
const parentModel = modelInfo?.fields[context.field.backLink];
128-
129-
if (
130-
parentModel?.isDataModel &&
131-
parentModel.foreignKeyMapping &&
132-
Object.keys(parentModel.foreignKeyMapping).includes(fieldInfo.name)
133-
) {
134-
// if the field is part of a fk as part of a nested write, then prisma handles setting it
124+
// if the field is a fk, and the relation field is already set, we should not override it
125+
if (fieldInfo.relationField && fieldInfo.relationField in data) {
135126
return;
136127
}
137-
}
138128

139-
if (fieldInfo.isForeignKey && !isUnsafeMutate(model, data, this.options.modelMeta)) {
140-
// if the field is a fk, and the create payload is not unsafe, we need to translate
141-
// the fk field setting to a `connect` of the corresponding relation field
142-
const relFieldName = fieldInfo.relationField;
143-
if (!relFieldName) {
144-
throw new Error(
145-
`Field \`${fieldInfo.name}\` is a foreign key field but no corresponding relation field is found`
129+
if (context.field?.backLink && context.nestingPath.length > 1) {
130+
// if the fk field is in a creation context where its implied by the parent,
131+
// we should not set the default value, e.g.:
132+
//
133+
// ```
134+
// parent.create({ data: { child: { create: {} } } })
135+
// ```
136+
//
137+
// event if child's fk to parent has a default value, we should not set default
138+
// value here
139+
140+
// fetch parent model from the parent context
141+
const parentModel = getModelInfo(
142+
this.options.modelMeta,
143+
context.nestingPath[context.nestingPath.length - 2].model
146144
);
147-
}
148-
const relationField = requireField(this.options.modelMeta, model, relFieldName);
149145

150-
// construct a `{ connect: { ... } }` payload
151-
let connect = data[relationField.name]?.connect;
152-
if (!connect) {
153-
connect = {};
154-
data[relationField.name] = { connect };
146+
if (parentModel) {
147+
// get the opposite side of the relation for the current create context
148+
const oppositeRelationField = requireField(this.options.modelMeta, model, context.field.backLink);
149+
if (parentModel.name === oppositeRelationField.type) {
150+
// if the opposite side matches the parent model, it means we currently in a creation context
151+
// that implicitly sets this fk field
152+
return;
153+
}
154+
}
155155
}
156156

157-
// sets the opposite fk field to value `authDefaultValue`
158-
const oppositeFkFieldName = this.getOppositeFkFieldName(relationField, fieldInfo);
159-
if (!oppositeFkFieldName) {
160-
throw new Error(
161-
`Cannot find opposite foreign key field for \`${fieldInfo.name}\` in relation field \`${relFieldName}\``
162-
);
157+
if (!isUnsafeMutate(model, data, this.options.modelMeta)) {
158+
// if the field is a fk, and the create payload is not unsafe, we need to translate
159+
// the fk field setting to a `connect` of the corresponding relation field
160+
const relFieldName = fieldInfo.relationField;
161+
if (!relFieldName) {
162+
throw new Error(
163+
`Field \`${fieldInfo.name}\` is a foreign key field but no corresponding relation field is found`
164+
);
165+
}
166+
const relationField = requireField(this.options.modelMeta, model, relFieldName);
167+
168+
// construct a `{ connect: { ... } }` payload
169+
let connect = data[relationField.name]?.connect;
170+
if (!connect) {
171+
connect = {};
172+
data[relationField.name] = { connect };
173+
}
174+
175+
// sets the opposite fk field to value `authDefaultValue`
176+
const oppositeFkFieldName = this.getOppositeFkFieldName(relationField, fieldInfo);
177+
if (!oppositeFkFieldName) {
178+
throw new Error(
179+
`Cannot find opposite foreign key field for \`${fieldInfo.name}\` in relation field \`${relFieldName}\``
180+
);
181+
}
182+
connect[oppositeFkFieldName] = authDefaultValue;
183+
return;
163184
}
164-
connect[oppositeFkFieldName] = authDefaultValue;
165-
} else {
166-
// set default value directly
167-
data[fieldInfo.name] = authDefaultValue;
168185
}
186+
187+
// set default value directly
188+
data[fieldInfo.name] = authDefaultValue;
169189
}
170190

171191
private getOppositeFkFieldName(relationField: FieldInfo, fieldInfo: FieldInfo) {

tests/regression/tests/issue-1989.test.ts renamed to tests/regression/tests/issue-1997.test.ts

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { loadSchema } from '@zenstackhq/testtools';
22

3-
describe('issue 1989', () => {
3+
describe('issue 1997', () => {
44
it('regression', async () => {
55
const { prisma, enhance } = await loadSchema(
66
`
@@ -79,43 +79,53 @@ describe('issue 1989', () => {
7979

8080
const db = enhance({ id: user.id, tenantId: tenant.id });
8181

82-
await expect(db.post.create({
83-
data: {
84-
likes: {
85-
createMany: {
86-
data: [{
87-
userId: user.id
88-
}]
89-
}
90-
}
91-
},
92-
include: {
93-
likes: true
94-
}
95-
})).resolves.toMatchObject({
82+
await expect(
83+
db.post.create({
84+
data: {
85+
likes: {
86+
createMany: {
87+
data: [
88+
{
89+
userId: user.id,
90+
},
91+
],
92+
},
93+
},
94+
},
95+
include: {
96+
likes: true,
97+
},
98+
})
99+
).resolves.toMatchObject({
96100
authorId: user.id,
97-
likes: [{
98-
tenantId: tenant.id,
99-
userId: user.id
100-
}]
101+
likes: [
102+
{
103+
tenantId: tenant.id,
104+
userId: user.id,
105+
},
106+
],
101107
});
102108

103-
await expect(db.post.create({
104-
data: {
105-
comments: {
106-
createMany: {
107-
data: [{}]
108-
}
109-
}
110-
},
111-
include: {
112-
comments: true
113-
}
114-
})).resolves.toMatchObject({
109+
await expect(
110+
db.post.create({
111+
data: {
112+
comments: {
113+
createMany: {
114+
data: [{}],
115+
},
116+
},
117+
},
118+
include: {
119+
comments: true,
120+
},
121+
})
122+
).resolves.toMatchObject({
115123
authorId: user.id,
116-
comments: [{
117-
tenantId: tenant.id
118-
}]
124+
comments: [
125+
{
126+
tenantId: tenant.id,
127+
},
128+
],
119129
});
120130
});
121131
});

0 commit comments

Comments
 (0)