@smartive/graphql-magic 16.3.7 → 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.
- package/CHANGELOG.md +2 -2
- package/dist/bin/gqm.cjs +37 -35
- package/dist/cjs/index.cjs +51 -48
- package/dist/esm/api/execute.d.ts +1 -5
- package/dist/esm/api/execute.js.map +1 -1
- package/dist/esm/client/queries.d.ts +4 -4
- package/dist/esm/client/queries.js.map +1 -1
- package/dist/esm/db/generate.js +2 -0
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.js +1 -1
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models/models.d.ts +1 -1
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/utils.d.ts +8 -12
- package/dist/esm/models/utils.js +3 -5
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.js +0 -15
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/permissions/generate.d.ts +5 -19
- package/dist/esm/permissions/generate.js.map +1 -1
- package/dist/esm/resolvers/arguments.js.map +1 -1
- package/dist/esm/resolvers/filters.js +0 -2
- package/dist/esm/resolvers/filters.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +5 -4
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/node.js.map +1 -1
- package/dist/esm/resolvers/resolver.d.ts +2 -2
- package/dist/esm/resolvers/resolver.js +1 -1
- package/dist/esm/resolvers/resolver.js.map +1 -1
- package/dist/esm/resolvers/selects.js.map +1 -1
- package/dist/esm/resolvers/utils.d.ts +2 -6
- package/dist/esm/resolvers/utils.js +4 -2
- package/dist/esm/resolvers/utils.js.map +1 -1
- package/dist/esm/schema/utils.d.ts +4 -4
- package/dist/esm/schema/utils.js +31 -30
- package/dist/esm/schema/utils.js.map +1 -1
- package/dist/esm/utils/dates.d.ts +2 -4
- package/dist/esm/utils/dates.js +1 -3
- package/dist/esm/utils/dates.js.map +1 -1
- package/docs/docs/1-tutorial.md +1 -1
- package/eslint.config.mjs +42 -0
- package/migrations/20230912185644_setup.ts +1 -1
- package/package.json +6 -11
- package/src/api/execute.ts +1 -0
- package/src/bin/gqm/codegen.ts +1 -1
- package/src/client/gql.ts +1 -1
- package/src/client/mutations.ts +8 -8
- package/src/client/queries.ts +15 -14
- package/src/db/generate.ts +8 -5
- package/src/migrations/generate.ts +26 -22
- package/src/models/models.ts +24 -9
- package/src/models/mutation-hook.ts +1 -1
- package/src/models/utils.ts +8 -7
- package/src/permissions/check.ts +22 -30
- package/src/permissions/generate.ts +8 -25
- package/src/resolvers/arguments.ts +7 -2
- package/src/resolvers/filters.ts +8 -10
- package/src/resolvers/mutations.ts +19 -16
- package/src/resolvers/node.ts +3 -2
- package/src/resolvers/resolver.ts +11 -10
- package/src/resolvers/selects.ts +4 -3
- package/src/resolvers/utils.ts +15 -10
- package/src/schema/generate.ts +11 -11
- package/src/schema/utils.ts +84 -82
- package/src/utils/dates.ts +3 -3
- package/tests/generated/api/index.ts +2 -2
- package/tests/generated/client/index.ts +1 -193
- package/tests/generated/db/index.ts +4 -4
- package/tests/generated/models.json +2 -1
- package/tests/generated/schema.graphql +1 -1
- package/tests/utils/graphql-client.ts +49 -0
- package/tests/utils/models.ts +1 -0
- package/tests/utils/server.ts +4 -5
- package/tsconfig.eslint.json +18 -3
- package/tsconfig.json +3 -1
- package/.eslintrc +0 -13
package/src/models/utils.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
|
|
20
20
|
const isNotFalsy = <T>(v: T | null | undefined | false): v is T => typeof v !== 'undefined' && v !== null && v !== false;
|
|
21
21
|
|
|
22
|
-
export const merge = <T>(objects: (
|
|
22
|
+
export const merge = <T>(objects: (Record<string, T> | undefined | false)[] | undefined): Record<string, T> =>
|
|
23
23
|
(objects || []).filter(isNotFalsy).reduce((i, acc) => ({ ...acc, ...i }), {});
|
|
24
24
|
|
|
25
25
|
// Target -> target
|
|
@@ -42,7 +42,7 @@ export const not =
|
|
|
42
42
|
(field: T) =>
|
|
43
43
|
!predicate(field);
|
|
44
44
|
|
|
45
|
-
export const isRootModel = (model: EntityModel) => model.root;
|
|
45
|
+
export const isRootModel = (model: EntityModel) => !!model.root;
|
|
46
46
|
|
|
47
47
|
export const isEntityModel = (model: Model): model is EntityModel => model instanceof EntityModel;
|
|
48
48
|
|
|
@@ -77,7 +77,7 @@ export const isEnum = (field: EntityField): field is EnumField => field.kind ===
|
|
|
77
77
|
|
|
78
78
|
export const isRelation = (field: EntityField): field is RelationField => field.kind === 'relation';
|
|
79
79
|
|
|
80
|
-
export const isInherited = (field: EntityField) => field.inherited;
|
|
80
|
+
export const isInherited = (field: EntityField) => !!field.inherited;
|
|
81
81
|
|
|
82
82
|
export const isInTable = (field: EntityField) => field.name === 'id' || !field.inherited;
|
|
83
83
|
|
|
@@ -114,7 +114,7 @@ export const getActionableRelations = (model: EntityModel, action: 'create' | 'u
|
|
|
114
114
|
(relation) =>
|
|
115
115
|
relation.field[
|
|
116
116
|
`${action === 'filter' ? action : action.slice(0, -1)}able` as 'filterable' | 'creatable' | 'updatable'
|
|
117
|
-
]
|
|
117
|
+
],
|
|
118
118
|
)
|
|
119
119
|
.map(({ name }) => name);
|
|
120
120
|
|
|
@@ -133,6 +133,7 @@ export const summon = <T>(array: readonly T[] | undefined, cb: Parameters<T[]['f
|
|
|
133
133
|
console.trace();
|
|
134
134
|
throw new Error(errorMessage || 'Element not found.');
|
|
135
135
|
}
|
|
136
|
+
|
|
136
137
|
return result;
|
|
137
138
|
};
|
|
138
139
|
|
|
@@ -154,11 +155,13 @@ export const get = <T, U extends keyof ForSure<T>>(object: T | null | undefined,
|
|
|
154
155
|
console.warn(error);
|
|
155
156
|
throw error;
|
|
156
157
|
}
|
|
158
|
+
|
|
157
159
|
return value as ForSure<ForSure<T>[U]>;
|
|
158
160
|
};
|
|
159
161
|
|
|
160
162
|
export const getString = (v: unknown) => {
|
|
161
163
|
assert(typeof v === 'string');
|
|
164
|
+
|
|
162
165
|
return v;
|
|
163
166
|
};
|
|
164
167
|
|
|
@@ -168,9 +171,8 @@ export const retry = async <T>(cb: () => Promise<T>, condition: (e: any) => bool
|
|
|
168
171
|
} catch (e) {
|
|
169
172
|
if (condition(e)) {
|
|
170
173
|
return await cb();
|
|
171
|
-
} else {
|
|
172
|
-
throw e;
|
|
173
174
|
}
|
|
175
|
+
throw e;
|
|
174
176
|
}
|
|
175
177
|
};
|
|
176
178
|
|
|
@@ -182,7 +184,6 @@ type Typeof = {
|
|
|
182
184
|
symbol: symbol;
|
|
183
185
|
undefined: undefined;
|
|
184
186
|
object: object;
|
|
185
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
186
187
|
function: Function;
|
|
187
188
|
};
|
|
188
189
|
|
package/src/permissions/check.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const getRole = (ctx: Pick<FullContext, 'user'>) => ctx.user?.role ?? 'UN
|
|
|
11
11
|
export const getPermissionStack = (
|
|
12
12
|
ctx: Pick<FullContext, 'permissions' | 'user'>,
|
|
13
13
|
type: string,
|
|
14
|
-
action: PermissionAction
|
|
14
|
+
action: PermissionAction,
|
|
15
15
|
): boolean | PermissionStack => {
|
|
16
16
|
const rolePermissions = ctx.permissions[getRole(ctx)];
|
|
17
17
|
if (typeof rolePermissions === 'boolean' || rolePermissions === undefined) {
|
|
@@ -37,7 +37,7 @@ export const applyPermissions = (
|
|
|
37
37
|
tableAlias: string,
|
|
38
38
|
query: Knex.QueryBuilder,
|
|
39
39
|
action: PermissionAction,
|
|
40
|
-
verifiedPermissionStack?: PermissionStack
|
|
40
|
+
verifiedPermissionStack?: PermissionStack,
|
|
41
41
|
): boolean | PermissionStack => {
|
|
42
42
|
const permissionStack = getPermissionStack(ctx, type, action);
|
|
43
43
|
|
|
@@ -47,8 +47,9 @@ export const applyPermissions = (
|
|
|
47
47
|
|
|
48
48
|
if (permissionStack === false) {
|
|
49
49
|
console.error(`No applicable permissions exist for ${getRole(ctx)} ${type} ${action}.`);
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
query.where(false);
|
|
52
|
+
|
|
52
53
|
return permissionStack;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -59,8 +60,8 @@ export const applyPermissions = (
|
|
|
59
60
|
hash(prefixChain) === hash(chain.slice(0, -1)) &&
|
|
60
61
|
// TODO: this is stricter than it could be if we add these checks to the query
|
|
61
62
|
!('where' in get(chain, chain.length - 1)) &&
|
|
62
|
-
!('me' in get(chain, chain.length - 1))
|
|
63
|
-
)
|
|
63
|
+
!('me' in get(chain, chain.length - 1)),
|
|
64
|
+
),
|
|
64
65
|
)
|
|
65
66
|
) {
|
|
66
67
|
// The user has access to a parent entity with one or more from a set of rules, all of which are inherited by this entity
|
|
@@ -68,15 +69,14 @@ export const applyPermissions = (
|
|
|
68
69
|
return permissionStack;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
72
72
|
ors(
|
|
73
73
|
query,
|
|
74
74
|
permissionStack.map(
|
|
75
75
|
(links) => (query) =>
|
|
76
76
|
query
|
|
77
77
|
.whereNull(`${tableAlias}.id`)
|
|
78
|
-
.orWhereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, ctx.knex.raw(`"${tableAlias}".id`)))
|
|
79
|
-
)
|
|
78
|
+
.orWhereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, ctx.knex.raw(`"${tableAlias}".id`))),
|
|
79
|
+
),
|
|
80
80
|
);
|
|
81
81
|
|
|
82
82
|
return permissionStack;
|
|
@@ -89,7 +89,7 @@ export const getEntityToMutate = async (
|
|
|
89
89
|
ctx: Pick<FullContext, 'models' | 'permissions' | 'user' | 'knex'>,
|
|
90
90
|
model: EntityModel,
|
|
91
91
|
where: Record<string, unknown>,
|
|
92
|
-
action: 'UPDATE' | 'DELETE' | 'RESTORE'
|
|
92
|
+
action: 'UPDATE' | 'DELETE' | 'RESTORE',
|
|
93
93
|
) => {
|
|
94
94
|
const query = ctx
|
|
95
95
|
.knex(model.parent || model.name)
|
|
@@ -101,7 +101,7 @@ export const getEntityToMutate = async (
|
|
|
101
101
|
console.error(
|
|
102
102
|
`Not found: ${Object.entries(where)
|
|
103
103
|
.map(([key, value]) => `${key}: ${value}`)
|
|
104
|
-
.join(', ')}
|
|
104
|
+
.join(', ')}`,
|
|
105
105
|
);
|
|
106
106
|
throw new NotFoundError(`Entity to ${action.toLowerCase()}`);
|
|
107
107
|
}
|
|
@@ -112,7 +112,7 @@ export const getEntityToMutate = async (
|
|
|
112
112
|
console.error(
|
|
113
113
|
`Permission error: ${Object.entries(where)
|
|
114
114
|
.map(([key, value]) => `${key}: ${value}`)
|
|
115
|
-
.join(', ')}
|
|
115
|
+
.join(', ')}`,
|
|
116
116
|
);
|
|
117
117
|
throw new PermissionError(getRole(ctx), action, `this ${model.name}`, 'no available permissions applied');
|
|
118
118
|
}
|
|
@@ -132,7 +132,7 @@ export const checkCanWrite = async (
|
|
|
132
132
|
ctx: Pick<FullContext, 'models' | 'permissions' | 'user' | 'knex'>,
|
|
133
133
|
model: EntityModel,
|
|
134
134
|
data: Record<string, unknown>,
|
|
135
|
-
action: 'CREATE' | 'UPDATE'
|
|
135
|
+
action: 'CREATE' | 'UPDATE',
|
|
136
136
|
) => {
|
|
137
137
|
const permissionStack = getPermissionStack(ctx, model.name, action);
|
|
138
138
|
|
|
@@ -143,7 +143,6 @@ export const checkCanWrite = async (
|
|
|
143
143
|
throw new PermissionError(getRole(ctx), action, model.plural, 'no applicable permissions');
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- using `select(1 as any)` to instantiate an "empty" query builder
|
|
147
146
|
const query = ctx.knex.select(1 as any).first();
|
|
148
147
|
let linked = false;
|
|
149
148
|
|
|
@@ -168,7 +167,7 @@ export const checkCanWrite = async (
|
|
|
168
167
|
|
|
169
168
|
if (fieldPermissionStack === true) {
|
|
170
169
|
// User can link any entity from this type, just check whether it exists
|
|
171
|
-
|
|
170
|
+
|
|
172
171
|
query.whereExists((subQuery) => subQuery.from(`${field.type} as a`).whereRaw(`a.id = ?`, foreignId));
|
|
173
172
|
continue;
|
|
174
173
|
}
|
|
@@ -178,16 +177,15 @@ export const checkCanWrite = async (
|
|
|
178
177
|
role,
|
|
179
178
|
action,
|
|
180
179
|
`this ${model.name}'s ${field.name}`,
|
|
181
|
-
'no applicable permissions on data to link'
|
|
180
|
+
'no applicable permissions on data to link',
|
|
182
181
|
);
|
|
183
182
|
}
|
|
184
183
|
|
|
185
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
186
184
|
ors(
|
|
187
185
|
query,
|
|
188
186
|
fieldPermissionStack.map(
|
|
189
|
-
(links) => (query) => query.whereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, foreignId))
|
|
190
|
-
)
|
|
187
|
+
(links) => (query) => query.whereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, foreignId)),
|
|
188
|
+
),
|
|
191
189
|
);
|
|
192
190
|
}
|
|
193
191
|
|
|
@@ -206,7 +204,7 @@ const permissionLinkQuery = (
|
|
|
206
204
|
ctx: Pick<FullContext, 'models' | 'user'>,
|
|
207
205
|
subQuery: Knex.QueryBuilder,
|
|
208
206
|
links: PermissionLink[],
|
|
209
|
-
id: Knex.RawBinding | Knex.ValueDict
|
|
207
|
+
id: Knex.RawBinding | Knex.ValueDict,
|
|
210
208
|
) => {
|
|
211
209
|
const aliases = new AliasGenerator();
|
|
212
210
|
let alias = aliases.getShort();
|
|
@@ -214,16 +212,14 @@ const permissionLinkQuery = (
|
|
|
214
212
|
|
|
215
213
|
if (me) {
|
|
216
214
|
if (!ctx.user) {
|
|
217
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
218
215
|
subQuery.where(false);
|
|
216
|
+
|
|
219
217
|
return;
|
|
220
218
|
}
|
|
221
219
|
|
|
222
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
223
220
|
subQuery.where({ [`${alias}.id`]: ctx.user.id });
|
|
224
221
|
}
|
|
225
222
|
|
|
226
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
227
223
|
subQuery.from(`${type} as ${alias}`);
|
|
228
224
|
|
|
229
225
|
if (where) {
|
|
@@ -234,20 +230,18 @@ const permissionLinkQuery = (
|
|
|
234
230
|
const model = ctx.models.getModel(type, 'entity');
|
|
235
231
|
const subAlias = aliases.getShort();
|
|
236
232
|
if (reverse) {
|
|
237
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
238
233
|
subQuery.leftJoin(`${type} as ${subAlias}`, `${alias}.${foreignKey || 'id'}`, `${subAlias}.id`);
|
|
239
234
|
} else {
|
|
240
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
241
235
|
subQuery.rightJoin(`${type} as ${subAlias}`, `${alias}.id`, `${subAlias}.${foreignKey || 'id'}`);
|
|
242
236
|
}
|
|
243
|
-
|
|
237
|
+
|
|
244
238
|
subQuery.where({ [`${subAlias}.deleted`]: false });
|
|
245
239
|
if (where) {
|
|
246
240
|
applyWhere(model, subQuery, subAlias, where, aliases);
|
|
247
241
|
}
|
|
248
242
|
alias = subAlias;
|
|
249
243
|
}
|
|
250
|
-
|
|
244
|
+
|
|
251
245
|
subQuery.whereRaw(`"${alias}".id = ?`, id);
|
|
252
246
|
};
|
|
253
247
|
|
|
@@ -257,18 +251,16 @@ const applyWhere = (model: EntityModel, query: Knex.QueryBuilder, alias: string,
|
|
|
257
251
|
|
|
258
252
|
if (relation) {
|
|
259
253
|
const subAlias = aliases.getShort();
|
|
260
|
-
|
|
254
|
+
|
|
261
255
|
query.leftJoin(
|
|
262
256
|
`${relation.targetModel.name} as ${subAlias}`,
|
|
263
257
|
`${alias}.${relation.field.foreignKey || `${relation.field.name}Id`}`,
|
|
264
|
-
`${subAlias}.id
|
|
258
|
+
`${subAlias}.id`,
|
|
265
259
|
);
|
|
266
260
|
applyWhere(relation.targetModel, query, subAlias, value, aliases);
|
|
267
261
|
} else if (Array.isArray(value)) {
|
|
268
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
269
262
|
query.whereIn(`${alias}.${key}`, value);
|
|
270
263
|
} else {
|
|
271
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
272
264
|
query.where({ [`${alias}.${key}`]: value });
|
|
273
265
|
}
|
|
274
266
|
}
|
|
@@ -8,36 +8,19 @@ const ACTIONS: PermissionAction[] = ['READ', 'CREATE', 'UPDATE', 'DELETE', 'REST
|
|
|
8
8
|
/**
|
|
9
9
|
* Initial representation (tree structure, as defined by user).
|
|
10
10
|
*/
|
|
11
|
-
export type PermissionsConfig =
|
|
12
|
-
[role: string]:
|
|
13
|
-
| true
|
|
14
|
-
| {
|
|
15
|
-
[type: string]: PermissionsBlock;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
11
|
+
export type PermissionsConfig = Record<string, true | Record<string, PermissionsBlock>>;
|
|
18
12
|
|
|
19
|
-
export type PermissionsBlock = {
|
|
20
|
-
[action in PermissionAction]?: true;
|
|
21
|
-
} & {
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export type PermissionsBlock = Partial<Record<PermissionAction, true>> & {
|
|
23
14
|
WHERE?: Record<string, any>;
|
|
24
|
-
RELATIONS?:
|
|
25
|
-
[relation: string]: PermissionsBlock;
|
|
26
|
-
};
|
|
15
|
+
RELATIONS?: Record<string, PermissionsBlock>;
|
|
27
16
|
};
|
|
28
17
|
|
|
29
18
|
/**
|
|
30
19
|
* Final representation (lookup table (role, model, action) -> permission stack).
|
|
31
20
|
*/
|
|
32
|
-
export type Permissions =
|
|
33
|
-
[role: string]: true | RolePermissions;
|
|
34
|
-
};
|
|
21
|
+
export type Permissions = Record<string, true | RolePermissions>;
|
|
35
22
|
|
|
36
|
-
type RolePermissions =
|
|
37
|
-
[type: string]: {
|
|
38
|
-
[action in PermissionAction]?: true | PermissionStack;
|
|
39
|
-
};
|
|
40
|
-
};
|
|
23
|
+
type RolePermissions = Record<string, Partial<Record<PermissionAction, true | PermissionStack>>>;
|
|
41
24
|
|
|
42
25
|
/**
|
|
43
26
|
* For a given role, model and action,
|
|
@@ -53,7 +36,7 @@ export type PermissionLink = {
|
|
|
53
36
|
foreignKey?: string;
|
|
54
37
|
reverse?: boolean;
|
|
55
38
|
me?: boolean;
|
|
56
|
-
|
|
39
|
+
|
|
57
40
|
where?: any;
|
|
58
41
|
};
|
|
59
42
|
|
|
@@ -85,7 +68,7 @@ export const generatePermissions = (models: Models, config: PermissionsConfig) =
|
|
|
85
68
|
...('WHERE' in block && { where: block.WHERE }),
|
|
86
69
|
},
|
|
87
70
|
],
|
|
88
|
-
block
|
|
71
|
+
block,
|
|
89
72
|
);
|
|
90
73
|
}
|
|
91
74
|
permissions[role] = rolePermissions;
|
|
@@ -107,7 +90,7 @@ const addPermissions = (models: Models, permissions: RolePermissions, links: Per
|
|
|
107
90
|
permissions[type][action] = [];
|
|
108
91
|
}
|
|
109
92
|
if (permissions[type][action] !== true) {
|
|
110
|
-
|
|
93
|
+
permissions[type][action].push(links);
|
|
111
94
|
}
|
|
112
95
|
}
|
|
113
96
|
}
|
|
@@ -35,6 +35,7 @@ function getRawValue(value: ValueNode, values?: VariableValues): Value {
|
|
|
35
35
|
if (!values) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
+
|
|
38
39
|
return value.values.map((value) => getRawValue(value, values));
|
|
39
40
|
case Kind.VARIABLE:
|
|
40
41
|
return values?.[value.name.value];
|
|
@@ -56,6 +57,7 @@ function getRawValue(value: ValueNode, values?: VariableValues): Value {
|
|
|
56
57
|
for (const field of value.fields) {
|
|
57
58
|
res[field.name.value] = getRawValue(field.value, values);
|
|
58
59
|
}
|
|
60
|
+
|
|
59
61
|
return res;
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -69,7 +71,7 @@ export const normalizeArguments = (node: FieldResolverNode) => {
|
|
|
69
71
|
const normalizedValue = normalizeValue(
|
|
70
72
|
rawValue,
|
|
71
73
|
summonByKey(node.fieldDefinition.arguments || [], 'name.value', argument.name.value).type,
|
|
72
|
-
node.ctx.info.schema
|
|
74
|
+
node.ctx.info.schema,
|
|
73
75
|
);
|
|
74
76
|
if (normalizedValue === undefined) {
|
|
75
77
|
continue;
|
|
@@ -77,6 +79,7 @@ export const normalizeArguments = (node: FieldResolverNode) => {
|
|
|
77
79
|
normalizedArguments[argument.name.value] = normalizedValue as any;
|
|
78
80
|
}
|
|
79
81
|
}
|
|
82
|
+
|
|
80
83
|
return normalizedArguments;
|
|
81
84
|
};
|
|
82
85
|
|
|
@@ -88,6 +91,7 @@ export function normalizeValue(value: Value, type: TypeNode, schema: GraphQLSche
|
|
|
88
91
|
for (const v of value) {
|
|
89
92
|
res.push(normalizeValue(v, type.type, schema));
|
|
90
93
|
}
|
|
94
|
+
|
|
91
95
|
return res;
|
|
92
96
|
}
|
|
93
97
|
|
|
@@ -104,7 +108,7 @@ export function normalizeValue(value: Value, type: TypeNode, schema: GraphQLSche
|
|
|
104
108
|
return normalizeValueByTypeDefinition(
|
|
105
109
|
value,
|
|
106
110
|
(schema.getType(type.name.value) as Maybe<GraphQLObjectType>)?.astNode,
|
|
107
|
-
schema
|
|
111
|
+
schema,
|
|
108
112
|
);
|
|
109
113
|
}
|
|
110
114
|
}
|
|
@@ -125,5 +129,6 @@ export const normalizeValueByTypeDefinition = (value: Value, type: Maybe<TypeDef
|
|
|
125
129
|
}
|
|
126
130
|
res[key] = normalizedValue;
|
|
127
131
|
}
|
|
132
|
+
|
|
128
133
|
return res;
|
|
129
134
|
};
|
package/src/resolvers/filters.ts
CHANGED
|
@@ -33,12 +33,10 @@ export const applyFilters = (node: FieldResolverNode, query: Knex.QueryBuilder,
|
|
|
33
33
|
const { limit, offset, orderBy, where, search } = normalizedArguments;
|
|
34
34
|
|
|
35
35
|
if (limit) {
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
37
36
|
query.limit(limit);
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
if (offset) {
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
42
40
|
query.offset(offset);
|
|
43
41
|
}
|
|
44
42
|
|
|
@@ -108,8 +106,8 @@ const applyWhere = (node: WhereNode, where: Where | undefined, ops: QueryBuilder
|
|
|
108
106
|
ops.push((query) =>
|
|
109
107
|
ors(
|
|
110
108
|
query,
|
|
111
|
-
allSubOps.map((subOps) => (subQuery) => apply(subQuery, subOps))
|
|
112
|
-
)
|
|
109
|
+
allSubOps.map((subOps) => (subQuery) => apply(subQuery, subOps)),
|
|
110
|
+
),
|
|
113
111
|
);
|
|
114
112
|
continue;
|
|
115
113
|
}
|
|
@@ -145,7 +143,7 @@ const applyWhere = (node: WhereNode, where: Where | undefined, ops: QueryBuilder
|
|
|
145
143
|
]);
|
|
146
144
|
void apply(subQuery, subOps);
|
|
147
145
|
applyJoins(aliases, subQuery, subJoins);
|
|
148
|
-
})
|
|
146
|
+
}),
|
|
149
147
|
);
|
|
150
148
|
continue;
|
|
151
149
|
}
|
|
@@ -183,8 +181,8 @@ const applyWhere = (node: WhereNode, where: Where | undefined, ops: QueryBuilder
|
|
|
183
181
|
ops.push((query) =>
|
|
184
182
|
ors(
|
|
185
183
|
query,
|
|
186
|
-
value.map((v) => (subQuery) => subQuery.whereRaw('? = ANY(??)', [v, column] as string[]))
|
|
187
|
-
)
|
|
184
|
+
value.map((v) => (subQuery) => subQuery.whereRaw('? = ANY(??)', [v, column] as string[])),
|
|
185
|
+
),
|
|
188
186
|
);
|
|
189
187
|
continue;
|
|
190
188
|
}
|
|
@@ -195,7 +193,7 @@ const applyWhere = (node: WhereNode, where: Where | undefined, ops: QueryBuilder
|
|
|
195
193
|
ors(query, [
|
|
196
194
|
(subQuery) => subQuery.whereIn(column, value.filter((v) => v !== null) as string[]),
|
|
197
195
|
(subQuery) => subQuery.whereNull(column),
|
|
198
|
-
])
|
|
196
|
+
]),
|
|
199
197
|
);
|
|
200
198
|
continue;
|
|
201
199
|
}
|
|
@@ -220,8 +218,8 @@ const applySearch = (node: FieldResolverNode, search: string, query: Knex.QueryB
|
|
|
220
218
|
.map(
|
|
221
219
|
({ name }) =>
|
|
222
220
|
(query) =>
|
|
223
|
-
query.whereILike(getColumn(node, name), `%${search}%`)
|
|
224
|
-
)
|
|
221
|
+
query.whereILike(getColumn(node, name), `%${search}%`),
|
|
222
|
+
),
|
|
225
223
|
);
|
|
226
224
|
|
|
227
225
|
const applyOrderBy = (node: FieldResolverNode, orderBy: OrderBy, query: Knex.QueryBuilder) => {
|
|
@@ -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:
|
|
141
|
-
const toUnlink:
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
172
|
+
const mutationHook = ctx.mutationHook;
|
|
173
|
+
if (mutationHook) {
|
|
171
174
|
beforeHooks.push(async () => {
|
|
172
|
-
await
|
|
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 (
|
|
182
|
+
if (mutationHook) {
|
|
180
183
|
afterHooks.push(async () => {
|
|
181
|
-
await
|
|
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)
|
|
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)
|
|
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
|
|
386
|
+
const isEndOfDay = (field: EntityField) =>
|
|
384
387
|
isPrimitive(field) && field.type === 'DateTime' && field?.endOfDay === true && field?.dateTimeType === 'date';
|
package/src/resolvers/node.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
};
|
package/src/resolvers/selects.ts
CHANGED
|
@@ -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)) {
|