@smartive/graphql-magic 9.1.2 → 10.0.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.
Files changed (120) hide show
  1. package/.eslintrc +2 -10
  2. package/.github/workflows/release.yml +1 -1
  3. package/.gqmrc.json +6 -0
  4. package/CHANGELOG.md +2 -2
  5. package/README.md +1 -1
  6. package/dist/bin/gqm.cjs +684 -330
  7. package/dist/cjs/index.cjs +998 -554
  8. package/dist/esm/api/execute.js +1 -1
  9. package/dist/esm/api/execute.js.map +1 -1
  10. package/dist/esm/client/mutations.d.ts +2 -2
  11. package/dist/esm/client/mutations.js +5 -4
  12. package/dist/esm/client/mutations.js.map +1 -1
  13. package/dist/esm/client/queries.d.ts +12 -17
  14. package/dist/esm/client/queries.js +30 -50
  15. package/dist/esm/client/queries.js.map +1 -1
  16. package/dist/esm/context.d.ts +1 -2
  17. package/dist/esm/db/generate.d.ts +3 -3
  18. package/dist/esm/db/generate.js +31 -29
  19. package/dist/esm/db/generate.js.map +1 -1
  20. package/dist/esm/migrations/generate.d.ts +3 -4
  21. package/dist/esm/migrations/generate.js +114 -107
  22. package/dist/esm/migrations/generate.js.map +1 -1
  23. package/dist/esm/models/index.d.ts +1 -0
  24. package/dist/esm/models/index.js +1 -0
  25. package/dist/esm/models/index.js.map +1 -1
  26. package/dist/esm/models/model-definitions.d.ts +189 -0
  27. package/dist/esm/models/model-definitions.js +2 -0
  28. package/dist/esm/models/model-definitions.js.map +1 -0
  29. package/dist/esm/models/models.d.ts +128 -174
  30. package/dist/esm/models/models.js +411 -1
  31. package/dist/esm/models/models.js.map +1 -1
  32. package/dist/esm/models/mutation-hook.d.ts +2 -2
  33. package/dist/esm/models/utils.d.ts +35 -497
  34. package/dist/esm/models/utils.js +21 -144
  35. package/dist/esm/models/utils.js.map +1 -1
  36. package/dist/esm/permissions/check.d.ts +3 -3
  37. package/dist/esm/permissions/check.js +14 -7
  38. package/dist/esm/permissions/check.js.map +1 -1
  39. package/dist/esm/permissions/generate.js +6 -6
  40. package/dist/esm/permissions/generate.js.map +1 -1
  41. package/dist/esm/resolvers/filters.d.ts +8 -0
  42. package/dist/esm/resolvers/filters.js +28 -25
  43. package/dist/esm/resolvers/filters.js.map +1 -1
  44. package/dist/esm/resolvers/index.d.ts +1 -0
  45. package/dist/esm/resolvers/index.js +1 -0
  46. package/dist/esm/resolvers/index.js.map +1 -1
  47. package/dist/esm/resolvers/mutations.js +85 -21
  48. package/dist/esm/resolvers/mutations.js.map +1 -1
  49. package/dist/esm/resolvers/node.d.ts +13 -15
  50. package/dist/esm/resolvers/node.js +41 -36
  51. package/dist/esm/resolvers/node.js.map +1 -1
  52. package/dist/esm/resolvers/resolver.js +19 -49
  53. package/dist/esm/resolvers/resolver.js.map +1 -1
  54. package/dist/esm/resolvers/resolvers.d.ts +1 -8
  55. package/dist/esm/resolvers/resolvers.js +15 -7
  56. package/dist/esm/resolvers/resolvers.js.map +1 -1
  57. package/dist/esm/resolvers/selects.d.ts +3 -0
  58. package/dist/esm/resolvers/selects.js +50 -0
  59. package/dist/esm/resolvers/selects.js.map +1 -0
  60. package/dist/esm/resolvers/utils.d.ts +12 -4
  61. package/dist/esm/resolvers/utils.js +30 -22
  62. package/dist/esm/resolvers/utils.js.map +1 -1
  63. package/dist/esm/schema/generate.d.ts +4 -4
  64. package/dist/esm/schema/generate.js +122 -131
  65. package/dist/esm/schema/generate.js.map +1 -1
  66. package/dist/esm/schema/utils.d.ts +1 -1
  67. package/dist/esm/schema/utils.js +2 -1
  68. package/dist/esm/schema/utils.js.map +1 -1
  69. package/knexfile.ts +31 -0
  70. package/migrations/20230912185644_setup.ts +127 -0
  71. package/package.json +16 -14
  72. package/src/api/execute.ts +1 -1
  73. package/src/bin/gqm/gqm.ts +25 -23
  74. package/src/bin/gqm/parse-models.ts +5 -5
  75. package/src/bin/gqm/settings.ts +13 -4
  76. package/src/bin/gqm/static-eval.ts +5 -0
  77. package/src/bin/gqm/templates.ts +23 -3
  78. package/src/client/mutations.ts +11 -5
  79. package/src/client/queries.ts +43 -80
  80. package/src/context.ts +1 -2
  81. package/src/db/generate.ts +41 -41
  82. package/src/migrations/generate.ts +165 -146
  83. package/src/models/index.ts +1 -0
  84. package/src/models/model-definitions.ts +168 -0
  85. package/src/models/models.ts +510 -166
  86. package/src/models/mutation-hook.ts +2 -2
  87. package/src/models/utils.ts +53 -187
  88. package/src/permissions/check.ts +19 -11
  89. package/src/permissions/generate.ts +6 -6
  90. package/src/resolvers/filters.ts +44 -28
  91. package/src/resolvers/index.ts +1 -0
  92. package/src/resolvers/mutations.ts +98 -36
  93. package/src/resolvers/node.ts +79 -51
  94. package/src/resolvers/resolver.ts +20 -74
  95. package/src/resolvers/resolvers.ts +18 -7
  96. package/src/resolvers/selects.ts +77 -0
  97. package/src/resolvers/utils.ts +41 -25
  98. package/src/schema/generate.ts +106 -127
  99. package/src/schema/utils.ts +2 -1
  100. package/tests/api/__snapshots__/inheritance.spec.ts.snap +83 -0
  101. package/tests/api/inheritance.spec.ts +130 -0
  102. package/tests/generated/api/index.ts +1174 -0
  103. package/tests/generated/client/index.ts +1163 -0
  104. package/tests/generated/client/mutations.ts +109 -0
  105. package/tests/generated/db/index.ts +291 -0
  106. package/tests/generated/db/knex.ts +14 -0
  107. package/tests/generated/models.json +675 -0
  108. package/tests/generated/schema.graphql +325 -0
  109. package/tests/unit/__snapshots__/resolve.spec.ts.snap +23 -0
  110. package/tests/unit/queries.spec.ts +5 -5
  111. package/tests/unit/resolve.spec.ts +8 -8
  112. package/tests/utils/database/knex.ts +5 -13
  113. package/tests/utils/database/seed.ts +57 -18
  114. package/tests/utils/models.ts +62 -7
  115. package/tests/utils/server.ts +5 -5
  116. package/tsconfig.eslint.json +1 -0
  117. package/tests/unit/__snapshots__/generate.spec.ts.snap +0 -128
  118. package/tests/unit/generate.spec.ts +0 -8
  119. package/tests/utils/database/schema.ts +0 -64
  120. package/tests/utils/generate-migration.ts +0 -24
