@smartive/graphql-magic 1.0.1
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 +21 -0
- package/.github/workflows/release.yml +24 -0
- package/.github/workflows/testing.yml +37 -0
- package/.nvmrc +1 -0
- package/.prettierignore +34 -0
- package/.prettierrc.json +1 -0
- package/.releaserc +27 -0
- package/CHANGELOG.md +6 -0
- package/README.md +15 -0
- package/dist/cjs/index.cjs +2646 -0
- package/dist/esm/client/gql.d.ts +1 -0
- package/dist/esm/client/gql.js +5 -0
- package/dist/esm/client/gql.js.map +1 -0
- package/dist/esm/client/index.d.ts +2 -0
- package/dist/esm/client/index.js +4 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/queries.d.ts +24 -0
- package/dist/esm/client/queries.js +152 -0
- package/dist/esm/client/queries.js.map +1 -0
- package/dist/esm/context.d.ts +30 -0
- package/dist/esm/context.js +2 -0
- package/dist/esm/context.js.map +1 -0
- package/dist/esm/errors.d.ts +17 -0
- package/dist/esm/errors.js +27 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/generate/generate.d.ts +7 -0
- package/dist/esm/generate/generate.js +211 -0
- package/dist/esm/generate/generate.js.map +1 -0
- package/dist/esm/generate/index.d.ts +3 -0
- package/dist/esm/generate/index.js +5 -0
- package/dist/esm/generate/index.js.map +1 -0
- package/dist/esm/generate/mutations.d.ts +2 -0
- package/dist/esm/generate/mutations.js +18 -0
- package/dist/esm/generate/mutations.js.map +1 -0
- package/dist/esm/generate/utils.d.ts +22 -0
- package/dist/esm/generate/utils.js +150 -0
- package/dist/esm/generate/utils.js.map +1 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/migrations/generate.d.ts +28 -0
- package/dist/esm/migrations/generate.js +516 -0
- package/dist/esm/migrations/generate.js.map +1 -0
- package/dist/esm/migrations/index.d.ts +1 -0
- package/dist/esm/migrations/index.js +3 -0
- package/dist/esm/migrations/index.js.map +1 -0
- package/dist/esm/models.d.ts +170 -0
- package/dist/esm/models.js +27 -0
- package/dist/esm/models.js.map +1 -0
- package/dist/esm/permissions/check.d.ts +15 -0
- package/dist/esm/permissions/check.js +162 -0
- package/dist/esm/permissions/check.js.map +1 -0
- package/dist/esm/permissions/generate.d.ts +45 -0
- package/dist/esm/permissions/generate.js +77 -0
- package/dist/esm/permissions/generate.js.map +1 -0
- package/dist/esm/permissions/index.d.ts +2 -0
- package/dist/esm/permissions/index.js +4 -0
- package/dist/esm/permissions/index.js.map +1 -0
- package/dist/esm/resolvers/arguments.d.ts +26 -0
- package/dist/esm/resolvers/arguments.js +88 -0
- package/dist/esm/resolvers/arguments.js.map +1 -0
- package/dist/esm/resolvers/filters.d.ts +5 -0
- package/dist/esm/resolvers/filters.js +126 -0
- package/dist/esm/resolvers/filters.js.map +1 -0
- package/dist/esm/resolvers/index.d.ts +7 -0
- package/dist/esm/resolvers/index.js +9 -0
- package/dist/esm/resolvers/index.js.map +1 -0
- package/dist/esm/resolvers/mutations.d.ts +3 -0
- package/dist/esm/resolvers/mutations.js +255 -0
- package/dist/esm/resolvers/mutations.js.map +1 -0
- package/dist/esm/resolvers/node.d.ts +44 -0
- package/dist/esm/resolvers/node.js +102 -0
- package/dist/esm/resolvers/node.js.map +1 -0
- package/dist/esm/resolvers/resolver.d.ts +5 -0
- package/dist/esm/resolvers/resolver.js +143 -0
- package/dist/esm/resolvers/resolver.js.map +1 -0
- package/dist/esm/resolvers/resolvers.d.ts +9 -0
- package/dist/esm/resolvers/resolvers.js +39 -0
- package/dist/esm/resolvers/resolvers.js.map +1 -0
- package/dist/esm/resolvers/utils.d.ts +43 -0
- package/dist/esm/resolvers/utils.js +125 -0
- package/dist/esm/resolvers/utils.js.map +1 -0
- package/dist/esm/utils.d.ts +25 -0
- package/dist/esm/utils.js +159 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/values.d.ts +15 -0
- package/dist/esm/values.js +7 -0
- package/dist/esm/values.js.map +1 -0
- package/jest.config.ts +12 -0
- package/package.json +66 -0
- package/renovate.json +32 -0
- package/src/client/gql.ts +7 -0
- package/src/client/index.ts +4 -0
- package/src/client/queries.ts +251 -0
- package/src/context.ts +27 -0
- package/src/errors.ts +32 -0
- package/src/generate/generate.ts +273 -0
- package/src/generate/index.ts +5 -0
- package/src/generate/mutations.ts +35 -0
- package/src/generate/utils.ts +223 -0
- package/src/index.ts +12 -0
- package/src/migrations/generate.ts +633 -0
- package/src/migrations/index.ts +3 -0
- package/src/models.ts +228 -0
- package/src/permissions/check.ts +239 -0
- package/src/permissions/generate.ts +143 -0
- package/src/permissions/index.ts +4 -0
- package/src/resolvers/arguments.ts +129 -0
- package/src/resolvers/filters.ts +163 -0
- package/src/resolvers/index.ts +9 -0
- package/src/resolvers/mutations.ts +313 -0
- package/src/resolvers/node.ts +193 -0
- package/src/resolvers/resolver.ts +223 -0
- package/src/resolvers/resolvers.ts +40 -0
- package/src/resolvers/utils.ts +188 -0
- package/src/utils.ts +186 -0
- package/src/values.ts +19 -0
- package/tests/unit/__snapshots__/generate.spec.ts.snap +105 -0
- package/tests/unit/__snapshots__/resolve.spec.ts.snap +60 -0
- package/tests/unit/generate.spec.ts +8 -0
- package/tests/unit/resolve.spec.ts +128 -0
- package/tests/unit/utils.ts +82 -0
- package/tsconfig.jest.json +13 -0
- package/tsconfig.json +13 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import { pluralize } from 'inflection';
|
|
3
|
+
import camelCase from 'lodash/camelCase';
|
|
4
|
+
import lodashGet from 'lodash/get';
|
|
5
|
+
import kebabCase from 'lodash/kebabCase';
|
|
6
|
+
import startCase from 'lodash/startCase';
|
|
7
|
+
import { Model, Models, ObjectModel, RawModels, Relation, ReverseRelation, isObjectModel } from './models';
|
|
8
|
+
|
|
9
|
+
const isNotFalsy = <T>(v: T | null | undefined | false): v is T => typeof v !== 'undefined' && v !== null && v !== false;
|
|
10
|
+
|
|
11
|
+
export const merge = <T>(objects: ({ [name: string]: T } | undefined | false)[] | undefined): { [name: string]: T } =>
|
|
12
|
+
(objects || []).filter(isNotFalsy).reduce((i, acc) => ({ ...acc, ...i }), {});
|
|
13
|
+
|
|
14
|
+
// Target -> target
|
|
15
|
+
export const typeToField = (type: string) => type.substr(0, 1).toLowerCase() + type.substr(1);
|
|
16
|
+
|
|
17
|
+
export const getModelPlural = (model: ObjectModel | Model) => model.plural || pluralize(model.name);
|
|
18
|
+
|
|
19
|
+
export const getModelPluralField = (model: Model) => typeToField(getModelPlural(model));
|
|
20
|
+
|
|
21
|
+
export const getModelSlug = (model: Model) => kebabCase(getModelPlural(model));
|
|
22
|
+
|
|
23
|
+
export const getModelLabelPlural = (model: Model) => getLabel(getModelPlural(model));
|
|
24
|
+
|
|
25
|
+
export const getModelLabel = (model: Model) => getLabel(model.name);
|
|
26
|
+
|
|
27
|
+
export const getLabel = (s: string) => startCase(camelCase(s));
|
|
28
|
+
|
|
29
|
+
export const getModels = (rawModels: RawModels): Models => {
|
|
30
|
+
const models: Models = rawModels.filter(isObjectModel).map((model) => {
|
|
31
|
+
const objectModel: Model = {
|
|
32
|
+
...model,
|
|
33
|
+
fieldsByName: {},
|
|
34
|
+
relations: [],
|
|
35
|
+
relationsByName: {},
|
|
36
|
+
reverseRelations: [],
|
|
37
|
+
reverseRelationsByName: {},
|
|
38
|
+
fields: [
|
|
39
|
+
{ name: 'id', type: 'ID', nonNull: true, unique: true, primary: true, generated: true },
|
|
40
|
+
...model.fields,
|
|
41
|
+
...(model.creatable
|
|
42
|
+
? [
|
|
43
|
+
{ name: 'createdAt', type: 'DateTime', nonNull: !model.nonStrict, orderable: true, generated: true },
|
|
44
|
+
{
|
|
45
|
+
name: 'createdBy',
|
|
46
|
+
type: 'User',
|
|
47
|
+
relation: true,
|
|
48
|
+
nonNull: !model.nonStrict,
|
|
49
|
+
reverse: `created${getModelPlural(model)}`,
|
|
50
|
+
generated: true,
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
: []),
|
|
54
|
+
...(model.updatable
|
|
55
|
+
? [
|
|
56
|
+
{ name: 'updatedAt', type: 'DateTime', nonNull: !model.nonStrict, orderable: true, generated: true },
|
|
57
|
+
{
|
|
58
|
+
name: 'updatedBy',
|
|
59
|
+
type: 'User',
|
|
60
|
+
relation: true,
|
|
61
|
+
nonNull: !model.nonStrict,
|
|
62
|
+
reverse: `updated${getModelPlural(model)}`,
|
|
63
|
+
generated: true,
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
: []),
|
|
67
|
+
...(model.deletable
|
|
68
|
+
? [
|
|
69
|
+
{
|
|
70
|
+
name: 'deleted',
|
|
71
|
+
type: 'Boolean',
|
|
72
|
+
nonNull: true,
|
|
73
|
+
default: false,
|
|
74
|
+
filterable: true,
|
|
75
|
+
defaultFilter: false,
|
|
76
|
+
generated: true,
|
|
77
|
+
},
|
|
78
|
+
{ name: 'deletedAt', type: 'DateTime', orderable: true, generated: true },
|
|
79
|
+
{
|
|
80
|
+
name: 'deletedBy',
|
|
81
|
+
type: 'User',
|
|
82
|
+
relation: true,
|
|
83
|
+
reverse: `deleted${getModelPlural(model)}`,
|
|
84
|
+
generated: true,
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
: []),
|
|
88
|
+
].map(({ foreignKey, ...field }) => ({
|
|
89
|
+
...field,
|
|
90
|
+
...(field.relation && {
|
|
91
|
+
foreignKey: foreignKey || `${field.name}Id`,
|
|
92
|
+
}),
|
|
93
|
+
})),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
for (const field of objectModel.fields) {
|
|
97
|
+
objectModel.fieldsByName[field.name] = field;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return objectModel;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
for (const model of models) {
|
|
104
|
+
for (const field of model.fields.filter(({ relation }) => relation)) {
|
|
105
|
+
const fieldModel = summonByName(models, field.type);
|
|
106
|
+
|
|
107
|
+
const reverseRelation: ReverseRelation = {
|
|
108
|
+
name: field.reverse || (field.toOne ? typeToField(model.name) : getModelPluralField(model)),
|
|
109
|
+
foreignKey: get(field, 'foreignKey'),
|
|
110
|
+
type: model.name,
|
|
111
|
+
toOne: !!field.toOne,
|
|
112
|
+
fieldModel,
|
|
113
|
+
field,
|
|
114
|
+
model,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const relation: Relation = {
|
|
118
|
+
field,
|
|
119
|
+
model: fieldModel,
|
|
120
|
+
reverseRelation,
|
|
121
|
+
};
|
|
122
|
+
model.relations.push(relation);
|
|
123
|
+
model.relationsByName[relation.field.name] = relation;
|
|
124
|
+
|
|
125
|
+
fieldModel.reverseRelations.push(reverseRelation);
|
|
126
|
+
|
|
127
|
+
fieldModel.reverseRelationsByName[reverseRelation.name] = reverseRelation;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return models;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const summonByName = <T extends { name: string }>(array: T[], value: string) => summonByKey(array, 'name', value);
|
|
135
|
+
|
|
136
|
+
export const summonByKey = <T>(array: readonly T[] | undefined, key: string, value: unknown) =>
|
|
137
|
+
summon(array, (element: T) => lodashGet(element, key) === value, `No element found with ${key} ${value}`);
|
|
138
|
+
|
|
139
|
+
export const summon = <T>(array: readonly T[] | undefined, cb: Parameters<T[]['find']>[1], errorMessage?: string) => {
|
|
140
|
+
if (array === undefined) {
|
|
141
|
+
throw new Error('Base array is not defined.');
|
|
142
|
+
}
|
|
143
|
+
const result = array.find(cb);
|
|
144
|
+
if (result === undefined) {
|
|
145
|
+
console.trace();
|
|
146
|
+
throw new Error(errorMessage || 'Element not found.');
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
type ForSure<T> = T extends undefined | null ? never : T;
|
|
152
|
+
|
|
153
|
+
export const it = <T>(object: T | null | undefined): ForSure<T> => {
|
|
154
|
+
if (object === undefined || object === null) {
|
|
155
|
+
console.trace();
|
|
156
|
+
throw new Error('Base object is not defined.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return object as ForSure<T>;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const get = <T, U extends keyof ForSure<T>>(object: T | null | undefined, key: U): ForSure<ForSure<T>[U]> => {
|
|
163
|
+
const value = it(object)[key];
|
|
164
|
+
if (value === undefined || value === null) {
|
|
165
|
+
console.trace();
|
|
166
|
+
throw new Error(`Object doesn't have ${String(key)}`);
|
|
167
|
+
}
|
|
168
|
+
return value as ForSure<ForSure<T>[U]>;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export const getString = (v: unknown) => {
|
|
172
|
+
assert(typeof v === 'string');
|
|
173
|
+
return v;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const retry = async <T>(cb: () => Promise<T>, condition: (e: any) => boolean) => {
|
|
177
|
+
try {
|
|
178
|
+
return await cb();
|
|
179
|
+
} catch (e) {
|
|
180
|
+
if (condition(e)) {
|
|
181
|
+
return await cb();
|
|
182
|
+
} else {
|
|
183
|
+
throw e;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
package/src/values.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
|
|
3
|
+
export class Enum {
|
|
4
|
+
constructor(public value: string) {}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type BasicValue = undefined | null | boolean | string | number | DateTime;
|
|
8
|
+
|
|
9
|
+
export type Value = any; // BasicValue | Enum | Enum[] | Record<string, Value> | Value[];
|
|
10
|
+
|
|
11
|
+
export type Values = {
|
|
12
|
+
name: string;
|
|
13
|
+
values: Value;
|
|
14
|
+
}[];
|
|
15
|
+
|
|
16
|
+
export type Directive = {
|
|
17
|
+
name: string;
|
|
18
|
+
values?: Values | undefined;
|
|
19
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`generate generates a schema 1`] = `
|
|
4
|
+
"type AnotherObject {
|
|
5
|
+
id: ID!
|
|
6
|
+
manyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
input AnotherObjectWhere {
|
|
10
|
+
id: [ID!]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
input AnotherObjectWhereUnique {
|
|
14
|
+
id: ID
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
input CreateSomeObject {
|
|
18
|
+
xyz: Int!
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
scalar DateTime
|
|
22
|
+
|
|
23
|
+
type Mutation {
|
|
24
|
+
createSomeObject(data: CreateSomeObject!): SomeObject!
|
|
25
|
+
updateSomeObject(where: SomeObjectWhereUnique!, data: UpdateSomeObject!): SomeObject!
|
|
26
|
+
deleteSomeObject(where: SomeObjectWhereUnique!, dryRun: Boolean): ID!
|
|
27
|
+
restoreSomeObject(where: SomeObjectWhereUnique!): ID!
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
enum Order {
|
|
31
|
+
ASC
|
|
32
|
+
DESC
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type Query {
|
|
36
|
+
me: User
|
|
37
|
+
someObject(where: SomeObjectWhereUnique!): SomeObject!
|
|
38
|
+
manyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
enum SomeEnum {
|
|
42
|
+
A
|
|
43
|
+
B
|
|
44
|
+
C
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type SomeObject {
|
|
48
|
+
id: ID!
|
|
49
|
+
field: String
|
|
50
|
+
another: AnotherObject!
|
|
51
|
+
list(magic: Boolean): [Float!]!
|
|
52
|
+
xyz: Int!
|
|
53
|
+
createdAt: DateTime!
|
|
54
|
+
createdBy: User!
|
|
55
|
+
updatedAt: DateTime!
|
|
56
|
+
updatedBy: User!
|
|
57
|
+
deleted: Boolean!
|
|
58
|
+
deletedAt: DateTime
|
|
59
|
+
deletedBy: User
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
input SomeObjectOrderBy {
|
|
63
|
+
xyz: Order
|
|
64
|
+
createdAt: Order
|
|
65
|
+
updatedAt: Order
|
|
66
|
+
deletedAt: Order
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
input SomeObjectWhere {
|
|
70
|
+
id: [ID!]
|
|
71
|
+
deleted: [Boolean!] = false
|
|
72
|
+
another: AnotherObjectWhere
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
input SomeObjectWhereUnique {
|
|
76
|
+
id: ID
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type SomeRawObject {
|
|
80
|
+
field: String
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
input UpdateSomeObject {
|
|
84
|
+
xyz: Int
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
scalar Upload
|
|
88
|
+
|
|
89
|
+
type User {
|
|
90
|
+
id: ID!
|
|
91
|
+
username: String
|
|
92
|
+
createdManyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
|
|
93
|
+
updatedManyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
|
|
94
|
+
deletedManyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
input UserWhere {
|
|
98
|
+
id: [ID!]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
input UserWhereUnique {
|
|
102
|
+
id: ID
|
|
103
|
+
}
|
|
104
|
+
"
|
|
105
|
+
`;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`resolvers are generated correctly 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"Mutation": {
|
|
6
|
+
"createSomeObject": [Function],
|
|
7
|
+
"deleteSomeObject": [Function],
|
|
8
|
+
"restoreSomeObject": [Function],
|
|
9
|
+
"updateSomeObject": [Function],
|
|
10
|
+
},
|
|
11
|
+
"Query": {
|
|
12
|
+
"manyObjects": [Function],
|
|
13
|
+
"me": [Function],
|
|
14
|
+
"someObject": [Function],
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
exports[`resolvers resolve lists, many-to-one and one-to-many queries 1`] = `
|
|
20
|
+
{
|
|
21
|
+
"data": {
|
|
22
|
+
"manyObjects": [
|
|
23
|
+
{
|
|
24
|
+
"another": {
|
|
25
|
+
"id": "bar",
|
|
26
|
+
"manyObjects": [
|
|
27
|
+
{
|
|
28
|
+
"field": "foo",
|
|
29
|
+
"id": "foo",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
"field": "foo",
|
|
34
|
+
"id": "foo",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
exports[`resolvers resolve lists, many-to-one and one-to-many queries: query 1`] = `"select * from "User" where "id" = $1 limit $2"`;
|
|
42
|
+
|
|
43
|
+
exports[`resolvers resolve lists, many-to-one and one-to-many queries: query 2`] = `"select "SO"."id" as "SO__ID", "SO"."id" as "SO__id", "SO"."field" as "SO__field", "SO__a"."id" as "SO__a__ID", "SO__a"."id" as "SO__a__id" from "SomeObject" as "SO" left join "AnotherObject" as "SO__W__a" on "SO"."anotherId" = "SO__W__a"."id" left join "AnotherObject" as "SO__a" on "SO"."anotherId" = "SO__a"."id" where "SO__W__a"."id" in ($1) and "SO"."deleted" = $2 order by "SO"."xyz" DESC"`;
|
|
44
|
+
|
|
45
|
+
exports[`resolvers resolve lists, many-to-one and one-to-many queries: query 3`] = `"select "SO__a__mO"."id" as "SO__a__mO__ID", "SO__a__mO"."id" as "SO__a__mO__id", "SO__a__mO"."field" as "SO__a__mO__field", "SO__a__mO"."anotherId" as "SO__a__mO__anotherId" from "SomeObject" as "SO__a__mO" where "SO__a__mO"."id" in ($1) and "SO__a__mO"."deleted" = $2 and "SO__a__mO"."anotherId" = $3 order by "SO__a__mO"."createdAt" DESC"`;
|
|
46
|
+
|
|
47
|
+
exports[`resolvers resolve single query 1`] = `
|
|
48
|
+
{
|
|
49
|
+
"data": {
|
|
50
|
+
"someObject": {
|
|
51
|
+
"field": "foo",
|
|
52
|
+
"id": "foo",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
exports[`resolvers resolve single query: query 1`] = `"select * from "User" where "id" = $1 limit $2"`;
|
|
59
|
+
|
|
60
|
+
exports[`resolvers resolve single query: query 2`] = `"select "SO"."id" as "SO__ID", "SO"."id" as "SO__id", "SO"."field" as "SO__field" from "SomeObject" as "SO" where "SO"."id" = $1 and "SO"."deleted" = $2 order by "SO"."createdAt" DESC limit $3"`;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { makeExecutableSchema } from '@graphql-tools/schema';
|
|
2
|
+
import { execute, parse, Source } from 'graphql';
|
|
3
|
+
import knex from 'knex';
|
|
4
|
+
import { gql } from '../../src/client/gql';
|
|
5
|
+
import { Context } from '../../src/context';
|
|
6
|
+
import { generate } from '../../src/generate';
|
|
7
|
+
import { getResolvers } from '../../src/resolvers';
|
|
8
|
+
import { models, permissions, rawModels } from './utils';
|
|
9
|
+
import { DateTime } from 'luxon';
|
|
10
|
+
|
|
11
|
+
const test = async (operationName: string, query: string, variableValues: object, responses: unknown[]) => {
|
|
12
|
+
const knexInstance = knex({
|
|
13
|
+
client: 'postgresql',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
17
|
+
const mockKnex = require('mock-knex');
|
|
18
|
+
mockKnex.mock(knexInstance);
|
|
19
|
+
const tracker = mockKnex.getTracker();
|
|
20
|
+
|
|
21
|
+
tracker.install();
|
|
22
|
+
tracker.on('query', function someFunction(query, step) {
|
|
23
|
+
expect(query.sql).toMatchSnapshot('query');
|
|
24
|
+
query.response(responses[step - 1]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const user = await knexInstance('User').where({ id: 1 }).first();
|
|
28
|
+
const typeDefs = generate(rawModels);
|
|
29
|
+
const contextValue: Context = {
|
|
30
|
+
req: null as any,
|
|
31
|
+
knex: knexInstance,
|
|
32
|
+
document: typeDefs,
|
|
33
|
+
locale: 'en',
|
|
34
|
+
locales: ['en'],
|
|
35
|
+
user,
|
|
36
|
+
rawModels,
|
|
37
|
+
models,
|
|
38
|
+
permissions,
|
|
39
|
+
now: DateTime.fromISO('2020-01-01T00:00:00.000Z'),
|
|
40
|
+
};
|
|
41
|
+
const result = await execute({
|
|
42
|
+
schema: makeExecutableSchema({
|
|
43
|
+
typeDefs,
|
|
44
|
+
resolvers: getResolvers(models),
|
|
45
|
+
}),
|
|
46
|
+
document: parse(new Source(query, 'GraphQL request')),
|
|
47
|
+
contextValue,
|
|
48
|
+
variableValues,
|
|
49
|
+
operationName,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(result).toMatchSnapshot();
|
|
53
|
+
|
|
54
|
+
tracker.uninstall();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
describe('resolvers', () => {
|
|
58
|
+
it('are generated correctly', () => {
|
|
59
|
+
expect(getResolvers(models)).toMatchSnapshot();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('resolve lists, many-to-one and one-to-many queries', async () => {
|
|
63
|
+
await test(
|
|
64
|
+
'SomeQuery',
|
|
65
|
+
gql`
|
|
66
|
+
query SomeQuery {
|
|
67
|
+
manyObjects(where: { another: { id: "bar" } }, orderBy: [{ xyz: DESC }]) {
|
|
68
|
+
id
|
|
69
|
+
field
|
|
70
|
+
another {
|
|
71
|
+
id
|
|
72
|
+
manyObjects(where: { id: "foo" }) {
|
|
73
|
+
id
|
|
74
|
+
field
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`,
|
|
80
|
+
{},
|
|
81
|
+
[
|
|
82
|
+
{ id: 1, role: 'ADMIN' },
|
|
83
|
+
[
|
|
84
|
+
{
|
|
85
|
+
SO__ID: 'foo',
|
|
86
|
+
SO__id: 'foo',
|
|
87
|
+
SO__field: 'foo',
|
|
88
|
+
SO__a__ID: 'bar',
|
|
89
|
+
SO__a__id: 'bar',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
SO__a__mO__ID: 'foo',
|
|
95
|
+
SO__a__mO__id: 'foo',
|
|
96
|
+
SO__a__mO__field: 'foo',
|
|
97
|
+
SO__a__mO__anotherId: 'bar',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
]
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('resolve single query', async () => {
|
|
105
|
+
await test(
|
|
106
|
+
'SomeQuery',
|
|
107
|
+
gql`
|
|
108
|
+
query SomeQuery {
|
|
109
|
+
someObject(where: { id: "foo" }) {
|
|
110
|
+
id
|
|
111
|
+
field
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
`,
|
|
115
|
+
{},
|
|
116
|
+
[
|
|
117
|
+
{ id: 1, role: 'ADMIN' },
|
|
118
|
+
[
|
|
119
|
+
{
|
|
120
|
+
SO__ID: 'foo',
|
|
121
|
+
SO__id: 'foo',
|
|
122
|
+
SO__field: 'foo',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
]
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { RawModels } from '../../src/models';
|
|
2
|
+
import { generatePermissions, PermissionsConfig } from '../../src/permissions/generate';
|
|
3
|
+
import { getModels } from '../../src/utils';
|
|
4
|
+
|
|
5
|
+
export const rawModels: RawModels = [
|
|
6
|
+
{
|
|
7
|
+
name: 'SomeEnum',
|
|
8
|
+
type: 'enum',
|
|
9
|
+
values: ['A', 'B', 'C'],
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
name: 'SomeRawObject',
|
|
14
|
+
type: 'raw-object',
|
|
15
|
+
fields: [{ name: 'field', type: 'String' }],
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
type: 'object',
|
|
20
|
+
name: 'User',
|
|
21
|
+
fields: [
|
|
22
|
+
{
|
|
23
|
+
name: 'username',
|
|
24
|
+
type: 'String',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'object',
|
|
30
|
+
name: 'SomeObject',
|
|
31
|
+
plural: 'ManyObjects',
|
|
32
|
+
description: 'An object',
|
|
33
|
+
queriable: true,
|
|
34
|
+
listQueriable: true,
|
|
35
|
+
creatable: true,
|
|
36
|
+
updatable: true,
|
|
37
|
+
deletable: true,
|
|
38
|
+
fields: [
|
|
39
|
+
{
|
|
40
|
+
name: 'field',
|
|
41
|
+
searchable: true,
|
|
42
|
+
type: 'String',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'another',
|
|
46
|
+
type: 'AnotherObject',
|
|
47
|
+
relation: true,
|
|
48
|
+
filterable: true,
|
|
49
|
+
nonNull: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'list',
|
|
53
|
+
type: 'Float',
|
|
54
|
+
nonNull: true,
|
|
55
|
+
list: true,
|
|
56
|
+
args: [{ name: 'magic', type: 'Boolean' }],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'xyz',
|
|
60
|
+
type: 'Int',
|
|
61
|
+
description: 'yay',
|
|
62
|
+
nonNull: true,
|
|
63
|
+
creatable: true,
|
|
64
|
+
updatable: true,
|
|
65
|
+
orderable: true,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'object',
|
|
71
|
+
name: 'AnotherObject',
|
|
72
|
+
fields: [],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
export const models = getModels(rawModels);
|
|
77
|
+
|
|
78
|
+
const permissionsConfig: PermissionsConfig = {
|
|
79
|
+
ADMIN: true,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const permissions = generatePermissions(models, permissionsConfig);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"include": ["**/*.ts", "**/*.tsx", "**/unit/**/*.spec.ts"],
|
|
4
|
+
"exclude": ["node_modules", "dist"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"rootDir": "./",
|
|
7
|
+
"types": ["jest"],
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"moduleResolution": "node",
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"allowJs": true
|
|
12
|
+
}
|
|
13
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["./src/**/*.ts", "./src/**/*.tsx"],
|
|
3
|
+
"exclude": ["node_modules", "dist"],
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "./dist/esm",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"target": "esnext",
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowSyntheticDefaultImports": true
|
|
12
|
+
}
|
|
13
|
+
}
|