Skip to content

Commit dd6be95

Browse files
authored
fix: issue with connecting multiple relations (#450)
1 parent 2855647 commit dd6be95

File tree

24 files changed

+483
-195
lines changed

24 files changed

+483
-195
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zenstack-monorepo",
3-
"version": "1.0.0-alpha.122",
3+
"version": "1.0.0-alpha.124",
44
"description": "",
55
"scripts": {
66
"build": "pnpm -r build",

packages/language/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zenstackhq/language",
3-
"version": "1.0.0-alpha.122",
3+
"version": "1.0.0-alpha.124",
44
"displayName": "ZenStack modeling language compiler",
55
"description": "ZenStack modeling language compiler",
66
"homepage": "https://zenstack.dev",

packages/next/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zenstackhq/next",
3-
"version": "1.0.0-alpha.122",
3+
"version": "1.0.0-alpha.124",
44
"displayName": "ZenStack Next.js integration",
55
"description": "ZenStack Next.js integration",
66
"homepage": "https://zenstack.dev",

packages/plugins/openapi/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@zenstackhq/openapi",
33
"displayName": "ZenStack Plugin and Runtime for OpenAPI",
4-
"version": "1.0.0-alpha.122",
4+
"version": "1.0.0-alpha.124",
55
"description": "ZenStack plugin and runtime supporting OpenAPI",
66
"main": "index.js",
77
"repository": {

packages/plugins/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@zenstackhq/react",
33
"displayName": "ZenStack plugin and runtime for ReactJS",
4-
"version": "1.0.0-alpha.122",
4+
"version": "1.0.0-alpha.124",
55
"description": "ZenStack plugin and runtime for ReactJS",
66
"main": "index.js",
77
"repository": {

packages/plugins/swr/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@zenstackhq/swr",
33
"displayName": "ZenStack plugin for generating SWR hooks",
4-
"version": "1.0.0-alpha.122",
4+
"version": "1.0.0-alpha.124",
55
"description": "ZenStack plugin for generating SWR hooks",
66
"main": "index.js",
77
"repository": {

packages/plugins/tanstack-query/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@zenstackhq/tanstack-query",
33
"displayName": "ZenStack plugin for generating tanstack-query hooks",
4-
"version": "1.0.0-alpha.122",
4+
"version": "1.0.0-alpha.124",
55
"description": "ZenStack plugin for generating tanstack-query hooks",
66
"main": "index.js",
77
"repository": {

packages/plugins/trpc/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@zenstackhq/trpc",
33
"displayName": "ZenStack plugin for tRPC",
4-
"version": "1.0.0-alpha.122",
4+
"version": "1.0.0-alpha.124",
55
"description": "ZenStack plugin for tRPC",
66
"main": "index.js",
77
"repository": {

packages/runtime/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@zenstackhq/runtime",
33
"displayName": "ZenStack Runtime Library",
4-
"version": "1.0.0-alpha.122",
4+
"version": "1.0.0-alpha.124",
55
"description": "Runtime of ZenStack for both client-side and server-side environments.",
66
"repository": {
77
"type": "git",

packages/runtime/src/enhancements/nested-write-vistor.ts

Lines changed: 98 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { FieldInfo, PrismaWriteActionType, PrismaWriteActions } from '../types';
55
import { resolveField } from './model-meta';
66
import { ModelMeta } from './types';
7-
import { Enumerable, ensureArray, getModelFields } from './utils';
7+
import { enumerate, getModelFields } from './utils';
88

99
type NestingPathItem = { field?: FieldInfo; where: any; unique: boolean };
1010

@@ -34,33 +34,25 @@ export type VisitorContext = {
3434
export type NestedWriterVisitorCallback = {
3535
create?: (model: string, args: any[], context: VisitorContext) => Promise<void>;
3636

37-
connectOrCreate?: (
38-
model: string,
39-
args: Enumerable<{ where: object; create: any }>,
40-
context: VisitorContext
41-
) => Promise<void>;
37+
connectOrCreate?: (model: string, args: { where: object; create: any }, context: VisitorContext) => Promise<void>;
4238

43-
connect?: (model: string, args: Enumerable<object>, context: VisitorContext) => Promise<void>;
39+
connect?: (model: string, args: object, context: VisitorContext) => Promise<void>;
4440

45-
disconnect?: (model: string, args: Enumerable<object>, context: VisitorContext) => Promise<void>;
41+
disconnect?: (model: string, args: object, context: VisitorContext) => Promise<void>;
4642

47-
update?: (model: string, args: Enumerable<{ where: object; data: any }>, context: VisitorContext) => Promise<void>;
43+
update?: (model: string, args: { where: object; data: any }, context: VisitorContext) => Promise<void>;
4844

49-
updateMany?: (
50-
model: string,
51-
args: Enumerable<{ where?: object; data: any }>,
52-
context: VisitorContext
53-
) => Promise<void>;
45+
updateMany?: (model: string, args: { where?: object; data: any }, context: VisitorContext) => Promise<void>;
5446

5547
upsert?: (
5648
model: string,
57-
args: Enumerable<{ where: object; create: any; update: any }>,
49+
args: { where: object; create: any; update: any },
5850
context: VisitorContext
5951
) => Promise<void>;
6052

61-
delete?: (model: string, args: Enumerable<object> | boolean, context: VisitorContext) => Promise<void>;
53+
delete?: (model: string, args: object | boolean, context: VisitorContext) => Promise<void>;
6254

63-
deleteMany?: (model: string, args: Enumerable<object>, context: VisitorContext) => Promise<void>;
55+
deleteMany?: (model: string, args: any | object, context: VisitorContext) => Promise<void>;
6456

6557
field?: (field: FieldInfo, action: PrismaWriteActionType, data: any, context: VisitorContext) => Promise<void>;
6658
};
@@ -115,126 +107,162 @@ export class NestedWriteVisitor {
115107
return;
116108
}
117109

118-
const fieldContainers: any[] = [];
119110
const isToOneUpdate = field?.isDataModel && !field.isArray;
120111
const context = { parent, field, nestingPath: [...nestingPath] };
121112

122113
// visit payload
123114
switch (action) {
124115
case 'create':
125116
context.nestingPath.push({ field, where: {}, unique: false });
126-
if (this.callback.create) {
127-
await this.callback.create(model, data, context);
117+
for (const item of enumerate(data)) {
118+
if (this.callback.create) {
119+
await this.callback.create(model, item, context);
120+
}
121+
await this.visitSubPayload(model, action, item, context.nestingPath);
128122
}
129-
fieldContainers.push(...ensureArray(data));
130123
break;
131124

132125
case 'createMany':
133126
// skip the 'data' layer so as to keep consistency with 'create'
134127
if (data.data) {
135128
context.nestingPath.push({ field, where: {}, unique: false });
136-
if (this.callback.create) {
137-
await this.callback.create(model, data.data, context);
129+
for (const item of enumerate(data.data)) {
130+
if (this.callback.create) {
131+
await this.callback.create(model, item, context);
132+
}
133+
await this.visitSubPayload(model, action, item, context.nestingPath);
138134
}
139-
fieldContainers.push(...ensureArray(data.data));
140135
}
141136
break;
142137

143138
case 'connectOrCreate':
144139
context.nestingPath.push({ field, where: data.where, unique: true });
145-
if (this.callback.connectOrCreate) {
146-
await this.callback.connectOrCreate(model, data, context);
140+
for (const item of enumerate(data)) {
141+
if (this.callback.connectOrCreate) {
142+
await this.callback.connectOrCreate(model, item, context);
143+
}
144+
await this.visitSubPayload(model, action, item.create, context.nestingPath);
147145
}
148-
fieldContainers.push(...ensureArray(data).map((d) => d.create));
149146
break;
150147

151148
case 'connect':
152-
context.nestingPath.push({ field, where: data, unique: true });
153149
if (this.callback.connect) {
154-
await this.callback.connect(model, data, context);
150+
for (const item of enumerate(data)) {
151+
const newContext = {
152+
...context,
153+
nestingPath: [...context.nestingPath, { field, where: item, unique: true }],
154+
};
155+
await this.callback.connect(model, item, newContext);
156+
}
155157
}
156158
break;
157159

158160
case 'disconnect':
159161
// disconnect has two forms:
160162
// if relation is to-many, the payload is a unique filter object
161163
// if relation is to-one, the payload can only be boolean `true`
162-
context.nestingPath.push({ field, where: data, unique: typeof data === 'object' });
163164
if (this.callback.disconnect) {
164-
await this.callback.disconnect(model, data, context);
165+
for (const item of enumerate(data)) {
166+
const newContext = {
167+
...context,
168+
nestingPath: [
169+
...context.nestingPath,
170+
{ field, where: item, unique: typeof item === 'object' },
171+
],
172+
};
173+
await this.callback.disconnect(model, item, newContext);
174+
}
165175
}
166176
break;
167177

168178
case 'update':
169179
context.nestingPath.push({ field, where: data.where, unique: false });
170-
if (this.callback.update) {
171-
await this.callback.update(model, data, context);
180+
for (const item of enumerate(data)) {
181+
if (this.callback.update) {
182+
await this.callback.update(model, item, context);
183+
}
184+
const payload = isToOneUpdate ? item : item.data;
185+
await this.visitSubPayload(model, action, payload, context.nestingPath);
172186
}
173-
fieldContainers.push(...ensureArray(data).map((d) => (isToOneUpdate ? d : d.data)));
174187
break;
175188

176189
case 'updateMany':
177190
context.nestingPath.push({ field, where: data.where, unique: false });
178-
if (this.callback.updateMany) {
179-
await this.callback.updateMany(model, data, context);
191+
for (const item of enumerate(data)) {
192+
if (this.callback.updateMany) {
193+
await this.callback.updateMany(model, item, context);
194+
}
195+
await this.visitSubPayload(model, action, item, context.nestingPath);
180196
}
181-
fieldContainers.push(...ensureArray(data));
182197
break;
183198

184-
case 'upsert':
199+
case 'upsert': {
185200
context.nestingPath.push({ field, where: data.where, unique: true });
186-
if (this.callback.upsert) {
187-
await this.callback.upsert(model, data, context);
201+
for (const item of enumerate(data)) {
202+
if (this.callback.upsert) {
203+
await this.callback.upsert(model, item, context);
204+
}
205+
await this.visitSubPayload(model, action, item.create, context.nestingPath);
206+
await this.visitSubPayload(model, action, item.update, context.nestingPath);
188207
}
189-
fieldContainers.push(...ensureArray(data).map((d) => d.create));
190-
fieldContainers.push(...ensureArray(data).map((d) => d.update));
191208
break;
209+
}
192210

193-
case 'delete':
194-
context.nestingPath.push({ field, where: data.where, unique: false });
211+
case 'delete': {
195212
if (this.callback.delete) {
196-
await this.callback.delete(model, data, context);
213+
context.nestingPath.push({ field, where: data.where, unique: false });
214+
for (const item of enumerate(data)) {
215+
await this.callback.delete(model, item, context);
216+
}
197217
}
198218
break;
219+
}
199220

200221
case 'deleteMany':
201-
context.nestingPath.push({ field, where: data.where, unique: false });
202222
if (this.callback.deleteMany) {
203-
await this.callback.deleteMany(model, data, context);
223+
context.nestingPath.push({ field, where: data.where, unique: false });
224+
for (const item of enumerate(data)) {
225+
await this.callback.deleteMany(model, item, context);
226+
}
204227
}
205228
break;
206229

207230
default: {
208231
throw new Error(`unhandled action type ${action}`);
209232
}
210233
}
234+
}
211235

212-
for (const fieldContainer of fieldContainers) {
213-
for (const field of getModelFields(fieldContainer)) {
214-
const fieldInfo = resolveField(this.modelMeta, model, field);
215-
if (!fieldInfo) {
216-
continue;
217-
}
236+
private async visitSubPayload(
237+
model: string,
238+
action: PrismaWriteActionType,
239+
payload: any,
240+
nestingPath: NestingPathItem[]
241+
) {
242+
for (const field of getModelFields(payload)) {
243+
const fieldInfo = resolveField(this.modelMeta, model, field);
244+
if (!fieldInfo) {
245+
continue;
246+
}
218247

219-
if (fieldInfo.isDataModel) {
220-
// recurse into nested payloads
221-
for (const [subAction, subData] of Object.entries<any>(fieldContainer[field])) {
222-
if (this.isPrismaWriteAction(subAction) && subData) {
223-
await this.doVisit(fieldInfo.type, subAction, subData, fieldContainer[field], fieldInfo, [
224-
...context.nestingPath,
225-
]);
226-
}
227-
}
228-
} else {
229-
// visit plain field
230-
if (this.callback.field) {
231-
await this.callback.field(fieldInfo, action, fieldContainer[field], {
232-
parent: fieldContainer,
233-
nestingPath: [...context.nestingPath],
234-
field: fieldInfo,
235-
});
248+
if (fieldInfo.isDataModel) {
249+
// recurse into nested payloads
250+
for (const [subAction, subData] of Object.entries<any>(payload[field])) {
251+
if (this.isPrismaWriteAction(subAction) && subData) {
252+
await this.doVisit(fieldInfo.type, subAction, subData, payload[field], fieldInfo, [
253+
...nestingPath,
254+
]);
236255
}
237256
}
257+
} else {
258+
// visit plain field
259+
if (this.callback.field) {
260+
await this.callback.field(fieldInfo, action, payload[field], {
261+
parent: payload,
262+
nestingPath,
263+
field: fieldInfo,
264+
});
265+
}
238266
}
239267
}
240268
}

packages/runtime/src/enhancements/omit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { DbClientContract } from '../types';
55
import { getDefaultModelMeta, resolveField } from './model-meta';
66
import { DefaultPrismaProxyHandler, makeProxy } from './proxy';
77
import { ModelMeta } from './types';
8-
import { ensureArray, getModelFields } from './utils';
8+
import { enumerate, getModelFields } from './utils';
99

1010
/**
1111
* Gets an enhanced Prisma client that supports @omit attribute.
@@ -28,7 +28,7 @@ class OmitHandler extends DefaultPrismaProxyHandler {
2828
// base override
2929
protected async processResultEntity<T>(data: T): Promise<T> {
3030
if (data) {
31-
for (const value of ensureArray(data)) {
31+
for (const value of enumerate(data)) {
3232
await this.doPostProcess(value, this.model);
3333
}
3434
}

0 commit comments

Comments
 (0)