@smartive/graphql-magic 19.2.0-next.1 → 19.2.0-next.2
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 +2 -2
- package/dist/bin/gqm.cjs +1 -0
- package/dist/cjs/index.cjs +84 -60
- package/dist/esm/models/mutation-hook.d.ts +2 -1
- package/dist/esm/models/utils.js +1 -0
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/resolvers/mutations.d.ts +7 -6
- package/dist/esm/resolvers/mutations.js +81 -60
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/package.json +1 -1
- package/src/models/mutation-hook.ts +3 -1
- package/src/models/utils.ts +1 -0
- package/src/resolvers/mutations.ts +104 -68
|
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
3
3
|
import { Context } from '../context';
|
|
4
4
|
import { ForbiddenError, GraphQLError } from '../errors';
|
|
5
5
|
import { EntityField, EntityModel } from '../models/models';
|
|
6
|
-
import { Entity, MutationContext } from '../models/mutation-hook';
|
|
6
|
+
import { Entity, MutationContext, Trigger } from '../models/mutation-hook';
|
|
7
7
|
import { get, isPrimitive, it, typeToField } from '../models/utils';
|
|
8
8
|
import { applyPermissions, checkCanWrite, getEntityToMutate } from '../permissions/check';
|
|
9
9
|
import { anyDateToLuxon } from '../utils';
|
|
@@ -17,7 +17,7 @@ export const mutationResolver = async (_parent: any, args: any, partialCtx: Cont
|
|
|
17
17
|
const model = ctx.models.getModel(modelName, 'entity');
|
|
18
18
|
switch (mutation) {
|
|
19
19
|
case 'create': {
|
|
20
|
-
const id = await createEntity(model, args.data, ctx);
|
|
20
|
+
const id = await createEntity(model, args.data, ctx, 'mutation');
|
|
21
21
|
|
|
22
22
|
return await resolve(ctx, id);
|
|
23
23
|
}
|
|
@@ -31,17 +31,27 @@ export const mutationResolver = async (_parent: any, args: any, partialCtx: Cont
|
|
|
31
31
|
case 'delete': {
|
|
32
32
|
const id = args.where.id;
|
|
33
33
|
|
|
34
|
-
await deleteEntity(model, id, args.dryRun, model.rootModel.name, id, ctx);
|
|
34
|
+
await deleteEntity(model, id, args.dryRun, model.rootModel.name, id, ctx, 'mutation');
|
|
35
35
|
|
|
36
|
-
return
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
38
|
+
case 'restore': {
|
|
39
|
+
const id = args.where.id;
|
|
40
|
+
|
|
41
|
+
await restoreEntity(model, id, ctx, 'mutation');
|
|
42
|
+
|
|
43
|
+
return id;
|
|
37
44
|
}
|
|
38
|
-
case 'restore':
|
|
39
|
-
return await restoreEntity(model, args.where.id, ctx);
|
|
40
45
|
}
|
|
41
46
|
});
|
|
42
47
|
};
|
|
43
48
|
|
|
44
|
-
export const createEntity = async (
|
|
49
|
+
export const createEntity = async (
|
|
50
|
+
model: EntityModel,
|
|
51
|
+
input: Entity,
|
|
52
|
+
ctx: MutationContext,
|
|
53
|
+
trigger: Trigger = 'direct-call',
|
|
54
|
+
) => {
|
|
45
55
|
const normalizedInput = { ...input };
|
|
46
56
|
if (!normalizedInput.id) {
|
|
47
57
|
normalizedInput.id = uuid();
|
|
@@ -62,30 +72,28 @@ export const createEntity = async (model: EntityModel, input: Entity, ctx: Mutat
|
|
|
62
72
|
await ctx.handleUploads?.(normalizedInput);
|
|
63
73
|
|
|
64
74
|
const data = { prev: {}, input, normalizedInput, next: normalizedInput };
|
|
65
|
-
await ctx.mutationHook?.({ model, action: 'create', trigger
|
|
66
|
-
|
|
67
|
-
await createEntity(model, normalizedInput, ctx);
|
|
75
|
+
await ctx.mutationHook?.({ model, action: 'create', trigger, when: 'before', data, ctx });
|
|
68
76
|
|
|
69
77
|
if (model.parent) {
|
|
70
78
|
const rootInput = {};
|
|
71
79
|
const childInput = { id };
|
|
72
80
|
for (const field of model.fields) {
|
|
73
81
|
const columnName = field.kind === 'relation' ? `${field.name}Id` : field.name;
|
|
74
|
-
if (columnName in
|
|
82
|
+
if (columnName in normalizedInput) {
|
|
75
83
|
if (field.inherited) {
|
|
76
|
-
rootInput[columnName] =
|
|
84
|
+
rootInput[columnName] = normalizedInput[columnName];
|
|
77
85
|
} else {
|
|
78
|
-
childInput[columnName] =
|
|
86
|
+
childInput[columnName] = normalizedInput[columnName];
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
await ctx.knex(model.parent).insert(rootInput);
|
|
83
91
|
await ctx.knex(model.name).insert(childInput);
|
|
84
92
|
} else {
|
|
85
|
-
await ctx.knex(model.name).insert(
|
|
93
|
+
await ctx.knex(model.name).insert(normalizedInput);
|
|
86
94
|
}
|
|
87
|
-
await createRevision(model,
|
|
88
|
-
await ctx.mutationHook?.({ model, action: 'create', trigger
|
|
95
|
+
await createRevision(model, normalizedInput, ctx);
|
|
96
|
+
await ctx.mutationHook?.({ model, action: 'create', trigger, when: 'after', data, ctx });
|
|
89
97
|
|
|
90
98
|
return normalizedInput.id as string;
|
|
91
99
|
};
|
|
@@ -102,16 +110,22 @@ export const updateEntities = async (
|
|
|
102
110
|
}
|
|
103
111
|
};
|
|
104
112
|
|
|
105
|
-
export const updateEntity = async (
|
|
113
|
+
export const updateEntity = async (
|
|
114
|
+
model: EntityModel,
|
|
115
|
+
id: string,
|
|
116
|
+
input: Entity,
|
|
117
|
+
ctx: MutationContext,
|
|
118
|
+
trigger: Trigger = 'direct-call',
|
|
119
|
+
) => {
|
|
106
120
|
const normalizedInput = { ...input };
|
|
107
121
|
|
|
108
122
|
sanitize(ctx, model, normalizedInput);
|
|
109
123
|
|
|
110
|
-
const
|
|
124
|
+
const currentEntity = await getEntityToMutate(ctx, model, { id }, 'UPDATE');
|
|
111
125
|
|
|
112
126
|
// Remove data that wouldn't mutate given that it's irrelevant for permissions
|
|
113
127
|
for (const key of Object.keys(normalizedInput)) {
|
|
114
|
-
if (normalizedInput[key] ===
|
|
128
|
+
if (normalizedInput[key] === currentEntity[key]) {
|
|
115
129
|
delete normalizedInput[key];
|
|
116
130
|
}
|
|
117
131
|
}
|
|
@@ -120,12 +134,23 @@ export const updateEntity = async (model: EntityModel, id: string, input: Entity
|
|
|
120
134
|
await checkCanWrite(ctx, model, normalizedInput, 'UPDATE');
|
|
121
135
|
await ctx.handleUploads?.(normalizedInput);
|
|
122
136
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
await ctx.mutationHook?.({
|
|
138
|
+
model,
|
|
139
|
+
action: 'update',
|
|
140
|
+
trigger,
|
|
141
|
+
when: 'before',
|
|
142
|
+
data: { prev: currentEntity, input, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
|
|
143
|
+
ctx,
|
|
144
|
+
});
|
|
145
|
+
await doUpdate(model, currentEntity, normalizedInput, ctx);
|
|
146
|
+
await ctx.mutationHook?.({
|
|
147
|
+
model,
|
|
148
|
+
action: 'update',
|
|
149
|
+
trigger,
|
|
150
|
+
when: 'after',
|
|
151
|
+
data: { prev: currentEntity, input, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
|
|
152
|
+
ctx,
|
|
153
|
+
});
|
|
129
154
|
}
|
|
130
155
|
};
|
|
131
156
|
|
|
@@ -134,8 +159,8 @@ type Callbacks = (() => Promise<void>)[];
|
|
|
134
159
|
export const deleteEntities = async (
|
|
135
160
|
model: EntityModel,
|
|
136
161
|
where: Record<string, unknown>,
|
|
137
|
-
deleteRootType: string,
|
|
138
|
-
deleteRootId: string,
|
|
162
|
+
deleteRootType: string | undefined,
|
|
163
|
+
deleteRootId: string | undefined,
|
|
139
164
|
ctx: MutationContext,
|
|
140
165
|
) => {
|
|
141
166
|
const entities = await ctx.knex(model.name).where(where).select('id');
|
|
@@ -148,9 +173,10 @@ export const deleteEntity = async (
|
|
|
148
173
|
model: EntityModel,
|
|
149
174
|
id: string,
|
|
150
175
|
dryRun: boolean,
|
|
151
|
-
deleteRootType: string,
|
|
152
|
-
deleteRootId: string,
|
|
176
|
+
deleteRootType: string | undefined = model.rootModel.name,
|
|
177
|
+
deleteRootId: string | undefined = id,
|
|
153
178
|
ctx: MutationContext,
|
|
179
|
+
trigger: Trigger = 'direct-call',
|
|
154
180
|
) => {
|
|
155
181
|
const rootModel = model.rootModel;
|
|
156
182
|
const entity = await getEntityToMutate(ctx, rootModel, { id }, 'DELETE');
|
|
@@ -186,7 +212,7 @@ export const deleteEntity = async (
|
|
|
186
212
|
const afterHooks: Callbacks = [];
|
|
187
213
|
|
|
188
214
|
const mutationHook = ctx.mutationHook;
|
|
189
|
-
const deleteCascade = async (currentModel: EntityModel, currentEntity: Entity) => {
|
|
215
|
+
const deleteCascade = async (currentModel: EntityModel, currentEntity: Entity, currentTrigger: Trigger) => {
|
|
190
216
|
if (!(currentModel.name in toDelete)) {
|
|
191
217
|
toDelete[currentModel.name] = {};
|
|
192
218
|
}
|
|
@@ -194,7 +220,6 @@ export const deleteEntity = async (
|
|
|
194
220
|
return;
|
|
195
221
|
}
|
|
196
222
|
toDelete[currentModel.name][currentEntity.id as string] = await fetchDisplay(ctx.knex, currentModel, currentEntity);
|
|
197
|
-
const trigger = currentModel.name === rootModel.name && currentEntity.id === entity.id ? 'mutation' : 'cascade';
|
|
198
223
|
|
|
199
224
|
if (!dryRun) {
|
|
200
225
|
const normalizedInput = {
|
|
@@ -204,31 +229,29 @@ export const deleteEntity = async (
|
|
|
204
229
|
deleteRootType,
|
|
205
230
|
deleteRootId,
|
|
206
231
|
};
|
|
207
|
-
const next = { ...currentEntity, ...normalizedInput };
|
|
208
|
-
const data = { prev: currentEntity, input: {}, normalizedInput, next };
|
|
209
232
|
if (mutationHook) {
|
|
210
233
|
beforeHooks.push(async () => {
|
|
211
234
|
await mutationHook({
|
|
212
235
|
model: currentModel,
|
|
213
236
|
action: 'delete',
|
|
214
|
-
trigger,
|
|
237
|
+
trigger: currentTrigger,
|
|
215
238
|
when: 'before',
|
|
216
|
-
data,
|
|
239
|
+
data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
|
|
217
240
|
ctx,
|
|
218
241
|
});
|
|
219
242
|
});
|
|
220
243
|
}
|
|
221
244
|
mutations.push(async () => {
|
|
222
|
-
await doUpdate(currentModel,
|
|
245
|
+
await doUpdate(currentModel, currentEntity, normalizedInput, ctx);
|
|
223
246
|
});
|
|
224
247
|
if (mutationHook) {
|
|
225
248
|
afterHooks.push(async () => {
|
|
226
249
|
await mutationHook({
|
|
227
250
|
model: currentModel,
|
|
228
251
|
action: 'delete',
|
|
229
|
-
trigger,
|
|
252
|
+
trigger: currentTrigger,
|
|
230
253
|
when: 'after',
|
|
231
|
-
data,
|
|
254
|
+
data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
|
|
232
255
|
ctx,
|
|
233
256
|
});
|
|
234
257
|
});
|
|
@@ -258,8 +281,6 @@ export const deleteEntity = async (
|
|
|
258
281
|
toUnlink[descendantModel.name][descendant.id].fields.push(name);
|
|
259
282
|
} else {
|
|
260
283
|
const normalizedInput = { [`${name}Id`]: null };
|
|
261
|
-
const next = { ...descendant, ...normalizedInput };
|
|
262
|
-
const data = { prev: descendant, input: {}, normalizedInput, next };
|
|
263
284
|
if (mutationHook) {
|
|
264
285
|
beforeHooks.push(async () => {
|
|
265
286
|
await mutationHook({
|
|
@@ -267,13 +288,13 @@ export const deleteEntity = async (
|
|
|
267
288
|
action: 'update',
|
|
268
289
|
trigger: 'set-null',
|
|
269
290
|
when: 'before',
|
|
270
|
-
data,
|
|
291
|
+
data: { prev: descendant, input: {}, normalizedInput, next: { ...descendant, ...normalizedInput } },
|
|
271
292
|
ctx,
|
|
272
293
|
});
|
|
273
294
|
});
|
|
274
295
|
}
|
|
275
296
|
mutations.push(async () => {
|
|
276
|
-
await doUpdate(descendantModel,
|
|
297
|
+
await doUpdate(descendantModel, descendant, normalizedInput, ctx);
|
|
277
298
|
});
|
|
278
299
|
if (mutationHook) {
|
|
279
300
|
afterHooks.push(async () => {
|
|
@@ -282,7 +303,7 @@ export const deleteEntity = async (
|
|
|
282
303
|
action: 'update',
|
|
283
304
|
trigger: 'set-null',
|
|
284
305
|
when: 'after',
|
|
285
|
-
data,
|
|
306
|
+
data: { prev: descendant, input: {}, normalizedInput, next: { ...descendant, ...normalizedInput } },
|
|
286
307
|
ctx,
|
|
287
308
|
});
|
|
288
309
|
});
|
|
@@ -328,7 +349,7 @@ export const deleteEntity = async (
|
|
|
328
349
|
);
|
|
329
350
|
}
|
|
330
351
|
for (const descendant of descendants) {
|
|
331
|
-
await deleteCascade(descendantModel, descendant);
|
|
352
|
+
await deleteCascade(descendantModel, descendant, 'cascade');
|
|
332
353
|
}
|
|
333
354
|
break;
|
|
334
355
|
}
|
|
@@ -337,7 +358,7 @@ export const deleteEntity = async (
|
|
|
337
358
|
}
|
|
338
359
|
};
|
|
339
360
|
|
|
340
|
-
await deleteCascade(rootModel, entity);
|
|
361
|
+
await deleteCascade(rootModel, entity, trigger);
|
|
341
362
|
|
|
342
363
|
for (const callback of [...beforeHooks, ...mutations, ...afterHooks]) {
|
|
343
364
|
await callback();
|
|
@@ -351,11 +372,14 @@ export const deleteEntity = async (
|
|
|
351
372
|
restricted,
|
|
352
373
|
});
|
|
353
374
|
}
|
|
354
|
-
|
|
355
|
-
return entity.id;
|
|
356
375
|
};
|
|
357
376
|
|
|
358
|
-
const restoreEntity = async (
|
|
377
|
+
export const restoreEntity = async (
|
|
378
|
+
model: EntityModel,
|
|
379
|
+
id: string,
|
|
380
|
+
ctx: MutationContext,
|
|
381
|
+
trigger: Trigger = 'direct-call',
|
|
382
|
+
) => {
|
|
359
383
|
const rootModel = model.rootModel;
|
|
360
384
|
|
|
361
385
|
const entity = await getEntityToMutate(ctx, rootModel, { id }, 'RESTORE');
|
|
@@ -378,7 +402,7 @@ const restoreEntity = async (model: EntityModel, id: string, ctx: MutationContex
|
|
|
378
402
|
const mutations: Callbacks = [];
|
|
379
403
|
const afterHooks: Callbacks = [];
|
|
380
404
|
|
|
381
|
-
const restoreCascade = async (currentModel: EntityModel, currentEntity: Entity) => {
|
|
405
|
+
const restoreCascade = async (currentModel: EntityModel, currentEntity: Entity, currentTrigger: Trigger) => {
|
|
382
406
|
if (entity.deleteRootId) {
|
|
383
407
|
if (!(currentEntity.deleteRootType === model.name && currentEntity.deleteRootId === entity.id)) {
|
|
384
408
|
return;
|
|
@@ -422,15 +446,29 @@ const restoreEntity = async (model: EntityModel, id: string, ctx: MutationContex
|
|
|
422
446
|
const data = { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } };
|
|
423
447
|
if (ctx.mutationHook) {
|
|
424
448
|
beforeHooks.push(async () => {
|
|
425
|
-
await ctx.mutationHook!({
|
|
449
|
+
await ctx.mutationHook!({
|
|
450
|
+
model: currentModel,
|
|
451
|
+
action: 'restore',
|
|
452
|
+
trigger: currentTrigger,
|
|
453
|
+
when: 'before',
|
|
454
|
+
data,
|
|
455
|
+
ctx,
|
|
456
|
+
});
|
|
426
457
|
});
|
|
427
458
|
}
|
|
428
459
|
mutations.push(async () => {
|
|
429
|
-
await doUpdate(currentModel,
|
|
460
|
+
await doUpdate(currentModel, currentEntity, normalizedInput, ctx);
|
|
430
461
|
});
|
|
431
462
|
if (ctx.mutationHook) {
|
|
432
463
|
afterHooks.push(async () => {
|
|
433
|
-
await ctx.mutationHook!({
|
|
464
|
+
await ctx.mutationHook!({
|
|
465
|
+
model: currentModel,
|
|
466
|
+
action: 'restore',
|
|
467
|
+
trigger: currentTrigger,
|
|
468
|
+
when: 'after',
|
|
469
|
+
data,
|
|
470
|
+
ctx,
|
|
471
|
+
});
|
|
434
472
|
});
|
|
435
473
|
}
|
|
436
474
|
|
|
@@ -453,18 +491,16 @@ const restoreEntity = async (model: EntityModel, id: string, ctx: MutationContex
|
|
|
453
491
|
);
|
|
454
492
|
}
|
|
455
493
|
for (const descendant of deletedDescendants) {
|
|
456
|
-
await restoreCascade(descendantModel, descendant);
|
|
494
|
+
await restoreCascade(descendantModel, descendant, 'cascade');
|
|
457
495
|
}
|
|
458
496
|
}
|
|
459
497
|
};
|
|
460
498
|
|
|
461
|
-
await restoreCascade(rootModel, entity);
|
|
499
|
+
await restoreCascade(rootModel, entity, trigger);
|
|
462
500
|
|
|
463
501
|
for (const callback of [...beforeHooks, ...mutations, ...afterHooks]) {
|
|
464
502
|
await callback();
|
|
465
503
|
}
|
|
466
|
-
|
|
467
|
-
return id;
|
|
468
504
|
};
|
|
469
505
|
|
|
470
506
|
export const createRevision = async (model: EntityModel, data: Entity, ctx: MutationContext) => {
|
|
@@ -546,13 +582,13 @@ const isEndOfDay = (field: EntityField) =>
|
|
|
546
582
|
const isEndOfMonth = (field: EntityField) =>
|
|
547
583
|
isPrimitive(field) && field.type === 'DateTime' && field?.endOfMonth === true && field?.dateTimeType === 'year_and_month';
|
|
548
584
|
|
|
549
|
-
const doUpdate = async (model: EntityModel,
|
|
585
|
+
const doUpdate = async (model: EntityModel, currentEntity: Entity, update: Entity, ctx: MutationContext) => {
|
|
550
586
|
if (model.updatable) {
|
|
551
|
-
if (!
|
|
552
|
-
|
|
587
|
+
if (!update.updatedAt) {
|
|
588
|
+
update.updatedAt = ctx.now;
|
|
553
589
|
}
|
|
554
|
-
if (!
|
|
555
|
-
|
|
590
|
+
if (!update.updatedById) {
|
|
591
|
+
update.updatedById = ctx.user?.id;
|
|
556
592
|
}
|
|
557
593
|
}
|
|
558
594
|
if (model.parent) {
|
|
@@ -560,22 +596,22 @@ const doUpdate = async (model: EntityModel, updateFields: Entity, allFields: Ent
|
|
|
560
596
|
const childInput = {};
|
|
561
597
|
for (const field of model.fields) {
|
|
562
598
|
const columnName = field.kind === 'relation' ? `${field.name}Id` : field.name;
|
|
563
|
-
if (columnName in
|
|
599
|
+
if (columnName in update) {
|
|
564
600
|
if (field.inherited) {
|
|
565
|
-
rootInput[columnName] =
|
|
601
|
+
rootInput[columnName] = update[columnName];
|
|
566
602
|
} else {
|
|
567
|
-
childInput[columnName] =
|
|
603
|
+
childInput[columnName] = update[columnName];
|
|
568
604
|
}
|
|
569
605
|
}
|
|
570
606
|
}
|
|
571
607
|
if (Object.keys(rootInput).length) {
|
|
572
|
-
await ctx.knex(model.parent).where({ id:
|
|
608
|
+
await ctx.knex(model.parent).where({ id: currentEntity.id }).update(rootInput);
|
|
573
609
|
}
|
|
574
610
|
if (Object.keys(childInput).length) {
|
|
575
|
-
await ctx.knex(model.name).where({ id:
|
|
611
|
+
await ctx.knex(model.name).where({ id: currentEntity.id }).update(childInput);
|
|
576
612
|
}
|
|
577
613
|
} else {
|
|
578
|
-
await ctx.knex(model.name).where({ id:
|
|
614
|
+
await ctx.knex(model.name).where({ id: currentEntity.id }).update(update);
|
|
579
615
|
}
|
|
580
|
-
await createRevision(model,
|
|
616
|
+
await createRevision(model, { ...currentEntity, ...update }, ctx);
|
|
581
617
|
};
|