@smartive/graphql-magic 16.3.6 → 16.3.8

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.
Files changed (79) hide show
  1. package/.github/workflows/testing.yml +1 -1
  2. package/CHANGELOG.md +2 -2
  3. package/dist/bin/gqm.cjs +37 -35
  4. package/dist/cjs/index.cjs +51 -48
  5. package/dist/esm/api/execute.d.ts +1 -5
  6. package/dist/esm/api/execute.js.map +1 -1
  7. package/dist/esm/client/queries.d.ts +4 -4
  8. package/dist/esm/client/queries.js.map +1 -1
  9. package/dist/esm/db/generate.js +2 -0
  10. package/dist/esm/db/generate.js.map +1 -1
  11. package/dist/esm/migrations/generate.js +1 -1
  12. package/dist/esm/migrations/generate.js.map +1 -1
  13. package/dist/esm/models/models.d.ts +1 -1
  14. package/dist/esm/models/models.js.map +1 -1
  15. package/dist/esm/models/utils.d.ts +8 -12
  16. package/dist/esm/models/utils.js +3 -5
  17. package/dist/esm/models/utils.js.map +1 -1
  18. package/dist/esm/permissions/check.js +0 -15
  19. package/dist/esm/permissions/check.js.map +1 -1
  20. package/dist/esm/permissions/generate.d.ts +5 -19
  21. package/dist/esm/permissions/generate.js.map +1 -1
  22. package/dist/esm/resolvers/arguments.js.map +1 -1
  23. package/dist/esm/resolvers/filters.js +0 -2
  24. package/dist/esm/resolvers/filters.js.map +1 -1
  25. package/dist/esm/resolvers/mutations.js +5 -4
  26. package/dist/esm/resolvers/mutations.js.map +1 -1
  27. package/dist/esm/resolvers/node.js.map +1 -1
  28. package/dist/esm/resolvers/resolver.d.ts +2 -2
  29. package/dist/esm/resolvers/resolver.js +1 -1
  30. package/dist/esm/resolvers/resolver.js.map +1 -1
  31. package/dist/esm/resolvers/selects.js.map +1 -1
  32. package/dist/esm/resolvers/utils.d.ts +2 -6
  33. package/dist/esm/resolvers/utils.js +4 -2
  34. package/dist/esm/resolvers/utils.js.map +1 -1
  35. package/dist/esm/schema/utils.d.ts +4 -4
  36. package/dist/esm/schema/utils.js +31 -30
  37. package/dist/esm/schema/utils.js.map +1 -1
  38. package/dist/esm/utils/dates.d.ts +2 -4
  39. package/dist/esm/utils/dates.js +1 -3
  40. package/dist/esm/utils/dates.js.map +1 -1
  41. package/docs/docs/1-tutorial.md +1 -1
  42. package/docs/package-lock.json +1675 -893
  43. package/docs/package.json +4 -4
  44. package/eslint.config.mjs +42 -0
  45. package/migrations/20230912185644_setup.ts +3 -3
  46. package/package.json +8 -13
  47. package/src/api/execute.ts +1 -0
  48. package/src/bin/gqm/codegen.ts +1 -1
  49. package/src/client/gql.ts +1 -1
  50. package/src/client/mutations.ts +8 -8
  51. package/src/client/queries.ts +15 -14
  52. package/src/db/generate.ts +8 -5
  53. package/src/migrations/generate.ts +26 -22
  54. package/src/models/models.ts +24 -9
  55. package/src/models/mutation-hook.ts +1 -1
  56. package/src/models/utils.ts +8 -7
  57. package/src/permissions/check.ts +22 -30
  58. package/src/permissions/generate.ts +8 -25
  59. package/src/resolvers/arguments.ts +7 -2
  60. package/src/resolvers/filters.ts +8 -10
  61. package/src/resolvers/mutations.ts +19 -16
  62. package/src/resolvers/node.ts +3 -2
  63. package/src/resolvers/resolver.ts +11 -10
  64. package/src/resolvers/selects.ts +4 -3
  65. package/src/resolvers/utils.ts +15 -10
  66. package/src/schema/generate.ts +11 -11
  67. package/src/schema/utils.ts +84 -82
  68. package/src/utils/dates.ts +3 -3
  69. package/tests/generated/api/index.ts +2 -2
  70. package/tests/generated/client/index.ts +1 -193
  71. package/tests/generated/db/index.ts +4 -4
  72. package/tests/generated/models.json +2 -1
  73. package/tests/generated/schema.graphql +1 -1
  74. package/tests/utils/graphql-client.ts +49 -0
  75. package/tests/utils/models.ts +1 -0
  76. package/tests/utils/server.ts +4 -5
  77. package/tsconfig.eslint.json +18 -3
  78. package/tsconfig.json +11 -2
  79. package/.eslintrc +0 -13
