@smartive/graphql-magic 23.10.0 → 23.11.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartive/graphql-magic",
3
- "version": "23.10.0",
3
+ "version": "23.11.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,39 +1,70 @@
1
1
  import upperCase from 'lodash/upperCase';
2
+ import { Field } from '../schema/utils';
2
3
  import { isRootModel, not } from '..';
3
4
  import { Models } from '../models/models';
4
5
 
5
6
  const constantCase = (str: string) => upperCase(str).replace(/ /g, '_');
6
7
 
8
+ const argTypeStr = (field: Field) => {
9
+ const base = field.list ? `[${field.type}!]` : field.type;
10
+
11
+ return field.nonNull ? `${base}!` : base;
12
+ };
13
+
14
+ const argsToVariables = (args: readonly Field[]) => args.map((a) => `$${a.name}: ${argTypeStr(a)}`).join(', ');
15
+
16
+ const argsToMutationArgs = (args: readonly Field[]) => args.map((a) => `${a.name}: $${a.name}`).join(', ');
17
+
7
18
  export const generateMutations = (models: Models) => {
8
19
  const parts: string[] = [];
9
20
  for (const { name, creatable, updatable, deletable } of models.entities.filter(not(isRootModel))) {
10
21
  if (creatable) {
22
+ const extraArgs = creatable !== true && creatable.args ? creatable.args : [];
23
+ const variables = extraArgs.length ? `$data: Create${name}!, ${argsToVariables(extraArgs)}` : `$data: Create${name}!`;
24
+ const mutationArgs = extraArgs.length ? `data: $data, ${argsToMutationArgs(extraArgs)}` : `data: $data`;
11
25
  parts.push(
12
26
  `export const CREATE_${constantCase(
13
27
  name,
14
- )} = gql\`\n mutation Create${name}Mutation($data: Create${name}!) {\n create${name}(data: $data) { id }\n }\n\`;`,
28
+ )} = gql\`\n mutation Create${name}Mutation(${variables}) {\n create${name}(${mutationArgs}) { id }\n }\n\`;`,
15
29
  );
16
30
  }
17
31
 
18
32
  if (updatable) {
33
+ const extraArgs = updatable !== true && updatable.args ? updatable.args : [];
34
+ const variables = extraArgs.length
35
+ ? `$id: ID!, $data: Update${name}!, ${argsToVariables(extraArgs)}`
36
+ : `$id: ID!, $data: Update${name}!`;
37
+ const mutationArgs = extraArgs.length
38
+ ? `where: { id: $id }, data: $data, ${argsToMutationArgs(extraArgs)}`
39
+ : `where: { id: $id }, data: $data`;
19
40
  parts.push(
20
41
  `export const UPDATE_${constantCase(
21
42
  name,
22
- )} = gql\`\n mutation Update${name}Mutation($id: ID!, $data: Update${name}!) {\n update${name}(where: { id: $id }, data: $data) { id }\n }\n\`;`,
43
+ )} = gql\`\n mutation Update${name}Mutation(${variables}) {\n update${name}(${mutationArgs}) { id }\n }\n\`;`,
23
44
  );
24
45
  }
25
46
 
26
47
  if (deletable) {
48
+ const deleteExtraArgs = deletable !== true && deletable.args ? deletable.args : [];
49
+ const deleteVariables = deleteExtraArgs.length ? `$id: ID!, ${argsToVariables(deleteExtraArgs)}` : `$id: ID!`;
50
+ const deleteMutationArgs = deleteExtraArgs.length
51
+ ? `where: { id: $id }, ${argsToMutationArgs(deleteExtraArgs)}`
52
+ : `where: { id: $id }`;
27
53
  parts.push(
28
54
  `export const DELETE_${constantCase(
29
55
  name,
30
- )} = gql\`\n mutation Delete${name}Mutation($id: ID!) {\n delete${name}(where: { id: $id })\n }\n\`;`,
56
+ )} = gql\`\n mutation Delete${name}Mutation(${deleteVariables}) {\n delete${name}(${deleteMutationArgs})\n }\n\`;`,
31
57
  );
32
58
 
