@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.
- package/.eslintrc +2 -10
- package/.github/workflows/release.yml +1 -1
- package/.gqmrc.json +6 -0
- package/CHANGELOG.md +2 -2
- package/README.md +1 -1
- package/dist/bin/gqm.cjs +684 -330
- package/dist/cjs/index.cjs +998 -554
- package/dist/esm/api/execute.js +1 -1
- package/dist/esm/api/execute.js.map +1 -1
- package/dist/esm/client/mutations.d.ts +2 -2
- package/dist/esm/client/mutations.js +5 -4
- package/dist/esm/client/mutations.js.map +1 -1
- package/dist/esm/client/queries.d.ts +12 -17
- package/dist/esm/client/queries.js +30 -50
- package/dist/esm/client/queries.js.map +1 -1
- package/dist/esm/context.d.ts +1 -2
- package/dist/esm/db/generate.d.ts +3 -3
- package/dist/esm/db/generate.js +31 -29
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.d.ts +3 -4
- package/dist/esm/migrations/generate.js +114 -107
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models/index.d.ts +1 -0
- package/dist/esm/models/index.js +1 -0
- package/dist/esm/models/index.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +189 -0
- package/dist/esm/models/model-definitions.js +2 -0
- package/dist/esm/models/model-definitions.js.map +1 -0
- package/dist/esm/models/models.d.ts +128 -174
- package/dist/esm/models/models.js +411 -1
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/mutation-hook.d.ts +2 -2
- package/dist/esm/models/utils.d.ts +35 -497
- package/dist/esm/models/utils.js +21 -144
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.d.ts +3 -3
- package/dist/esm/permissions/check.js +14 -7
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/permissions/generate.js +6 -6
- package/dist/esm/permissions/generate.js.map +1 -1
- package/dist/esm/resolvers/filters.d.ts +8 -0
- package/dist/esm/resolvers/filters.js +28 -25
- package/dist/esm/resolvers/filters.js.map +1 -1
- package/dist/esm/resolvers/index.d.ts +1 -0
- package/dist/esm/resolvers/index.js +1 -0
- package/dist/esm/resolvers/index.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +85 -21
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/node.d.ts +13 -15
- package/dist/esm/resolvers/node.js +41 -36
- package/dist/esm/resolvers/node.js.map +1 -1
- package/dist/esm/resolvers/resolver.js +19 -49
- package/dist/esm/resolvers/resolver.js.map +1 -1
- package/dist/esm/resolvers/resolvers.d.ts +1 -8
- package/dist/esm/resolvers/resolvers.js +15 -7
- package/dist/esm/resolvers/resolvers.js.map +1 -1
- package/dist/esm/resolvers/selects.d.ts +3 -0
- package/dist/esm/resolvers/selects.js +50 -0
- package/dist/esm/resolvers/selects.js.map +1 -0
- package/dist/esm/resolvers/utils.d.ts +12 -4
- package/dist/esm/resolvers/utils.js +30 -22
- package/dist/esm/resolvers/utils.js.map +1 -1
- package/dist/esm/schema/generate.d.ts +4 -4
- package/dist/esm/schema/generate.js +122 -131
- package/dist/esm/schema/generate.js.map +1 -1
- package/dist/esm/schema/utils.d.ts +1 -1
- package/dist/esm/schema/utils.js +2 -1
- package/dist/esm/schema/utils.js.map +1 -1
- package/knexfile.ts +31 -0
- package/migrations/20230912185644_setup.ts +127 -0
- package/package.json +16 -14
- package/src/api/execute.ts +1 -1
- package/src/bin/gqm/gqm.ts +25 -23
- package/src/bin/gqm/parse-models.ts +5 -5
- package/src/bin/gqm/settings.ts +13 -4
- package/src/bin/gqm/static-eval.ts +5 -0
- package/src/bin/gqm/templates.ts +23 -3
- package/src/client/mutations.ts +11 -5
- package/src/client/queries.ts +43 -80
- package/src/context.ts +1 -2
- package/src/db/generate.ts +41 -41
- package/src/migrations/generate.ts +165 -146
- package/src/models/index.ts +1 -0
- package/src/models/model-definitions.ts +168 -0
- package/src/models/models.ts +510 -166
- package/src/models/mutation-hook.ts +2 -2
- package/src/models/utils.ts +53 -187
- package/src/permissions/check.ts +19 -11
- package/src/permissions/generate.ts +6 -6
- package/src/resolvers/filters.ts +44 -28
- package/src/resolvers/index.ts +1 -0
- package/src/resolvers/mutations.ts +98 -36
- package/src/resolvers/node.ts +79 -51
- package/src/resolvers/resolver.ts +20 -74
- package/src/resolvers/resolvers.ts +18 -7
- package/src/resolvers/selects.ts +77 -0
- package/src/resolvers/utils.ts +41 -25
- package/src/schema/generate.ts +106 -127
- package/src/schema/utils.ts +2 -1
- package/tests/api/__snapshots__/inheritance.spec.ts.snap +83 -0
- package/tests/api/inheritance.spec.ts +130 -0
- package/tests/generated/api/index.ts +1174 -0
- package/tests/generated/client/index.ts +1163 -0
- package/tests/generated/client/mutations.ts +109 -0
- package/tests/generated/db/index.ts +291 -0
- package/tests/generated/db/knex.ts +14 -0
- package/tests/generated/models.json +675 -0
- package/tests/generated/schema.graphql +325 -0
- package/tests/unit/__snapshots__/resolve.spec.ts.snap +23 -0
- package/tests/unit/queries.spec.ts +5 -5
- package/tests/unit/resolve.spec.ts +8 -8
- package/tests/utils/database/knex.ts +5 -13
- package/tests/utils/database/seed.ts +57 -18
- package/tests/utils/models.ts +62 -7
- package/tests/utils/server.ts +5 -5
- package/tsconfig.eslint.json +1 -0
- package/tests/unit/__snapshots__/generate.spec.ts.snap +0 -128
- package/tests/unit/generate.spec.ts +0 -8
- package/tests/utils/database/schema.ts +0 -64
- package/tests/utils/generate-migration.ts +0 -24
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Models } from '../models/models';
|
|
2
|
-
import {
|
|
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
|
-
[
|
|
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
|
+
};
|
package/src/resolvers/utils.ts
CHANGED
|
@@ -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
|
|
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
|
|
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(`^${
|
|
79
|
-
const allParts = [
|
|
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[
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
153
|
-
|
|
152
|
+
table2Name: string,
|
|
153
|
+
table2Alias: string,
|
|
154
154
|
column1: string,
|
|
155
155
|
column2: string
|
|
156
156
|
) => {
|
|
157
|
-
|
|
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
|
+
};
|
package/src/schema/generate.ts
CHANGED
|
@@ -1,120 +1,105 @@
|
|
|
1
1
|
import { DefinitionNode, DocumentNode, GraphQLSchema, buildASTSchema, print } from 'graphql';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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(
|
|
24
|
-
|
|
25
|
-
scalar(
|
|
26
|
-
|
|
27
|
-
...
|
|
28
|
-
...
|
|
29
|
-
...
|
|
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
|
-
|
|
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
|
-
...
|
|
42
|
-
.filter(isObjectModel)
|
|
28
|
+
...objects
|
|
43
29
|
.filter((model) =>
|
|
44
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
...
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
...
|
|
164
|
+
...entities
|
|
180
165
|
.filter(({ listQueriable }) => listQueriable)
|
|
181
166
|
.map((model) => ({
|
|
182
|
-
name:
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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 = (
|
|
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:
|
|
279
|
+
export const printSchemaFromModels = (models: Models) => printSchema(buildASTSchema(generate(models)));
|
package/src/schema/utils.ts
CHANGED
|
@@ -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
|
+
`;
|