@@ -1,5 +1,5 @@
1
1
  import { Models } from '../models/models';
2
- import { getModelPluralField, merge, typeToField } from '../models/utils';
2
+ import { isRootModel, merge, not, typeToField } from '../models/utils';
3
3
  import { mutationResolver } from './mutations';
4
4
  import { queryResolver } from './resolver';
5
5
 
@@ -8,33 +8,44 @@ export const getResolvers = (models: Models) => ({
8
8
  {
9
9
  me: queryResolver,
10
10
  },
11
- ...models
11
+ ...models.entities
12
12
  .filter(({ queriable }) => queriable)
13
13
  .map((model) => ({
14
14
  [typeToField(model.name)]: queryResolver,
15
15
  })),
16
- ...models
16
+ ...models.entities
17
17
  .filter(({ listQueriable }) => listQueriable)
18
18
  .map((model) => ({
19
- [getModelPluralField(model)]: queryResolver,
19
+ [model.pluralField]: queryResolver,
20
20
  })),
21
21
  ]),
22
22
  Mutation: merge<unknown>([
23
- ...models
23
+ ...models.entities
24
+ .filter(not(isRootModel))
24
25
  .filter(({ creatable }) => creatable)
25
26
  .map((model) => ({
26
27
  [`create${model.name}`]: mutationResolver,
27
28
  })),
28
- ...models
29
+ ...models.entities
30
+ .filter(not(isRootModel))
29
31
  .filter(({ updatable }) => updatable)
30
32
  .map((model) => ({
31
33
  [`update${model.name}`]: mutationResolver,
32
34
  })),
33
- ...models
35
+ ...models.entities
36
+ .filter(not(isRootModel))
34
37
  .filter(({ deletable }) => deletable)
35
38
  .map((model) => ({
36
39
  [`delete${model.name}`]: mutationResolver,
37
40
  [`restore${model.name}`]: mutationResolver,
38
41
  })),
39
42
  ]),