@@ -137,15 +137,17 @@ const del = async (model: EntityModel, { where, dryRun }: { where: any; dryRun:
137
137
  throw new ForbiddenError('Entity is already deleted.');
138
138
  }
139
139
 
140
- const toDelete: { [type: string]: { [id: string]: string } } = {};
141
- const toUnlink: {
142
- [type: string]: {
143
- [id: string]: {
140
+ const toDelete: Record<string, Record<string, string>> = {};
141
+ const toUnlink: Record<
142
+ string,
143
+ Record<
144
+ string,
145
+ {
144
146
  display: string;
145
147
  fields: string[];
146
- };
147
- };
148
- } = {};
148
+ }
149
+ >
150
+ > = {};
149
151
 
150
152
  const beforeHooks: Callbacks = [];
151
153
  const mutations: Callbacks = [];
@@ -167,18 +169,19 @@ const del = async (model: EntityModel, { where, dryRun }: { where: any; dryRun:
167
169
  if (!dryRun) {
168
170
  const normalizedInput = { deleted: true, deletedAt: ctx.now, deletedById: ctx.user?.id };
169
171
  const data = { prev: entity, input: {}, normalizedInput, next: { ...entity, ...normalizedInput } };
170
- if (ctx.mutationHook) {
172
+ const mutationHook = ctx.mutationHook;
173
+ if (mutationHook) {
171
174
  beforeHooks.push(async () => {
172
- await ctx.mutationHook(currentModel, 'delete', 'before', data, ctx);
175
+ await mutationHook(currentModel, 'delete', 'before', data, ctx);
173
176
  });
174
177
  }
175
178
  mutations.push(async () => {
176
179
  await ctx.knex(currentModel.name).where({ id: entity.id }).update(normalizedInput);
177
180
  await createRevision(currentModel, { ...entity, deleted: true }, ctx);
178
181
  });
179
- if (ctx.mutationHook) {
182
+ if (mutationHook) {
180
183
  afterHooks.push(async () => {
181
- await ctx.mutationHook(currentModel, 'delete', 'after', data, ctx);
184
+ await mutationHook(currentModel, 'delete', 'after', data, ctx);
182
185
  });
183
186
  }
184
187
  }
@@ -270,7 +273,7 @@ const restore = async (model: EntityModel, { where }: { where: any }, ctx: FullC
270
273
  if (
271
274
  !relatedEntity.deleted ||
272
275
  !relatedEntity.deletedAt ||
273
- !anyDateToLuxon(relatedEntity.deletedAt, ctx.timeZone).equals(anyDateToLuxon(entity.deletedAt, ctx.timeZone))
276
+ !anyDateToLuxon(relatedEntity.deletedAt, ctx.timeZone)!.equals(anyDateToLuxon(entity.deletedAt, ctx.timeZone)!)
274
277
  ) {
275
278
  return;
276
279
  }
@@ -279,7 +282,7 @@ const restore = async (model: EntityModel, { where }: { where: any }, ctx: FullC
279
282
  const data = { prev: relatedEntity, input: {}, normalizedInput, next: { ...relatedEntity, ...normalizedInput } };
280
283
  if (ctx.mutationHook) {
281
284
  beforeHooks.push(async () => {
282
- await ctx.mutationHook(currentModel, 'restore', 'before', data, ctx);
285
+ await ctx.mutationHook!(currentModel, 'restore', 'before', data, ctx);
283
286
  });
284
287
  }
285
288
  mutations.push(async () => {
@@ -288,7 +291,7 @@ const restore = async (model: EntityModel, { where }: { where: any }, ctx: FullC
288
291
  });
289
292
  if (ctx.mutationHook) {
290
293
  afterHooks.push(async () => {
291
- await ctx.mutationHook(currentModel, 'restore', 'after', data, ctx);
294
+ await ctx.mutationHook!(currentModel, 'restore', 'after', data, ctx);
292
295
  });
293
296
  }
294
297
 
@@ -369,7 +372,7 @@ const sanitize = (ctx: FullContext, model: EntityModel, data: Entity) => {
369
372
  }
370
373
 
371
374
  if (isEndOfDay(field) && data[key]) {
372
- data[key] = anyDateToLuxon(data[key], ctx.timeZone).endOf('day');
375
+ data[key] = anyDateToLuxon(data[key], ctx.timeZone)!.endOf('day');
373
376
  continue;
374
377
  }
375
378
 
@@ -380,5 +383,5 @@ const sanitize = (ctx: FullContext, model: EntityModel, data: Entity) => {
380
383
  }
381
384
  };
382
385
 
383
- const isEndOfDay = (field?: EntityField) =>
386
+ const isEndOfDay = (field: EntityField) =>
384
387
  isPrimitive(field) && field.type === 'DateTime' && field?.endOfDay === true && field?.dateTimeType === 'date';
@@ -149,7 +149,7 @@ export const getInlineFragments = (node: ResolverNode) =>
149
149
 
150
150
  baseTypeDefinition: node.baseTypeDefinition,
151
151
  typeName: getFragmentTypeName(subNode),
152
- })
152
+ }),
153
153
  );
154
154
 
155
155
  export const getFragmentSpreads = (node: ResolverNode) =>
@@ -166,7 +166,7 @@ export const getFragmentSpreads = (node: ResolverNode) =>
166
166
 
167
167
  baseTypeDefinition: node.baseTypeDefinition,
168
168
  typeName: node.model.name,
169
- })
169
+ }),
170
170
  );