59
+ const restoreExtraArgs = deletable !== true && deletable.restoreArgs ? deletable.restoreArgs : [];
60
+ const restoreVariables = restoreExtraArgs.length ? `$id: ID!, ${argsToVariables(restoreExtraArgs)}` : `$id: ID!`;
61
+ const restoreMutationArgs = restoreExtraArgs.length
62
+ ? `where: { id: $id }, ${argsToMutationArgs(restoreExtraArgs)}`
63
+ : `where: { id: $id }`;
33
64
  parts.push(
34
65
  `export const RESTORE_${constantCase(
35
66
  name,
36
- )} = gql\`\n mutation Restore${name}Mutation($id: ID!) {\n restore${name}(where: { id: $id })\n }\n\`;`,
67
+ )} = gql\`\n mutation Restore${name}Mutation(${restoreVariables}) {\n restore${name}(${restoreMutationArgs})\n }\n\`;`,
37
68
  );
38
69
  }
39
70
  }
@@ -1026,19 +1026,11 @@ export class MigrationGenerator {
1026
1026
  return -1;
1027
1027
  }
1028
1028
 
1029
- private static readonly TRIGGER_EVENT_ORDER: readonly ('INSERT' | 'UPDATE' | 'DELETE')[] = [
1030
- 'INSERT',
1031
- 'UPDATE',
1032
- 'DELETE',
1033
- ];
1034
-
1035
- private static sortTriggerEvents(
1036
- events: readonly ('INSERT' | 'UPDATE' | 'DELETE')[],
1037
- ): ('INSERT' | 'UPDATE' | 'DELETE')[] {
1029
+ private static readonly TRIGGER_EVENT_ORDER: readonly ('INSERT' | 'UPDATE' | 'DELETE')[] = ['INSERT', 'UPDATE', 'DELETE'];
1030
+
1031
+ private static sortTriggerEvents(events: readonly ('INSERT' | 'UPDATE' | 'DELETE')[]): ('INSERT' | 'UPDATE' | 'DELETE')[] {
1038
1032
  return [...events].sort(
1039
- (a, b) =>
1040
- MigrationGenerator.TRIGGER_EVENT_ORDER.indexOf(a) -
1041
- MigrationGenerator.TRIGGER_EVENT_ORDER.indexOf(b),
1033
+ (a, b) => MigrationGenerator.TRIGGER_EVENT_ORDER.indexOf(a) - MigrationGenerator.TRIGGER_EVENT_ORDER.indexOf(b),
1042
1034
  );
1043
1035
  }
1044
1036
 
@@ -1050,7 +1042,9 @@ export class MigrationGenerator {
1050
1042
  .replace(/\bON\s+[a-zA-Z_][a-zA-Z0-9_]*\./gi, 'ON ')
1051
1043
  .trim();
1052
1044
 
1053
- const eventsMatch = s.match(/\b(AFTER|BEFORE)\s+((?:INSERT|UPDATE|DELETE)(?:\s+OR\s+(?:INSERT|UPDATE|DELETE))+)\s+ON\b/i);
1045
+ const eventsMatch = s.match(
1046
+ /\b(AFTER|BEFORE)\s+((?:INSERT|UPDATE|DELETE)(?:\s+OR\s+(?:INSERT|UPDATE|DELETE))+)\s+ON\b/i,
1047
+ );
1054
1048
  if (eventsMatch) {
1055
1049
  const events = eventsMatch[2].split(/\s+OR\s+/).map((e) => e.toUpperCase());
1056
1050
  const sorted = [...events].sort(
@@ -1311,8 +1305,7 @@ export class MigrationGenerator {
1311
1305
 
1312
1306
  private sortTriggerEvents(events: readonly ('INSERT' | 'UPDATE' | 'DELETE')[]): ('INSERT' | 'UPDATE' | 'DELETE')[] {
1313
1307
  return [...events].sort(
1314
- (a, b) =>
1315
- MigrationGenerator.TRIGGER_EVENT_ORDER.indexOf(a) - MigrationGenerator.TRIGGER_EVENT_ORDER.indexOf(b),
1308
+ (a, b) => MigrationGenerator.TRIGGER_EVENT_ORDER.indexOf(a) - MigrationGenerator.TRIGGER_EVENT_ORDER.indexOf(b),
1316
1309
  );
1317
1310
  }
1318
1311
 
@@ -145,14 +145,28 @@ export type ModelDefinition = {
145
145
  interfaces?: readonly string[];
146
146
  queriable?: boolean;
147
147
  listQueriable?: boolean | { args?: readonly Field[] };
148
- creatable?: boolean | { createdBy?: Partial<RelationFieldDefinition>; createdAt?: Partial<DateTimeFieldDefinition> };
149
- updatable?: boolean | { updatedBy?: Partial<RelationFieldDefinition>; updatedAt?: Partial<DateTimeFieldDefinition> };
148
+ creatable?:
149
+ | boolean
150
+ | {
151
+ createdBy?: Partial<RelationFieldDefinition>;
152
+ createdAt?: Partial<DateTimeFieldDefinition>;
153
+ args?: readonly Field[];
154
+ };
155
+ updatable?:
156
+ | boolean
157
+ | {
158
+ updatedBy?: Partial<RelationFieldDefinition>;
159
+ updatedAt?: Partial<DateTimeFieldDefinition>;
160
+ args?: readonly Field[];
161
+ };
150
162
  deletable?:
151
163
  | boolean
152
164
  | {
153
165
  deleted?: Partial<BooleanFieldDefinition>;
154
166
  deletedBy?: Partial<RelationFieldDefinition>;
155
167
  deletedAt?: Partial<DateTimeFieldDefinition>;
168
+ args?: readonly Field[];
169
+ restoreArgs?: readonly Field[];
156
170
  };
157
171
  aggregatable?: boolean;
158
172
  displayField?: string;
@@ -344,14 +344,28 @@ export class EntityModel extends Model {
344
344
  interfaces?: string[];
345
345
  queriable?: boolean;
346
346
  listQueriable?: boolean | { args?: readonly Field[] };
347
- creatable?: boolean | { createdBy?: Partial<RelationFieldDefinition>; createdAt?: Partial<DateTimeFieldDefinition> };
348
- updatable?: boolean | { updatedBy?: Partial<RelationFieldDefinition>; updatedAt?: Partial<DateTimeFieldDefinition> };
347
+ creatable?:
348
+ | boolean
349
+ | {
350
+ createdBy?: Partial<RelationFieldDefinition>;
351
+ createdAt?: Partial<DateTimeFieldDefinition>;
352
+ args?: readonly Field[];
353
+ };
354
+ updatable?:
355
+ | boolean
356
+ | {
357
+ updatedBy?: Partial<RelationFieldDefinition>;
358
+ updatedAt?: Partial<DateTimeFieldDefinition>;
359
+ args?: readonly Field[];
360
+ };
349
361
  deletable?:
350
362
  | boolean
351
363
  | {
352
364
  deleted?: Partial<BooleanFieldDefinition>;
353
365
  deletedBy?: Partial<RelationFieldDefinition>;
354
366
  deletedAt?: Partial<DateTimeFieldDefinition>;
367
+ args?: readonly Field[];
368
+ restoreArgs?: readonly Field[];
355
369
  };
356
370
  aggregatable?: boolean;
357
371
  displayField?: string;
@@ -19,6 +19,7 @@ export type MutationHook<DateType extends AnyDateType = AnyDateType> = (args: {
19
19
  trigger: Trigger;
20
20
  when: 'before' | 'after';
21
21
  data: { prev: Entity; input: Entity; normalizedInput: Entity; next: Entity };
22
+ args?: Record<string, unknown>;
22
23
  ctx: MutationContext<DateType>;
23
24
  }) => Promise<void> | void;
24
25
 
@@ -18,14 +18,14 @@ export const mutationResolver = async (_parent: any, args: any, partialCtx: Cont
18
18
  const [, mutation, modelName] = it(info.fieldName.match(/^(create|update|delete|restore)(.+)$/));
19
19
  switch (mutation) {
20
20
  case 'create': {
21
- const id = await createEntity(modelName, args.data, ctx, 'mutation');
21
+ const id = await createEntity(modelName, args.data, ctx, { trigger: 'mutation', args });
22
22
 
23
23
  return await resolve(ctx, id);
24
24
  }
25
25
  case 'update': {
26
26
  const id = args.where.id;
27
27
 
28
- await updateEntity(modelName, id, args.data, ctx, 'mutation');
28
+ await updateEntity(modelName, id, args.data, ctx, { trigger: 'mutation', args });
29
29
 
30
30
  return await resolve(ctx, id);
31
31
  }
@@ -35,6 +35,7 @@ export const mutationResolver = async (_parent: any, args: any, partialCtx: Cont
35
35
  await deleteEntity(modelName, id, ctx, {
36
36
  dryRun: args.dryRun,
37
37
  trigger: 'mutation',
38
+ args,
38
39
  });
39
40
 
40
41
  return id;
@@ -42,7 +43,7 @@ export const mutationResolver = async (_parent: any, args: any, partialCtx: Cont
42
43
  case 'restore': {
43
44
  const id = args.where.id;
44
45
 
45
- await restoreEntity(modelName, id, ctx, 'mutation');
46
+ await restoreEntity(modelName, id, ctx, { trigger: 'mutation', args });
46
47
 
47
48
  return id;
48
49
  }
@@ -53,7 +54,7 @@ export const createEntity = async (
53
54
  modelName: string,
54
55
  input: Entity,
55
56
  ctx: MutationContext,
56
- trigger: Trigger = 'direct-call',
57
+ { args, trigger = 'direct-call' }: { args?: Record<string, unknown>; trigger?: Trigger } = {},
57
58
  ) =>
58
59
  withTransaction(ctx, async (ctx) => {
59
60
  const model = ctx.models.getModel(modelName, 'entity');
@@ -81,6 +82,7 @@ export const createEntity = async (
81
82
  trigger,
82
83
  when: 'before',
83
84
  data: { prev: {}, input, normalizedInput, next: normalizedInput },
85
+ args,
84
86
  ctx,
85
87
  });
86
88
 
@@ -114,6 +116,7 @@ export const createEntity = async (
114
116
  trigger,
115
117
  when: 'after',
116
118
  data: { prev: {}, input, normalizedInput, next: normalizedInput },
119
+ args,
117
120
  ctx,
118
121
  });
119
122
 
@@ -125,11 +128,12 @@ export const updateEntities = async (
125
128
  where: Record<string, unknown>,
126
129
  updateFields: Entity,
127
130
  ctx: MutationContext,
131
+ { args }: { args?: Record<string, unknown> } = {},
128
132
  ) =>
129
133
  withTransaction(ctx, async (ctx) => {
130
134
  const entities = await ctx.knex(modelName).where(where).select('id');
131
135
  for (const entity of entities) {
132
- await updateEntity(modelName, entity.id, updateFields, ctx);
136
+ await updateEntity(modelName, entity.id, updateFields, ctx, args);
133
137
  }
134
138
  });
135
139
 
@@ -138,7 +142,7 @@ export const updateEntity = async (
138
142
  id: string,
139
143
  input: Entity,
140
144
  ctx: MutationContext,
141
- trigger: Trigger = 'direct-call',
145
+ { args, trigger = 'direct-call' }: { args?: Record<string, unknown>; trigger?: Trigger } = {},
142
146
  ) =>
143
147
  withTransaction(ctx, async (ctx) => {
144
148
  const model = ctx.models.getModel(modelName, 'entity');
@@ -164,6 +168,7 @@ export const updateEntity = async (
164
168
  trigger,
165
169
  when: 'before',
166
170
  data: { prev: currentEntity, input, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
171
+ args,
167
172
  ctx,
168
173
  });
169
174
  await doUpdate(model, currentEntity, normalizedInput, ctx);
@@ -173,6 +178,7 @@ export const updateEntity = async (
173
178
  trigger,
174
179
  when: 'after',
175
180
  data: { prev: currentEntity, input, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
181
+ args,
176
182
  ctx,
177
183
  });
178
184
  }
@@ -180,12 +186,18 @@ export const updateEntity = async (
180
186
 
181
187
  type Callbacks = (() => Promise<void>)[];
182
188
 
183
- export const deleteEntities = async (modelName: string, where: Record<string, unknown>, ctx: MutationContext) =>
189
+ export const deleteEntities = async (
190
+ modelName: string,
191
+ where: Record<string, unknown>,
192
+ ctx: MutationContext,
193
+ args?: Record<string, unknown>,
194
+ ) =>
184
195
  withTransaction(ctx, async (ctx) => {
185
196
  const entities = await ctx.knex(modelName).where(where).select('id');
186
197
  for (const entity of entities) {
187
198
  await deleteEntity(modelName, entity.id, ctx, {
188
199
  trigger: 'direct-call',
200
+ args,
189
201
  });
190
202
  }
191
203
  });
@@ -197,9 +209,11 @@ export const deleteEntity = async (
197
209
  {
198
210
  dryRun = false,
199
211
  trigger = 'direct-call',
212
+ args,
200
213
  }: {
201
214
  dryRun?: boolean;
202
215
  trigger?: Trigger;
216
+ args?: Record<string, unknown>;
203
217
  } = {},
204
218
  ) =>
205
219
  withTransaction(ctx, async (ctx) => {
@@ -239,6 +253,7 @@ export const deleteEntity = async (
239
253
 
240
254
  const mutationHook = ctx.mutationHook;
241
255
  const deleteCascade = async (currentModel: EntityModel, currentEntity: Entity, currentTrigger: Trigger) => {
256
+ const isDeleteRoot = currentModel.name === rootModel.name && currentEntity.id === entity.id;
242
257
  if (!(currentModel.name in toDelete)) {
243
258
  toDelete[currentModel.name] = {};
244
259
  }
@@ -263,6 +278,7 @@ export const deleteEntity = async (
263
278
  trigger: currentTrigger,
264
279
  when: 'before',
265
280
  data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
281
+ ...(isDeleteRoot ? { args } : {}),
266
282
  ctx,
267
283
  });
268
284
  });
@@ -278,6 +294,7 @@ export const deleteEntity = async (
278
294
  trigger: currentTrigger,
279
295
  when: 'after',
280
296
  data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
297
+ ...(isDeleteRoot ? { args } : {}),
281
298
  ctx,
282
299
  });
283
300
  });
@@ -407,7 +424,12 @@ export const deleteEntity = async (
407
424
  }
408
425
  });
409
426
 
410
- export const restoreEntity = async (modelName: string, id: string, ctx: MutationContext, trigger: Trigger = 'direct-call') =>
427
+ export const restoreEntity = async (
428
+ modelName: string,
429
+ id: string,
430
+ ctx: MutationContext,
431
+ { args, trigger = 'direct-call' }: { args?: Record<string, unknown>; trigger?: Trigger } = {},
432
+ ) =>
411
433
  withTransaction(ctx, async (ctx) => {
412
434
  const model = ctx.models.getModel(modelName, 'entity');
413
435
  const rootModel = model.rootModel;
@@ -433,6 +455,7 @@ export const restoreEntity = async (modelName: string, id: string, ctx: Mutation
433
455
  const afterHooks: Callbacks = [];
434
456
 
435
457
  const restoreCascade = async (currentModel: EntityModel, currentEntity: Entity, currentTrigger: Trigger) => {
458
+ const isRestoreRoot = currentModel.name === rootModel.name && currentEntity.id === entity.id;
436
459
  if (entity.deleteRootId || currentEntity.deleteRootId) {
437
460
  if (!(currentEntity.deleteRootType === model.name && currentEntity.deleteRootId === entity.id)) {
438
461
  return;
@@ -465,6 +488,7 @@ export const restoreEntity = async (modelName: string, id: string, ctx: Mutation
465
488
  trigger: currentTrigger,
466
489
  when: 'before',
467
490
  data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
491
+ ...(isRestoreRoot ? { args } : {}),
468
492
  ctx,
469
493
  });
470
494
  });
@@ -496,6 +520,7 @@ export const restoreEntity = async (modelName: string, id: string, ctx: Mutation
496
520
  trigger: currentTrigger,
497
521
  when: 'after',
498
522
  data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
523
+ ...(isRestoreRoot ? { args } : {}),
499
524
  ctx,
500
525
  });
501
526
  });
@@ -249,6 +249,7 @@ export const generateDefinitions = ({
249
249
  type: `Create${model.name}`,
250
250
  nonNull: true,
251
251
  },
252
+ ...((model.creatable && model.creatable !== true && model.creatable.args) || []),
252
253
  ],
253
254
  });
254
255
  }
@@ -269,6 +270,7 @@ export const generateDefinitions = ({
269
270
  type: `Update${model.name}`,
270
271
  nonNull: true,
271
272
  },
273
+ ...((model.updatable && model.updatable !== true && model.updatable.args) || []),
272
274
  ],
273
275
  });
274
276
  }
@@ -288,6 +290,7 @@ export const generateDefinitions = ({
288
290
  name: 'dryRun',
289
291
  type: 'Boolean',
290
292
  },
293
+ ...((model.deletable && model.deletable !== true && model.deletable.args) || []),
291
294
  ],
292
295
  });
293
296
  mutations.push({
@@ -300,6 +303,7 @@ export const generateDefinitions = ({
300
303
  type: `${model.name}WhereUnique`,
301
304
  nonNull: true,
302
305
  },
306
+ ...((model.deletable && model.deletable !== true && model.deletable.restoreArgs) || []),
303
307
  ],
304
308
  });
305
309
  }