43
+ ...Object.assign(
44
+ {},
45
+ ...models.entities.filter(isRootModel).map((model) => ({
46
+ [model.name]: {
47
+ __resolveType: ({ TYPE }) => TYPE,
48
+ },
49
+ }))
50
+ ),
40
51
  });
@@ -0,0 +1,77 @@
1
+ import { Knex } from 'knex';
2
+ import {
3
+ ID_ALIAS,
4
+ Joins,
5
+ ResolverNode,
6
+ TYPE_ALIAS,
7
+ addJoin,
8
+ getFragmentSpreads,
9
+ getInlineFragments,
10
+ getJoins,
11
+ getNameOrAlias,
12
+ getSimpleFields,
13
+ } from '.';
14
+ import { PermissionError, UserInputError } from '..';
15
+
16
+ export const applySelects = (node: ResolverNode, query: Knex.QueryBuilder, joins: Joins) => {
17
+ // Simple field selects
18
+ void query.select(
19
+ ...[
20
+ { tableAlias: node.rootTableAlias, resultAlias: node.resultAlias, field: 'id', fieldAlias: ID_ALIAS },
21
+ ...(node.model.root
22
+ ? [{ tableAlias: node.rootTableAlias, resultAlias: node.resultAlias, field: 'type', fieldAlias: TYPE_ALIAS }]
23
+ : []),
24
+ ...getSimpleFields(node)
25
+ .filter((fieldNode) => {
26
+ const field = node.model.fieldsByName[fieldNode.name.value];
27
+ if (!field || field.kind === 'relation' || field.kind === 'custom') {
28
+ return false;
29
+ }
30
+
31
+ if (typeof field.queriable === 'object' && !field.queriable.roles?.includes(node.ctx.user.role)) {
32
+ throw new PermissionError(
33
+ 'READ',
34
+ `${node.model.name}'s field "${field.name}"`,
35
+ 'field permission not available'
36
+ );
37
+ }
38
+
39
+ return true;
40
+ })
41
+ .map((fieldNode) => {
42
+ const field = node.model.getField(fieldNode.name.value);
43
+ if (node.model.parent && !field.inherited) {
44
+ addJoin(joins, node.rootTableAlias, node.model.name, node.tableAlias, 'id', 'id');
45
+ }
46
+ const fieldAlias = getNameOrAlias(fieldNode);
47
+ if ([ID_ALIAS, TYPE_ALIAS].includes(fieldAlias)) {
48
+ throw new UserInputError(`Keyword ${fieldAlias} is reserved by graphql-magic.`);
49
+ }
50
+ return {
51
+ fieldNode,
52
+ field: fieldNode.name.value,
53
+ tableAlias: field.inherited ? node.rootTableAlias : node.tableAlias,
54
+ resultAlias: node.resultAlias,
55
+ fieldAlias,
56
+ };
57
+ }),
58
+ ].map(
59
+ ({ tableAlias, resultAlias, field, fieldAlias }) =>
60
+ `${node.ctx.aliases.getShort(tableAlias)}.${field} as ${node.ctx.aliases.getShort(resultAlias)}__${fieldAlias}`
61
+ )
62
+ );
63
+
64
+ for (const subNode of getInlineFragments(node)) {
65
+ applySelects(subNode, query, joins);
66
+ }
67
+
68
+ for (const subNode of getFragmentSpreads(node)) {
69
+ applySelects(subNode, query, joins);
70
+ }
71
+
72
+ for (const subNode of getJoins(node, false)) {
73
+ addJoin(joins, node.tableAlias, subNode.rootModel.name, subNode.rootTableAlias, subNode.foreignKey, 'id');
74
+
75
+ applySelects(subNode, query, joins);
76
+ }
77
+ };
@@ -11,12 +11,15 @@ import type {
11
11
  } from 'graphql';
