@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.
@@ -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 await resolve(ctx, id);
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 (model: EntityModel, input: Entity, ctx: MutationContext) => {
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: 'mutation', when: 'before', data, ctx });
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 data) {
82
+ if (columnName in normalizedInput) {
75
83
  if (field.inherited) {
76
- rootInput[columnName] = data[columnName];
84
+ rootInput[columnName] = normalizedInput[columnName];
77
85
  } else {
78
- childInput[columnName] = data[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(data);
93
+ await ctx.knex(model.name).insert(normalizedInput);
86
94
  }
87
- await createRevision(model, data, ctx);
88
- await ctx.mutationHook?.({ model, action: 'create', trigger: 'mutation', when: 'after', data, ctx });
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 (model: EntityModel, id: string, input: Entity, ctx: MutationContext) => {
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 prev = await getEntityToMutate(ctx, model, { id }, 'UPDATE');
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] === prev[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
- const next = { ...prev, ...normalizedInput };
124
- const data = { prev, input, normalizedInput, next };
125
- await ctx.mutationHook?.({ model, action: 'update', trigger: 'mutation', when: 'before', data, ctx });
126
-
127
- await doUpdate(model, normalizedInput, next, ctx);
128
- await ctx.mutationHook?.({ model, action: 'update', trigger: 'mutation', when: 'after', data, ctx });
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, normalizedInput, next, ctx);
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, normalizedInput, next, ctx);
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 (model: EntityModel, id: string, ctx: MutationContext) => {
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!({ model: currentModel, action: 'restore', trigger: 'mutation', when: 'before', data, ctx });
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, normalizedInput, data.next, ctx);
460
+ await doUpdate(currentModel, currentEntity, normalizedInput, ctx);
430
461
  });
431
462
  if (ctx.mutationHook) {
432
463
  afterHooks.push(async () => {
433
- await ctx.mutationHook!({ model: currentModel, action: 'restore', trigger: 'mutation', when: 'after', data, ctx });
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, updateFields: Entity, allFields: Entity, ctx: MutationContext) => {
585
+ const doUpdate = async (model: EntityModel, currentEntity: Entity, update: Entity, ctx: MutationContext) => {
550
586
  if (model.updatable) {
551
- if (!updateFields.updatedAt) {
552
- updateFields.updatedAt = ctx.now;
587
+ if (!update.updatedAt) {
588
+ update.updatedAt = ctx.now;
553
589
  }
554
- if (!updateFields.updatedById) {
555
- updateFields.updatedById = ctx.user?.id;
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 updateFields) {
599
+ if (columnName in update) {
564
600
  if (field.inherited) {
565
- rootInput[columnName] = updateFields[columnName];
601
+ rootInput[columnName] = update[columnName];
566
602
  } else {
567
- childInput[columnName] = updateFields[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: allFields.id }).update(rootInput);
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: allFields.id }).update(childInput);
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: allFields.id }).update(updateFields);
614
+ await ctx.knex(model.name).where({ id: currentEntity.id }).update(update);
579
615
  }
580
- await createRevision(model, allFields, ctx);
616
+ await createRevision(model, { ...currentEntity, ...update }, ctx);
581
617
  };