171
171
 
172
172
  export const getJoins = (node: ResolverNode, toMany: boolean) => {
@@ -217,5 +217,6 @@ export const getJoins = (node: ResolverNode, toMany: boolean) => {
217
217
  isList: isListType(fieldDefinition.type),
218
218
  });
219
219
  }
220
+
220
221
  return nodes;
221
222
  };
@@ -19,7 +19,7 @@ export const resolve = async (ctx: FullContext, id?: string) => {
19
19
  const fieldNode = summonByKey(ctx.info.fieldNodes, 'name.value', ctx.info.fieldName);
20
20
  const baseTypeDefinition = get(
21
21
  ctx.info.operation.operation === 'query' ? ctx.info.schema.getQueryType() : ctx.info.schema.getMutationType(),
22
- 'astNode'
22
+ 'astNode',
23
23
  );
24
24
  const node = getRootFieldNode({
25
25
  ctx,
@@ -29,7 +29,7 @@ export const resolve = async (ctx: FullContext, id?: string) => {
29
29
  const { query, verifiedPermissionStacks } = await buildQuery(node);
30
30
 
31
31
  if (ctx.info.fieldName === 'me') {
32
- if (!node.ctx.user.id) {
32
+ if (!node.ctx.user?.id) {
33
33
  return undefined;
34
34
  }
35
35
 
@@ -66,7 +66,7 @@ type VerifiedPermissionStacks = Record<string, PermissionStack>;
66
66
 
67
67
  const buildQuery = async (
68
68
  node: FieldResolverNode,
69
- parentVerifiedPermissionStacks?: VerifiedPermissionStacks
69
+ parentVerifiedPermissionStacks?: VerifiedPermissionStacks,
70
70
  ): Promise<{ query: Knex.QueryBuilder; verifiedPermissionStacks: VerifiedPermissionStacks }> => {
71
71
  const query = node.ctx.knex.fromRaw(`"${node.rootModel.name}" as "${node.ctx.aliases.getShort(node.resultAlias)}"`);
72
72
 
@@ -88,7 +88,7 @@ const buildQuery = async (
88
88
  node.ctx.aliases.getShort(alias),
89
89
  query,
90
90
  'READ',
91
- parentVerifiedPermissionStacks?.[alias.split('__').slice(0, -1).join('__')]
91
+ parentVerifiedPermissionStacks?.[alias.split('__').slice(0, -1).join('__')],
92
92
  );
93
93
 
94
94
  if (typeof verifiedPermissionStack !== 'boolean') {
@@ -102,13 +102,13 @@ const buildQuery = async (
102
102
  const applySubQueries = async (
103
103
  node: ResolverNode,
104
104
  entries: Entry[],
105
- parentVerifiedPermissionStacks: VerifiedPermissionStacks
105
+ parentVerifiedPermissionStacks: VerifiedPermissionStacks,
106
106
  ): Promise<void> => {
107
107
  if (!entries.length) {
108
108
  return;
109
109
  }
110
110
 
111
- const entriesById: { [id: string]: Entry[] } = {};
111
+ const entriesById: Record<string, Entry[]> = {};
112
112
  for (const entry of entries) {
113
113
  if (!entriesById[entry[ID_ALIAS]]) {
114
114
  entriesById[entry[ID_ALIAS]] = [];
@@ -130,7 +130,7 @@ const applySubQueries = async (
130
130
  query
131
131
  .clone()
132
132
  .select(`${shortTableAlias}.${foreignKey} as ${shortResultAlias}__${foreignKey}`)
133
- .where({ [`${shortTableAlias}.${foreignKey}`]: id })
133
+ .where({ [`${shortTableAlias}.${foreignKey}`]: id }),
134
134
  );
135
135
 
136
136
  // TODO: make unionAll faster then promise.all...
@@ -153,10 +153,11 @@ const applySubQueries = async (
153
153
  flatMap(
154
154
  entries.map((entry) => {
155
155
  const children = entry[fieldName];
156
+
156
157
  return (isList ? children : children ? [children] : []) as Entry[];
157
- })
158
+ }),
158
159
  ),
159
- verifiedPermissionStacks
160
+ verifiedPermissionStacks,
160
161
  );
161
162
  }
162
163
 
@@ -172,7 +173,7 @@ const applySubQueries = async (
172
173
  await applySubQueries(
173
174
  subNode,
174
175
  entries.map((item) => item[getNameOrAlias(subNode.field)] as Entry).filter(Boolean),
175
- parentVerifiedPermissionStacks
176
+ parentVerifiedPermissionStacks,
176
177
  );
177
178
  }
178
179
  };
@@ -34,7 +34,7 @@ export const applySelects = (node: ResolverNode, query: Knex.QueryBuilder, joins
34
34
  role,
35
35
  'READ',
36
36
  `${node.model.name}'s field "${field.name}"`,
37
- 'field permission not available'
37
+ 'field permission not available',
38
38
  );
39
39
  }
40
40
 
@@ -49,6 +49,7 @@ export const applySelects = (node: ResolverNode, query: Knex.QueryBuilder, joins
49
49
  if ([ID_ALIAS, TYPE_ALIAS].includes(fieldAlias)) {
50
50
  throw new UserInputError(`Keyword ${fieldAlias} is reserved by graphql-magic.`);
51
51
  }
52
+
52
53
  return {
53
54
  fieldNode,
54
55
  field: fieldNode.name.value,
@@ -59,8 +60,8 @@ export const applySelects = (node: ResolverNode, query: Knex.QueryBuilder, joins
59
60
  }),
60
61
  ].map(
61
62
  ({ tableAlias, resultAlias, field, fieldAlias }) =>
62
- `${node.ctx.aliases.getShort(tableAlias)}.${field} as ${node.ctx.aliases.getShort(resultAlias)}__${fieldAlias}`
63
- )
63
+ `${node.ctx.aliases.getShort(tableAlias)}.${field} as ${node.ctx.aliases.getShort(resultAlias)}__${fieldAlias}`,
64
+ ),
64
65
  );
65
66
 
66
67
  for (const subNode of getInlineFragments(node)) {
@@ -21,17 +21,17 @@ import { FieldResolverNode, ResolverNode } from './node';
21
21
  export const ID_ALIAS = 'ID';
22
22
  export const TYPE_ALIAS = 'TYPE';
23
23
 
24
- export type VariableValues = {
25
- [variableName: string]: Value;
26
- };
24
+ export type VariableValues = Record<string, Value>;
27
25
 
28
26
  export const getTypeName = (t: TypeNode): string => {
29
27
  switch (t.kind) {
30
28
  case 'ListType':
31
29
  case 'NonNullType':
32
30
  return getTypeName(t.type);
33
- default:
31
+ case 'NamedType':
34
32
  return t.name.value;
33
+ default:
34
+ throw new Error(`Unknown type node kind: ${t.kind}`);
35
35
  }
36
36
  };
37
37
 
@@ -67,14 +67,14 @@ export type Entry = {
67
67
 
68
68
  export function hydrate<T extends Entry>(
69
69
  node: FieldResolverNode,
70
- raw: { [key: string]: undefined | null | string | Date | number }[]
70
+ raw: Record<string, undefined | null | string | Date | number>[],
71
71
  ): T[] {
72
72
  const resultAlias = node.resultAlias;
73
73
  const res = raw.map((entry) => {
74
74
  const res: any = {};
75
75
  outer: for (const [column, value] of Object.entries(entry)) {
76
76
  let current = res;
77
- const [, columnWithoutField, fieldName] = column.match(/^(.*\w)__(\w+)$/);
77
+ const [, columnWithoutField, fieldName] = column.match(/^(.*\w)__(\w+)$/)!;
78
78
  const longColumn = node.ctx.aliases.getLong(columnWithoutField);
79
79
  const longColumnWithoutRoot = longColumn.replace(new RegExp(`^${resultAlias}(__)?`), '');
80
80
  const allParts = [resultAlias, ...(longColumnWithoutRoot ? longColumnWithoutRoot.split('__') : []), fieldName];
@@ -92,6 +92,7 @@ export function hydrate<T extends Entry>(
92
92
  }
93
93
  current[it(fieldName)] = value;
94
94
  }
95
+
95
96
  return res[resultAlias];
96
97
  });
97
98
 
@@ -102,6 +103,7 @@ export const ors = (query: Knex.QueryBuilder, [first, ...rest]: ((query: Knex.Qu
102
103
  if (!first) {
103
104
  return query;
104
105
  }
106
+
105
107
  return query.where((subQuery) => {
106
108
  void subQuery.where((subSubQuery) => {
107
109
  void first(subSubQuery);
@@ -117,7 +119,7 @@ export const ors = (query: Knex.QueryBuilder, [first, ...rest]: ((query: Knex.Qu
117
119
  export const getNameOrAlias = (node: FieldNode) => {
118
120
  const name = node.alias ? node.alias.value : node.name.value;
119
121
 
120
- if ([ID_ALIAS].indexOf(name) >= 0) {
122
+ if ([ID_ALIAS].includes(name)) {
121
123
  throw new UserInputError(`"${name}" can not be used as alias since it's a reserved word`);
122
124
  }
123
125
 
@@ -140,7 +142,7 @@ export const applyJoins = (aliases: AliasGenerator, query: Knex.QueryBuilder, jo
140
142
  void query.leftJoin(
141
143
  `${table2Name} as ${table2ShortAlias}`,
142
144
  `${table1ShortAlias}.${column1}`,
143
- `${table2ShortAlias}.${column2}`
145
+ `${table2ShortAlias}.${column2}`,
144
146
  );
145
147
  }
146
148
  };
@@ -154,7 +156,7 @@ export const addJoin = (
154
156
  table2Name: string,
155
157
  table2Alias: string,
156
158
  column1: string,
157
- column2: string
159
+ column2: string,
158
160
  ) => {
159
161
  const join = { table1Alias, table2Name, table2Alias, column1, column2 };
160
162
  const existingJoin = joins.find((j) => j.table2Alias === join.table2Alias);
@@ -173,6 +175,7 @@ export class AliasGenerator {
173
175
  public getShort(long?: string) {
174
176
  if (!long) {
175
177
  const short = `a${Object.keys(this.reverse).length}`;
178
+
176
179
  return (this.reverse[short] = short);
177
180
  }
178
181
 
@@ -187,6 +190,7 @@ export class AliasGenerator {
187
190
  }
188
191
 
189
192
  this.reverse[short] = long;
193
+
190
194
  return short;
191
195
  }
192
196
 
@@ -201,6 +205,7 @@ export const getColumnName = (field: EntityField) =>
201
205
  field.kind === 'relation' ? field.foreignKey || `${field.name}Id` : field.name;
202
206
 
203
207
  export const getColumn = (node: Pick<ResolverNode, 'model' | 'ctx' | 'rootTableAlias' | 'tableAlias'>, key: string) => {
204
- const field = node.model.fields.find((field) => getColumnName(field) === key);
208
+ const field = node.model.fields.find((field) => getColumnName(field) === key)!;
209
+
205
210
  return `${node.ctx.aliases.getShort(field.inherited ? node.rootTableAlias : node.tableAlias)}.${key}`;
206
211
  };
@@ -22,12 +22,12 @@ export const generateDefinitions = ({
22
22
  ...inputs.map((model) => input(model.name, model.fields)),
23
23
  ...objects
24
24
  .filter((model) =>
25
- entities.some((m) => m.creatable && m.fields.some((f) => f.creatable && f.kind === 'json' && f.type === model.name))
25
+ entities.some((m) => m.creatable && m.fields.some((f) => f.creatable && f.kind === 'json' && f.type === model.name)),
26
26
  )
27
27
  .map((model) => input(`Create${model.name}`, model.fields)),
28
28
  ...objects
29
29
  .filter((model) =>
30
- entities.some((m) => m.updatable && m.fields.some((f) => f.updatable && f.kind === 'json' && f.type === model.name))
30
+ entities.some((m) => m.updatable && m.fields.some((f) => f.updatable && f.kind === 'json' && f.type === model.name)),
31
31
  )
32
32
  .map((model) => input(`Update${model.name}`, model.fields)),
33
33
 
@@ -58,7 +58,7 @@ export const generateDefinitions = ({
58
58
  ],
59
59
  })),
60
60
  ],
61
- [...(model.parent ? [model.parent] : []), ...(model.interfaces || [])]
61
+ [...(model.parent ? [model.parent] : []), ...(model.interfaces || [])],
62
62
  ),
63
63
  input(`${model.name}Where`, [
64
64
  ...model.fields
@@ -112,13 +112,13 @@ export const generateDefinitions = ({
112
112
  ]),
113
113
  input(
114
114
  `${model.name}WhereUnique`,
115
- model.fields.filter(({ unique }) => unique).map((field) => ({ name: field.name, type: field.type }))
115
+ model.fields.filter(({ unique }) => unique).map((field) => ({ name: field.name, type: field.type })),
116
116
  ),
117
117
  ...(model.fields.some(({ orderable }) => orderable)
118
118
  ? [
119
119
  input(
120
120
  `${model.name}OrderBy`,
121
- model.fields.filter(({ orderable }) => orderable).map(({ name }) => ({ name, type: 'Order' }))
121
+ model.fields.filter(({ orderable }) => orderable).map(({ name }) => ({ name, type: 'Order' })),
122
122
  ),
123
123
  ]
124
124
  : []),
@@ -139,9 +139,9 @@ export const generateDefinitions = ({
139
139
  type: field.kind === 'json' ? `Create${field.type}` : field.type,
140
140
  list: field.list,
141
141
  nonNull: field.nonNull && field.defaultValue === undefined,
142
- }
143
- )
144
- )
142
+ },
143
+ ),
144
+ ),
145
145
  );
146
146
  }
147
147
 
@@ -158,9 +158,9 @@ export const generateDefinitions = ({
158
158
  name: field.name,
159
159
  type: field.kind === 'json' ? `Update${field.type}` : field.type,
160
160
  list: field.list,
161
- }
162
- )
163
- )
161
+ },
162
+ ),
163
+ ),
164
164
  );
165
165
  }
166
166
  }