12
12
  import { Kind } from 'graphql';
13
13
  import { Knex } from 'knex';
14
+ import { isEqual } from 'lodash';
15
+ import { EntityField } from '..';
14
16
  import { UserInputError } from '../errors';
15
17
  import { get, it } from '../models/utils';
16
18
  import { Value } from '../values';
17
- import { FieldResolverNode } from './node';
19
+ import { FieldResolverNode, ResolverNode } from './node';
18
20
 
19
21
  export const ID_ALIAS = 'ID';
22
+ export const TYPE_ALIAS = 'TYPE';
20
23
 
21
24
  export type VariableValues = {
22
25
  [variableName: string]: Value;
@@ -66,17 +69,15 @@ export function hydrate<T extends Entry>(
66
69
  node: FieldResolverNode,
67
70
  raw: { [key: string]: undefined | null | string | Date | number }[]
68
71
  ): T[] {
69
- const tableAlias = node.tableAlias;
72
+ const resultAlias = node.resultAlias;
70
73
  const res = raw.map((entry) => {
71
74
  const res: any = {};
72
75
  outer: for (const [column, value] of Object.entries(entry)) {
73
76
  let current = res;
74
- const shortParts = column.split('__');
75
- const fieldName = shortParts.pop();
76
- const columnWithoutField = shortParts.join('__');
77
+ const [, columnWithoutField, fieldName] = column.match(/^(.*\w)__(\w+)$/);
77
78
  const longColumn = node.ctx.aliases.getLong(columnWithoutField);
78
- const longColumnWithoutRoot = longColumn.replace(new RegExp(`^${tableAlias}(__)?`), '');
79
- const allParts = [tableAlias, ...(longColumnWithoutRoot ? longColumnWithoutRoot.split('__') : []), fieldName];
79
+ const longColumnWithoutRoot = longColumn.replace(new RegExp(`^${resultAlias}(__)?`), '');
80
+ const allParts = [resultAlias, ...(longColumnWithoutRoot ? longColumnWithoutRoot.split('__') : []), fieldName];
80
81
  for (let i = 0; i < allParts.length - 1; i++) {
81
82
  const part = allParts[i];
82
83
 
@@ -91,7 +92,7 @@ export function hydrate<T extends Entry>(
91
92
  }
92
93
  current[it(fieldName)] = value;
93
94
  }
94
- return res[tableAlias];
95
+ return res[resultAlias];
95
96
  });
96
97
 
97
98
  return res;
@@ -102,16 +103,12 @@ export const ors = (query: Knex.QueryBuilder, [first, ...rest]: ((query: Knex.Qu
102
103
  return query;
103
104
  }
104
105
  return query.where((subQuery) => {
105
- // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
106
- subQuery.where((subSubQuery) => {
107
- // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
108
- first(subSubQuery);
106
+ void subQuery.where((subSubQuery) => {
107
+ void first(subSubQuery);
109
108
  });
110
109
  for (const cb of rest) {
111
- // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
112
- subQuery.orWhere((subSubQuery) => {
113
- // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
114
- cb(subSubQuery);
110
+ void subQuery.orWhere((subSubQuery) => {
111
+ void cb(subSubQuery);
115
112
  });
116
113
  }
117
114
  });
@@ -131,15 +128,18 @@ export type Ops<T> = ((target: T) => T)[];
131
128
 
132
129
  export const apply = <T>(target: T, ops: ((target: T) => T)[]) => ops.reduce((target, op) => op(target), target);
133
130
 
134
- export type Joins = Record<`${string}:${string}`, { table1Alias: string; column1: string; column2: string }>;
131
+ type Join = { table1Alias: string; column1: string; table2Name: string; table2Alias: string; column2: string };
132
+ export type Joins = Join[];
135
133
 
136
134
  export const applyJoins = (aliases: AliasGenerator, query: Knex.QueryBuilder, joins: Joins) => {
137
- for (const [tableName, { table1Alias, column1, column2 }] of Object.entries(joins)) {
138
- const [table, alias] = tableName.split(':');
135
+ for (const { table1Alias, table2Name, table2Alias, column1, column2 } of joins) {
139
136
  const table1ShortAlias = aliases.getShort(table1Alias);
140
- const table2ShortAlias = aliases.getShort(alias);
141
- // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
142
- query.leftJoin(`${table} as ${table2ShortAlias}`, `${table1ShortAlias}.${column1}`, `${table2ShortAlias}.${column2}`);
137
+ const table2ShortAlias = aliases.getShort(table2Alias);
138
+ void query.leftJoin(
139
+ `${table2Name} as ${table2ShortAlias}`,
140
+ `${table1ShortAlias}.${column1}`,
141
+ `${table2ShortAlias}.${column2}`
142
+ );
143
143
  }
144
144
  };
145
145
 
@@ -149,12 +149,20 @@ export const applyJoins = (aliases: AliasGenerator, query: Knex.QueryBuilder, jo
149
149
  export const addJoin = (
150
150
  joins: Joins,
151
151
  table1Alias: string,
152
- table2: string,
153
- alias2: string,
152
+ table2Name: string,
153
+ table2Alias: string,
154
154
  column1: string,
155
155
  column2: string
156
156
  ) => {
157
- joins[`${table2}:${alias2}`] ||= { table1Alias, column1, column2 };
157
+ const join = { table1Alias, table2Name, table2Alias, column1, column2 };
158
+ const existingJoin = joins.find((j) => j.table2Alias === join.table2Alias);
159
+ if (existingJoin) {
160
+ if (!isEqual(existingJoin, join)) {
161
+ throw new Error(`Join collision: ${existingJoin}, ${join}`);
162
+ }
163
+ } else {
164
+ joins.push(join);
165
+ }
158
166
  };
159
167
 
160
168
  export class AliasGenerator {
@@ -186,3 +194,11 @@ export class AliasGenerator {
186
194
  }
187
195
 
188
196
  export const hash = (s: any) => createHash('md5').update(JSON.stringify(s)).digest('hex');
197
+
198
+ export const getColumnName = (field: EntityField) =>
199
+ field.kind === 'relation' ? field.foreignKey || `${field.name}Id` : field.name;
200
+
201
+ export const getColumn = (node: Pick<ResolverNode, 'model' | 'ctx' | 'rootTableAlias' | 'tableAlias'>, key: string) => {
202
+ const field = node.model.fields.find((field) => getColumnName(field) === key);
203
+ return `${node.ctx.aliases.getShort(field.inherited ? node.rootTableAlias : node.tableAlias)}.${key}`;
204
+ };
@@ -1,120 +1,105 @@
1
1
  import { DefinitionNode, DocumentNode, GraphQLSchema, buildASTSchema, print } from 'graphql';
2
- import flatMap from 'lodash/flatMap';
3
- import { RawModels } from '../models/models';
4
- import {
5
- getModelPluralField,
6
- getModels,
7
- isEnumModel,
8
- isInputModel,
9
- isObjectModel,
10
- isQueriableField,
11
- isRawEnumModel,
12
- isRelation,
13
- isScalarModel,
14
- typeToField,
15
- } from '../models/utils';
16
- import { Field, document, enm, input, object, scalar } from './utils';
17
-
18
- export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
19
- const models = getModels(rawModels);
2
+ import { Models } from '../models/models';
3
+ import { isQueriableField, isRelation, isRootModel, typeToField } from '../models/utils';
4
+ import { Field, document, enm, iface, input, object, scalar } from './utils';
20
5
 
6
+ export const generateDefinitions = ({
7
+ scalars,
8
+ rawEnums,
9
+ enums,
10
+ inputs,
11
+ interfaces,
12
+ entities,
13
+ objects,
14
+ }: Models): DefinitionNode[] => {
21
15
  return [
22
16
  // Predefined types
23
- enm('Order', ['ASC', 'DESC']),
24
- scalar('DateTime'),
25
- scalar('Upload'),
26
-
27
- ...rawModels.filter(isEnumModel).map((model) => enm(model.name, model.values)),
28
- ...rawModels.filter(isRawEnumModel).map((model) => enm(model.name, model.values)),
29
- ...rawModels.filter(isScalarModel).map((model) => scalar(model.name)),
30
- ...rawModels
31
- .filter(isObjectModel)
32
- .filter(({ name }) => !['Query', 'Mutation'].includes(name))
33
- .map((model) => object(model.name, model.fields)),
34
- ...rawModels.filter(isInputModel).map((model) => input(model.name, model.fields)),
35
- ...rawModels
36
- .filter(isObjectModel)
17
+ ...rawEnums.map((model) => enm(model.name, model.values)),
18
+ ...enums.map((model) => enm(model.name, model.values)),
19
+ ...scalars.map((model) => scalar(model.name)),
20
+ ...objects.filter(({ name }) => !['Query', 'Mutation'].includes(name)).map((model) => object(model.name, model.fields)),
21
+ ...interfaces.map(({ name, fields }) => iface(name, fields)),
22
+ ...inputs.map((model) => input(model.name, model.fields)),
23
+ ...objects
37
24
  .filter((model) =>
38
- models.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))
39
26
  )
40
27
  .map((model) => input(`Create${model.name}`, model.fields)),
41
- ...rawModels
42
- .filter(isObjectModel)
28
+ ...objects
43
29
  .filter((model) =>
44
- models.some((m) => m.creatable && m.fields.some((f) => f.creatable && 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))
45
31
  )
46
32
  .map((model) => input(`Update${model.name}`, model.fields)),
47
33
 
48
- ...flatMap(
49
- models.map((model) => {
50
- const types = [
51
- object(
52
- model.name,
53
- [
54
- ...model.fields.filter(isQueriableField).map((field) => ({
55
- ...field,
56
- type: field.type,
57
- args: [...(field.args || [])],
58
- directives: field.directives,
59
- })),
60
- ...model.reverseRelations.map(({ name, field, model }) => ({
61
- name,
62
- type: model.name,
63
- list: !field.toOne,
64
- nonNull: !field.toOne,
65
- args: [
66
- { name: 'where', type: `${model.name}Where` },
67
- ...(model.fields.some(({ searchable }) => searchable) ? [{ name: 'search', type: 'String' }] : []),
68
- ...(model.fields.some(({ orderable }) => orderable)
69
- ? [{ name: 'orderBy', type: `${model.name}OrderBy`, list: true }]
70
- : []),
71
- { name: 'limit', type: 'Int' },
72
- { name: 'offset', type: 'Int' },
73
- ],
74
- })),
75
- ],
76
- model.interfaces
77
- ),
78
- input(`${model.name}Where`, [
79
- ...model.fields
80
- .filter(({ kind, unique, filterable }) => (unique || filterable) && kind !== 'relation')
81
- .map((field) => ({
82
- name: field.name,
83
- type: field.type,
84
- list: true,
85
- default: typeof field.filterable === 'object' ? field.filterable.default : undefined,
86
- })),
87
- ...flatMap(
88
- model.fields.filter(({ comparable }) => comparable),
89
- (field) => [
90
- { name: `${field.name}_GT`, type: field.type },
91
- { name: `${field.name}_GTE`, type: field.type },
92
- { name: `${field.name}_LT`, type: field.type },
93
- { name: `${field.name}_LTE`, type: field.type },
94
- ]
95
- ),
96
- ...model.fields
97
- .filter(isRelation)
98
- .filter(({ filterable }) => filterable)
99
- .map(({ name, type }) => ({
100
- name,
101
- type: `${type}Where`,
102
- })),
103
- ]),
104
- input(
105
- `${model.name}WhereUnique`,
106
- model.fields.filter(({ unique }) => unique).map((field) => ({ name: field.name, type: field.type }))
107
- ),
108
- ...(model.fields.some(({ orderable }) => orderable)
109
- ? [
110
- input(
111
- `${model.name}OrderBy`,
112
- model.fields.filter(({ orderable }) => orderable).map(({ name }) => ({ name, type: 'Order' }))
113
- ),
114
- ]
115
- : []),
116
- ];
34
+ ...entities.flatMap((model) => {
35
+ const types: DefinitionNode[] = [
36
+ (isRootModel(model) ? iface : object)(
37
+ model.name,
38
+ [
39
+ ...model.fields.filter(isQueriableField).map((field) => ({
40
+ ...field,
41
+ type: field.type,
42
+ args: [...(field.args || [])],
43
+ directives: field.directives,
44
+ })),
45
+ ...model.reverseRelations.map(({ name, field, targetModel }) => ({
46
+ name,
47
+ type: targetModel.name,
48
+ list: !field.toOne,
49
+ nonNull: !field.toOne,
50
+ args: [
51
+ { name: 'where', type: `${targetModel.name}Where` },
52
+ ...(targetModel.fields.some(({ searchable }) => searchable) ? [{ name: 'search', type: 'String' }] : []),
53
+ ...(targetModel.fields.some(({ orderable }) => orderable)
54
+ ? [{ name: 'orderBy', type: `${targetModel.name}OrderBy`, list: true }]
55
+ : []),
56
+ { name: 'limit', type: 'Int' },
57
+ { name: 'offset', type: 'Int' },
58
+ ],
59
+ })),
60
+ ],
61
+ [...(model.parent ? [model.parent] : []), ...(model.interfaces || [])]
62
+ ),
63
+ input(`${model.name}Where`, [
64
+ ...model.fields
65
+ .filter(({ kind, unique, filterable }) => (unique || filterable) && kind !== 'relation')
66
+ .map((field) => ({
67
+ name: field.name,
68
+ type: field.type,
69
+ list: true,
70
+ default: typeof field.filterable === 'object' ? field.filterable.default : undefined,
71
+ })),
72
+ ...model.fields
73
+ .filter(({ comparable }) => comparable)
74
+ .flatMap((field) => [
75
+ { name: `${field.name}_GT`, type: field.type },
76
+ { name: `${field.name}_GTE`, type: field.type },
77
+ { name: `${field.name}_LT`, type: field.type },
78
+ { name: `${field.name}_LTE`, type: field.type },
79
+ ]),
80
+ ...model.fields
81
+ .filter(isRelation)
82
+ .filter(({ filterable }) => filterable)
83
+ .map(({ name, type }) => ({
84
+ name,
85
+ type: `${type}Where`,
86
+ })),
87
+ ]),
88
+ input(
89
+ `${model.name}WhereUnique`,
90
+ model.fields.filter(({ unique }) => unique).map((field) => ({ name: field.name, type: field.type }))
91
+ ),
92
+ ...(model.fields.some(({ orderable }) => orderable)
93
+ ? [
94
+ input(
95
+ `${model.name}OrderBy`,
96
+ model.fields.filter(({ orderable }) => orderable).map(({ name }) => ({ name, type: 'Order' }))
97
+ ),
98
+ ]
99
+ : []),
100
+ ];
117
101
 
102
+ if (!isRootModel(model)) {
118
103
  if (model.creatable) {
119
104
  types.push(
120
105
  input(
@@ -153,16 +138,16 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
153
138
  )
154
139
  );
155
140
  }
156
- return types;
157
- })
158
- ),
141
+ }
159
142
 
143
+ return types;
144
+ }),
160
145
  object('Query', [
161
146
  {
162
147
  name: 'me',
163
148
  type: 'User',
164
149
  },
165
- ...models
150
+ ...entities
166
151
  .filter(({ queriable }) => queriable)
167
152
  .map(({ name }) => ({
168
153
  name: typeToField(name),
@@ -176,10 +161,10 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
176
161
  },
177
162
  ],
178
163
  })),
179
- ...models
164
+ ...entities
180
165
  .filter(({ listQueriable }) => listQueriable)
181
166
  .map((model) => ({
182
- name: getModelPluralField(model),
167
+ name: model.pluralField,
183
168
  type: model.name,
184
169
  list: true,
185
170
  nonNull: true,
@@ -193,17 +178,14 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
193
178
  { name: 'offset', type: 'Int' },
194
179
  ],
195
180
  })),
196
- ...rawModels
197
- .filter(isObjectModel)
198
- .filter((model) => model.name === 'Query')
199
- .flatMap((model) => model.fields),
181
+ ...objects.filter((model) => model.name === 'Query').flatMap((model) => model.fields),
200
182
  ]),
201
183
 
202
184
  object('Mutation', [
203
- ...flatMap(
204
- models.map((model): Field[] => {
205
- const mutations: Field[] = [];
185
+ ...entities.flatMap((model): Field[] => {
186
+ const mutations: Field[] = [];
206
187
 
188
+ if (!isRootModel(model)) {
207
189
  if (model.creatable) {
208
190
  mutations.push({
209
191
  name: `create${model.name}`,
@@ -269,19 +251,16 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
269
251
  ],
270
252
  });
271
253
  }
254
+ }
272
255
 
273
- return mutations;
274
- })
275
- ),
276
- ...rawModels
277
- .filter(isObjectModel)
278
- .filter((model) => model.name === 'Mutation')
279
- .flatMap((model) => model.fields),
256
+ return mutations;
257
+ }),
258
+ ...objects.filter((model) => model.name === 'Mutation').flatMap((model) => model.fields),
280
259
  ]),
281
260
  ];
282
261
  };
283
262
 
284
- export const generate = (rawModels: RawModels) => document(generateDefinitions(rawModels));
263
+ export const generate = (models: Models) => document(generateDefinitions(models));
285
264
 
286
265
  export const printSchema = (schema: GraphQLSchema): string =>
287
266
  [
@@ -297,4 +276,4 @@ export const printSchema = (schema: GraphQLSchema): string =>
297
276
 
298
277
  export const printSchemaFromDocument = (document: DocumentNode) => printSchema(buildASTSchema(document));
299
278
 
300
- export const printSchemaFromModels = (models: RawModels) => printSchema(buildASTSchema(generate(models)));
279
+ export const printSchemaFromModels = (models: Models) => printSchema(buildASTSchema(generate(models)));
@@ -77,10 +77,11 @@ export const object = (nme: string, fds: Field[], interfaces?: string[], dvs?: D
77
77
  directives: directives(dvs),
78
78
  });
79
79
 
80
- export const iface = (nme: string, fds: Field[], dvs?: Directive[]): InterfaceTypeDefinitionNode => ({
80
+ export const iface = (nme: string, fds: Field[], interfaces?: string[], dvs?: Directive[]): InterfaceTypeDefinitionNode => ({
81
81
  name: name(nme),
82
82
  fields: fields(fds),
83
83
  kind: 'InterfaceTypeDefinition',
84
+ interfaces: interfaces && interfaces.map((i) => namedType(i)),
84
85
  directives: directives(dvs),
85
86
  });
86
87
 
@@ -0,0 +1,83 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`inheritance mutations create 1`] = `
4
+ {
5
+ "createReview": {
6
+ "content": "A review",
7
+ "rating": 5,
8
+ },
9
+ }
10
+ `;
11
+
12
+ exports[`inheritance mutations delete and restore 1`] = `
13
+ {
14
+ "deleteReview": "817c55de-2f77-4159-bd44-9837d868f889",
15
+ }
16
+ `;
17
+
18
+ exports[`inheritance mutations delete and restore 2`] = `
19
+ {
20
+ "restoreReview": "817c55de-2f77-4159-bd44-9837d868f889",
21
+ }
22
+ `;
23
+
24
+ exports[`inheritance mutations update 1`] = `
25
+ {
26
+ "updateReview": {
27
+ "content": "A review",
28
+ "rating": 5,
29
+ },
30
+ }
31
+ `;
32
+
33
+ exports[`inheritance queries root type listQuery 1`] = `
34
+ {
35
+ "reactions": [
36
+ {
37
+ "content": "This is a review with a rating",
38
+ "rating": 5,
39
+ "type": "Review",
40
+ },
41
+ {
42
+ "content": "I do not know but here is the answer.",
43
+ "type": "Answer",
44
+ },
45
+ {
46
+ "content": "What is the question?",
47
+ "type": "Question",
48
+ },
49
+ ],
50
+ }
51
+ `;
52
+
53
+ exports[`inheritance queries root type query 1`] = `
54
+ {
55
+ "reaction": {
56
+ "content": "This is a review with a rating",
57
+ "rating": 5,
58
+ "type": "Review",
59
+ },
60
+ }
61
+ `;
62
+
63
+ exports[`inheritance queries sub type listQuery 1`] = `
64
+ {
65
+ "reviews": [
66
+ {
67
+ "content": "This is a review with a rating",
68
+ "rating": 5,
69
+ "type": "Review",
70
+ },
71
+ ],
72
+ }
73
+ `;
74
+
75
+ exports[`inheritance queries sub type query 1`] = `
76
+ {
77
+ "review": {
78
+ "content": "This is a review with a rating",
79
+ "rating": 5,
80
+ "type": "Review",
81
+ },
82
+ }
83
+ `;